Merge "Remove FLAG_SPLIT_TOUCH from SystemUI" into main
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 93d751c..fbe5b94 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -11854,7 +11854,7 @@
// If segment limit is exceeded. All segments will be replaced
// with a single segment
boolean allSameColor = true;
- int firstSegmentColor = segments.get(0).getColor();
+ int firstSegmentColor = segments.getFirst().getColor();
for (int i = 1; i < segments.size(); i++) {
if (segments.get(i).getColor() != firstSegmentColor) {
@@ -11887,8 +11887,31 @@
}
}
+ // If the segments and points can't all fit inside the progress drawable, the
+ // view will replace all segments with a single segment.
+ final int segmentsFallbackColor;
+ if (segments.size() <= 1) {
+ segmentsFallbackColor = NotificationProgressModel.INVALID_COLOR;
+ } else {
+
+ boolean allSameColor = true;
+ int firstSegmentColor = segments.getFirst().getColor();
+ for (int i = 1; i < segments.size(); i++) {
+ if (segments.get(i).getColor() != firstSegmentColor) {
+ allSameColor = false;
+ break;
+ }
+ }
+ // If the segments are of the same color, the view can just use that color.
+ // In that case there is no need to send the fallback color.
+ segmentsFallbackColor = allSameColor ? NotificationProgressModel.INVALID_COLOR
+ : sanitizeProgressColor(Notification.COLOR_DEFAULT, backgroundColor,
+ defaultProgressColor);
+ }
+
model = new NotificationProgressModel(segments, points,
- Math.clamp(mProgress, 0, totalLength), mIsStyledByProgress);
+ Math.clamp(mProgress, 0, totalLength), mIsStyledByProgress,
+ segmentsFallbackColor);
}
return model;
}
diff --git a/core/java/android/app/TaskInfo.java b/core/java/android/app/TaskInfo.java
index 76705dc..6936ddc 100644
--- a/core/java/android/app/TaskInfo.java
+++ b/core/java/android/app/TaskInfo.java
@@ -376,6 +376,14 @@
}
/**
+ * Returns the task id.
+ * @hide
+ */
+ public int getTaskId() {
+ return taskId;
+ }
+
+ /**
* Whether this task is visible.
*/
public boolean isVisible() {
diff --git a/core/java/android/appwidget/AppWidgetManager.java b/core/java/android/appwidget/AppWidgetManager.java
index 67ad459..b54e17b 100644
--- a/core/java/android/appwidget/AppWidgetManager.java
+++ b/core/java/android/appwidget/AppWidgetManager.java
@@ -1684,10 +1684,14 @@
private IBinder mIBinder;
ConnectionTask(@NonNull FilterComparison filter) {
- mContext.bindService(filter.getIntent(),
- Context.BindServiceFlags.of(Context.BIND_AUTO_CREATE),
- mHandler::post,
- this);
+ try {
+ mContext.bindService(filter.getIntent(),
+ Context.BindServiceFlags.of(Context.BIND_AUTO_CREATE),
+ mHandler::post,
+ this);
+ } catch (Exception e) {
+ Log.e(TAG, "Error connecting to service in connection cache", e);
+ }
}
@Override
@@ -1737,7 +1741,11 @@
handleNext();
return;
}
- mContext.unbindService(this);
+ try {
+ mContext.unbindService(this);
+ } catch (Exception e) {
+ Log.e(TAG, "Error unbinding the cached connection", e);
+ }
mActiveConnections.values().remove(this);
}
}
diff --git a/core/java/android/content/pm/RegisteredServicesCache.java b/core/java/android/content/pm/RegisteredServicesCache.java
index 9d11710..8266384 100644
--- a/core/java/android/content/pm/RegisteredServicesCache.java
+++ b/core/java/android/content/pm/RegisteredServicesCache.java
@@ -85,6 +85,8 @@
private static final boolean DEBUG = false;
protected static final String REGISTERED_SERVICES_DIR = "registered_services";
+ static final long SERVICE_INFO_CACHES_TIMEOUT_MILLIS = 30000; // 30 seconds
+
public final Context mContext;
private final String mInterfaceName;
private final String mMetaDataName;
@@ -96,8 +98,18 @@
@GuardedBy("mServicesLock")
private final SparseArray<UserServices<V>> mUserServices = new SparseArray<UserServices<V>>(2);
- @GuardedBy("mServicesLock")
- private final ArrayMap<String, ServiceInfo<V>> mServiceInfoCaches = new ArrayMap<>();
+ @GuardedBy("mServiceInfoCaches")
+ private final ArrayMap<ComponentName, ServiceInfo<V>> mServiceInfoCaches = new ArrayMap<>();
+
+ private final Handler mBackgroundHandler;
+
+ private final Runnable mClearServiceInfoCachesRunnable = new Runnable() {
+ public void run() {
+ synchronized (mServiceInfoCaches) {
+ mServiceInfoCaches.clear();
+ }
+ }
+ };
private static class UserServices<V> {
@GuardedBy("mServicesLock")
@@ -172,9 +184,9 @@
if (isCore) {
intentFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
}
- Handler handler = BackgroundThread.getHandler();
+ mBackgroundHandler = BackgroundThread.getHandler();
mContext.registerReceiverAsUser(
- mPackageReceiver, UserHandle.ALL, intentFilter, null, handler);
+ mPackageReceiver, UserHandle.ALL, intentFilter, null, mBackgroundHandler);
// Register for events related to sdcard installation.
IntentFilter sdFilter = new IntentFilter();
@@ -183,7 +195,7 @@
if (isCore) {
sdFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
}
- mContext.registerReceiver(mExternalReceiver, sdFilter, null, handler);
+ mContext.registerReceiver(mExternalReceiver, sdFilter, null, mBackgroundHandler);
// Register for user-related events
IntentFilter userFilter = new IntentFilter();
@@ -191,7 +203,7 @@
if (isCore) {
userFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
}
- mContext.registerReceiver(mUserRemovedReceiver, userFilter, null, handler);
+ mContext.registerReceiver(mUserRemovedReceiver, userFilter, null, mBackgroundHandler);
}
private void handlePackageEvent(Intent intent, int userId) {
@@ -328,6 +340,10 @@
public final ComponentName componentName;
@UnsupportedAppUsage
public final int uid;
+ /**
+ * The last update time of the package that contains the service.
+ * It's from {@link PackageInfo#lastUpdateTime}.
+ */
public final long lastUpdateTime;
/** @hide */
@@ -342,7 +358,8 @@
@Override
public String toString() {
- return "ServiceInfo: " + type + ", " + componentName + ", uid " + uid;
+ return "ServiceInfo: " + type + ", " + componentName + ", uid " + uid
+ + ", lastUpdateTime " + lastUpdateTime;
}
}
@@ -496,19 +513,56 @@
final ArrayList<ServiceInfo<V>> serviceInfos = new ArrayList<>();
final List<ResolveInfo> resolveInfos = queryIntentServices(userId);
+ final PackageManager pm = mContext.getPackageManager();
for (ResolveInfo resolveInfo : resolveInfos) {
+ // Check if the service has been in the service cache.
+ long lastUpdateTime = -1;
+ final android.content.pm.ServiceInfo si = resolveInfo.serviceInfo;
+ final ComponentName componentName = si.getComponentName();
+ if (Flags.optimizeParsingInRegisteredServicesCache()) {
+ try {
+ PackageInfo packageInfo = pm.getPackageInfoAsUser(si.packageName,
+ PackageManager.MATCH_DIRECT_BOOT_AWARE
+ | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, userId);
+ lastUpdateTime = packageInfo.lastUpdateTime;
+ } catch (NameNotFoundException | SecurityException e) {
+ Slog.d(TAG, "Fail to get the PackageInfo in generateServicesMap: " + e);
+ continue;
+ }
+ ServiceInfo<V> serviceInfo = getServiceInfoFromServiceCache(componentName,
+ lastUpdateTime);
+ if (serviceInfo != null) {
+ serviceInfos.add(serviceInfo);
+ continue;
+ }
+ }
try {
- ServiceInfo<V> info = parseServiceInfo(resolveInfo, userId);
+ ServiceInfo<V> info = parseServiceInfo(resolveInfo, lastUpdateTime);
if (info == null) {
Log.w(TAG, "Unable to load service info " + resolveInfo.toString());
continue;
}
serviceInfos.add(info);
+ if (Flags.optimizeParsingInRegisteredServicesCache()) {
+ synchronized (mServiceInfoCaches) {
+ mServiceInfoCaches.put(componentName, info);
+ }
+ }
} catch (XmlPullParserException | IOException e) {
Log.w(TAG, "Unable to load service info " + resolveInfo.toString(), e);
}
}
+ if (Flags.optimizeParsingInRegisteredServicesCache()) {
+ synchronized (mServiceInfoCaches) {
+ if (!mServiceInfoCaches.isEmpty()) {
+ mBackgroundHandler.removeCallbacks(mClearServiceInfoCachesRunnable);
+ mBackgroundHandler.postDelayed(mClearServiceInfoCachesRunnable,
+ SERVICE_INFO_CACHES_TIMEOUT_MILLIS);
+ }
+ }
+ }
+
synchronized (mServicesLock) {
final UserServices<V> user = findOrCreateUserLocked(userId);
final boolean firstScan = user.services == null;
@@ -645,32 +699,18 @@
return false;
}
+ /**
+ * If the service has already existed in the caches, this method will not be called to parse
+ * the service.
+ */
@VisibleForTesting
- protected ServiceInfo<V> parseServiceInfo(ResolveInfo service, int userId)
+ protected ServiceInfo<V> parseServiceInfo(ResolveInfo service, long lastUpdateTime)
throws XmlPullParserException, IOException {
android.content.pm.ServiceInfo si = service.serviceInfo;
ComponentName componentName = new ComponentName(si.packageName, si.name);
PackageManager pm = mContext.getPackageManager();
- // Check if the service has been in the service cache.
- long lastUpdateTime = -1;
- if (Flags.optimizeParsingInRegisteredServicesCache()) {
- try {
- PackageInfo packageInfo = pm.getPackageInfoAsUser(si.packageName,
- PackageManager.MATCH_DIRECT_BOOT_AWARE
- | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, userId);
- lastUpdateTime = packageInfo.lastUpdateTime;
-
- ServiceInfo<V> serviceInfo = getServiceInfoFromServiceCache(si, lastUpdateTime);
- if (serviceInfo != null) {
- return serviceInfo;
- }
- } catch (NameNotFoundException | SecurityException e) {
- Slog.d(TAG, "Fail to get the PackageInfo in parseServiceInfo: " + e);
- }
- }
-
XmlResourceParser parser = null;
try {
parser = si.loadXmlMetaData(pm, mMetaDataName);
@@ -696,13 +736,7 @@
if (v == null) {
return null;
}
- ServiceInfo<V> serviceInfo = new ServiceInfo<V>(v, si, componentName, lastUpdateTime);
- if (Flags.optimizeParsingInRegisteredServicesCache()) {
- synchronized (mServicesLock) {
- mServiceInfoCaches.put(getServiceCacheKey(si), serviceInfo);
- }
- }
- return serviceInfo;
+ return new ServiceInfo<V>(v, si, componentName, lastUpdateTime);
} catch (NameNotFoundException e) {
throw new XmlPullParserException(
"Unable to load resources for pacakge " + si.packageName);
@@ -873,26 +907,13 @@
mContext.unregisterReceiver(mUserRemovedReceiver);
}
- private static String getServiceCacheKey(@NonNull android.content.pm.ServiceInfo serviceInfo) {
- StringBuilder sb = new StringBuilder(serviceInfo.packageName);
- sb.append('-');
- sb.append(serviceInfo.name);
- return sb.toString();
- }
-
- private ServiceInfo<V> getServiceInfoFromServiceCache(
- @NonNull android.content.pm.ServiceInfo serviceInfo, long lastUpdateTime) {
- String serviceCacheKey = getServiceCacheKey(serviceInfo);
- synchronized (mServicesLock) {
- ServiceInfo<V> serviceCache = mServiceInfoCaches.get(serviceCacheKey);
- if (serviceCache == null) {
- return null;
- }
- if (serviceCache.lastUpdateTime == lastUpdateTime) {
+ private ServiceInfo<V> getServiceInfoFromServiceCache(@NonNull ComponentName componentName,
+ long lastUpdateTime) {
+ synchronized (mServiceInfoCaches) {
+ ServiceInfo<V> serviceCache = mServiceInfoCaches.get(componentName);
+ if (serviceCache != null && serviceCache.lastUpdateTime == lastUpdateTime) {
return serviceCache;
}
- // The service is not latest, remove it from the cache.
- mServiceInfoCaches.remove(serviceCacheKey);
return null;
}
}
diff --git a/core/java/android/hardware/biometrics/IBiometricEnabledOnKeyguardCallback.aidl b/core/java/android/hardware/biometrics/IBiometricEnabledOnKeyguardCallback.aidl
index 1268658..cf25b7e 100644
--- a/core/java/android/hardware/biometrics/IBiometricEnabledOnKeyguardCallback.aidl
+++ b/core/java/android/hardware/biometrics/IBiometricEnabledOnKeyguardCallback.aidl
@@ -20,5 +20,5 @@
* @hide
*/
oneway interface IBiometricEnabledOnKeyguardCallback {
- void onChanged(boolean enabled, int userId);
+ void onChanged(boolean enabled, int userId, int modality);
}
\ No newline at end of file
diff --git a/core/java/android/hardware/input/InputSettings.java b/core/java/android/hardware/input/InputSettings.java
index b380e25..cd48047 100644
--- a/core/java/android/hardware/input/InputSettings.java
+++ b/core/java/android/hardware/input/InputSettings.java
@@ -385,6 +385,42 @@
}
/**
+ * Whether touchpad acceleration is enabled or not.
+ *
+ * @param context The application context.
+ *
+ * @hide
+ */
+ public static boolean isTouchpadAccelerationEnabled(@NonNull Context context) {
+ if (!isPointerAccelerationFeatureFlagEnabled()) {
+ return false;
+ }
+
+ return Settings.System.getIntForUser(context.getContentResolver(),
+ Settings.System.TOUCHPAD_ACCELERATION_ENABLED, 1, UserHandle.USER_CURRENT)
+ == 1;
+ }
+
+ /**
+ * Enables or disables touchpad acceleration.
+ *
+ * @param context The application context.
+ * @param enabled Will enable touchpad acceleration if true, disable it if
+ * false.
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.WRITE_SETTINGS)
+ public static void setTouchpadAccelerationEnabled(@NonNull Context context,
+ boolean enabled) {
+ if (!isPointerAccelerationFeatureFlagEnabled()) {
+ return;
+ }
+ Settings.System.putIntForUser(context.getContentResolver(),
+ Settings.System.TOUCHPAD_ACCELERATION_ENABLED, enabled ? 1 : 0,
+ UserHandle.USER_CURRENT);
+ }
+
+ /**
* Returns true if the feature flag for disabling system gestures on touchpads is enabled.
*
* @hide
@@ -835,7 +871,6 @@
UserHandle.USER_CURRENT);
}
-
/**
* Whether Accessibility bounce keys feature is enabled.
*
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 9626808..2e231e3 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -6330,6 +6330,17 @@
public static final String TOUCHPAD_SYSTEM_GESTURES = "touchpad_system_gestures";
/**
+ * Whether touchpad acceleration is enabled.
+ *
+ * When enabled, the speed of the pointer will increase as the user moves their
+ * finger faster on the touchpad.
+ *
+ * @hide
+ */
+ public static final String TOUCHPAD_ACCELERATION_ENABLED =
+ "touchpad_acceleration_enabled";
+
+ /**
* Whether to enable reversed vertical scrolling for connected mice.
*
* When enabled, scrolling down on the mouse wheel will move the screen up and vice versa.
@@ -6624,6 +6635,7 @@
PRIVATE_SETTINGS.add(TOUCHPAD_TAP_DRAGGING);
PRIVATE_SETTINGS.add(TOUCHPAD_RIGHT_CLICK_ZONE);
PRIVATE_SETTINGS.add(TOUCHPAD_SYSTEM_GESTURES);
+ PRIVATE_SETTINGS.add(TOUCHPAD_ACCELERATION_ENABLED);
PRIVATE_SETTINGS.add(CAMERA_FLASH_NOTIFICATION);
PRIVATE_SETTINGS.add(SCREEN_FLASH_NOTIFICATION);
PRIVATE_SETTINGS.add(SCREEN_FLASH_NOTIFICATION_COLOR);
@@ -11368,17 +11380,54 @@
/**
* Whether or not biometric is allowed on Keyguard.
+ *
+ * @deprecated Use {@link #FINGERPRINT_KEYGUARD_ENABLED} or {@link #FACE_KEYGUARD_ENABLED}
+ * instead.
+ *
* @hide
*/
+ @Deprecated
@Readable
public static final String BIOMETRIC_KEYGUARD_ENABLED = "biometric_keyguard_enabled";
/**
* Whether or not biometric is allowed for apps (through BiometricPrompt).
+ *
+ * @deprecated Use {@link #FINGERPRINT_APP_ENABLED} or {@link #FACE_APP_ENABLED} instead.
+ *
+ * @hide
+ */
+ @Deprecated
+ @Readable
+ public static final String BIOMETRIC_APP_ENABLED = "biometric_app_enabled";
+
+ /**
+ * Whether or not fingerprint is allowed on Keyguard.
* @hide
*/
@Readable
- public static final String BIOMETRIC_APP_ENABLED = "biometric_app_enabled";
+ public static final String FINGERPRINT_KEYGUARD_ENABLED = "fingerprint_keyguard_enabled";
+
+ /**
+ * Whether or not fingerprint is allowed for apps (through BiometricPrompt).
+ * @hide
+ */
+ @Readable
+ public static final String FINGERPRINT_APP_ENABLED = "fingerptint_app_enabled";
+
+ /**
+ * Whether or not face is allowed on Keyguard.
+ * @hide
+ */
+ @Readable
+ public static final String FACE_KEYGUARD_ENABLED = "face_keyguard_enabled";
+
+ /**
+ * Whether or not face is allowed for apps (through BiometricPrompt).
+ * @hide
+ */
+ @Readable
+ public static final String FACE_APP_ENABLED = "face_app_enabled";
/**
* Whether or not mandatory biometrics is enabled.
diff --git a/core/java/com/android/internal/widget/NotificationProgressBar.java b/core/java/com/android/internal/widget/NotificationProgressBar.java
index 0d3c470..973fd7e 100644
--- a/core/java/com/android/internal/widget/NotificationProgressBar.java
+++ b/core/java/com/android/internal/widget/NotificationProgressBar.java
@@ -47,6 +47,7 @@
import com.android.internal.widget.NotificationProgressDrawable.DrawableSegment;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -161,7 +162,7 @@
final int progress = mProgressModel.getProgress();
final int progressMax = mProgressModel.getProgressMax();
- mParts = processAndConvertToViewParts(mProgressModel.getSegments(),
+ mParts = processModelAndConvertToViewParts(mProgressModel.getSegments(),
mProgressModel.getPoints(),
progress,
progressMax);
@@ -439,23 +440,107 @@
return;
}
- mProgressDrawableParts = processAndConvertToDrawableParts(
+ final float segSegGap = mNotificationProgressDrawable.getSegSegGap();
+ final float segPointGap = mNotificationProgressDrawable.getSegPointGap();
+ final float pointRadius = mNotificationProgressDrawable.getPointRadius();
+ mProgressDrawableParts = processPartsAndConvertToDrawableParts(
mParts,
width,
- mNotificationProgressDrawable.getSegSegGap(),
- mNotificationProgressDrawable.getSegPointGap(),
- mNotificationProgressDrawable.getPointRadius(),
+ segSegGap,
+ segPointGap,
+ pointRadius,
mHasTrackerIcon
);
- Pair<List<DrawablePart>, Float> p = maybeStretchAndRescaleSegments(
- mParts,
- mProgressDrawableParts,
- mNotificationProgressDrawable.getSegmentMinWidth(),
- mNotificationProgressDrawable.getPointRadius(),
- getProgressFraction(),
- width,
- mProgressModel.isStyledByProgress(),
- mHasTrackerIcon ? 0F : mNotificationProgressDrawable.getSegSegGap());
+
+ final float segmentMinWidth = mNotificationProgressDrawable.getSegmentMinWidth();
+ final float progressFraction = getProgressFraction();
+ final boolean isStyledByProgress = mProgressModel.isStyledByProgress();
+ final float progressGap =
+ mHasTrackerIcon ? 0F : mNotificationProgressDrawable.getSegSegGap();
+ Pair<List<DrawablePart>, Float> p = null;
+ try {
+ p = maybeStretchAndRescaleSegments(
+ mParts,
+ mProgressDrawableParts,
+ segmentMinWidth,
+ pointRadius,
+ progressFraction,
+ width,
+ isStyledByProgress,
+ progressGap
+ );
+ } catch (NotEnoughWidthToFitAllPartsException ex) {
+ Log.w(TAG, "Failed to stretch and rescale segments", ex);
+ }
+
+ List<ProgressStyle.Segment> fallbackSegments = null;
+ if (p == null && mProgressModel.getSegments().size() > 1) {
+ Log.w(TAG, "Falling back to single segment");
+ try {
+ fallbackSegments = List.of(new ProgressStyle.Segment(getMax()).setColor(
+ mProgressModel.getSegmentsFallbackColor()
+ == NotificationProgressModel.INVALID_COLOR
+ ? mProgressModel.getSegments().getFirst().getColor()
+ : mProgressModel.getSegmentsFallbackColor()));
+ p = processModelAndConvertToFinalDrawableParts(
+ fallbackSegments,
+ mProgressModel.getPoints(),
+ mProgressModel.getProgress(),
+ getMax(),
+ width,
+ segSegGap,
+ segPointGap,
+ pointRadius,
+ mHasTrackerIcon,
+ segmentMinWidth,
+ isStyledByProgress
+ );
+ } catch (NotEnoughWidthToFitAllPartsException ex) {
+ Log.w(TAG, "Failed to stretch and rescale segments with single segment fallback",
+ ex);
+ }
+ }
+
+ if (p == null && !mProgressModel.getPoints().isEmpty()) {
+ Log.w(TAG, "Falling back to single segment and no points");
+ if (fallbackSegments == null) {
+ fallbackSegments = List.of(new ProgressStyle.Segment(getMax()).setColor(
+ mProgressModel.getSegmentsFallbackColor()
+ == NotificationProgressModel.INVALID_COLOR
+ ? mProgressModel.getSegments().getFirst().getColor()
+ : mProgressModel.getSegmentsFallbackColor()));
+ }
+ try {
+ p = processModelAndConvertToFinalDrawableParts(
+ fallbackSegments,
+ Collections.emptyList(),
+ mProgressModel.getProgress(),
+ getMax(),
+ width,
+ segSegGap,
+ segPointGap,
+ pointRadius,
+ mHasTrackerIcon,
+ segmentMinWidth,
+ isStyledByProgress
+ );
+ } catch (NotEnoughWidthToFitAllPartsException ex) {
+ Log.w(TAG,
+ "Failed to stretch and rescale segments with single segments and no points",
+ ex);
+ }
+ }
+
+ if (p == null) {
+ Log.w(TAG, "Falling back to no stretching and rescaling");
+ p = maybeSplitDrawableSegmentsByProgress(
+ mParts,
+ mProgressDrawableParts,
+ progressFraction,
+ width,
+ isStyledByProgress,
+ progressGap);
+ }
if (DEBUG) {
Log.d(TAG, "Updating NotificationProgressDrawable parts");
@@ -502,7 +587,11 @@
int min = getMin();
int max = getMax();
int range = max - min;
- return range > 0 ? (getProgress() - min) / (float) range : 0;
+ return getProgressFraction(range, (getProgress() - min));
+ }
+
+ private static float getProgressFraction(int progressMax, int progress) {
+ return progressMax > 0 ? progress / (float) progressMax : 0;
}
/**
@@ -636,7 +725,7 @@
* Processes the ProgressStyle data and convert to a list of {@code Part}.
*/
@VisibleForTesting
- public static List<Part> processAndConvertToViewParts(
+ public static List<Part> processModelAndConvertToViewParts(
List<ProgressStyle.Segment> segments,
List<ProgressStyle.Point> points,
int progress,
@@ -796,7 +885,7 @@
* Processes the list of {@code Part} and convert to a list of {@code DrawablePart}.
*/
@VisibleForTesting
- public static List<DrawablePart> processAndConvertToDrawableParts(
+ public static List<DrawablePart> processPartsAndConvertToDrawableParts(
List<Part> parts,
float totalWidth,
float segSegGap,
@@ -823,7 +912,7 @@
// Retract the end position to account for the padding and a point immediately
// after.
final float endOffset = getSegEndOffset(segment, nextPart, pointRadius, segPointGap,
- segSegGap, iPart == nParts - 2, totalWidth, hasTrackerIcon);
+ segSegGap, iPart == nParts - 2, hasTrackerIcon);
final float end = x + segWidth - endOffset;
drawableParts.add(new DrawableSegment(start, end, segment.mColor, segment.mFaded));
@@ -864,7 +953,7 @@
}
private static float getSegEndOffset(Segment seg, Part nextPart, float pointRadius,
- float segPointGap, float segSegGap, boolean isSecondToLastPart, float totalWidth,
+ float segPointGap, float segSegGap, boolean isSecondToLastPart,
boolean hasTrackerIcon) {
if (nextPart == null) return 0F;
if (nextPart instanceof Segment nextSeg) {
@@ -894,7 +983,7 @@
float totalWidth,
boolean isStyledByProgress,
float progressGap
- ) {
+ ) throws NotEnoughWidthToFitAllPartsException {
final List<DrawableSegment> drawableSegments = drawableParts
.stream()
.filter(DrawableSegment.class::isInstance)
@@ -920,16 +1009,8 @@
}
if (totalExcessWidth < 0) {
- // TODO: b/372908709 - throw an error so that the caller can catch and go to fallback
- // option. (instead of return.)
- Log.w(TAG, "Not enough width to satisfy the minimum width for segments.");
- return maybeSplitDrawableSegmentsByProgress(
- parts,
- drawableParts,
- progressFraction,
- totalWidth,
- isStyledByProgress,
- progressGap);
+ throw new NotEnoughWidthToFitAllPartsException(
+ "Not enough width to satisfy the minimum width for segments.");
}
final int nParts = drawableParts.size();
@@ -1003,8 +1084,7 @@
final int nParts = parts.size();
for (int iPart = 0; iPart < nParts; iPart++) {
final Part part = parts.get(iPart);
- if (!(part instanceof Segment)) continue;
- final Segment segment = (Segment) part;
+ if (!(part instanceof Segment segment)) continue;
if (startFraction == progressFraction) {
iPartFirstSegmentToStyle = iPart;
rescaledProgressX = segment.mStart;
@@ -1066,11 +1146,37 @@
}
/**
+ * Processes the ProgressStyle data and convert to a pair of:
+ * - list of processed {@code DrawablePart}.
+ * - location of progress on the stretched and rescaled progress bar.
+ */
+ @VisibleForTesting
+ public static Pair<List<DrawablePart>, Float> processModelAndConvertToFinalDrawableParts(
+ List<ProgressStyle.Segment> segments,
+ List<ProgressStyle.Point> points,
+ int progress,
+ int progressMax,
+ float totalWidth,
+ float segSegGap,
+ float segPointGap,
+ float pointRadius,
+ boolean hasTrackerIcon,
+ float segmentMinWidth,
+ boolean isStyledByProgress
+ ) throws NotEnoughWidthToFitAllPartsException {
+ List<Part> parts = processModelAndConvertToViewParts(segments, points, progress,
+ progressMax);
+ List<DrawablePart> drawableParts = processPartsAndConvertToDrawableParts(parts, totalWidth,
+ segSegGap, segPointGap, pointRadius, hasTrackerIcon);
+ return maybeStretchAndRescaleSegments(parts, drawableParts, segmentMinWidth, pointRadius,
+ getProgressFraction(progressMax, progress), totalWidth, isStyledByProgress,
+ hasTrackerIcon ? 0F : segSegGap);
+ }
+
+ /**
* A part of the progress bar, which is either a {@link Segment} with non-zero length, or a
* {@link Point} with zero length.
*/
- // TODO: b/372908709 - maybe this should be made private? Only test the final
- // NotificationDrawable.Parts.
public interface Part {
}
@@ -1176,4 +1282,10 @@
return Objects.hash(mColor);
}
}
+
+ public static class NotEnoughWidthToFitAllPartsException extends Exception {
+ public NotEnoughWidthToFitAllPartsException(String message) {
+ super(message);
+ }
+ }
}
diff --git a/core/java/com/android/internal/widget/NotificationProgressModel.java b/core/java/com/android/internal/widget/NotificationProgressModel.java
index e8cb37e..7eaf861 100644
--- a/core/java/com/android/internal/widget/NotificationProgressModel.java
+++ b/core/java/com/android/internal/widget/NotificationProgressModel.java
@@ -16,7 +16,6 @@
package com.android.internal.widget;
-
import android.annotation.ColorInt;
import android.annotation.FlaggedApi;
import android.annotation.NonNull;
@@ -45,24 +44,29 @@
*/
@FlaggedApi(Flags.FLAG_API_RICH_ONGOING)
public final class NotificationProgressModel {
- private static final int INVALID_INDETERMINATE_COLOR = Color.TRANSPARENT;
+ public static final int INVALID_COLOR = Color.TRANSPARENT;
private static final String KEY_SEGMENTS = "segments";
private static final String KEY_POINTS = "points";
private static final String KEY_PROGRESS = "progress";
private static final String KEY_IS_STYLED_BY_PROGRESS = "isStyledByProgress";
+ private static final String KEY_SEGMENTS_FALLBACK_COLOR = "segmentsFallColor";
private static final String KEY_INDETERMINATE_COLOR = "indeterminateColor";
private final List<Segment> mSegments;
private final List<Point> mPoints;
private final int mProgress;
private final boolean mIsStyledByProgress;
@ColorInt
+ private final int mSegmentsFallbackColor;
+
+ @ColorInt
private final int mIndeterminateColor;
public NotificationProgressModel(
@NonNull List<Segment> segments,
@NonNull List<Point> points,
int progress,
- boolean isStyledByProgress
+ boolean isStyledByProgress,
+ @ColorInt int segmentsFallbackColor
) {
Preconditions.checkArgument(progress >= 0);
Preconditions.checkArgument(!segments.isEmpty());
@@ -70,17 +74,19 @@
mPoints = points;
mProgress = progress;
mIsStyledByProgress = isStyledByProgress;
- mIndeterminateColor = INVALID_INDETERMINATE_COLOR;
+ mSegmentsFallbackColor = segmentsFallbackColor;
+ mIndeterminateColor = INVALID_COLOR;
}
public NotificationProgressModel(
@ColorInt int indeterminateColor
) {
- Preconditions.checkArgument(indeterminateColor != INVALID_INDETERMINATE_COLOR);
+ Preconditions.checkArgument(indeterminateColor != INVALID_COLOR);
mSegments = Collections.emptyList();
mPoints = Collections.emptyList();
mProgress = 0;
mIsStyledByProgress = false;
+ mSegmentsFallbackColor = INVALID_COLOR;
mIndeterminateColor = indeterminateColor;
}
@@ -105,12 +111,17 @@
}
@ColorInt
+ public int getSegmentsFallbackColor() {
+ return mSegmentsFallbackColor;
+ }
+
+ @ColorInt
public int getIndeterminateColor() {
return mIndeterminateColor;
}
public boolean isIndeterminate() {
- return mIndeterminateColor != INVALID_INDETERMINATE_COLOR;
+ return mIndeterminateColor != INVALID_COLOR;
}
/**
@@ -119,7 +130,7 @@
@NonNull
public Bundle toBundle() {
final Bundle bundle = new Bundle();
- if (mIndeterminateColor != INVALID_INDETERMINATE_COLOR) {
+ if (mIndeterminateColor != INVALID_COLOR) {
bundle.putInt(KEY_INDETERMINATE_COLOR, mIndeterminateColor);
} else {
bundle.putParcelableList(KEY_SEGMENTS,
@@ -128,6 +139,9 @@
Notification.ProgressStyle.getProgressPointsAsBundleList(mPoints));
bundle.putInt(KEY_PROGRESS, mProgress);
bundle.putBoolean(KEY_IS_STYLED_BY_PROGRESS, mIsStyledByProgress);
+ if (mSegmentsFallbackColor != INVALID_COLOR) {
+ bundle.putInt(KEY_SEGMENTS_FALLBACK_COLOR, mSegmentsFallbackColor);
+ }
}
return bundle;
}
@@ -138,8 +152,8 @@
@NonNull
public static NotificationProgressModel fromBundle(@NonNull Bundle bundle) {
final int indeterminateColor = bundle.getInt(KEY_INDETERMINATE_COLOR,
- INVALID_INDETERMINATE_COLOR);
- if (indeterminateColor != INVALID_INDETERMINATE_COLOR) {
+ INVALID_COLOR);
+ if (indeterminateColor != INVALID_COLOR) {
return new NotificationProgressModel(indeterminateColor);
} else {
final List<Segment> segments =
@@ -150,7 +164,10 @@
bundle.getParcelableArrayList(KEY_POINTS, Bundle.class));
final int progress = bundle.getInt(KEY_PROGRESS);
final boolean isStyledByProgress = bundle.getBoolean(KEY_IS_STYLED_BY_PROGRESS);
- return new NotificationProgressModel(segments, points, progress, isStyledByProgress);
+ final int segmentsFallbackColor = bundle.getInt(KEY_SEGMENTS_FALLBACK_COLOR,
+ INVALID_COLOR);
+ return new NotificationProgressModel(segments, points, progress, isStyledByProgress,
+ segmentsFallbackColor);
}
}
@@ -161,6 +178,7 @@
+ ", mPoints=" + mPoints
+ ", mProgress=" + mProgress
+ ", mIsStyledByProgress=" + mIsStyledByProgress
+ + ", mSegmentsFallbackColor=" + mSegmentsFallbackColor
+ ", mIndeterminateColor=" + mIndeterminateColor + "}";
}
@@ -171,6 +189,7 @@
final NotificationProgressModel that = (NotificationProgressModel) o;
return mProgress == that.mProgress
&& mIsStyledByProgress == that.mIsStyledByProgress
+ && mSegmentsFallbackColor == that.mSegmentsFallbackColor
&& mIndeterminateColor == that.mIndeterminateColor
&& Objects.equals(mSegments, that.mSegments)
&& Objects.equals(mPoints, that.mPoints);
@@ -182,6 +201,7 @@
mPoints,
mProgress,
mIsStyledByProgress,
+ mSegmentsFallbackColor,
mIndeterminateColor);
}
}
diff --git a/core/proto/android/providers/settings/system.proto b/core/proto/android/providers/settings/system.proto
index 64c9f54..325790c 100644
--- a/core/proto/android/providers/settings/system.proto
+++ b/core/proto/android/providers/settings/system.proto
@@ -218,7 +218,8 @@
optional SettingProto tap_to_click = 4 [ (android.privacy).dest = DEST_AUTOMATIC ];
optional SettingProto tap_dragging = 5 [ (android.privacy).dest = DEST_AUTOMATIC ];
optional SettingProto three_finger_tap_customization = 6 [ (android.privacy).dest = DEST_AUTOMATIC ];
- optional SettingProto system_gestures = 7 [ (android.privacy).dest = DEST_AUTOMATIC ];;
+ optional SettingProto system_gestures = 7 [ (android.privacy).dest = DEST_AUTOMATIC ];
+ optional SettingProto acceleration_enabled = 8 [ (android.privacy).dest = DEST_AUTOMATIC ];;
}
optional Touchpad touchpad = 36;
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index bd1d303..a1f85c3 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -7585,7 +7585,7 @@
<!-- Used to config the segments of a NotificationProgressDrawable. -->
<!-- @hide internal use only -->
<declare-styleable name="NotificationProgressDrawableSegments">
- <!-- TODO: b/372908709 - maybe move this to NotificationProgressBar, because that's the only
+ <!-- TODO: b/390196782 - maybe move this to NotificationProgressBar, because that's the only
place this is used actually. Same for NotificationProgressDrawable.segSegGap/segPointGap
above. -->
<!-- Minimum required drawing width. The drawing width refers to the width after
diff --git a/core/tests/coretests/src/android/content/pm/RegisteredServicesCacheTest.java b/core/tests/coretests/src/android/content/pm/RegisteredServicesCacheTest.java
index 939bf2e..ee4761b 100644
--- a/core/tests/coretests/src/android/content/pm/RegisteredServicesCacheTest.java
+++ b/core/tests/coretests/src/android/content/pm/RegisteredServicesCacheTest.java
@@ -76,6 +76,10 @@
mUsers = new ArrayList<>();
mUsers.add(new UserInfo(0, "Owner", UserInfo.FLAG_ADMIN));
mUsers.add(new UserInfo(1, "User1", 0));
+ addServiceInfoIntoResolveInfo(r1, "r1.package.name" /* packageName */,
+ "r1.service.name" /* serviceName */);
+ addServiceInfoIntoResolveInfo(r2, "r2.package.name" /* packageName */,
+ "r2.service.name" /* serviceName */);
}
public void testGetAllServicesHappyPath() {
@@ -218,6 +222,14 @@
assertTrue("File should be created at " + file, file.length() > 0);
}
+ private void addServiceInfoIntoResolveInfo(ResolveInfo resolveInfo, String packageName,
+ String serviceName) {
+ final ServiceInfo serviceInfo = new ServiceInfo();
+ serviceInfo.packageName = packageName;
+ serviceInfo.name = serviceName;
+ resolveInfo.serviceInfo = serviceInfo;
+ }
+
/**
* Mock implementation of {@link android.content.pm.RegisteredServicesCache} for testing
*/
@@ -301,8 +313,8 @@
}
@Override
- protected ServiceInfo<TestServiceType> parseServiceInfo(
- ResolveInfo resolveInfo, int userId) throws XmlPullParserException, IOException {
+ protected ServiceInfo<TestServiceType> parseServiceInfo(ResolveInfo resolveInfo,
+ long lastUpdateTime) throws XmlPullParserException, IOException {
int size = mServices.size();
for (int i = 0; i < size; i++) {
Map<ResolveInfo, ServiceInfo<TestServiceType>> map = mServices.valueAt(i);
diff --git a/core/tests/coretests/src/com/android/internal/widget/NotificationProgressBarTest.java b/core/tests/coretests/src/com/android/internal/widget/NotificationProgressBarTest.java
index 9818e19..ec19c0c 100644
--- a/core/tests/coretests/src/com/android/internal/widget/NotificationProgressBarTest.java
+++ b/core/tests/coretests/src/com/android/internal/widget/NotificationProgressBarTest.java
@@ -16,6 +16,8 @@
package com.android.internal.widget;
+import static com.android.internal.widget.NotificationProgressBar.NotEnoughWidthToFitAllPartsException;
+
import static com.google.common.truth.Truth.assertThat;
import android.app.Notification.ProgressStyle;
@@ -35,6 +37,7 @@
import org.junit.runner.RunWith;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
@RunWith(AndroidJUnit4.class)
@@ -47,7 +50,7 @@
int progress = 50;
int progressMax = 100;
- NotificationProgressBar.processAndConvertToViewParts(segments, points, progress,
+ NotificationProgressBar.processModelAndConvertToViewParts(segments, points, progress,
progressMax);
}
@@ -60,7 +63,7 @@
int progress = 50;
int progressMax = 100;
- NotificationProgressBar.processAndConvertToViewParts(segments, points, progress,
+ NotificationProgressBar.processModelAndConvertToViewParts(segments, points, progress,
progressMax);
}
@@ -73,7 +76,7 @@
int progress = 50;
int progressMax = 100;
- NotificationProgressBar.processAndConvertToViewParts(segments, points, progress,
+ NotificationProgressBar.processModelAndConvertToViewParts(segments, points, progress,
progressMax);
}
@@ -86,7 +89,7 @@
int progress = 50;
int progressMax = 100;
- NotificationProgressBar.processAndConvertToViewParts(segments, points, progress,
+ NotificationProgressBar.processModelAndConvertToViewParts(segments, points, progress,
progressMax);
}
@@ -98,20 +101,21 @@
int progress = -50;
int progressMax = 100;
- NotificationProgressBar.processAndConvertToViewParts(segments, points, progress,
+ NotificationProgressBar.processModelAndConvertToViewParts(segments, points, progress,
progressMax);
}
@Test
- public void processAndConvertToParts_progressIsZero() {
+ public void processAndConvertToParts_progressIsZero()
+ throws NotificationProgressBar.NotEnoughWidthToFitAllPartsException {
List<ProgressStyle.Segment> segments = new ArrayList<>();
segments.add(new ProgressStyle.Segment(100).setColor(Color.RED));
List<ProgressStyle.Point> points = new ArrayList<>();
int progress = 0;
int progressMax = 100;
- List<Part> parts = NotificationProgressBar.processAndConvertToViewParts(segments, points,
- progress, progressMax);
+ List<Part> parts = NotificationProgressBar.processModelAndConvertToViewParts(segments,
+ points, progress, progressMax);
List<Part> expectedParts = new ArrayList<>(List.of(new Segment(1f, Color.RED)));
@@ -123,8 +127,9 @@
float pointRadius = 6;
boolean hasTrackerIcon = true;
- List<DrawablePart> drawableParts = NotificationProgressBar.processAndConvertToDrawableParts(
- parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
+ List<DrawablePart> drawableParts =
+ NotificationProgressBar.processPartsAndConvertToDrawableParts(
+ parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
List<DrawablePart> expectedDrawableParts = new ArrayList<>(
List.of(new DrawableSegment(0, 300, Color.RED)));
@@ -148,15 +153,16 @@
}
@Test
- public void processAndConvertToParts_progressAtMax() {
+ public void processAndConvertToParts_progressAtMax()
+ throws NotEnoughWidthToFitAllPartsException {
List<ProgressStyle.Segment> segments = new ArrayList<>();
segments.add(new ProgressStyle.Segment(100).setColor(Color.RED));
List<ProgressStyle.Point> points = new ArrayList<>();
int progress = 100;
int progressMax = 100;
- List<Part> parts = NotificationProgressBar.processAndConvertToViewParts(segments, points,
- progress, progressMax);
+ List<Part> parts = NotificationProgressBar.processModelAndConvertToViewParts(segments,
+ points, progress, progressMax);
List<Part> expectedParts = new ArrayList<>(List.of(new Segment(1f, Color.RED)));
@@ -168,8 +174,9 @@
float pointRadius = 6;
boolean hasTrackerIcon = true;
- List<DrawablePart> drawableParts = NotificationProgressBar.processAndConvertToDrawableParts(
- parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
+ List<DrawablePart> drawableParts =
+ NotificationProgressBar.processPartsAndConvertToDrawableParts(
+ parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
List<DrawablePart> expectedDrawableParts = new ArrayList<>(
List.of(new DrawableSegment(0, 300, Color.RED)));
@@ -195,7 +202,7 @@
int progress = 150;
int progressMax = 100;
- NotificationProgressBar.processAndConvertToViewParts(segments, points, progress,
+ NotificationProgressBar.processModelAndConvertToViewParts(segments, points, progress,
progressMax);
}
@@ -208,7 +215,7 @@
int progress = 50;
int progressMax = 100;
- NotificationProgressBar.processAndConvertToViewParts(segments, points, progress,
+ NotificationProgressBar.processModelAndConvertToViewParts(segments, points, progress,
progressMax);
}
@@ -221,12 +228,62 @@
int progress = 50;
int progressMax = 100;
- NotificationProgressBar.processAndConvertToViewParts(segments, points, progress,
+ NotificationProgressBar.processModelAndConvertToViewParts(segments, points, progress,
progressMax);
}
@Test
- public void processAndConvertToParts_multipleSegmentsWithoutPoints() {
+ public void processAndConvertToParts_singleSegmentWithoutPoints()
+ throws NotEnoughWidthToFitAllPartsException {
+ List<ProgressStyle.Segment> segments = new ArrayList<>();
+ segments.add(new ProgressStyle.Segment(100).setColor(Color.BLUE));
+ List<ProgressStyle.Point> points = new ArrayList<>();
+ int progress = 60;
+ int progressMax = 100;
+
+ List<Part> parts = NotificationProgressBar.processModelAndConvertToViewParts(segments,
+ points, progress, progressMax);
+
+ List<Part> expectedParts = new ArrayList<>(
+ List.of(new Segment(1, Color.BLUE)));
+
+ assertThat(parts).isEqualTo(expectedParts);
+
+ float drawableWidth = 300;
+ float segSegGap = 4;
+ float segPointGap = 4;
+ float pointRadius = 6;
+ boolean hasTrackerIcon = true;
+
+ List<DrawablePart> drawableParts =
+ NotificationProgressBar.processPartsAndConvertToDrawableParts(
+ parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
+
+ List<DrawablePart> expectedDrawableParts = new ArrayList<>(
+ List.of(new DrawableSegment(0, 300, Color.BLUE)));
+
+ assertThat(drawableParts).isEqualTo(expectedDrawableParts);
+
+ float segmentMinWidth = 16;
+ boolean isStyledByProgress = true;
+
+ Pair<List<DrawablePart>, Float> p = NotificationProgressBar.maybeStretchAndRescaleSegments(
+ parts, drawableParts, segmentMinWidth, pointRadius, (float) progress / progressMax,
+ 300, isStyledByProgress, hasTrackerIcon ? 0 : segSegGap);
+
+ // Colors with 40% opacity
+ int fadedBlue = 0x660000FF;
+ expectedDrawableParts = new ArrayList<>(
+ List.of(new DrawableSegment(0, 180, Color.BLUE),
+ new DrawableSegment(180, 300, fadedBlue, true)));
+
+ assertThat(p.second).isEqualTo(180);
+ assertThat(p.first).isEqualTo(expectedDrawableParts);
+ }
+
+ @Test
+ public void processAndConvertToParts_multipleSegmentsWithoutPoints()
+ throws NotEnoughWidthToFitAllPartsException {
List<ProgressStyle.Segment> segments = new ArrayList<>();
segments.add(new ProgressStyle.Segment(50).setColor(Color.RED));
segments.add(new ProgressStyle.Segment(50).setColor(Color.GREEN));
@@ -234,8 +291,8 @@
int progress = 60;
int progressMax = 100;
- List<Part> parts = NotificationProgressBar.processAndConvertToViewParts(segments, points,
- progress, progressMax);
+ List<Part> parts = NotificationProgressBar.processModelAndConvertToViewParts(segments,
+ points, progress, progressMax);
List<Part> expectedParts = new ArrayList<>(
List.of(new Segment(0.50f, Color.RED), new Segment(0.50f, Color.GREEN)));
@@ -248,8 +305,9 @@
float pointRadius = 6;
boolean hasTrackerIcon = true;
- List<DrawablePart> drawableParts = NotificationProgressBar.processAndConvertToDrawableParts(
- parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
+ List<DrawablePart> drawableParts =
+ NotificationProgressBar.processPartsAndConvertToDrawableParts(
+ parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
List<DrawablePart> expectedDrawableParts = new ArrayList<>(
List.of(new DrawableSegment(0, 146, Color.RED),
@@ -274,15 +332,16 @@
}
@Test
- public void processAndConvertToParts_multipleSegmentsWithoutPoints_noTracker() {
+ public void processAndConvertToParts_multipleSegmentsWithoutPoints_noTracker()
+ throws NotEnoughWidthToFitAllPartsException {
List<ProgressStyle.Segment> segments = new ArrayList<>();
segments.add(new ProgressStyle.Segment(50).setColor(Color.RED));
segments.add(new ProgressStyle.Segment(50).setColor(Color.GREEN));
List<ProgressStyle.Point> points = new ArrayList<>();
int progress = 60;
int progressMax = 100;
- List<Part> parts = NotificationProgressBar.processAndConvertToViewParts(segments, points,
- progress, progressMax);
+ List<Part> parts = NotificationProgressBar.processModelAndConvertToViewParts(segments,
+ points, progress, progressMax);
List<Part> expectedParts = new ArrayList<>(
List.of(new Segment(0.50f, Color.RED), new Segment(0.50f, Color.GREEN)));
@@ -295,8 +354,9 @@
float pointRadius = 6;
boolean hasTrackerIcon = false;
- List<DrawablePart> drawableParts = NotificationProgressBar.processAndConvertToDrawableParts(
- parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
+ List<DrawablePart> drawableParts =
+ NotificationProgressBar.processPartsAndConvertToDrawableParts(
+ parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
List<DrawablePart> expectedDrawableParts = new ArrayList<>(
List.of(new DrawableSegment(0, 146, Color.RED),
@@ -321,7 +381,8 @@
}
@Test
- public void processAndConvertToParts_singleSegmentWithPoints() {
+ public void processAndConvertToParts_singleSegmentWithPoints()
+ throws NotEnoughWidthToFitAllPartsException {
List<ProgressStyle.Segment> segments = new ArrayList<>();
segments.add(new ProgressStyle.Segment(100).setColor(Color.BLUE));
List<ProgressStyle.Point> points = new ArrayList<>();
@@ -332,8 +393,8 @@
int progress = 60;
int progressMax = 100;
- List<Part> parts = NotificationProgressBar.processAndConvertToViewParts(segments, points,
- progress, progressMax);
+ List<Part> parts = NotificationProgressBar.processModelAndConvertToViewParts(segments,
+ points, progress, progressMax);
List<Part> expectedParts = new ArrayList<>(
List.of(new Segment(0.15f, Color.BLUE),
@@ -354,8 +415,9 @@
float pointRadius = 6;
boolean hasTrackerIcon = true;
- List<DrawablePart> drawableParts = NotificationProgressBar.processAndConvertToDrawableParts(
- parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
+ List<DrawablePart> drawableParts =
+ NotificationProgressBar.processPartsAndConvertToDrawableParts(
+ parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
List<DrawablePart> expectedDrawableParts = new ArrayList<>(
List.of(new DrawableSegment(0, 35, Color.BLUE),
@@ -396,7 +458,8 @@
}
@Test
- public void processAndConvertToParts_multipleSegmentsWithPoints() {
+ public void processAndConvertToParts_multipleSegmentsWithPoints()
+ throws NotEnoughWidthToFitAllPartsException {
List<ProgressStyle.Segment> segments = new ArrayList<>();
segments.add(new ProgressStyle.Segment(50).setColor(Color.RED));
segments.add(new ProgressStyle.Segment(50).setColor(Color.GREEN));
@@ -408,8 +471,8 @@
int progress = 60;
int progressMax = 100;
- List<Part> parts = NotificationProgressBar.processAndConvertToViewParts(segments, points,
- progress, progressMax);
+ List<Part> parts = NotificationProgressBar.processModelAndConvertToViewParts(segments,
+ points, progress, progressMax);
List<Part> expectedParts = new ArrayList<>(
List.of(new Segment(0.15f, Color.RED),
@@ -430,8 +493,9 @@
float segPointGap = 4;
float pointRadius = 6;
boolean hasTrackerIcon = true;
- List<DrawablePart> drawableParts = NotificationProgressBar.processAndConvertToDrawableParts(
- parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
+ List<DrawablePart> drawableParts =
+ NotificationProgressBar.processPartsAndConvertToDrawableParts(
+ parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
List<DrawablePart> expectedDrawableParts = new ArrayList<>(
List.of(new DrawableSegment(0, 35, Color.RED), new DrawablePoint(39, 51, Color.RED),
@@ -473,7 +537,8 @@
}
@Test
- public void processAndConvertToParts_multipleSegmentsWithPointsAtStartAndEnd() {
+ public void processAndConvertToParts_multipleSegmentsWithPointsAtStartAndEnd()
+ throws NotEnoughWidthToFitAllPartsException {
List<ProgressStyle.Segment> segments = new ArrayList<>();
segments.add(new ProgressStyle.Segment(50).setColor(Color.RED));
segments.add(new ProgressStyle.Segment(50).setColor(Color.GREEN));
@@ -485,8 +550,8 @@
int progress = 60;
int progressMax = 100;
- List<Part> parts = NotificationProgressBar.processAndConvertToViewParts(segments, points,
- progress, progressMax);
+ List<Part> parts = NotificationProgressBar.processModelAndConvertToViewParts(segments,
+ points, progress, progressMax);
List<Part> expectedParts = new ArrayList<>(
List.of(new Point(Color.RED),
@@ -505,8 +570,10 @@
float segPointGap = 4;
float pointRadius = 6;
boolean hasTrackerIcon = true;
- List<DrawablePart> drawableParts = NotificationProgressBar.processAndConvertToDrawableParts(
- parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
+
+ List<DrawablePart> drawableParts =
+ NotificationProgressBar.processPartsAndConvertToDrawableParts(
+ parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
List<DrawablePart> expectedDrawableParts = new ArrayList<>(
List.of(new DrawablePoint(0, 12, Color.RED),
@@ -547,7 +614,8 @@
// The points are so close to start/end that they would go out of bounds without the minimum
// segment width requirement.
@Test
- public void processAndConvertToParts_multipleSegmentsWithPointsNearStartAndEnd() {
+ public void processAndConvertToParts_multipleSegmentsWithPointsNearStartAndEnd()
+ throws NotEnoughWidthToFitAllPartsException {
List<ProgressStyle.Segment> segments = new ArrayList<>();
segments.add(new ProgressStyle.Segment(50).setColor(Color.RED));
segments.add(new ProgressStyle.Segment(50).setColor(Color.GREEN));
@@ -559,8 +627,8 @@
int progress = 60;
int progressMax = 100;
- List<Part> parts = NotificationProgressBar.processAndConvertToViewParts(segments, points,
- progress, progressMax);
+ List<Part> parts = NotificationProgressBar.processModelAndConvertToViewParts(segments,
+ points, progress, progressMax);
List<Part> expectedParts = new ArrayList<>(
List.of(new Segment(0.01f, Color.RED),
@@ -581,8 +649,10 @@
float segPointGap = 4;
float pointRadius = 6;
boolean hasTrackerIcon = true;
- List<DrawablePart> drawableParts = NotificationProgressBar.processAndConvertToDrawableParts(
- parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
+
+ List<DrawablePart> drawableParts =
+ NotificationProgressBar.processPartsAndConvertToDrawableParts(
+ parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
List<DrawablePart> expectedDrawableParts = new ArrayList<>(
List.of(new DrawableSegment(0, -7, Color.RED),
@@ -625,7 +695,8 @@
}
@Test
- public void processAndConvertToParts_multipleSegmentsWithPoints_notStyledByProgress() {
+ public void processAndConvertToParts_multipleSegmentsWithPoints_notStyledByProgress()
+ throws NotEnoughWidthToFitAllPartsException {
List<ProgressStyle.Segment> segments = new ArrayList<>();
segments.add(new ProgressStyle.Segment(50).setColor(Color.RED));
segments.add(new ProgressStyle.Segment(50).setColor(Color.GREEN));
@@ -636,8 +707,8 @@
int progress = 60;
int progressMax = 100;
- List<Part> parts = NotificationProgressBar.processAndConvertToViewParts(segments, points,
- progress, progressMax);
+ List<Part> parts = NotificationProgressBar.processModelAndConvertToViewParts(segments,
+ points, progress, progressMax);
List<Part> expectedParts = new ArrayList<>(
List.of(new Segment(0.15f, Color.RED), new Point(Color.RED),
@@ -653,8 +724,9 @@
float pointRadius = 6;
boolean hasTrackerIcon = true;
- List<DrawablePart> drawableParts = NotificationProgressBar.processAndConvertToDrawableParts(
- parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
+ List<DrawablePart> drawableParts =
+ NotificationProgressBar.processPartsAndConvertToDrawableParts(
+ parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
List<DrawablePart> expectedDrawableParts = new ArrayList<>(
List.of(new DrawableSegment(0, 35, Color.RED), new DrawablePoint(39, 51, Color.RED),
@@ -691,7 +763,8 @@
// The only difference from the `zeroWidthDrawableSegment` test below is the longer
// segmentMinWidth (= 16dp).
@Test
- public void maybeStretchAndRescaleSegments_negativeWidthDrawableSegment() {
+ public void maybeStretchAndRescaleSegments_negativeWidthDrawableSegment()
+ throws NotEnoughWidthToFitAllPartsException {
List<ProgressStyle.Segment> segments = new ArrayList<>();
segments.add(new ProgressStyle.Segment(100).setColor(Color.BLUE));
segments.add(new ProgressStyle.Segment(200).setColor(Color.BLUE));
@@ -702,8 +775,8 @@
int progress = 1000;
int progressMax = 1000;
- List<Part> parts = NotificationProgressBar.processAndConvertToViewParts(segments, points,
- progress, progressMax);
+ List<Part> parts = NotificationProgressBar.processModelAndConvertToViewParts(segments,
+ points, progress, progressMax);
List<Part> expectedParts = new ArrayList<>(
List.of(new Point(Color.BLUE), new Segment(0.1f, Color.BLUE),
@@ -717,8 +790,10 @@
float segPointGap = 4;
float pointRadius = 6;
boolean hasTrackerIcon = true;
- List<DrawablePart> drawableParts = NotificationProgressBar.processAndConvertToDrawableParts(
- parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
+
+ List<DrawablePart> drawableParts =
+ NotificationProgressBar.processPartsAndConvertToDrawableParts(
+ parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
List<DrawablePart> expectedDrawableParts = new ArrayList<>(
List.of(new DrawablePoint(0, 12, Color.BLUE),
@@ -749,7 +824,8 @@
// The only difference from the `negativeWidthDrawableSegment` test above is the shorter
// segmentMinWidth (= 10dp).
@Test
- public void maybeStretchAndRescaleSegments_zeroWidthDrawableSegment() {
+ public void maybeStretchAndRescaleSegments_zeroWidthDrawableSegment()
+ throws NotEnoughWidthToFitAllPartsException {
List<ProgressStyle.Segment> segments = new ArrayList<>();
segments.add(new ProgressStyle.Segment(100).setColor(Color.BLUE));
segments.add(new ProgressStyle.Segment(200).setColor(Color.BLUE));
@@ -760,8 +836,8 @@
int progress = 1000;
int progressMax = 1000;
- List<Part> parts = NotificationProgressBar.processAndConvertToViewParts(segments, points,
- progress, progressMax);
+ List<Part> parts = NotificationProgressBar.processModelAndConvertToViewParts(segments,
+ points, progress, progressMax);
List<Part> expectedParts = new ArrayList<>(
List.of(new Point(Color.BLUE), new Segment(0.1f, Color.BLUE),
@@ -775,8 +851,10 @@
float segPointGap = 4;
float pointRadius = 6;
boolean hasTrackerIcon = true;
- List<DrawablePart> drawableParts = NotificationProgressBar.processAndConvertToDrawableParts(
- parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
+
+ List<DrawablePart> drawableParts =
+ NotificationProgressBar.processPartsAndConvertToDrawableParts(
+ parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
List<DrawablePart> expectedDrawableParts = new ArrayList<>(
List.of(new DrawablePoint(0, 12, Color.BLUE),
@@ -805,7 +883,8 @@
}
@Test
- public void maybeStretchAndRescaleSegments_noStretchingNecessary() {
+ public void maybeStretchAndRescaleSegments_noStretchingNecessary()
+ throws NotEnoughWidthToFitAllPartsException {
List<ProgressStyle.Segment> segments = new ArrayList<>();
segments.add(new ProgressStyle.Segment(200).setColor(Color.BLUE));
segments.add(new ProgressStyle.Segment(100).setColor(Color.BLUE));
@@ -816,8 +895,8 @@
int progress = 1000;
int progressMax = 1000;
- List<Part> parts = NotificationProgressBar.processAndConvertToViewParts(segments, points,
- progress, progressMax);
+ List<Part> parts = NotificationProgressBar.processModelAndConvertToViewParts(segments,
+ points, progress, progressMax);
List<Part> expectedParts = new ArrayList<>(
List.of(new Point(Color.BLUE), new Segment(0.2f, Color.BLUE),
@@ -832,8 +911,9 @@
float pointRadius = 6;
boolean hasTrackerIcon = true;
- List<DrawablePart> drawableParts = NotificationProgressBar.processAndConvertToDrawableParts(
- parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
+ List<DrawablePart> drawableParts =
+ NotificationProgressBar.processPartsAndConvertToDrawableParts(
+ parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
List<DrawablePart> expectedDrawableParts = new ArrayList<>(
List.of(new DrawablePoint(0, 12, Color.BLUE),
@@ -854,4 +934,168 @@
assertThat(p.second).isEqualTo(200);
assertThat(p.first).isEqualTo(expectedDrawableParts);
}
+
+ @Test(expected = NotEnoughWidthToFitAllPartsException.class)
+ public void maybeStretchAndRescaleSegments_notEnoughWidthToFitAllParts()
+ throws NotEnoughWidthToFitAllPartsException {
+ final int orange = 0xff7f50;
+ List<ProgressStyle.Segment> segments = new ArrayList<>();
+ segments.add(new ProgressStyle.Segment(10).setColor(orange));
+ segments.add(new ProgressStyle.Segment(10).setColor(Color.YELLOW));
+ segments.add(new ProgressStyle.Segment(10).setColor(Color.BLUE));
+ segments.add(new ProgressStyle.Segment(10).setColor(Color.GREEN));
+ segments.add(new ProgressStyle.Segment(10).setColor(Color.RED));
+ segments.add(new ProgressStyle.Segment(10).setColor(orange));
+ segments.add(new ProgressStyle.Segment(10).setColor(Color.YELLOW));
+ segments.add(new ProgressStyle.Segment(10).setColor(Color.BLUE));
+ segments.add(new ProgressStyle.Segment(10).setColor(Color.GREEN));
+ segments.add(new ProgressStyle.Segment(10).setColor(Color.RED));
+ List<ProgressStyle.Point> points = new ArrayList<>();
+ points.add(new ProgressStyle.Point(0).setColor(orange));
+ points.add(new ProgressStyle.Point(1).setColor(Color.BLUE));
+ points.add(new ProgressStyle.Point(55).setColor(Color.BLUE));
+ points.add(new ProgressStyle.Point(100).setColor(orange));
+ int progress = 50;
+ int progressMax = 100;
+
+ List<Part> parts = NotificationProgressBar.processModelAndConvertToViewParts(segments,
+ points, progress, progressMax);
+
+ List<Part> expectedParts = new ArrayList<>(
+ List.of(new Point(orange),
+ new Segment(0.01f, orange),
+ new Point(Color.BLUE),
+ new Segment(0.09f, orange),
+ new Segment(0.1f, Color.YELLOW),
+ new Segment(0.1f, Color.BLUE),
+ new Segment(0.1f, Color.GREEN),
+ new Segment(0.1f, Color.RED),
+ new Segment(0.05f, orange),
+ new Point(Color.BLUE),
+ new Segment(0.05f, orange),
+ new Segment(0.1f, Color.YELLOW),
+ new Segment(0.1f, Color.BLUE),
+ new Segment(0.1f, Color.GREEN),
+ new Segment(0.1f, Color.RED),
+ new Point(orange)));
+
+ assertThat(parts).isEqualTo(expectedParts);
+
+ // For the list of ProgressStyle.Part used in this test, 300 is the minimum width.
+ float drawableWidth = 299;
+ float segSegGap = 4;
+ float segPointGap = 4;
+ float pointRadius = 6;
+ boolean hasTrackerIcon = true;
+
+ List<DrawablePart> drawableParts =
+ NotificationProgressBar.processPartsAndConvertToDrawableParts(
+ parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
+
+ // Skips the validation of the intermediate list of DrawableParts.
+
+ float segmentMinWidth = 16;
+ boolean isStyledByProgress = true;
+
+ NotificationProgressBar.maybeStretchAndRescaleSegments(
+ parts, drawableParts, segmentMinWidth, pointRadius, (float) progress / progressMax,
+ 300, isStyledByProgress, hasTrackerIcon ? 0 : segSegGap);
+ }
+
+ @Test
+ public void processModelAndConvertToFinalDrawableParts_singleSegmentWithPoints()
+ throws NotEnoughWidthToFitAllPartsException {
+ List<ProgressStyle.Segment> segments = new ArrayList<>();
+ segments.add(new ProgressStyle.Segment(100).setColor(Color.BLUE));
+ List<ProgressStyle.Point> points = new ArrayList<>();
+ points.add(new ProgressStyle.Point(15).setColor(Color.RED));
+ points.add(new ProgressStyle.Point(25).setColor(Color.BLUE));
+ points.add(new ProgressStyle.Point(60).setColor(Color.BLUE));
+ points.add(new ProgressStyle.Point(75).setColor(Color.YELLOW));
+ int progress = 60;
+ int progressMax = 100;
+
+ float drawableWidth = 300;
+ float segSegGap = 4;
+ float segPointGap = 4;
+ float pointRadius = 6;
+ boolean hasTrackerIcon = true;
+
+ float segmentMinWidth = 16;
+ boolean isStyledByProgress = true;
+
+ Pair<List<DrawablePart>, Float> p =
+ NotificationProgressBar.processModelAndConvertToFinalDrawableParts(
+ segments,
+ points,
+ progress,
+ progressMax,
+ drawableWidth,
+ segSegGap,
+ segPointGap,
+ pointRadius,
+ hasTrackerIcon,
+ segmentMinWidth,
+ isStyledByProgress
+ );
+
+ // Colors with 40% opacity
+ int fadedBlue = 0x660000FF;
+ int fadedYellow = 0x66FFFF00;
+ List<DrawablePart> expectedDrawableParts = new ArrayList<>(
+ List.of(new DrawableSegment(0, 34.219177F, Color.BLUE),
+ new DrawablePoint(38.219177F, 50.219177F, Color.RED),
+ new DrawableSegment(54.219177F, 70.21918F, Color.BLUE),
+ new DrawablePoint(74.21918F, 86.21918F, Color.BLUE),
+ new DrawableSegment(90.21918F, 172.38356F, Color.BLUE),
+ new DrawablePoint(176.38356F, 188.38356F, Color.BLUE),
+ new DrawableSegment(192.38356F, 217.0137F, fadedBlue, true),
+ new DrawablePoint(221.0137F, 233.0137F, fadedYellow),
+ new DrawableSegment(237.0137F, 300F, fadedBlue, true)));
+
+ assertThat(p.second).isEqualTo(182.38356F);
+ assertThat(p.first).isEqualTo(expectedDrawableParts);
+ }
+
+ @Test
+ public void processModelAndConvertToFinalDrawableParts_singleSegmentWithoutPoints()
+ throws NotEnoughWidthToFitAllPartsException {
+ List<ProgressStyle.Segment> segments = new ArrayList<>();
+ segments.add(new ProgressStyle.Segment(100).setColor(Color.BLUE));
+ int progress = 60;
+ int progressMax = 100;
+
+ float drawableWidth = 100;
+ float segSegGap = 4;
+ float segPointGap = 4;
+ float pointRadius = 6;
+ boolean hasTrackerIcon = true;
+
+ float segmentMinWidth = 16;
+ boolean isStyledByProgress = true;
+
+ Pair<List<DrawablePart>, Float> p =
+ NotificationProgressBar.processModelAndConvertToFinalDrawableParts(
+ segments,
+ Collections.emptyList(),
+ progress,
+ progressMax,
+ drawableWidth,
+ segSegGap,
+ segPointGap,
+ pointRadius,
+ hasTrackerIcon,
+ segmentMinWidth,
+ isStyledByProgress
+ );
+
+ // Colors with 40% opacity
+ int fadedBlue = 0x660000FF;
+ List<DrawablePart> expectedDrawableParts = new ArrayList<>(
+ List.of(new DrawableSegment(0, 60.000004F, Color.BLUE),
+ new DrawableSegment(60.000004F, 100, fadedBlue, true)));
+
+ assertThat(p.second).isWithin(1e-5f).of(60);
+ assertThat(p.first).isEqualTo(expectedDrawableParts);
+ }
}
diff --git a/core/tests/coretests/src/com/android/internal/widget/NotificationProgressModelTest.java b/core/tests/coretests/src/com/android/internal/widget/NotificationProgressModelTest.java
index 962399e..e1f5b1c 100644
--- a/core/tests/coretests/src/com/android/internal/widget/NotificationProgressModelTest.java
+++ b/core/tests/coretests/src/com/android/internal/widget/NotificationProgressModelTest.java
@@ -49,7 +49,8 @@
new NotificationProgressModel(List.of(),
List.of(),
10,
- false);
+ false,
+ Color.TRANSPARENT);
}
@Test(expected = IllegalArgumentException.class)
@@ -58,7 +59,8 @@
List.of(new Notification.ProgressStyle.Segment(50).setColor(Color.YELLOW)),
List.of(),
-1,
- false);
+ false,
+ Color.TRANSPARENT);
}
@Test
@@ -74,14 +76,15 @@
// THEN
assertThat(restoredModel.getIndeterminateColor()).isEqualTo(Color.RED);
assertThat(restoredModel.isIndeterminate()).isTrue();
- assertThat(restoredModel.getProgress()).isEqualTo(-1);
+ assertThat(restoredModel.getProgress()).isEqualTo(0);
assertThat(restoredModel.getSegments()).isEmpty();
assertThat(restoredModel.getPoints()).isEmpty();
assertThat(restoredModel.isStyledByProgress()).isFalse();
+ assertThat(restoredModel.getSegmentsFallbackColor()).isEqualTo(Color.TRANSPARENT);
}
@Test
- public void save_and_restore_non_indeterminate_progress_model() {
+ public void save_and_restore_determinate_progress_model() {
// GIVEN
final List<Notification.ProgressStyle.Segment> segments = List.of(
new Notification.ProgressStyle.Segment(50).setColor(Color.YELLOW),
@@ -92,7 +95,8 @@
final NotificationProgressModel savedModel = new NotificationProgressModel(segments,
points,
100,
- true);
+ true,
+ Color.RED);
final Bundle bundle = savedModel.toBundle();
@@ -106,6 +110,38 @@
assertThat(restoredModel.getPoints()).isEqualTo(points);
assertThat(restoredModel.getProgress()).isEqualTo(100);
assertThat(restoredModel.isStyledByProgress()).isTrue();
- assertThat(restoredModel.getIndeterminateColor()).isEqualTo(-1);
+ assertThat(restoredModel.getSegmentsFallbackColor()).isEqualTo(Color.RED);
+ assertThat(restoredModel.getIndeterminateColor()).isEqualTo(Color.TRANSPARENT);
+ }
+
+ @Test
+ public void save_and_restore_non_determinate_progress_model_segments_fallback_color_invalid() {
+ // GIVEN
+ final List<Notification.ProgressStyle.Segment> segments = List.of(
+ new Notification.ProgressStyle.Segment(50).setColor(Color.YELLOW),
+ new Notification.ProgressStyle.Segment(50).setColor(Color.YELLOW));
+ final List<Notification.ProgressStyle.Point> points = List.of(
+ new Notification.ProgressStyle.Point(0).setColor(Color.RED),
+ new Notification.ProgressStyle.Point(20).setColor(Color.BLUE));
+ final NotificationProgressModel savedModel = new NotificationProgressModel(segments,
+ points,
+ 100,
+ true,
+ Color.TRANSPARENT);
+
+ final Bundle bundle = savedModel.toBundle();
+
+ // WHEN
+ final NotificationProgressModel restoredModel =
+ NotificationProgressModel.fromBundle(bundle);
+
+ // THEN
+ assertThat(restoredModel.isIndeterminate()).isFalse();
+ assertThat(restoredModel.getSegments()).isEqualTo(segments);
+ assertThat(restoredModel.getPoints()).isEqualTo(points);
+ assertThat(restoredModel.getProgress()).isEqualTo(100);
+ assertThat(restoredModel.isStyledByProgress()).isTrue();
+ assertThat(restoredModel.getSegmentsFallbackColor()).isEqualTo(Color.TRANSPARENT);
+ assertThat(restoredModel.getIndeterminateColor()).isEqualTo(Color.TRANSPARENT);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
index 0182588..2d4d458 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
@@ -43,7 +43,6 @@
import android.os.Bundle;
import android.os.RemoteException;
import android.util.Slog;
-import android.util.SparseArray;
import android.util.SparseIntArray;
import android.window.DesktopModeFlags;
import android.window.WindowContainerToken;
@@ -83,6 +82,7 @@
import java.util.Set;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
+import java.util.stream.Collectors;
/**
* Manages the recent task list from the system, caching it as necessary.
@@ -124,6 +124,10 @@
* Cached list of the visible tasks, sorted from top most to bottom most.
*/
private final List<RunningTaskInfo> mVisibleTasks = new ArrayList<>();
+ private final Map<Integer, TaskInfo> mVisibleTasksMap = new HashMap<>();
+
+ // Temporary vars used in `generateList()`
+ private final Map<Integer, TaskInfo> mTmpRemaining = new HashMap<>();
/**
* Creates {@link RecentTasksController}, returns {@code null} if the feature is not
@@ -348,8 +352,11 @@
public void onVisibleTasksChanged(@NonNull List<? extends RunningTaskInfo> visibleTasks) {
mVisibleTasks.clear();
mVisibleTasks.addAll(visibleTasks);
+ mVisibleTasksMap.clear();
+ mVisibleTasksMap.putAll(mVisibleTasks.stream().collect(
+ Collectors.toMap(TaskInfo::getTaskId, task -> task)));
// Notify with all the info and not just the running task info
- notifyVisibleTasksChanged(visibleTasks);
+ notifyVisibleTasksChanged(mVisibleTasks);
}
@VisibleForTesting
@@ -458,7 +465,7 @@
}
try {
// Compute the visible recent tasks in order, and move the task to the top
- mListener.onVisibleTasksChanged(generateList(visibleTasks)
+ mListener.onVisibleTasksChanged(generateList(visibleTasks, "visibleTasksChanged")
.toArray(new GroupedTaskInfo[0]));
} catch (RemoteException e) {
Slog.w(TAG, "Failed call onVisibleTasksChanged", e);
@@ -494,40 +501,87 @@
@VisibleForTesting
ArrayList<GroupedTaskInfo> getRecentTasks(int maxNum, int flags, int userId) {
// Note: the returned task list is ordered from the most-recent to least-recent order
- return generateList(mActivityTaskManager.getRecentTasks(maxNum, flags, userId));
+ return generateList(mActivityTaskManager.getRecentTasks(maxNum, flags, userId),
+ "getRecentTasks");
}
/**
- * Generates a list of GroupedTaskInfos for the given list of tasks.
+ * Returns whether the given task should be excluded from the generated list.
*/
- private <T extends TaskInfo> ArrayList<GroupedTaskInfo> generateList(@NonNull List<T> tasks) {
- // Make a mapping of task id -> task info
- final SparseArray<TaskInfo> rawMapping = new SparseArray<>();
- for (int i = 0; i < tasks.size(); i++) {
- final TaskInfo taskInfo = tasks.get(i);
- rawMapping.put(taskInfo.taskId, taskInfo);
+ private boolean excludeTaskFromGeneratedList(TaskInfo taskInfo) {
+ if (taskInfo.getWindowingMode() == WINDOWING_MODE_PINNED) {
+ // We don't current send pinned tasks as a part of recent or running tasks
+ return true;
+ }
+ if (isWallpaperTask(taskInfo)) {
+ // Don't add the fullscreen wallpaper task as an entry in grouped tasks
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Generates a list of GroupedTaskInfos for the given raw list of tasks (either recents or
+ * running tasks).
+ *
+ * The general flow is:
+ * - Collect the desktop tasks
+ * - Collect the visible tasks (in order), including the desktop tasks if visible
+ * - Construct the final list with the visible tasks, followed by the subsequent tasks
+ * - if enableShellTopTaskTracking() is enabled, the visible tasks will be grouped into
+ * a single mixed task
+ * - if the desktop tasks are not visible, they will be appended to the end of the list
+ *
+ * TODO(346588978): Generate list in per-display order
+ *
+ * @param tasks The list of tasks ordered from most recent to least recent
+ */
+ @VisibleForTesting
+ <T extends TaskInfo> ArrayList<GroupedTaskInfo> generateList(@NonNull List<T> tasks,
+ String reason) {
+ if (tasks.isEmpty()) {
+ return new ArrayList<>();
}
- ArrayList<TaskInfo> freeformTasks = new ArrayList<>();
- Set<Integer> minimizedFreeformTasks = new HashSet<>();
+ if (enableShellTopTaskTracking()) {
+ ProtoLog.v(WM_SHELL_TASK_OBSERVER, "RecentTasksController.generateList(%s)", reason);
+ }
- int mostRecentFreeformTaskIndex = Integer.MAX_VALUE;
+ // Make a mapping of task id -> task info for the remaining tasks to be processed, this
+ // mapping is used to keep track of split tasks that may exist later in the task list that
+ // should be ignored because they've already been grouped
+ mTmpRemaining.clear();
+ mTmpRemaining.putAll(tasks.stream().collect(
+ Collectors.toMap(TaskInfo::getTaskId, task -> task)));
- ArrayList<GroupedTaskInfo> groupedTasks = new ArrayList<>();
- // Pull out the pairs as we iterate back in the list
+ // The final grouped tasks
+ ArrayList<GroupedTaskInfo> groupedTasks = new ArrayList<>(tasks.size());
+ ArrayList<GroupedTaskInfo> visibleGroupedTasks = new ArrayList<>();
+
+ // Phase 1: Extract the desktop and visible fullscreen/split tasks. We make new collections
+ // here as the GroupedTaskInfo can store them without copying
+ ArrayList<TaskInfo> desktopTasks = new ArrayList<>();
+ Set<Integer> minimizedDesktopTasks = new HashSet<>();
+ boolean desktopTasksVisible = false;
for (int i = 0; i < tasks.size(); i++) {
final TaskInfo taskInfo = tasks.get(i);
- if (!rawMapping.contains(taskInfo.taskId)) {
- // If it's not in the mapping, then it was already paired with another task
+ final int taskId = taskInfo.taskId;
+
+ if (!mTmpRemaining.containsKey(taskInfo.taskId)) {
+ // Skip if we've already processed it
continue;
}
+
+ if (excludeTaskFromGeneratedList(taskInfo)) {
+ // Skip and update the list if we are excluding this task
+ mTmpRemaining.remove(taskId);
+ continue;
+ }
+
+ // Desktop tasks
if (DesktopModeStatus.canEnterDesktopMode(mContext) &&
- mDesktopUserRepositories.isPresent()
- && mDesktopUserRepositories.get().getCurrent().isActiveTask(taskInfo.taskId)) {
- // Freeform tasks will be added as a separate entry
- if (mostRecentFreeformTaskIndex == Integer.MAX_VALUE) {
- mostRecentFreeformTaskIndex = groupedTasks.size();
- }
+ mDesktopUserRepositories.isPresent()
+ && mDesktopUserRepositories.get().getCurrent().isActiveTask(taskId)) {
// If task has their app bounds set to null which happens after reboot, set the
// app bounds to persisted lastFullscreenBounds. Also set the position in parent
// to the top left of the bounds.
@@ -538,49 +592,132 @@
taskInfo.positionInParent = new Point(taskInfo.lastNonFullscreenBounds.left,
taskInfo.lastNonFullscreenBounds.top);
}
- freeformTasks.add(taskInfo);
- if (mDesktopUserRepositories.get().getCurrent().isMinimizedTask(taskInfo.taskId)) {
- minimizedFreeformTasks.add(taskInfo.taskId);
+ desktopTasks.add(taskInfo);
+ if (mDesktopUserRepositories.get().getCurrent().isMinimizedTask(taskId)) {
+ minimizedDesktopTasks.add(taskId);
}
+ desktopTasksVisible |= mVisibleTasksMap.containsKey(taskId);
+ mTmpRemaining.remove(taskId);
continue;
}
- final int pairedTaskId = mSplitTasks.get(taskInfo.taskId, INVALID_TASK_ID);
- if (pairedTaskId != INVALID_TASK_ID && rawMapping.contains(pairedTaskId)) {
- final TaskInfo pairedTaskInfo = rawMapping.get(pairedTaskId);
- rawMapping.remove(pairedTaskId);
- groupedTasks.add(GroupedTaskInfo.forSplitTasks(taskInfo, pairedTaskInfo,
- mTaskSplitBoundsMap.get(pairedTaskId)));
+ if (enableShellTopTaskTracking()) {
+ // Visible tasks
+ if (mVisibleTasksMap.containsKey(taskId)) {
+ // Split tasks
+ if (extractAndAddSplitGroupedTask(taskInfo, mTmpRemaining,
+ visibleGroupedTasks)) {
+ continue;
+ }
+
+ // Fullscreen tasks
+ visibleGroupedTasks.add(GroupedTaskInfo.forFullscreenTasks(taskInfo));
+ mTmpRemaining.remove(taskId);
+ }
} else {
- if (isWallpaperTask(taskInfo)) {
- // Don't add the wallpaper task as an entry in grouped tasks
+ // Split tasks
+ if (extractAndAddSplitGroupedTask(taskInfo, mTmpRemaining, groupedTasks)) {
continue;
}
- // TODO(346588978): Consolidate multiple visible fullscreen tasks into the same
- // grouped task
+
+ // Fullscreen tasks
groupedTasks.add(GroupedTaskInfo.forFullscreenTasks(taskInfo));
}
}
- // Add a special entry for freeform tasks
- if (!freeformTasks.isEmpty()) {
- groupedTasks.add(mostRecentFreeformTaskIndex,
- GroupedTaskInfo.forFreeformTasks(
- freeformTasks,
- minimizedFreeformTasks));
- }
-
if (enableShellTopTaskTracking()) {
- // We don't current send pinned tasks as a part of recent or running tasks, so remove
- // them from the list here
- groupedTasks.removeIf(
- gti -> gti.getTaskInfo1().getWindowingMode() == WINDOWING_MODE_PINNED);
+ // Phase 2: If there were desktop tasks and they are visible, add them to the visible
+ // list as well (the actual order doesn't matter for Overview)
+ if (!desktopTasks.isEmpty() && desktopTasksVisible) {
+ visibleGroupedTasks.add(
+ GroupedTaskInfo.forFreeformTasks(desktopTasks, minimizedDesktopTasks));
+ }
+
+ if (!visibleGroupedTasks.isEmpty()) {
+ // Phase 3: Combine the visible tasks into a single mixed grouped task, only if
+ // there are > 1 tasks to group, and add them to the final list
+ if (visibleGroupedTasks.size() > 1) {
+ groupedTasks.add(GroupedTaskInfo.forMixed(visibleGroupedTasks));
+ } else {
+ groupedTasks.addAll(visibleGroupedTasks);
+ }
+ }
+ dumpGroupedTasks(groupedTasks, "Phase 3");
+
+ // Phase 4: For the remaining non-visible split and fullscreen tasks, add grouped tasks
+ // in order to the final list
+ for (int i = 0; i < tasks.size(); i++) {
+ final TaskInfo taskInfo = tasks.get(i);
+ if (!mTmpRemaining.containsKey(taskInfo.taskId)) {
+ // Skip if we've already processed it
+ continue;
+ }
+
+ // Split tasks
+ if (extractAndAddSplitGroupedTask(taskInfo, mTmpRemaining, groupedTasks)) {
+ continue;
+ }
+
+ // Fullscreen tasks
+ groupedTasks.add(GroupedTaskInfo.forFullscreenTasks(taskInfo));
+ }
+ dumpGroupedTasks(groupedTasks, "Phase 4");
+
+ // Phase 5: If there were desktop tasks and they are not visible (ie. weren't added
+ // above), add them to the end of the final list (the actual order doesn't
+ // matter for Overview)
+ if (!desktopTasks.isEmpty() && !desktopTasksVisible) {
+ groupedTasks.add(
+ GroupedTaskInfo.forFreeformTasks(desktopTasks, minimizedDesktopTasks));
+ }
+ dumpGroupedTasks(groupedTasks, "Phase 5");
+ } else {
+ // Add the desktop tasks at the end of the list
+ if (!desktopTasks.isEmpty()) {
+ groupedTasks.add(
+ GroupedTaskInfo.forFreeformTasks(desktopTasks, minimizedDesktopTasks));
+ }
}
return groupedTasks;
}
/**
+ * Only to be called from `generateList()`. If the given {@param taskInfo} has a paired task,
+ * then a split grouped task with the pair is added to {@param tasksOut}.
+ *
+ * @return whether a split task was extracted and added to the given list
+ */
+ private boolean extractAndAddSplitGroupedTask(@NonNull TaskInfo taskInfo,
+ @NonNull Map<Integer, TaskInfo> remainingTasks,
+ @NonNull ArrayList<GroupedTaskInfo> tasksOut) {
+ final int pairedTaskId = mSplitTasks.get(taskInfo.taskId, INVALID_TASK_ID);
+ if (pairedTaskId == INVALID_TASK_ID || !remainingTasks.containsKey(pairedTaskId)) {
+ return false;
+ }
+
+ // Add both this task and its pair to the list, and mark the paired task to be
+ // skipped when it is encountered in the list
+ final TaskInfo pairedTaskInfo = remainingTasks.get(pairedTaskId);
+ remainingTasks.remove(taskInfo.taskId);
+ remainingTasks.remove(pairedTaskId);
+ tasksOut.add(GroupedTaskInfo.forSplitTasks(taskInfo, pairedTaskInfo,
+ mTaskSplitBoundsMap.get(pairedTaskId)));
+ return true;
+ }
+
+ /** Dumps the set of tasks to protolog */
+ private void dumpGroupedTasks(List<GroupedTaskInfo> groupedTasks, String reason) {
+ if (!WM_SHELL_TASK_OBSERVER.isEnabled()) {
+ return;
+ }
+ ProtoLog.v(WM_SHELL_TASK_OBSERVER, " Tasks (%s):", reason);
+ for (GroupedTaskInfo task : groupedTasks) {
+ ProtoLog.v(WM_SHELL_TASK_OBSERVER, " %s", task);
+ }
+ }
+
+ /**
* Returns the top running leaf task ignoring {@param ignoreTaskToken} if it is specified.
* NOTE: This path currently makes assumptions that ignoreTaskToken is for the top task.
*/
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/TaskStackTransitionObserver.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/TaskStackTransitionObserver.kt
index 93f2e4c..11cd403 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/TaskStackTransitionObserver.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/TaskStackTransitionObserver.kt
@@ -193,16 +193,18 @@
override fun onTransitionMerged(merged: IBinder, playing: IBinder) {}
override fun onTransitionFinished(transition: IBinder, aborted: Boolean) {
- if (enableShellTopTaskTracking()) {
- if (pendingCloseTasks.isNotEmpty()) {
- // Update the visible task list based on the pending close tasks
- for (change in pendingCloseTasks) {
- visibleTasks.removeIf {
- it.taskId == change.taskId
- }
+ if (!enableShellTopTaskTracking()) {
+ return
+ }
+
+ if (pendingCloseTasks.isNotEmpty()) {
+ // Update the visible task list based on the pending close tasks
+ for (change in pendingCloseTasks) {
+ visibleTasks.removeIf {
+ it.taskId == change.taskId
}
- updateVisibleTasksList("transition-finished")
}
+ updateVisibleTasksList("transition-finished")
}
}
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 792f5ca..7aa0037 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
@@ -284,6 +284,10 @@
}
private boolean shouldShowWindowDecor(RunningTaskInfo taskInfo) {
+ if (mDisplayController.getDisplay(taskInfo.displayId) == null) {
+ // If DisplayController doesn't have it tracked, it could be a private/managed display.
+ return false;
+ }
if (taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) {
return true;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index eb3a698..51b0291 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -1643,6 +1643,10 @@
}
private boolean shouldShowWindowDecor(RunningTaskInfo taskInfo) {
+ if (mDisplayController.getDisplay(taskInfo.displayId) == null) {
+ // If DisplayController doesn't have it tracked, it could be a private/managed display.
+ return false;
+ }
if (taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) return true;
if (mSplitScreenController != null
&& mSplitScreenController.isTaskRootOrStageRoot(taskInfo.taskId)) {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
index 28f4ea0..065fa21 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
@@ -20,6 +20,7 @@
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
import static com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_PERSISTENCE;
@@ -28,7 +29,6 @@
import static com.android.wm.shell.shared.GroupedTaskInfo.TYPE_SPLIT;
import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_50_50;
-import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
@@ -48,9 +48,10 @@
import static org.mockito.Mockito.when;
import static java.lang.Integer.MAX_VALUE;
+import static java.util.stream.Collectors.joining;
-import android.app.ActivityManager;
import android.app.ActivityManager.RecentTaskInfo;
+import android.app.ActivityManager.RunningTaskInfo;
import android.app.ActivityTaskManager;
import android.app.KeyguardManager;
import android.content.ComponentName;
@@ -247,10 +248,10 @@
ArrayList<GroupedTaskInfo> recentTasks =
mRecentTasksController.getRecentTasks(MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0);
- assertGroupedTasksListEquals(recentTasks,
- t1.taskId, -1,
- t2.taskId, -1,
- t3.taskId, -1);
+ assertGroupedTasksListEquals(recentTasks, List.of(
+ List.of(t1.taskId),
+ List.of(t2.taskId),
+ List.of(t3.taskId)));
}
@Test
@@ -262,7 +263,9 @@
ArrayList<GroupedTaskInfo> recentTasks =
mRecentTasksController.getRecentTasks(MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0);
- assertGroupedTasksListEquals(recentTasks, t1.taskId, -1, t3.taskId, -1);
+ assertGroupedTasksListEquals(recentTasks, List.of(
+ List.of(t1.taskId),
+ List.of(t3.taskId)));
}
@Test
@@ -286,11 +289,11 @@
ArrayList<GroupedTaskInfo> recentTasks =
mRecentTasksController.getRecentTasks(MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0);
- assertGroupedTasksListEquals(recentTasks,
- t1.taskId, -1,
- t2.taskId, t4.taskId,
- t3.taskId, t5.taskId,
- t6.taskId, -1);
+ assertGroupedTasksListEquals(recentTasks, List.of(
+ List.of(t1.taskId),
+ List.of(t2.taskId, t4.taskId),
+ List.of(t3.taskId, t5.taskId),
+ List.of(t6.taskId)));
}
@Test
@@ -320,11 +323,11 @@
consumer);
mMainExecutor.flushAll();
- assertGroupedTasksListEquals(recentTasks[0],
- t1.taskId, -1,
- t2.taskId, t4.taskId,
- t3.taskId, t5.taskId,
- t6.taskId, -1);
+ assertGroupedTasksListEquals(recentTasks[0], List.of(
+ List.of(t1.taskId),
+ List.of(t2.taskId, t4.taskId),
+ List.of(t3.taskId, t5.taskId),
+ List.of(t6.taskId)));
}
@Test
@@ -343,9 +346,9 @@
// 2 freeform tasks should be grouped into one, 3 total recents entries
assertEquals(3, recentTasks.size());
- GroupedTaskInfo freeformGroup = recentTasks.get(0);
- GroupedTaskInfo singleGroup1 = recentTasks.get(1);
- GroupedTaskInfo singleGroup2 = recentTasks.get(2);
+ GroupedTaskInfo singleGroup1 = recentTasks.get(0);
+ GroupedTaskInfo singleGroup2 = recentTasks.get(1);
+ GroupedTaskInfo freeformGroup = recentTasks.get(2);
// Check that groups have expected types
assertTrue(freeformGroup.isBaseType(TYPE_FREEFORM));
@@ -383,8 +386,8 @@
// 2 split screen tasks grouped, 2 freeform tasks grouped, 3 total recents entries
assertEquals(3, recentTasks.size());
GroupedTaskInfo splitGroup = recentTasks.get(0);
- GroupedTaskInfo freeformGroup = recentTasks.get(1);
- GroupedTaskInfo singleGroup = recentTasks.get(2);
+ GroupedTaskInfo singleGroup = recentTasks.get(1);
+ GroupedTaskInfo freeformGroup = recentTasks.get(2);
// Check that groups have expected types
assertTrue(splitGroup.isBaseType(TYPE_SPLIT));
@@ -454,9 +457,9 @@
// 3 freeform tasks should be grouped into one, 2 single tasks, 3 total recents entries
assertEquals(3, recentTasks.size());
- GroupedTaskInfo freeformGroup = recentTasks.get(0);
- GroupedTaskInfo singleGroup1 = recentTasks.get(1);
- GroupedTaskInfo singleGroup2 = recentTasks.get(2);
+ GroupedTaskInfo singleGroup1 = recentTasks.get(0);
+ GroupedTaskInfo singleGroup2 = recentTasks.get(1);
+ GroupedTaskInfo freeformGroup = recentTasks.get(2);
// Check that groups have expected types
assertTrue(freeformGroup.isBaseType(TYPE_FREEFORM));
@@ -523,7 +526,7 @@
// Remove one of the tasks and ensure the pair is removed
SurfaceControl mockLeash = mock(SurfaceControl.class);
- ActivityManager.RunningTaskInfo rt2 = makeRunningTaskInfo(2);
+ RunningTaskInfo rt2 = makeRunningTaskInfo(2);
mShellTaskOrganizer.onTaskAppeared(rt2, mockLeash);
mShellTaskOrganizer.onTaskVanished(rt2);
@@ -537,13 +540,13 @@
// Remove one of the tasks and ensure the pair is removed
SurfaceControl mockLeash = mock(SurfaceControl.class);
- ActivityManager.RunningTaskInfo rt2Fullscreen = makeRunningTaskInfo(2);
+ RunningTaskInfo rt2Fullscreen = makeRunningTaskInfo(2);
rt2Fullscreen.configuration.windowConfiguration.setWindowingMode(
WINDOWING_MODE_FULLSCREEN);
mShellTaskOrganizer.onTaskAppeared(rt2Fullscreen, mockLeash);
// Change the windowing mode and ensure the recent tasks change is notified
- ActivityManager.RunningTaskInfo rt2MultiWIndow = makeRunningTaskInfo(2);
+ RunningTaskInfo rt2MultiWIndow = makeRunningTaskInfo(2);
rt2MultiWIndow.configuration.windowConfiguration.setWindowingMode(
WINDOWING_MODE_MULTI_WINDOW);
mShellTaskOrganizer.onTaskInfoChanged(rt2MultiWIndow);
@@ -557,7 +560,7 @@
public void onTaskAdded_desktopModeRunningAppsEnabled_triggersOnRunningTaskAppeared()
throws Exception {
mRecentTasksControllerReal.registerRecentTasksListener(mRecentTasksListener);
- ActivityManager.RunningTaskInfo taskInfo = makeRunningTaskInfo(/* taskId= */10);
+ RunningTaskInfo taskInfo = makeRunningTaskInfo(/* taskId= */10);
mRecentTasksControllerReal.onTaskAdded(taskInfo);
@@ -570,7 +573,7 @@
public void onTaskAdded_desktopModeRunningAppsDisabled_doesNotTriggerOnRunningTaskAppeared()
throws Exception {
mRecentTasksControllerReal.registerRecentTasksListener(mRecentTasksListener);
- ActivityManager.RunningTaskInfo taskInfo = makeRunningTaskInfo(/* taskId= */10);
+ RunningTaskInfo taskInfo = makeRunningTaskInfo(/* taskId= */10);
mRecentTasksControllerReal.onTaskAdded(taskInfo);
@@ -583,7 +586,7 @@
public void taskWindowingModeChanged_desktopRunningAppsEnabled_triggersOnRunningTaskChanged()
throws Exception {
mRecentTasksControllerReal.registerRecentTasksListener(mRecentTasksListener);
- ActivityManager.RunningTaskInfo taskInfo = makeRunningTaskInfo(/* taskId= */10);
+ RunningTaskInfo taskInfo = makeRunningTaskInfo(/* taskId= */10);
mRecentTasksControllerReal.onTaskRunningInfoChanged(taskInfo);
@@ -597,7 +600,7 @@
taskWindowingModeChanged_desktopRunningAppsDisabled_doesNotTriggerOnRunningTaskChanged()
throws Exception {
mRecentTasksControllerReal.registerRecentTasksListener(mRecentTasksListener);
- ActivityManager.RunningTaskInfo taskInfo = makeRunningTaskInfo(/* taskId= */10);
+ RunningTaskInfo taskInfo = makeRunningTaskInfo(/* taskId= */10);
mRecentTasksControllerReal.onTaskRunningInfoChanged(taskInfo);
@@ -610,7 +613,7 @@
public void onTaskRemoved_desktopModeRunningAppsEnabled_triggersOnRunningTaskVanished()
throws Exception {
mRecentTasksControllerReal.registerRecentTasksListener(mRecentTasksListener);
- ActivityManager.RunningTaskInfo taskInfo = makeRunningTaskInfo(/* taskId= */10);
+ RunningTaskInfo taskInfo = makeRunningTaskInfo(/* taskId= */10);
mRecentTasksControllerReal.onTaskRemoved(taskInfo);
@@ -623,7 +626,7 @@
public void onTaskRemoved_desktopModeRunningAppsDisabled_doesNotTriggerOnRunningTaskVanished()
throws Exception {
mRecentTasksControllerReal.registerRecentTasksListener(mRecentTasksListener);
- ActivityManager.RunningTaskInfo taskInfo = makeRunningTaskInfo(/* taskId= */10);
+ RunningTaskInfo taskInfo = makeRunningTaskInfo(/* taskId= */10);
mRecentTasksControllerReal.onTaskRemoved(taskInfo);
@@ -632,10 +635,11 @@
@Test
@EnableFlags(Flags.FLAG_ENABLE_TASK_STACK_OBSERVER_IN_SHELL)
+ @DisableFlags(com.android.wm.shell.Flags.FLAG_ENABLE_SHELL_TOP_TASK_TRACKING)
public void onTaskMovedToFront_TaskStackObserverEnabled_triggersOnTaskMovedToFront()
throws Exception {
mRecentTasksControllerReal.registerRecentTasksListener(mRecentTasksListener);
- ActivityManager.RunningTaskInfo taskInfo = makeRunningTaskInfo(/* taskId= */10);
+ RunningTaskInfo taskInfo = makeRunningTaskInfo(/* taskId= */10);
mRecentTasksControllerReal.onTaskMovedToFrontThroughTransition(taskInfo);
@@ -648,7 +652,7 @@
public void onTaskMovedToFront_TaskStackObserverEnabled_doesNotTriggersOnTaskMovedToFront()
throws Exception {
mRecentTasksControllerReal.registerRecentTasksListener(mRecentTasksListener);
- ActivityManager.RunningTaskInfo taskInfo = makeRunningTaskInfo(/* taskId= */10);
+ RunningTaskInfo taskInfo = makeRunningTaskInfo(/* taskId= */10);
mRecentTasksControllerReal.onTaskMovedToFront(taskInfo);
@@ -700,7 +704,7 @@
@EnableFlags(com.android.wm.shell.Flags.FLAG_ENABLE_SHELL_TOP_TASK_TRACKING)
public void shellTopTaskTracker_onTaskRemoved_expectNoRecentsChanged() throws Exception {
mRecentTasksControllerReal.registerRecentTasksListener(mRecentTasksListener);
- ActivityManager.RunningTaskInfo taskInfo = makeRunningTaskInfo(/* taskId= */10);
+ RunningTaskInfo taskInfo = makeRunningTaskInfo(/* taskId= */10);
mRecentTasksControllerReal.onTaskRemoved(taskInfo);
verify(mRecentTasksListener, never()).onRecentTasksChanged();
}
@@ -710,22 +714,105 @@
@EnableFlags(com.android.wm.shell.Flags.FLAG_ENABLE_SHELL_TOP_TASK_TRACKING)
public void shellTopTaskTracker_onVisibleTasksChanged() throws Exception {
mRecentTasksControllerReal.registerRecentTasksListener(mRecentTasksListener);
- ActivityManager.RunningTaskInfo taskInfo = makeRunningTaskInfo(/* taskId= */10);
+ RunningTaskInfo taskInfo = makeRunningTaskInfo(/* taskId= */10);
mRecentTasksControllerReal.onVisibleTasksChanged(List.of(taskInfo));
verify(mRecentTasksListener, never()).onVisibleTasksChanged(any());
}
+ @Test
+ public void generateList_emptyTaskList_expectNoGroupedTasks() throws Exception {
+ assertTrue(mRecentTasksControllerReal.generateList(List.of(), "test").isEmpty());
+ }
+
+ @Test
+ public void generateList_excludePipTask() throws Exception {
+ RunningTaskInfo task1 = makeRunningTaskInfo(1);
+ RunningTaskInfo pipTask = makeRunningTaskInfo(2);
+ pipTask.configuration.windowConfiguration.setWindowingMode(
+ WINDOWING_MODE_PINNED);
+
+ ArrayList<GroupedTaskInfo> groupedTasks = mRecentTasksControllerReal.generateList(
+ List.of(task1, pipTask),
+ "test");
+
+ assertGroupedTasksListEquals(groupedTasks, List.of(List.of(task1.taskId)));
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_TASK_STACK_OBSERVER_IN_SHELL)
+ @EnableFlags(com.android.wm.shell.Flags.FLAG_ENABLE_SHELL_TOP_TASK_TRACKING)
+ public void generateList_fullscreen_noVisibleTasks_expectNoGrouping() throws Exception {
+ RunningTaskInfo task1 = makeRunningTaskInfo(1);
+ RunningTaskInfo task2 = makeRunningTaskInfo(2);
+ RunningTaskInfo task3 = makeRunningTaskInfo(3);
+ // Reset visible tasks list
+ mRecentTasksControllerReal.onVisibleTasksChanged(List.of());
+
+ // Generate a list with a number of fullscreen tasks
+ ArrayList<GroupedTaskInfo> groupedTasks = mRecentTasksControllerReal.generateList(
+ List.of(task1, task2, task3),
+ "test");
+
+ assertGroupedTasksListEquals(groupedTasks, List.of(
+ List.of(task1.taskId),
+ List.of(task2.taskId),
+ List.of(task3.taskId)));
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_TASK_STACK_OBSERVER_IN_SHELL)
+ @EnableFlags(com.android.wm.shell.Flags.FLAG_ENABLE_SHELL_TOP_TASK_TRACKING)
+ public void generateList_fullscreen_singleVisibleTask_expectNoGrouping() throws Exception {
+ RunningTaskInfo task1 = makeRunningTaskInfo(1);
+ RunningTaskInfo task2 = makeRunningTaskInfo(2);
+ RunningTaskInfo task3 = makeRunningTaskInfo(3);
+
+ // Reset visible tasks list
+ mRecentTasksControllerReal.onVisibleTasksChanged(List.of(task1));
+
+ // Generate a list with a number of fullscreen tasks
+ ArrayList<GroupedTaskInfo> groupedTasks = mRecentTasksControllerReal.generateList(
+ List.of(task1, task2, task3),
+ "test");
+
+ assertGroupedTasksListEquals(groupedTasks, List.of(
+ List.of(task1.taskId),
+ List.of(task2.taskId),
+ List.of(task3.taskId)));
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_TASK_STACK_OBSERVER_IN_SHELL)
+ @EnableFlags(com.android.wm.shell.Flags.FLAG_ENABLE_SHELL_TOP_TASK_TRACKING)
+ public void generateList_fullscreen_multipleVisibleTasks_expectGrouping() throws Exception {
+ RunningTaskInfo task1 = makeRunningTaskInfo(1);
+ RunningTaskInfo task2 = makeRunningTaskInfo(2);
+ RunningTaskInfo task3 = makeRunningTaskInfo(3);
+
+ // Reset visible tasks list
+ mRecentTasksControllerReal.onVisibleTasksChanged(List.of(task1, task2));
+
+ // Generate a list with a number of fullscreen tasks
+ ArrayList<GroupedTaskInfo> groupedTasks = mRecentTasksControllerReal.generateList(
+ List.of(task1, task2, task3),
+ "test");
+
+ assertGroupedTasksListEquals(groupedTasks, List.of(
+ List.of(task1.taskId),
+ List.of(task2.taskId),
+ List.of(task3.taskId)));
+ }
+
/**
* Helper to create a task with a given task id.
*/
private RecentTaskInfo makeTaskInfo(int taskId) {
RecentTaskInfo info = new RecentTaskInfo();
info.taskId = taskId;
-
+ info.realActivity = new ComponentName("testPackage", "testClass");
Intent intent = new Intent();
intent.setComponent(new ComponentName("com." + taskId, "Activity" + taskId));
info.baseIntent = intent;
-
info.lastNonFullscreenBounds = new Rect();
return info;
}
@@ -742,10 +829,14 @@
/**
* Helper to create a running task with a given task id.
*/
- private ActivityManager.RunningTaskInfo makeRunningTaskInfo(int taskId) {
- ActivityManager.RunningTaskInfo info = new ActivityManager.RunningTaskInfo();
+ private RunningTaskInfo makeRunningTaskInfo(int taskId) {
+ RunningTaskInfo info = new RunningTaskInfo();
info.taskId = taskId;
info.realActivity = new ComponentName("testPackage", "testClass");
+ Intent intent = new Intent();
+ intent.setComponent(new ComponentName("com." + taskId, "Activity" + taskId));
+ info.baseIntent = intent;
+ info.lastNonFullscreenBounds = new Rect();
return info;
}
@@ -759,37 +850,42 @@
/**
* Asserts that the recent tasks matches the given task ids.
+ * TODO(346588978): Separate out specific split verification during the iteration below
*
- * @param expectedTaskIds list of task ids that map to the flattened task ids of the tasks in
- * the grouped task list
+ * @param expectedTaskIds a list of expected grouped task ids (itself a list of ints)
*/
- private void assertGroupedTasksListEquals(List<GroupedTaskInfo> recentTasks,
- int... expectedTaskIds) {
- int[] flattenedTaskIds = new int[recentTasks.size() * 2];
- for (int i = 0; i < recentTasks.size(); i++) {
- GroupedTaskInfo pair = recentTasks.get(i);
- int taskId1 = pair.getTaskInfo1().taskId;
- flattenedTaskIds[2 * i] = taskId1;
- flattenedTaskIds[2 * i + 1] = pair.getTaskInfo2() != null
- ? pair.getTaskInfo2().taskId
- : -1;
+ private void assertGroupedTasksListEquals(List<GroupedTaskInfo> groupedTasks,
+ List<List<Integer>> expectedTaskIds) {
+ List<List<Integer>> foundTaskIds = new ArrayList<>();
+ for (int i = 0; i < groupedTasks.size(); i++) {
+ GroupedTaskInfo groupedTask = groupedTasks.get(i);
+ List<Integer> groupedTaskIds = groupedTask.getTaskInfoList().stream()
+ .map(taskInfo -> taskInfo.taskId)
+ .toList();
+ foundTaskIds.add(groupedTaskIds);
- if (pair.getTaskInfo2() != null) {
- assertNotNull(pair.getSplitBounds());
- int leftTopTaskId = pair.getSplitBounds().leftTopTaskId;
- int bottomRightTaskId = pair.getSplitBounds().rightBottomTaskId;
+ if (groupedTask.isBaseType(TYPE_SPLIT)) {
+ assertNotNull(groupedTask.getSplitBounds());
+ int leftTopTaskId = groupedTask.getSplitBounds().leftTopTaskId;
+ int bottomRightTaskId = groupedTask.getSplitBounds().rightBottomTaskId;
// Unclear if pairs are ordered by split position, most likely not.
- assertTrue(leftTopTaskId == taskId1
- || leftTopTaskId == pair.getTaskInfo2().taskId);
- assertTrue(bottomRightTaskId == taskId1
- || bottomRightTaskId == pair.getTaskInfo2().taskId);
- } else {
- assertNull(pair.getSplitBounds());
+ assertTrue(leftTopTaskId == groupedTaskIds.getFirst()
+ || leftTopTaskId == groupedTaskIds.getLast());
+ assertTrue(bottomRightTaskId == groupedTaskIds.getFirst()
+ || bottomRightTaskId == groupedTaskIds.getLast());
}
}
- assertArrayEquals("Expected: " + Arrays.toString(expectedTaskIds)
- + " Received: " + Arrays.toString(flattenedTaskIds),
- flattenedTaskIds,
- expectedTaskIds);
+ List<Integer> flattenedExpectedTaskIds = expectedTaskIds.stream()
+ .flatMap(List::stream)
+ .toList();
+ List<Integer> flattenedFoundTaskIds = foundTaskIds.stream()
+ .flatMap(List::stream)
+ .toList();
+ assertEquals("Expected: "
+ + flattenedExpectedTaskIds.stream().map(String::valueOf).collect(joining())
+ + " Received: "
+ + flattenedFoundTaskIds.stream().map(String::valueOf).collect(joining()),
+ flattenedExpectedTaskIds,
+ flattenedFoundTaskIds);
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt
index b44af47..908bc99 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt
@@ -31,6 +31,7 @@
import android.testing.TestableContext
import android.util.SparseArray
import android.view.Choreographer
+import android.view.Display
import android.view.Display.DEFAULT_DISPLAY
import android.view.IWindowManager
import android.view.InputChannel
@@ -157,6 +158,7 @@
protected val mockRecentsTransitionHandler = mock<RecentsTransitionHandler>()
protected val motionEvent = mock<MotionEvent>()
val displayLayout = mock<DisplayLayout>()
+ val display = mock<Display>()
protected lateinit var spyContext: TestableContext
private lateinit var desktopModeEventLogger: DesktopModeEventLogger
@@ -183,6 +185,7 @@
desktopModeEventLogger = mock<DesktopModeEventLogger>()
whenever(mockDesktopUserRepositories.current).thenReturn(mockDesktopRepository)
whenever(mockDisplayController.getDisplayContext(any())).thenReturn(spyContext)
+ whenever(mockDisplayController.getDisplay(any())).thenReturn(display)
whenever(mockDesktopUserRepositories.getProfile(anyInt()))
.thenReturn(mockDesktopRepository)
desktopModeWindowDecorViewModel = DesktopModeWindowDecorViewModel(
diff --git a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGraphBuilder.kt b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGraphBuilder.kt
index ce66a36..c0d2449 100644
--- a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGraphBuilder.kt
+++ b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGraphBuilder.kt
@@ -40,6 +40,7 @@
import com.android.settingslib.graph.proto.PreferenceProto.ActionTarget
import com.android.settingslib.graph.proto.PreferenceScreenProto
import com.android.settingslib.graph.proto.TextProto
+import com.android.settingslib.metadata.IntRangeValuePreference
import com.android.settingslib.metadata.PersistentPreference
import com.android.settingslib.metadata.PreferenceAvailabilityProvider
import com.android.settingslib.metadata.PreferenceHierarchy
@@ -50,7 +51,6 @@
import com.android.settingslib.metadata.PreferenceScreenRegistry
import com.android.settingslib.metadata.PreferenceSummaryProvider
import com.android.settingslib.metadata.PreferenceTitleProvider
-import com.android.settingslib.metadata.RangeValue
import com.android.settingslib.metadata.ReadWritePermit
import com.android.settingslib.preference.PreferenceScreenFactory
import com.android.settingslib.preference.PreferenceScreenProvider
@@ -407,22 +407,20 @@
) {
val storage = metadata.storage(context)
value = preferenceValueProto {
- when (metadata) {
- is RangeValue -> storage.getInt(metadata.key)?.let { intValue = it }
- else -> {}
- }
when (metadata.valueType) {
+ Int::class.javaObjectType -> storage.getInt(metadata.key)?.let { intValue = it }
Boolean::class.javaObjectType ->
storage.getBoolean(metadata.key)?.let { booleanValue = it }
Float::class.javaObjectType ->
storage.getFloat(metadata.key)?.let { floatValue = it }
+ else -> {}
}
}
}
if (flags.includeValueDescriptor()) {
valueDescriptor = preferenceValueDescriptorProto {
when (metadata) {
- is RangeValue -> rangeValue = rangeValueProto {
+ is IntRangeValuePreference -> rangeValue = rangeValueProto {
min = metadata.getMinValue(context)
max = metadata.getMaxValue(context)
step = metadata.getIncrementStep(context)
diff --git a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceSetterApi.kt b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceSetterApi.kt
index 4719064..728055c 100644
--- a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceSetterApi.kt
+++ b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceSetterApi.kt
@@ -26,12 +26,12 @@
import com.android.settingslib.ipc.ApiPermissionChecker
import com.android.settingslib.ipc.IntMessageCodec
import com.android.settingslib.ipc.MessageCodec
+import com.android.settingslib.metadata.IntRangeValuePreference
import com.android.settingslib.metadata.PersistentPreference
import com.android.settingslib.metadata.PreferenceAvailabilityProvider
import com.android.settingslib.metadata.PreferenceMetadata
import com.android.settingslib.metadata.PreferenceRestrictionProvider
import com.android.settingslib.metadata.PreferenceScreenRegistry
-import com.android.settingslib.metadata.RangeValue
import com.android.settingslib.metadata.ReadWritePermit
import com.android.settingslib.metadata.SensitivityLevel.Companion.HIGH_SENSITIVITY
import com.android.settingslib.metadata.SensitivityLevel.Companion.UNKNOWN_SENSITIVITY
@@ -157,7 +157,10 @@
val intValue = value.intValue
val resultCode = metadata.checkWritePermit(intValue)
if (resultCode != PreferenceSetterResult.OK) return resultCode
- if (metadata is RangeValue && !metadata.isValidValue(application, intValue)) {
+ if (
+ metadata is IntRangeValuePreference &&
+ !metadata.isValidValue(application, intValue)
+ ) {
return PreferenceSetterResult.INVALID_REQUEST
}
storage.setInt(key, intValue)
diff --git a/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PersistentPreference.kt b/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PersistentPreference.kt
index be705b5..63f1050 100644
--- a/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PersistentPreference.kt
+++ b/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PersistentPreference.kt
@@ -75,7 +75,7 @@
}
}
-/** Preference interface that has a value persisted in datastore. */
+/** Preference metadata that has a value persisted in datastore. */
interface PersistentPreference<T> : PreferenceMetadata {
/**
@@ -204,18 +204,3 @@
override fun getValue(context: Context, index: Int): Int =
context.resources.getIntArray(values)[index]
}
-
-/** Value is between a range. */
-interface RangeValue : ValueDescriptor {
- /** The lower bound (inclusive) of the range. */
- fun getMinValue(context: Context): Int
-
- /** The upper bound (inclusive) of the range. */
- fun getMaxValue(context: Context): Int
-
- /** The increment step within the range. 0 means unset, which implies step size is 1. */
- fun getIncrementStep(context: Context) = 0
-
- override fun isValidValue(context: Context, index: Int) =
- index in getMinValue(context)..getMaxValue(context)
-}
diff --git a/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceTypes.kt b/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceTypes.kt
index fecf3e5..edd45d3 100644
--- a/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceTypes.kt
+++ b/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceTypes.kt
@@ -16,6 +16,7 @@
package com.android.settingslib.metadata
+import android.content.Context
import androidx.annotation.StringRes
/** A persistent preference that has a boolean value. */
@@ -30,6 +31,24 @@
get() = Float::class.javaObjectType
}
+/** A persistent preference that has a int value between a range. */
+interface IntRangeValuePreference : PersistentPreference<Int>, ValueDescriptor {
+ override val valueType: Class<Int>
+ get() = Int::class.javaObjectType
+
+ /** The lower bound (inclusive) of the range. */
+ fun getMinValue(context: Context): Int
+
+ /** The upper bound (inclusive) of the range. */
+ fun getMaxValue(context: Context): Int
+
+ /** The increment step within the range. 0 means unset, which implies step size is 1. */
+ fun getIncrementStep(context: Context) = 0
+
+ override fun isValidValue(context: Context, index: Int) =
+ index in getMinValue(context)..getMaxValue(context)
+}
+
/** A preference that provides a two-state toggleable option. */
open class SwitchPreference
@JvmOverloads
diff --git a/packages/SettingsLib/OWNERS b/packages/SettingsLib/OWNERS
index e4bc7b4..04df308 100644
--- a/packages/SettingsLib/OWNERS
+++ b/packages/SettingsLib/OWNERS
@@ -3,6 +3,7 @@
chiujason@google.com
cipson@google.com
dsandler@android.com
+dswliu@google.com
edgarwang@google.com
evanlaird@google.com
jiannan@google.com
diff --git a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBinding.kt b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBinding.kt
index c61c6a5..59141c9 100644
--- a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBinding.kt
+++ b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBinding.kt
@@ -25,10 +25,10 @@
import androidx.preference.SeekBarPreference
import com.android.settingslib.metadata.DiscreteIntValue
import com.android.settingslib.metadata.DiscreteValue
+import com.android.settingslib.metadata.IntRangeValuePreference
import com.android.settingslib.metadata.PreferenceAvailabilityProvider
import com.android.settingslib.metadata.PreferenceMetadata
import com.android.settingslib.metadata.PreferenceScreenMetadata
-import com.android.settingslib.metadata.RangeValue
/** Binding of preference widget and preference metadata. */
interface PreferenceBinding {
@@ -101,7 +101,7 @@
} else {
preference.setEntryValues(values)
}
- } else if (preference is SeekBarPreference && this is RangeValue) {
+ } else if (preference is SeekBarPreference && this is IntRangeValuePreference) {
preference.min = getMinValue(context)
preference.max = getMaxValue(context)
preference.seekBarIncrement = getIncrementStep(context)
diff --git a/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_collapsable_textview.xml b/packages/SettingsLib/SettingsTheme/res/layout/settingslib_expressive_collapsable_textview.xml
similarity index 100%
rename from packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_collapsable_textview.xml
rename to packages/SettingsLib/SettingsTheme/res/layout/settingslib_expressive_collapsable_textview.xml
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v31/styles_expressive.xml b/packages/SettingsLib/SettingsTheme/res/values-v31/styles_expressive.xml
index b5f22b7..ec67d06 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-v31/styles_expressive.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-v31/styles_expressive.xml
@@ -104,6 +104,11 @@
<item name="android:layout_width">match_parent</item>
</style>
+ <style name="SettingslibTextAppearance.LinkableTextStyle.Expressive"
+ parent="@style/TextAppearance.SettingsLib.LabelLarge">
+ <item name="android:textColor">?android:attr/colorAccent</item>
+ </style>
+
<style name="SettingslibTextButtonStyle.Expressive"
parent="@style/Widget.Material3Expressive.Button.TextButton.Icon">
<item name="android:theme">@style/Theme.Material3.DynamicColors.DayNight</item>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v35/styles_expressive.xml b/packages/SettingsLib/SettingsTheme/res/values-v35/styles_expressive.xml
index 1a08568..3af88c4 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-v35/styles_expressive.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-v35/styles_expressive.xml
@@ -70,11 +70,6 @@
<item name="android:textAppearance">@style/TextAppearance.SettingsLib.TitleLarge.Emphasized</item>
</style>
- <style name="SettingslibTextAppearance.LinkableTextStyle.Expressive"
- parent="@style/TextAppearance.SettingsLib.LabelLarge">
- <item name="android:textColor">?android:attr/colorAccent</item>
- </style>
-
<style name="SettingsLibStatusBannerCardStyle">
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">wrap_content</item>
diff --git a/packages/SettingsLib/TopIntroPreference/res/layout-v33/top_intro_preference.xml b/packages/SettingsLib/TopIntroPreference/res/layout-v33/top_intro_preference.xml
deleted file mode 100644
index 195d45f..0000000
--- a/packages/SettingsLib/TopIntroPreference/res/layout-v33/top_intro_preference.xml
+++ /dev/null
@@ -1,41 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2022 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
- -->
-
-<LinearLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:paddingStart="?android:attr/listPreferredItemPaddingStart"
- android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
- android:paddingBottom="16dp"
- android:paddingTop="8dp"
- android:background="?android:attr/selectableItemBackground"
- android:clipToPadding="false">
-
- <TextView
- android:id="@android:id/title"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="start"
- android:textAlignment="viewStart"
- android:clickable="false"
- android:longClickable="false"
- android:maxLines="10"
- android:hyphenationFrequency="normalFast"
- android:lineBreakWordStyle="phrase"
- android:textAppearance="@style/TextAppearance.TopIntroText"/>
-</LinearLayout>
\ No newline at end of file
diff --git a/packages/SettingsLib/TopIntroPreference/res/layout-v35/settingslib_expressive_top_intro.xml b/packages/SettingsLib/TopIntroPreference/res/layout/settingslib_expressive_top_intro.xml
similarity index 95%
rename from packages/SettingsLib/TopIntroPreference/res/layout-v35/settingslib_expressive_top_intro.xml
rename to packages/SettingsLib/TopIntroPreference/res/layout/settingslib_expressive_top_intro.xml
index fb13ef7..834814c 100644
--- a/packages/SettingsLib/TopIntroPreference/res/layout-v35/settingslib_expressive_top_intro.xml
+++ b/packages/SettingsLib/TopIntroPreference/res/layout/settingslib_expressive_top_intro.xml
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
- Copyright (C) 2024 The Android Open Source Project
+ Copyright (C) 2025 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
diff --git a/packages/SettingsLib/TopIntroPreference/res/layout/top_intro_preference.xml b/packages/SettingsLib/TopIntroPreference/res/layout/top_intro_preference.xml
deleted file mode 100644
index bee6bc7..0000000
--- a/packages/SettingsLib/TopIntroPreference/res/layout/top_intro_preference.xml
+++ /dev/null
@@ -1,39 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- 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.
- -->
-
-<LinearLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:paddingStart="?android:attr/listPreferredItemPaddingStart"
- android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
- android:paddingBottom="16dp"
- android:paddingTop="8dp"
- android:background="?android:attr/selectableItemBackground"
- android:clipToPadding="false">
-
- <TextView
- android:id="@android:id/title"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="start"
- android:textAlignment="viewStart"
- android:clickable="false"
- android:longClickable="false"
- android:maxLines="10"
- android:textAppearance="@style/TextAppearance.TopIntroText"/>
-</LinearLayout>
\ No newline at end of file
diff --git a/packages/SettingsLib/TopIntroPreference/src/com/android/settingslib/widget/TopIntroPreference.kt b/packages/SettingsLib/TopIntroPreference/src/com/android/settingslib/widget/TopIntroPreference.kt
index 4428480..08ba836 100644
--- a/packages/SettingsLib/TopIntroPreference/src/com/android/settingslib/widget/TopIntroPreference.kt
+++ b/packages/SettingsLib/TopIntroPreference/src/com/android/settingslib/widget/TopIntroPreference.kt
@@ -17,20 +17,20 @@
package com.android.settingslib.widget
import android.content.Context
-import android.os.Build
import android.text.TextUtils
import android.util.AttributeSet
import android.view.View
-import androidx.annotation.RequiresApi
import androidx.preference.Preference
import androidx.preference.PreferenceViewHolder
import com.android.settingslib.widget.preference.topintro.R
-open class TopIntroPreference @JvmOverloads constructor(
+open class TopIntroPreference
+@JvmOverloads
+constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0,
- defStyleRes: Int = 0
+ defStyleRes: Int = 0,
) : Preference(context, attrs, defStyleAttr, defStyleRes), GroupSectionDividerMixin {
private var isCollapsable: Boolean = false
@@ -40,25 +40,18 @@
private var learnMoreText: CharSequence? = null
init {
- if (SettingsThemeHelper.isExpressiveTheme(context)) {
- layoutResource = R.layout.settingslib_expressive_top_intro
- initAttributes(context, attrs, defStyleAttr)
- } else {
- layoutResource = R.layout.top_intro_preference
- }
+ layoutResource = R.layout.settingslib_expressive_top_intro
+ initAttributes(context, attrs, defStyleAttr)
+
isSelectable = false
}
private fun initAttributes(context: Context, attrs: AttributeSet?, defStyleAttr: Int) {
- context.obtainStyledAttributes(
- attrs,
- COLLAPSABLE_TEXT_VIEW_ATTRS, defStyleAttr, 0
- ).apply {
+ context.obtainStyledAttributes(attrs, COLLAPSABLE_TEXT_VIEW_ATTRS, defStyleAttr, 0).apply {
isCollapsable = getBoolean(IS_COLLAPSABLE, false)
- minLines = getInt(
- MIN_LINES,
- if (isCollapsable) DEFAULT_MIN_LINES else DEFAULT_MAX_LINES
- ).coerceIn(1, DEFAULT_MAX_LINES)
+ minLines =
+ getInt(MIN_LINES, if (isCollapsable) DEFAULT_MIN_LINES else DEFAULT_MAX_LINES)
+ .coerceIn(1, DEFAULT_MAX_LINES)
recycle()
}
}
@@ -68,10 +61,6 @@
holder.isDividerAllowedAbove = false
holder.isDividerAllowedBelow = false
- if (!SettingsThemeHelper.isExpressiveTheme(context)) {
- return
- }
-
(holder.findViewById(R.id.collapsable_text_view) as? CollapsableTextView)?.apply {
setCollapsable(isCollapsable)
setMinLines(minLines)
@@ -89,9 +78,9 @@
/**
* Sets whether the text view is collapsable.
+ *
* @param collapsable True if the text view should be collapsable, false otherwise.
*/
- @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
fun setCollapsable(collapsable: Boolean) {
isCollapsable = collapsable
notifyChanged()
@@ -99,9 +88,9 @@
/**
* Sets the minimum number of lines to display when collapsed.
+ *
* @param lines The minimum number of lines.
*/
- @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
fun setMinLines(lines: Int) {
minLines = lines.coerceIn(1, DEFAULT_MAX_LINES)
notifyChanged()
@@ -109,9 +98,9 @@
/**
* Sets the action when clicking on the hyperlink in the text.
+ *
* @param listener The click listener for hyperlink.
*/
- @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
fun setHyperlinkListener(listener: View.OnClickListener) {
if (hyperlinkListener != listener) {
hyperlinkListener = listener
@@ -121,9 +110,9 @@
/**
* Sets the action when clicking on the learn more view.
+ *
* @param listener The click listener for learn more.
*/
- @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
fun setLearnMoreAction(listener: View.OnClickListener) {
if (learnMoreListener != listener) {
learnMoreListener = listener
@@ -133,9 +122,9 @@
/**
* Sets the text of learn more view.
+ *
* @param text The text of learn more.
*/
- @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
fun setLearnMoreText(text: CharSequence) {
if (!TextUtils.equals(learnMoreText, text)) {
learnMoreText = text
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
index 640829e..d936a5c6 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
@@ -287,5 +287,9 @@
Settings.Secure.ADVANCED_PROTECTION_MODE,
Settings.Secure.ACCESSIBILITY_KEY_GESTURE_TARGETS,
Settings.Secure.EM_VALUE,
+ Settings.Secure.FACE_APP_ENABLED,
+ Settings.Secure.FACE_KEYGUARD_ENABLED,
+ Settings.Secure.FINGERPRINT_APP_ENABLED,
+ Settings.Secure.FINGERPRINT_KEYGUARD_ENABLED,
};
}
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
index 1f56f10..cf0447f 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
@@ -117,6 +117,7 @@
Settings.System.TOUCHPAD_TAP_TO_CLICK,
Settings.System.TOUCHPAD_TAP_DRAGGING,
Settings.System.TOUCHPAD_RIGHT_CLICK_ZONE,
+ Settings.System.TOUCHPAD_ACCELERATION_ENABLED,
Settings.System.CAMERA_FLASH_NOTIFICATION,
Settings.System.SCREEN_FLASH_NOTIFICATION,
Settings.System.SCREEN_FLASH_NOTIFICATION_COLOR,
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index 9fd0cc8..919c3c4 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -456,5 +456,9 @@
new InclusiveIntegerRangeValidator(0, 1));
VALIDATORS.put(Secure.ADVANCED_PROTECTION_MODE, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.DISABLE_ADAPTIVE_AUTH_LIMIT_LOCK, BOOLEAN_VALIDATOR);
+ VALIDATORS.put(Secure.FACE_APP_ENABLED, BOOLEAN_VALIDATOR);
+ VALIDATORS.put(Secure.FACE_KEYGUARD_ENABLED, BOOLEAN_VALIDATOR);
+ VALIDATORS.put(Secure.FINGERPRINT_APP_ENABLED, BOOLEAN_VALIDATOR);
+ VALIDATORS.put(Secure.FINGERPRINT_KEYGUARD_ENABLED, BOOLEAN_VALIDATOR);
}
}
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
index 4d98a11..4f649ed 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
@@ -234,6 +234,7 @@
VALIDATORS.put(System.TOUCHPAD_TAP_DRAGGING, BOOLEAN_VALIDATOR);
VALIDATORS.put(System.TOUCHPAD_RIGHT_CLICK_ZONE, BOOLEAN_VALIDATOR);
VALIDATORS.put(System.TOUCHPAD_SYSTEM_GESTURES, BOOLEAN_VALIDATOR);
+ VALIDATORS.put(System.TOUCHPAD_ACCELERATION_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(System.LOCK_TO_APP_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(
System.EGG_MODE,
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 0224b29..0a7d880 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -521,6 +521,7 @@
"androidx.compose.animation_animation-graphics",
"androidx.lifecycle_lifecycle-viewmodel-compose",
"kairos",
+ "aconfig_settings_flags_lib",
],
libs: [
"keepanno-annotations",
@@ -706,6 +707,7 @@
"androidx.lifecycle_lifecycle-viewmodel-compose",
"TraceurCommon",
"Traceur-res",
+ "aconfig_settings_flags_lib",
],
}
@@ -714,6 +716,9 @@
use_resource_processor: true,
manifest: "tests/AndroidManifest-base.xml",
resource_dirs: [],
+
+ kotlin_lang_version: "1.9",
+
additional_manifests: ["tests/AndroidManifest.xml"],
srcs: [
"tests/src/**/*.kt",
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/gesture/effect/OffsetOverscrollEffect.kt b/packages/SystemUI/compose/core/src/com/android/compose/gesture/effect/OffsetOverscrollEffect.kt
index d992403..fc01c78 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/gesture/effect/OffsetOverscrollEffect.kt
+++ b/packages/SystemUI/compose/core/src/com/android/compose/gesture/effect/OffsetOverscrollEffect.kt
@@ -18,10 +18,10 @@
import androidx.annotation.VisibleForTesting
import androidx.compose.animation.core.AnimationSpec
-import androidx.compose.animation.core.Spring
-import androidx.compose.animation.core.spring
import androidx.compose.foundation.OverscrollEffect
import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
+import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
@@ -41,7 +41,7 @@
class OffsetOverscrollEffect(
orientation: Orientation,
animationScope: CoroutineScope,
- animationSpec: AnimationSpec<Float> = DefaultAnimationSpec,
+ animationSpec: AnimationSpec<Float>,
) : BaseContentOverscrollEffect(orientation, animationScope, animationSpec) {
private var _node: DelegatableNode = newNode()
override val node: DelegatableNode
@@ -71,13 +71,6 @@
companion object {
private val MaxDistance = 400.dp
- internal val DefaultAnimationSpec =
- spring(
- stiffness = Spring.StiffnessLow,
- dampingRatio = Spring.DampingRatioLowBouncy,
- visibilityThreshold = 0.5f,
- )
-
@VisibleForTesting
fun computeOffset(density: Density, overscrollDistance: Float): Int {
val maxDistancePx = with(density) { MaxDistance.toPx() }
@@ -87,10 +80,11 @@
}
}
+@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
fun rememberOffsetOverscrollEffect(
orientation: Orientation,
- animationSpec: AnimationSpec<Float> = OffsetOverscrollEffect.DefaultAnimationSpec,
+ animationSpec: AnimationSpec<Float> = MaterialTheme.motionScheme.defaultSpatialSpec(),
): OffsetOverscrollEffect {
val animationScope = rememberCoroutineScope()
return remember(orientation, animationScope, animationSpec) {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt
index d43b596..456edaf 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt
@@ -89,9 +89,9 @@
import com.android.compose.animation.Easings
import com.android.compose.animation.scene.ContentScope
import com.android.compose.animation.scene.ElementKey
-import com.android.compose.animation.scene.MutableSceneTransitionLayoutState
import com.android.compose.animation.scene.SceneKey
import com.android.compose.animation.scene.SceneTransitionLayout
+import com.android.compose.animation.scene.rememberMutableSceneTransitionLayoutState
import com.android.compose.animation.scene.transitions
import com.android.compose.windowsizeclass.LocalWindowSizeClass
import com.android.systemui.bouncer.shared.model.BouncerActionButtonModel
@@ -494,7 +494,7 @@
val currentSceneKey =
if (isSplitAroundTheFold) SceneKeys.SplitSceneKey else SceneKeys.ContiguousSceneKey
- val state = remember { MutableSceneTransitionLayoutState(currentSceneKey, SceneTransitions) }
+ val state = rememberMutableSceneTransitionLayoutState(currentSceneKey, SceneTransitions)
// Update state whenever currentSceneKey has changed.
LaunchedEffect(state, currentSceneKey) {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
index 9b5ff7f..824f5a2 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
@@ -43,6 +43,7 @@
import com.android.compose.animation.scene.SceneTransitionLayout
import com.android.compose.animation.scene.Swipe
import com.android.compose.animation.scene.observableTransitionState
+import com.android.compose.animation.scene.rememberMutableSceneTransitionLayoutState
import com.android.compose.animation.scene.transitions
import com.android.compose.modifiers.thenIf
import com.android.systemui.communal.shared.model.CommunalBackgroundType
@@ -165,13 +166,13 @@
viewModel.communalBackground.collectAsStateWithLifecycle(
initialValue = CommunalBackgroundType.ANIMATED
)
- val state: MutableSceneTransitionLayoutState = remember {
- MutableSceneTransitionLayoutState(
+ val state: MutableSceneTransitionLayoutState =
+ rememberMutableSceneTransitionLayoutState(
initialScene = currentSceneKey,
canChangeScene = { _ -> viewModel.canChangeScene() },
transitions = if (viewModel.v2FlagEnabled()) sceneTransitionsV2 else sceneTransitions,
)
- }
+
val isUiBlurred by viewModel.isUiBlurred.collectAsStateWithLifecycle()
val detector = remember { CommunalSwipeDetector() }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/TopAreaSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/TopAreaSection.kt
index 79cf24b..410499a 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/TopAreaSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/TopAreaSection.kt
@@ -27,7 +27,6 @@
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
-import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
@@ -36,7 +35,7 @@
import androidx.compose.ui.unit.IntOffset
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.compose.animation.scene.ContentScope
-import com.android.compose.animation.scene.MutableSceneTransitionLayoutState
+import com.android.compose.animation.scene.rememberMutableSceneTransitionLayoutState
import com.android.compose.modifiers.thenIf
import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
import com.android.systemui.keyguard.ui.composable.blueprint.ClockScenes.largeClockScene
@@ -82,9 +81,11 @@
WeatherClockScenes.splitShadeLargeClockScene
}
- val state = remember {
- MutableSceneTransitionLayoutState(currentScene, ClockTransition.defaultClockTransitions)
- }
+ val state =
+ rememberMutableSceneTransitionLayoutState(
+ currentScene,
+ ClockTransition.defaultClockTransitions,
+ )
// Update state whenever currentSceneKey has changed.
LaunchedEffect(state, currentScene) {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt
index a6a6362..0ca7a6a 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt
@@ -36,7 +36,6 @@
import androidx.compose.ui.platform.LocalView
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.compose.animation.scene.ContentKey
-import com.android.compose.animation.scene.MutableSceneTransitionLayoutState
import com.android.compose.animation.scene.OverlayKey
import com.android.compose.animation.scene.SceneKey
import com.android.compose.animation.scene.SceneTransitionLayout
@@ -44,6 +43,7 @@
import com.android.compose.animation.scene.UserAction
import com.android.compose.animation.scene.UserActionResult
import com.android.compose.animation.scene.observableTransitionState
+import com.android.compose.animation.scene.rememberMutableSceneTransitionLayoutState
import com.android.systemui.lifecycle.rememberActivated
import com.android.systemui.qs.ui.adapter.QSSceneAdapter
import com.android.systemui.qs.ui.composable.QuickSettingsTheme
@@ -94,29 +94,27 @@
val sceneJankMonitor =
rememberActivated(traceName = "sceneJankMonitor") { sceneJankMonitorFactory.create() }
- val state: MutableSceneTransitionLayoutState =
- remember(view, sceneJankMonitor) {
- MutableSceneTransitionLayoutState(
- initialScene = initialSceneKey,
- canChangeScene = { toScene -> viewModel.canChangeScene(toScene) },
- transitions = sceneTransitions,
- onTransitionStart = { transition ->
- sceneJankMonitor.onTransitionStart(
- view = view,
- from = transition.fromContent,
- to = transition.toContent,
- cuj = transition.cuj,
- )
- },
- onTransitionEnd = { transition ->
- sceneJankMonitor.onTransitionEnd(
- from = transition.fromContent,
- to = transition.toContent,
- cuj = transition.cuj,
- )
- },
- )
- }
+ val state =
+ rememberMutableSceneTransitionLayoutState(
+ initialScene = initialSceneKey,
+ canChangeScene = { toScene -> viewModel.canChangeScene(toScene) },
+ transitions = sceneTransitions,
+ onTransitionStart = { transition ->
+ sceneJankMonitor.onTransitionStart(
+ view = view,
+ from = transition.fromContent,
+ to = transition.toContent,
+ cuj = transition.cuj,
+ )
+ },
+ onTransitionEnd = { transition ->
+ sceneJankMonitor.onTransitionEnd(
+ from = transition.fromContent,
+ to = transition.toContent,
+ cuj = transition.cuj,
+ )
+ },
+ )
DisposableEffect(state) {
val dataSource = SceneTransitionLayoutDataSource(state, coroutineScope)
@@ -179,6 +177,12 @@
}
}
) {
+ SceneRevealScrim(
+ viewModel = viewModel.lightRevealScrim,
+ wallpaperViewModel = viewModel.wallpaperViewModel,
+ modifier = Modifier.fillMaxSize(),
+ )
+
SceneTransitionLayout(
state = state,
modifier = modifier.fillMaxSize(),
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt
index aa8b4ae..d7545cb 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt
@@ -1,6 +1,5 @@
package com.android.systemui.scene.ui.composable
-import androidx.compose.animation.core.spring
import com.android.compose.animation.scene.TransitionKey
import com.android.compose.animation.scene.transitions
import com.android.internal.jank.Cuj
@@ -48,9 +47,6 @@
val SceneContainerTransitions = transitions {
interruptionHandler = SceneContainerInterruptionHandler
- defaultMotionSpatialSpec =
- spring(stiffness = 300f, dampingRatio = 0.8f, visibilityThreshold = 0.5f)
-
// Scene transitions
from(Scenes.Bouncer, to = Scenes.Gone) { bouncerToGoneTransition() }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneRevealScrim.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneRevealScrim.kt
new file mode 100644
index 0000000..a1f89fcb
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneRevealScrim.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.scene.ui.composable
+
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.viewinterop.AndroidView
+import com.android.systemui.keyguard.ui.binder.LightRevealScrimViewBinder
+import com.android.systemui.keyguard.ui.viewmodel.LightRevealScrimViewModel
+import com.android.systemui.statusbar.LightRevealScrim
+import com.android.systemui.wallpapers.ui.viewmodel.WallpaperViewModel
+
+@Composable
+fun SceneRevealScrim(
+ viewModel: LightRevealScrimViewModel,
+ wallpaperViewModel: WallpaperViewModel,
+ modifier: Modifier = Modifier,
+) {
+ AndroidView(
+ factory = { context ->
+ LightRevealScrim(context).apply {
+ LightRevealScrimViewBinder.bind(
+ revealScrim = this,
+ viewModel = viewModel,
+ wallpaperViewModel = wallpaperViewModel,
+ )
+ }
+ },
+ modifier = modifier,
+ )
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToSplitShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToSplitShadeTransition.kt
index e30e7d3..b53c4e238 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToSplitShadeTransition.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToSplitShadeTransition.kt
@@ -16,8 +16,6 @@
package com.android.systemui.scene.ui.composable.transitions
-import androidx.compose.animation.core.Spring
-import androidx.compose.animation.core.spring
import androidx.compose.animation.core.tween
import com.android.compose.animation.scene.TransitionBuilder
import com.android.compose.animation.scene.UserActionDistance
@@ -30,11 +28,6 @@
fun TransitionBuilder.goneToSplitShadeTransition(durationScale: Double = 1.0) {
spec = tween(durationMillis = (DefaultDuration * durationScale).inWholeMilliseconds.toInt())
- motionSpatialSpec =
- spring(
- stiffness = Spring.StiffnessMediumLow,
- visibilityThreshold = Shade.Dimensions.ScrimVisibilityThreshold,
- )
distance = UserActionDistance { fromContent, _, _ ->
val fromContentSize = checkNotNull(fromContent.targetSize())
fromContentSize.height.toFloat() * 2 / 3f
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToSplitShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToSplitShadeTransition.kt
index 1a243ca..6c8885e 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToSplitShadeTransition.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToSplitShadeTransition.kt
@@ -16,8 +16,6 @@
package com.android.systemui.scene.ui.composable.transitions
-import androidx.compose.animation.core.Spring
-import androidx.compose.animation.core.spring
import androidx.compose.animation.core.tween
import com.android.compose.animation.scene.TransitionBuilder
import com.android.compose.animation.scene.UserActionDistance
@@ -29,11 +27,6 @@
fun TransitionBuilder.lockscreenToSplitShadeTransition(durationScale: Double = 1.0) {
spec = tween(durationMillis = (DefaultDuration * durationScale).inWholeMilliseconds.toInt())
- motionSpatialSpec =
- spring(
- stiffness = Spring.StiffnessMediumLow,
- visibilityThreshold = Shade.Dimensions.ScrimVisibilityThreshold,
- )
distance = UserActionDistance { fromContent, _, _ ->
val fromContentSize = checkNotNull(fromContent.targetSize())
fromContentSize.height.toFloat() * 2 / 3f
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromNotificationsShadeToQuickSettingsShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromNotificationsShadeToQuickSettingsShadeTransition.kt
index a9af95b..9a7f99f 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromNotificationsShadeToQuickSettingsShadeTransition.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromNotificationsShadeToQuickSettingsShadeTransition.kt
@@ -16,22 +16,14 @@
package com.android.systemui.scene.ui.composable.transitions
-import androidx.compose.animation.core.Spring
-import androidx.compose.animation.core.spring
import androidx.compose.animation.core.tween
import com.android.compose.animation.scene.TransitionBuilder
-import com.android.systemui.shade.ui.composable.Shade
import kotlin.time.Duration.Companion.milliseconds
fun TransitionBuilder.notificationsShadeToQuickSettingsShadeTransition(
durationScale: Double = 1.0
) {
spec = tween(durationMillis = (DefaultDuration * durationScale).inWholeMilliseconds.toInt())
- motionSpatialSpec =
- spring(
- stiffness = Spring.StiffnessMediumLow,
- visibilityThreshold = Shade.Dimensions.ScrimVisibilityThreshold,
- )
}
private val DefaultDuration = 300.milliseconds
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToNotificationsShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToNotificationsShadeTransition.kt
index ddea585..019639d 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToNotificationsShadeTransition.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToNotificationsShadeTransition.kt
@@ -16,8 +16,6 @@
package com.android.systemui.scene.ui.composable.transitions
-import androidx.compose.animation.core.Spring
-import androidx.compose.animation.core.spring
import androidx.compose.animation.core.tween
import com.android.compose.animation.scene.Edge
import com.android.compose.animation.scene.TransitionBuilder
@@ -26,16 +24,10 @@
import com.android.systemui.notifications.ui.composable.NotificationsShade
import com.android.systemui.scene.shared.model.Overlays
import com.android.systemui.shade.ui.composable.OverlayShade
-import com.android.systemui.shade.ui.composable.Shade
import kotlin.time.Duration.Companion.milliseconds
fun TransitionBuilder.toNotificationsShadeTransition(durationScale: Double = 1.0) {
spec = tween(durationMillis = (DefaultDuration * durationScale).inWholeMilliseconds.toInt())
- motionSpatialSpec =
- spring(
- stiffness = Spring.StiffnessMediumLow,
- visibilityThreshold = Shade.Dimensions.ScrimVisibilityThreshold,
- )
// Ensure the clock isn't clipped by the shade outline during the transition from lockscreen.
sharedElement(
ClockElementKeys.smallClockElementKey,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToQuickSettingsShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToQuickSettingsShadeTransition.kt
index e477a41..faccf14 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToQuickSettingsShadeTransition.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToQuickSettingsShadeTransition.kt
@@ -16,23 +16,15 @@
package com.android.systemui.scene.ui.composable.transitions
-import androidx.compose.animation.core.Spring
-import androidx.compose.animation.core.spring
import androidx.compose.animation.core.tween
import com.android.compose.animation.scene.Edge
import com.android.compose.animation.scene.TransitionBuilder
import com.android.compose.animation.scene.UserActionDistance
import com.android.systemui.shade.ui.composable.OverlayShade
-import com.android.systemui.shade.ui.composable.Shade
import kotlin.time.Duration.Companion.milliseconds
fun TransitionBuilder.toQuickSettingsShadeTransition(durationScale: Double = 1.0) {
spec = tween(durationMillis = (DefaultDuration * durationScale).inWholeMilliseconds.toInt())
- motionSpatialSpec =
- spring(
- stiffness = Spring.StiffnessMediumLow,
- visibilityThreshold = Shade.Dimensions.ScrimVisibilityThreshold,
- )
distance = UserActionDistance { fromContent, _, _ ->
val fromContentSize = checkNotNull(fromContent.targetSize())
fromContentSize.height.toFloat() * 2 / 3f
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToShadeTransition.kt
index 4db4934..2ed296b 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToShadeTransition.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToShadeTransition.kt
@@ -16,8 +16,6 @@
package com.android.systemui.scene.ui.composable.transitions
-import androidx.compose.animation.core.Spring
-import androidx.compose.animation.core.spring
import androidx.compose.animation.core.tween
import com.android.compose.animation.scene.Edge
import com.android.compose.animation.scene.TransitionBuilder
@@ -32,11 +30,6 @@
fun TransitionBuilder.toShadeTransition(durationScale: Double = 1.0) {
spec = tween(durationMillis = (DefaultDuration * durationScale).inWholeMilliseconds.toInt())
- motionSpatialSpec =
- spring(
- stiffness = Spring.StiffnessMediumLow,
- visibilityThreshold = Shade.Dimensions.ScrimVisibilityThreshold,
- )
distance = UserActionDistance { _, _, _ ->
Notifications.Elements.NotificationScrim.targetOffset(Scenes.Shade)?.y ?: 0f
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateContent.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateContent.kt
index d876606..b04d89d 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateContent.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateContent.kt
@@ -19,6 +19,7 @@
import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.AnimationVector1D
import androidx.compose.animation.core.SpringSpec
+import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import com.android.compose.animation.scene.content.state.TransitionState
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
@@ -32,7 +33,10 @@
): Job {
oneOffAnimation.onRun = {
// Animate the progress to its target value.
- val animationSpec = transition.transformationSpec.progressSpec
+ @OptIn(ExperimentalMaterial3ExpressiveApi::class)
+ val animationSpec =
+ transition.transformationSpec.progressSpec
+ ?: layoutState.motionScheme.defaultSpatialSpec()
val visibilityThreshold =
(animationSpec as? SpringSpec)?.visibilityThreshold ?: ProgressVisibilityThreshold
val replacedTransition = transition.replacedTransition
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
index 8153586..5b275a5 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
@@ -14,14 +14,22 @@
* limitations under the License.
*/
+@file:OptIn(ExperimentalMaterial3ExpressiveApi::class)
+
package com.android.compose.animation.scene
import android.util.Log
import androidx.annotation.VisibleForTesting
+import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.MotionScheme
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.SideEffect
import androidx.compose.runtime.Stable
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.util.fastAll
import androidx.compose.ui.util.fastAny
@@ -226,6 +234,7 @@
*/
fun MutableSceneTransitionLayoutState(
initialScene: SceneKey,
+ motionScheme: MotionScheme,
transitions: SceneTransitions = SceneTransitions.Empty,
initialOverlays: Set<OverlayKey> = emptySet(),
canChangeScene: (SceneKey) -> Boolean = { true },
@@ -237,6 +246,7 @@
): MutableSceneTransitionLayoutState {
return MutableSceneTransitionLayoutStateImpl(
initialScene,
+ motionScheme,
transitions,
initialOverlays,
canChangeScene,
@@ -248,19 +258,62 @@
)
}
+@Composable
+fun rememberMutableSceneTransitionLayoutState(
+ initialScene: SceneKey,
+ transitions: SceneTransitions = SceneTransitions.Empty,
+ initialOverlays: Set<OverlayKey> = emptySet(),
+ canChangeScene: (SceneKey) -> Boolean = { true },
+ canShowOverlay: (OverlayKey) -> Boolean = { true },
+ canHideOverlay: (OverlayKey) -> Boolean = { true },
+ canReplaceOverlay: (from: OverlayKey, to: OverlayKey) -> Boolean = { _, _ -> true },
+ onTransitionStart: (TransitionState.Transition) -> Unit = {},
+ onTransitionEnd: (TransitionState.Transition) -> Unit = {},
+): MutableSceneTransitionLayoutState {
+ val motionScheme = MaterialTheme.motionScheme
+ val layoutState = remember {
+ MutableSceneTransitionLayoutStateImpl(
+ initialScene = initialScene,
+ motionScheme = motionScheme,
+ transitions = transitions,
+ initialOverlays = initialOverlays,
+ canChangeScene = canChangeScene,
+ canShowOverlay = canShowOverlay,
+ canHideOverlay = canHideOverlay,
+ canReplaceOverlay = canReplaceOverlay,
+ onTransitionStart = onTransitionStart,
+ onTransitionEnd = onTransitionEnd,
+ )
+ }
+
+ SideEffect {
+ layoutState.transitions = transitions
+ layoutState.motionScheme = motionScheme
+ layoutState.canChangeScene = canChangeScene
+ layoutState.canShowOverlay = canShowOverlay
+ layoutState.canHideOverlay = canHideOverlay
+ layoutState.canReplaceOverlay = canReplaceOverlay
+ layoutState.onTransitionStart = onTransitionStart
+ layoutState.onTransitionEnd = onTransitionEnd
+ }
+ return layoutState
+}
+
/** A [MutableSceneTransitionLayoutState] that holds the value for the current scene. */
+@OptIn(ExperimentalMaterial3ExpressiveApi::class)
internal class MutableSceneTransitionLayoutStateImpl(
initialScene: SceneKey,
+ internal var motionScheme: MotionScheme,
override var transitions: SceneTransitions = transitions {},
initialOverlays: Set<OverlayKey> = emptySet(),
- internal val canChangeScene: (SceneKey) -> Boolean = { true },
- internal val canShowOverlay: (OverlayKey) -> Boolean = { true },
- internal val canHideOverlay: (OverlayKey) -> Boolean = { true },
- internal val canReplaceOverlay: (from: OverlayKey, to: OverlayKey) -> Boolean = { _, _ ->
+ internal var canChangeScene: (SceneKey) -> Boolean = { true },
+ internal var canShowOverlay: (OverlayKey) -> Boolean = { true },
+ internal var canHideOverlay: (OverlayKey) -> Boolean = { true },
+ internal var canReplaceOverlay: (from: OverlayKey, to: OverlayKey) -> Boolean = { _, _ ->
true
},
- private val onTransitionStart: (TransitionState.Transition) -> Unit = {},
- private val onTransitionEnd: (TransitionState.Transition) -> Unit = {},
+ internal var onTransitionStart: (TransitionState.Transition) -> Unit = {},
+ internal var onTransitionEnd: (TransitionState.Transition) -> Unit = {},
) : MutableSceneTransitionLayoutState {
private val creationThread: Thread = Thread.currentThread()
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt
index d50304d4..52c9ddc 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt
@@ -17,10 +17,7 @@
package com.android.compose.animation.scene
import androidx.compose.animation.core.AnimationSpec
-import androidx.compose.animation.core.Spring
-import androidx.compose.animation.core.SpringSpec
import androidx.compose.animation.core.snap
-import androidx.compose.animation.core.spring
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.util.fastForEach
@@ -34,7 +31,6 @@
/** The transitions configuration of a [SceneTransitionLayout]. */
class SceneTransitions
internal constructor(
- internal val defaultMotionSpatialSpec: SpringSpec<Float>,
internal val transitionSpecs: List<TransitionSpecImpl>,
internal val interruptionHandler: InterruptionHandler,
) {
@@ -123,16 +119,8 @@
)
companion object {
- internal val DefaultSwipeSpec =
- spring(
- stiffness = Spring.StiffnessMediumLow,
- dampingRatio = Spring.DampingRatioLowBouncy,
- visibilityThreshold = OffsetVisibilityThreshold,
- )
-
val Empty =
SceneTransitions(
- defaultMotionSpatialSpec = DefaultSwipeSpec,
transitionSpecs = emptyList(),
interruptionHandler = DefaultInterruptionHandler,
)
@@ -188,15 +176,7 @@
* The [AnimationSpec] used to animate the associated transition progress from `0` to `1` when
* the transition is triggered (i.e. it is not gesture-based).
*/
- val progressSpec: AnimationSpec<Float>
-
- /**
- * The [SpringSpec] used to animate the associated transition progress when the transition was
- * started by a swipe and is now animating back to a scene because the user lifted their finger.
- *
- * If `null`, then the [SceneTransitions.defaultMotionSpatialSpec] will be used.
- */
- val motionSpatialSpec: AnimationSpec<Float>?
+ val progressSpec: AnimationSpec<Float>?
/**
* The distance it takes for this transition to animate from 0% to 100% when it is driven by a
@@ -213,7 +193,6 @@
internal val Empty =
TransformationSpecImpl(
progressSpec = snap(),
- motionSpatialSpec = null,
distance = null,
transformationMatchers = emptyList(),
)
@@ -246,7 +225,6 @@
val reverse = transformationSpec.invoke(transition)
TransformationSpecImpl(
progressSpec = reverse.progressSpec,
- motionSpatialSpec = reverse.motionSpatialSpec,
distance = reverse.distance,
transformationMatchers =
reverse.transformationMatchers.map {
@@ -275,8 +253,7 @@
* [ElementTransformations].
*/
internal class TransformationSpecImpl(
- override val progressSpec: AnimationSpec<Float>,
- override val motionSpatialSpec: SpringSpec<Float>?,
+ override val progressSpec: AnimationSpec<Float>?,
override val distance: UserActionDistance?,
override val transformationMatchers: List<TransformationMatcher>,
) : TransformationSpec {
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeAnimation.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeAnimation.kt
index 4137f5f..3bd37ad 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeAnimation.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeAnimation.kt
@@ -14,12 +14,15 @@
* limitations under the License.
*/
+@file:OptIn(ExperimentalMaterial3ExpressiveApi::class)
+
package com.android.compose.animation.scene
import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.AnimationSpec
import androidx.compose.animation.core.AnimationVector1D
import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
@@ -364,10 +367,7 @@
check(isAnimatingOffset())
- val motionSpatialSpec =
- spec
- ?: contentTransition.transformationSpec.motionSpatialSpec
- ?: layoutState.transitions.defaultMotionSpatialSpec
+ val motionSpatialSpec = spec ?: layoutState.motionScheme.defaultSpatialSpec()
val velocityConsumed = CompletableDeferred<Float>()
offsetAnimationRunnable.complete {
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt
index 776d553..a29c1bb 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt
@@ -19,7 +19,6 @@
import androidx.compose.animation.core.AnimationSpec
import androidx.compose.animation.core.Easing
import androidx.compose.animation.core.LinearEasing
-import androidx.compose.animation.core.SpringSpec
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
@@ -37,12 +36,6 @@
@TransitionDsl
interface SceneTransitionsBuilder {
/**
- * The default [AnimationSpec] used when after the user lifts their finger after starting a
- * swipe to transition, to animate back into one of the 2 scenes we are transitioning to.
- */
- var defaultMotionSpatialSpec: SpringSpec<Float>
-
- /**
* The [InterruptionHandler] used when transitions are interrupted. Defaults to
* [DefaultInterruptionHandler].
*/
@@ -139,15 +132,7 @@
* The [AnimationSpec] used to animate the associated transition progress from `0` to `1` when
* the transition is triggered (i.e. it is not gesture-based).
*/
- var spec: AnimationSpec<Float>
-
- /**
- * The [SpringSpec] used to animate the associated transition progress when the transition was
- * started by a swipe and is now animating back to a scene because the user lifted their finger.
- *
- * If `null`, then the [SceneTransitionsBuilder.defaultMotionSpatialSpec] will be used.
- */
- var motionSpatialSpec: SpringSpec<Float>?
+ var spec: AnimationSpec<Float>?
/** The CUJ associated to this transitions. */
@CujType var cuj: Int?
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt
index 9a9b05e..ccb7024 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt
@@ -19,10 +19,7 @@
import androidx.compose.animation.core.AnimationSpec
import androidx.compose.animation.core.DurationBasedAnimationSpec
import androidx.compose.animation.core.Easing
-import androidx.compose.animation.core.Spring
-import androidx.compose.animation.core.SpringSpec
import androidx.compose.animation.core.VectorConverter
-import androidx.compose.animation.core.spring
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.unit.Dp
import com.android.compose.animation.scene.content.state.TransitionState
@@ -42,14 +39,12 @@
internal fun transitionsImpl(builder: SceneTransitionsBuilder.() -> Unit): SceneTransitions {
val impl = SceneTransitionsBuilderImpl().apply(builder)
return SceneTransitions(
- defaultMotionSpatialSpec = impl.defaultMotionSpatialSpec,
transitionSpecs = impl.transitionSpecs,
interruptionHandler = impl.interruptionHandler,
)
}
private class SceneTransitionsBuilderImpl : SceneTransitionsBuilder {
- override var defaultMotionSpatialSpec: SpringSpec<Float> = SceneTransitions.DefaultSwipeSpec
override var interruptionHandler: InterruptionHandler = DefaultInterruptionHandler
val transitionSpecs = mutableListOf<TransitionSpecImpl>()
@@ -109,7 +104,6 @@
val impl = TransitionBuilderImpl(transition).apply(builder)
return TransformationSpecImpl(
progressSpec = impl.spec,
- motionSpatialSpec = impl.motionSpatialSpec,
distance = impl.distance,
transformationMatchers = impl.transformationMatchers,
)
@@ -212,8 +206,7 @@
internal class TransitionBuilderImpl(override val transition: TransitionState.Transition) :
BaseTransitionBuilderImpl(), TransitionBuilder {
- override var spec: AnimationSpec<Float> = spring(stiffness = Spring.StiffnessLow)
- override var motionSpatialSpec: SpringSpec<Float>? = null
+ override var spec: AnimationSpec<Float>? = null
override var distance: UserActionDistance? = null
override var cuj: Int? = null
private val durationMillis: Int by lazy {
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Content.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Content.kt
index c61bb6e..6ccd498 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Content.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Content.kt
@@ -17,8 +17,12 @@
package com.android.compose.animation.scene.content
import android.annotation.SuppressLint
+import androidx.compose.animation.core.AnimationSpec
+import androidx.compose.animation.core.AnimationVector
+import androidx.compose.animation.core.TwoWayConverter
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.layout.Box
+import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
import androidx.compose.runtime.getValue
@@ -123,16 +127,25 @@
override val lookaheadScope: LookaheadScope
get() = layoutImpl.lookaheadScope
+ @OptIn(ExperimentalMaterial3ExpressiveApi::class)
+ private val animationSpatialSpec =
+ object : AnimationSpec<Float> {
+ override fun <V : AnimationVector> vectorize(converter: TwoWayConverter<Float, V>) =
+ layoutImpl.state.motionScheme.defaultSpatialSpec<Float>().vectorize(converter)
+ }
+
private val _verticalOverscrollEffect =
OffsetOverscrollEffect(
orientation = Orientation.Vertical,
animationScope = layoutImpl.animationScope,
+ animationSpec = animationSpatialSpec,
)
private val _horizontalOverscrollEffect =
OffsetOverscrollEffect(
orientation = Orientation.Horizontal,
animationScope = layoutImpl.animationScope,
+ animationSpec = animationSpatialSpec,
)
val verticalOverscrollGestureEffect = GestureEffect(_verticalOverscrollEffect)
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/state/TransitionState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/state/TransitionState.kt
index e9542c8..e23e234 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/state/TransitionState.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/state/TransitionState.kt
@@ -18,8 +18,7 @@
import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.AnimationVector1D
-import androidx.compose.animation.core.Spring
-import androidx.compose.animation.core.spring
+import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.runtime.Stable
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
@@ -385,14 +384,13 @@
fun create(): Animatable<Float, AnimationVector1D> {
val animatable = Animatable(1f, visibilityThreshold = ProgressVisibilityThreshold)
layoutImpl.animationScope.launch {
- val motionSpatialSpec = layoutImpl.state.transitions.defaultMotionSpatialSpec
- val progressSpec =
- spring(
- stiffness = motionSpatialSpec.stiffness,
- dampingRatio = Spring.DampingRatioNoBouncy,
- visibilityThreshold = ProgressVisibilityThreshold,
- )
- animatable.animateTo(0f, progressSpec)
+ @OptIn(ExperimentalMaterial3ExpressiveApi::class)
+ animatable.animateTo(
+ targetValue = 0f,
+ // Quickly animate (use fast) the current transition and without bounces
+ // (use effects). A new transition will start soon.
+ animationSpec = layoutImpl.state.motionScheme.fastEffectsSpec(),
+ )
}
return animatable
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/reveal/ContainerReveal.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/reveal/ContainerReveal.kt
index 00cd0ca..2134510 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/reveal/ContainerReveal.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/reveal/ContainerReveal.kt
@@ -20,7 +20,6 @@
import androidx.compose.animation.core.DeferredTargetAnimation
import androidx.compose.animation.core.ExperimentalAnimatableApi
import androidx.compose.animation.core.FiniteAnimationSpec
-import androidx.compose.animation.core.Spring
import androidx.compose.animation.core.VectorConverter
import androidx.compose.animation.core.spring
import androidx.compose.ui.unit.Dp
@@ -103,14 +102,6 @@
// The spring animating the alpha of the container.
val alphaSpec = spring<Float>(stiffness = 1200f, dampingRatio = 0.99f)
- // The spring animating the progress when releasing the finger.
- motionSpatialSpec =
- spring(
- stiffness = Spring.StiffnessMediumLow,
- dampingRatio = Spring.DampingRatioNoBouncy,
- visibilityThreshold = 0.5f,
- )
-
// Size transformation.
transformation(container) {
VerticalContainerRevealSizeTransformation(
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/AnimatedSharedAsStateTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/AnimatedSharedAsStateTest.kt
index 62ec221..d11e6da1 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/AnimatedSharedAsStateTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/AnimatedSharedAsStateTest.kt
@@ -386,7 +386,7 @@
@Test
fun animatedValueIsUsingLastTransition() = runTest {
val state =
- rule.runOnUiThread { MutableSceneTransitionLayoutStateImpl(SceneA, transitions {}) }
+ rule.runOnUiThread { MutableSceneTransitionLayoutStateForTests(SceneA, transitions {}) }
val foo = ValueKey("foo")
val bar = ValueKey("bar")
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ContentTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ContentTest.kt
index 06a9735..dc69426 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ContentTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ContentTest.kt
@@ -42,7 +42,7 @@
lateinit var layoutImpl: SceneTransitionLayoutImpl
rule.setContent {
SceneTransitionLayoutForTesting(
- remember { MutableSceneTransitionLayoutState(SceneA) },
+ remember { MutableSceneTransitionLayoutStateForTests(SceneA) },
onLayoutImpl = { layoutImpl = it },
) {
scene(SceneA) {
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt
index ef36077..35ff004 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt
@@ -60,7 +60,7 @@
private class TestGestureScope(val testScope: MonotonicClockTestScope) {
var canChangeScene: (SceneKey) -> Boolean = { true }
val layoutState =
- MutableSceneTransitionLayoutStateImpl(
+ MutableSceneTransitionLayoutStateForTests(
SceneA,
EmptyTestTransitions,
canChangeScene = { canChangeScene(it) },
@@ -640,10 +640,7 @@
@Test
fun overscroll_releaseBetween0And100Percent_up() = runGestureTest {
// Make scene B overscrollable.
- layoutState.transitions = transitions {
- defaultMotionSpatialSpec = spring(dampingRatio = Spring.DampingRatioNoBouncy)
- from(SceneA, to = SceneB) {}
- }
+ layoutState.transitions = transitions { from(SceneA, to = SceneB) {} }
val dragController =
onDragStarted(
@@ -671,10 +668,7 @@
@Test
fun overscroll_releaseBetween0And100Percent_down() = runGestureTest {
// Make scene C overscrollable.
- layoutState.transitions = transitions {
- defaultMotionSpatialSpec = spring(dampingRatio = Spring.DampingRatioNoBouncy)
- from(SceneA, to = SceneC) {}
- }
+ layoutState.transitions = transitions { from(SceneA, to = SceneC) {} }
val dragController =
onDragStarted(
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
index b405fbe..8d0af9b 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
@@ -209,7 +209,7 @@
val state =
rule.runOnUiThread {
- MutableSceneTransitionLayoutState(
+ MutableSceneTransitionLayoutStateForTests(
SceneA,
transitions {
from(SceneA, to = SceneB) { spec = tween }
@@ -343,7 +343,7 @@
@Test
fun elementIsReusedBetweenScenes() {
- val state = rule.runOnUiThread { MutableSceneTransitionLayoutState(SceneA) }
+ val state = rule.runOnUiThread { MutableSceneTransitionLayoutStateForTests(SceneA) }
var sceneCState by mutableStateOf(0)
val key = TestElements.Foo
var nullableLayoutImpl: SceneTransitionLayoutImpl? = null
@@ -473,7 +473,7 @@
@Test
fun elementModifierSupportsUpdates() {
- val state = rule.runOnUiThread { MutableSceneTransitionLayoutState(SceneA) }
+ val state = rule.runOnUiThread { MutableSceneTransitionLayoutStateForTests(SceneA) }
var key by mutableStateOf(TestElements.Foo)
var nullableLayoutImpl: SceneTransitionLayoutImpl? = null
@@ -523,7 +523,7 @@
rule.waitUntil(timeoutMillis = 10_000) { animationFinished }
}
- val state = rule.runOnUiThread { MutableSceneTransitionLayoutState(SceneA) }
+ val state = rule.runOnUiThread { MutableSceneTransitionLayoutStateForTests(SceneA) }
rule.setContent {
scrollScope = rememberCoroutineScope()
@@ -618,7 +618,7 @@
fun layoutGetsCurrentTransitionStateFromComposition() {
val state =
rule.runOnUiThread {
- MutableSceneTransitionLayoutState(
+ MutableSceneTransitionLayoutStateForTests(
SceneA,
transitions {
from(SceneA, to = SceneB) {
@@ -663,7 +663,8 @@
// The draggable touch slop, i.e. the min px distance a touch pointer must move before it is
// detected as a drag event.
var touchSlop = 0f
- val state = rule.runOnUiThread { MutableSceneTransitionLayoutState(initialScene = SceneA) }
+ val state =
+ rule.runOnUiThread { MutableSceneTransitionLayoutStateForTests(initialScene = SceneA) }
rule.setContent {
density = LocalDensity.current
touchSlop = LocalViewConfiguration.current.touchSlop
@@ -718,7 +719,8 @@
// The draggable touch slop, i.e. the min px distance a touch pointer must move before it is
// detected as a drag event.
var touchSlop = 0f
- val state = rule.runOnUiThread { MutableSceneTransitionLayoutState(initialScene = SceneA) }
+ val state =
+ rule.runOnUiThread { MutableSceneTransitionLayoutStateForTests(initialScene = SceneA) }
rule.setContent {
density = LocalDensity.current
touchSlop = LocalViewConfiguration.current.touchSlop
@@ -813,7 +815,8 @@
// The draggable touch slop, i.e. the min px distance a touch pointer must move before it is
// detected as a drag event.
var touchSlop = 0f
- val state = rule.runOnUiThread { MutableSceneTransitionLayoutState(initialScene = SceneA) }
+ val state =
+ rule.runOnUiThread { MutableSceneTransitionLayoutStateForTests(initialScene = SceneA) }
rule.setContent {
density = LocalDensity.current
touchSlop = LocalViewConfiguration.current.touchSlop
@@ -866,7 +869,8 @@
val layoutWidth = 200.dp
val layoutHeight = 400.dp
- val state = rule.runOnUiThread { MutableSceneTransitionLayoutState(initialScene = SceneA) }
+ val state =
+ rule.runOnUiThread { MutableSceneTransitionLayoutStateForTests(initialScene = SceneA) }
rule.setContent {
density = LocalDensity.current
@@ -934,7 +938,7 @@
val state =
rule.runOnUiThread {
- MutableSceneTransitionLayoutState(
+ MutableSceneTransitionLayoutStateForTests(
initialScene = SceneA,
transitions =
transitions {
@@ -943,7 +947,6 @@
}
},
)
- as MutableSceneTransitionLayoutStateImpl
}
rule.setContent {
@@ -999,7 +1002,7 @@
val state =
rule.runOnUiThread {
- MutableSceneTransitionLayoutState(
+ MutableSceneTransitionLayoutStateForTests(
SceneA,
transitions {
// Foo is at the top left corner of scene A. We make it disappear during A
@@ -1126,7 +1129,7 @@
val state =
rule.runOnUiThread {
- MutableSceneTransitionLayoutState(
+ MutableSceneTransitionLayoutStateForTests(
SceneA,
transitions {
from(SceneA, to = SceneB) { spec = tween(duration, easing = LinearEasing) }
@@ -1331,7 +1334,7 @@
val fooSize = 100.dp
val state =
rule.runOnUiThread {
- MutableSceneTransitionLayoutState(
+ MutableSceneTransitionLayoutStateForTests(
SceneA,
transitions {
from(SceneA, to = SceneB) { spec = tween(duration, easing = LinearEasing) }
@@ -1439,7 +1442,7 @@
@Test
fun targetStateIsSetEvenWhenNotPlaced() {
// Start directly at A => B but with progress < 0f to overscroll on A.
- val state = rule.runOnUiThread { MutableSceneTransitionLayoutState(SceneA) }
+ val state = rule.runOnUiThread { MutableSceneTransitionLayoutStateForTests(SceneA) }
lateinit var layoutImpl: SceneTransitionLayoutImpl
val scope =
@@ -1473,7 +1476,7 @@
fun lastAlphaIsNotSetByOutdatedLayer() {
val state =
rule.runOnUiThread {
- MutableSceneTransitionLayoutState(
+ MutableSceneTransitionLayoutStateForTests(
SceneA,
transitions { from(SceneA, to = SceneB) { fade(TestElements.Foo) } },
)
@@ -1537,7 +1540,7 @@
fun fadingElementsDontAppearInstantly() {
val state =
rule.runOnUiThread {
- MutableSceneTransitionLayoutState(
+ MutableSceneTransitionLayoutStateForTests(
SceneA,
transitions { from(SceneA, to = SceneB) { fade(TestElements.Foo) } },
)
@@ -1583,7 +1586,7 @@
@Test
fun lastPlacementValuesAreClearedOnNestedElements() {
- val state = rule.runOnIdle { MutableSceneTransitionLayoutState(SceneA) }
+ val state = rule.runOnIdle { MutableSceneTransitionLayoutStateForTests(SceneA) }
@Composable
fun ContentScope.NestedFooBar() {
@@ -1658,7 +1661,7 @@
fun currentTransitionSceneIsUsedToComputeElementValues() {
val state =
rule.runOnIdle {
- MutableSceneTransitionLayoutState(
+ MutableSceneTransitionLayoutStateForTests(
SceneA,
transitions {
from(SceneB, to = SceneC) {
@@ -1709,7 +1712,7 @@
@Test
fun interruptionDeltasAreProperlyCleaned() {
- val state = rule.runOnIdle { MutableSceneTransitionLayoutState(SceneA) }
+ val state = rule.runOnIdle { MutableSceneTransitionLayoutStateForTests(SceneA) }
@Composable
fun ContentScope.Foo(offset: Dp) {
@@ -1780,7 +1783,7 @@
fun transparentElementIsNotImpactingInterruption() {
val state =
rule.runOnIdle {
- MutableSceneTransitionLayoutState(
+ MutableSceneTransitionLayoutStateForTests(
SceneA,
transitions {
from(SceneA, to = SceneB) {
@@ -1856,7 +1859,7 @@
@Test
fun replacedTransitionDoesNotTriggerInterruption() {
- val state = rule.runOnIdle { MutableSceneTransitionLayoutState(SceneA) }
+ val state = rule.runOnIdle { MutableSceneTransitionLayoutStateForTests(SceneA) }
@Composable
fun ContentScope.Foo(modifier: Modifier = Modifier) {
@@ -2027,7 +2030,7 @@
): SceneTransitionLayoutImpl {
val state =
rule.runOnIdle {
- MutableSceneTransitionLayoutState(
+ MutableSceneTransitionLayoutStateForTests(
from,
transitions { from(from, to = to, preview = preview, builder = transition) },
)
@@ -2174,7 +2177,7 @@
)
}
- val state = rule.runOnUiThread { MutableSceneTransitionLayoutState(SceneA) }
+ val state = rule.runOnUiThread { MutableSceneTransitionLayoutStateForTests(SceneA) }
val scope =
rule.setContentAndCreateMainScope {
SceneTransitionLayout(state) {
@@ -2215,7 +2218,7 @@
Box(modifier.element(TestElements.Foo).size(50.dp))
}
- val state = rule.runOnUiThread { MutableSceneTransitionLayoutState(SceneA) }
+ val state = rule.runOnUiThread { MutableSceneTransitionLayoutStateForTests(SceneA) }
val scope =
rule.setContentAndCreateMainScope {
SceneTransitionLayout(state) {
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/InterruptionHandlerTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/InterruptionHandlerTest.kt
index 3622369..b44f552 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/InterruptionHandlerTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/InterruptionHandlerTest.kt
@@ -39,7 +39,7 @@
@Test
fun default() = runMonotonicClockTest {
val state =
- MutableSceneTransitionLayoutState(
+ MutableSceneTransitionLayoutStateForTests(
SceneA,
transitions { /* default interruption handler */ },
)
@@ -62,7 +62,7 @@
@Test
fun chainingDisabled() = runMonotonicClockTest {
val state =
- MutableSceneTransitionLayoutState(
+ MutableSceneTransitionLayoutStateForTests(
SceneA,
transitions {
// Handler that animates from currentScene (default) but disables chaining.
@@ -97,7 +97,7 @@
fun animateFromOtherScene() = runMonotonicClockTest {
val duration = 500
val state =
- MutableSceneTransitionLayoutState(
+ MutableSceneTransitionLayoutStateForTests(
SceneA,
transitions {
// Handler that animates from the scene that is not currentScene.
@@ -146,7 +146,7 @@
@Test
fun animateToFromScene() = runMonotonicClockTest {
- val state = MutableSceneTransitionLayoutStateImpl(SceneA, transitions {})
+ val state = MutableSceneTransitionLayoutStateForTests(SceneA, transitions {})
// Fake a transition from A to B that has a non 0 velocity.
val progressVelocity = 1f
@@ -182,7 +182,7 @@
@Test
fun animateToToScene() = runMonotonicClockTest {
- val state = MutableSceneTransitionLayoutStateImpl(SceneA, transitions {})
+ val state = MutableSceneTransitionLayoutStateForTests(SceneA, transitions {})
// Fake a transition from A to B with current scene = A that has a non 0 velocity.
val progressVelocity = -1f
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementTest.kt
index 9e1bae5..8d718c1 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementTest.kt
@@ -380,7 +380,7 @@
val state =
rule.runOnUiThread {
- MutableSceneTransitionLayoutState(
+ MutableSceneTransitionLayoutStateForTests(
initialScene = SceneA,
initialOverlays = setOf(OverlayA),
)
@@ -420,7 +420,7 @@
val state =
rule.runOnUiThread {
- MutableSceneTransitionLayoutState(
+ MutableSceneTransitionLayoutStateForTests(
initialScene = SceneA,
initialOverlays = setOf(OverlayA),
)
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MutableSceneTransitionLayoutStateForTests.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MutableSceneTransitionLayoutStateForTests.kt
new file mode 100644
index 0000000..4326ec9
--- /dev/null
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MutableSceneTransitionLayoutStateForTests.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.compose.animation.scene
+
+import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
+import androidx.compose.material3.MotionScheme
+import com.android.compose.animation.scene.content.state.TransitionState
+
+internal fun MutableSceneTransitionLayoutStateForTests(
+ initialScene: SceneKey,
+ transitions: SceneTransitions = SceneTransitions.Empty,
+ initialOverlays: Set<OverlayKey> = emptySet(),
+ canChangeScene: (SceneKey) -> Boolean = { true },
+ canShowOverlay: (OverlayKey) -> Boolean = { true },
+ canHideOverlay: (OverlayKey) -> Boolean = { true },
+ canReplaceOverlay: (from: OverlayKey, to: OverlayKey) -> Boolean = { _, _ -> true },
+ onTransitionStart: (TransitionState.Transition) -> Unit = {},
+ onTransitionEnd: (TransitionState.Transition) -> Unit = {},
+): MutableSceneTransitionLayoutStateImpl {
+ @OptIn(ExperimentalMaterial3ExpressiveApi::class)
+ return MutableSceneTransitionLayoutStateImpl(
+ initialScene,
+ motionScheme = MotionScheme.standard(),
+ transitions,
+ initialOverlays,
+ canChangeScene,
+ canShowOverlay,
+ canHideOverlay,
+ canReplaceOverlay,
+ onTransitionStart,
+ onTransitionEnd,
+ )
+}
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ObservableTransitionStateTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ObservableTransitionStateTest.kt
index 596e2cd..f2c0ca5 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ObservableTransitionStateTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ObservableTransitionStateTest.kt
@@ -47,7 +47,9 @@
@Test
fun testObservableTransitionState() = runTest {
val state =
- rule.runOnUiThread { MutableSceneTransitionLayoutState(SceneA, EmptyTestTransitions) }
+ rule.runOnUiThread {
+ MutableSceneTransitionLayoutStateForTests(SceneA, EmptyTestTransitions)
+ }
// Collect the current observable state into [observableState].
// TODO(b/290184746): Use collectValues {} once it is extracted into a library that can be
@@ -106,7 +108,7 @@
@Test
fun observableCurrentScene() = runTestWithSnapshots {
val state =
- MutableSceneTransitionLayoutStateImpl(
+ MutableSceneTransitionLayoutStateForTests(
initialScene = SceneA,
transitions = transitions {},
)
@@ -150,7 +152,7 @@
fun testObservablePreviewTransitionState() = runTest {
val layoutState =
rule.runOnUiThread {
- MutableSceneTransitionLayoutState(
+ MutableSceneTransitionLayoutStateForTests(
SceneA,
transitions = transitions { from(SceneA, to = SceneB, preview = {}) },
)
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/OverlayTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/OverlayTest.kt
index 93fa516..50bfbfe 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/OverlayTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/OverlayTest.kt
@@ -73,7 +73,7 @@
@Test
fun showThenHideOverlay() {
- val state = rule.runOnUiThread { MutableSceneTransitionLayoutState(SceneA) }
+ val state = rule.runOnUiThread { MutableSceneTransitionLayoutStateForTests(SceneA) }
lateinit var coroutineScope: CoroutineScope
rule.setContent {
coroutineScope = rememberCoroutineScope()
@@ -115,7 +115,7 @@
@Test
fun multipleOverlays() {
- val state = rule.runOnUiThread { MutableSceneTransitionLayoutState(SceneA) }
+ val state = rule.runOnUiThread { MutableSceneTransitionLayoutStateForTests(SceneA) }
lateinit var coroutineScope: CoroutineScope
rule.setContent {
coroutineScope = rememberCoroutineScope()
@@ -213,7 +213,7 @@
}
}
- val state = rule.runOnUiThread { MutableSceneTransitionLayoutState(SceneA) }
+ val state = rule.runOnUiThread { MutableSceneTransitionLayoutStateForTests(SceneA) }
lateinit var coroutineScope: CoroutineScope
rule.setContent {
coroutineScope = rememberCoroutineScope()
@@ -285,7 +285,7 @@
fun overlayAlignment() {
val state =
rule.runOnUiThread {
- MutableSceneTransitionLayoutState(SceneA, initialOverlays = setOf(OverlayA))
+ MutableSceneTransitionLayoutStateForTests(SceneA, initialOverlays = setOf(OverlayA))
}
var alignment by mutableStateOf(Alignment.Center)
rule.setContent {
@@ -320,7 +320,7 @@
fun overlayMaxSizeIsCurrentSceneSize() {
val state =
rule.runOnUiThread {
- MutableSceneTransitionLayoutState(SceneA, initialOverlays = setOf(OverlayA))
+ MutableSceneTransitionLayoutStateForTests(SceneA, initialOverlays = setOf(OverlayA))
}
val contentTag = "overlayContent"
@@ -742,7 +742,7 @@
@Test
fun overscrollingOverlay_movableElementNotInOverlay() {
- val state = rule.runOnUiThread { MutableSceneTransitionLayoutStateImpl(SceneA) }
+ val state = rule.runOnUiThread { MutableSceneTransitionLayoutStateForTests(SceneA) }
val key = MovableElementKey("Foo", contents = setOf(SceneA))
val movableElementChildTag = "movableElementChildTag"
@@ -769,7 +769,7 @@
@Test
fun overlaysAreModalByDefault() {
- val state = rule.runOnUiThread { MutableSceneTransitionLayoutStateImpl(SceneA) }
+ val state = rule.runOnUiThread { MutableSceneTransitionLayoutStateForTests(SceneA) }
val scrollState = ScrollState(initial = 0)
val scope =
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/PredictiveBackHandlerTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/PredictiveBackHandlerTest.kt
index 4224a0c..9f15ebd 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/PredictiveBackHandlerTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/PredictiveBackHandlerTest.kt
@@ -50,7 +50,7 @@
@Test
fun testBack() {
- val layoutState = rule.runOnUiThread { MutableSceneTransitionLayoutState(SceneA) }
+ val layoutState = rule.runOnUiThread { MutableSceneTransitionLayoutStateForTests(SceneA) }
rule.setContent {
SceneTransitionLayout(layoutState) {
scene(SceneA, mapOf(Back to SceneB)) { Box(Modifier.fillMaxSize()) }
@@ -70,7 +70,7 @@
val transitionFrames = 2
val layoutState =
rule.runOnUiThread {
- MutableSceneTransitionLayoutState(
+ MutableSceneTransitionLayoutStateForTests(
SceneA,
transitions =
transitions {
@@ -142,7 +142,7 @@
fun testPredictiveBackWithPreview() {
val layoutState =
rule.runOnUiThread {
- MutableSceneTransitionLayoutState(
+ MutableSceneTransitionLayoutStateForTests(
SceneA,
transitions = transitions { from(SceneA, to = SceneB, preview = {}) },
)
@@ -192,7 +192,7 @@
var canChangeSceneCalled = false
val layoutState =
rule.runOnUiThread {
- MutableSceneTransitionLayoutState(
+ MutableSceneTransitionLayoutStateForTests(
SceneA,
canChangeScene = {
canChangeSceneCalled = true
@@ -241,7 +241,7 @@
fun backDismissesOverlayWithHighestZIndexByDefault() {
val state =
rule.runOnUiThread {
- MutableSceneTransitionLayoutState(
+ MutableSceneTransitionLayoutStateForTests(
SceneA,
initialOverlays = setOf(OverlayA, OverlayB),
)
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt
index f3be5e4..c5e4061 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt
@@ -51,7 +51,7 @@
@Test
fun isTransitioningTo_idle() {
- val state = MutableSceneTransitionLayoutStateImpl(SceneA, SceneTransitions.Empty)
+ val state = MutableSceneTransitionLayoutStateForTests(SceneA, SceneTransitions.Empty)
assertThat(state.isTransitioning()).isFalse()
assertThat(state.isTransitioning(from = SceneA)).isFalse()
@@ -61,7 +61,7 @@
@Test
fun isTransitioningTo_transition() = runTest {
- val state = MutableSceneTransitionLayoutStateImpl(SceneA, SceneTransitions.Empty)
+ val state = MutableSceneTransitionLayoutStateForTests(SceneA, SceneTransitions.Empty)
state.startTransitionImmediately(
animationScope = backgroundScope,
transition(from = SceneA, to = SceneB),
@@ -77,13 +77,13 @@
@Test
fun setTargetScene_idleToSameScene() = runMonotonicClockTest {
- val state = MutableSceneTransitionLayoutState(SceneA)
+ val state = MutableSceneTransitionLayoutStateForTests(SceneA)
assertThat(state.setTargetScene(SceneA, animationScope = this)).isNull()
}
@Test
fun setTargetScene_idleToDifferentScene() = runMonotonicClockTest {
- val state = MutableSceneTransitionLayoutState(SceneA)
+ val state = MutableSceneTransitionLayoutStateForTests(SceneA)
val (transition, job) = checkNotNull(state.setTargetScene(SceneB, animationScope = this))
assertThat(state.transitionState).isEqualTo(transition)
@@ -93,7 +93,7 @@
@Test
fun setTargetScene_transitionToSameScene() = runMonotonicClockTest {
- val state = MutableSceneTransitionLayoutState(SceneA)
+ val state = MutableSceneTransitionLayoutStateForTests(SceneA)
val (_, job) = checkNotNull(state.setTargetScene(SceneB, animationScope = this))
assertThat(state.setTargetScene(SceneB, animationScope = this)).isNull()
@@ -104,7 +104,7 @@
@Test
fun setTargetScene_transitionToDifferentScene() = runMonotonicClockTest {
- val state = MutableSceneTransitionLayoutState(SceneA)
+ val state = MutableSceneTransitionLayoutStateForTests(SceneA)
assertThat(state.setTargetScene(SceneB, animationScope = this)).isNotNull()
val (_, job) = checkNotNull(state.setTargetScene(SceneC, animationScope = this))
@@ -115,7 +115,7 @@
@Test
fun setTargetScene_coroutineScopeCancelled() = runMonotonicClockTest {
- val state = MutableSceneTransitionLayoutState(SceneA)
+ val state = MutableSceneTransitionLayoutStateForTests(SceneA)
lateinit var transition: TransitionState.Transition
val job =
@@ -133,7 +133,7 @@
fun setTargetScene_withTransitionKey() = runMonotonicClockTest {
val transitionkey = TransitionKey(debugName = "foo")
val state =
- MutableSceneTransitionLayoutState(
+ MutableSceneTransitionLayoutStateForTests(
SceneA,
transitions =
transitions {
@@ -176,7 +176,7 @@
return { /* do nothing */ }
}
- val state = MutableSceneTransitionLayoutStateImpl(SceneA, EmptyTestTransitions)
+ val state = MutableSceneTransitionLayoutStateForTests(SceneA, EmptyTestTransitions)
val aToB = transition(SceneA, SceneB, onFreezeAndAnimate = ::onFreezeAndAnimate)
val bToC = transition(SceneB, SceneC, onFreezeAndAnimate = ::onFreezeAndAnimate)
val cToA = transition(SceneC, SceneA, onFreezeAndAnimate = ::onFreezeAndAnimate)
@@ -226,7 +226,7 @@
@Test
fun tooManyTransitionsLogsWtfAndClearsTransitions() = runTest {
- val state = MutableSceneTransitionLayoutStateImpl(SceneA, EmptyTestTransitions)
+ val state = MutableSceneTransitionLayoutStateForTests(SceneA, EmptyTestTransitions)
fun startTransition() {
val transition =
@@ -251,7 +251,7 @@
@Test
fun snapToScene() = runMonotonicClockTest {
- val state = MutableSceneTransitionLayoutState(SceneA)
+ val state = MutableSceneTransitionLayoutStateForTests(SceneA)
// Transition to B.
state.setTargetScene(SceneB, animationScope = this)
@@ -266,7 +266,7 @@
@Test
fun snapToScene_freezesCurrentTransition() = runMonotonicClockTest {
- val state = MutableSceneTransitionLayoutStateImpl(SceneA)
+ val state = MutableSceneTransitionLayoutStateForTests(SceneA)
// Start a transition that is never finished. We don't use backgroundScope on purpose so
// that this test would fail if the transition was not frozen when snapping.
@@ -283,7 +283,7 @@
@Test
fun seekToScene() = runMonotonicClockTest {
- val state = MutableSceneTransitionLayoutState(SceneA)
+ val state = MutableSceneTransitionLayoutStateForTests(SceneA)
val progress = Channel<Float>()
val job =
@@ -309,7 +309,7 @@
@Test
fun seekToScene_cancelled() = runMonotonicClockTest {
- val state = MutableSceneTransitionLayoutState(SceneA)
+ val state = MutableSceneTransitionLayoutStateForTests(SceneA)
val progress = Channel<Float>()
val job =
@@ -335,7 +335,7 @@
@Test
fun seekToScene_interrupted() = runMonotonicClockTest {
- val state = MutableSceneTransitionLayoutState(SceneA)
+ val state = MutableSceneTransitionLayoutStateForTests(SceneA)
val progress = Channel<Float>()
val job =
@@ -354,7 +354,7 @@
@Test
fun replacedTransitionIsRemovedFromFinishedTransitions() = runTest {
- val state = MutableSceneTransitionLayoutState(SceneA)
+ val state = MutableSceneTransitionLayoutStateForTests(SceneA)
val aToB =
transition(
@@ -403,7 +403,7 @@
@Test
fun transitionCanBeStartedOnlyOnce() = runTest {
- val state = MutableSceneTransitionLayoutState(SceneA)
+ val state = MutableSceneTransitionLayoutStateForTests(SceneA)
val transition = transition(from = SceneA, to = SceneB)
state.startTransitionImmediately(backgroundScope, transition)
@@ -414,7 +414,7 @@
@Test
fun transitionFinishedWhenScopeIsEmpty() = runTest {
- val state = MutableSceneTransitionLayoutState(SceneA)
+ val state = MutableSceneTransitionLayoutStateForTests(SceneA)
// Start a transition.
val transition = transition(from = SceneA, to = SceneB)
@@ -439,7 +439,7 @@
@Test
fun transitionScopeIsCancelledWhenTransitionIsForceFinished() = runTest {
- val state = MutableSceneTransitionLayoutState(SceneA)
+ val state = MutableSceneTransitionLayoutStateForTests(SceneA)
// Start a transition.
val transition = transition(from = SceneA, to = SceneB)
@@ -458,7 +458,7 @@
@Test
fun badTransitionSpecThrowsMeaningfulMessageWhenStartingTransition() {
val state =
- MutableSceneTransitionLayoutState(
+ MutableSceneTransitionLayoutStateForTests(
SceneA,
transitions {
// This transition definition is bad because they both match when transitioning
@@ -483,7 +483,7 @@
@Test
fun snapToScene_multipleTransitions() = runMonotonicClockTest {
- val state = MutableSceneTransitionLayoutState(SceneA)
+ val state = MutableSceneTransitionLayoutStateForTests(SceneA)
state.startTransitionImmediately(this, transition(SceneA, SceneB))
state.startTransitionImmediately(this, transition(SceneB, SceneC))
state.snapToScene(SceneC)
@@ -498,7 +498,7 @@
val finished = mutableSetOf<TransitionState.Transition>()
val cujWhenStarting = mutableMapOf<TransitionState.Transition, Int?>()
val state =
- MutableSceneTransitionLayoutState(
+ MutableSceneTransitionLayoutStateForTests(
SceneA,
transitions {
// A <=> B.
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt
index e580e3c..3c490ae 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt
@@ -17,7 +17,9 @@
package com.android.compose.animation.scene
import androidx.compose.animation.core.FastOutSlowInEasing
+import androidx.compose.animation.core.FiniteAnimationSpec
import androidx.compose.animation.core.LinearEasing
+import androidx.compose.animation.core.spring
import androidx.compose.animation.core.tween
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
@@ -25,9 +27,13 @@
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.size
+import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.MotionScheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
@@ -60,7 +66,6 @@
import com.android.compose.test.assertSizeIsEqualTo
import com.android.compose.test.subjects.DpOffsetSubject
import com.android.compose.test.subjects.assertThat
-import com.android.compose.test.transition
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.CoroutineScope
import org.junit.Assert.assertThrows
@@ -88,7 +93,9 @@
@Composable
private fun TestContent() {
coroutineScope = rememberCoroutineScope()
- layoutState = remember { MutableSceneTransitionLayoutState(SceneA, EmptyTestTransitions) }
+ layoutState = remember {
+ MutableSceneTransitionLayoutStateForTests(SceneA, EmptyTestTransitions)
+ }
SceneTransitionLayout(state = layoutState, modifier = Modifier.size(LayoutSize)) {
scene(SceneA, userActions = mapOf(Back to SceneB)) {
@@ -317,7 +324,7 @@
val state =
rule.runOnUiThread {
- MutableSceneTransitionLayoutState(
+ MutableSceneTransitionLayoutStateForTests(
SceneA,
transitions {
from(SceneA, to = SceneB) {
@@ -421,7 +428,7 @@
assertThrows(IllegalStateException::class.java) {
rule.setContent {
SceneTransitionLayout(
- state = remember { MutableSceneTransitionLayoutState(SceneA) },
+ state = remember { MutableSceneTransitionLayoutStateForTests(SceneA) },
modifier = Modifier.size(LayoutSize),
) {
// from SceneA to SceneA
@@ -436,7 +443,7 @@
@Test
fun sceneKeyInScope() {
- val state = rule.runOnUiThread { MutableSceneTransitionLayoutState(SceneA) }
+ val state = rule.runOnUiThread { MutableSceneTransitionLayoutStateForTests(SceneA) }
var keyInA: ContentKey? = null
var keyInB: ContentKey? = null
@@ -465,7 +472,7 @@
lateinit var layoutImpl: SceneTransitionLayoutImpl
rule.setContent {
SceneTransitionLayoutForTesting(
- remember { MutableSceneTransitionLayoutState(SceneA) },
+ remember { MutableSceneTransitionLayoutStateForTests(SceneA) },
onLayoutImpl = { layoutImpl = it },
) {
scene(SceneA) { Box(Modifier.fillMaxSize()) }
@@ -483,7 +490,8 @@
// The draggable touch slop, i.e. the min px distance a touch pointer must move before it is
// detected as a drag event.
var touchSlop = 0f
- val state = rule.runOnUiThread { MutableSceneTransitionLayoutState(initialScene = SceneA) }
+ val state =
+ rule.runOnUiThread { MutableSceneTransitionLayoutStateForTests(initialScene = SceneA) }
rule.setContent {
touchSlop = LocalViewConfiguration.current.touchSlop
SceneTransitionLayout(state, Modifier.size(layoutWidth, layoutHeight)) {
@@ -511,4 +519,67 @@
// Fling animation, we are overscrolling now. Progress should always be between [0, 1].
assertThat(transition).hasProgress(1f)
}
+
+ @OptIn(ExperimentalMaterial3ExpressiveApi::class)
+ @Test
+ fun motionSchemeArePassedToSTLState() {
+ // Implementation inspired by MotionScheme.standard()
+ @Suppress("UNCHECKED_CAST")
+ fun motionScheme(animationSpec: FiniteAnimationSpec<Any>) =
+ object : MotionScheme {
+ override fun <T> defaultEffectsSpec() = animationSpec as FiniteAnimationSpec<T>
+
+ override fun <T> defaultSpatialSpec() = animationSpec as FiniteAnimationSpec<T>
+
+ override fun <T> fastEffectsSpec() = animationSpec as FiniteAnimationSpec<T>
+
+ override fun <T> fastSpatialSpec() = animationSpec as FiniteAnimationSpec<T>
+
+ override fun <T> slowEffectsSpec() = animationSpec as FiniteAnimationSpec<T>
+
+ override fun <T> slowSpatialSpec() = animationSpec as FiniteAnimationSpec<T>
+ }
+
+ lateinit var state1: MutableSceneTransitionLayoutState
+ lateinit var state2: MutableSceneTransitionLayoutState
+
+ lateinit var motionScheme1: MotionScheme
+ var motionScheme2 by mutableStateOf(motionScheme(animationSpec = tween(500)))
+ rule.setContent {
+ motionScheme1 = MaterialTheme.motionScheme
+ state1 = rememberMutableSceneTransitionLayoutState(initialScene = SceneA)
+ SceneTransitionLayout(state1) {
+ scene(SceneA, userActions = mapOf(Swipe.Down to SceneB)) {
+ Spacer(Modifier.fillMaxSize())
+ }
+ }
+
+ MaterialTheme(motionScheme = motionScheme2) {
+ // Important: we should read this state inside the MaterialTheme composable.
+ state2 = rememberMutableSceneTransitionLayoutState(initialScene = SceneA)
+ SceneTransitionLayout(state2) {
+ scene(SceneA, userActions = mapOf(Swipe.Down to SceneB)) {
+ Spacer(Modifier.fillMaxSize())
+ }
+ }
+ }
+ }
+
+ assertThat(motionScheme1).isNotNull()
+ assertThat(motionScheme1).isNotEqualTo(motionScheme2)
+
+ assertThat((state1 as MutableSceneTransitionLayoutStateImpl).motionScheme)
+ .isEqualTo(motionScheme1)
+
+ assertThat((state2 as MutableSceneTransitionLayoutStateImpl).motionScheme)
+ .isEqualTo(motionScheme2)
+
+ // Update the MaterialTheme's MotionScheme configuration.
+ motionScheme2 = motionScheme(animationSpec = spring())
+
+ // We just updated the motionScheme2 state, wait for a recomposition.
+ rule.waitForIdle()
+ assertThat((state2 as MutableSceneTransitionLayoutStateImpl).motionScheme)
+ .isEqualTo(motionScheme2)
+ }
}
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt
index e036084..751b314 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt
@@ -93,7 +93,9 @@
initialScene: SceneKey = SceneA,
transitions: SceneTransitions = EmptyTestTransitions,
): MutableSceneTransitionLayoutState {
- return rule.runOnUiThread { MutableSceneTransitionLayoutState(initialScene, transitions) }
+ return rule.runOnUiThread {
+ MutableSceneTransitionLayoutStateForTests(initialScene, transitions)
+ }
}
/** The content under test. */
@@ -738,7 +740,7 @@
fun startEnd_ltrLayout() {
val state =
rule.runOnUiThread {
- MutableSceneTransitionLayoutState(
+ MutableSceneTransitionLayoutStateForTests(
initialScene = SceneA,
transitions =
transitions {
@@ -811,7 +813,7 @@
fun startEnd_rtlLayout() {
val state =
rule.runOnUiThread {
- MutableSceneTransitionLayoutState(
+ MutableSceneTransitionLayoutStateForTests(
initialScene = SceneA,
transitions =
transitions {
@@ -893,7 +895,9 @@
Text("Count: $count")
}
- SceneTransitionLayout(remember { MutableSceneTransitionLayoutState(SceneA) }) {
+ SceneTransitionLayout(
+ remember { MutableSceneTransitionLayoutStateForTests(SceneA) }
+ ) {
scene(SceneA) { Box(Modifier.fillMaxSize()) }
}
}
@@ -909,7 +913,7 @@
@Test
fun swipeToSceneSupportsUpdates() {
- val state = rule.runOnUiThread { MutableSceneTransitionLayoutState(SceneA) }
+ val state = rule.runOnUiThread { MutableSceneTransitionLayoutStateForTests(SceneA) }
rule.setContent {
SceneTransitionLayout(state) {
@@ -943,7 +947,7 @@
@Test
fun swipeToSceneNodeIsKeptWhenDisabled() {
var hasHorizontalActions by mutableStateOf(false)
- val state = rule.runOnUiThread { MutableSceneTransitionLayoutState(SceneA) }
+ val state = rule.runOnUiThread { MutableSceneTransitionLayoutStateForTests(SceneA) }
var touchSlop = 0f
rule.setContent {
touchSlop = LocalViewConfiguration.current.touchSlop
@@ -983,7 +987,7 @@
@Test
fun nestedScroll_useFromSourceInfo() {
- val state = rule.runOnUiThread { MutableSceneTransitionLayoutState(SceneA) }
+ val state = rule.runOnUiThread { MutableSceneTransitionLayoutStateForTests(SceneA) }
var touchSlop = 0f
rule.setContent {
touchSlop = LocalViewConfiguration.current.touchSlop
@@ -1033,7 +1037,7 @@
@Test
fun nestedScroll_ignoreMouseWheel() {
- val state = rule.runOnUiThread { MutableSceneTransitionLayoutState(SceneA) }
+ val state = rule.runOnUiThread { MutableSceneTransitionLayoutStateForTests(SceneA) }
var touchSlop = 0f
rule.setContent {
touchSlop = LocalViewConfiguration.current.touchSlop
@@ -1057,7 +1061,7 @@
@Test
fun nestedScroll_keepPriorityEvenIfWeCanNoLongerScrollOnThatDirection() {
- val state = rule.runOnUiThread { MutableSceneTransitionLayoutState(SceneA) }
+ val state = rule.runOnUiThread { MutableSceneTransitionLayoutStateForTests(SceneA) }
var touchSlop = 0f
rule.setContent {
touchSlop = LocalViewConfiguration.current.touchSlop
@@ -1097,7 +1101,7 @@
fun nestedScroll_replaceOverlay() {
val state =
rule.runOnUiThread {
- MutableSceneTransitionLayoutState(SceneA, initialOverlays = setOf(OverlayA))
+ MutableSceneTransitionLayoutStateForTests(SceneA, initialOverlays = setOf(OverlayA))
}
var touchSlop = 0f
rule.setContent {
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt
index aada4a50..e098aac 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt
@@ -17,9 +17,7 @@
package com.android.compose.animation.scene
import androidx.compose.animation.core.CubicBezierEasing
-import androidx.compose.animation.core.SpringSpec
import androidx.compose.animation.core.TweenSpec
-import androidx.compose.animation.core.spring
import androidx.compose.animation.core.tween
import androidx.compose.ui.unit.IntSize
import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -84,7 +82,7 @@
fun defaultTransitionSpec() {
val transitions = transitions { from(SceneA, to = SceneB) }
val transformationSpec = transitions.transitionSpecs.single().transformationSpec(aToB())
- assertThat(transformationSpec.progressSpec).isInstanceOf(SpringSpec::class.java)
+ assertThat(transformationSpec.progressSpec).isNull()
}
@Test
@@ -272,44 +270,10 @@
}
@Test
- fun springSpec() {
- val defaultSpec = spring<Float>(stiffness = 1f)
- val specFromAToC = spring<Float>(stiffness = 2f)
- val transitions = transitions {
- defaultMotionSpatialSpec = defaultSpec
-
- from(SceneA, to = SceneB) {
- // Default swipe spec.
- }
- from(SceneA, to = SceneC) { motionSpatialSpec = specFromAToC }
- }
-
- assertThat(transitions.defaultMotionSpatialSpec).isSameInstanceAs(defaultSpec)
-
- // A => B does not have a custom spec.
- assertThat(
- transitions
- .transitionSpec(from = SceneA, to = SceneB, key = null)
- .transformationSpec(aToB())
- .motionSpatialSpec
- )
- .isNull()
-
- // A => C has a custom swipe spec.
- assertThat(
- transitions
- .transitionSpec(from = SceneA, to = SceneC, key = null)
- .transformationSpec(transition(from = SceneA, to = SceneC))
- .motionSpatialSpec
- )
- .isSameInstanceAs(specFromAToC)
- }
-
- @Test
fun transitionIsPassedToBuilder() = runTest {
var transitionPassedToBuilder: TransitionState.Transition? = null
val state =
- MutableSceneTransitionLayoutState(
+ MutableSceneTransitionLayoutStateForTests(
SceneA,
transitions { from(SceneA, to = SceneB) { transitionPassedToBuilder = transition } },
)
@@ -340,7 +304,7 @@
}
}
- val state = MutableSceneTransitionLayoutState(SceneA, transitions)
+ val state = MutableSceneTransitionLayoutStateForTests(SceneA, transitions)
assertThrows(IllegalStateException::class.java) {
runBlocking { state.startTransition(transition(from = SceneA, to = SceneB)) }
}
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/NestedElementTransformationTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/NestedElementTransformationTest.kt
index 0da422b..bb511bc 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/NestedElementTransformationTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/NestedElementTransformationTest.kt
@@ -37,6 +37,7 @@
import com.android.compose.animation.scene.ContentScope
import com.android.compose.animation.scene.ElementKey
import com.android.compose.animation.scene.MutableSceneTransitionLayoutState
+import com.android.compose.animation.scene.MutableSceneTransitionLayoutStateForTests
import com.android.compose.animation.scene.Scale
import com.android.compose.animation.scene.SceneKey
import com.android.compose.animation.scene.SceneTransitionLayout
@@ -104,7 +105,9 @@
startScene: SceneKey,
transitions: SceneTransitions = SceneTransitions.Empty,
): MutableSceneTransitionLayoutState {
- return rule.runOnUiThread { MutableSceneTransitionLayoutState(startScene, transitions) }
+ return rule.runOnUiThread {
+ MutableSceneTransitionLayoutStateForTests(startScene, transitions)
+ }
}
private val threeNestedStls:
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/NestedSharedElementTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/NestedSharedElementTest.kt
index d8b7136..83dd6d3 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/NestedSharedElementTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/NestedSharedElementTest.kt
@@ -39,6 +39,7 @@
import com.android.compose.animation.scene.Default4FrameLinearTransition
import com.android.compose.animation.scene.Edge
import com.android.compose.animation.scene.MutableSceneTransitionLayoutState
+import com.android.compose.animation.scene.MutableSceneTransitionLayoutStateForTests
import com.android.compose.animation.scene.SceneKey
import com.android.compose.animation.scene.TestElements
import com.android.compose.animation.scene.TestScenes
@@ -94,7 +95,7 @@
private val nestedState: MutableSceneTransitionLayoutState =
rule.runOnUiThread {
- MutableSceneTransitionLayoutState(
+ MutableSceneTransitionLayoutStateForTests(
Scenes.NestedSceneA,
transitions {
from(
@@ -108,7 +109,7 @@
private val nestedNestedState: MutableSceneTransitionLayoutState =
rule.runOnUiThread {
- MutableSceneTransitionLayoutState(
+ MutableSceneTransitionLayoutStateForTests(
Scenes.NestedNestedSceneA,
transitions {
from(
diff --git a/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestContentScope.kt b/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestContentScope.kt
index 5cccfb1..6d47bab 100644
--- a/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestContentScope.kt
+++ b/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestContentScope.kt
@@ -29,6 +29,6 @@
currentScene: SceneKey = remember { SceneKey("current") },
content: @Composable ContentScope.() -> Unit,
) {
- val state = remember { MutableSceneTransitionLayoutState(currentScene) }
+ val state = rememberMutableSceneTransitionLayoutState(currentScene)
SceneTransitionLayout(state, modifier) { scene(currentScene, content = content) }
}
diff --git a/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestTransition.kt b/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestTransition.kt
index bc160fc..f94a7ed 100644
--- a/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestTransition.kt
+++ b/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestTransition.kt
@@ -129,13 +129,12 @@
builder: TransitionTestBuilder.() -> Unit,
) {
testTransition(
- state =
- runOnUiThread {
- MutableSceneTransitionLayoutState(
- fromScene,
- transitions { from(fromScene, to = toScene, builder = transition) },
- )
- },
+ state = {
+ rememberMutableSceneTransitionLayoutState(
+ fromScene,
+ transitions { from(fromScene, to = toScene, builder = transition) },
+ )
+ },
changeState = changeState,
transitionLayout = { state ->
SceneTransitionLayout(state, layoutModifier) {
@@ -157,13 +156,12 @@
builder: TransitionTestBuilder.() -> Unit,
) {
testTransition(
- state =
- runOnUiThread {
- MutableSceneTransitionLayoutState(
- fromScene,
- transitions = transitions { from(fromScene, overlay, builder = transition) },
- )
- },
+ state = {
+ rememberMutableSceneTransitionLayoutState(
+ fromScene,
+ transitions = transitions { from(fromScene, overlay, builder = transition) },
+ )
+ },
transitionLayout = { state ->
SceneTransitionLayout(state) {
scene(fromScene) { fromSceneContent() }
@@ -185,14 +183,13 @@
builder: TransitionTestBuilder.() -> Unit,
) {
testTransition(
- state =
- runOnUiThread {
- MutableSceneTransitionLayoutState(
- toScene,
- initialOverlays = setOf(overlay),
- transitions = transitions { from(overlay, toScene, builder = transition) },
- )
- },
+ state = {
+ rememberMutableSceneTransitionLayoutState(
+ toScene,
+ initialOverlays = setOf(overlay),
+ transitions = transitions { from(overlay, toScene, builder = transition) },
+ )
+ },
transitionLayout = { state ->
SceneTransitionLayout(state) {
scene(toScene) { toSceneContent() }
@@ -218,14 +215,13 @@
builder: TransitionTestBuilder.() -> Unit,
) {
testTransition(
- state =
- runOnUiThread {
- MutableSceneTransitionLayoutState(
- currentScene,
- initialOverlays = setOf(from),
- transitions = transitions { from(from, to, builder = transition) },
- )
- },
+ state = {
+ rememberMutableSceneTransitionLayoutState(
+ currentScene,
+ initialOverlays = setOf(from),
+ transitions = transitions { from(from, to, builder = transition) },
+ )
+ },
transitionLayout = { state ->
SceneTransitionLayout(state) {
scene(currentScene) { currentSceneContent() }
@@ -263,16 +259,14 @@
fromScene: SceneKey = TestScenes.SceneA,
toScene: SceneKey = TestScenes.SceneB,
): RecordedMotion {
- val state =
- toolkit.composeContentTestRule.runOnUiThread {
- MutableSceneTransitionLayoutState(
- fromScene,
- transitions { from(fromScene, to = toScene, builder = transition) },
- )
- }
-
+ lateinit var state: MutableSceneTransitionLayoutState
return recordMotion(
content = { play ->
+ state =
+ rememberMutableSceneTransitionLayoutState(
+ fromScene,
+ transitions { from(fromScene, to = toScene, builder = transition) },
+ )
LaunchedEffect(play) {
if (play) {
state.setTargetScene(toScene, animationScope = this)
@@ -309,7 +303,7 @@
}
testTransition(
- state = state,
+ state = { state },
changeState = { state -> state.setTargetScene(to, animationScope = this) },
transitionLayout = transitionLayout,
builder = builder,
@@ -323,7 +317,7 @@
builder: TransitionTestBuilder.() -> Unit,
) {
testTransition(
- state = states[0],
+ state = { states[0] },
changeState = { changeState(states) },
transitionLayout = { transitionLayout(states) },
builder = builder,
@@ -331,16 +325,18 @@
}
/** Test the transition from [state] to [to]. */
-fun ComposeContentTestRule.testTransition(
- state: MutableSceneTransitionLayoutState,
+private fun ComposeContentTestRule.testTransition(
+ state: @Composable () -> MutableSceneTransitionLayoutState,
changeState: CoroutineScope.(MutableSceneTransitionLayoutState) -> Unit,
transitionLayout: @Composable (state: MutableSceneTransitionLayoutState) -> Unit,
builder: TransitionTestBuilder.() -> Unit,
) {
lateinit var coroutineScope: CoroutineScope
+ lateinit var layoutState: MutableSceneTransitionLayoutState
setContent {
+ layoutState = state()
coroutineScope = rememberCoroutineScope()
- transitionLayout(state)
+ transitionLayout(layoutState)
}
val assertionScope =
@@ -390,7 +386,7 @@
mainClock.autoAdvance = false
// Change the current scene.
- runOnUiThread { coroutineScope.changeState(state) }
+ runOnUiThread { coroutineScope.changeState(layoutState) }
waitForIdle()
mainClock.advanceTimeByFrame()
waitForIdle()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt
index 69fb03d..4e8a2a3 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt
@@ -22,8 +22,12 @@
import android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_FINGERPRINT
import android.content.Intent
import android.content.pm.UserInfo
+import android.hardware.biometrics.BiometricAuthenticator.TYPE_ANY_BIOMETRIC
+import android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE
+import android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT
import android.hardware.biometrics.BiometricManager
import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback
+import android.platform.test.annotations.EnableFlags
import android.testing.TestableLooper
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
@@ -75,6 +79,8 @@
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.stub
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@@ -153,7 +159,7 @@
fun fingerprintEnrollmentChange() =
testScope.runTest {
createBiometricSettingsRepository()
- biometricsAreEnabledBySettings()
+ biometricsAreEnabledBySettings(PRIMARY_USER_ID, TYPE_FINGERPRINT)
val fingerprintAllowed = collectLastValue(underTest.isFingerprintEnrolledAndEnabled)
runCurrent()
@@ -173,7 +179,7 @@
fun fingerprintEnabledStateChange() =
testScope.runTest {
createBiometricSettingsRepository()
- biometricsAreEnabledBySettings()
+ biometricsAreEnabledBySettings(PRIMARY_USER_ID, TYPE_FINGERPRINT)
val fingerprintAllowed = collectLastValue(underTest.isFingerprintEnrolledAndEnabled)
runCurrent()
@@ -183,21 +189,59 @@
assertThat(fingerprintAllowed()).isTrue()
// when biometrics are not enabled by settings
- biometricsAreNotEnabledBySettings()
+ biometricsAreNotEnabledBySettings(PRIMARY_USER_ID, TYPE_FINGERPRINT)
assertThat(fingerprintAllowed()).isFalse()
// when biometrics are enabled by settings
- biometricsAreEnabledBySettings()
+ biometricsAreEnabledBySettings(PRIMARY_USER_ID, TYPE_FINGERPRINT)
assertThat(fingerprintAllowed()).isTrue()
}
@Test
+ @EnableFlags(com.android.settings.flags.Flags.FLAG_BIOMETRICS_ONBOARDING_EDUCATION)
+ fun enabledStateChange_typeFingerprintEnabled_typeFaceDisabled() =
+ testScope.runTest {
+ createBiometricSettingsRepository()
+ biometricsAreEnabledBySettings(PRIMARY_USER_ID, TYPE_FINGERPRINT)
+ val fingerprintAllowed = collectLastValue(underTest.isFingerprintEnrolledAndEnabled)
+ val faceAllowed = collectLastValue(underTest.isFaceAuthEnrolledAndEnabled)
+ runCurrent()
+
+ // start state
+ authController.stub {
+ on { isFingerprintEnrolled(anyInt()) } doReturn true
+ }
+ enrollmentChange(UNDER_DISPLAY_FINGERPRINT, PRIMARY_USER_ID, true)
+ assertThat(fingerprintAllowed()).isTrue()
+ assertThat(faceAllowed()).isFalse()
+ }
+
+ @Test
+ @EnableFlags(com.android.settings.flags.Flags.FLAG_BIOMETRICS_ONBOARDING_EDUCATION)
+ fun enabledStateChange_typeFingerprintDisabled_typeFaceEnabled() =
+ testScope.runTest {
+ createBiometricSettingsRepository()
+ biometricsAreEnabledBySettings(PRIMARY_USER_ID, TYPE_FACE)
+ val fingerprintAllowed = collectLastValue(underTest.isFingerprintEnrolledAndEnabled)
+ val faceAllowed = collectLastValue(underTest.isFaceAuthEnrolledAndEnabled)
+ runCurrent()
+
+ // start state
+ authController.stub {
+ on { isFaceAuthEnrolled(anyInt()) } doReturn true
+ }
+ enrollmentChange(FACE, PRIMARY_USER_ID, true)
+ assertThat(fingerprintAllowed()).isFalse()
+ assertThat(faceAllowed()).isTrue()
+ }
+
+ @Test
fun strongBiometricAllowedChange() =
testScope.runTest {
fingerprintIsEnrolled()
doNotDisableKeyguardAuthFeatures()
createBiometricSettingsRepository()
- biometricsAreEnabledBySettings()
+ biometricsAreEnabledBySettings(PRIMARY_USER_ID, TYPE_FINGERPRINT)
val strongBiometricAllowed by
collectLastValue(underTest.isFingerprintAuthCurrentlyAllowed)
@@ -220,7 +264,7 @@
createBiometricSettingsRepository()
val convenienceFaceAuthAllowed by collectLastValue(underTest.isFaceAuthCurrentlyAllowed)
doNotDisableKeyguardAuthFeatures()
- biometricsAreEnabledBySettings()
+ biometricsAreEnabledBySettings(PRIMARY_USER_ID, TYPE_FACE)
onStrongAuthChanged(STRONG_AUTH_NOT_REQUIRED, PRIMARY_USER_ID)
onNonStrongAuthChanged(true, PRIMARY_USER_ID)
@@ -282,7 +326,7 @@
faceAuthIsEnrolled()
createBiometricSettingsRepository()
doNotDisableKeyguardAuthFeatures()
- biometricsAreEnabledBySettings()
+ biometricsAreEnabledBySettings(PRIMARY_USER_ID, TYPE_FACE)
runCurrent()
val convenienceBiometricAllowed by
@@ -315,7 +359,7 @@
testScope.runTest {
fingerprintIsEnrolled(PRIMARY_USER_ID)
createBiometricSettingsRepository()
- biometricsAreEnabledBySettings()
+ biometricsAreEnabledBySettings(PRIMARY_USER_ID, TYPE_FINGERPRINT)
val fingerprintEnabledByDevicePolicy =
collectLastValue(underTest.isFingerprintEnrolledAndEnabled)
@@ -341,7 +385,7 @@
createBiometricSettingsRepository()
val faceAuthAllowed = collectLastValue(underTest.isFaceAuthEnrolledAndEnabled)
- biometricsAreEnabledBySettings()
+ biometricsAreEnabledBySettings(PRIMARY_USER_ID, TYPE_FACE)
doNotDisableKeyguardAuthFeatures(PRIMARY_USER_ID)
@@ -376,16 +420,22 @@
assertThat(faceAuthAllowed()).isTrue()
}
- private fun biometricsAreEnabledBySettings(userId: Int = PRIMARY_USER_ID) {
+ private fun biometricsAreEnabledBySettings(
+ userId: Int = PRIMARY_USER_ID,
+ modality: Int = TYPE_ANY_BIOMETRIC,
+ ) {
verify(biometricManager, atLeastOnce())
.registerEnabledOnKeyguardCallback(biometricManagerCallback.capture())
- biometricManagerCallback.value.onChanged(true, userId)
+ biometricManagerCallback.value.onChanged(true, userId, modality)
}
- private fun biometricsAreNotEnabledBySettings(userId: Int = PRIMARY_USER_ID) {
+ private fun biometricsAreNotEnabledBySettings(
+ userId: Int = PRIMARY_USER_ID,
+ modality: Int = TYPE_ANY_BIOMETRIC,
+ ) {
verify(biometricManager, atLeastOnce())
.registerEnabledOnKeyguardCallback(biometricManagerCallback.capture())
- biometricManagerCallback.value.onChanged(false, userId)
+ biometricManagerCallback.value.onChanged(false, userId, modality)
}
@Test
@@ -413,7 +463,7 @@
userRepository.setSelectedUserInfo(ANOTHER_USER)
doNotDisableKeyguardAuthFeatures(ANOTHER_USER_ID)
- biometricManagerCallback.value.onChanged(true, ANOTHER_USER_ID)
+ biometricManagerCallback.value.onChanged(true, ANOTHER_USER_ID, TYPE_FACE)
onNonStrongAuthChanged(true, ANOTHER_USER_ID)
whenever(authController.isFaceAuthEnrolled(ANOTHER_USER_ID)).thenReturn(true)
enrollmentChange(FACE, ANOTHER_USER_ID, true)
@@ -441,7 +491,7 @@
assertThat(isFaceAuthAllowed()).isFalse()
- biometricManagerCallback.value.onChanged(true, PRIMARY_USER_ID)
+ biometricManagerCallback.value.onChanged(true, PRIMARY_USER_ID, TYPE_FACE)
runCurrent()
assertThat(isFaceAuthAllowed()).isFalse()
@@ -458,7 +508,7 @@
faceAuthIsEnrolled()
createBiometricSettingsRepository()
- biometricsAreEnabledBySettings()
+ biometricsAreEnabledBySettings(PRIMARY_USER_ID, TYPE_FACE)
doNotDisableKeyguardAuthFeatures()
mobileConnectionsRepository.fake.isAnySimSecure.value = false
runCurrent()
@@ -485,7 +535,7 @@
deviceIsInPostureThatSupportsFaceAuth()
doNotDisableKeyguardAuthFeatures()
faceAuthIsStrongBiometric()
- biometricsAreEnabledBySettings()
+ biometricsAreEnabledBySettings(PRIMARY_USER_ID, TYPE_FACE)
mobileConnectionsRepository.fake.isAnySimSecure.value = false
onStrongAuthChanged(STRONG_AUTH_NOT_REQUIRED, PRIMARY_USER_ID)
@@ -512,12 +562,12 @@
assertThat(isFaceAuthAllowed()).isFalse()
// Value changes for another user
- biometricManagerCallback.value.onChanged(true, ANOTHER_USER_ID)
+ biometricManagerCallback.value.onChanged(true, ANOTHER_USER_ID, TYPE_FACE)
assertThat(isFaceAuthAllowed()).isFalse()
// Value changes for current user.
- biometricManagerCallback.value.onChanged(true, PRIMARY_USER_ID)
+ biometricManagerCallback.value.onChanged(true, PRIMARY_USER_ID, TYPE_FACE)
assertThat(isFaceAuthAllowed()).isTrue()
}
@@ -537,13 +587,13 @@
.registerEnabledOnKeyguardCallback(biometricManagerCallback.capture())
val isFingerprintEnrolledAndEnabled =
collectLastValue(underTest.isFingerprintEnrolledAndEnabled)
- biometricManagerCallback.value.onChanged(true, ANOTHER_USER_ID)
+ biometricManagerCallback.value.onChanged(true, ANOTHER_USER_ID, TYPE_FINGERPRINT)
runCurrent()
userRepository.setSelectedUserInfo(ANOTHER_USER)
runCurrent()
assertThat(isFingerprintEnrolledAndEnabled()).isFalse()
- biometricManagerCallback.value.onChanged(true, PRIMARY_USER_ID)
+ biometricManagerCallback.value.onChanged(true, PRIMARY_USER_ID, TYPE_FINGERPRINT)
runCurrent()
userRepository.setSelectedUserInfo(PRIMARY_USER)
runCurrent()
@@ -559,7 +609,7 @@
verify(biometricManager)
.registerEnabledOnKeyguardCallback(biometricManagerCallback.capture())
val isFaceAuthAllowed = collectLastValue(underTest.isFaceAuthEnrolledAndEnabled)
- biometricManagerCallback.value.onChanged(true, ANOTHER_USER_ID)
+ biometricManagerCallback.value.onChanged(true, ANOTHER_USER_ID, TYPE_FACE)
runCurrent()
userRepository.setSelectedUserInfo(ANOTHER_USER)
@@ -691,7 +741,7 @@
deviceIsInPostureThatSupportsFaceAuth()
doNotDisableKeyguardAuthFeatures()
faceAuthIsStrongBiometric()
- biometricsAreEnabledBySettings()
+ biometricsAreEnabledBySettings(PRIMARY_USER_ID, TYPE_FACE)
onStrongAuthChanged(STRONG_AUTH_NOT_REQUIRED, PRIMARY_USER_ID)
onNonStrongAuthChanged(false, PRIMARY_USER_ID)
@@ -715,7 +765,7 @@
deviceIsInPostureThatSupportsFaceAuth()
doNotDisableKeyguardAuthFeatures()
faceAuthIsNonStrongBiometric()
- biometricsAreEnabledBySettings()
+ biometricsAreEnabledBySettings(PRIMARY_USER_ID, TYPE_FACE)
onStrongAuthChanged(STRONG_AUTH_NOT_REQUIRED, PRIMARY_USER_ID)
onNonStrongAuthChanged(false, PRIMARY_USER_ID)
@@ -737,7 +787,7 @@
fun fpAuthCurrentlyAllowed_dependsOnNonStrongAuthBiometricSetting_ifFpIsNotStrong() =
testScope.runTest {
createBiometricSettingsRepository()
- biometricsAreEnabledBySettings()
+ biometricsAreEnabledBySettings(PRIMARY_USER_ID, TYPE_FINGERPRINT)
val isFingerprintCurrentlyAllowed by
collectLastValue(underTest.isFingerprintAuthCurrentlyAllowed)
@@ -779,7 +829,7 @@
fun fpAuthCurrentlyAllowed_dependsOnStrongAuthBiometricSetting_ifFpIsStrong() =
testScope.runTest {
createBiometricSettingsRepository()
- biometricsAreEnabledBySettings()
+ biometricsAreEnabledBySettings(PRIMARY_USER_ID, TYPE_FINGERPRINT)
val isFingerprintCurrentlyAllowed by
collectLastValue(underTest.isFingerprintAuthCurrentlyAllowed)
diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
index 71b622a..9b852df 100644
--- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
+++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
@@ -61,6 +61,8 @@
import com.android.systemui.plugins.clocks.WeatherData
import com.android.systemui.plugins.clocks.ZenData
import com.android.systemui.plugins.clocks.ZenData.ZenMode
+import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.power.shared.model.ScreenPowerState
import com.android.systemui.res.R as SysuiR
import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.settings.UserTracker
@@ -106,6 +108,7 @@
private val zenModeController: ZenModeController,
private val zenModeInteractor: ZenModeInteractor,
private val userTracker: UserTracker,
+ private val powerInteractor: PowerInteractor,
) {
var loggers =
listOf(
@@ -377,13 +380,13 @@
override fun onTimeChanged() {
refreshTime()
}
-
- private fun refreshTime() {
- clock?.smallClock?.events?.onTimeTick()
- clock?.largeClock?.events?.onTimeTick()
- }
}
+ private fun refreshTime() {
+ clock?.smallClock?.events?.onTimeTick()
+ clock?.largeClock?.events?.onTimeTick()
+ }
+
@VisibleForTesting
internal fun listenForDnd(scope: CoroutineScope): Job {
ModesUi.assertInNewMode()
@@ -474,6 +477,7 @@
listenForAnyStateToAodTransition(this)
listenForAnyStateToLockscreenTransition(this)
listenForAnyStateToDozingTransition(this)
+ listenForScreenPowerOn(this)
}
}
smallTimeListener?.update(shouldTimeListenerRun)
@@ -643,6 +647,17 @@
}
}
+ @VisibleForTesting
+ internal fun listenForScreenPowerOn(scope: CoroutineScope): Job {
+ return scope.launch {
+ powerInteractor.screenPowerState.collect { powerState ->
+ if (powerState != ScreenPowerState.SCREEN_OFF) {
+ refreshTime()
+ }
+ }
+ }
+ }
+
class TimeListener(val clockFace: ClockFaceController, val executor: DelayableExecutor) {
val predrawListener =
ViewTreeObserver.OnPreDrawListener {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 7d291c3..61038ef 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -437,8 +437,12 @@
private final IBiometricEnabledOnKeyguardCallback mBiometricEnabledCallback =
new IBiometricEnabledOnKeyguardCallback.Stub() {
@Override
- public void onChanged(boolean enabled, int userId) {
+ public void onChanged(boolean enabled, int userId, int modality) {
mHandler.post(() -> {
+ if (com.android.settings.flags.Flags.biometricsOnboardingEducation()
+ && modality != TYPE_FINGERPRINT) {
+ return;
+ }
mBiometricEnabledForUser.put(userId, enabled);
updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE);
});
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt
index ab8cc71..4e7de5d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt
@@ -20,6 +20,9 @@
import android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED
import android.content.Context
import android.content.IntentFilter
+import android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE
+import android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT
+import android.hardware.biometrics.BiometricAuthenticator.TYPE_NONE
import android.hardware.biometrics.BiometricManager
import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback
import android.os.UserHandle
@@ -141,6 +144,8 @@
) : BiometricSettingsRepository, Dumpable {
private val biometricsEnabledForUser = mutableMapOf<Int, Boolean>()
+ private val fingerprintEnabledForUser = mutableMapOf<Int, Boolean>()
+ private val faceEnabledForUser = mutableMapOf<Int, Boolean>()
override val isFaceAuthSupportedInCurrentPosture: Flow<Boolean>
@@ -246,10 +251,25 @@
}
}
- private val areBiometricsEnabledForCurrentUser: Flow<Boolean> =
+ private val isFingerprintEnabledForCurrentUser: Flow<Boolean> =
userRepository.selectedUserInfo.flatMapLatest { userInfo ->
areBiometricsEnabledForDeviceEntryFromUserSetting.map {
- biometricsEnabledForUser[userInfo.id] ?: false
+ if (com.android.settings.flags.Flags.biometricsOnboardingEducation()) {
+ fingerprintEnabledForUser[userInfo.id] ?: false
+ } else {
+ biometricsEnabledForUser[userInfo.id] ?: false
+ }
+ }
+ }
+
+ private val isFaceEnabledForCurrentUser: Flow<Boolean> =
+ userRepository.selectedUserInfo.flatMapLatest { userInfo ->
+ areBiometricsEnabledForDeviceEntryFromUserSetting.map {
+ if (com.android.settings.flags.Flags.biometricsOnboardingEducation()) {
+ faceEnabledForUser[userInfo.id] ?: false
+ } else {
+ biometricsEnabledForUser[userInfo.id] ?: false
+ }
}
}
@@ -264,31 +284,44 @@
.distinctUntilChanged()
private val isFaceAuthenticationEnabled: Flow<Boolean> =
- combine(areBiometricsEnabledForCurrentUser, isFaceEnabledByDevicePolicy) {
+ combine(isFaceEnabledForCurrentUser, isFaceEnabledByDevicePolicy) {
biometricsManagerSetting,
devicePolicySetting ->
biometricsManagerSetting && devicePolicySetting
}
- private val areBiometricsEnabledForDeviceEntryFromUserSetting: Flow<Pair<Int, Boolean>> =
+ private val areBiometricsEnabledForDeviceEntryFromUserSetting: Flow<Triple<Int, Boolean, Int>> =
conflatedCallbackFlow {
val callback =
object : IBiometricEnabledOnKeyguardCallback.Stub() {
- override fun onChanged(enabled: Boolean, userId: Int) {
+ override fun onChanged(enabled: Boolean, userId: Int, modality: Int) {
trySendWithFailureLogging(
- Pair(userId, enabled),
+ Triple(userId, enabled, modality),
TAG,
- "biometricsEnabled state changed"
+ "biometricsEnabled state changed",
)
}
}
biometricManager?.registerEnabledOnKeyguardCallback(callback)
awaitClose {}
}
- .onEach { biometricsEnabledForUser[it.first] = it.second }
+ .onEach {
+ if (com.android.settings.flags.Flags.biometricsOnboardingEducation()) {
+ when (it.third) {
+ TYPE_FACE -> {
+ faceEnabledForUser[it.first] = it.second
+ }
+ TYPE_FINGERPRINT -> {
+ fingerprintEnabledForUser[it.first] = it.second
+ }
+ }
+ } else {
+ biometricsEnabledForUser[it.first] = it.second
+ }
+ }
// This is because the callback is binder-based and we want to avoid multiple callbacks
// being registered.
- .stateIn(scope, SharingStarted.Eagerly, Pair(0, false))
+ .stateIn(scope, SharingStarted.Eagerly, Triple(0, false, TYPE_NONE))
private val isStrongBiometricAllowed: StateFlow<Boolean> =
strongAuthTracker.isStrongBiometricAllowed.stateIn(
@@ -333,7 +366,7 @@
override val isFingerprintEnrolledAndEnabled: StateFlow<Boolean> =
isFingerprintEnrolled
- .and(areBiometricsEnabledForCurrentUser)
+ .and(isFingerprintEnabledForCurrentUser)
.and(isFingerprintEnabledByDevicePolicy)
.stateIn(scope, SharingStarted.Eagerly, false)
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt
index d94424c..e19d6e9 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt
@@ -57,6 +57,7 @@
// activity and a null second task, so the foreground task will be index 1, but when
// opening the app selector in split screen mode, the foreground task will be the second
// task in index 0.
+ // TODO(346588978): This needs to be updated for mixed groups
val foregroundGroup =
if (groupedTasks.firstOrNull()?.splitBounds != null) groupedTasks.first()
else groupedTasks.elementAtOrNull(1)
@@ -69,7 +70,7 @@
it.taskInfo1,
it.taskInfo1.taskId in foregroundTaskIds && it.taskInfo1.isVisible,
userManager.getUserInfo(it.taskInfo1.userId).toUserType(),
- it.splitBounds
+ it.splitBounds,
)
val task2 =
@@ -78,7 +79,7 @@
it.taskInfo2!!,
it.taskInfo2!!.taskId in foregroundTaskIds && it.taskInfo2!!.isVisible,
userManager.getUserInfo(it.taskInfo2!!.userId).toUserType(),
- it.splitBounds
+ it.splitBounds,
)
} else null
@@ -92,7 +93,7 @@
Integer.MAX_VALUE,
RECENT_IGNORE_UNAVAILABLE,
userTracker.userId,
- backgroundExecutor
+ backgroundExecutor,
) { tasks ->
continuation.resume(tasks)
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt b/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
index b53685e..6d90784 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
@@ -57,7 +57,6 @@
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Alignment
@@ -95,6 +94,7 @@
import com.android.compose.animation.scene.SceneKey
import com.android.compose.animation.scene.SceneTransitionLayout
import com.android.compose.animation.scene.content.state.TransitionState
+import com.android.compose.animation.scene.rememberMutableSceneTransitionLayoutState
import com.android.compose.animation.scene.transitions
import com.android.compose.modifiers.height
import com.android.compose.modifiers.padding
@@ -313,8 +313,8 @@
*/
@Composable
private fun CollapsableQuickSettingsSTL() {
- val sceneState = remember {
- MutableSceneTransitionLayoutState(
+ val sceneState =
+ rememberMutableSceneTransitionLayoutState(
viewModel.expansionState.toIdleSceneKey(),
transitions =
transitions {
@@ -323,7 +323,6 @@
}
},
)
- }
LaunchedEffect(Unit) {
synchronizeQsState(
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt
index c1e8032..40e6d28 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt
@@ -30,6 +30,7 @@
import com.android.compose.animation.scene.UserActionResult
import com.android.systemui.classifier.Classifier
import com.android.systemui.classifier.domain.interactor.FalsingInteractor
+import com.android.systemui.keyguard.ui.viewmodel.LightRevealScrimViewModel
import com.android.systemui.lifecycle.ExclusiveActivatable
import com.android.systemui.lifecycle.Hydrator
import com.android.systemui.power.domain.interactor.PowerInteractor
@@ -41,6 +42,7 @@
import com.android.systemui.shade.shared.model.ShadeMode
import com.android.systemui.statusbar.domain.interactor.RemoteInputInteractor
import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer
+import com.android.systemui.wallpapers.ui.viewmodel.WallpaperViewModel
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
@@ -62,6 +64,8 @@
private val splitEdgeDetector: SplitEdgeDetector,
private val logger: SceneLogger,
hapticsViewModelFactory: SceneContainerHapticsViewModel.Factory,
+ val lightRevealScrim: LightRevealScrimViewModel,
+ val wallpaperViewModel: WallpaperViewModel,
@Assisted view: View,
@Assisted private val motionEventHandlerReceiver: (MotionEventHandler?) -> Unit,
) : ExclusiveActivatable() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt
index 2885ce8..a56624c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt
@@ -84,7 +84,7 @@
scrim.width * initialWidthMultiplier + -scrim.width * ovalWidthIncreaseAmount,
scrim.height * OVAL_INITIAL_TOP_PERCENT - scrim.height * interpolatedAmount,
scrim.width * (1f - initialWidthMultiplier) + scrim.width * ovalWidthIncreaseAmount,
- scrim.height * OVAL_INITIAL_BOTTOM_PERCENT + scrim.height * interpolatedAmount
+ scrim.height * OVAL_INITIAL_BOTTOM_PERCENT + scrim.height * interpolatedAmount,
)
}
}
@@ -98,7 +98,7 @@
/* controlX1= */ 0.4f,
/* controlY1= */ 0f,
/* controlX2= */ 0.2f,
- /* controlY2= */ 1f
+ /* controlY2= */ 1f,
)
override fun setRevealAmountOnScrim(amount: Float, scrim: LightRevealScrim) {
@@ -109,14 +109,14 @@
scrim.startColorAlpha =
getPercentPastThreshold(
1 - interpolatedAmount,
- threshold = 1 - START_COLOR_REVEAL_PERCENTAGE
+ threshold = 1 - START_COLOR_REVEAL_PERCENTAGE,
)
scrim.revealGradientEndColorAlpha =
1f -
getPercentPastThreshold(
interpolatedAmount,
- threshold = REVEAL_GRADIENT_END_COLOR_ALPHA_START_PERCENTAGE
+ threshold = REVEAL_GRADIENT_END_COLOR_ALPHA_START_PERCENTAGE,
)
// Start changing gradient bounds later to avoid harsh gradient in the beginning
@@ -127,14 +127,14 @@
left = scrim.viewWidth / 2 - (scrim.viewWidth / 2) * gradientBoundsAmount,
top = 0f,
right = scrim.viewWidth / 2 + (scrim.viewWidth / 2) * gradientBoundsAmount,
- bottom = scrim.viewHeight.toFloat()
+ bottom = scrim.viewHeight.toFloat(),
)
} else {
scrim.setRevealGradientBounds(
left = 0f,
top = scrim.viewHeight / 2 - (scrim.viewHeight / 2) * gradientBoundsAmount,
right = scrim.viewWidth.toFloat(),
- bottom = scrim.viewHeight / 2 + (scrim.viewHeight / 2) * gradientBoundsAmount
+ bottom = scrim.viewHeight / 2 + (scrim.viewHeight / 2) * gradientBoundsAmount,
)
}
}
@@ -166,7 +166,7 @@
1f -
getPercentPastThreshold(
amount,
- threshold = REVEAL_GRADIENT_END_COLOR_ALPHA_START_PERCENTAGE
+ threshold = REVEAL_GRADIENT_END_COLOR_ALPHA_START_PERCENTAGE,
)
val gradientBoundsAmount = lerp(GRADIENT_START_BOUNDS_PERCENTAGE, 1f, amount)
@@ -175,14 +175,14 @@
left = -(scrim.viewWidth) * gradientBoundsAmount,
top = -(scrim.viewHeight) * gradientBoundsAmount,
right = (scrim.viewWidth) * gradientBoundsAmount,
- bottom = (scrim.viewHeight) + (scrim.viewHeight) * gradientBoundsAmount
+ bottom = (scrim.viewHeight) + (scrim.viewHeight) * gradientBoundsAmount,
)
} else {
scrim.setRevealGradientBounds(
left = -(scrim.viewWidth) * gradientBoundsAmount,
top = -(scrim.viewHeight) * gradientBoundsAmount,
right = (scrim.viewWidth) + (scrim.viewWidth) * gradientBoundsAmount,
- bottom = (scrim.viewHeight) * gradientBoundsAmount
+ bottom = (scrim.viewHeight) * gradientBoundsAmount,
)
}
}
@@ -212,7 +212,7 @@
/** Radius of initial state of circle reveal */
val startRadius: Int,
/** Radius of end state of circle reveal */
- val endRadius: Int
+ val endRadius: Int,
) : LightRevealEffect {
override fun setRevealAmountOnScrim(amount: Float, scrim: LightRevealScrim) {
// reveal amount updates already have an interpolator, so we intentionally use the
@@ -225,7 +225,7 @@
centerX - radius /* left */,
centerY - radius /* top */,
centerX + radius /* right */,
- centerY + radius /* bottom */
+ centerY + radius, /* bottom */
)
}
}
@@ -259,7 +259,7 @@
powerButtonY - height * interpolatedAmount,
width * (1f + OFF_SCREEN_START_AMOUNT) +
width * INCREASE_MULTIPLIER * interpolatedAmount,
- powerButtonY + height * interpolatedAmount
+ powerButtonY + height * interpolatedAmount,
)
} else if (rotation == RotationUtils.ROTATION_LANDSCAPE) {
setRevealGradientBounds(
@@ -268,7 +268,7 @@
height * INCREASE_MULTIPLIER * interpolatedAmount,
powerButtonY + width * interpolatedAmount,
(-height * OFF_SCREEN_START_AMOUNT) +
- height * INCREASE_MULTIPLIER * interpolatedAmount
+ height * INCREASE_MULTIPLIER * interpolatedAmount,
)
} else {
// RotationUtils.ROTATION_SEASCAPE
@@ -278,7 +278,7 @@
height * INCREASE_MULTIPLIER * interpolatedAmount,
(width - powerButtonY) + width * interpolatedAmount,
height * (1f + OFF_SCREEN_START_AMOUNT) +
- height * INCREASE_MULTIPLIER * interpolatedAmount
+ height * INCREASE_MULTIPLIER * interpolatedAmount,
)
}
}
@@ -296,9 +296,9 @@
@JvmOverloads
constructor(
context: Context?,
- attrs: AttributeSet?,
+ attrs: AttributeSet? = null,
initialWidth: Int? = null,
- initialHeight: Int? = null
+ initialHeight: Int? = null,
) : View(context, attrs) {
private val logString = this::class.simpleName!! + "@" + hashCode()
@@ -322,8 +322,9 @@
revealEffect.setRevealAmountOnScrim(value, this)
updateScrimOpaque()
TrackTracer.instantForGroup(
- "scrim", { "light_reveal_amount $logString" },
- (field * 100).toInt()
+ "scrim",
+ { "light_reveal_amount $logString" },
+ (field * 100).toInt(),
)
invalidate()
}
@@ -440,7 +441,7 @@
1f,
intArrayOf(Color.TRANSPARENT, Color.WHITE),
floatArrayOf(0f, 1f),
- Shader.TileMode.CLAMP
+ Shader.TileMode.CLAMP,
)
// SRC_OVER ensures that we draw the semitransparent pixels over other views in the same
@@ -466,6 +467,7 @@
viewWidth = measuredWidth
viewHeight = measuredHeight
}
+
/**
* Sets bounds for the transparent oval gradient that reveals the views below the scrim. This is
* simply a helper method that sets [revealGradientCenter], [revealGradientWidth], and
@@ -513,7 +515,7 @@
gradientPaint.colorFilter =
PorterDuffColorFilter(
getColorWithAlpha(revealGradientEndColor, revealGradientEndColorAlpha),
- PorterDuff.Mode.MULTIPLY
+ PorterDuff.Mode.MULTIPLY,
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt
index 99e8472..541a07c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt
@@ -17,7 +17,7 @@
package com.android.systemui.statusbar.chips.call.ui.viewmodel
import android.view.View
-import com.android.internal.jank.InteractionJankMonitor
+import com.android.internal.jank.Cuj
import com.android.systemui.animation.ActivityTransitionAnimator
import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.Icon
@@ -128,7 +128,7 @@
state.intent,
ActivityTransitionAnimator.Controller.fromView(
backgroundView,
- InteractionJankMonitor.CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP,
+ Cuj.CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP,
),
)
}
@@ -145,7 +145,7 @@
StatusBarChipsModernization.assertInNewMode()
val animationController =
expandable.activityTransitionController(
- InteractionJankMonitor.CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP
+ Cuj.CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP
)
activityStarter.postStartActivityDismissingKeyguard(
state.intent,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index d43fed0..2cd8eaf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -31,7 +31,6 @@
import android.annotation.IntDef;
import android.graphics.Color;
import android.os.Handler;
-import android.os.Trace;
import android.util.Log;
import android.util.MathUtils;
import android.util.Pair;
@@ -53,6 +52,7 @@
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.KeyguardUpdateMonitorCallback;
import com.android.systemui.Dumpable;
+import com.android.systemui.Flags;
import com.android.systemui.animation.ShadeInterpolation;
import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants;
import com.android.systemui.dagger.SysUISingleton;
@@ -82,6 +82,9 @@
import com.android.systemui.util.wakelock.DelayedWakeLock;
import com.android.systemui.util.wakelock.WakeLock;
+import kotlinx.coroutines.CoroutineDispatcher;
+import kotlinx.coroutines.ExperimentalCoroutinesApi;
+
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -90,9 +93,6 @@
import javax.inject.Inject;
-import kotlinx.coroutines.CoroutineDispatcher;
-import kotlinx.coroutines.ExperimentalCoroutinesApi;
-
/**
* Controls both the scrim behind the notifications and in front of the notifications (when a
* security method gets shown).
@@ -226,6 +226,8 @@
private float mAdditionalScrimBehindAlphaKeyguard = 0f;
// Combined scrim behind keyguard alpha of default scrim + additional scrim
private float mScrimBehindAlphaKeyguard = KEYGUARD_SCRIM_ALPHA;
+
+ static final float TRANSPARENT_BOUNCER_SCRIM_ALPHA = 0.54f;
private final float mDefaultScrimAlpha;
private float mRawPanelExpansionFraction;
@@ -340,7 +342,10 @@
LargeScreenShadeInterpolator largeScreenShadeInterpolator) {
mScrimStateListener = lightBarController::setScrimState;
mLargeScreenShadeInterpolator = largeScreenShadeInterpolator;
- mDefaultScrimAlpha = BUSY_SCRIM_ALPHA;
+ // All scrims default alpha need to match bouncer background alpha to make sure the
+ // transitions involving the bouncer are smooth and don't overshoot the bouncer alpha.
+ mDefaultScrimAlpha =
+ Flags.bouncerUiRevamp() ? TRANSPARENT_BOUNCER_SCRIM_ALPHA : BUSY_SCRIM_ALPHA;
mKeyguardStateController = keyguardStateController;
mDarkenWhileDragging = !mKeyguardStateController.canDismissLockScreen();
@@ -974,8 +979,6 @@
mBehindTint,
interpolatedFraction);
}
- } else if (mState == ScrimState.AUTH_SCRIMMED_SHADE) {
- mNotificationsAlpha = (float) Math.pow(getInterpolatedFraction(), 0.8f);
} else if (mState == ScrimState.KEYGUARD || mState == ScrimState.SHADE_LOCKED
|| mState == ScrimState.PULSING || mState == ScrimState.GLANCEABLE_HUB) {
Pair<Integer, Float> result = calculateBackStateForState(mState);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
index 8dcb663..1493729 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
@@ -16,17 +16,24 @@
package com.android.systemui.statusbar.phone;
+import static com.android.systemui.statusbar.phone.ScrimController.BUSY_SCRIM_ALPHA;
+import static com.android.systemui.statusbar.phone.ScrimController.TRANSPARENT_BOUNCER_SCRIM_ALPHA;
+
import android.graphics.Color;
import com.android.app.tracing.coroutines.TrackTracer;
+import com.android.systemui.Flags;
import com.android.systemui.dock.DockManager;
import com.android.systemui.res.R;
import com.android.systemui.scrim.ScrimView;
import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
+import kotlinx.coroutines.ExperimentalCoroutinesApi;
+
/**
* Possible states of the ScrimController state machine.
*/
+@ExperimentalCoroutinesApi
public enum ScrimState {
/**
@@ -92,40 +99,19 @@
}
},
- AUTH_SCRIMMED_SHADE {
- @Override
- public void prepare(ScrimState previousState) {
- // notif scrim alpha values are determined by ScrimController#applyState
- // based on the shade expansion
-
- mFrontTint = mBackgroundColor;
- mFrontAlpha = .66f;
-
- mBehindTint = mBackgroundColor;
- mBehindAlpha = 1f;
- }
- },
-
- AUTH_SCRIMMED {
- @Override
- public void prepare(ScrimState previousState) {
- mNotifTint = previousState.mNotifTint;
- mNotifAlpha = previousState.mNotifAlpha;
-
- mBehindTint = previousState.mBehindTint;
- mBehindAlpha = previousState.mBehindAlpha;
-
- mFrontTint = mBackgroundColor;
- mFrontAlpha = .66f;
- }
- },
-
/**
* Showing password challenge on the keyguard.
*/
BOUNCER {
@Override
public void prepare(ScrimState previousState) {
+ if (Flags.bouncerUiRevamp()) {
+ mBehindAlpha = mClipQsScrim ? 0.0f : TRANSPARENT_BOUNCER_SCRIM_ALPHA;
+ mNotifAlpha = mClipQsScrim ? TRANSPARENT_BOUNCER_SCRIM_ALPHA : 0;
+ mBehindTint = mNotifTint = mSurfaceColor;
+ mFrontAlpha = 0f;
+ return;
+ }
mBehindAlpha = mClipQsScrim ? 1 : mDefaultScrimAlpha;
mBehindTint = mClipQsScrim ? mBackgroundColor : mSurfaceColor;
mNotifAlpha = mClipQsScrim ? mDefaultScrimAlpha : 0;
@@ -136,6 +122,10 @@
@Override
public void setSurfaceColor(int surfaceColor) {
super.setSurfaceColor(surfaceColor);
+ if (Flags.bouncerUiRevamp()) {
+ mBehindTint = mNotifTint = mSurfaceColor;
+ return;
+ }
if (!mClipQsScrim) {
mBehindTint = mSurfaceColor;
}
@@ -146,15 +136,38 @@
* Showing password challenge on top of a FLAG_SHOW_WHEN_LOCKED activity.
*/
BOUNCER_SCRIMMED {
+ @ExperimentalCoroutinesApi
@Override
public void prepare(ScrimState previousState) {
+ if (Flags.bouncerUiRevamp()) {
+ mBehindAlpha = 0f;
+ mFrontAlpha = TRANSPARENT_BOUNCER_SCRIM_ALPHA;
+ mFrontTint = mSurfaceColor;
+ return;
+ }
mBehindAlpha = 0;
mFrontAlpha = mDefaultScrimAlpha;
}
+
+ @Override
+ public boolean shouldBlendWithMainColor() {
+ return !Flags.bouncerUiRevamp();
+ }
},
SHADE_LOCKED {
@Override
+ public void setDefaultScrimAlpha(float defaultScrimAlpha) {
+ super.setDefaultScrimAlpha(defaultScrimAlpha);
+ if (!Flags.notificationShadeBlur()) {
+ // Temporary change that prevents the shade from being semi-transparent when
+ // bouncer blur is enabled but notification shade blur is not enabled. This is
+ // required to perf test these two flags independently.
+ mDefaultScrimAlpha = BUSY_SCRIM_ALPHA;
+ }
+ }
+
+ @Override
public void prepare(ScrimState previousState) {
mBehindAlpha = mClipQsScrim ? 1 : mDefaultScrimAlpha;
mNotifAlpha = 1f;
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
index bac2c47..1729a4d 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
@@ -55,6 +55,7 @@
import com.android.systemui.plugins.clocks.ThemeConfig
import com.android.systemui.plugins.clocks.ZenData
import com.android.systemui.plugins.clocks.ZenData.ZenMode
+import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.res.R
import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.policy.BatteryController
@@ -131,6 +132,7 @@
@Mock private lateinit var parentView: View
@Mock private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor
@Mock private lateinit var userTracker: UserTracker
+ @Mock private lateinit var powerInteractor: PowerInteractor
@Mock private lateinit var zenModeController: ZenModeController
private var zenModeControllerCallback: ZenModeController.Callback? = null
@@ -178,6 +180,7 @@
zenModeController,
zenModeInteractor,
userTracker,
+ powerInteractor,
)
underTest.clock = clock
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index 839a2bd..2645811 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -18,6 +18,8 @@
import static android.app.StatusBarManager.SESSION_KEYGUARD;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.hardware.biometrics.BiometricAuthenticator.TYPE_ANY_BIOMETRIC;
+import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE;
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
import static android.hardware.biometrics.BiometricFaceConstants.FACE_ERROR_LOCKOUT_PERMANENT;
import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ERROR_LOCKOUT;
@@ -1507,6 +1509,72 @@
}
@Test
+ @EnableFlags(com.android.settings.flags.Flags.FLAG_BIOMETRICS_ONBOARDING_EDUCATION)
+ public void listenForFingerprint_whenEnabledForUser_typeFingerprint()
+ throws RemoteException {
+ // GIVEN keyguard showing
+ mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
+ mKeyguardUpdateMonitor.setKeyguardShowing(true, false);
+
+ biometricsEnabledForCurrentUser(true, TYPE_FINGERPRINT);
+ mTestableLooper.processAllMessages();
+
+ assertThat(mKeyguardUpdateMonitor.shouldListenForFingerprint(true)).isEqualTo(true);
+ }
+
+ @Test
+ @EnableFlags(com.android.settings.flags.Flags.FLAG_BIOMETRICS_ONBOARDING_EDUCATION)
+ public void doNotListenForFingerprint_whenDisabledForUser_typeFingerprint()
+ throws RemoteException {
+ // GIVEN keyguard showing
+ mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
+ mKeyguardUpdateMonitor.setKeyguardShowing(true, false);
+
+ biometricsEnabledForCurrentUser(false, TYPE_FINGERPRINT);
+ mTestableLooper.processAllMessages();
+
+ assertThat(mKeyguardUpdateMonitor.shouldListenForFingerprint(true)).isEqualTo(false);
+ }
+
+ @Test
+ @EnableFlags(com.android.settings.flags.Flags.FLAG_BIOMETRICS_ONBOARDING_EDUCATION)
+ public void listenForFingerprint_typeFingerprintEnabled_typeFaceDisabled()
+ throws RemoteException {
+ // GIVEN keyguard showing
+ mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
+ mKeyguardUpdateMonitor.setKeyguardShowing(true, false);
+
+ // Enable with fingerprint
+ biometricsEnabledForCurrentUser(true, TYPE_FINGERPRINT);
+ mTestableLooper.processAllMessages();
+
+ // Disable with face
+ biometricsEnabledForCurrentUser(false, TYPE_FACE);
+ mTestableLooper.processAllMessages();
+
+ assertThat(mKeyguardUpdateMonitor.shouldListenForFingerprint(true)).isEqualTo(true);
+ }
+
+ @Test
+ @EnableFlags(com.android.settings.flags.Flags.FLAG_BIOMETRICS_ONBOARDING_EDUCATION)
+ public void doNotListenForFingerprint_typeFingerprintDisabled_typeFaceEnabled()
+ throws RemoteException {
+ // GIVEN keyguard showing
+ mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
+ mKeyguardUpdateMonitor.setKeyguardShowing(true, false);
+
+ // Enable with face
+ biometricsEnabledForCurrentUser(true, TYPE_FACE);
+ mTestableLooper.processAllMessages();
+
+ // Disable with fingerprint
+ biometricsEnabledForCurrentUser(false, TYPE_FINGERPRINT);
+ mTestableLooper.processAllMessages();
+
+ assertThat(mKeyguardUpdateMonitor.shouldListenForFingerprint(true)).isEqualTo(false);
+ }
+
+ @Test
public void testOccludingAppFingerprintListeningState() {
// GIVEN keyguard isn't visible (app occluding)
mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
@@ -2464,8 +2532,13 @@
}
private void biometricsEnabledForCurrentUser() throws RemoteException {
- mBiometricEnabledOnKeyguardCallback.onChanged(true,
- mSelectedUserInteractor.getSelectedUserId());
+ biometricsEnabledForCurrentUser(true /* enabled */, TYPE_FINGERPRINT);
+ }
+
+ private void biometricsEnabledForCurrentUser(boolean enabled, int modality)
+ throws RemoteException {
+ mBiometricEnabledOnKeyguardCallback.onChanged(enabled,
+ mSelectedUserInteractor.getSelectedUserId(), modality);
}
private void primaryAuthNotRequiredByStrongAuthTracker() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
index 38ddb3e..a02d333 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
@@ -30,7 +30,6 @@
import static org.junit.Assert.assertFalse;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyFloat;
-import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
@@ -1425,8 +1424,7 @@
HashSet<ScrimState> regularStates = new HashSet<>(Arrays.asList(
ScrimState.UNINITIALIZED, ScrimState.KEYGUARD, BOUNCER,
ScrimState.DREAMING, ScrimState.BOUNCER_SCRIMMED, ScrimState.BRIGHTNESS_MIRROR,
- ScrimState.UNLOCKED, SHADE_LOCKED, ScrimState.AUTH_SCRIMMED,
- ScrimState.AUTH_SCRIMMED_SHADE, ScrimState.GLANCEABLE_HUB,
+ ScrimState.UNLOCKED, SHADE_LOCKED, ScrimState.GLANCEABLE_HUB,
ScrimState.GLANCEABLE_HUB_OVER_DREAM));
for (ScrimState state : ScrimState.values()) {
@@ -1451,79 +1449,6 @@
}
@Test
- public void testAuthScrim_setClipQSScrimTrue_notifScrimOpaque_whenShadeFullyExpanded() {
- // GIVEN device has an activity showing ('UNLOCKED' state can occur on the lock screen
- // with the camera app occluding the keyguard)
- mScrimController.legacyTransitionTo(ScrimState.UNLOCKED);
- mScrimController.setClipsQsScrim(true);
- mScrimController.setRawPanelExpansionFraction(1);
- // notifications scrim alpha change require calling setQsPosition
- mScrimController.setQsPosition(0, 300);
- finishAnimationsImmediately();
-
- // WHEN the user triggers the auth bouncer
- mScrimController.legacyTransitionTo(ScrimState.AUTH_SCRIMMED_SHADE);
- finishAnimationsImmediately();
-
- assertEquals("Behind scrim should be opaque",
- mScrimBehind.getViewAlpha(), 1, 0.0);
- assertEquals("Notifications scrim should be opaque",
- mNotificationsScrim.getViewAlpha(), 1, 0.0);
-
- assertScrimTinted(Map.of(
- mScrimInFront, true,
- mScrimBehind, true,
- mNotificationsScrim, false
- ));
- }
-
-
- @Test
- public void testAuthScrim_setClipQSScrimFalse_notifScrimOpaque_whenShadeFullyExpanded() {
- // GIVEN device has an activity showing ('UNLOCKED' state can occur on the lock screen
- // with the camera app occluding the keyguard)
- mScrimController.legacyTransitionTo(ScrimState.UNLOCKED);
- mScrimController.setClipsQsScrim(false);
- mScrimController.setRawPanelExpansionFraction(1);
- // notifications scrim alpha change require calling setQsPosition
- mScrimController.setQsPosition(0, 300);
- finishAnimationsImmediately();
-
- // WHEN the user triggers the auth bouncer
- mScrimController.legacyTransitionTo(ScrimState.AUTH_SCRIMMED_SHADE);
- finishAnimationsImmediately();
-
- assertEquals("Behind scrim should be opaque",
- mScrimBehind.getViewAlpha(), 1, 0.0);
- assertEquals("Notifications scrim should be opaque",
- mNotificationsScrim.getViewAlpha(), 1, 0.0);
-
- assertScrimTinted(Map.of(
- mScrimInFront, true,
- mScrimBehind, true,
- mNotificationsScrim, false
- ));
- }
-
- @Test
- public void testAuthScrimKeyguard() {
- // GIVEN device is on the keyguard
- mScrimController.legacyTransitionTo(ScrimState.KEYGUARD);
- finishAnimationsImmediately();
-
- // WHEN the user triggers the auth bouncer
- mScrimController.legacyTransitionTo(ScrimState.AUTH_SCRIMMED);
- finishAnimationsImmediately();
-
- // THEN the front scrim is updated and the KEYGUARD scrims are the same as the
- // KEYGUARD scrim state
- assertScrimAlpha(Map.of(
- mScrimInFront, SEMI_TRANSPARENT,
- mScrimBehind, SEMI_TRANSPARENT,
- mNotificationsScrim, TRANSPARENT));
- }
-
- @Test
public void testScrimsVisible_whenShadeVisible() {
mScrimController.setClipsQsScrim(true);
mScrimController.legacyTransitionTo(ScrimState.UNLOCKED);
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LightRevealScrimViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LightRevealScrimViewModelKosmos.kt
new file mode 100644
index 0000000..93da1f3
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LightRevealScrimViewModelKosmos.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import com.android.systemui.keyguard.domain.interactor.lightRevealScrimInteractor
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+
+val Kosmos.lightRevealScrimViewModel by Fixture {
+ LightRevealScrimViewModel(interactor = lightRevealScrimInteractor)
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneKosmos.kt
index 0eca818..609f97d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneKosmos.kt
@@ -4,6 +4,7 @@
import com.android.compose.animation.scene.ObservableTransitionState
import com.android.systemui.classifier.domain.interactor.falsingInteractor
import com.android.systemui.haptics.msdl.msdlPlayer
+import com.android.systemui.keyguard.ui.viewmodel.lightRevealScrimViewModel
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
import com.android.systemui.power.domain.interactor.powerInteractor
@@ -19,6 +20,7 @@
import com.android.systemui.scene.ui.viewmodel.splitEdgeDetector
import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.statusbar.domain.interactor.remoteInputInteractor
+import com.android.systemui.wallpapers.ui.viewmodel.wallpaperViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import org.mockito.kotlin.mock
@@ -96,6 +98,8 @@
hapticsViewModelFactory = sceneContainerHapticsViewModelFactory,
view = view,
motionEventHandlerReceiver = motionEventHandlerReceiver,
+ lightRevealScrim = lightRevealScrimViewModel,
+ wallpaperViewModel = wallpaperViewModel,
)
}
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/wallpapers/data/repository/WallpaperRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/wallpapers/data/repository/WallpaperRepositoryKosmos.kt
index 1d8c891..ddb9a3f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/wallpapers/data/repository/WallpaperRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/wallpapers/data/repository/WallpaperRepositoryKosmos.kt
@@ -26,13 +26,11 @@
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
import com.android.systemui.user.data.repository.userRepository
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-@OptIn(ExperimentalCoroutinesApi::class)
val Kosmos.wallpaperRepository by Fixture {
WallpaperRepositoryImpl(
context = applicationContext,
- scope = testScope,
+ scope = testScope.backgroundScope,
bgDispatcher = testDispatcher,
broadcastDispatcher = broadcastDispatcher,
userRepository = userRepository,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/wallpapers/ui/viewmodel/WallpaperViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/wallpapers/ui/viewmodel/WallpaperViewModelKosmos.kt
new file mode 100644
index 0000000..bb6596e
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/wallpapers/ui/viewmodel/WallpaperViewModelKosmos.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.wallpapers.ui.viewmodel
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.wallpapers.domain.interactor.wallpaperInteractor
+
+val Kosmos.wallpaperViewModel by Fixture { WallpaperViewModel(interactor = wallpaperInteractor) }
diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
index cffdfbd..8ef44ad 100644
--- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
+++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
@@ -5666,6 +5666,16 @@
}
public boolean canAccessAppWidget(Widget widget, int uid, String packageName) {
+ if (packageName != null
+ && widget.provider != null
+ && isDifferentPackageFromProvider(widget.provider, packageName)
+ && widget.host != null
+ && isDifferentPackageFromHost(widget.host, packageName)) {
+ // An AppWidget can only be accessed by either
+ // 1. The package that provides the AppWidget.
+ // 2. The package that hosts the AppWidget.
+ return false;
+ }
if (isHostInPackageForUid(widget.host, uid, packageName)) {
// Apps hosting the AppWidget have access to it.
return true;
@@ -5768,6 +5778,22 @@
&& provider.id.componentName.getPackageName().equals(packageName);
}
+ private boolean isDifferentPackageFromHost(
+ @NonNull final Host host, @NonNull final String packageName) {
+ if (host.id == null || host.id.packageName == null) {
+ return true;
+ }
+ return !packageName.equals(host.id.packageName);
+ }
+
+ private boolean isDifferentPackageFromProvider(
+ @NonNull final Provider provider, @NonNull final String packageName) {
+ if (provider.id == null || provider.id.componentName == null) {
+ return true;
+ }
+ return !packageName.equals(provider.id.componentName.getPackageName());
+ }
+
private boolean isProfileEnabled(int profileId) {
final long identity = Binder.clearCallingIdentity();
try {
diff --git a/services/core/Android.bp b/services/core/Android.bp
index 42385fc..bb49337 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -230,6 +230,7 @@
"notification_flags_lib",
"power_hint_flags_lib",
"biometrics_flags_lib",
+ "aconfig_settings_flags_lib",
"am_flags_lib",
"com_android_server_accessibility_flags_lib",
"//frameworks/libs/systemui:com_android_systemui_shared_flags_lib",
diff --git a/services/core/java/com/android/server/am/UidObserverController.java b/services/core/java/com/android/server/am/UidObserverController.java
index f58e3f8..23edafc 100644
--- a/services/core/java/com/android/server/am/UidObserverController.java
+++ b/services/core/java/com/android/server/am/UidObserverController.java
@@ -78,6 +78,7 @@
* This is for verifying the UID report flow.
*/
private static final boolean VALIDATE_UID_STATES = true;
+ @GuardedBy("mLock")
private final ActiveUids mValidateUids;
UidObserverController(@NonNull Handler handler) {
@@ -285,31 +286,30 @@
}
mUidObservers.finishBroadcast();
- if (VALIDATE_UID_STATES && mUidObservers.getRegisteredCallbackCount() > 0) {
- for (int j = 0; j < numUidChanges; ++j) {
- final ChangeRecord item = mActiveUidChanges[j];
- if ((item.change & UidRecord.CHANGE_GONE) != 0) {
- mValidateUids.remove(item.uid);
- } else {
- UidRecord validateUid = mValidateUids.get(item.uid);
- if (validateUid == null) {
- validateUid = new UidRecord(item.uid, null);
- mValidateUids.put(item.uid, validateUid);
+ synchronized (mLock) {
+ if (VALIDATE_UID_STATES && mUidObservers.getRegisteredCallbackCount() > 0) {
+ for (int j = 0; j < numUidChanges; ++j) {
+ final ChangeRecord item = mActiveUidChanges[j];
+ if ((item.change & UidRecord.CHANGE_GONE) != 0) {
+ mValidateUids.remove(item.uid);
+ } else {
+ UidRecord validateUid = mValidateUids.get(item.uid);
+ if (validateUid == null) {
+ validateUid = new UidRecord(item.uid, null);
+ mValidateUids.put(item.uid, validateUid);
+ }
+ if ((item.change & UidRecord.CHANGE_IDLE) != 0) {
+ validateUid.setIdle(true);
+ } else if ((item.change & UidRecord.CHANGE_ACTIVE) != 0) {
+ validateUid.setIdle(false);
+ }
+ validateUid.setSetProcState(item.procState);
+ validateUid.setCurProcState(item.procState);
+ validateUid.setSetCapability(item.capability);
+ validateUid.setCurCapability(item.capability);
}
- if ((item.change & UidRecord.CHANGE_IDLE) != 0) {
- validateUid.setIdle(true);
- } else if ((item.change & UidRecord.CHANGE_ACTIVE) != 0) {
- validateUid.setIdle(false);
- }
- validateUid.setSetProcState(item.procState);
- validateUid.setCurProcState(item.procState);
- validateUid.setSetCapability(item.capability);
- validateUid.setCurCapability(item.capability);
}
}
- }
-
- synchronized (mLock) {
for (int j = 0; j < numUidChanges; j++) {
final ChangeRecord changeRecord = mActiveUidChanges[j];
changeRecord.isPending = false;
@@ -436,7 +436,9 @@
}
UidRecord getValidateUidRecord(int uid) {
- return mValidateUids.get(uid);
+ synchronized (mLock) {
+ return mValidateUids.get(uid);
+ }
}
void dump(@NonNull PrintWriter pw, @Nullable String dumpPackage) {
@@ -491,12 +493,16 @@
boolean dumpValidateUids(@NonNull PrintWriter pw, @Nullable String dumpPackage, int dumpAppId,
@NonNull String header, boolean needSep) {
- return mValidateUids.dump(pw, dumpPackage, dumpAppId, header, needSep);
+ synchronized (mLock) {
+ return mValidateUids.dump(pw, dumpPackage, dumpAppId, header, needSep);
+ }
}
void dumpValidateUidsProto(@NonNull ProtoOutputStream proto, @Nullable String dumpPackage,
int dumpAppId, long fieldId) {
- mValidateUids.dumpProto(proto, dumpPackage, dumpAppId, fieldId);
+ synchronized (mLock) {
+ mValidateUids.dumpProto(proto, dumpPackage, dumpAppId, fieldId);
+ }
}
static final class ChangeRecord {
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index d2073aa..8e09e3b 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -33,6 +33,7 @@
import static android.app.AppOpsManager.MODE_ERRORED;
import static android.app.AppOpsManager.MODE_FOREGROUND;
import static android.app.AppOpsManager.MODE_IGNORED;
+import static android.app.AppOpsManager.OP_BLUETOOTH_CONNECT;
import static android.app.AppOpsManager.OP_CAMERA;
import static android.app.AppOpsManager.OP_CAMERA_SANDBOXED;
import static android.app.AppOpsManager.OP_FLAGS_ALL;
@@ -3267,6 +3268,11 @@
packageName);
}
if (!isIncomingPackageValid(packageName, UserHandle.getUserId(uid))) {
+ // TODO(b/333931259): Remove extra logging after this issue is diagnosed.
+ if (code == OP_BLUETOOTH_CONNECT) {
+ Slog.e(TAG, "noting OP_BLUETOOTH_CONNECT returned MODE_ERRORED as incoming "
+ + "package: " + packageName + " and uid: " + uid + " is invalid");
+ }
return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, attributionTag,
packageName);
}
@@ -3306,6 +3312,13 @@
}
} catch (SecurityException e) {
logVerifyAndGetBypassFailure(uid, e, "noteOperation");
+ // TODO(b/333931259): Remove extra logging after this issue is diagnosed.
+ if (code == OP_BLUETOOTH_CONNECT) {
+ Slog.e(TAG, "noting OP_BLUETOOTH_CONNECT returned MODE_ERRORED as"
+ + " verifyAndGetBypass returned a SecurityException for package: "
+ + packageName + " and uid: " + uid + " and attributionTag: "
+ + attributionTag, e);
+ }
return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, attributionTag,
packageName);
}
@@ -3323,6 +3336,17 @@
if (DEBUG) Slog.d(TAG, "noteOperation: no op for code " + code + " uid " + uid
+ " package " + packageName + "flags: " +
AppOpsManager.flagsToString(flags));
+ // TODO(b/333931259): Remove extra logging after this issue is diagnosed.
+ if (code == OP_BLUETOOTH_CONNECT) {
+ Slog.e(TAG, "noting OP_BLUETOOTH_CONNECT returned MODE_ERRORED as"
+ + " #getOpsLocked returned null for"
+ + " uid: " + uid
+ + " packageName: " + packageName
+ + " attributionTag: " + attributionTag
+ + " pvr.isAttributionTagValid: " + pvr.isAttributionTagValid
+ + " pvr.bypass: " + pvr.bypass);
+ Slog.e(TAG, "mUidStates.get(" + uid + "): " + mUidStates.get(uid));
+ }
return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, attributionTag,
packageName);
}
@@ -3367,6 +3391,11 @@
attributedOp.rejected(uidState.getState(), flags);
scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag,
virtualDeviceId, flags, uidMode);
+ // TODO(b/333931259): Remove extra logging after this issue is diagnosed.
+ if (code == OP_BLUETOOTH_CONNECT && uidMode == MODE_ERRORED) {
+ Slog.e(TAG, "noting OP_BLUETOOTH_CONNECT returned MODE_ERRORED as"
+ + " uid mode is MODE_ERRORED");
+ }
return new SyncNotedAppOp(uidMode, code, attributionTag, packageName);
}
} else {
@@ -3386,6 +3415,11 @@
attributedOp.rejected(uidState.getState(), flags);
scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag,
virtualDeviceId, flags, mode);
+ // TODO(b/333931259): Remove extra logging after this issue is diagnosed.
+ if (code == OP_BLUETOOTH_CONNECT && mode == MODE_ERRORED) {
+ Slog.e(TAG, "noting OP_BLUETOOTH_CONNECT returned MODE_ERRORED as"
+ + " package mode is MODE_ERRORED");
+ }
return new SyncNotedAppOp(mode, code, attributionTag, packageName);
}
}
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index c125d2d..12b666f 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -11953,7 +11953,7 @@
mLoudnessCodecHelper.startLoudnessCodecUpdates(sessionId);
}
- /** @see LoudnessCodecController#release() */
+ /** @see LoudnessCodecController#close() */
@Override
public void stopLoudnessCodecUpdates(int sessionId) {
mLoudnessCodecHelper.stopLoudnessCodecUpdates(sessionId);
diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java
index b365ef7..15b1f22 100644
--- a/services/core/java/com/android/server/biometrics/BiometricService.java
+++ b/services/core/java/com/android/server/biometrics/BiometricService.java
@@ -17,6 +17,7 @@
package com.android.server.biometrics;
import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL;
+import static android.hardware.biometrics.BiometricAuthenticator.TYPE_ANY_BIOMETRIC;
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE;
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
import static android.hardware.biometrics.BiometricManager.Authenticators;
@@ -81,6 +82,7 @@
import android.util.ArraySet;
import android.util.Pair;
import android.util.Slog;
+import android.util.SparseBooleanArray;
import android.util.proto.ProtoOutputStream;
import com.android.internal.R;
@@ -100,8 +102,8 @@
import java.util.Map;
import java.util.Random;
import java.util.Set;
-import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.atomic.AtomicLong;
import java.util.function.Supplier;
/**
@@ -263,6 +265,14 @@
Settings.Secure.getUriFor(Settings.Secure.BIOMETRIC_KEYGUARD_ENABLED);
private final Uri BIOMETRIC_APP_ENABLED =
Settings.Secure.getUriFor(Settings.Secure.BIOMETRIC_APP_ENABLED);
+ private final Uri FACE_KEYGUARD_ENABLED =
+ Settings.Secure.getUriFor(Settings.Secure.FACE_KEYGUARD_ENABLED);
+ private final Uri FACE_APP_ENABLED =
+ Settings.Secure.getUriFor(Settings.Secure.FACE_APP_ENABLED);
+ private final Uri FINGERPRINT_KEYGUARD_ENABLED =
+ Settings.Secure.getUriFor(Settings.Secure.FINGERPRINT_KEYGUARD_ENABLED);
+ private final Uri FINGERPRINT_APP_ENABLED =
+ Settings.Secure.getUriFor(Settings.Secure.FINGERPRINT_APP_ENABLED);
private final Uri MANDATORY_BIOMETRICS_ENABLED =
Settings.Secure.getUriFor(Settings.Secure.MANDATORY_BIOMETRICS);
private final Uri MANDATORY_BIOMETRICS_REQUIREMENTS_SATISFIED = Settings.Secure.getUriFor(
@@ -274,6 +284,10 @@
private final Map<Integer, Boolean> mBiometricEnabledOnKeyguard = new HashMap<>();
private final Map<Integer, Boolean> mBiometricEnabledForApps = new HashMap<>();
+ private final SparseBooleanArray mFaceEnabledOnKeyguard = new SparseBooleanArray();
+ private final SparseBooleanArray mFaceEnabledForApps = new SparseBooleanArray();
+ private final SparseBooleanArray mFingerprintEnabledOnKeyguard = new SparseBooleanArray();
+ private final SparseBooleanArray mFingerprintEnabledForApps = new SparseBooleanArray();
private final Map<Integer, Boolean> mFaceAlwaysRequireConfirmation = new HashMap<>();
private final Map<Integer, Boolean> mMandatoryBiometricsEnabled = new HashMap<>();
private final Map<Integer, Boolean> mMandatoryBiometricsRequirementsSatisfied =
@@ -323,6 +337,23 @@
false /* notifyForDescendants */,
this /* observer */,
UserHandle.USER_ALL);
+ } else if (com.android.settings.flags.Flags.biometricsOnboardingEducation()) {
+ mContentResolver.registerContentObserver(FINGERPRINT_KEYGUARD_ENABLED,
+ false /* notifyForDescendants */,
+ this /* observer */,
+ UserHandle.USER_ALL);
+ mContentResolver.registerContentObserver(FACE_KEYGUARD_ENABLED,
+ false /* notifyForDescendants */,
+ this /* observer */,
+ UserHandle.USER_ALL);
+ mContentResolver.registerContentObserver(FINGERPRINT_APP_ENABLED,
+ false /* notifyForDescendants */,
+ this /* observer */,
+ UserHandle.USER_ALL);
+ mContentResolver.registerContentObserver(FACE_APP_ENABLED,
+ false /* notifyForDescendants */,
+ this /* observer */,
+ UserHandle.USER_ALL);
} else {
mContentResolver.registerContentObserver(BIOMETRIC_KEYGUARD_ENABLED,
false /* notifyForDescendants */,
@@ -357,7 +388,7 @@
userId) != 0);
if (userId == ActivityManager.getCurrentUser() && !selfChange) {
- notifyEnabledOnKeyguardCallbacks(userId);
+ notifyEnabledOnKeyguardCallbacks(userId, TYPE_FACE);
}
} else if (FACE_UNLOCK_APP_ENABLED.equals(uri)) {
mBiometricEnabledForApps.put(userId, Settings.Secure.getIntForUser(
@@ -379,7 +410,27 @@
userId) != 0);
if (userId == ActivityManager.getCurrentUser() && !selfChange) {
- notifyEnabledOnKeyguardCallbacks(userId);
+ notifyEnabledOnKeyguardCallbacks(userId, TYPE_ANY_BIOMETRIC);
+ }
+ } else if (FACE_KEYGUARD_ENABLED.equals(uri)) {
+ mFaceEnabledOnKeyguard.put(userId, Settings.Secure.getIntForUser(
+ mContentResolver,
+ Settings.Secure.FACE_KEYGUARD_ENABLED,
+ DEFAULT_KEYGUARD_ENABLED ? 1 : 0 /* default */,
+ userId) != 0);
+
+ if (userId == ActivityManager.getCurrentUser() && !selfChange) {
+ notifyEnabledOnKeyguardCallbacks(userId, TYPE_FACE);
+ }
+ } else if (FINGERPRINT_KEYGUARD_ENABLED.equals(uri)) {
+ mFingerprintEnabledOnKeyguard.put(userId, Settings.Secure.getIntForUser(
+ mContentResolver,
+ Settings.Secure.FINGERPRINT_KEYGUARD_ENABLED,
+ DEFAULT_KEYGUARD_ENABLED ? 1 : 0 /* default */,
+ userId) != 0);
+
+ if (userId == ActivityManager.getCurrentUser() && !selfChange) {
+ notifyEnabledOnKeyguardCallbacks(userId, TYPE_FINGERPRINT);
}
} else if (BIOMETRIC_APP_ENABLED.equals(uri)) {
mBiometricEnabledForApps.put(userId, Settings.Secure.getIntForUser(
@@ -387,6 +438,18 @@
Settings.Secure.BIOMETRIC_APP_ENABLED,
DEFAULT_APP_ENABLED ? 1 : 0 /* default */,
userId) != 0);
+ } else if (FACE_APP_ENABLED.equals(uri)) {
+ mFaceEnabledForApps.put(userId, Settings.Secure.getIntForUser(
+ mContentResolver,
+ Settings.Secure.FACE_APP_ENABLED,
+ DEFAULT_APP_ENABLED ? 1 : 0 /* default */,
+ userId) != 0);
+ } else if (FINGERPRINT_APP_ENABLED.equals(uri)) {
+ mFingerprintEnabledForApps.put(userId, Settings.Secure.getIntForUser(
+ mContentResolver,
+ Settings.Secure.FINGERPRINT_APP_ENABLED,
+ DEFAULT_APP_ENABLED ? 1 : 0 /* default */,
+ userId) != 0);
} else if (MANDATORY_BIOMETRICS_ENABLED.equals(uri)) {
updateMandatoryBiometricsForAllProfiles(userId);
} else if (MANDATORY_BIOMETRICS_REQUIREMENTS_SATISFIED.equals(uri)) {
@@ -394,26 +457,60 @@
}
}
- public boolean getEnabledOnKeyguard(int userId) {
- if (!mBiometricEnabledOnKeyguard.containsKey(userId)) {
- if (mUseLegacyFaceOnlySettings) {
- onChange(true /* selfChange */, FACE_UNLOCK_KEYGUARD_ENABLED, userId);
- } else {
- onChange(true /* selfChange */, BIOMETRIC_KEYGUARD_ENABLED, userId);
+ public boolean getEnabledOnKeyguard(int userId, int modality) {
+ if (com.android.settings.flags.Flags.biometricsOnboardingEducation()) {
+ if (modality == TYPE_FACE) {
+ if (mFaceEnabledOnKeyguard.indexOfKey(userId) < 0) {
+ onChange(true /* selfChange */, FACE_KEYGUARD_ENABLED, userId);
+ }
+ return mFaceEnabledOnKeyguard.get(userId, DEFAULT_KEYGUARD_ENABLED);
+ } else if (modality == TYPE_FINGERPRINT) {
+ if (mFingerprintEnabledOnKeyguard.indexOfKey(userId) < 0) {
+ onChange(true /* selfChange */, FINGERPRINT_KEYGUARD_ENABLED, userId);
+ }
+ return mFingerprintEnabledOnKeyguard.get(userId, DEFAULT_KEYGUARD_ENABLED);
+ } else { // modality == TYPE_ANY_BIOMETRIC
+ return mFingerprintEnabledOnKeyguard.get(userId, DEFAULT_KEYGUARD_ENABLED)
+ || mFaceEnabledOnKeyguard.get(userId, DEFAULT_KEYGUARD_ENABLED);
}
+ } else {
+ if (!mBiometricEnabledOnKeyguard.containsKey(userId)) {
+ if (mUseLegacyFaceOnlySettings) {
+ onChange(true /* selfChange */, FACE_UNLOCK_KEYGUARD_ENABLED, userId);
+ } else {
+ onChange(true /* selfChange */, BIOMETRIC_KEYGUARD_ENABLED, userId);
+ }
+ }
+ return mBiometricEnabledOnKeyguard.get(userId);
}
- return mBiometricEnabledOnKeyguard.get(userId);
}
- public boolean getEnabledForApps(int userId) {
- if (!mBiometricEnabledForApps.containsKey(userId)) {
- if (mUseLegacyFaceOnlySettings) {
- onChange(true /* selfChange */, FACE_UNLOCK_APP_ENABLED, userId);
- } else {
- onChange(true /* selfChange */, BIOMETRIC_APP_ENABLED, userId);
+ public boolean getEnabledForApps(int userId, int modality) {
+ if (com.android.settings.flags.Flags.biometricsOnboardingEducation()) {
+ if (modality == TYPE_FACE) {
+ if (mFaceEnabledForApps.indexOfKey(userId) < 0) {
+ onChange(true /* selfChange */, FACE_APP_ENABLED, userId);
+ }
+ return mFaceEnabledForApps.get(userId, DEFAULT_APP_ENABLED);
+ } else if (modality == TYPE_FINGERPRINT) {
+ if (mFingerprintEnabledForApps.indexOfKey(userId) < 0) {
+ onChange(true /* selfChange */, FINGERPRINT_APP_ENABLED, userId);
+ }
+ return mFingerprintEnabledForApps.get(userId, DEFAULT_APP_ENABLED);
+ } else { // modality == TYPE_ANY_BIOMETRIC
+ return mFingerprintEnabledForApps.get(userId, DEFAULT_APP_ENABLED)
+ || mFaceEnabledForApps.get(userId, DEFAULT_APP_ENABLED);
}
+ } else {
+ if (!mBiometricEnabledForApps.containsKey(userId)) {
+ if (mUseLegacyFaceOnlySettings) {
+ onChange(true /* selfChange */, FACE_UNLOCK_APP_ENABLED, userId);
+ } else {
+ onChange(true /* selfChange */, BIOMETRIC_APP_ENABLED, userId);
+ }
+ }
+ return mBiometricEnabledForApps.getOrDefault(userId, DEFAULT_APP_ENABLED);
}
- return mBiometricEnabledForApps.getOrDefault(userId, DEFAULT_APP_ENABLED);
}
public boolean getConfirmationAlwaysRequired(@BiometricAuthenticator.Modality int modality,
@@ -444,17 +541,16 @@
DEFAULT_MANDATORY_BIOMETRICS_STATUS)
&& mMandatoryBiometricsRequirementsSatisfied.getOrDefault(userId,
DEFAULT_MANDATORY_BIOMETRICS_REQUIREMENTS_SATISFIED_STATUS)
- && getEnabledForApps(userId)
+ && getEnabledForApps(userId, TYPE_ANY_BIOMETRIC)
&& (mFingerprintEnrolledForUser.getOrDefault(userId, false /* default */)
|| mFaceEnrolledForUser.getOrDefault(userId, false /* default */));
}
- void notifyEnabledOnKeyguardCallbacks(int userId) {
+ void notifyEnabledOnKeyguardCallbacks(int userId, int modality) {
List<EnabledOnKeyguardCallback> callbacks = mCallbacks;
+ final boolean enabled = getEnabledOnKeyguard(userId, modality);
for (int i = 0; i < callbacks.size(); i++) {
- callbacks.get(i).notify(
- mBiometricEnabledOnKeyguard.getOrDefault(userId, DEFAULT_KEYGUARD_ENABLED),
- userId);
+ callbacks.get(i).notify(enabled, userId, modality);
}
}
@@ -596,9 +692,9 @@
}
}
- void notify(boolean enabled, int userId) {
+ void notify(boolean enabled, int userId, int modality) {
try {
- mCallback.onChanged(enabled, userId);
+ mCallback.onChanged(enabled, userId, modality);
} catch (DeadObjectException e) {
Slog.w(TAG, "Death while invoking notify", e);
mEnabledOnKeyguardCallbacks.remove(this);
@@ -930,8 +1026,16 @@
try {
for (UserInfo userInfo: aliveUsers) {
final int userId = userInfo.id;
- callback.onChanged(mSettingObserver.getEnabledOnKeyguard(userId),
- userId);
+ if (com.android.settings.flags.Flags.biometricsOnboardingEducation()) {
+ callback.onChanged(mSettingObserver.getEnabledOnKeyguard(userId, TYPE_FACE),
+ userId, TYPE_FACE);
+ callback.onChanged(
+ mSettingObserver.getEnabledOnKeyguard(userId, TYPE_FINGERPRINT),
+ userId, TYPE_FINGERPRINT);
+ } else {
+ callback.onChanged(mSettingObserver.getEnabledOnKeyguard(userId,
+ TYPE_ANY_BIOMETRIC), userId, TYPE_ANY_BIOMETRIC);
+ }
}
} catch (RemoteException e) {
Slog.w(TAG, "Remote exception", e);
@@ -1309,7 +1413,15 @@
@Override
public void onUserSwitchComplete(int newUserId) {
mSettingObserver.updateContentObserver();
- mSettingObserver.notifyEnabledOnKeyguardCallbacks(newUserId);
+ if (com.android.settings.flags.Flags.biometricsOnboardingEducation()) {
+ mSettingObserver.notifyEnabledOnKeyguardCallbacks(newUserId,
+ TYPE_FACE);
+ mSettingObserver.notifyEnabledOnKeyguardCallbacks(
+ newUserId, TYPE_FINGERPRINT);
+ } else {
+ mSettingObserver.notifyEnabledOnKeyguardCallbacks(
+ newUserId, TYPE_ANY_BIOMETRIC);
+ }
}
}, BiometricService.class.getName()
);
diff --git a/services/core/java/com/android/server/biometrics/PreAuthInfo.java b/services/core/java/com/android/server/biometrics/PreAuthInfo.java
index 6ed1ac85..c739118 100644
--- a/services/core/java/com/android/server/biometrics/PreAuthInfo.java
+++ b/services/core/java/com/android/server/biometrics/PreAuthInfo.java
@@ -284,7 +284,7 @@
private static boolean isEnabledForApp(BiometricService.SettingObserver settingObserver,
@BiometricAuthenticator.Modality int modality, int userId) {
- return settingObserver.getEnabledForApps(userId);
+ return settingObserver.getEnabledForApps(userId, modality);
}
private static boolean isBiometricDisabledByDevicePolicy(
diff --git a/services/core/java/com/android/server/input/InputSettingsObserver.java b/services/core/java/com/android/server/input/InputSettingsObserver.java
index e25ea4b..d1f07cb 100644
--- a/services/core/java/com/android/server/input/InputSettingsObserver.java
+++ b/services/core/java/com/android/server/input/InputSettingsObserver.java
@@ -90,6 +90,8 @@
(reason) -> updateTouchpadRightClickZoneEnabled()),
Map.entry(Settings.System.getUriFor(Settings.System.TOUCHPAD_SYSTEM_GESTURES),
(reason) -> updateTouchpadSystemGesturesEnabled()),
+ Map.entry(Settings.System.getUriFor(Settings.System.TOUCHPAD_ACCELERATION_ENABLED),
+ (reason) -> updateTouchpadAccelerationEnabled()),
Map.entry(Settings.System.getUriFor(Settings.System.SHOW_TOUCHES),
(reason) -> updateShowTouches()),
Map.entry(Settings.System.getUriFor(Settings.System.POINTER_LOCATION),
@@ -241,6 +243,11 @@
mNative.setTouchpadSystemGesturesEnabled(InputSettings.useTouchpadSystemGestures(mContext));
}
+ private void updateTouchpadAccelerationEnabled() {
+ mNative.setTouchpadAccelerationEnabled(
+ InputSettings.isTouchpadAccelerationEnabled(mContext));
+ }
+
private void updateShowTouches() {
mNative.setShowTouches(getBoolean(Settings.System.SHOW_TOUCHES, false));
}
diff --git a/services/core/java/com/android/server/input/NativeInputManagerService.java b/services/core/java/com/android/server/input/NativeInputManagerService.java
index 4d38c84..f34338a 100644
--- a/services/core/java/com/android/server/input/NativeInputManagerService.java
+++ b/services/core/java/com/android/server/input/NativeInputManagerService.java
@@ -158,6 +158,8 @@
void setTouchpadSystemGesturesEnabled(boolean enabled);
+ void setTouchpadAccelerationEnabled(boolean enabled);
+
void setShowTouches(boolean enabled);
void setNonInteractiveDisplays(int[] displayIds);
@@ -463,6 +465,9 @@
public native void setTouchpadSystemGesturesEnabled(boolean enabled);
@Override
+ public native void setTouchpadAccelerationEnabled(boolean enabled);
+
+ @Override
public native void setShowTouches(boolean enabled);
@Override
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index f089c3a..b39b6fd 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -16,6 +16,7 @@
package com.android.server.notification;
+import static android.app.AutomaticZenRule.TYPE_DRIVING;
import static android.app.AutomaticZenRule.TYPE_UNKNOWN;
import static android.app.NotificationManager.AUTOMATIC_RULE_STATUS_ACTIVATED;
import static android.app.NotificationManager.AUTOMATIC_RULE_STATUS_DEACTIVATED;
@@ -46,6 +47,7 @@
import static com.android.internal.util.FrameworkStatsLog.DND_MODE_RULE;
import static com.android.internal.util.Preconditions.checkArgument;
+import static com.android.server.notification.Flags.preventZenDeviceEffectsWhileDriving;
import static java.util.Objects.requireNonNull;
@@ -2379,6 +2381,26 @@
}
if (Flags.modesApi()) {
+ // Prevent other rules from applying grayscale if Driving is active (but allow it
+ // if _Driving itself_ wants grayscale).
+ if (Flags.modesUi() && preventZenDeviceEffectsWhileDriving()) {
+ boolean hasActiveDriving = false;
+ boolean hasActiveDrivingWithGrayscale = false;
+ for (ZenRule rule : mConfig.automaticRules.values()) {
+ if (rule.isActive() && rule.type == TYPE_DRIVING) {
+ hasActiveDriving = true;
+ if (rule.zenDeviceEffects != null
+ && rule.zenDeviceEffects.shouldDisplayGrayscale()) {
+ hasActiveDrivingWithGrayscale = true;
+ break; // Further rules won't affect decision.
+ }
+ }
+ }
+ if (hasActiveDriving && !hasActiveDrivingWithGrayscale) {
+ deviceEffectsBuilder.setShouldDisplayGrayscale(false);
+ }
+ }
+
ZenDeviceEffects deviceEffects = deviceEffectsBuilder.build();
if (!deviceEffects.equals(mConsolidatedDeviceEffects)) {
mConsolidatedDeviceEffects = deviceEffects;
diff --git a/services/core/java/com/android/server/notification/flags.aconfig b/services/core/java/com/android/server/notification/flags.aconfig
index b4a8aee..822ff48 100644
--- a/services/core/java/com/android/server/notification/flags.aconfig
+++ b/services/core/java/com/android/server/notification/flags.aconfig
@@ -190,3 +190,13 @@
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "prevent_zen_device_effects_while_driving"
+ namespace: "systemui"
+ description: "Don't apply certain device effects (such as grayscale) from active zen rules, if a rule of TYPE_DRIVING is active"
+ bug: "390389174"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
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 9d840d0..b905041 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -26,6 +26,7 @@
import static android.app.AppOpsManager.MODE_ALLOWED;
import static android.app.AppOpsManager.MODE_ERRORED;
import static android.app.AppOpsManager.MODE_IGNORED;
+import static android.app.AppOpsManager.OP_BLUETOOTH_CONNECT;
import static android.content.pm.ApplicationInfo.AUTO_REVOKE_DISALLOWED;
import static android.content.pm.ApplicationInfo.AUTO_REVOKE_DISCOURAGED;
import static android.permission.flags.Flags.serverSideAttributionRegistration;
@@ -1668,7 +1669,22 @@
throw new SecurityException(msg + ":" + e.getMessage());
}
}
- return Math.max(checkedOpResult, notedOpResult);
+ int result = Math.max(checkedOpResult, notedOpResult);
+ // TODO(b/333931259): Remove extra logging after this issue is diagnosed.
+ if (op == OP_BLUETOOTH_CONNECT && result == MODE_ERRORED) {
+ if (result == checkedOpResult) {
+ Slog.e(LOG_TAG, "BLUETOOTH_CONNECT permission hard denied as"
+ + " checkOp for resolvedAttributionSource "
+ + resolvedAttributionSource + " and op " + op
+ + " returned MODE_ERRORED");
+ } else {
+ Slog.e(LOG_TAG, "BLUETOOTH_CONNECT permission hard denied as"
+ + " noteOp for resolvedAttributionSource "
+ + resolvedAttributionSource + " and op " + notedOp
+ + " returned MODE_ERRORED");
+ }
+ }
+ return result;
}
}
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 4300b60..f4870d5 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -406,7 +406,7 @@
/**
* An entry in the history task, representing an activity.
*/
-final class ActivityRecord extends WindowToken implements WindowManagerService.AppFreezeListener {
+final class ActivityRecord extends WindowToken {
private static final String TAG = TAG_WITH_CLASS_NAME ? "ActivityRecord" : TAG_ATM;
private static final String TAG_ADD_REMOVE = TAG + POSTFIX_ADD_REMOVE;
private static final String TAG_APP = TAG + POSTFIX_APP;
@@ -736,9 +736,6 @@
*/
boolean mAllowCrossUidActivitySwitchFromBelow;
- /** Have we been asked to have this token keep the screen frozen? */
- private boolean mFreezingScreen;
-
// These are used for determining when all windows associated with
// an activity have been drawn, so they can be made visible together
// at the same time.
@@ -4511,8 +4508,6 @@
removeIfPossible();
}
- stopFreezingScreen(true, true);
-
final DisplayContent dc = getDisplayContent();
if (dc.mFocusedApp == this) {
ProtoLog.v(WM_DEBUG_FOCUS_LIGHT,
@@ -5793,9 +5788,7 @@
+ " visibleRequested=%b, isInTransition=%b, runningAnimation=%b, caller=%s",
this, isVisible(), mVisibleRequested, isInTransition(), runningAnimation,
Debug.getCallers(5));
- if (!visible) {
- stopFreezingScreen(true, true);
- } else {
+ if (visible) {
// If we are being set visible, and the starting window is not yet displayed,
// then make sure it doesn't get displayed.
if (mStartingWindow != null && !mStartingWindow.isDrawn()
@@ -5803,9 +5796,6 @@
mStartingWindow.clearPolicyVisibilityFlag(LEGACY_POLICY_VISIBILITY);
mStartingWindow.mLegacyPolicyVisibilityAfterAnim = false;
}
- // We are becoming visible, so better freeze the screen with the windows that are
- // getting visible so we also wait for them.
- forAllWindows(mWmService::makeWindowFreezingScreenIfNeededLocked, true);
}
// dispatchTaskInfoChangedIfNeeded() right after ActivityRecord#setVisibility() can report
// the stale visible state, because the state will be updated after the app transition.
@@ -6842,123 +6832,6 @@
rootTask.removeLaunchTickMessages();
}
- boolean mayFreezeScreenLocked() {
- return mayFreezeScreenLocked(app);
- }
-
- private boolean mayFreezeScreenLocked(WindowProcessController app) {
- // Only freeze the screen if this activity is currently attached to
- // an application, and that application is not blocked or unresponding.
- // In any other case, we can't count on getting the screen unfrozen,
- // so it is best to leave as-is.
- return hasProcess() && !app.isCrashing() && !app.isNotResponding();
- }
-
- void startFreezingScreenLocked(WindowProcessController app, int configChanges) {
- if (mayFreezeScreenLocked(app)) {
- if (getParent() == null) {
- Slog.w(TAG_WM,
- "Attempted to freeze screen with non-existing app token: " + token);
- return;
- }
-
- // Window configuration changes only effect windows, so don't require a screen freeze.
- int freezableConfigChanges = configChanges & ~(CONFIG_WINDOW_CONFIGURATION);
- if (freezableConfigChanges == 0 && okToDisplay()) {
- ProtoLog.v(WM_DEBUG_ORIENTATION, "Skipping set freeze of %s", token);
- return;
- }
-
- startFreezingScreen();
- }
- }
-
- void startFreezingScreen() {
- startFreezingScreen(ROTATION_UNDEFINED /* overrideOriginalDisplayRotation */);
- }
-
- void startFreezingScreen(int overrideOriginalDisplayRotation) {
- if (mTransitionController.isShellTransitionsEnabled()) {
- return;
- }
- ProtoLog.i(WM_DEBUG_ORIENTATION,
- "Set freezing of %s: visible=%b freezing=%b visibleRequested=%b. %s",
- token, isVisible(), mFreezingScreen, mVisibleRequested,
- new RuntimeException().fillInStackTrace());
- if (!mVisibleRequested) {
- return;
- }
-
- // If the override is given, the rotation of display doesn't change but we still want to
- // cover the activity whose configuration is changing by freezing the display and running
- // the rotation animation.
- final boolean forceRotation = overrideOriginalDisplayRotation != ROTATION_UNDEFINED;
- if (!mFreezingScreen) {
- mFreezingScreen = true;
- mWmService.registerAppFreezeListener(this);
- mWmService.mAppsFreezingScreen++;
- if (mWmService.mAppsFreezingScreen == 1) {
- if (forceRotation) {
- // Make sure normal rotation animation will be applied.
- mDisplayContent.getDisplayRotation().cancelSeamlessRotation();
- }
- mWmService.startFreezingDisplay(0 /* exitAnim */, 0 /* enterAnim */,
- mDisplayContent, overrideOriginalDisplayRotation);
- mWmService.mH.removeMessages(H.APP_FREEZE_TIMEOUT);
- mWmService.mH.sendEmptyMessageDelayed(H.APP_FREEZE_TIMEOUT, 2000);
- }
- }
- if (forceRotation) {
- // The rotation of the real display won't change, so in order to unfreeze the screen
- // via {@link #checkAppWindowsReadyToShow}, the windows have to be able to call
- // {@link WindowState#reportResized} (it is skipped if the window is freezing) to update
- // the drawn state.
- return;
- }
- final int count = mChildren.size();
- for (int i = 0; i < count; i++) {
- final WindowState w = mChildren.get(i);
- w.onStartFreezingScreen();
- }
- }
-
- boolean isFreezingScreen() {
- return mFreezingScreen;
- }
-
- @Override
- public void onAppFreezeTimeout() {
- Slog.w(TAG_WM, "Force clearing freeze: " + this);
- stopFreezingScreen(true, true);
- }
-
- void stopFreezingScreen(boolean unfreezeSurfaceNow, boolean force) {
- if (!mFreezingScreen) {
- return;
- }
- ProtoLog.v(WM_DEBUG_ORIENTATION,
- "Clear freezing of %s force=%b", this, force);
- final int count = mChildren.size();
- boolean unfrozeWindows = false;
- for (int i = 0; i < count; i++) {
- final WindowState w = mChildren.get(i);
- unfrozeWindows |= w.onStopFreezingScreen();
- }
- if (force || unfrozeWindows) {
- ProtoLog.v(WM_DEBUG_ORIENTATION, "No longer freezing: %s", this);
- mFreezingScreen = false;
- mWmService.unregisterAppFreezeListener(this);
- mWmService.mAppsFreezingScreen--;
- mWmService.mLastFinishedFreezeSource = this;
- }
- if (unfreezeSurfaceNow) {
- if (unfrozeWindows) {
- mWmService.mWindowPlacerLocked.performSurfacePlacement();
- }
- mWmService.stopFreezingDisplayLocked();
- }
- }
-
void onFirstWindowDrawn(WindowState win) {
firstWindowDrawn = true;
// stop tracking
@@ -7101,24 +6974,11 @@
return;
}
- // The token has now changed state to having all windows shown... what to do, what to do?
- if (mFreezingScreen) {
- showAllWindowsLocked();
- stopFreezingScreen(false, true);
- ProtoLog.i(WM_DEBUG_ORIENTATION,
- "Setting mOrientationChangeComplete=true because wtoken %s "
- + "numInteresting=%d numDrawn=%d",
- this, mNumInterestingWindows, mNumDrawnWindows);
- // This will set mOrientationChangeComplete and cause a pass through layout.
- setAppLayoutChanges(FINISH_LAYOUT_REDO_WALLPAPER,
- "checkAppWindowsReadyToShow: freezingScreen");
- } else {
- setAppLayoutChanges(FINISH_LAYOUT_REDO_ANIM, "checkAppWindowsReadyToShow");
+ setAppLayoutChanges(FINISH_LAYOUT_REDO_ANIM, "checkAppWindowsReadyToShow");
- // We can now show all of the drawn windows!
- if (!getDisplayContent().mOpeningApps.contains(this) && canShowWindows()) {
- showAllWindowsLocked();
- }
+ // We can now show all of the drawn windows!
+ if (!getDisplayContent().mOpeningApps.contains(this) && canShowWindows()) {
+ showAllWindowsLocked();
}
}
@@ -7204,10 +7064,10 @@
if (DEBUG_STARTING_WINDOW_VERBOSE && w == mStartingWindow) {
Slog.d(TAG, "updateWindows: starting " + w + " isOnScreen=" + w.isOnScreen()
- + " allDrawn=" + allDrawn + " freezingScreen=" + mFreezingScreen);
+ + " allDrawn=" + allDrawn);
}
- if (allDrawn && !mFreezingScreen) {
+ if (allDrawn) {
return false;
}
@@ -7248,10 +7108,8 @@
mNumDrawnWindows++;
if (DEBUG_VISIBILITY || WM_DEBUG_ORIENTATION.isLogToLogcat()) {
- Slog.v(TAG, "tokenMayBeDrawn: "
- + this + " w=" + w + " numInteresting=" + mNumInterestingWindows
- + " freezingScreen=" + mFreezingScreen
- + " mAppFreezing=" + w.mAppFreezing);
+ Slog.v(TAG, "tokenMayBeDrawn: " + this + " w=" + w
+ + " numInteresting=" + mNumInterestingWindows);
}
isInterestingAndDrawn = true;
@@ -8199,8 +8057,6 @@
}
mDisplayContent.mPinnedTaskController.onCancelFixedRotationTransform();
- // Perform rotation animation according to the rotation of this activity.
- startFreezingScreen(originalDisplayRotation);
// This activity may relaunch or perform configuration change so once it has reported drawn,
// the screen can be unfrozen.
ensureActivityConfiguration();
@@ -9384,11 +9240,6 @@
mLastReportedConfiguration);
if (shouldRelaunchLocked(changes, mTmpConfig)) {
- // Aha, the activity isn't handling the change, so DIE DIE DIE.
- if (mVisible && mAtmService.mTmpUpdateConfigurationResult.mIsUpdating
- && !mTransitionController.isShellTransitionsEnabled()) {
- startFreezingScreenLocked(app, mAtmService.mTmpUpdateConfigurationResult.changes);
- }
final boolean displayMayChange = mTmpConfig.windowConfiguration.getDisplayRotation()
!= getWindowConfiguration().getDisplayRotation()
|| !mTmpConfig.windowConfiguration.getMaxBounds().equals(
@@ -9396,10 +9247,8 @@
final boolean isAppResizeOnly = !displayMayChange
&& (changes & ~(CONFIG_SCREEN_SIZE | CONFIG_SMALLEST_SCREEN_SIZE
| CONFIG_ORIENTATION | CONFIG_SCREEN_LAYOUT)) == 0;
- // Do not preserve window if it is freezing screen because the original window won't be
- // able to update drawn state that causes freeze timeout.
// TODO(b/258618073): Always preserve if possible.
- final boolean preserveWindow = isAppResizeOnly && !mFreezingScreen;
+ final boolean preserveWindow = isAppResizeOnly;
final boolean hasResizeChange = hasResizeChange(changes & ~info.getRealConfigChanged());
if (hasResizeChange) {
final boolean isDragResizing = task.isDragResizing();
@@ -9695,7 +9544,6 @@
scheduleStopForRestartProcess();
});
} else {
- startFreezingScreen();
scheduleStopForRestartProcess();
}
}
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 7321f28..d4f9c090 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -509,7 +509,6 @@
int changes;
// If the activity was relaunched to match the new configuration.
boolean activityRelaunched;
- boolean mIsUpdating;
}
/** Current sequencing integer of the configuration, for skipping old configurations. */
@@ -4694,14 +4693,12 @@
if (values != null) {
changes = updateGlobalConfigurationLocked(values, initLocale, persistent, userId);
mTmpUpdateConfigurationResult.changes = changes;
- mTmpUpdateConfigurationResult.mIsUpdating = true;
}
if (!deferResume) {
kept = ensureConfigAndVisibilityAfterUpdate(starting, changes);
}
} finally {
- mTmpUpdateConfigurationResult.mIsUpdating = false;
continueWindowLayout();
}
mTmpUpdateConfigurationResult.activityRelaunched = !kept;
diff --git a/services/core/java/com/android/server/wm/AppTransitionController.java b/services/core/java/com/android/server/wm/AppTransitionController.java
index a972ecb..0a2f685 100644
--- a/services/core/java/com/android/server/wm/AppTransitionController.java
+++ b/services/core/java/com/android/server/wm/AppTransitionController.java
@@ -1195,27 +1195,12 @@
private boolean transitionGoodToGo(ArraySet<? extends WindowContainer> apps,
ArrayMap<WindowContainer, Integer> outReasons) {
ProtoLog.v(WM_DEBUG_APP_TRANSITIONS,
- "Checking %d opening apps (frozen=%b timeout=%b)...", apps.size(),
- mService.mDisplayFrozen, mDisplayContent.mAppTransition.isTimeout());
+ "Checking %d opening apps (timeout=%b)...", apps.size(),
+ mDisplayContent.mAppTransition.isTimeout());
if (mDisplayContent.mAppTransition.isTimeout()) {
return true;
}
- final ScreenRotationAnimation screenRotationAnimation = mService.mRoot.getDisplayContent(
- Display.DEFAULT_DISPLAY).getRotationAnimation();
- // Imagine the case where we are changing orientation due to an app transition, but a
- // previous orientation change is still in progress. We won't process the orientation
- // change for our transition because we need to wait for the rotation animation to
- // finish.
- // If we start the app transition at this point, we will interrupt it halfway with a
- // new rotation animation after the old one finally finishes. It's better to defer the
- // app transition.
- if (screenRotationAnimation != null && screenRotationAnimation.isAnimating()
- && mDisplayContent.getDisplayRotation().needsUpdate()) {
- ProtoLog.v(WM_DEBUG_APP_TRANSITIONS,
- "Delaying app transition for screen rotation animation to finish");
- return false;
- }
for (int i = 0; i < apps.size(); i++) {
WindowContainer wc = apps.valueAt(i);
final ActivityRecord activity = getAppFromContainer(wc);
diff --git a/services/core/java/com/android/server/wm/AsyncRotationController.java b/services/core/java/com/android/server/wm/AsyncRotationController.java
index 6b6f011..d3fd0e3 100644
--- a/services/core/java/com/android/server/wm/AsyncRotationController.java
+++ b/services/core/java/com/android/server/wm/AsyncRotationController.java
@@ -135,8 +135,7 @@
// decides not to perform seamless rotation, it only affects whether to use fade animation
// when the windows are drawn. If the windows are not too slow (after rotation animation is
// done) to be drawn, the visual result can still look smooth.
- mHasScreenRotationAnimation =
- displayContent.getRotationAnimation() != null || mTransitionOp == OP_CHANGE;
+ mHasScreenRotationAnimation = mTransitionOp == OP_CHANGE;
if (mHasScreenRotationAnimation) {
// Hide the windows immediately because screen should have been covered by screenshot.
mHideImmediately = true;
diff --git a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
index b9febb83..119709e 100644
--- a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
+++ b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
@@ -1036,8 +1036,10 @@
// Normal apps with visible app window will be allowed to start activity if app switching
// is allowed, or apps like live wallpaper with non app visible window will be allowed.
+ // The home app can start apps even if app switches are usually disallowed.
final boolean appSwitchAllowedOrFg = state.mAppSwitchState == APP_SWITCH_ALLOW
- || state.mAppSwitchState == APP_SWITCH_FG_ONLY;
+ || state.mAppSwitchState == APP_SWITCH_FG_ONLY
+ || isHomeApp(state.mCallingUid, state.mCallingPackage);
if (appSwitchAllowedOrFg && state.mCallingUidHasVisibleActivity) {
return new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW,
/*background*/ false, "callingUid has visible window");
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 5435d8f..60c80a3 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -128,7 +128,6 @@
import static com.android.server.wm.DisplayContentProto.OPENING_APPS;
import static com.android.server.wm.DisplayContentProto.RESUMED_ACTIVITY;
import static com.android.server.wm.DisplayContentProto.ROOT_DISPLAY_AREA;
-import static com.android.server.wm.DisplayContentProto.SCREEN_ROTATION_ANIMATION;
import static com.android.server.wm.DisplayContentProto.SLEEP_TOKENS;
import static com.android.server.wm.EventLogTags.IMF_REMOVE_IME_SCREENSHOT;
import static com.android.server.wm.EventLogTags.IMF_SHOW_IME_SCREENSHOT;
@@ -150,7 +149,6 @@
import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_PLACING_SURFACES;
import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_WILL_ASSIGN_LAYERS;
import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_WILL_PLACE_SURFACES;
-import static com.android.server.wm.WindowManagerService.WINDOWS_FREEZING_SCREENS_TIMEOUT;
import static com.android.server.wm.WindowManagerService.dipToPixel;
import static com.android.server.wm.WindowState.EXCLUSION_LEFT;
import static com.android.server.wm.WindowState.EXCLUSION_RIGHT;
@@ -630,8 +628,6 @@
/** Windows removed since {@link #mCurrentFocus} was set to null. Used for ANR blaming. */
final ArrayList<WindowState> mWinRemovedSinceNullFocus = new ArrayList<>();
- private ScreenRotationAnimation mScreenRotationAnimation;
-
/**
* Sequence number for the current layout pass.
*/
@@ -1594,8 +1590,6 @@
mTransitionController.setDisplaySyncMethod(startBounds, endBounds, this);
collectDisplayChange(transition);
}
- } else if (mLastHasContent) {
- mWmService.startFreezingDisplay(0 /* exitAnim */, 0 /* enterAnim */, this);
}
sendNewConfiguration();
}
@@ -1638,7 +1632,6 @@
// DisplayContent#updateRotationUnchecked.
if (mWaitingForConfig) {
mWaitingForConfig = false;
- mWmService.mLastFinishedFreezeSource = "config-unchanged";
setLayoutNeeded();
mWmService.mWindowPlacerLocked.performSurfacePlacement();
}
@@ -1647,8 +1640,7 @@
@Override
boolean onDescendantOrientationChanged(@Nullable WindowContainer requestingContainer) {
- final Configuration config = updateOrientation(
- requestingContainer, false /* forceUpdate */);
+ final Configuration config = updateOrientationAndComputeConfig(false /* forceUpdate */);
// If display rotation class tells us that it doesn't consider app requested orientation,
// this display won't rotate just because of an app changes its requested orientation. Thus
// it indicates that this display chooses not to handle this request.
@@ -1696,28 +1688,17 @@
/**
* Update orientation of the display, returning a non-null new Configuration if it has
* changed from the current orientation. If a non-null configuration is returned, someone must
- * call {@link WindowManagerService#setNewDisplayOverrideConfiguration(Configuration,
- * DisplayContent)} to tell the window manager it can unfreeze the screen. This will typically
- * be done by calling {@link #sendNewConfiguration}.
+ * request a change transition or call {@link #sendNewConfiguration}.
*
- * @param freezeDisplayWindow Freeze the app window if the orientation is changed.
* @param forceUpdate See {@link DisplayRotation#updateRotationUnchecked(boolean)}
*/
- Configuration updateOrientation(WindowContainer<?> freezeDisplayWindow, boolean forceUpdate) {
+ Configuration updateOrientationAndComputeConfig(boolean forceUpdate) {
if (!mDisplayReady) {
return null;
}
Configuration config = null;
if (updateOrientation(forceUpdate)) {
- // If we changed the orientation but mOrientationChangeComplete is already true,
- // we used seamless rotation, and we don't need to freeze the screen.
- if (freezeDisplayWindow != null && !mWmService.mRoot.mOrientationChangeComplete) {
- final ActivityRecord activity = freezeDisplayWindow.asActivityRecord();
- if (activity != null && activity.mayFreezeScreenLocked()) {
- activity.startFreezingScreen();
- }
- }
config = new Configuration();
computeScreenConfiguration(config);
}
@@ -2254,8 +2235,6 @@
mDisplayRotation.isRotatingSeamlessly() && !shellTransitions;
final Transaction transaction =
shellTransitions ? getSyncTransaction() : getPendingTransaction();
- ScreenRotationAnimation screenRotationAnimation = rotateSeamlessly
- ? null : getRotationAnimation();
// We need to update our screen size information to match the new rotation. If the rotation
// has actually changed then this method will return true and, according to the comment at
// the top of the method, the caller is obligated to call computeNewConfigurationLocked().
@@ -2263,12 +2242,6 @@
// #computeScreenConfiguration() later.
updateDisplayAndOrientation(null /* outConfig */);
- // NOTE: We disable the rotation in the emulator because
- // it doesn't support hardware OpenGL emulation yet.
- if (screenRotationAnimation != null && screenRotationAnimation.hasScreenshot()) {
- screenRotationAnimation.setRotation(transaction, rotation);
- }
-
if (!shellTransitions) {
forAllWindows(w -> {
w.seamlesslyRotateIfAllowed(transaction, oldRotation, rotation, rotateSeamlessly);
@@ -2932,20 +2905,6 @@
@ScreenOrientation
@Override
int getOrientation() {
- if (mWmService.mDisplayFrozen) {
- if (mWmService.mPolicy.isKeyguardLocked()) {
- // Use the last orientation the while the display is frozen with the keyguard
- // locked. This could be the keyguard forced orientation or from a SHOW_WHEN_LOCKED
- // window. We don't want to check the show when locked window directly though as
- // things aren't stable while the display is frozen, for example the window could be
- // momentarily unavailable due to activity relaunch.
- ProtoLog.v(WM_DEBUG_ORIENTATION,
- "Display id=%d is frozen while keyguard locked, return %d",
- mDisplayId, getLastOrientation());
- return getLastOrientation();
- }
- }
-
final int compatOrientation = mAppCompatCameraPolicy.getOrientation();
if (compatOrientation != SCREEN_ORIENTATION_UNSPECIFIED) {
mLastOrientationSource = null;
@@ -3413,12 +3372,10 @@
mAppTransition.removeAppTransitionTimeoutCallbacks();
mTransitionController.unregisterLegacyListener(mFixedRotationTransitionListener);
handleAnimatingStoppedAndTransition();
- mWmService.stopFreezingDisplayLocked();
mDeviceStateController.unregisterDeviceStateCallback(mDeviceStateConsumer);
super.removeImmediately();
if (DEBUG_DISPLAY) Slog.v(TAG_WM, "Removing display=" + this);
mPointerEventDispatcher.dispose();
- setRotationAnimation(null);
// Unlink death from remote to clear the reference from binder -> mRemoteInsetsDeath
// -> this DisplayContent.
setRemoteInsetsController(null);
@@ -3514,24 +3471,6 @@
RotationUtils.rotateBounds(inOutBounds, mTmpRect, oldRotation, newRotation);
}
- public void setRotationAnimation(ScreenRotationAnimation screenRotationAnimation) {
- final ScreenRotationAnimation prev = mScreenRotationAnimation;
- mScreenRotationAnimation = screenRotationAnimation;
- if (prev != null) {
- prev.kill();
- }
-
- // Hide the windows which are not significant in rotation animation. So that the windows
- // don't need to block the unfreeze time.
- if (screenRotationAnimation != null && screenRotationAnimation.hasScreenshot()) {
- startAsyncRotationIfNeeded();
- }
- }
-
- public ScreenRotationAnimation getRotationAnimation() {
- return mScreenRotationAnimation;
- }
-
/**
* Collects this display into an already-collecting transition.
*/
@@ -3595,12 +3534,6 @@
}
}
- /** If the display is in transition, there should be a screenshot covering it. */
- @Override
- boolean inTransition() {
- return mScreenRotationAnimation != null || super.inTransition();
- }
-
@Override
public void dumpDebug(ProtoOutputStream proto, long fieldId,
@WindowTracingLogLevel int logLevel) {
@@ -3616,10 +3549,6 @@
proto.write(DPI, mBaseDisplayDensity);
mDisplayInfo.dumpDebug(proto, DISPLAY_INFO);
mDisplayRotation.dumpDebug(proto, DISPLAY_ROTATION);
- final ScreenRotationAnimation screenRotationAnimation = getRotationAnimation();
- if (screenRotationAnimation != null) {
- screenRotationAnimation.dumpDebug(proto, SCREEN_ROTATION_ANIMATION);
- }
mDisplayFrames.dumpDebug(proto, DISPLAY_FRAMES);
proto.write(MIN_SIZE_OF_RESIZEABLE_TASK_DP, mMinSizeOfResizeableTaskDp);
if (mTransitionController.isShellTransitionsEnabled()) {
@@ -3766,16 +3695,6 @@
pw.println();
- final ScreenRotationAnimation rotationAnimation = getRotationAnimation();
- if (rotationAnimation != null) {
- pw.println(" mScreenRotationAnimation:");
- rotationAnimation.printTo(subPrefix, pw);
- } else if (dumpAll) {
- pw.println(" no ScreenRotationAnimation ");
- }
-
- pw.println();
-
// Dump root task references
final Task rootHomeTask = getDefaultTaskDisplayArea().getRootHomeTask();
if (rootHomeTask != null) {
@@ -5068,13 +4987,6 @@
return win != null;
}
- void onWindowFreezeTimeout() {
- Slog.w(TAG_WM, "Window freeze timeout expired.");
- mWmService.mWindowsFreezingScreen = WINDOWS_FREEZING_SCREENS_TIMEOUT;
-
- mWmService.mWindowPlacerLocked.performSurfacePlacement();
- }
-
/**
* Callbacks when the given type of {@link WindowContainer} animation finished running in the
* hierarchy.
@@ -5297,8 +5209,7 @@
boolean okToDisplay(boolean ignoreFrozen, boolean ignoreScreenOn) {
if (mDisplayId == DEFAULT_DISPLAY) {
- return (!mWmService.mDisplayFrozen || ignoreFrozen)
- && mWmService.mDisplayEnabled
+ return mWmService.mDisplayEnabled
&& (ignoreScreenOn || mWmService.mPolicy.isScreenOn());
}
return mDisplayInfo.state == Display.STATE_ON;
@@ -5443,10 +5354,7 @@
// We skip IME windows so they're processed just above their target.
// Note that this method check should align with {@link
// WindowState#applyImeWindowsIfNeeded} in case of any state mismatch.
- return dc.mImeLayeringTarget != null
- // Make sure that the IME window won't be skipped to report that it has
- // completed the orientation change.
- && !dc.mWmService.mDisplayFrozen;
+ return dc.mImeLayeringTarget != null;
}
/** Like {@link #forAllWindows}, but ignores {@link #skipImeWindowsDuringTraversal} */
@@ -6427,14 +6335,12 @@
changes = performDisplayOverrideConfigUpdate(values);
}
mAtmService.mTmpUpdateConfigurationResult.changes = changes;
- mAtmService.mTmpUpdateConfigurationResult.mIsUpdating = true;
}
if (!deferResume) {
kept = mAtmService.ensureConfigAndVisibilityAfterUpdate(starting, changes);
}
} finally {
- mAtmService.mTmpUpdateConfigurationResult.mIsUpdating = false;
mAtmService.continueWindowLayout();
}
@@ -6490,7 +6396,6 @@
mCurrentOverrideConfigurationChanges = 0;
if (mWaitingForConfig) {
mWaitingForConfig = false;
- mWmService.mLastFinishedFreezeSource = "new-config";
}
mAtmService.addWindowLayoutReasons(
ActivityTaskManagerService.LAYOUT_REASON_CONFIG_CHANGED);
@@ -7086,13 +6991,6 @@
}
@Override
- public void onAppTransitionCancelledLocked(boolean keyguardGoingAwayCancelled) {
- // It is only needed when freezing display in legacy transition.
- if (mTransitionController.isShellTransitionsEnabled()) return;
- continueUpdateOrientationForDiffOrienLaunchingApp();
- }
-
- @Override
public void onAppTransitionTimeoutLocked() {
continueUpdateOrientationForDiffOrienLaunchingApp();
}
diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java
index f53bc70..f8c1755 100644
--- a/services/core/java/com/android/server/wm/DisplayRotation.java
+++ b/services/core/java/com/android/server/wm/DisplayRotation.java
@@ -22,12 +22,8 @@
import static android.view.Display.TYPE_EXTERNAL;
import static android.view.Display.TYPE_OVERLAY;
import static android.view.Display.TYPE_VIRTUAL;
-import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_CROSSFADE;
-import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_JUMPCUT;
-import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_ROTATE;
import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS;
-import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_ANIM;
import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_ORIENTATION;
import static com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs.LID_OPEN;
import static com.android.server.wm.DisplayRotationProto.FIXED_TO_USER_ROTATION_MODE;
@@ -41,10 +37,7 @@
import static com.android.server.wm.DisplayRotationReversionController.REVERSION_TYPE_NOSENSOR;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
-import static com.android.server.wm.WindowManagerService.WINDOWS_FREEZING_SCREENS_ACTIVE;
-import static com.android.server.wm.WindowManagerService.WINDOW_FREEZE_TIMEOUT_DURATION;
-import android.annotation.AnimRes;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -104,13 +97,6 @@
private static final int ROTATION_UNDEFINED = -1;
- private static class RotationAnimationPair {
- @AnimRes
- int mEnter;
- @AnimRes
- int mExit;
- }
-
@Nullable
final FoldController mFoldController;
@@ -130,7 +116,6 @@
private final int mCarDockRotation;
private final int mDeskDockRotation;
private final int mUndockedHdmiRotation;
- private final RotationAnimationPair mTmpRotationAnim = new RotationAnimationPair();
private final RotationHistory mRotationHistory = new RotationHistory();
private final RotationLockHistory mRotationLockHistory = new RotationLockHistory();
@@ -540,14 +525,6 @@
ProtoLog.v(WM_DEBUG_ORIENTATION, "Deferring rotation, animation in progress.");
return false;
}
- if (mService.mDisplayFrozen) {
- // Even if the screen rotation animation has finished (e.g. isAnimating returns
- // false), there is still some time where we haven't yet unfrozen the display. We
- // also need to abort rotation here.
- ProtoLog.v(WM_DEBUG_ORIENTATION,
- "Deferring rotation, still finishing previous rotation");
- return false;
- }
if (mDisplayContent.mFixedRotationTransitionListener.shouldDeferRotation()) {
// Makes sure that after the transition is finished, updateOrientation() can see
@@ -644,17 +621,13 @@
return true;
}
- mService.mWindowsFreezingScreen = WINDOWS_FREEZING_SCREENS_ACTIVE;
- mService.mH.sendNewMessageDelayed(WindowManagerService.H.WINDOW_FREEZE_TIMEOUT,
- mDisplayContent, WINDOW_FREEZE_TIMEOUT_DURATION);
-
if (shouldRotateSeamlessly(oldRotation, rotation, forceUpdate)) {
// The screen rotation animation uses a screenshot to freeze the screen while windows
// resize underneath. When we are rotating seamlessly, we allow the elements to
// transition to their rotated state independently and without a freeze required.
prepareSeamlessRotation();
} else {
- prepareNormalRotationAnimation();
+ cancelSeamlessRotation();
}
// Give a remote handler (system ui) some time to reposition things.
@@ -695,12 +668,6 @@
}
}
- void prepareNormalRotationAnimation() {
- cancelSeamlessRotation();
- final RotationAnimationPair anim = selectRotationAnimation();
- mService.startFreezingDisplay(anim.mExit, anim.mEnter, mDisplayContent);
- }
-
/**
* This ensures that normal rotation animation is used. E.g. {@link #mRotatingSeamlessly} was
* set by previous {@link #updateRotationUnchecked}, but another orientation change happens
@@ -820,79 +787,6 @@
}
}
- /**
- * Returns the animation to run for a rotation transition based on the top fullscreen windows
- * {@link android.view.WindowManager.LayoutParams#rotationAnimation} and whether it is currently
- * fullscreen and frontmost.
- */
- private RotationAnimationPair selectRotationAnimation() {
- // If the screen is off or non-interactive, force a jumpcut.
- final boolean forceJumpcut = !mDisplayPolicy.isScreenOnFully()
- || !mService.mPolicy.okToAnimate(false /* ignoreScreenOn */);
- final WindowState topFullscreen = mDisplayPolicy.getTopFullscreenOpaqueWindow();
- ProtoLog.i(WM_DEBUG_ANIM, "selectRotationAnimation topFullscreen=%s"
- + " rotationAnimation=%d forceJumpcut=%b",
- topFullscreen,
- topFullscreen == null ? 0 : topFullscreen.getAttrs().rotationAnimation,
- forceJumpcut);
- if (forceJumpcut) {
- mTmpRotationAnim.mExit = R.anim.rotation_animation_jump_exit;
- mTmpRotationAnim.mEnter = R.anim.rotation_animation_enter;
- return mTmpRotationAnim;
- }
- if (topFullscreen != null) {
- int animationHint = topFullscreen.getRotationAnimationHint();
- if (animationHint < 0 && mDisplayPolicy.isTopLayoutFullscreen()) {
- animationHint = topFullscreen.getAttrs().rotationAnimation;
- }
- switch (animationHint) {
- case ROTATION_ANIMATION_CROSSFADE:
- case ROTATION_ANIMATION_SEAMLESS: // Crossfade is fallback for seamless.
- mTmpRotationAnim.mExit = R.anim.rotation_animation_xfade_exit;
- mTmpRotationAnim.mEnter = R.anim.rotation_animation_enter;
- break;
- case ROTATION_ANIMATION_JUMPCUT:
- mTmpRotationAnim.mExit = R.anim.rotation_animation_jump_exit;
- mTmpRotationAnim.mEnter = R.anim.rotation_animation_enter;
- break;
- case ROTATION_ANIMATION_ROTATE:
- default:
- mTmpRotationAnim.mExit = mTmpRotationAnim.mEnter = 0;
- break;
- }
- } else {
- mTmpRotationAnim.mExit = mTmpRotationAnim.mEnter = 0;
- }
- return mTmpRotationAnim;
- }
-
- /**
- * Validate whether the current top fullscreen has specified the same
- * {@link android.view.WindowManager.LayoutParams#rotationAnimation} value as that being passed
- * in from the previous top fullscreen window.
- *
- * @param exitAnimId exiting resource id from the previous window.
- * @param enterAnimId entering resource id from the previous window.
- * @param forceDefault For rotation animations only, if true ignore the animation values and
- * just return false.
- * @return {@code true} if the previous values are still valid, false if they should be replaced
- * with the default.
- */
- boolean validateRotationAnimation(int exitAnimId, int enterAnimId, boolean forceDefault) {
- switch (exitAnimId) {
- case R.anim.rotation_animation_xfade_exit:
- case R.anim.rotation_animation_jump_exit:
- // These are the only cases that matter.
- if (forceDefault) {
- return false;
- }
- final RotationAnimationPair anim = selectRotationAnimation();
- return exitAnimId == anim.mExit && enterAnimId == anim.mEnter;
- default:
- return true;
- }
- }
-
void restoreSettings(int userRotationMode, int userRotation, int fixedToUserRotation) {
mFixedToUserRotation = fixedToUserRotation;
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 7a3eb67..e2b5c83 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -78,11 +78,9 @@
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WINDOW_TRACE;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
-import static com.android.server.wm.WindowManagerService.H.WINDOW_FREEZE_TIMEOUT;
import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_NORMAL;
import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_PLACING_SURFACES;
import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_WILL_PLACE_SURFACES;
-import static com.android.server.wm.WindowManagerService.WINDOWS_FREEZING_SCREENS_NONE;
import static com.android.server.wm.WindowSurfacePlacer.SET_UPDATE_ROTATION;
import static com.android.server.wm.WindowSurfacePlacer.SET_WALLPAPER_ACTION_PENDING;
@@ -200,12 +198,6 @@
private boolean mSustainedPerformanceModeEnabled = false;
private boolean mSustainedPerformanceModeCurrent = false;
- // During an orientation change, we track whether all windows have rendered
- // at the new orientation, and this will be false from changing orientation until that occurs.
- // For seamless rotation cases this always stays true, as the windows complete their orientation
- // changes 1 by 1 without disturbing global state.
- boolean mOrientationChangeComplete = true;
-
private final Handler mHandler;
private String mCloseSystemDialogsReason;
@@ -841,20 +833,6 @@
}
}
- if (mWmService.mDisplayFrozen) {
- ProtoLog.v(WM_DEBUG_ORIENTATION,
- "With display frozen, orientationChangeComplete=%b",
- mOrientationChangeComplete);
- }
- if (mOrientationChangeComplete) {
- if (mWmService.mWindowsFreezingScreen != WINDOWS_FREEZING_SCREENS_NONE) {
- mWmService.mWindowsFreezingScreen = WINDOWS_FREEZING_SCREENS_NONE;
- mWmService.mLastFinishedFreezeSource = mLastWindowFreezeSource;
- mWmService.mH.removeMessages(WINDOW_FREEZE_TIMEOUT);
- }
- mWmService.stopFreezingDisplayLocked();
- }
-
// Destroy the surface of any windows that are no longer visible.
i = mWmService.mDestroySurface.size();
if (i > 0) {
@@ -881,13 +859,11 @@
}
}
- if (!mWmService.mDisplayFrozen) {
- // Post these on a handler such that we don't call into power manager service while
- // holding the window manager lock to avoid lock contention with power manager lock.
- mHandler.obtainMessage(SET_SCREEN_BRIGHTNESS_OVERRIDE, mDisplayBrightnessOverrides)
- .sendToTarget();
- mHandler.obtainMessage(SET_USER_ACTIVITY_TIMEOUT, mUserActivityTimeout).sendToTarget();
- }
+ // Post these on a handler such that we don't call into power manager service while
+ // holding the window manager lock to avoid lock contention with power manager lock.
+ mHandler.obtainMessage(SET_SCREEN_BRIGHTNESS_OVERRIDE, mDisplayBrightnessOverrides)
+ .sendToTarget();
+ mHandler.obtainMessage(SET_USER_ACTIVITY_TIMEOUT, mUserActivityTimeout).sendToTarget();
if (mSustainedPerformanceModeCurrent != mSustainedPerformanceModeEnabled) {
mSustainedPerformanceModeEnabled = mSustainedPerformanceModeCurrent;
@@ -902,8 +878,7 @@
}
if (!mWmService.mWaitingForDrawnCallbacks.isEmpty()
- || (mOrientationChangeComplete && !isLayoutNeeded()
- && !mUpdateRotation)) {
+ || (!isLayoutNeeded() && !mUpdateRotation)) {
mWmService.checkDrawnWindowsLocked();
}
@@ -991,7 +966,7 @@
private void handleResizingWindows() {
for (int i = mWmService.mResizingWindows.size() - 1; i >= 0; i--) {
WindowState win = mWmService.mResizingWindows.get(i);
- if (win.mAppFreezing || win.getDisplayContent().mWaitingForConfig) {
+ if (win.getDisplayContent().mWaitingForConfig) {
// Don't remove this window until rotation has completed and is not waiting for the
// complete configuration.
continue;
@@ -1092,12 +1067,6 @@
mUpdateRotation = true;
doRequest = true;
}
- if (mOrientationChangeComplete) {
- mLastWindowFreezeSource = mWmService.mAnimator.mLastWindowFreezeSource;
- if (mWmService.mWindowsFreezingScreen != WINDOWS_FREEZING_SCREENS_NONE) {
- doRequest = true;
- }
- }
return doRequest;
}
@@ -1765,7 +1734,7 @@
// Force-update the orientation from the WindowManager, since we need the true configuration
// to send to the client now.
final Configuration config =
- displayContent.updateOrientation(starting, true /* forceUpdate */);
+ displayContent.updateOrientationAndComputeConfig(true /* forceUpdate */);
// Visibilities may change so let the starting activity have a chance to report. Can't do it
// when visibility is changed in each AppWindowToken because it may trigger wrong
// configuration push because the visibility of some activities may not be updated yet.
diff --git a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
deleted file mode 100644
index 4bd294b..0000000
--- a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
+++ /dev/null
@@ -1,825 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wm;
-
-import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
-import static android.util.RotationUtils.deltaRotation;
-import static android.view.WindowManagerPolicyConstants.SCREEN_FREEZE_LAYER_BASE;
-
-import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_ORIENTATION;
-import static com.android.internal.protolog.WmProtoLogGroups.WM_SHOW_SURFACE_ALLOC;
-import static com.android.server.wm.AnimationSpecProto.ROTATE;
-import static com.android.server.wm.RotationAnimationSpecProto.DURATION_MS;
-import static com.android.server.wm.RotationAnimationSpecProto.END_LUMA;
-import static com.android.server.wm.RotationAnimationSpecProto.START_LUMA;
-import static com.android.server.wm.ScreenRotationAnimationProto.ANIMATION_RUNNING;
-import static com.android.server.wm.ScreenRotationAnimationProto.STARTED;
-import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_SCREEN_ROTATION;
-import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
-import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
-import static com.android.server.wm.utils.CoordinateTransforms.computeRotationMatrix;
-
-import android.animation.ArgbEvaluator;
-import android.content.Context;
-import android.graphics.Color;
-import android.graphics.Matrix;
-import android.graphics.Point;
-import android.graphics.Rect;
-import android.hardware.HardwareBuffer;
-import android.os.Trace;
-import android.util.Slog;
-import android.util.proto.ProtoOutputStream;
-import android.view.DisplayInfo;
-import android.view.Surface;
-import android.view.Surface.OutOfResourcesException;
-import android.view.SurfaceControl;
-import android.view.animation.Animation;
-import android.view.animation.AnimationUtils;
-import android.view.animation.Transformation;
-import android.window.ScreenCapture;
-
-import com.android.internal.R;
-import com.android.internal.policy.TransitionAnimation;
-import com.android.internal.protolog.ProtoLog;
-import com.android.server.wm.SurfaceAnimator.AnimationType;
-import com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback;
-
-import java.io.PrintWriter;
-
-/**
- * This class handles the rotation animation when the device is rotated.
- *
- * <p>
- * The screen rotation animation is composed of 4 different part:
- * <ul>
- * <li> The screenshot: <p>
- * A screenshot of the whole screen prior the change of orientation is taken to hide the
- * element resizing below. The screenshot is then animated to rotate and cross-fade to
- * the new orientation with the content in the new orientation.
- *
- * <li> The windows on the display: <p>y
- * Once the device is rotated, the screen and its content are in the new orientation. The
- * animation first rotate the new content into the old orientation to then be able to
- * animate to the new orientation
- *
- * <li> The Background color frame: <p>
- * To have the animation seem more seamless, we add a color transitioning background behind the
- * exiting and entering layouts. We compute the brightness of the start and end
- * layouts and transition from the two brightness values as grayscale underneath the animation
- *
- * <li> The entering Blackframe: <p>
- * The enter Blackframe is similar to the exit Blackframe but is only used when a custom
- * rotation animation is used and matches the new content size instead of the screenshot.
- * </ul>
- *
- * Each part has its own Surface which are then animated by {@link SurfaceAnimator}s.
- */
-class ScreenRotationAnimation {
- private static final String TAG = TAG_WITH_CLASS_NAME ? "ScreenRotationAnimation" : TAG_WM;
-
- private final Context mContext;
- private final DisplayContent mDisplayContent;
- private final float[] mTmpFloats = new float[9];
- private final Transformation mRotateExitTransformation = new Transformation();
- private final Transformation mRotateEnterTransformation = new Transformation();
- // Complete transformations being applied.
- private final Matrix mSnapshotInitialMatrix = new Matrix();
- private final WindowManagerService mService;
- /** Only used for custom animations and not screen rotation. */
- private SurfaceControl mEnterBlackFrameLayer;
- /** This layer contains the actual screenshot that is to be faded out. */
- private SurfaceControl mScreenshotLayer;
- private SurfaceControl[] mRoundedCornerOverlay;
- /**
- * Only used for screen rotation and not custom animations. Layered behind all other layers
- * to avoid showing any "empty" spots
- */
- private SurfaceControl mBackColorSurface;
- private BlackFrame mEnteringBlackFrame;
-
- private final int mOriginalRotation;
- private final int mOriginalWidth;
- private final int mOriginalHeight;
- private int mCurRotation;
-
- // The current active animation to move from the old to the new rotated
- // state. Which animation is run here will depend on the old and new
- // rotations.
- private Animation mRotateExitAnimation;
- private Animation mRotateEnterAnimation;
- private Animation mRotateAlphaAnimation;
- private boolean mStarted;
- private boolean mAnimRunning;
- private boolean mFinishAnimReady;
- private long mFinishAnimStartTime;
- private SurfaceRotationAnimationController mSurfaceRotationAnimationController;
- /** Intensity of light/whiteness of the layout before rotation occurs. */
- private float mStartLuma;
- /** Intensity of light/whiteness of the layout after rotation occurs. */
- private float mEndLuma;
-
- ScreenRotationAnimation(DisplayContent displayContent, @Surface.Rotation int originalRotation) {
- mService = displayContent.mWmService;
- mContext = mService.mContext;
- mDisplayContent = displayContent;
- final Rect currentBounds = displayContent.getBounds();
- final int width = currentBounds.width();
- final int height = currentBounds.height();
-
- // Screenshot does NOT include rotation!
- final DisplayInfo displayInfo = displayContent.getDisplayInfo();
- final int realOriginalRotation = displayInfo.rotation;
-
- mOriginalRotation = originalRotation;
- // If the delta is not zero, the rotation of display may not change, but we still want to
- // apply rotation animation because there should be a top app shown as rotated. So the
- // specified original rotation customizes the direction of animation to have better look
- // when restoring the rotated app to the same rotation as current display.
- final int delta = deltaRotation(originalRotation, realOriginalRotation);
- final boolean flipped = delta == Surface.ROTATION_90 || delta == Surface.ROTATION_270;
- mOriginalWidth = flipped ? height : width;
- mOriginalHeight = flipped ? width : height;
- final int logicalWidth = displayInfo.logicalWidth;
- final int logicalHeight = displayInfo.logicalHeight;
- final boolean isSizeChanged =
- logicalWidth > mOriginalWidth == logicalHeight > mOriginalHeight
- && (logicalWidth != mOriginalWidth || logicalHeight != mOriginalHeight);
- mSurfaceRotationAnimationController = new SurfaceRotationAnimationController();
-
- // Check whether the current screen contains any secure content.
- boolean isSecure = displayContent.hasSecureWindowOnScreen();
- final int displayId = displayContent.getDisplayId();
- final SurfaceControl.Transaction t = mService.mTransactionFactory.get();
-
- try {
- final ScreenCapture.ScreenshotHardwareBuffer screenshotBuffer;
- if (isSizeChanged) {
- // Temporarily not skip screenshot for the rounded corner overlays and screenshot
- // the whole display to include the rounded corner overlays.
- setSkipScreenshotForRoundedCornerOverlays(false, t);
- ScreenCapture.LayerCaptureArgs captureArgs =
- new ScreenCapture.LayerCaptureArgs.Builder(
- displayContent.getSurfaceControl())
- .setCaptureSecureLayers(true)
- .setAllowProtected(true)
- .setSourceCrop(new Rect(0, 0, width, height))
- .setHintForSeamlessTransition(true)
- .build();
- screenshotBuffer = ScreenCapture.captureLayers(captureArgs);
- } else {
- ScreenCapture.LayerCaptureArgs captureArgs =
- new ScreenCapture.LayerCaptureArgs.Builder(
- displayContent.getSurfaceControl())
- .setCaptureSecureLayers(true)
- .setAllowProtected(true)
- .setSourceCrop(new Rect(0, 0, width, height))
- .setHintForSeamlessTransition(true)
- .build();
- screenshotBuffer = ScreenCapture.captureLayers(captureArgs);
- }
-
- if (screenshotBuffer == null) {
- Slog.w(TAG, "Unable to take screenshot of display " + displayId);
- return;
- }
-
- // If the screenshot contains secure layers, we have to make sure the
- // screenshot surface we display it in also has FLAG_SECURE so that
- // the user can not screenshot secure layers via the screenshot surface.
- if (screenshotBuffer.containsSecureLayers()) {
- isSecure = true;
- }
-
- mBackColorSurface = displayContent.makeChildSurface(null)
- .setName("BackColorSurface")
- .setColorLayer()
- .setCallsite("ScreenRotationAnimation")
- .build();
-
- String name = "RotationLayer";
- mScreenshotLayer = displayContent.makeOverlay()
- .setName(name)
- .setOpaque(true)
- .setSecure(isSecure)
- .setCallsite("ScreenRotationAnimation")
- .setBLASTLayer()
- .build();
- // This is the way to tell the input system to exclude this surface from occlusion
- // detection since we don't have a window for it. We do this because this window is
- // generated by the system as well as its content.
- InputMonitor.setTrustedOverlayInputInfo(mScreenshotLayer, t, displayId, name);
-
- mEnterBlackFrameLayer = displayContent.makeOverlay()
- .setName("EnterBlackFrameLayer")
- .setContainerLayer()
- .setCallsite("ScreenRotationAnimation")
- .build();
-
- HardwareBuffer hardwareBuffer = screenshotBuffer.getHardwareBuffer();
- Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER,
- "ScreenRotationAnimation#getMedianBorderLuma");
- mStartLuma = TransitionAnimation.getBorderLuma(hardwareBuffer,
- screenshotBuffer.getColorSpace());
- Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
-
- t.setLayer(mScreenshotLayer, SCREEN_FREEZE_LAYER_BASE);
- t.reparent(mBackColorSurface, displayContent.getSurfaceControl());
- // If hdr layers are on-screen, e.g. picture-in-picture mode, the screenshot of
- // rotation animation is an sdr image containing tone-mapping hdr content, then
- // disable dimming effect to get avoid of hdr content being dimmed during animation.
- t.setDimmingEnabled(mScreenshotLayer, !screenshotBuffer.containsHdrLayers());
- t.setLayer(mBackColorSurface, -1);
- t.setColor(mBackColorSurface, new float[]{mStartLuma, mStartLuma, mStartLuma});
- t.setAlpha(mBackColorSurface, 1);
- t.setBuffer(mScreenshotLayer, hardwareBuffer);
- t.setColorSpace(mScreenshotLayer, screenshotBuffer.getColorSpace());
- t.show(mScreenshotLayer);
- t.show(mBackColorSurface);
- hardwareBuffer.close();
-
- if (mRoundedCornerOverlay != null) {
- for (SurfaceControl sc : mRoundedCornerOverlay) {
- if (sc.isValid()) {
- t.hide(sc);
- }
- }
- }
-
- } catch (OutOfResourcesException e) {
- Slog.w(TAG, "Unable to allocate freeze surface", e);
- }
-
- // If display size is changed with the same orientation, map the bounds of screenshot to
- // the new logical display size. Currently pending transaction and RWC#mDisplayTransaction
- // are merged to global transaction, so it can be synced with display change when calling
- // DisplayManagerInternal#performTraversal(transaction).
- if (mScreenshotLayer != null && isSizeChanged) {
- displayContent.getPendingTransaction().setGeometry(mScreenshotLayer,
- new Rect(0, 0, mOriginalWidth, mOriginalHeight),
- new Rect(0, 0, logicalWidth, logicalHeight), Surface.ROTATION_0);
- }
-
- ProtoLog.i(WM_SHOW_SURFACE_ALLOC,
- " FREEZE %s: CREATE", mScreenshotLayer);
- if (originalRotation == realOriginalRotation) {
- setRotation(t, realOriginalRotation);
- } else {
- // If the given original rotation is different from real original display rotation,
- // this is playing non-zero degree rotation animation without display rotation change,
- // so the snapshot doesn't need to be transformed.
- mCurRotation = realOriginalRotation;
- mSnapshotInitialMatrix.reset();
- setRotationTransform(t, mSnapshotInitialMatrix);
- }
- t.apply();
- }
-
- void setSkipScreenshotForRoundedCornerOverlays(
- boolean skipScreenshot, SurfaceControl.Transaction t) {
- mDisplayContent.forAllWindows(w -> {
- if (!w.mToken.mRoundedCornerOverlay || !w.isVisible() || !w.mWinAnimator.hasSurface()) {
- return;
- }
- t.setSkipScreenshot(w.mWinAnimator.mSurfaceControl, skipScreenshot);
- }, false);
- if (!skipScreenshot) {
- // Use sync apply to apply the change immediately, so that the next
- // SC.captureDisplay can capture the screen decor layers.
- t.apply(true /* sync */);
- }
- }
-
- public void dumpDebug(ProtoOutputStream proto, long fieldId) {
- final long token = proto.start(fieldId);
- proto.write(STARTED, mStarted);
- proto.write(ANIMATION_RUNNING, mAnimRunning);
- proto.end(token);
- }
-
- boolean hasScreenshot() {
- return mScreenshotLayer != null;
- }
-
- private void setRotationTransform(SurfaceControl.Transaction t, Matrix matrix) {
- if (mScreenshotLayer == null) {
- return;
- }
- matrix.getValues(mTmpFloats);
- float x = mTmpFloats[Matrix.MTRANS_X];
- float y = mTmpFloats[Matrix.MTRANS_Y];
- t.setPosition(mScreenshotLayer, x, y);
- t.setMatrix(mScreenshotLayer,
- mTmpFloats[Matrix.MSCALE_X], mTmpFloats[Matrix.MSKEW_Y],
- mTmpFloats[Matrix.MSKEW_X], mTmpFloats[Matrix.MSCALE_Y]);
-
- t.setAlpha(mScreenshotLayer, (float) 1.0);
- t.show(mScreenshotLayer);
- }
-
- public void printTo(String prefix, PrintWriter pw) {
- pw.print(prefix); pw.print("mSurface="); pw.print(mScreenshotLayer);
- pw.print(prefix);
- pw.print("mEnteringBlackFrame=");
- pw.println(mEnteringBlackFrame);
- if (mEnteringBlackFrame != null) {
- mEnteringBlackFrame.printTo(prefix + " ", pw);
- }
- pw.print(prefix); pw.print("mCurRotation="); pw.print(mCurRotation);
- pw.print(" mOriginalRotation="); pw.println(mOriginalRotation);
- pw.print(prefix); pw.print("mOriginalWidth="); pw.print(mOriginalWidth);
- pw.print(" mOriginalHeight="); pw.println(mOriginalHeight);
- pw.print(prefix); pw.print("mStarted="); pw.print(mStarted);
- pw.print(" mAnimRunning="); pw.print(mAnimRunning);
- pw.print(" mFinishAnimReady="); pw.print(mFinishAnimReady);
- pw.print(" mFinishAnimStartTime="); pw.println(mFinishAnimStartTime);
- pw.print(prefix); pw.print("mRotateExitAnimation="); pw.print(mRotateExitAnimation);
- pw.print(" "); mRotateExitTransformation.printShortString(pw); pw.println();
- pw.print(prefix); pw.print("mRotateEnterAnimation="); pw.print(mRotateEnterAnimation);
- pw.print(" "); mRotateEnterTransformation.printShortString(pw); pw.println();
- pw.print(prefix); pw.print("mSnapshotInitialMatrix=");
- mSnapshotInitialMatrix.dump(pw); pw.println();
- }
-
- public void setRotation(SurfaceControl.Transaction t, int rotation) {
- mCurRotation = rotation;
-
- // Compute the transformation matrix that must be applied
- // to the snapshot to make it stay in the same original position
- // with the current screen rotation.
- int delta = deltaRotation(rotation, mOriginalRotation);
- computeRotationMatrix(delta, mOriginalWidth, mOriginalHeight, mSnapshotInitialMatrix);
- setRotationTransform(t, mSnapshotInitialMatrix);
- }
-
- /**
- * Returns true if animating.
- */
- private boolean startAnimation(SurfaceControl.Transaction t, long maxAnimationDuration,
- float animationScale, int finalWidth, int finalHeight, int exitAnim, int enterAnim) {
- if (mScreenshotLayer == null) {
- // Can't do animation.
- return false;
- }
- if (mStarted) {
- return true;
- }
-
- mStarted = true;
-
- // Figure out how the screen has moved from the original rotation.
- int delta = deltaRotation(mCurRotation, mOriginalRotation);
-
- final boolean customAnim;
- if (exitAnim != 0 && enterAnim != 0) {
- customAnim = true;
- mRotateExitAnimation = AnimationUtils.loadAnimation(mContext, exitAnim);
- mRotateEnterAnimation = AnimationUtils.loadAnimation(mContext, enterAnim);
- mRotateAlphaAnimation = AnimationUtils.loadAnimation(mContext,
- R.anim.screen_rotate_alpha);
- } else {
- customAnim = false;
- switch (delta) { /* Counter-Clockwise Rotations */
- case Surface.ROTATION_0:
- mRotateExitAnimation = AnimationUtils.loadAnimation(mContext,
- R.anim.screen_rotate_0_exit);
- mRotateEnterAnimation = AnimationUtils.loadAnimation(mContext,
- R.anim.rotation_animation_enter);
- break;
- case Surface.ROTATION_90:
- mRotateExitAnimation = AnimationUtils.loadAnimation(mContext,
- R.anim.screen_rotate_plus_90_exit);
- mRotateEnterAnimation = AnimationUtils.loadAnimation(mContext,
- R.anim.screen_rotate_plus_90_enter);
- break;
- case Surface.ROTATION_180:
- mRotateExitAnimation = AnimationUtils.loadAnimation(mContext,
- R.anim.screen_rotate_180_exit);
- mRotateEnterAnimation = AnimationUtils.loadAnimation(mContext,
- R.anim.screen_rotate_180_enter);
- break;
- case Surface.ROTATION_270:
- mRotateExitAnimation = AnimationUtils.loadAnimation(mContext,
- R.anim.screen_rotate_minus_90_exit);
- mRotateEnterAnimation = AnimationUtils.loadAnimation(mContext,
- R.anim.screen_rotate_minus_90_enter);
- break;
- }
- }
-
- ProtoLog.d(WM_DEBUG_ORIENTATION, "Start rotation animation. customAnim=%s, "
- + "mCurRotation=%s, mOriginalRotation=%s",
- customAnim, Surface.rotationToString(mCurRotation),
- Surface.rotationToString(mOriginalRotation));
-
- mRotateExitAnimation.initialize(finalWidth, finalHeight, mOriginalWidth, mOriginalHeight);
- mRotateExitAnimation.restrictDuration(maxAnimationDuration);
- mRotateExitAnimation.scaleCurrentDuration(animationScale);
- mRotateEnterAnimation.initialize(finalWidth, finalHeight, mOriginalWidth, mOriginalHeight);
- mRotateEnterAnimation.restrictDuration(maxAnimationDuration);
- mRotateEnterAnimation.scaleCurrentDuration(animationScale);
-
- mAnimRunning = false;
- mFinishAnimReady = false;
- mFinishAnimStartTime = -1;
-
- if (customAnim) {
- mRotateAlphaAnimation.restrictDuration(maxAnimationDuration);
- mRotateAlphaAnimation.scaleCurrentDuration(animationScale);
- }
-
- if (customAnim && mEnteringBlackFrame == null) {
- try {
- Rect outer = new Rect(-finalWidth, -finalHeight,
- finalWidth * 2, finalHeight * 2);
- Rect inner = new Rect(0, 0, finalWidth, finalHeight);
- mEnteringBlackFrame = new BlackFrame(mService.mTransactionFactory, t, outer, inner,
- SCREEN_FREEZE_LAYER_BASE, mDisplayContent, false, mEnterBlackFrameLayer);
- } catch (OutOfResourcesException e) {
- Slog.w(TAG, "Unable to allocate black surface", e);
- }
- }
-
- if (customAnim) {
- mSurfaceRotationAnimationController.startCustomAnimation();
- } else {
- mSurfaceRotationAnimationController.startScreenRotationAnimation();
- }
-
- return true;
- }
-
- /**
- * Returns true if animating.
- */
- public boolean dismiss(SurfaceControl.Transaction t, long maxAnimationDuration,
- float animationScale, int finalWidth, int finalHeight, int exitAnim, int enterAnim) {
- if (mScreenshotLayer == null) {
- // Can't do animation.
- return false;
- }
- if (!mStarted) {
- mEndLuma = TransitionAnimation.getBorderLuma(mDisplayContent.getWindowingLayer(),
- finalWidth, finalHeight);
- startAnimation(t, maxAnimationDuration, animationScale, finalWidth, finalHeight,
- exitAnim, enterAnim);
- }
- if (!mStarted) {
- return false;
- }
- mFinishAnimReady = true;
- return true;
- }
-
- public void kill() {
- if (mSurfaceRotationAnimationController != null) {
- mSurfaceRotationAnimationController.cancel();
- mSurfaceRotationAnimationController = null;
- }
-
- if (mScreenshotLayer != null) {
- ProtoLog.i(WM_SHOW_SURFACE_ALLOC, " FREEZE %s: DESTROY", mScreenshotLayer);
- SurfaceControl.Transaction t = mService.mTransactionFactory.get();
- if (mScreenshotLayer.isValid()) {
- t.remove(mScreenshotLayer);
- }
- mScreenshotLayer = null;
-
- if (mEnterBlackFrameLayer != null) {
- if (mEnterBlackFrameLayer.isValid()) {
- t.remove(mEnterBlackFrameLayer);
- }
- mEnterBlackFrameLayer = null;
- }
- if (mBackColorSurface != null) {
- if (mBackColorSurface.isValid()) {
- t.remove(mBackColorSurface);
- }
- mBackColorSurface = null;
- }
-
- if (mRoundedCornerOverlay != null) {
- if (mDisplayContent.getRotationAnimation() == null
- || mDisplayContent.getRotationAnimation() == this) {
- setSkipScreenshotForRoundedCornerOverlays(true, t);
- for (SurfaceControl sc : mRoundedCornerOverlay) {
- if (sc.isValid()) {
- t.show(sc);
- }
- }
- }
- mRoundedCornerOverlay = null;
- }
- t.apply();
- }
-
- if (mEnteringBlackFrame != null) {
- mEnteringBlackFrame.kill();
- mEnteringBlackFrame = null;
- }
- if (mRotateExitAnimation != null) {
- mRotateExitAnimation.cancel();
- mRotateExitAnimation = null;
- }
- if (mRotateEnterAnimation != null) {
- mRotateEnterAnimation.cancel();
- mRotateEnterAnimation = null;
- }
- if (mRotateAlphaAnimation != null) {
- mRotateAlphaAnimation.cancel();
- mRotateAlphaAnimation = null;
- }
- }
-
- public boolean isAnimating() {
- return mSurfaceRotationAnimationController != null
- && mSurfaceRotationAnimationController.isAnimating();
- }
-
- public boolean isRotating() {
- return mCurRotation != mOriginalRotation;
- }
-
- /**
- * Utility class that runs a {@link ScreenRotationAnimation} on the {@link
- * SurfaceAnimationRunner}.
- * <p>
- * The rotation animation supports both screen rotation and custom animations
- *
- * For custom animations:
- * <ul>
- * <li>
- * The screenshot layer which has an added animation of it's alpha channel
- * ("screen_rotate_alpha") and that will be applied along with the custom animation.
- * </li>
- * <li> A device layer that is animated with the provided custom animation </li>
- * </ul>
- *
- * For screen rotation:
- * <ul>
- * <li> A rotation layer that is both rotated and faded out during a single animation </li>
- * <li> A device layer that is both rotated and faded in during a single animation </li>
- * <li> A background color layer that transitions colors behind the first two layers </li>
- * </ul>
- *
- * {@link ScreenRotationAnimation#startAnimation(
- * SurfaceControl.Transaction, long, float, int, int, int, int)}.
- * </ul>
- *
- * <p>
- * Thus an {@link LocalAnimationAdapter.AnimationSpec} is created for each of
- * this three {@link SurfaceControl}s which then delegates the animation to the
- * {@link ScreenRotationAnimation}.
- */
- class SurfaceRotationAnimationController {
- private SurfaceAnimator mDisplayAnimator;
- private SurfaceAnimator mScreenshotRotationAnimator;
- private SurfaceAnimator mRotateScreenAnimator;
- private SurfaceAnimator mEnterBlackFrameAnimator;
-
- void startCustomAnimation() {
- try {
- mService.mSurfaceAnimationRunner.deferStartingAnimations();
- mRotateScreenAnimator = startScreenshotAlphaAnimation();
- mDisplayAnimator = startDisplayRotation();
- if (mEnteringBlackFrame != null) {
- mEnterBlackFrameAnimator = startEnterBlackFrameAnimation();
- }
- } finally {
- mService.mSurfaceAnimationRunner.continueStartingAnimations();
- }
- }
-
- /**
- * Start the rotation animation of the display and the screenshot on the
- * {@link SurfaceAnimationRunner}.
- */
- void startScreenRotationAnimation() {
- try {
- mService.mSurfaceAnimationRunner.deferStartingAnimations();
- mDisplayAnimator = startDisplayRotation();
- mScreenshotRotationAnimator = startScreenshotRotationAnimation();
- startColorAnimation();
- } finally {
- mService.mSurfaceAnimationRunner.continueStartingAnimations();
- }
- }
-
- private SimpleSurfaceAnimatable.Builder initializeBuilder() {
- return new SimpleSurfaceAnimatable.Builder()
- .setSyncTransactionSupplier(mDisplayContent::getSyncTransaction)
- .setPendingTransactionSupplier(mDisplayContent::getPendingTransaction)
- .setCommitTransactionRunnable(mDisplayContent::commitPendingTransaction)
- .setAnimationLeashSupplier(mDisplayContent::makeOverlay);
- }
-
- private SurfaceAnimator startDisplayRotation() {
- SurfaceAnimator animator = startAnimation(initializeBuilder()
- .setAnimationLeashParent(mDisplayContent.getSurfaceControl())
- .setSurfaceControl(mDisplayContent.getWindowingLayer())
- .setParentSurfaceControl(mDisplayContent.getSurfaceControl())
- .setWidth(mDisplayContent.getSurfaceWidth())
- .setHeight(mDisplayContent.getSurfaceHeight())
- .build(),
- createWindowAnimationSpec(mRotateEnterAnimation),
- this::onAnimationEnd);
-
- // Crop the animation leash to avoid extended wallpaper from showing over
- // mBackColorSurface
- Rect displayBounds = mDisplayContent.getBounds();
- mDisplayContent.getPendingTransaction()
- .setWindowCrop(animator.mLeash, displayBounds.width(), displayBounds.height());
- return animator;
- }
-
- private SurfaceAnimator startScreenshotAlphaAnimation() {
- return startAnimation(initializeBuilder()
- .setSurfaceControl(mScreenshotLayer)
- .setAnimationLeashParent(mDisplayContent.getOverlayLayer())
- .setWidth(mDisplayContent.getSurfaceWidth())
- .setHeight(mDisplayContent.getSurfaceHeight())
- .build(),
- createWindowAnimationSpec(mRotateAlphaAnimation),
- this::onAnimationEnd);
- }
-
- private SurfaceAnimator startEnterBlackFrameAnimation() {
- return startAnimation(initializeBuilder()
- .setSurfaceControl(mEnterBlackFrameLayer)
- .setAnimationLeashParent(mDisplayContent.getOverlayLayer())
- .build(),
- createWindowAnimationSpec(mRotateEnterAnimation),
- this::onAnimationEnd);
- }
-
- private SurfaceAnimator startScreenshotRotationAnimation() {
- return startAnimation(initializeBuilder()
- .setSurfaceControl(mScreenshotLayer)
- .setAnimationLeashParent(mDisplayContent.getOverlayLayer())
- .build(),
- createWindowAnimationSpec(mRotateExitAnimation),
- this::onAnimationEnd);
- }
-
-
- /**
- * Applies the color change from {@link #mStartLuma} to {@link #mEndLuma} as a
- * grayscale color
- */
- private void startColorAnimation() {
- int colorTransitionMs = mContext.getResources().getInteger(
- R.integer.config_screen_rotation_color_transition);
- final SurfaceAnimationRunner runner = mService.mSurfaceAnimationRunner;
- final float[] rgbTmpFloat = new float[3];
- final int startColor = Color.rgb(mStartLuma, mStartLuma, mStartLuma);
- final int endColor = Color.rgb(mEndLuma, mEndLuma, mEndLuma);
- final long duration = colorTransitionMs * (long) mService.getCurrentAnimatorScale();
- final ArgbEvaluator va = ArgbEvaluator.getInstance();
- runner.startAnimation(
- new LocalAnimationAdapter.AnimationSpec() {
- @Override
- public long getDuration() {
- return duration;
- }
-
- @Override
- public void apply(SurfaceControl.Transaction t, SurfaceControl leash,
- long currentPlayTime) {
- final float fraction = getFraction(currentPlayTime);
- final int color = (Integer) va.evaluate(fraction, startColor, endColor);
- Color middleColor = Color.valueOf(color);
- rgbTmpFloat[0] = middleColor.red();
- rgbTmpFloat[1] = middleColor.green();
- rgbTmpFloat[2] = middleColor.blue();
- if (leash.isValid()) {
- t.setColor(leash, rgbTmpFloat);
- }
- }
-
- @Override
- public void dump(PrintWriter pw, String prefix) {
- pw.println(prefix + "startLuma=" + mStartLuma
- + " endLuma=" + mEndLuma
- + " durationMs=" + colorTransitionMs);
- }
-
- @Override
- public void dumpDebugInner(ProtoOutputStream proto) {
- final long token = proto.start(ROTATE);
- proto.write(START_LUMA, mStartLuma);
- proto.write(END_LUMA, mEndLuma);
- proto.write(DURATION_MS, colorTransitionMs);
- proto.end(token);
- }
- },
- mBackColorSurface, mDisplayContent.getPendingTransaction(), null);
- }
-
- private WindowAnimationSpec createWindowAnimationSpec(Animation mAnimation) {
- return new WindowAnimationSpec(mAnimation, new Point(0, 0) /* position */,
- false /* canSkipFirstFrame */, 0 /* WindowCornerRadius */);
- }
-
- /**
- * Start an animation defined by animationSpec on a new {@link SurfaceAnimator}.
- *
- * @param animatable The animatable used for the animation.
- * @param animationSpec The spec of the animation.
- * @param animationFinishedCallback Callback passed to the {@link SurfaceAnimator}
- * and called when the animation finishes.
- * @return The newly created {@link SurfaceAnimator} that as been started.
- */
- private SurfaceAnimator startAnimation(
- SurfaceAnimator.Animatable animatable,
- LocalAnimationAdapter.AnimationSpec animationSpec,
- OnAnimationFinishedCallback animationFinishedCallback) {
- SurfaceAnimator animator = new SurfaceAnimator(
- animatable, animationFinishedCallback, mService);
-
- LocalAnimationAdapter localAnimationAdapter = new LocalAnimationAdapter(
- animationSpec, mService.mSurfaceAnimationRunner);
- animator.startAnimation(mDisplayContent.getPendingTransaction(),
- localAnimationAdapter, false, ANIMATION_TYPE_SCREEN_ROTATION);
- return animator;
- }
-
- private void onAnimationEnd(@AnimationType int type, AnimationAdapter anim) {
- synchronized (mService.mGlobalLock) {
- if (isAnimating()) {
- ProtoLog.v(WM_DEBUG_ORIENTATION,
- "ScreenRotation still animating: type: %d\n"
- + "mDisplayAnimator: %s\n"
- + "mEnterBlackFrameAnimator: %s\n"
- + "mRotateScreenAnimator: %s\n"
- + "mScreenshotRotationAnimator: %s",
- type,
- mDisplayAnimator != null
- ? mDisplayAnimator.isAnimating() : null,
- mEnterBlackFrameAnimator != null
- ? mEnterBlackFrameAnimator.isAnimating() : null,
- mRotateScreenAnimator != null
- ? mRotateScreenAnimator.isAnimating() : null,
- mScreenshotRotationAnimator != null
- ? mScreenshotRotationAnimator.isAnimating() : null
- );
- return;
- }
- ProtoLog.d(WM_DEBUG_ORIENTATION, "ScreenRotationAnimation onAnimationEnd");
- mEnterBlackFrameAnimator = null;
- mScreenshotRotationAnimator = null;
- mRotateScreenAnimator = null;
- mService.mAnimator.mBulkUpdateParams |= WindowSurfacePlacer.SET_UPDATE_ROTATION;
- if (mDisplayContent.getRotationAnimation() == ScreenRotationAnimation.this) {
- // It also invokes kill().
- mDisplayContent.setRotationAnimation(null);
- mDisplayContent.mAppCompatCameraPolicy.onScreenRotationAnimationFinished();
- } else {
- kill();
- }
- mService.updateRotation(false, false);
- }
- }
-
- public void cancel() {
- if (mEnterBlackFrameAnimator != null) {
- mEnterBlackFrameAnimator.cancelAnimation();
- }
- if (mScreenshotRotationAnimator != null) {
- mScreenshotRotationAnimator.cancelAnimation();
- }
-
- if (mRotateScreenAnimator != null) {
- mRotateScreenAnimator.cancelAnimation();
- }
-
- if (mDisplayAnimator != null) {
- mDisplayAnimator.cancelAnimation();
- }
-
- if (mBackColorSurface != null) {
- mService.mSurfaceAnimationRunner.onAnimationCancelled(mBackColorSurface);
- }
- }
-
- public boolean isAnimating() {
- return mDisplayAnimator != null && mDisplayAnimator.isAnimating()
- || mEnterBlackFrameAnimator != null && mEnterBlackFrameAnimator.isAnimating()
- || mRotateScreenAnimator != null && mRotateScreenAnimator.isAnimating()
- || mScreenshotRotationAnimator != null
- && mScreenshotRotationAnimator.isAnimating();
- }
- }
-}
diff --git a/services/core/java/com/android/server/wm/SimpleSurfaceAnimatable.java b/services/core/java/com/android/server/wm/SimpleSurfaceAnimatable.java
deleted file mode 100644
index 3b3db89..0000000
--- a/services/core/java/com/android/server/wm/SimpleSurfaceAnimatable.java
+++ /dev/null
@@ -1,330 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wm;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.view.SurfaceControl;
-
-import java.util.function.BiConsumer;
-import java.util.function.Consumer;
-import java.util.function.Supplier;
-
-/**
- * An implementation of {@link SurfaceAnimator.Animatable} that is instantiated
- * using a builder pattern for more convenience over reimplementing the whole interface.
- * <p>
- * Use {@link SimpleSurfaceAnimatable.Builder} to create a new instance of this class.
- *
- * @see com.android.server.wm.SurfaceAnimator.Animatable
- */
-public class SimpleSurfaceAnimatable implements SurfaceAnimator.Animatable {
- private final int mWidth;
- private final int mHeight;
- private final boolean mShouldDeferAnimationFinish;
- private final SurfaceControl mAnimationLeashParent;
- private final SurfaceControl mSurfaceControl;
- private final SurfaceControl mParentSurfaceControl;
- private final Runnable mCommitTransactionRunnable;
- private final Supplier<SurfaceControl.Builder> mAnimationLeashFactory;
- private final Supplier<SurfaceControl.Transaction> mSyncTransaction;
- private final Supplier<SurfaceControl.Transaction> mPendingTransaction;
- private final BiConsumer<SurfaceControl.Transaction, SurfaceControl> mOnAnimationLeashCreated;
- private final Consumer<SurfaceControl.Transaction> mOnAnimationLeashLost;
- private final Consumer<Runnable> mOnAnimationFinished;
-
- /**
- * Use {@link SimpleSurfaceAnimatable.Builder} to create a new instance.
- */
- private SimpleSurfaceAnimatable(Builder builder) {
- mWidth = builder.mWidth;
- mHeight = builder.mHeight;
- mShouldDeferAnimationFinish = builder.mShouldDeferAnimationFinish;
- mAnimationLeashParent = builder.mAnimationLeashParent;
- mSurfaceControl = builder.mSurfaceControl;
- mParentSurfaceControl = builder.mParentSurfaceControl;
- mCommitTransactionRunnable = builder.mCommitTransactionRunnable;
- mAnimationLeashFactory = builder.mAnimationLeashFactory;
- mOnAnimationLeashCreated = builder.mOnAnimationLeashCreated;
- mOnAnimationLeashLost = builder.mOnAnimationLeashLost;
- mSyncTransaction = builder.mSyncTransactionSupplier;
- mPendingTransaction = builder.mPendingTransactionSupplier;
- mOnAnimationFinished = builder.mOnAnimationFinished;
- }
-
- @Override
- public SurfaceControl.Transaction getSyncTransaction() {
- return mSyncTransaction.get();
- }
-
- @NonNull
- @Override
- public SurfaceControl.Transaction getPendingTransaction() {
- return mPendingTransaction.get();
- }
-
- @Override
- public void commitPendingTransaction() {
- mCommitTransactionRunnable.run();
- }
-
- @Override
- public void onAnimationLeashCreated(SurfaceControl.Transaction t, SurfaceControl leash) {
- if (mOnAnimationLeashCreated != null) {
- mOnAnimationLeashCreated.accept(t, leash);
- }
-
- }
-
- @Override
- public void onAnimationLeashLost(SurfaceControl.Transaction t) {
- if (mOnAnimationLeashLost != null) {
- mOnAnimationLeashLost.accept(t);
- }
- }
-
- @Override
- @NonNull
- public SurfaceControl.Builder makeAnimationLeash() {
- return mAnimationLeashFactory.get();
- }
-
- @Override
- public SurfaceControl getAnimationLeashParent() {
- return mAnimationLeashParent;
- }
-
- @Override
- @Nullable
- public SurfaceControl getSurfaceControl() {
- return mSurfaceControl;
- }
-
- @Override
- public SurfaceControl getParentSurfaceControl() {
- return mParentSurfaceControl;
- }
-
- @Override
- public int getSurfaceWidth() {
- return mWidth;
- }
-
- @Override
- public int getSurfaceHeight() {
- return mHeight;
- }
-
- @Override
- public boolean shouldDeferAnimationFinish(Runnable endDeferFinishCallback) {
- if (mOnAnimationFinished != null) {
- mOnAnimationFinished.accept(endDeferFinishCallback);
- }
- return mShouldDeferAnimationFinish;
- }
-
- /**
- * Builder class to create a {@link SurfaceAnimator.Animatable} without having to
- * create a new class that implements the interface.
- */
- static class Builder {
- private int mWidth = -1;
- private int mHeight = -1;
- private boolean mShouldDeferAnimationFinish = false;
-
- @Nullable
- private SurfaceControl mAnimationLeashParent = null;
-
- @Nullable
- private SurfaceControl mSurfaceControl = null;
-
- @Nullable
- private SurfaceControl mParentSurfaceControl = null;
- private Runnable mCommitTransactionRunnable;
-
- @Nullable
- private BiConsumer<SurfaceControl.Transaction, SurfaceControl> mOnAnimationLeashCreated =
- null;
-
- @Nullable
- private Consumer<SurfaceControl.Transaction> mOnAnimationLeashLost = null;
-
- @Nullable
- private Consumer<Runnable> mOnAnimationFinished = null;
-
- @NonNull
- private Supplier<SurfaceControl.Transaction> mSyncTransactionSupplier;
-
- @NonNull
- private Supplier<SurfaceControl.Transaction> mPendingTransactionSupplier;
-
- @NonNull
- private Supplier<SurfaceControl.Builder> mAnimationLeashFactory;
-
- /**
- * Set the runnable to be called when
- * {@link SurfaceAnimator.Animatable#commitPendingTransaction()}
- * is called.
- *
- * @see SurfaceAnimator.Animatable#commitPendingTransaction()
- */
- public SimpleSurfaceAnimatable.Builder setCommitTransactionRunnable(
- @NonNull Runnable commitTransactionRunnable) {
- mCommitTransactionRunnable = commitTransactionRunnable;
- return this;
- }
-
- /**
- * Set the callback called when
- * {@link SurfaceAnimator.Animatable#onAnimationLeashCreated(SurfaceControl.Transaction,
- * SurfaceControl)} is called
- *
- * @see SurfaceAnimator.Animatable#onAnimationLeashCreated(SurfaceControl.Transaction,
- * SurfaceControl)
- */
- public SimpleSurfaceAnimatable.Builder setOnAnimationLeashCreated(
- @Nullable BiConsumer<SurfaceControl.Transaction, SurfaceControl>
- onAnimationLeashCreated) {
- mOnAnimationLeashCreated = onAnimationLeashCreated;
- return this;
- }
-
- /**
- * Set the callback called when
- * {@link SurfaceAnimator.Animatable#onAnimationLeashLost(SurfaceControl.Transaction)}
- * (SurfaceControl.Transaction, SurfaceControl)} is called
- *
- * @see SurfaceAnimator.Animatable#onAnimationLeashLost(SurfaceControl.Transaction)
- */
- public SimpleSurfaceAnimatable.Builder setOnAnimationLeashLost(
- @Nullable Consumer<SurfaceControl.Transaction> onAnimationLeashLost) {
- mOnAnimationLeashLost = onAnimationLeashLost;
- return this;
- }
-
- /**
- * @see SurfaceAnimator.Animatable#getSyncTransaction()
- */
- public Builder setSyncTransactionSupplier(
- @NonNull Supplier<SurfaceControl.Transaction> syncTransactionSupplier) {
- mSyncTransactionSupplier = syncTransactionSupplier;
- return this;
- }
-
- /**
- * @see SurfaceAnimator.Animatable#getPendingTransaction()
- */
- public Builder setPendingTransactionSupplier(
- @NonNull Supplier<SurfaceControl.Transaction> pendingTransactionSupplier) {
- mPendingTransactionSupplier = pendingTransactionSupplier;
- return this;
- }
-
- /**
- * Set the {@link Supplier} responsible for creating a new animation leash.
- *
- * @see SurfaceAnimator.Animatable#makeAnimationLeash()
- */
- public SimpleSurfaceAnimatable.Builder setAnimationLeashSupplier(
- @NonNull Supplier<SurfaceControl.Builder> animationLeashFactory) {
- mAnimationLeashFactory = animationLeashFactory;
- return this;
- }
-
- /**
- * @see SurfaceAnimator.Animatable#getAnimationLeashParent()
- */
- public SimpleSurfaceAnimatable.Builder setAnimationLeashParent(
- SurfaceControl animationLeashParent) {
- mAnimationLeashParent = animationLeashParent;
- return this;
- }
-
- /**
- * @see SurfaceAnimator.Animatable#getSurfaceControl()
- */
- public SimpleSurfaceAnimatable.Builder setSurfaceControl(
- @NonNull SurfaceControl surfaceControl) {
- mSurfaceControl = surfaceControl;
- return this;
- }
-
- /**
- * @see SurfaceAnimator.Animatable#getParentSurfaceControl()
- */
- public SimpleSurfaceAnimatable.Builder setParentSurfaceControl(
- SurfaceControl parentSurfaceControl) {
- mParentSurfaceControl = parentSurfaceControl;
- return this;
- }
-
- /**
- * Default to -1.
- *
- * @see SurfaceAnimator.Animatable#getSurfaceWidth()
- */
- public SimpleSurfaceAnimatable.Builder setWidth(int width) {
- mWidth = width;
- return this;
- }
-
- /**
- * Default to -1.
- *
- * @see SurfaceAnimator.Animatable#getSurfaceHeight()
- */
- public SimpleSurfaceAnimatable.Builder setHeight(int height) {
- mHeight = height;
- return this;
- }
-
- /**
- * Set the value returned by
- * {@link SurfaceAnimator.Animatable#shouldDeferAnimationFinish(Runnable)}.
- *
- * @param onAnimationFinish will be called with the runnable to execute when the animation
- * needs to be finished.
- * @see SurfaceAnimator.Animatable#shouldDeferAnimationFinish(Runnable)
- */
- public SimpleSurfaceAnimatable.Builder setShouldDeferAnimationFinish(
- boolean shouldDeferAnimationFinish,
- @Nullable Consumer<Runnable> onAnimationFinish) {
- mShouldDeferAnimationFinish = shouldDeferAnimationFinish;
- mOnAnimationFinished = onAnimationFinish;
- return this;
- }
-
- public SurfaceAnimator.Animatable build() {
- if (mSyncTransactionSupplier == null) {
- throw new IllegalArgumentException("mSyncTransactionSupplier cannot be null");
- }
- if (mPendingTransactionSupplier == null) {
- throw new IllegalArgumentException("mPendingTransactionSupplier cannot be null");
- }
- if (mAnimationLeashFactory == null) {
- throw new IllegalArgumentException("mAnimationLeashFactory cannot be null");
- }
- if (mCommitTransactionRunnable == null) {
- throw new IllegalArgumentException("mCommitTransactionRunnable cannot be null");
- }
- if (mSurfaceControl == null) {
- throw new IllegalArgumentException("mSurfaceControl cannot be null");
- }
- return new SimpleSurfaceAnimatable(this);
- }
- }
-}
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 1993053..fc7437b 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -2092,12 +2092,6 @@
ProtoLog.v(WM_DEBUG_STATES, "App died during pause, not stopping: %s", prev);
prev = null;
}
- // It is possible the activity was freezing the screen before it was paused.
- // In that case go ahead and remove the freeze this activity has on the screen
- // since it is no longer visible.
- if (prev != null) {
- prev.stopFreezingScreen(true /* unfreezeNow */, true /* force */);
- }
}
if (resumeNext) {
diff --git a/services/core/java/com/android/server/wm/WindowAnimator.java b/services/core/java/com/android/server/wm/WindowAnimator.java
index 790ae1e..80137a2 100644
--- a/services/core/java/com/android/server/wm/WindowAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowAnimator.java
@@ -146,7 +146,6 @@
boolean rootAnimating = false;
mCurrentTime = frameTimeNs / TimeUtils.NANOS_PER_MS;
mBulkUpdateParams = 0;
- root.mOrientationChangeComplete = true;
if (DEBUG_WINDOW_TRACE) {
Slog.i(TAG, "!!! animate: entry time=" + mCurrentTime);
}
@@ -203,8 +202,7 @@
}
final boolean hasPendingLayoutChanges = root.hasPendingLayoutChanges(this);
- final boolean doRequest = (mBulkUpdateParams != 0 || root.mOrientationChangeComplete)
- && root.copyAnimToLayoutParams();
+ final boolean doRequest = mBulkUpdateParams != 0 && root.copyAnimToLayoutParams();
if (hasPendingLayoutChanges || doRequest) {
mService.mWindowPlacerLocked.requestTraversal();
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 1754d73..0e9b423 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -31,7 +31,6 @@
import static android.app.ActivityManagerInternal.ALLOW_NON_FULL;
import static android.app.AppOpsManager.OP_SYSTEM_ALERT_WINDOW;
import static android.app.StatusBarManager.DISABLE_MASK;
-import static android.app.WindowConfiguration.ROTATION_UNDEFINED;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED;
import static android.content.pm.PackageManager.FEATURE_FREEFORM_WINDOW_MANAGEMENT;
@@ -116,13 +115,11 @@
import static com.android.internal.protolog.WmProtoLogGroups.WM_ERROR;
import static com.android.internal.protolog.WmProtoLogGroups.WM_SHOW_TRANSACTIONS;
import static com.android.internal.util.FrameworkStatsLog.SENSITIVE_NOTIFICATION_APP_PROTECTION_APPLIED;
-import static com.android.internal.util.LatencyTracker.ACTION_ROTATE_SCREEN;
import static com.android.server.LockGuard.INDEX_WINDOW;
import static com.android.server.LockGuard.installLock;
import static com.android.server.policy.PhoneWindowManager.TRACE_WAIT_FOR_ALL_WINDOWS_DRAWN_METHOD;
import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
import static com.android.server.wm.ActivityTaskManagerService.DEMOTE_TOP_REASON_EXPANDED_NOTIFICATION_SHADE;
-import static com.android.server.wm.ActivityTaskManagerService.POWER_MODE_REASON_CHANGE_DISPLAY;
import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND;
import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING;
import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_BACKGROUND_SOLID_COLOR;
@@ -151,7 +148,6 @@
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
import static com.android.server.wm.WindowManagerInternal.OnWindowRemovedListener;
import static com.android.server.wm.WindowManagerServiceDumpProto.BACK_NAVIGATION;
-import static com.android.server.wm.WindowManagerServiceDumpProto.DISPLAY_FROZEN;
import static com.android.server.wm.WindowManagerServiceDumpProto.FOCUSED_APP;
import static com.android.server.wm.WindowManagerServiceDumpProto.FOCUSED_DISPLAY_ID;
import static com.android.server.wm.WindowManagerServiceDumpProto.FOCUSED_WINDOW;
@@ -251,7 +247,6 @@
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseIntArray;
-import android.util.TimeUtils;
import android.util.TypedValue;
import android.util.proto.ProtoOutputStream;
import android.view.Choreographer;
@@ -403,8 +398,6 @@
static final int LAYOUT_REPEAT_THRESHOLD = 4;
- static final boolean PROFILE_ORIENTATION = false;
-
/** The maximum length we will accept for a loaded animation duration:
* this is 10 seconds.
*/
@@ -742,19 +735,8 @@
@Nullable
private Runnable mPointerDownOutsideFocusRunnable;
- boolean mDisplayFrozen = false;
- long mDisplayFreezeTime = 0;
- int mLastDisplayFreezeDuration = 0;
- Object mLastFinishedFreezeSource = null;
boolean mSwitchingUser = false;
- final static int WINDOWS_FREEZING_SCREENS_NONE = 0;
- final static int WINDOWS_FREEZING_SCREENS_ACTIVE = 1;
- final static int WINDOWS_FREEZING_SCREENS_TIMEOUT = 2;
- int mWindowsFreezingScreen = WINDOWS_FREEZING_SCREENS_NONE;
-
- int mAppsFreezingScreen = 0;
-
@VisibleForTesting
boolean mPerDisplayFocusEnabled;
@@ -764,11 +746,6 @@
// Number of windows whose insets state have been changed.
int mWindowsInsetsChanged = 0;
- // This is held as long as we have the screen frozen, to give us time to
- // perform a rotation animation when turning off shows the lock screen which
- // changes the orientation.
- private final PowerManager.WakeLock mScreenFrozenLock;
-
final TaskSnapshotController mTaskSnapshotController;
final SnapshotController mSnapshotController;
@@ -1053,12 +1030,6 @@
final DragDropController mDragDropController;
- /** For frozen screen animations. */
- private int mExitAnimId, mEnterAnimId;
-
- /** The display that the rotation animation is applying to. */
- private int mFrozenDisplayId = INVALID_DISPLAY;
-
/** Skip repeated ActivityRecords initialization. Note that AppWindowsToken's version of this
* is a long initialized to Long.MIN_VALUE so that it doesn't match this value on startup. */
int mTransactionSequence;
@@ -1161,12 +1132,6 @@
}
};
- final ArrayList<AppFreezeListener> mAppFreezeListeners = new ArrayList<>();
-
- interface AppFreezeListener {
- void onAppFreezeTimeout();
- }
-
private final ScreenRecordingCallbackController mScreenRecordingCallbackController;
private volatile boolean mDisableSecureWindows = false;
@@ -1347,9 +1312,6 @@
mAnimationsDisabled = mPowerManagerInternal
.getLowPowerState(ServiceType.ANIMATION).batterySaverEnabled;
}
- mScreenFrozenLock = mPowerManager.newWakeLock(
- PowerManager.PARTIAL_WAKE_LOCK, "SCREEN_FROZEN");
- mScreenFrozenLock.setReferenceCounted(false);
mRotationWatcherController = new RotationWatcherController(this);
mDisplayNotificationController = new DisplayWindowListenerController(this);
@@ -5601,11 +5563,8 @@
// -------------------------------------------------------------
final class H extends android.os.Handler {
- public static final int WINDOW_FREEZE_TIMEOUT = 11;
-
public static final int PERSIST_ANIMATION_SCALE = 14;
public static final int ENABLE_SCREEN = 16;
- public static final int APP_FREEZE_TIMEOUT = 17;
public static final int REPORT_WINDOWS_CHANGE = 19;
public static final int REPORT_HARD_KEYBOARD_STATUS_CHANGE = 22;
@@ -5645,14 +5604,6 @@
Slog.v(TAG_WM, "handleMessage: entry what=" + msg.what);
}
switch (msg.what) {
- case WINDOW_FREEZE_TIMEOUT: {
- final DisplayContent displayContent = (DisplayContent) msg.obj;
- synchronized (mGlobalLock) {
- displayContent.onWindowFreezeTimeout();
- }
- break;
- }
-
case PERSIST_ANIMATION_SCALE: {
Settings.Global.putFloat(mContext.getContentResolver(),
Settings.Global.WINDOW_ANIMATION_SCALE, mWindowAnimationScaleSetting);
@@ -5691,17 +5642,6 @@
break;
}
- case APP_FREEZE_TIMEOUT: {
- synchronized (mGlobalLock) {
- ProtoLog.w(WM_ERROR, "App freeze timeout expired.");
- mWindowsFreezingScreen = WINDOWS_FREEZING_SCREENS_TIMEOUT;
- for (int i = mAppFreezeListeners.size() - 1; i >= 0; --i) {
- mAppFreezeListeners.get(i).onAppFreezeTimeout();
- }
- }
- break;
- }
-
case REPORT_WINDOWS_CHANGE: {
if (mWindowsChanged) {
synchronized (mGlobalLock) {
@@ -6310,22 +6250,6 @@
return win;
}
- void makeWindowFreezingScreenIfNeededLocked(WindowState w) {
- // If the screen is currently frozen, then keep it frozen until this window draws at its
- // new orientation.
- if (mFrozenDisplayId != INVALID_DISPLAY && mFrozenDisplayId == w.getDisplayId()
- && mWindowsFreezingScreen != WINDOWS_FREEZING_SCREENS_TIMEOUT) {
- ProtoLog.v(WM_DEBUG_ORIENTATION, "Changing surface while display frozen: %s", w);
- if (mWindowsFreezingScreen == WINDOWS_FREEZING_SCREENS_NONE) {
- mWindowsFreezingScreen = WINDOWS_FREEZING_SCREENS_ACTIVE;
- // XXX should probably keep timeout from
- // when we first froze the display.
- mH.sendNewMessageDelayed(H.WINDOW_FREEZE_TIMEOUT, w.getDisplayContent(),
- WINDOW_FREEZE_TIMEOUT_DURATION);
- }
- }
- }
-
void checkDrawnWindowsLocked() {
if (mWaitingForDrawnCallbacks.isEmpty()) {
return;
@@ -6394,188 +6318,6 @@
return changed;
}
- void startFreezingDisplay(int exitAnim, int enterAnim) {
- startFreezingDisplay(exitAnim, enterAnim, getDefaultDisplayContentLocked());
- }
-
- void startFreezingDisplay(int exitAnim, int enterAnim, DisplayContent displayContent) {
- startFreezingDisplay(exitAnim, enterAnim, displayContent,
- ROTATION_UNDEFINED /* overrideOriginalRotation */);
- }
-
- void startFreezingDisplay(int exitAnim, int enterAnim, DisplayContent displayContent,
- int overrideOriginalRotation) {
- if (mDisplayFrozen || displayContent.getDisplayRotation().isRotatingSeamlessly()) {
- return;
- }
-
- if (!displayContent.isReady() || !displayContent.getDisplayPolicy().isScreenOnFully()
- || displayContent.getDisplayInfo().state == Display.STATE_OFF
- || !displayContent.okToAnimate()) {
- // No need to freeze the screen before the display is ready, if the screen is off,
- // or we can't currently animate.
- return;
- }
-
- displayContent.requestDisplayUpdate(() -> {
- Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "WMS.doStartFreezingDisplay");
- doStartFreezingDisplay(exitAnim, enterAnim, displayContent, overrideOriginalRotation);
- Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
- });
- }
-
- private void doStartFreezingDisplay(int exitAnim, int enterAnim, DisplayContent displayContent,
- int overrideOriginalRotation) {
- ProtoLog.d(WM_DEBUG_ORIENTATION,
- "startFreezingDisplayLocked: exitAnim=%d enterAnim=%d called by %s",
- exitAnim, enterAnim, Debug.getCallers(8));
- mScreenFrozenLock.acquire();
- // Apply launch power mode to reduce screen frozen time because orientation change may
- // relaunch activity and redraw windows. This may also help speed up user switching.
- mAtmService.startPowerMode(POWER_MODE_REASON_CHANGE_DISPLAY);
-
- mDisplayFrozen = true;
- mDisplayFreezeTime = SystemClock.elapsedRealtime();
- mLastFinishedFreezeSource = null;
-
- // {@link mDisplayFrozen} prevents us from freezing on multiple displays at the same time.
- // As a result, we only track the display that has initially froze the screen.
- mFrozenDisplayId = displayContent.getDisplayId();
-
- mInputManagerCallback.freezeInputDispatchingLw();
-
- if (displayContent.mAppTransition.isTransitionSet()) {
- displayContent.mAppTransition.freeze();
- }
-
- if (PROFILE_ORIENTATION) {
- File file = new File("/data/system/frozen");
- Debug.startMethodTracing(file.toString(), 8 * 1024 * 1024);
- }
-
- mLatencyTracker.onActionStart(ACTION_ROTATE_SCREEN);
- mExitAnimId = exitAnim;
- mEnterAnimId = enterAnim;
-
- final int originalRotation = overrideOriginalRotation != ROTATION_UNDEFINED
- ? overrideOriginalRotation
- : displayContent.getDisplayInfo().rotation;
- displayContent.setRotationAnimation(new ScreenRotationAnimation(displayContent,
- originalRotation));
- }
-
- void stopFreezingDisplayLocked() {
- if (!mDisplayFrozen) {
- return;
- }
-
- final DisplayContent displayContent = mRoot.getDisplayContent(mFrozenDisplayId);
- final int numOpeningApps;
- final boolean waitingForConfig;
- final boolean waitingForRemoteDisplayChange;
- if (displayContent != null) {
- numOpeningApps = displayContent.mOpeningApps.size();
- waitingForConfig = displayContent.mWaitingForConfig;
- waitingForRemoteDisplayChange = displayContent.mRemoteDisplayChangeController
- .isWaitingForRemoteDisplayChange();
- } else {
- waitingForConfig = waitingForRemoteDisplayChange = false;
- numOpeningApps = 0;
- }
- if (waitingForConfig || waitingForRemoteDisplayChange || mAppsFreezingScreen > 0
- || mWindowsFreezingScreen == WINDOWS_FREEZING_SCREENS_ACTIVE
- || numOpeningApps > 0) {
- ProtoLog.d(WM_DEBUG_ORIENTATION, "stopFreezingDisplayLocked: Returning "
- + "waitingForConfig=%b, waitingForRemoteDisplayChange=%b, "
- + "mAppsFreezingScreen=%d, mWindowsFreezingScreen=%d, "
- + "mOpeningApps.size()=%d",
- waitingForConfig, waitingForRemoteDisplayChange,
- mAppsFreezingScreen, mWindowsFreezingScreen,
- numOpeningApps);
- return;
- }
-
- Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "WMS.doStopFreezingDisplayLocked-"
- + mLastFinishedFreezeSource);
- doStopFreezingDisplayLocked(displayContent);
- Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
- }
-
- private void doStopFreezingDisplayLocked(DisplayContent displayContent) {
- ProtoLog.d(WM_DEBUG_ORIENTATION,
- "stopFreezingDisplayLocked: Unfreezing now");
-
- // We must make a local copy of the displayId as it can be potentially overwritten later on
- // in this method. For example, {@link startFreezingDisplayLocked} may be called as a result
- // of update rotation, but we reference the frozen display after that call in this method.
- mFrozenDisplayId = INVALID_DISPLAY;
- mDisplayFrozen = false;
- mInputManagerCallback.thawInputDispatchingLw();
- mLastDisplayFreezeDuration = (int)(SystemClock.elapsedRealtime() - mDisplayFreezeTime);
- StringBuilder sb = new StringBuilder(128);
- sb.append("Screen frozen for ");
- TimeUtils.formatDuration(mLastDisplayFreezeDuration, sb);
- if (mLastFinishedFreezeSource != null) {
- sb.append(" due to ");
- sb.append(mLastFinishedFreezeSource);
- }
- ProtoLog.i(WM_ERROR, "%s", sb.toString());
- mH.removeMessages(H.APP_FREEZE_TIMEOUT);
- if (PROFILE_ORIENTATION) {
- Debug.stopMethodTracing();
- }
-
- boolean updateRotation = false;
-
- ScreenRotationAnimation screenRotationAnimation = displayContent == null ? null
- : displayContent.getRotationAnimation();
- if (screenRotationAnimation != null && screenRotationAnimation.hasScreenshot()) {
- ProtoLog.i(WM_DEBUG_ORIENTATION, "**** Dismissing screen rotation animation");
- DisplayInfo displayInfo = displayContent.getDisplayInfo();
- // Get rotation animation again, with new top window
- if (!displayContent.getDisplayRotation().validateRotationAnimation(
- mExitAnimId, mEnterAnimId, false /* forceDefault */)) {
- mExitAnimId = mEnterAnimId = 0;
- }
- if (screenRotationAnimation.dismiss(mTransaction, MAX_ANIMATION_DURATION,
- getTransitionAnimationScaleLocked(), displayInfo.logicalWidth,
- displayInfo.logicalHeight, mExitAnimId, mEnterAnimId)) {
- mTransaction.apply();
- } else {
- screenRotationAnimation.kill();
- displayContent.setRotationAnimation(null);
- updateRotation = true;
- }
- } else {
- if (screenRotationAnimation != null) {
- screenRotationAnimation.kill();
- displayContent.setRotationAnimation(null);
- }
- updateRotation = true;
- }
-
- boolean configChanged;
-
- // While the display is frozen we don't re-compute the orientation
- // to avoid inconsistent states. However, something interesting
- // could have actually changed during that time so re-evaluate it
- // now to catch that.
- configChanged = displayContent != null && displayContent.updateOrientation();
-
- mScreenFrozenLock.release();
-
- if (updateRotation && displayContent != null) {
- ProtoLog.d(WM_DEBUG_ORIENTATION, "Performing post-rotate rotation");
- configChanged |= displayContent.updateRotationUnchecked();
- }
-
- if (configChanged) {
- displayContent.sendNewConfiguration();
- }
- mAtmService.endPowerMode(POWER_MODE_REASON_CHANGE_DISPLAY);
- mLatencyTracker.onActionEnd(ACTION_ROTATE_SCREEN);
- }
-
static int getPropertyInt(String[] tokens, int index, int defUnits, int defDps,
DisplayMetrics dm) {
if (index < tokens.length) {
@@ -6876,7 +6618,6 @@
if (imeWindow != null) {
imeWindow.writeIdentifierToProto(proto, INPUT_METHOD_WINDOW);
}
- proto.write(DISPLAY_FROZEN, mDisplayFrozen);
proto.write(FOCUSED_DISPLAY_ID, topFocusedDisplayContent.getDisplayId());
proto.write(HARD_KEYBOARD_AVAILABLE, mHardKeyboardAvailable);
@@ -6999,13 +6740,6 @@
pw.print(' '); pw.print(dc.mMinSizeOfResizeableTaskDp);
});
pw.print(" mBlurEnabled="); pw.println(mBlurController.getBlurEnabled());
- pw.print(" mLastDisplayFreezeDuration=");
- TimeUtils.formatDuration(mLastDisplayFreezeDuration, pw);
- if ( mLastFinishedFreezeSource != null) {
- pw.print(" due to ");
- pw.print(mLastFinishedFreezeSource);
- }
- pw.println();
pw.print(" mDisableSecureWindows="); pw.println(mDisableSecureWindows);
mInputManagerCallback.dump(pw, " ");
@@ -7025,9 +6759,6 @@
mRoot.dumpLayoutNeededDisplayIds(pw);
pw.print(" mTransactionSequence="); pw.println(mTransactionSequence);
- pw.print(" mDisplayFrozen="); pw.print(mDisplayFrozen);
- pw.print(" windows="); pw.print(mWindowsFreezingScreen);
- pw.print(" apps="); pw.println(mAppsFreezingScreen);
final DisplayContent defaultDisplayContent = getDefaultDisplayContentLocked();
pw.print(" mRotation="); pw.println(defaultDisplayContent.getRotation());
pw.print(" mLastOrientation=");
@@ -8905,16 +8636,6 @@
}
}
- void registerAppFreezeListener(AppFreezeListener listener) {
- if (!mAppFreezeListeners.contains(listener)) {
- mAppFreezeListeners.add(listener);
- }
- }
-
- void unregisterAppFreezeListener(AppFreezeListener listener) {
- mAppFreezeListeners.remove(listener);
- }
-
/** Called to inform window manager if non-Vr UI shoul be disabled or not. */
public void disableNonVrUi(boolean disable) {
synchronized (mGlobalLock) {
diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index 26bc09f..30dde54 100644
--- a/services/core/java/com/android/server/wm/WindowProcessController.java
+++ b/services/core/java/com/android/server/wm/WindowProcessController.java
@@ -969,14 +969,8 @@
return canUpdate;
}
+ // TODO(365884835): remove this method with external callers.
public void stopFreezingActivities() {
- synchronized (mAtm.mGlobalLock) {
- int i = mActivities.size();
- while (i > 0) {
- i--;
- mActivities.get(i).stopFreezingScreen(true /* unfreezeNow */, true /* force */);
- }
- }
}
void finishActivities() {
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index da58470..a8f22ea 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -218,7 +218,6 @@
import android.util.MergedConfiguration;
import android.util.Slog;
import android.util.SparseArray;
-import android.util.TimeUtils;
import android.util.proto.ProtoOutputStream;
import android.view.Display;
import android.view.DisplayInfo;
@@ -366,7 +365,6 @@
boolean mPermanentlyHidden; // the window should never be shown again
// This is a non-system overlay window that is currently force hidden.
private boolean mForceHideNonSystemOverlayWindow;
- boolean mAppFreezing;
boolean mHidden = true; // Used to determine if to show child windows.
private boolean mDragResizing;
private boolean mDragResizingChangeReported = true;
@@ -601,11 +599,6 @@
*/
int mLastVisibleLayoutRotation = -1;
- /**
- * How long we last kept the screen frozen.
- */
- int mLastFreezeDuration;
-
/** Is this window now (or just being) removed? */
boolean mRemoved;
@@ -1475,7 +1468,6 @@
consumeInsetsChange();
onResizeHandled();
- mWmService.makeWindowFreezingScreenIfNeededLocked(this);
// Reset the drawn state if the window need to redraw for the change, so the transition
// can wait until it has finished drawing to start.
@@ -1700,7 +1692,7 @@
@Override
boolean hasContentToDisplay() {
- if (!mAppFreezing && isDrawn() && (mViewVisibility == View.VISIBLE
+ if (!isDrawn() && (mViewVisibility == View.VISIBLE
|| (isAnimating(TRANSITION | PARENTS)
&& !getDisplayContent().mAppTransition.isTransitionSet()))) {
return true;
@@ -1912,7 +1904,6 @@
*/
boolean isInteresting() {
return mActivityRecord != null
- && (!mActivityRecord.isFreezingScreen() || !mAppFreezing)
&& mViewVisibility == View.VISIBLE;
}
@@ -2398,12 +2389,12 @@
ProtoLog.v(WM_DEBUG_APP_TRANSITIONS,
"Remove %s: mSurfaceControl=%s mAnimatingExit=%b mRemoveOnExit=%b "
+ "mHasSurface=%b surfaceShowing=%b animating=%b app-animation=%b "
- + "mDisplayFrozen=%b callers=%s",
+ + "callers=%s",
this, mWinAnimator.mSurfaceControl, mAnimatingExit, mRemoveOnExit,
mHasSurface, mWinAnimator.getShown(),
isAnimating(TRANSITION | PARENTS),
mActivityRecord != null && mActivityRecord.isAnimating(PARENTS | TRANSITION),
- mWmService.mDisplayFrozen, Debug.getCallers(6));
+ Debug.getCallers(6));
// First, see if we need to run an animation. If we do, we have to hold off on removing the
// window until the animation is done. If the display is frozen, just remove immediately,
@@ -3266,32 +3257,6 @@
}
}
- void onStartFreezingScreen() {
- mAppFreezing = true;
- for (int i = mChildren.size() - 1; i >= 0; --i) {
- final WindowState c = mChildren.get(i);
- c.onStartFreezingScreen();
- }
- }
-
- boolean onStopFreezingScreen() {
- boolean unfrozeWindows = false;
- for (int i = mChildren.size() - 1; i >= 0; --i) {
- final WindowState c = mChildren.get(i);
- unfrozeWindows |= c.onStopFreezingScreen();
- }
-
- if (!mAppFreezing) {
- return unfrozeWindows;
- }
-
- mAppFreezing = false;
-
- mLastFreezeDuration = 0;
- setDisplayLayoutNeeded();
- return true;
- }
-
boolean destroySurface(boolean cleanupOnResume, boolean appStopped) {
boolean destroyedSomething = false;
@@ -4192,16 +4157,6 @@
+ " mDestroying=" + mDestroying
+ " mRemoved=" + mRemoved);
}
- if (mAppFreezing) {
- pw.println(prefix + " configOrientationChanging="
- + (getLastReportedConfiguration().orientation != getConfiguration().orientation)
- + " mAppFreezing=" + mAppFreezing);
- }
- if (mLastFreezeDuration != 0) {
- pw.print(prefix + "mLastFreezeDuration=");
- TimeUtils.formatDuration(mLastFreezeDuration, pw);
- pw.println();
- }
pw.print(prefix + "mForceSeamlesslyRotate=" + mForceSeamlesslyRotate
+ " seamlesslyRotate: pending=");
if (mPendingSeamlessRotate != null) {
@@ -4882,7 +4837,7 @@
c.updateReportedVisibility(results);
}
- if (mAppFreezing || mViewVisibility != View.VISIBLE
+ if (mViewVisibility != View.VISIBLE
|| mAttrs.type == TYPE_APPLICATION_STARTING
|| mDestroying) {
return;
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index 298580e..1d8d867 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -303,8 +303,6 @@
resetDrawState();
- mService.makeWindowFreezingScreenIfNeededLocked(w);
-
int flags = SurfaceControl.HIDDEN;
final WindowManager.LayoutParams attrs = w.mAttrs;
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index 911c686..e1f3f0e 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -357,6 +357,7 @@
void setTouchpadRightClickZoneEnabled(bool enabled);
void setTouchpadThreeFingerTapShortcutEnabled(bool enabled);
void setTouchpadSystemGesturesEnabled(bool enabled);
+ void setTouchpadAccelerationEnabled(bool enabled);
void setInputDeviceEnabled(uint32_t deviceId, bool enabled);
void setShowTouches(bool enabled);
void setNonInteractiveDisplays(const std::set<ui::LogicalDisplayId>& displayIds);
@@ -540,6 +541,10 @@
// True to enable system gestures (three- and four-finger swipes) on touchpads.
bool touchpadSystemGesturesEnabled{true};
+ // True if the speed of the pointer will increase as the user moves
+ // their finger faster on the touchpad.
+ bool touchpadAccelerationEnabled{true};
+
// True if a pointer icon should be shown for stylus pointers.
bool stylusPointerIconEnabled{false};
@@ -869,6 +874,7 @@
outConfig->touchpadThreeFingerTapShortcutEnabled =
mLocked.touchpadThreeFingerTapShortcutEnabled;
outConfig->touchpadSystemGesturesEnabled = mLocked.touchpadSystemGesturesEnabled;
+ outConfig->touchpadAccelerationEnabled = mLocked.touchpadAccelerationEnabled;
outConfig->disabledDevices = mLocked.disabledInputDevices;
@@ -1666,6 +1672,21 @@
InputReaderConfiguration::Change::TOUCHPAD_SETTINGS);
}
+void NativeInputManager::setTouchpadAccelerationEnabled(bool enabled) {
+ { // acquire lock
+ std::scoped_lock _l(mLock);
+
+ if (mLocked.touchpadAccelerationEnabled == enabled) {
+ return;
+ }
+
+ mLocked.touchpadAccelerationEnabled = enabled;
+ } // release lock
+
+ mInputManager->getReader().requestRefreshConfiguration(
+ InputReaderConfiguration::Change::TOUCHPAD_SETTINGS);
+}
+
void NativeInputManager::setInputDeviceEnabled(uint32_t deviceId, bool enabled) {
bool refresh = false;
@@ -2644,6 +2665,13 @@
im->setTouchpadSystemGesturesEnabled(enabled);
}
+static void nativeSetTouchpadAccelerationEnabled(JNIEnv* env, jobject nativeImplObj,
+ jboolean enabled) {
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
+
+ im->setTouchpadAccelerationEnabled(enabled);
+}
+
static void nativeSetShowTouches(JNIEnv* env, jobject nativeImplObj, jboolean enabled) {
NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
@@ -3354,6 +3382,7 @@
{"setTouchpadThreeFingerTapShortcutEnabled", "(Z)V",
(void*)nativeSetTouchpadThreeFingerTapShortcutEnabled},
{"setTouchpadSystemGesturesEnabled", "(Z)V", (void*)nativeSetTouchpadSystemGesturesEnabled},
+ {"setTouchpadAccelerationEnabled", "(Z)V", (void*)nativeSetTouchpadAccelerationEnabled},
{"setShowTouches", "(Z)V", (void*)nativeSetShowTouches},
{"setNonInteractiveDisplays", "([I)V", (void*)nativeSetNonInteractiveDisplays},
{"reloadCalibration", "()V", (void*)nativeReloadCalibration},
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
index 4ef37b9..8b9def9 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
@@ -908,7 +908,7 @@
type,
false /* resetLockoutRequiresHardwareAuthToken */));
- when(mSettingObserver.getEnabledForApps(anyInt())).thenReturn(true);
+ when(mSettingObserver.getEnabledForApps(anyInt(), anyInt())).thenReturn(true);
}
private void setupFace(int id, boolean confirmationAlwaysRequired,
@@ -930,6 +930,6 @@
}
});
- when(mSettingObserver.getEnabledForApps(anyInt())).thenReturn(true);
+ when(mSettingObserver.getEnabledForApps(anyInt(), anyInt())).thenReturn(true);
}
}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
index 88829c1..acca4cc 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
@@ -16,6 +16,7 @@
package com.android.server.biometrics;
+import static android.hardware.biometrics.BiometricAuthenticator.TYPE_ANY_BIOMETRIC;
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_CREDENTIAL;
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE;
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
@@ -588,7 +589,8 @@
setupAuthForOnly(TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
// Disabled in user settings receives onError
- when(mBiometricService.mSettingObserver.getEnabledForApps(anyInt())).thenReturn(false);
+ when(mBiometricService.mSettingObserver.getEnabledForApps(anyInt(), anyInt()))
+ .thenReturn(false);
invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */,
null /* authenticators */, false /* useDefaultSubtitle */,
false /* deviceCredentialAllowed */);
@@ -602,7 +604,8 @@
// Enrolled, not disabled in settings, user requires confirmation in settings
resetReceivers();
- when(mBiometricService.mSettingObserver.getEnabledForApps(anyInt())).thenReturn(true);
+ when(mBiometricService.mSettingObserver.getEnabledForApps(anyInt(), anyInt()))
+ .thenReturn(true);
when(mBiometricService.mSettingObserver.getConfirmationAlwaysRequired(
anyInt() /* modality */, anyInt() /* userId */))
.thenReturn(true);
@@ -1493,7 +1496,8 @@
public void testCanAuthenticate_whenBiometricsNotEnabledForApps_returnsHardwareUnavailable()
throws Exception {
setupAuthForOnly(TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
- when(mBiometricService.mSettingObserver.getEnabledForApps(anyInt())).thenReturn(false);
+ when(mBiometricService.mSettingObserver.getEnabledForApps(anyInt(), anyInt()))
+ .thenReturn(false);
when(mTrustManager.isDeviceSecure(anyInt(), anyInt()))
.thenReturn(true);
@@ -1512,7 +1516,8 @@
@RequiresFlagsEnabled(Flags.FLAG_MANDATORY_BIOMETRICS)
public void testCanAuthenticate_whenBiometricsNotEnabledForApps() throws Exception {
setupAuthForOnly(TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
- when(mBiometricService.mSettingObserver.getEnabledForApps(anyInt())).thenReturn(false);
+ when(mBiometricService.mSettingObserver.getEnabledForApps(anyInt(), anyInt()))
+ .thenReturn(false);
when(mTrustManager.isDeviceSecure(anyInt(), anyInt()))
.thenReturn(true);
@@ -1741,7 +1746,8 @@
final int testId = 0;
- when(mBiometricService.mSettingObserver.getEnabledForApps(anyInt())).thenReturn(true);
+ when(mBiometricService.mSettingObserver.getEnabledForApps(anyInt(), anyInt()))
+ .thenReturn(true);
when(mFingerprintAuthenticator.hasEnrolledTemplates(anyInt(), any()))
.thenReturn(true);
@@ -1947,7 +1953,8 @@
}
@Test
- public void testRegisterEnabledOnKeyguardCallback() throws RemoteException {
+ @RequiresFlagsDisabled(com.android.settings.flags.Flags.FLAG_BIOMETRICS_ONBOARDING_EDUCATION)
+ public void testRegisterEnabledOnKeyguardCallback_flagDisabled() throws RemoteException {
final UserInfo userInfo1 = new UserInfo(0 /* userId */, "user1" /* name */, 0 /* flags */);
final UserInfo userInfo2 = new UserInfo(10 /* userId */, "user2" /* name */, 0 /* flags */);
final List<UserInfo> aliveUsers = List.of(userInfo1, userInfo2);
@@ -1957,10 +1964,10 @@
mBiometricService = new BiometricService(mContext, mInjector, mBiometricHandlerProvider);
when(mUserManager.getAliveUsers()).thenReturn(aliveUsers);
- when(mBiometricService.mSettingObserver.getEnabledOnKeyguard(userInfo1.id))
- .thenReturn(true);
- when(mBiometricService.mSettingObserver.getEnabledOnKeyguard(userInfo2.id))
- .thenReturn(false);
+ when(mBiometricService.mSettingObserver
+ .getEnabledOnKeyguard(userInfo1.id, TYPE_ANY_BIOMETRIC)).thenReturn(true);
+ when(mBiometricService.mSettingObserver
+ .getEnabledOnKeyguard(userInfo2.id, TYPE_ANY_BIOMETRIC)).thenReturn(false);
when(callback.asBinder()).thenReturn(mock(IBinder.class));
mBiometricService.mImpl.registerEnabledOnKeyguardCallback(callback);
@@ -1968,8 +1975,42 @@
waitForIdle();
verify(callback).asBinder();
- verify(callback).onChanged(true, userInfo1.id);
- verify(callback).onChanged(false, userInfo2.id);
+ verify(callback).onChanged(true, userInfo1.id, TYPE_ANY_BIOMETRIC);
+ verify(callback).onChanged(false, userInfo2.id, TYPE_ANY_BIOMETRIC);
+ verifyNoMoreInteractions(callback);
+ }
+
+ @Test
+ @RequiresFlagsEnabled(com.android.settings.flags.Flags.FLAG_BIOMETRICS_ONBOARDING_EDUCATION)
+ public void testRegisterEnabledOnKeyguardCallback_flagEnabled() throws RemoteException {
+ final UserInfo userInfo1 = new UserInfo(0 /* userId */, "user1" /* name */, 0 /* flags */);
+ final UserInfo userInfo2 = new UserInfo(10 /* userId */, "user2" /* name */, 0 /* flags */);
+ final List<UserInfo> aliveUsers = List.of(userInfo1, userInfo2);
+ final IBiometricEnabledOnKeyguardCallback callback =
+ mock(IBiometricEnabledOnKeyguardCallback.class);
+
+ mBiometricService = new BiometricService(mContext, mInjector, mBiometricHandlerProvider);
+
+ when(mUserManager.getAliveUsers()).thenReturn(aliveUsers);
+ when(mBiometricService.mSettingObserver.getEnabledOnKeyguard(userInfo1.id, TYPE_FACE))
+ .thenReturn(true);
+ when(mBiometricService.mSettingObserver.getEnabledOnKeyguard(userInfo1.id,
+ TYPE_FINGERPRINT)).thenReturn(true);
+ when(mBiometricService.mSettingObserver.getEnabledOnKeyguard(userInfo2.id, TYPE_FACE))
+ .thenReturn(false);
+ when(mBiometricService.mSettingObserver.getEnabledOnKeyguard(userInfo2.id,
+ TYPE_FINGERPRINT)).thenReturn(false);
+ when(callback.asBinder()).thenReturn(mock(IBinder.class));
+
+ mBiometricService.mImpl.registerEnabledOnKeyguardCallback(callback);
+
+ waitForIdle();
+
+ verify(callback).asBinder();
+ verify(callback).onChanged(true, userInfo1.id, TYPE_FACE);
+ verify(callback).onChanged(true, userInfo1.id, TYPE_FINGERPRINT);
+ verify(callback).onChanged(false, userInfo2.id, TYPE_FACE);
+ verify(callback).onChanged(false, userInfo2.id, TYPE_FINGERPRINT);
verifyNoMoreInteractions(callback);
}
@@ -2065,6 +2106,58 @@
userId));
}
+ @Test
+ @RequiresFlagsEnabled(com.android.settings.flags.Flags.FLAG_BIOMETRICS_ONBOARDING_EDUCATION)
+ public void testCanAuthenticate_faceEnabledForApps() throws Exception {
+ setupAuthForOnly(TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
+ when(mBiometricService.mSettingObserver.getEnabledForApps(0 /* userId */, TYPE_FACE))
+ .thenReturn(true);
+ when(mTrustManager.isDeviceSecure(anyInt(), anyInt()))
+ .thenReturn(true);
+
+ assertEquals(BiometricManager.BIOMETRIC_SUCCESS,
+ invokeCanAuthenticate(mBiometricService, Authenticators.BIOMETRIC_STRONG));
+ }
+
+ @Test
+ @RequiresFlagsEnabled(com.android.settings.flags.Flags.FLAG_BIOMETRICS_ONBOARDING_EDUCATION)
+ public void testCanAuthenticate_faceDisabledForApps() throws Exception {
+ setupAuthForOnly(TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
+ when(mBiometricService.mSettingObserver.getEnabledForApps(0 /* userId */, TYPE_FACE))
+ .thenReturn(false);
+ when(mTrustManager.isDeviceSecure(anyInt(), anyInt()))
+ .thenReturn(true);
+
+ assertEquals(BiometricManager.BIOMETRIC_ERROR_NOT_ENABLED_FOR_APPS,
+ invokeCanAuthenticate(mBiometricService, Authenticators.BIOMETRIC_STRONG));
+ }
+
+ @Test
+ @RequiresFlagsEnabled(com.android.settings.flags.Flags.FLAG_BIOMETRICS_ONBOARDING_EDUCATION)
+ public void testCanAuthenticate_fingerprintsEnabledForApps() throws Exception {
+ setupAuthForOnly(TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
+ when(mBiometricService.mSettingObserver.getEnabledForApps(0 /* userId */, TYPE_FINGERPRINT))
+ .thenReturn(true);
+ when(mTrustManager.isDeviceSecure(anyInt(), anyInt()))
+ .thenReturn(true);
+
+ assertEquals(BiometricManager.BIOMETRIC_SUCCESS,
+ invokeCanAuthenticate(mBiometricService, Authenticators.BIOMETRIC_STRONG));
+ }
+
+ @Test
+ @RequiresFlagsEnabled(com.android.settings.flags.Flags.FLAG_BIOMETRICS_ONBOARDING_EDUCATION)
+ public void testCanAuthenticate_fingerprintsDisabledForApps() throws Exception {
+ setupAuthForOnly(TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
+ when(mBiometricService.mSettingObserver.getEnabledForApps(0 /* userId */, TYPE_FINGERPRINT))
+ .thenReturn(false);
+ when(mTrustManager.isDeviceSecure(anyInt(), anyInt()))
+ .thenReturn(true);
+
+ assertEquals(BiometricManager.BIOMETRIC_ERROR_NOT_ENABLED_FOR_APPS,
+ invokeCanAuthenticate(mBiometricService, Authenticators.BIOMETRIC_STRONG));
+ }
+
// Helper methods
private int invokeCanAuthenticate(BiometricService service, int authenticators)
@@ -2082,7 +2175,8 @@
mBiometricService = new BiometricService(mContext, mInjector, mBiometricHandlerProvider);
mBiometricService.onStart();
- when(mBiometricService.mSettingObserver.getEnabledForApps(anyInt())).thenReturn(true);
+ when(mBiometricService.mSettingObserver.getEnabledForApps(anyInt(), anyInt()))
+ .thenReturn(true);
if ((modality & TYPE_FINGERPRINT) != 0) {
when(mFingerprintAuthenticator.hasEnrolledTemplates(anyInt(), any()))
@@ -2115,7 +2209,8 @@
mBiometricService = new BiometricService(mContext, mInjector, mBiometricHandlerProvider);
mBiometricService.onStart();
- when(mBiometricService.mSettingObserver.getEnabledForApps(anyInt())).thenReturn(true);
+ when(mBiometricService.mSettingObserver.getEnabledForApps(anyInt(), anyInt()))
+ .thenReturn(true);
assertEquals(modalities.length, strengths.length);
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/PreAuthInfoTest.java b/services/tests/servicestests/src/com/android/server/biometrics/PreAuthInfoTest.java
index f6f831f..f8cf21d 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/PreAuthInfoTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/PreAuthInfoTest.java
@@ -23,6 +23,7 @@
import static android.hardware.biometrics.BiometricManager.BIOMETRIC_ERROR_NOT_ENABLED_FOR_APPS;
import static android.hardware.biometrics.BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE;
+import static com.android.server.biometrics.PreAuthInfo.BIOMETRIC_NOT_ENABLED_FOR_APPS;
import static com.android.server.biometrics.sensors.LockoutTracker.LOCKOUT_NONE;
import static com.google.common.truth.Truth.assertThat;
@@ -96,7 +97,7 @@
when(mTrustManager.isDeviceSecure(anyInt(), anyInt())).thenReturn(true);
when(mDevicePolicyManager.getKeyguardDisabledFeatures(any(), anyInt()))
.thenReturn(KEYGUARD_DISABLE_FEATURES_NONE);
- when(mSettingObserver.getEnabledForApps(anyInt())).thenReturn(true);
+ when(mSettingObserver.getEnabledForApps(anyInt(), anyInt())).thenReturn(true);
when(mSettingObserver.getMandatoryBiometricsEnabledAndRequirementsSatisfiedForUser(
anyInt())).thenReturn(true);
when(mFaceAuthenticator.hasEnrolledTemplates(anyInt(), any())).thenReturn(true);
@@ -280,7 +281,7 @@
public void testCalculateByPriority()
throws Exception {
when(mFaceAuthenticator.hasEnrolledTemplates(anyInt(), any())).thenReturn(false);
- when(mSettingObserver.getEnabledForApps(anyInt())).thenReturn(false);
+ when(mSettingObserver.getEnabledForApps(anyInt(), anyInt())).thenReturn(false);
BiometricSensor faceSensor = getFaceSensor();
BiometricSensor fingerprintSensor = getFingerprintSensor();
@@ -370,6 +371,58 @@
assertThat(preAuthInfo.getCanAuthenticateResult()).isEqualTo(BIOMETRIC_ERROR_NO_HARDWARE);
}
+ @Test
+ @RequiresFlagsEnabled(com.android.settings.flags.Flags.FLAG_BIOMETRICS_ONBOARDING_EDUCATION)
+ public void testBiometricsEnabledForApps_fingerprintEnabled_faceDisabled()
+ throws Exception {
+ when(mSettingObserver.getEnabledForApps(USER_ID, TYPE_FINGERPRINT)).thenReturn(true);
+ when(mSettingObserver.getEnabledForApps(USER_ID, TYPE_FACE)).thenReturn(false);
+ when(mTrustManager.isInSignificantPlace()).thenReturn(true);
+
+ final BiometricSensor sensor = getFaceSensor();
+ BiometricSensor fingerprintSensor = getFingerprintSensor();
+ final PromptInfo promptInfo = new PromptInfo();
+ promptInfo.setAuthenticators(BiometricManager.Authenticators.IDENTITY_CHECK
+ | BiometricManager.Authenticators.BIOMETRIC_STRONG);
+ final PreAuthInfo preAuthInfo = PreAuthInfo.create(mTrustManager, mDevicePolicyManager,
+ mSettingObserver, List.of(sensor, fingerprintSensor), USER_ID, promptInfo,
+ TEST_PACKAGE_NAME, false /* checkDevicePolicyManager */, mContext,
+ mBiometricCameraManager, mUserManager);
+
+ assertThat(preAuthInfo.eligibleSensors).hasSize(1);
+ assertThat(preAuthInfo.eligibleSensors.get(0).modality).isEqualTo(TYPE_FINGERPRINT);
+ assertThat(preAuthInfo.ineligibleSensors).hasSize(1);
+ assertThat(preAuthInfo.ineligibleSensors.get(0).first.modality).isEqualTo(TYPE_FACE);
+ assertThat(preAuthInfo.ineligibleSensors.get(0).second)
+ .isEqualTo(BIOMETRIC_NOT_ENABLED_FOR_APPS);
+ }
+
+ @Test
+ @RequiresFlagsEnabled(com.android.settings.flags.Flags.FLAG_BIOMETRICS_ONBOARDING_EDUCATION)
+ public void testBiometricsEnabledForApps_fingerprintDisabled_faceEnabled()
+ throws Exception {
+ when(mSettingObserver.getEnabledForApps(USER_ID, TYPE_FINGERPRINT)).thenReturn(false);
+ when(mSettingObserver.getEnabledForApps(USER_ID, TYPE_FACE)).thenReturn(true);
+ when(mTrustManager.isInSignificantPlace()).thenReturn(true);
+
+ final BiometricSensor sensor = getFaceSensor();
+ BiometricSensor fingerprintSensor = getFingerprintSensor();
+ final PromptInfo promptInfo = new PromptInfo();
+ promptInfo.setAuthenticators(BiometricManager.Authenticators.IDENTITY_CHECK
+ | BiometricManager.Authenticators.BIOMETRIC_STRONG);
+ final PreAuthInfo preAuthInfo = PreAuthInfo.create(mTrustManager, mDevicePolicyManager,
+ mSettingObserver, List.of(sensor, fingerprintSensor), USER_ID, promptInfo,
+ TEST_PACKAGE_NAME, false /* checkDevicePolicyManager */, mContext,
+ mBiometricCameraManager, mUserManager);
+
+ assertThat(preAuthInfo.eligibleSensors).hasSize(1);
+ assertThat(preAuthInfo.eligibleSensors.get(0).modality).isEqualTo(TYPE_FACE);
+ assertThat(preAuthInfo.ineligibleSensors).hasSize(1);
+ assertThat(preAuthInfo.ineligibleSensors.get(0).first.modality).isEqualTo(TYPE_FINGERPRINT);
+ assertThat(preAuthInfo.ineligibleSensors.get(0).second)
+ .isEqualTo(BIOMETRIC_NOT_ENABLED_FOR_APPS);
+ }
+
private BiometricSensor getFingerprintSensor() {
BiometricSensor sensor = new BiometricSensor(mContext, SENSOR_ID_FINGERPRINT,
TYPE_FINGERPRINT, BiometricManager.Authenticators.BIOMETRIC_STRONG,
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 39206dc..3b32701 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java
@@ -1612,7 +1612,7 @@
);
}
- public void testThrottling() {
+ public void disabled_testThrottling() {
final ShortcutInfo si1 = makeShortcut("shortcut1");
assertTrue(mManager.setDynamicShortcuts(list(si1)));
@@ -1685,7 +1685,7 @@
assertEquals(START_TIME + INTERVAL * 9, mManager.getRateLimitResetTime());
}
- public void testThrottling_rewind() {
+ public void disabled_testThrottling_rewind() {
final ShortcutInfo si1 = makeShortcut("shortcut1");
assertTrue(mManager.setDynamicShortcuts(list(si1)));
@@ -1715,7 +1715,7 @@
assertEquals(3, mManager.getRemainingCallCount());
}
- public void testThrottling_perPackage() {
+ public void disabled_testThrottling_perPackage() {
final ShortcutInfo si1 = makeShortcut("shortcut1");
assertTrue(mManager.setDynamicShortcuts(list(si1)));
@@ -1847,7 +1847,7 @@
});
}
- public void testThrottling_foreground() throws Exception {
+ public void disabled_testThrottling_foreground() throws Exception {
prepareCrossProfileDataSet();
dumpsysOnLogcat("Before save & load");
diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest3.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest3.java
index aad06c6..81ebf86 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest3.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest3.java
@@ -171,11 +171,12 @@
.haveRanksInOrder("ms1");
}
- public void testSetDynamicShortcuts_withManifestShortcuts() {
- runTestWithManifestShortcuts(() -> testSetDynamicShortcuts_noManifestShortcuts());
+ public void disabled_testSetDynamicShortcuts_withManifestShortcuts() {
+ runTestWithManifestShortcuts(() ->
+ disabled_testAddDynamicShortcuts_noManifestShortcuts());
}
- public void testAddDynamicShortcuts_noManifestShortcuts() {
+ public void disabled_testAddDynamicShortcuts_noManifestShortcuts() {
mManager.addDynamicShortcuts(list(
shortcut("s1", A1)
));
@@ -264,8 +265,8 @@
.haveIds("s1", "s2", "s4", "s5", "s10");
}
- public void testAddDynamicShortcuts_withManifestShortcuts() {
- runTestWithManifestShortcuts(() -> testAddDynamicShortcuts_noManifestShortcuts());
+ public void disabled_testAddDynamicShortcuts_withManifestShortcuts() {
+ runTestWithManifestShortcuts(() -> disabled_testAddDynamicShortcuts_noManifestShortcuts());
}
public void testUpdateShortcuts_noManifestShortcuts() {
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
index 96fddf1..31b9cf72 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
@@ -17,8 +17,10 @@
package com.android.server.notification;
import static android.app.AutomaticZenRule.TYPE_BEDTIME;
+import static android.app.AutomaticZenRule.TYPE_DRIVING;
import static android.app.AutomaticZenRule.TYPE_IMMERSIVE;
import static android.app.AutomaticZenRule.TYPE_SCHEDULE_TIME;
+import static android.app.AutomaticZenRule.TYPE_THEATER;
import static android.app.AutomaticZenRule.TYPE_UNKNOWN;
import static android.app.Flags.FLAG_BACKUP_RESTORE_LOGGING;
import static android.app.Flags.FLAG_MODES_API;
@@ -87,6 +89,7 @@
import static com.android.os.dnd.DNDProtoEnums.ROOT_CONFIG;
import static com.android.os.dnd.DNDProtoEnums.STATE_ALLOW;
import static com.android.os.dnd.DNDProtoEnums.STATE_DISALLOW;
+import static com.android.server.notification.Flags.FLAG_PREVENT_ZEN_DEVICE_EFFECTS_WHILE_DRIVING;
import static com.android.server.notification.ZenModeEventLogger.ACTIVE_RULE_TYPE_MANUAL;
import static com.android.server.notification.ZenModeHelper.RULE_LIMIT_PER_PACKAGE;
@@ -5518,8 +5521,72 @@
eq(ORIGIN_INIT_USER));
}
+ @Test
+ @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI, FLAG_PREVENT_ZEN_DEVICE_EFFECTS_WHILE_DRIVING})
+ public void testDeviceEffects_allowsGrayscale() {
+ mZenModeHelper.setDeviceEffectsApplier(mDeviceEffectsApplier);
+ reset(mDeviceEffectsApplier);
+ ZenDeviceEffects grayscale = new ZenDeviceEffects.Builder()
+ .setShouldDisplayGrayscale(true)
+ .build();
+ String ruleId = addRuleWithEffects(TYPE_THEATER, grayscale);
+
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, ruleId, CONDITION_TRUE,
+ ORIGIN_APP, CUSTOM_PKG_UID);
+ mTestableLooper.processAllMessages();
+
+ verify(mDeviceEffectsApplier).apply(eq(grayscale), anyInt());
+ }
+
+ @Test
+ @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI, FLAG_PREVENT_ZEN_DEVICE_EFFECTS_WHILE_DRIVING})
+ public void testDeviceEffects_whileDriving_avoidsGrayscale() {
+ mZenModeHelper.setDeviceEffectsApplier(mDeviceEffectsApplier);
+ reset(mDeviceEffectsApplier);
+ ZenDeviceEffects grayscale = new ZenDeviceEffects.Builder()
+ .setShouldDisplayGrayscale(true)
+ .build();
+ String ruleWithGrayscale = addRuleWithEffects(TYPE_THEATER, grayscale);
+ String drivingRule = addRuleWithEffects(TYPE_DRIVING, null);
+
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, ruleWithGrayscale,
+ CONDITION_TRUE, ORIGIN_APP, CUSTOM_PKG_UID);
+ mTestableLooper.processAllMessages();
+
+ verify(mDeviceEffectsApplier).apply(eq(grayscale), anyInt());
+
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, drivingRule, CONDITION_TRUE,
+ ORIGIN_APP, CUSTOM_PKG_UID);
+ mTestableLooper.processAllMessages();
+
+ verify(mDeviceEffectsApplier).apply(eq(NO_EFFECTS), anyInt());
+ }
+
+ @Test
+ @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI, FLAG_PREVENT_ZEN_DEVICE_EFFECTS_WHILE_DRIVING})
+ public void testDeviceEffects_whileDrivingWithGrayscale_allowsGrayscale() {
+ mZenModeHelper.setDeviceEffectsApplier(mDeviceEffectsApplier);
+ reset(mDeviceEffectsApplier);
+ ZenDeviceEffects grayscale = new ZenDeviceEffects.Builder()
+ .setShouldDisplayGrayscale(true)
+ .build();
+ String weirdoDrivingWithGrayscale = addRuleWithEffects(TYPE_DRIVING, grayscale);
+
+ mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, weirdoDrivingWithGrayscale,
+ CONDITION_TRUE, ORIGIN_APP, CUSTOM_PKG_UID);
+ mTestableLooper.processAllMessages();
+
+ verify(mDeviceEffectsApplier).apply(eq(grayscale), anyInt());
+ }
+
private String addRuleWithEffects(ZenDeviceEffects effects) {
+ return addRuleWithEffects(TYPE_UNKNOWN, effects);
+ }
+
+ private String addRuleWithEffects(int type, @Nullable ZenDeviceEffects effects) {
AutomaticZenRule rule = new AutomaticZenRule.Builder("Test", CONDITION_ID)
+ .setPackage(mContext.getPackageName())
+ .setType(type)
.setDeviceEffects(effects)
.build();
return mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, mContext.getPackageName(),
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 c88d515..65150e7 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -2006,7 +2006,6 @@
display.continueUpdateOrientationForDiffOrienLaunchingApp();
assertTrue(display.isFixedRotationLaunchingApp(activity));
- activity.stopFreezingScreen(true /* unfreezeSurfaceNow */, true /* force */);
// Simulate the rotation has been updated to previous one, e.g. sensor updates before the
// remote rotation is completed.
doReturn(originalRotation).when(displayRotation).rotationForOrientation(
@@ -2015,14 +2014,10 @@
final DisplayInfo rotatedInfo = activity.getFixedRotationTransformDisplayInfo();
activity.finishFixedRotationTransform();
- final ScreenRotationAnimation rotationAnim = display.getRotationAnimation();
- assertNotNull(rotationAnim);
// Because the display doesn't rotate, the rotated activity needs to cancel the fixed
// rotation. There should be a rotation animation to cover the change of activity.
verify(activity).onCancelFixedRotationTransform(rotatedInfo.rotation);
- assertTrue(activity.isFreezingScreen());
- assertFalse(displayRotation.isRotatingSeamlessly());
// Simulate the remote rotation has completed and the configuration doesn't change, then
// the rotated activity should also be restored by clearing the transform.
@@ -2041,8 +2036,6 @@
activity.setVisibleRequested(false);
clearInvocations(activity);
activity.onCancelFixedRotationTransform(originalRotation);
- // The implementation of cancellation must be executed.
- verify(activity).startFreezingScreen(originalRotation);
}
@Test
@@ -2599,19 +2592,16 @@
final TestWindowState appWindow = createWindowState(attrs, activity);
activity.addWindow(appWindow);
spyOn(appWindow);
- doNothing().when(appWindow).onStartFreezingScreen();
// Set initial orientation and update.
activity.setOrientation(SCREEN_ORIENTATION_LANDSCAPE);
- mDisplayContent.updateOrientation(null /* freezeThisOneIfNeeded */,
- false /* forceUpdate */);
+ mDisplayContent.updateOrientationAndComputeConfig(false /* forceUpdate */);
assertEquals(SCREEN_ORIENTATION_LANDSCAPE, mDisplayContent.getLastOrientation());
appWindow.mResizeReported = false;
// Update the orientation to perform 180 degree rotation and check that resize was reported.
activity.setOrientation(SCREEN_ORIENTATION_REVERSE_LANDSCAPE);
- mDisplayContent.updateOrientation(null /* freezeThisOneIfNeeded */,
- false /* forceUpdate */);
+ mDisplayContent.updateOrientationAndComputeConfig(false /* forceUpdate */);
// In this test, DC will not get config update. Set the waiting flag to false.
mDisplayContent.mWaitingForConfig = false;
mWm.mRoot.performSurfacePlacement();
@@ -2635,8 +2625,6 @@
final TestWindowState appWindow = createWindowState(attrs, activity);
activity.addWindow(appWindow);
spyOn(appWindow);
- doNothing().when(appWindow).onStartFreezingScreen();
- doNothing().when(mWm).startFreezingDisplay(anyInt(), anyInt(), any(), anyInt());
// Set initial orientation and update.
performRotation(displayRotation, Surface.ROTATION_90);
@@ -2795,9 +2783,6 @@
assertEquals(Configuration.ORIENTATION_PORTRAIT, displayConfig.orientation);
assertEquals(Configuration.ORIENTATION_PORTRAIT, activityConfig.orientation);
- // Unblock the rotation animation, so the further orientation updates won't be ignored.
- unblockDisplayRotation(activity.mDisplayContent);
-
final ActivityRecord topActivity = createActivityRecord(activity.getTask());
topActivity.setOrientation(SCREEN_ORIENTATION_LANDSCAPE);
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index d76a907..0964ebe 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -1714,7 +1714,6 @@
@Test
public void testFinishFixedRotationNoAppTransitioningTask() {
- unblockDisplayRotation(mDisplayContent);
final ActivityRecord app = createActivityRecord(mDisplayContent);
final Task task = app.getTask();
final ActivityRecord app2 = new ActivityBuilder(mWm.mAtmService).setTask(task).build();
@@ -1742,7 +1741,6 @@
public void testFixedRotationWithPip() {
final DisplayContent displayContent = mDefaultDisplay;
displayContent.setIgnoreOrientationRequest(false);
- unblockDisplayRotation(displayContent);
// Unblock the condition in PinnedTaskController#continueOrientationChangeIfNeeded.
doNothing().when(displayContent).prepareAppTransition(anyInt());
// Make resume-top really update the activity state.
@@ -1762,7 +1760,6 @@
assertTrue(displayContent.hasTopFixedRotationLaunchingApp());
assertTrue(displayContent.mPinnedTaskController.shouldDeferOrientationChange());
- verify(mWm, never()).startFreezingDisplay(anyInt(), anyInt(), any(), anyInt());
clearInvocations(pinnedTask);
// Assume that the PiP enter animation is done then the new bounds are set. Expect the
@@ -1799,7 +1796,6 @@
@Test
public void testNoFixedRotationOnResumedScheduledApp() {
- unblockDisplayRotation(mDisplayContent);
final ActivityRecord app = new ActivityBuilder(mAtm).setCreateTask(true).build();
app.setVisible(false);
app.setState(ActivityRecord.State.RESUMED, "test");
@@ -1811,7 +1807,7 @@
// The condition should reject using fixed rotation because the resumed client in real case
// might get display info immediately. And the fixed rotation adjustments haven't arrived
// client side so the info may be inconsistent with the requested orientation.
- verify(mDisplayContent).updateOrientation(eq(app), anyBoolean());
+ verify(mDisplayContent).updateOrientationAndComputeConfig(anyBoolean());
assertFalse(app.isFixedRotationTransforming());
assertFalse(mDisplayContent.hasTopFixedRotationLaunchingApp());
}
@@ -1863,9 +1859,6 @@
@Test
public void testSecondaryInternalDisplayRotationFollowsDefaultDisplay() {
- // Skip freezing so the unrelated conditions in updateRotationUnchecked won't disturb.
- doNothing().when(mWm).startFreezingDisplay(anyInt(), anyInt(), any(), anyInt());
-
final DisplayRotationCoordinator coordinator =
mRootWindowContainer.getDisplayRotationCoordinator();
final DisplayContent defaultDisplayContent = mDisplayContent;
@@ -1921,9 +1914,6 @@
@Test
public void testSecondaryNonInternalDisplayDoesNotFollowDefaultDisplay() {
- // Skip freezing so the unrelated conditions in updateRotationUnchecked won't disturb.
- doNothing().when(mWm).startFreezingDisplay(anyInt(), anyInt(), any(), anyInt());
-
final DisplayRotationCoordinator coordinator =
mRootWindowContainer.getDisplayRotationCoordinator();
@@ -1994,20 +1984,11 @@
}
};
- // kill any existing rotation animation (vestigial from test setup).
- dc.setRotationAnimation(null);
-
mWm.updateRotation(true /* alwaysSendConfiguration */, false /* forceRelayout */);
- // If remote rotation is not finished, the display should not be able to unfreeze.
- mWm.stopFreezingDisplayLocked();
- assertTrue(mWm.mDisplayFrozen);
assertTrue(called[0]);
waitUntilHandlersIdle();
assertTrue(continued[0]);
-
- mWm.stopFreezingDisplayLocked();
- assertFalse(mWm.mDisplayFrozen);
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
index f6f473b..cff172f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -855,12 +855,6 @@
startingApp.updateResizingWindowIfNeeded();
assertTrue(mWm.mResizingWindows.contains(startingApp));
assertTrue(startingApp.isDrawn());
-
- // Even if the display is frozen, invisible requested window should not be affected.
- mWm.startFreezingDisplay(0, 0, mDisplayContent);
- startingApp.getWindowFrames().setInsetsChanged(true);
- startingApp.updateResizingWindowIfNeeded();
- assertTrue(startingApp.isDrawn());
}
@SetupWindows(addWindows = W_ABOVE_ACTIVITY)
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index 37d2a75..2c390c5 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -1010,20 +1010,6 @@
waitUntilWindowAnimatorIdle();
}
- /**
- * Avoids rotating screen disturbed by some conditions. It is usually used for the default
- * display that is not the instance of {@link TestDisplayContent} (it bypasses the conditions).
- *
- * @see DisplayRotation#updateRotationUnchecked
- */
- void unblockDisplayRotation(DisplayContent dc) {
- dc.mOpeningApps.clear();
- mWm.mAppsFreezingScreen = 0;
- mWm.stopFreezingDisplayLocked();
- // The rotation animation won't actually play, it needs to be cleared manually.
- dc.setRotationAnimation(null);
- }
-
static void resizeDisplay(DisplayContent displayContent, int width, int height) {
displayContent.updateBaseDisplayMetrics(width, height, displayContent.mBaseDisplayDensity,
displayContent.mBaseDisplayPhysicalXDpi, displayContent.mBaseDisplayPhysicalYDpi);