Merge "[Catalyst] Introduce IntRangeValuePreference" into main
diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index 4a78d017..8fa2362 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -273,4 +273,7 @@
void setAssistantAdjustmentKeyTypeState(int type, boolean enabled);
String[] getTypeAdjustmentDeniedPackages();
void setTypeAdjustmentForPackageState(String pkg, boolean enabled);
+
+ // TODO: b/389918945 - Remove once nm_binder_perf flags are going to Nextfood.
+ void incrementCounter(String metricId);
}
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/NotificationManager.java b/core/java/android/app/NotificationManager.java
index e5d80de..1e1ec60 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -665,8 +665,10 @@
private final InstantSource mClock;
private final RateLimiter mUpdateRateLimiter = new RateLimiter("notify (update)",
+ "notifications.value_client_throttled_notify_update",
MAX_NOTIFICATION_UPDATE_RATE);
private final RateLimiter mUnnecessaryCancelRateLimiter = new RateLimiter("cancel (dupe)",
+ "notifications.value_client_throttled_cancel_duplicate",
MAX_NOTIFICATION_UNNECESSARY_CANCEL_RATE);
// Value is KNOWN_STATUS_ENQUEUED/_CANCELLED
private final LruCache<NotificationKey, Integer> mKnownNotifications = new LruCache<>(100);
@@ -848,19 +850,21 @@
/** Helper class to rate-limit Binder calls. */
private class RateLimiter {
- private static final Duration RATE_LIMITER_LOG_INTERVAL = Duration.ofSeconds(5);
+ private static final Duration RATE_LIMITER_LOG_INTERVAL = Duration.ofSeconds(1);
private final RateEstimator mInputRateEstimator;
private final RateEstimator mOutputRateEstimator;
private final String mName;
+ private final String mCounterName;
private final float mLimitRate;
private Instant mLogSilencedUntil;
- private RateLimiter(String name, float limitRate) {
+ private RateLimiter(String name, String counterName, float limitRate) {
mInputRateEstimator = new RateEstimator();
mOutputRateEstimator = new RateEstimator();
mName = name;
+ mCounterName = counterName;
mLimitRate = limitRate;
}
@@ -880,6 +884,14 @@
return;
}
+ if (Flags.nmBinderPerfLogNmThrottling()) {
+ try {
+ service().incrementCounter(mCounterName);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Ignoring error while trying to log " + mCounterName, e);
+ }
+ }
+
long nowMillis = now.toEpochMilli();
Slog.w(TAG, TextUtils.formatSimple(
"Shedding %s of %s, rate limit (%s) exceeded: input %s, output would be %s",
@@ -1644,7 +1656,8 @@
if (Flags.modesApi() && Flags.modesUi()) {
PackageManager pm = mContext.getPackageManager();
return !pm.hasSystemFeature(PackageManager.FEATURE_WATCH)
- && !pm.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE);
+ && !pm.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)
+ && !pm.hasSystemFeature(PackageManager.FEATURE_LEANBACK);
} else {
return false;
}
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/app/notification.aconfig b/core/java/android/app/notification.aconfig
index edd17e8..914ca73 100644
--- a/core/java/android/app/notification.aconfig
+++ b/core/java/android/app/notification.aconfig
@@ -295,6 +295,16 @@
}
flag {
+ name: "nm_binder_perf_log_nm_throttling"
+ namespace: "systemui"
+ description: "Log throttled operations (notify, cancel) to statsd. This flag will NOT be pushed past Trunkfood."
+ bug: "389918945"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "no_sbnholder"
namespace: "systemui"
description: "removes sbnholder from NLS"
diff --git a/core/java/android/app/supervision/flags.aconfig b/core/java/android/app/supervision/flags.aconfig
index 4ee3a03..18182b8 100644
--- a/core/java/android/app/supervision/flags.aconfig
+++ b/core/java/android/app/supervision/flags.aconfig
@@ -40,3 +40,11 @@
description: "Flag to enable the SupervisionAppService"
bug: "389123070"
}
+
+flag {
+ name: "enable_supervision_settings_screen"
+ is_exported: true
+ namespace: "supervision"
+ description: "Flag that enables the Supervision settings screen with top-level Android settings entry point"
+ bug: "383404606"
+}
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/os/Parcel.java b/core/java/android/os/Parcel.java
index 4aa7462..c6a63a7 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -50,6 +50,7 @@
import dalvik.annotation.optimization.CriticalNative;
import dalvik.annotation.optimization.FastNative;
+import dalvik.annotation.optimization.NeverInline;
import libcore.util.SneakyThrow;
@@ -628,6 +629,19 @@
}
}
+ @NeverInline
+ private void errorUsedWhileRecycling() {
+ String error = "Parcel used while recycled. "
+ + Log.getStackTraceString(new Throwable())
+ + " Original recycle call (if DEBUG_RECYCLE): ", mStack;
+ Log.wtf(TAG, error);
+ // TODO(b/381155347): harder error
+ }
+
+ private void assertNotRecycled() {
+ if (mRecycled) errorUsedWhileRecycling();
+ }
+
/**
* Set a {@link ReadWriteHelper}, which can be used to avoid having duplicate strings, for
* example.
@@ -1180,6 +1194,7 @@
* growing dataCapacity() if needed.
*/
public final void writeInt(int val) {
+ assertNotRecycled();
int err = nativeWriteInt(mNativePtr, val);
if (err != OK) {
nativeSignalExceptionForError(err);
@@ -3282,6 +3297,7 @@
* Read an integer value from the parcel at the current dataPosition().
*/
public final int readInt() {
+ assertNotRecycled();
return nativeReadInt(mNativePtr);
}
diff --git a/core/java/android/permission/flags.aconfig b/core/java/android/permission/flags.aconfig
index 70d8891..a480a3b 100644
--- a/core/java/android/permission/flags.aconfig
+++ b/core/java/android/permission/flags.aconfig
@@ -493,3 +493,9 @@
bug: "341941666"
}
+flag {
+ name: "sqlite_discrete_op_event_logging_enabled"
+ namespace: "permissions"
+ description: "Collect sqlite performance metrics for discrete ops."
+ bug: "377584611"
+}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index a22333b..2e231e3 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -353,6 +353,18 @@
*/
public static final String ACTION_ONE_HANDED_SETTINGS =
"android.settings.action.ONE_HANDED_SETTINGS";
+
+ /**
+ * Activity Action: Show Double tap power gesture Settings page.
+ * <p>
+ * Input: Nothing
+ * <p>
+ * Output: Nothing
+ * @hide
+ */
+ public static final String ACTION_DOUBLE_TAP_POWER_SETTINGS =
+ "android.settings.action.DOUBLE_TAP_POWER_SETTINGS";
+
/**
* The return values for {@link Settings.Config#set}
* @hide
@@ -6318,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.
@@ -6612,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);
@@ -11356,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.
@@ -12356,12 +12417,6 @@
public static final String CAMERA_EXTENSIONS_FALLBACK = "camera_extensions_fallback";
/**
- * Controls whether contextual suggestions can be shown in the media controls.
- * @hide
- */
- public static final String MEDIA_CONTROLS_RECOMMENDATION = "qs_media_recommend";
-
- /**
* Controls magnification mode when magnification is enabled via a system-wide triple tap
* gesture or the accessibility shortcut.
*
diff --git a/core/java/android/timezone/MobileCountries.java b/core/java/android/timezone/MobileCountries.java
new file mode 100644
index 0000000..19ae608
--- /dev/null
+++ b/core/java/android/timezone/MobileCountries.java
@@ -0,0 +1,68 @@
+/*
+ * 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 android.timezone;
+
+import android.annotation.NonNull;
+
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * Information about a telephony network.
+ *
+ * @hide
+ */
+public final class MobileCountries {
+
+ @NonNull
+ private final com.android.i18n.timezone.MobileCountries mDelegate;
+
+ MobileCountries(@NonNull com.android.i18n.timezone.MobileCountries delegate) {
+ mDelegate = Objects.requireNonNull(delegate);
+ }
+
+ /**
+ * Returns the Mobile Country Code of the network.
+ */
+ @NonNull
+ public String getMcc() {
+ return mDelegate.getMcc();
+ }
+
+ /**
+ * Returns the Mobile Country Code of the network.
+ */
+ @NonNull
+ public Set<String> getCountryIsoCodes() {
+ return mDelegate.getCountryIsoCodes();
+ }
+
+ /**
+ * Returns the country in which the network operates as an ISO 3166 alpha-2 (lower case).
+ */
+ @NonNull
+ public String getDefaultCountryIsoCode() {
+ return mDelegate.getDefaultCountryIsoCode();
+ }
+
+ @Override
+ public String toString() {
+ return "MobileCountries{"
+ + "mDelegate=" + mDelegate
+ + '}';
+ }
+}
diff --git a/core/java/android/timezone/TelephonyNetworkFinder.java b/core/java/android/timezone/TelephonyNetworkFinder.java
index fb4a19b..eb50fc2 100644
--- a/core/java/android/timezone/TelephonyNetworkFinder.java
+++ b/core/java/android/timezone/TelephonyNetworkFinder.java
@@ -19,7 +19,6 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
-import com.android.i18n.timezone.MobileCountries;
import com.android.icu.Flags;
import java.util.Objects;
@@ -59,11 +58,13 @@
*/
@Nullable
public MobileCountries findCountriesByMcc(@NonNull String mcc) {
- Objects.requireNonNull(mcc);
-
if (!Flags.telephonyLookupMccExtension()) {
return null;
}
- return mDelegate.findCountriesByMcc(mcc);
+ Objects.requireNonNull(mcc);
+
+ com.android.i18n.timezone.MobileCountries countriesByMcc =
+ mDelegate.findCountriesByMcc(mcc);
+ return countriesByMcc != null ? new MobileCountries(countriesByMcc) : null;
}
}
diff --git a/core/java/android/util/proto/ProtoFieldFilter.java b/core/java/android/util/proto/ProtoFieldFilter.java
new file mode 100644
index 0000000..c3ae106
--- /dev/null
+++ b/core/java/android/util/proto/ProtoFieldFilter.java
@@ -0,0 +1,335 @@
+/*
+ * 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 android.util.proto;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.function.Predicate;
+
+/**
+ * A utility class that reads raw protobuf data from an InputStream
+ * and copies only those fields for which a given predicate returns true.
+ *
+ * <p>
+ * This is a low-level approach that does not fully decode fields
+ * (unless necessary to determine lengths). It simply:
+ * <ul>
+ * <li>Parses each field's tag (varint for field number & wire type)</li>
+ * <li>If {@code includeFn(fieldNumber) == true}, copies
+ * the tag bytes and the field bytes directly to the output</li>
+ * <li>Otherwise, skips that field in the input</li>
+ * </ul>
+ * </p>
+ *
+ * <p>
+ * Because we do not re-encode, unknown or unrecognized fields are copied
+ * <i>verbatim</i> and remain exactly as in the input (useful for partial
+ * parsing or partial transformations).
+ * </p>
+ *
+ * <p>
+ * Note: This class only filters based on top-level field numbers. For length-delimited
+ * fields (including nested messages), the entire contents are either copied or skipped
+ * as a single unit. The class is not capable of nested filtering.
+ * </p>
+ *
+ * @hide
+ */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
+public class ProtoFieldFilter {
+
+ private static final int BUFFER_SIZE_BYTES = 4096;
+
+ private final Predicate<Integer> mFieldPredicate;
+ // General-purpose buffer for reading proto fields and their data
+ private final byte[] mBuffer;
+ // Buffer specifically designated to hold varint values (max 10 bytes in protobuf encoding)
+ private final byte[] mVarIntBuffer = new byte[10];
+
+ /**
+ * Constructs a ProtoFieldFilter with a predicate that considers depth.
+ *
+ * @param fieldPredicate A predicate returning true if the given fieldNumber should be
+ * included in the output.
+ * @param bufferSize The size of the internal buffer used for processing proto fields.
+ * Larger buffers may improve performance when processing large
+ * length-delimited fields.
+ */
+ public ProtoFieldFilter(Predicate<Integer> fieldPredicate, int bufferSize) {
+ this.mFieldPredicate = fieldPredicate;
+ this.mBuffer = new byte[bufferSize];
+ }
+
+ /**
+ * Constructs a ProtoFieldFilter with a predicate that considers depth and
+ * uses a default buffer size.
+ *
+ * @param fieldPredicate A predicate returning true if the given fieldNumber should be
+ * included in the output.
+ */
+ public ProtoFieldFilter(Predicate<Integer> fieldPredicate) {
+ this(fieldPredicate, BUFFER_SIZE_BYTES);
+ }
+
+ /**
+ * Reads raw protobuf data from {@code in} and writes only those fields
+ * passing {@code includeFn} to {@code out}. The predicate is given
+ * (fieldNumber, wireType) for each encountered field.
+ *
+ * @param in The input stream of protobuf data
+ * @param out The output stream to which we write the filtered protobuf
+ * @throws IOException If reading or writing fails, or if the protobuf data is corrupted
+ */
+ public void filter(InputStream in, OutputStream out) throws IOException {
+ int tagBytesLength;
+ while ((tagBytesLength = readRawVarint(in)) > 0) {
+ // Parse the varint loaded in mVarIntBuffer, through readRawVarint
+ long tagVal = parseVarint(mVarIntBuffer, tagBytesLength);
+ int fieldNumber = (int) (tagVal >>> ProtoStream.FIELD_ID_SHIFT);
+ int wireType = (int) (tagVal & ProtoStream.WIRE_TYPE_MASK);
+
+ if (fieldNumber == 0) {
+ break;
+ }
+ if (mFieldPredicate.test(fieldNumber)) {
+ out.write(mVarIntBuffer, 0, tagBytesLength);
+ copyFieldData(in, out, wireType);
+ } else {
+ skipFieldData(in, wireType);
+ }
+ }
+ }
+
+ /**
+ * Reads a varint (up to 10 bytes) from the stream as raw bytes
+ * and returns it in a byte array. If the stream is at EOF, returns null.
+ *
+ * @param in The input stream
+ * @return the size of the varint bytes moved to mVarIntBuffer
+ * @throws IOException If an error occurs, or if we detect a malformed varint
+ */
+ private int readRawVarint(InputStream in) throws IOException {
+ // We attempt to read 1 byte. If none available => null
+ int b = in.read();
+ if (b < 0) {
+ return 0;
+ }
+ int count = 0;
+ mVarIntBuffer[count++] = (byte) b;
+ // If the continuation bit is set, we continue
+ while ((b & 0x80) != 0) {
+ // read next byte
+ b = in.read();
+ // EOF
+ if (b < 0) {
+ throw new IOException("Malformed varint: reached EOF mid-varint");
+ }
+ // max 10 bytes for varint 64
+ if (count >= 10) {
+ throw new IOException("Malformed varint: too many bytes (max 10)");
+ }
+ mVarIntBuffer[count++] = (byte) b;
+ }
+ return count;
+ }
+
+ /**
+ * Parses a varint from the given raw bytes and returns it as a long.
+ *
+ * @param rawVarint The bytes representing the varint
+ * @param byteLength The number of bytes to read from rawVarint
+ * @return The decoded long value
+ */
+ private static long parseVarint(byte[] rawVarint, int byteLength) throws IOException {
+ long result = 0;
+ int shift = 0;
+ for (int i = 0; i < byteLength; i++) {
+ result |= ((rawVarint[i] & 0x7F) << shift);
+ shift += 7;
+ if (shift > 63) {
+ throw new IOException("Malformed varint: exceeds 64 bits");
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Copies the wire data for a single field from {@code in} to {@code out},
+ * assuming we have already read the field's tag.
+ *
+ * @param in The input stream (protobuf data)
+ * @param out The output stream
+ * @param wireType The wire type (0=varint, 1=fixed64, 2=length-delim, 5=fixed32)
+ * @throws IOException if reading/writing fails or data is malformed
+ */
+ private void copyFieldData(InputStream in, OutputStream out, int wireType)
+ throws IOException {
+ switch (wireType) {
+ case ProtoStream.WIRE_TYPE_VARINT:
+ copyVarint(in, out);
+ break;
+ case ProtoStream.WIRE_TYPE_FIXED64:
+ copyFixed(in, out, 8);
+ break;
+ case ProtoStream.WIRE_TYPE_LENGTH_DELIMITED:
+ copyLengthDelimited(in, out);
+ break;
+ case ProtoStream.WIRE_TYPE_FIXED32:
+ copyFixed(in, out, 4);
+ break;
+ // case WIRE_TYPE_START_GROUP:
+ // Not Supported
+ // case WIRE_TYPE_END_GROUP:
+ // Not Supported
+ default:
+ // Error or unrecognized wire type
+ throw new IOException("Unknown or unsupported wire type: " + wireType);
+ }
+ }
+
+ /**
+ * Skips the wire data for a single field from {@code in},
+ * assuming the field's tag was already read.
+ */
+ private void skipFieldData(InputStream in, int wireType) throws IOException {
+ switch (wireType) {
+ case ProtoStream.WIRE_TYPE_VARINT:
+ skipVarint(in);
+ break;
+ case ProtoStream.WIRE_TYPE_FIXED64:
+ skipBytes(in, 8);
+ break;
+ case ProtoStream.WIRE_TYPE_LENGTH_DELIMITED:
+ skipLengthDelimited(in);
+ break;
+ case ProtoStream.WIRE_TYPE_FIXED32:
+ skipBytes(in, 4);
+ break;
+ // case WIRE_TYPE_START_GROUP:
+ // Not Supported
+ // case WIRE_TYPE_END_GROUP:
+ // Not Supported
+ default:
+ throw new IOException("Unknown or unsupported wire type: " + wireType);
+ }
+ }
+
+ /** Copies a varint (the field's value) from in to out. */
+ private static void copyVarint(InputStream in, OutputStream out) throws IOException {
+ while (true) {
+ int b = in.read();
+ if (b < 0) {
+ throw new IOException("EOF while copying varint");
+ }
+ out.write(b);
+ if ((b & 0x80) == 0) {
+ break;
+ }
+ }
+ }
+
+ /**
+ * Copies exactly {@code length} bytes from {@code in} to {@code out}.
+ */
+ private void copyFixed(InputStream in, OutputStream out,
+ int length) throws IOException {
+ int toRead = length;
+ while (toRead > 0) {
+ int chunk = Math.min(toRead, mBuffer.length);
+ int readCount = in.read(mBuffer, 0, chunk);
+ if (readCount < 0) {
+ throw new IOException("EOF while copying fixed" + (length * 8) + " field");
+ }
+ out.write(mBuffer, 0, readCount);
+ toRead -= readCount;
+ }
+ }
+
+ /** Copies a length-delimited field */
+ private void copyLengthDelimited(InputStream in,
+ OutputStream out) throws IOException {
+ // 1) read length varint (and copy)
+ int lengthVarintLength = readRawVarint(in);
+ if (lengthVarintLength <= 0) {
+ throw new IOException("EOF reading length for length-delimited field");
+ }
+ out.write(mVarIntBuffer, 0, lengthVarintLength);
+
+ long lengthVal = parseVarint(mVarIntBuffer, lengthVarintLength);
+ if (lengthVal < 0 || lengthVal > Integer.MAX_VALUE) {
+ throw new IOException("Invalid length for length-delimited field: " + lengthVal);
+ }
+
+ // 2) copy that many bytes
+ copyFixed(in, out, (int) lengthVal);
+ }
+
+ /** Skips a varint in the input (does not write anything). */
+ private static void skipVarint(InputStream in) throws IOException {
+ int bytesSkipped = 0;
+ while (true) {
+ int b = in.read();
+ if (b < 0) {
+ throw new IOException("EOF while skipping varint");
+ }
+ if ((b & 0x80) == 0) {
+ break;
+ }
+ bytesSkipped++;
+ if (bytesSkipped > 10) {
+ throw new IOException("Malformed varint: exceeds maximum length of 10 bytes");
+ }
+ }
+ }
+
+ /** Skips exactly n bytes. */
+ private void skipBytes(InputStream in, long n) throws IOException {
+ long skipped = in.skip(n);
+ // If skip fails, fallback to reading the remaining bytes
+ if (skipped < n) {
+ long bytesRemaining = n - skipped;
+
+ while (bytesRemaining > 0) {
+ int bytesToRead = (int) Math.min(bytesRemaining, mBuffer.length);
+ int bytesRead = in.read(mBuffer, 0, bytesToRead);
+ if (bytesRemaining < 0) {
+ throw new IOException("EOF while skipping bytes");
+ }
+ bytesRemaining -= bytesRead;
+ }
+ }
+ }
+
+ /**
+ * Skips a length-delimited field.
+ * 1) read the length as varint,
+ * 2) skip that many bytes
+ */
+ private void skipLengthDelimited(InputStream in) throws IOException {
+ int lengthVarintLength = readRawVarint(in);
+ if (lengthVarintLength <= 0) {
+ throw new IOException("EOF reading length for length-delimited field");
+ }
+ long lengthVal = parseVarint(mVarIntBuffer, lengthVarintLength);
+ if (lengthVal < 0 || lengthVal > Integer.MAX_VALUE) {
+ throw new IOException("Invalid length to skip: " + lengthVal);
+ }
+ skipBytes(in, lengthVal);
+ }
+
+}
diff --git a/core/java/android/view/Choreographer.java b/core/java/android/view/Choreographer.java
index 992790e..053ccdd 100644
--- a/core/java/android/view/Choreographer.java
+++ b/core/java/android/view/Choreographer.java
@@ -250,11 +250,14 @@
/**
* Set flag to indicate that client is blocked waiting for buffer release and
- * buffer stuffing recovery should soon begin.
+ * buffer stuffing recovery should soon begin. This is provided with the
+ * duration of time in nanoseconds that the client was blocked for.
* @hide
*/
- public void onWaitForBufferRelease() {
- mBufferStuffingState.isStuffed.set(true);
+ public void onWaitForBufferRelease(long durationNanos) {
+ if (durationNanos > mLastFrameIntervalNanos / 2) {
+ mBufferStuffingState.isStuffed.set(true);
+ }
}
/**
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index 780e761..dd32947 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -2391,4 +2391,9 @@
}
}
}
+
+ @Override
+ public CharSequence getAccessibilityClassName() {
+ return SurfaceView.class.getName();
+ }
}
diff --git a/core/java/android/view/TextureView.java b/core/java/android/view/TextureView.java
index ebc86ee..0c6eaae 100644
--- a/core/java/android/view/TextureView.java
+++ b/core/java/android/view/TextureView.java
@@ -918,6 +918,11 @@
mLastFrameTimeMillis = now;
}
+ @Override
+ public CharSequence getAccessibilityClassName() {
+ return TextureView.class.getName();
+ }
+
@UnsupportedAppUsage
private final SurfaceTexture.OnFrameAvailableListener mUpdateListener =
surfaceTexture -> {
diff --git a/core/java/android/widget/EditText.java b/core/java/android/widget/EditText.java
index 3e0161a..2056e22 100644
--- a/core/java/android/widget/EditText.java
+++ b/core/java/android/widget/EditText.java
@@ -57,8 +57,8 @@
* Choosing the input type configures the keyboard type that is shown, acceptable characters,
* and appearance of the edit text.
* For example, if you want to accept a secret number, like a unique pin or serial number,
- * you can set inputType to "numericPassword".
- * An inputType of "numericPassword" results in an edit text that accepts numbers only,
+ * you can set inputType to {@link android.R.styleable#TextView_inputType numberPassword}.
+ * An input type of {@code numberPassword} results in an edit text that accepts numbers only,
* shows a numeric keyboard when focused, and masks the text that is entered for privacy.
* <p>
* See the <a href="{@docRoot}guide/topics/ui/controls/text.html">Text Fields</a>
diff --git a/core/java/android/window/ITaskOrganizerController.aidl b/core/java/android/window/ITaskOrganizerController.aidl
index 1748b9d..8934cf6 100644
--- a/core/java/android/window/ITaskOrganizerController.aidl
+++ b/core/java/android/window/ITaskOrganizerController.aidl
@@ -39,7 +39,12 @@
*/
void unregisterTaskOrganizer(ITaskOrganizer organizer);
- /** Creates a persistent root task in WM for a particular windowing-mode. */
+ /**
+ * Creates a persistent root task in WM for a particular windowing-mode.
+ *
+ * It may be removed using {@link #deleteRootTask} or through
+ * {@link WindowContainerTransaction#removeRootTask}.
+ */
void createRootTask(int displayId, int windowingMode, IBinder launchCookie,
boolean removeWithTaskOrganizer);
diff --git a/core/java/android/window/WindowContainerTransaction.java b/core/java/android/window/WindowContainerTransaction.java
index 02f8e2f..ce0ccd5 100644
--- a/core/java/android/window/WindowContainerTransaction.java
+++ b/core/java/android/window/WindowContainerTransaction.java
@@ -614,6 +614,10 @@
/**
* Finds and removes a task and its children using its container token. The task is removed
* from recents.
+ *
+ * If the task is a root task, its leaves are removed but the root task is not. Use
+ * {@link #removeRootTask(WindowContainerToken)} to remove the root task.
+ *
* @param containerToken ContainerToken of Task to be removed
*/
@NonNull
@@ -623,6 +627,19 @@
}
/**
+ * Finds and removes a root task created by an organizer and its leaves using its container
+ * token.
+ *
+ * @param containerToken ContainerToken of the root task to be removed
+ * @hide
+ */
+ @NonNull
+ public WindowContainerTransaction removeRootTask(@NonNull WindowContainerToken containerToken) {
+ mHierarchyOps.add(HierarchyOp.createForRemoveRootTask(containerToken.asBinder()));
+ return this;
+ }
+
+ /**
* Sets whether a container is being drag-resized.
* When {@code true}, the client will reuse a single (larger) surface size to avoid
* continuous allocations on every size change.
@@ -1573,6 +1590,7 @@
public static final int HIERARCHY_OP_TYPE_SET_EXCLUDE_INSETS_TYPES = 21;
public static final int HIERARCHY_OP_TYPE_SET_KEYGUARD_STATE = 22;
public static final int HIERARCHY_OP_TYPE_SET_DISABLE_LAUNCH_ADJACENT = 23;
+ public static final int HIERARCHY_OP_TYPE_REMOVE_ROOT_TASK = 24;
@IntDef(prefix = {"HIERARCHY_OP_TYPE_"}, value = {
HIERARCHY_OP_TYPE_REPARENT,
@@ -1598,7 +1616,8 @@
HIERARCHY_OP_TYPE_RESTORE_BACK_NAVIGATION,
HIERARCHY_OP_TYPE_SET_EXCLUDE_INSETS_TYPES,
HIERARCHY_OP_TYPE_SET_KEYGUARD_STATE,
- HIERARCHY_OP_TYPE_SET_DISABLE_LAUNCH_ADJACENT
+ HIERARCHY_OP_TYPE_SET_DISABLE_LAUNCH_ADJACENT,
+ HIERARCHY_OP_TYPE_REMOVE_ROOT_TASK,
})
@Retention(RetentionPolicy.SOURCE)
public @interface HierarchyOpType {
@@ -1795,6 +1814,18 @@
.build();
}
+ /**
+ * Creates a hierarchy op for deleting a root task
+ *
+ * @hide
+ **/
+ @NonNull
+ public static HierarchyOp createForRemoveRootTask(@NonNull IBinder container) {
+ return new HierarchyOp.Builder(HIERARCHY_OP_TYPE_REMOVE_ROOT_TASK)
+ .setContainer(container)
+ .build();
+ }
+
/** Creates a hierarchy op for clearing adjacent root tasks. */
@NonNull
public static HierarchyOp createForClearAdjacentRoots(@NonNull IBinder root) {
@@ -2012,6 +2043,7 @@
return "removeInsetsFrameProvider";
case HIERARCHY_OP_TYPE_SET_ALWAYS_ON_TOP: return "setAlwaysOnTop";
case HIERARCHY_OP_TYPE_REMOVE_TASK: return "removeTask";
+ case HIERARCHY_OP_TYPE_REMOVE_ROOT_TASK: return "removeRootTask";
case HIERARCHY_OP_TYPE_FINISH_ACTIVITY: return "finishActivity";
case HIERARCHY_OP_TYPE_CLEAR_ADJACENT_ROOTS: return "clearAdjacentRoots";
case HIERARCHY_OP_TYPE_SET_REPARENT_LEAF_TASK_IF_RELAUNCH:
@@ -2096,6 +2128,9 @@
case HIERARCHY_OP_TYPE_REMOVE_TASK:
sb.append("task=").append(mContainer);
break;
+ case HIERARCHY_OP_TYPE_REMOVE_ROOT_TASK:
+ sb.append("rootTask=").append(mContainer);
+ break;
case HIERARCHY_OP_TYPE_FINISH_ACTIVITY:
sb.append("activity=").append(mContainer);
break;
diff --git a/core/java/android/window/flags/windowing_sdk.aconfig b/core/java/android/window/flags/windowing_sdk.aconfig
index f178b0e..1b946af 100644
--- a/core/java/android/window/flags/windowing_sdk.aconfig
+++ b/core/java/android/window/flags/windowing_sdk.aconfig
@@ -178,3 +178,10 @@
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ namespace: "windowing_sdk"
+ name: "safe_region_letterboxing"
+ description: "Enables letterboxing for a safe region"
+ bug: "380132497"
+}
diff --git a/core/java/com/android/internal/jank/Cuj.java b/core/java/com/android/internal/jank/Cuj.java
index 158b526..928fa8c 100644
--- a/core/java/com/android/internal/jank/Cuj.java
+++ b/core/java/com/android/internal/jank/Cuj.java
@@ -265,8 +265,17 @@
*/
public static final int CUJ_LAUNCHER_OVERVIEW_TASK_DISMISS = 121;
+ /**
+ * Track closing task in Desktop Windowing.
+ *
+ * <p> Tracking begins when the CloseDesktopTaskTransitionHandler in Launcher starts
+ * animating the task closure. This is triggered when the close button in the app header is
+ * clicked on a desktop window. </p>
+ */
+ public static final int CUJ_DESKTOP_MODE_CLOSE_TASK = 122;
+
// When adding a CUJ, update this and make sure to also update CUJ_TO_STATSD_INTERACTION_TYPE.
- @VisibleForTesting static final int LAST_CUJ = CUJ_LAUNCHER_OVERVIEW_TASK_DISMISS;
+ @VisibleForTesting static final int LAST_CUJ = CUJ_DESKTOP_MODE_CLOSE_TASK;
/** @hide */
@IntDef({
@@ -379,7 +388,8 @@
CUJ_DESKTOP_MODE_SNAP_RESIZE,
CUJ_DESKTOP_MODE_UNMAXIMIZE_WINDOW,
CUJ_DESKTOP_MODE_ENTER_FROM_OVERVIEW_MENU,
- CUJ_LAUNCHER_OVERVIEW_TASK_DISMISS
+ CUJ_LAUNCHER_OVERVIEW_TASK_DISMISS,
+ CUJ_DESKTOP_MODE_CLOSE_TASK
})
@Retention(RetentionPolicy.SOURCE)
public @interface CujType {}
@@ -503,6 +513,7 @@
CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_DESKTOP_MODE_UNMAXIMIZE_WINDOW] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__DESKTOP_MODE_UNMAXIMIZE_WINDOW;
CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_DESKTOP_MODE_ENTER_FROM_OVERVIEW_MENU] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__DESKTOP_MODE_ENTER_FROM_OVERVIEW_MENU;
CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_OVERVIEW_TASK_DISMISS] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_OVERVIEW_TASK_DISMISS;
+ CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_DESKTOP_MODE_CLOSE_TASK] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__DESKTOP_MODE_CLOSE_TASK;
}
private Cuj() {
@@ -741,6 +752,8 @@
return "DESKTOP_MODE_ENTER_FROM_OVERVIEW_MENU";
case CUJ_LAUNCHER_OVERVIEW_TASK_DISMISS:
return "LAUNCHER_OVERVIEW_TASK_DISMISS";
+ case CUJ_DESKTOP_MODE_CLOSE_TASK:
+ return "DESKTOP_MODE_CLOSE_TASK";
}
return "UNKNOWN";
}
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/jni/android_graphics_BLASTBufferQueue.cpp b/core/jni/android_graphics_BLASTBufferQueue.cpp
index 7b61a5d..10d6d33 100644
--- a/core/jni/android_graphics_BLASTBufferQueue.cpp
+++ b/core/jni/android_graphics_BLASTBufferQueue.cpp
@@ -107,10 +107,11 @@
}
}
- void onWaitForBufferRelease() {
+ void onWaitForBufferRelease(const nsecs_t durationNanos) {
JNIEnv* env = getenv(mVm);
getenv(mVm)->CallVoidMethod(mWaitForBufferReleaseObject,
- gWaitForBufferReleaseCallback.onWaitForBufferRelease);
+ gWaitForBufferReleaseCallback.onWaitForBufferRelease,
+ durationNanos);
DieIfException(env, "Uncaught exception in WaitForBufferReleaseCallback.");
}
@@ -255,7 +256,9 @@
} else {
sp<WaitForBufferReleaseCallbackWrapper> wrapper =
new WaitForBufferReleaseCallbackWrapper{env, waitForBufferReleaseCallback};
- queue->setWaitForBufferReleaseCallback([wrapper]() { wrapper->onWaitForBufferRelease(); });
+ queue->setWaitForBufferReleaseCallback([wrapper](const nsecs_t durationNanos) {
+ wrapper->onWaitForBufferRelease(durationNanos);
+ });
}
}
@@ -305,7 +308,7 @@
jclass waitForBufferReleaseClass =
FindClassOrDie(env, "android/graphics/BLASTBufferQueue$WaitForBufferReleaseCallback");
gWaitForBufferReleaseCallback.onWaitForBufferRelease =
- GetMethodIDOrDie(env, waitForBufferReleaseClass, "onWaitForBufferRelease", "()V");
+ GetMethodIDOrDie(env, waitForBufferReleaseClass, "onWaitForBufferRelease", "(J)V");
return 0;
}
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/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 7327970..aad8f8a 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -8324,16 +8324,15 @@
<!-- Allows an application to perform actions on behalf of users inside of
applications.
- <p>This permission is currently only granted to preinstalled / system apps having the
- {@link android.app.role.ASSISTANT} role.
+ <p>This permission is currently only granted to privileged system apps.
<p>Apps contributing app functions can opt to disallow callers with this permission,
limiting to only callers with {@link android.permission.EXECUTE_APP_FUNCTIONS_TRUSTED}
instead.
- <p>Protection level: internal|role
+ <p>Protection level: internal|privileged
@FlaggedApi(android.app.appfunctions.flags.Flags.FLAG_ENABLE_APP_FUNCTION_MANAGER) -->
<permission android:name="android.permission.EXECUTE_APP_FUNCTIONS"
android:featureFlag="android.app.appfunctions.flags.enable_app_function_manager"
- android:protectionLevel="internal|role" />
+ android:protectionLevel="internal|privileged" />
<!-- Allows an application to display its suggestions using the autofill framework.
<p>For now, this permission is only granted to the Browser application.
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/app/NotificationManagerTest.java b/core/tests/coretests/src/android/app/NotificationManagerTest.java
index 18ba6a1..4143229 100644
--- a/core/tests/coretests/src/android/app/NotificationManagerTest.java
+++ b/core/tests/coretests/src/android/app/NotificationManagerTest.java
@@ -30,8 +30,10 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.annotation.Nullable;
import android.content.Context;
import android.content.ContextWrapper;
+import android.content.pm.PackageManager;
import android.content.pm.ParceledListSlice;
import android.os.UserHandle;
import android.platform.test.annotations.EnableFlags;
@@ -263,6 +265,38 @@
}
@Test
+ @EnableFlags({Flags.FLAG_NM_BINDER_PERF_THROTTLE_NOTIFY,
+ Flags.FLAG_NM_BINDER_PERF_LOG_NM_THROTTLING})
+ public void notify_rapidUpdate_logsOncePerSecond() throws Exception {
+ Notification n = exampleNotification();
+
+ for (int i = 0; i < 650; i++) {
+ mNotificationManager.notify(1, n);
+ mClock.advanceByMillis(10);
+ }
+
+ // Runs for a total of 6.5 seconds, so should log once (when RateEstimator catches up) + 6
+ // more times (after 1 second each).
+ verify(mNotificationManager.mBackendService, times(7)).incrementCounter(
+ eq("notifications.value_client_throttled_notify_update"));
+ }
+
+ @Test
+ @EnableFlags({Flags.FLAG_NM_BINDER_PERF_THROTTLE_NOTIFY,
+ Flags.FLAG_NM_BINDER_PERF_LOG_NM_THROTTLING})
+ public void cancel_unnecessaryAndRapid_logsOncePerSecond() throws Exception {
+ for (int i = 0; i < 650; i++) {
+ mNotificationManager.cancel(1);
+ mClock.advanceByMillis(10);
+ }
+
+ // Runs for a total of 6.5 seconds, so should log once (when RateEstimator catches up) + 6
+ // more times (after 1 second each).
+ verify(mNotificationManager.mBackendService, times(7)).incrementCounter(
+ eq("notifications.value_client_throttled_cancel_duplicate"));
+ }
+
+ @Test
@EnableFlags(Flags.FLAG_NM_BINDER_PERF_CACHE_CHANNELS)
public void getNotificationChannel_cachedUntilInvalidated() throws Exception {
// Invalidate the cache first because the cache won't do anything until then
@@ -409,6 +443,46 @@
.getOrCreateNotificationChannels(any(), any(), anyInt(), anyBoolean());
}
+ @Test
+ @EnableFlags({Flags.FLAG_MODES_API, Flags.FLAG_MODES_UI})
+ public void areAutomaticZenRulesUserManaged_handheld_isTrue() {
+ PackageManager pm = mock(PackageManager.class);
+ when(pm.hasSystemFeature(any())).thenReturn(false);
+ mContext.setPackageManager(pm);
+
+ assertThat(mNotificationManager.areAutomaticZenRulesUserManaged()).isTrue();
+ }
+
+ @Test
+ @EnableFlags({Flags.FLAG_MODES_API, Flags.FLAG_MODES_UI})
+ public void areAutomaticZenRulesUserManaged_auto_isFalse() {
+ PackageManager pm = mock(PackageManager.class);
+ when(pm.hasSystemFeature(eq(PackageManager.FEATURE_AUTOMOTIVE))).thenReturn(true);
+ mContext.setPackageManager(pm);
+
+ assertThat(mNotificationManager.areAutomaticZenRulesUserManaged()).isFalse();
+ }
+
+ @Test
+ @EnableFlags({Flags.FLAG_MODES_API, Flags.FLAG_MODES_UI})
+ public void areAutomaticZenRulesUserManaged_tv_isFalse() {
+ PackageManager pm = mock(PackageManager.class);
+ when(pm.hasSystemFeature(eq(PackageManager.FEATURE_LEANBACK))).thenReturn(true);
+ mContext.setPackageManager(pm);
+
+ assertThat(mNotificationManager.areAutomaticZenRulesUserManaged()).isFalse();
+ }
+
+ @Test
+ @EnableFlags({Flags.FLAG_MODES_API, Flags.FLAG_MODES_UI})
+ public void areAutomaticZenRulesUserManaged_watch_isFalse() {
+ PackageManager pm = mock(PackageManager.class);
+ when(pm.hasSystemFeature(eq(PackageManager.FEATURE_WATCH))).thenReturn(true);
+ mContext.setPackageManager(pm);
+
+ assertThat(mNotificationManager.areAutomaticZenRulesUserManaged()).isFalse();
+ }
+
private Notification exampleNotification() {
return new Notification.Builder(mContext, "channel")
.setSmallIcon(android.R.drawable.star_big_on)
@@ -438,6 +512,7 @@
// Helper context wrapper class where we can control just the return values of getPackageName,
// getOpPackageName, and getUserId (used in getNotificationChannels).
private static class PackageTestableContext extends ContextWrapper {
+ private PackageManager mPm;
private String mPackage;
private String mOpPackage;
private Integer mUserId;
@@ -446,6 +521,10 @@
super(base);
}
+ void setPackageManager(@Nullable PackageManager pm) {
+ mPm = pm;
+ }
+
void setParameters(String packageName, String opPackageName, int userId) {
mPackage = packageName;
mOpPackage = opPackageName;
@@ -453,6 +532,12 @@
}
@Override
+ public PackageManager getPackageManager() {
+ if (mPm != null) return mPm;
+ return super.getPackageManager();
+ }
+
+ @Override
public String getPackageName() {
if (mPackage != null) return mPackage;
return super.getPackageName();
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/core/tests/utiltests/src/android/util/proto/ProtoFieldFilterTest.java b/core/tests/utiltests/src/android/util/proto/ProtoFieldFilterTest.java
new file mode 100644
index 0000000..76d0aaa
--- /dev/null
+++ b/core/tests/utiltests/src/android/util/proto/ProtoFieldFilterTest.java
@@ -0,0 +1,230 @@
+/*
+ * 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 android.util.proto;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+
+/**
+ * Unit tests for {@link android.util.proto.ProtoFieldFilter}.
+ *
+ * Build/Install/Run:
+ * atest FrameworksCoreTests:ProtoFieldFilterTest
+ *
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class ProtoFieldFilterTest {
+
+ private static final class FieldTypes {
+ static final long INT64 = ProtoStream.FIELD_TYPE_INT64 | ProtoStream.FIELD_COUNT_SINGLE;
+ static final long FIXED64 = ProtoStream.FIELD_TYPE_FIXED64 | ProtoStream.FIELD_COUNT_SINGLE;
+ static final long BYTES = ProtoStream.FIELD_TYPE_BYTES | ProtoStream.FIELD_COUNT_SINGLE;
+ static final long FIXED32 = ProtoStream.FIELD_TYPE_FIXED32 | ProtoStream.FIELD_COUNT_SINGLE;
+ static final long MESSAGE = ProtoStream.FIELD_TYPE_MESSAGE | ProtoStream.FIELD_COUNT_SINGLE;
+ static final long INT32 = ProtoStream.FIELD_TYPE_INT32 | ProtoStream.FIELD_COUNT_SINGLE;
+ }
+
+ private static ProtoOutputStream createBasicTestProto() {
+ ProtoOutputStream out = new ProtoOutputStream();
+
+ out.writeInt64(ProtoStream.makeFieldId(1, FieldTypes.INT64), 12345L);
+ out.writeFixed64(ProtoStream.makeFieldId(2, FieldTypes.FIXED64), 0x1234567890ABCDEFL);
+ out.writeBytes(ProtoStream.makeFieldId(3, FieldTypes.BYTES), new byte[]{1, 2, 3, 4, 5});
+ out.writeFixed32(ProtoStream.makeFieldId(4, FieldTypes.FIXED32), 0xDEADBEEF);
+
+ return out;
+ }
+
+ private static byte[] filterProto(byte[] input, ProtoFieldFilter filter) throws IOException {
+ ByteArrayInputStream inputStream = new ByteArrayInputStream(input);
+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ filter.filter(inputStream, outputStream);
+ return outputStream.toByteArray();
+ }
+
+ @Test
+ public void testNoFieldsFiltered() throws IOException {
+ byte[] input = createBasicTestProto().getBytes();
+ byte[] output = filterProto(input, new ProtoFieldFilter(fieldNumber -> true));
+ assertArrayEquals("No fields should be filtered out", input, output);
+ }
+
+ @Test
+ public void testAllFieldsFiltered() throws IOException {
+ byte[] input = createBasicTestProto().getBytes();
+ byte[] output = filterProto(input, new ProtoFieldFilter(fieldNumber -> false));
+
+ assertEquals("All fields should be filtered out", 0, output.length);
+ }
+
+ @Test
+ public void testSpecificFieldsFiltered() throws IOException {
+
+ ProtoOutputStream out = createBasicTestProto();
+ byte[] output = filterProto(out.getBytes(), new ProtoFieldFilter(n -> n != 2));
+
+ ProtoInputStream in = new ProtoInputStream(output);
+ boolean[] fieldsFound = new boolean[5];
+
+ int fieldNumber;
+ while ((fieldNumber = in.nextField()) != ProtoInputStream.NO_MORE_FIELDS) {
+ fieldsFound[fieldNumber] = true;
+ switch (fieldNumber) {
+ case 1:
+ assertEquals(12345L, in.readLong(ProtoStream.makeFieldId(1, FieldTypes.INT64)));
+ break;
+ case 2:
+ fail("Field 2 should be filtered out");
+ break;
+ case 3:
+ assertArrayEquals(new byte[]{1, 2, 3, 4, 5},
+ in.readBytes(ProtoStream.makeFieldId(3, FieldTypes.BYTES)));
+ break;
+ case 4:
+ assertEquals(0xDEADBEEF,
+ in.readInt(ProtoStream.makeFieldId(4, FieldTypes.FIXED32)));
+ break;
+ default:
+ fail("Unexpected field number: " + fieldNumber);
+ }
+ }
+
+ assertTrue("Field 1 should be present", fieldsFound[1]);
+ assertFalse("Field 2 should be filtered", fieldsFound[2]);
+ assertTrue("Field 3 should be present", fieldsFound[3]);
+ assertTrue("Field 4 should be present", fieldsFound[4]);
+ }
+
+ @Test
+ public void testDifferentWireTypes() throws IOException {
+ ProtoOutputStream out = new ProtoOutputStream();
+
+ out.writeInt64(ProtoStream.makeFieldId(1, FieldTypes.INT64), 12345L);
+ out.writeFixed64(ProtoStream.makeFieldId(2, FieldTypes.FIXED64), 0x1234567890ABCDEFL);
+ out.writeBytes(ProtoStream.makeFieldId(3, FieldTypes.BYTES), new byte[]{10, 20, 30});
+
+ long token = out.start(ProtoStream.makeFieldId(4, FieldTypes.MESSAGE));
+ out.writeInt32(ProtoStream.makeFieldId(1, FieldTypes.INT32), 42);
+ out.end(token);
+
+ out.writeFixed32(ProtoStream.makeFieldId(5, FieldTypes.FIXED32), 0xDEADBEEF);
+
+ byte[] output = filterProto(out.getBytes(), new ProtoFieldFilter(fieldNumber -> true));
+
+ ProtoInputStream in = new ProtoInputStream(output);
+ boolean[] fieldsFound = new boolean[6];
+
+ int fieldNumber;
+ while ((fieldNumber = in.nextField()) != ProtoInputStream.NO_MORE_FIELDS) {
+ fieldsFound[fieldNumber] = true;
+ switch (fieldNumber) {
+ case 1:
+ assertEquals(12345L, in.readLong(ProtoStream.makeFieldId(1, FieldTypes.INT64)));
+ break;
+ case 2:
+ assertEquals(0x1234567890ABCDEFL,
+ in.readLong(ProtoStream.makeFieldId(2, FieldTypes.FIXED64)));
+ break;
+ case 3:
+ assertArrayEquals(new byte[]{10, 20, 30},
+ in.readBytes(ProtoStream.makeFieldId(3, FieldTypes.BYTES)));
+ break;
+ case 4:
+ token = in.start(ProtoStream.makeFieldId(4, FieldTypes.MESSAGE));
+ assertTrue(in.nextField() == 1);
+ assertEquals(42, in.readInt(ProtoStream.makeFieldId(1, FieldTypes.INT32)));
+ assertTrue(in.nextField() == ProtoInputStream.NO_MORE_FIELDS);
+ in.end(token);
+ break;
+ case 5:
+ assertEquals(0xDEADBEEF,
+ in.readInt(ProtoStream.makeFieldId(5, FieldTypes.FIXED32)));
+ break;
+ default:
+ fail("Unexpected field number: " + fieldNumber);
+ }
+ }
+
+ assertTrue("All fields should be present",
+ fieldsFound[1] && fieldsFound[2] && fieldsFound[3]
+ && fieldsFound[4] && fieldsFound[5]);
+ }
+ @Test
+ public void testNestedMessagesUnfiltered() throws IOException {
+ ProtoOutputStream out = new ProtoOutputStream();
+
+ out.writeInt64(ProtoStream.makeFieldId(1, FieldTypes.INT64), 12345L);
+
+ long token = out.start(ProtoStream.makeFieldId(2, FieldTypes.MESSAGE));
+ out.writeInt32(ProtoStream.makeFieldId(1, FieldTypes.INT32), 6789);
+ out.writeFixed32(ProtoStream.makeFieldId(2, FieldTypes.FIXED32), 0xCAFEBABE);
+ out.end(token);
+
+ byte[] output = filterProto(out.getBytes(), new ProtoFieldFilter(n -> n != 2));
+
+ // Verify output
+ ProtoInputStream in = new ProtoInputStream(output);
+ boolean[] fieldsFound = new boolean[3];
+
+ int fieldNumber;
+ while ((fieldNumber = in.nextField()) != ProtoInputStream.NO_MORE_FIELDS) {
+ fieldsFound[fieldNumber] = true;
+ if (fieldNumber == 1) {
+ assertEquals(12345L, in.readLong(ProtoStream.makeFieldId(1, FieldTypes.INT64)));
+ } else {
+ fail("Unexpected field number: " + fieldNumber);
+ }
+ }
+
+ assertTrue("Field 1 should be present", fieldsFound[1]);
+ assertFalse("Field 2 should be filtered out", fieldsFound[2]);
+ }
+
+ @Test
+ public void testRepeatedFields() throws IOException {
+
+ ProtoOutputStream out = new ProtoOutputStream();
+ long fieldId = ProtoStream.makeFieldId(1,
+ ProtoStream.FIELD_TYPE_INT32 | ProtoStream.FIELD_COUNT_REPEATED);
+
+ out.writeRepeatedInt32(fieldId, 100);
+ out.writeRepeatedInt32(fieldId, 200);
+ out.writeRepeatedInt32(fieldId, 300);
+
+ byte[] input = out.getBytes();
+
+ byte[] output = filterProto(input, new ProtoFieldFilter(fieldNumber -> true));
+
+ assertArrayEquals("Repeated fields should be preserved", input, output);
+ }
+
+}
diff --git a/graphics/java/android/graphics/BLASTBufferQueue.java b/graphics/java/android/graphics/BLASTBufferQueue.java
index 1c34e0d..9b9be244 100644
--- a/graphics/java/android/graphics/BLASTBufferQueue.java
+++ b/graphics/java/android/graphics/BLASTBufferQueue.java
@@ -61,8 +61,10 @@
/**
* Indicates that the client is waiting on buffer release
* due to no free buffers being available to render into.
+ * @param durationNanos The length of time in nanoseconds
+ * that the client was blocked on buffer release.
*/
- void onWaitForBufferRelease();
+ void onWaitForBufferRelease(long durationNanos);
}
/** Create a new connection with the surface flinger. */
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt
index a7eebd6..9d445f0 100644
--- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt
@@ -36,6 +36,8 @@
import com.android.launcher3.icons.BubbleIconFactory
import com.android.wm.shell.Flags
import com.android.wm.shell.R
+import com.android.wm.shell.bubbles.BubbleStackView.SurfaceSynchronizer
+import com.android.wm.shell.bubbles.Bubbles.BubbleExpandListener
import com.android.wm.shell.bubbles.Bubbles.SysuiProxy
import com.android.wm.shell.bubbles.animation.AnimatableScaleMatrix
import com.android.wm.shell.common.FloatingContentCoordinator
@@ -75,6 +77,7 @@
private lateinit var bubbleTaskViewFactory: BubbleTaskViewFactory
private lateinit var bubbleData: BubbleData
private lateinit var bubbleStackViewManager: FakeBubbleStackViewManager
+ private lateinit var surfaceSynchronizer: FakeSurfaceSynchronizer
private var sysuiProxy = mock<SysuiProxy>()
@Before
@@ -108,13 +111,14 @@
bubbleStackViewManager = FakeBubbleStackViewManager()
expandedViewManager = FakeBubbleExpandedViewManager()
bubbleTaskViewFactory = FakeBubbleTaskViewFactory(context, shellExecutor)
+ surfaceSynchronizer = FakeSurfaceSynchronizer()
bubbleStackView =
BubbleStackView(
context,
bubbleStackViewManager,
positioner,
bubbleData,
- null,
+ surfaceSynchronizer,
FloatingContentCoordinator(),
{ sysuiProxy },
shellExecutor
@@ -309,6 +313,7 @@
@Test
fun tapDifferentBubble_shouldReorder() {
+ surfaceSynchronizer.isActive = false
val bubble1 = createAndInflateChatBubble(key = "bubble1")
val bubble2 = createAndInflateChatBubble(key = "bubble2")
InstrumentationRegistry.getInstrumentation().runOnMainSync {
@@ -378,6 +383,147 @@
.inOrder()
}
+ @Test
+ fun tapDifferentBubble_imeVisible_shouldWaitForIme() {
+ val bubble1 = createAndInflateChatBubble(key = "bubble1")
+ val bubble2 = createAndInflateChatBubble(key = "bubble2")
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ bubbleStackView.addBubble(bubble1)
+ bubbleStackView.addBubble(bubble2)
+ }
+ InstrumentationRegistry.getInstrumentation().waitForIdleSync()
+
+ assertThat(bubbleStackView.bubbleCount).isEqualTo(2)
+ assertThat(bubbleData.bubbles).hasSize(2)
+ assertThat(bubbleData.selectedBubble).isEqualTo(bubble2)
+ assertThat(bubble2.iconView).isNotNull()
+
+ val expandListener = FakeBubbleExpandListener()
+ bubbleStackView.setExpandListener(expandListener)
+
+ var lastUpdate: BubbleData.Update? = null
+ val semaphore = Semaphore(0)
+ val listener =
+ BubbleData.Listener { update ->
+ lastUpdate = update
+ semaphore.release()
+ }
+ bubbleData.setListener(listener)
+
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ bubble2.iconView!!.performClick()
+ assertThat(bubbleData.isExpanded).isTrue()
+
+ bubbleStackView.setSelectedBubble(bubble2)
+ bubbleStackView.isExpanded = true
+ shellExecutor.flushAll()
+ }
+
+ assertThat(semaphore.tryAcquire(5, TimeUnit.SECONDS)).isTrue()
+ assertThat(lastUpdate!!.expanded).isTrue()
+ assertThat(lastUpdate!!.bubbles.map { it.key })
+ .containsExactly("bubble2", "bubble1")
+ .inOrder()
+
+ // wait for idle to allow the animation to start
+ InstrumentationRegistry.getInstrumentation().waitForIdleSync()
+ // wait for the expansion animation to complete before interacting with the bubbles
+ PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(
+ AnimatableScaleMatrix.SCALE_X, AnimatableScaleMatrix.SCALE_Y)
+
+ // make the IME visible and tap on bubble1 to select it
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ positioner.setImeVisible(true, 100)
+ bubble1.iconView!!.performClick()
+ // we have to set the selected bubble in the stack view manually because we don't have a
+ // listener wired up.
+ bubbleStackView.setSelectedBubble(bubble1)
+ shellExecutor.flushAll()
+ }
+
+ val onImeHidden = bubbleStackViewManager.onImeHidden
+ assertThat(onImeHidden).isNotNull()
+
+ assertThat(expandListener.bubblesExpandedState).isEqualTo(mapOf("bubble2" to true))
+
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ onImeHidden!!.run()
+ shellExecutor.flushAll()
+ }
+
+ assertThat(expandListener.bubblesExpandedState)
+ .isEqualTo(mapOf("bubble1" to true, "bubble2" to false))
+ assertThat(semaphore.tryAcquire(5, TimeUnit.SECONDS)).isTrue()
+ assertThat(bubbleData.selectedBubble).isEqualTo(bubble1)
+ }
+
+ @Test
+ fun tapDifferentBubble_imeHidden_updatesImmediately() {
+ val bubble1 = createAndInflateChatBubble(key = "bubble1")
+ val bubble2 = createAndInflateChatBubble(key = "bubble2")
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ bubbleStackView.addBubble(bubble1)
+ bubbleStackView.addBubble(bubble2)
+ }
+ InstrumentationRegistry.getInstrumentation().waitForIdleSync()
+
+ assertThat(bubbleStackView.bubbleCount).isEqualTo(2)
+ assertThat(bubbleData.bubbles).hasSize(2)
+ assertThat(bubbleData.selectedBubble).isEqualTo(bubble2)
+ assertThat(bubble2.iconView).isNotNull()
+
+ val expandListener = FakeBubbleExpandListener()
+ bubbleStackView.setExpandListener(expandListener)
+
+ var lastUpdate: BubbleData.Update? = null
+ val semaphore = Semaphore(0)
+ val listener =
+ BubbleData.Listener { update ->
+ lastUpdate = update
+ semaphore.release()
+ }
+ bubbleData.setListener(listener)
+
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ bubble2.iconView!!.performClick()
+ assertThat(bubbleData.isExpanded).isTrue()
+
+ bubbleStackView.setSelectedBubble(bubble2)
+ bubbleStackView.isExpanded = true
+ shellExecutor.flushAll()
+ }
+
+ assertThat(semaphore.tryAcquire(5, TimeUnit.SECONDS)).isTrue()
+ assertThat(lastUpdate!!.expanded).isTrue()
+ assertThat(lastUpdate!!.bubbles.map { it.key })
+ .containsExactly("bubble2", "bubble1")
+ .inOrder()
+
+ // wait for idle to allow the animation to start
+ InstrumentationRegistry.getInstrumentation().waitForIdleSync()
+ // wait for the expansion animation to complete before interacting with the bubbles
+ PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(
+ AnimatableScaleMatrix.SCALE_X, AnimatableScaleMatrix.SCALE_Y)
+
+ // make the IME hidden and tap on bubble1 to select it
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ positioner.setImeVisible(false, 0)
+ bubble1.iconView!!.performClick()
+ // we have to set the selected bubble in the stack view manually because we don't have a
+ // listener wired up.
+ bubbleStackView.setSelectedBubble(bubble1)
+ shellExecutor.flushAll()
+ }
+
+ val onImeHidden = bubbleStackViewManager.onImeHidden
+ assertThat(onImeHidden).isNull()
+
+ assertThat(expandListener.bubblesExpandedState)
+ .isEqualTo(mapOf("bubble1" to true, "bubble2" to false))
+ assertThat(semaphore.tryAcquire(5, TimeUnit.SECONDS)).isTrue()
+ assertThat(bubbleData.selectedBubble).isEqualTo(bubble1)
+ }
+
@EnableFlags(Flags.FLAG_ENABLE_OPTIONAL_BUBBLE_OVERFLOW)
@Test
fun testCreateStackView_noOverflowContents_noOverflow() {
@@ -563,4 +709,18 @@
this.onImeHidden = onImeHidden
}
}
+
+ private class FakeBubbleExpandListener : BubbleExpandListener {
+ val bubblesExpandedState = mutableMapOf<String, Boolean>()
+ override fun onBubbleExpandChanged(isExpanding: Boolean, key: String) {
+ bubblesExpandedState[key] = isExpanding
+ }
+ }
+
+ private class FakeSurfaceSynchronizer : SurfaceSynchronizer {
+ var isActive = true
+ override fun syncSurfaceAndRun(callback: Runnable) {
+ if (isActive) callback.run()
+ }
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index 12dfbd9..30d6790 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -1440,9 +1440,9 @@
*
* @param intent the intent for the bubble.
*/
- public void expandStackAndSelectBubble(Intent intent) {
+ public void expandStackAndSelectBubble(Intent intent, UserHandle user) {
if (!Flags.enableBubbleAnything()) return;
- Bubble b = mBubbleData.getOrCreateBubble(intent); // Removes from overflow
+ Bubble b = mBubbleData.getOrCreateBubble(intent, user); // Removes from overflow
ProtoLog.v(WM_SHELL_BUBBLES, "expandStackAndSelectBubble - intent=%s", intent);
if (b.isInflated()) {
mBubbleData.setSelectedBubbleAndExpandStack(b);
@@ -2649,8 +2649,8 @@
}
@Override
- public void showAppBubble(Intent intent) {
- mMainExecutor.execute(() -> mController.expandStackAndSelectBubble(intent));
+ public void showAppBubble(Intent intent, UserHandle user) {
+ mMainExecutor.execute(() -> mController.expandStackAndSelectBubble(intent, user));
}
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
index dc2025b..76d91ed 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
@@ -461,10 +461,8 @@
return bubbleToReturn;
}
- Bubble getOrCreateBubble(Intent intent) {
- UserHandle user = UserHandle.of(mCurrentUserId);
- String bubbleKey = Bubble.getAppBubbleKeyForApp(intent.getPackage(),
- user);
+ Bubble getOrCreateBubble(Intent intent, UserHandle user) {
+ String bubbleKey = Bubble.getAppBubbleKeyForApp(intent.getPackage(), user);
Bubble bubbleToReturn = findAndRemoveBubbleFromOverflow(bubbleKey);
if (bubbleToReturn == null) {
bubbleToReturn = Bubble.createAppBubble(intent, user, null, mMainExecutor, mBgExecutor);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
index 979d958..1094c29 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
@@ -2183,34 +2183,39 @@
ProtoLog.d(WM_SHELL_BUBBLES, "showNewlySelectedBubble b=%s, previouslySelected=%s,"
+ " mIsExpanded=%b", newlySelectedKey, previouslySelectedKey, mIsExpanded);
if (mIsExpanded) {
- hideCurrentInputMethod();
-
- if (Flags.enableRetrievableBubbles()) {
- if (mBubbleData.getBubbles().size() == 1) {
- // First bubble, check if overflow visibility needs to change
- updateOverflowVisibility();
+ Runnable onImeHidden = () -> {
+ if (Flags.enableRetrievableBubbles()) {
+ if (mBubbleData.getBubbles().size() == 1) {
+ // First bubble, check if overflow visibility needs to change
+ updateOverflowVisibility();
+ }
}
+
+ // Make the container of the expanded view transparent before removing the expanded
+ // view from it. Otherwise a punch hole created by {@link android.view.SurfaceView}
+ // in the expanded view becomes visible on the screen. See b/126856255
+ mExpandedViewContainer.setAlpha(0.0f);
+ mSurfaceSynchronizer.syncSurfaceAndRun(() -> {
+ if (previouslySelected != null) {
+ previouslySelected.setTaskViewVisibility(false);
+ }
+
+ updateExpandedBubble();
+ requestUpdate();
+
+ logBubbleEvent(previouslySelected,
+ FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__COLLAPSED);
+ logBubbleEvent(bubbleToSelect,
+ FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__EXPANDED);
+ notifyExpansionChanged(previouslySelected, false /* expanded */);
+ notifyExpansionChanged(bubbleToSelect, true /* expanded */);
+ });
+ };
+ if (mPositioner.isImeVisible()) {
+ hideCurrentInputMethod(onImeHidden);
+ } else {
+ onImeHidden.run();
}
-
- // Make the container of the expanded view transparent before removing the expanded view
- // from it. Otherwise a punch hole created by {@link android.view.SurfaceView} in the
- // expanded view becomes visible on the screen. See b/126856255
- mExpandedViewContainer.setAlpha(0.0f);
- mSurfaceSynchronizer.syncSurfaceAndRun(() -> {
- if (previouslySelected != null) {
- previouslySelected.setTaskViewVisibility(false);
- }
-
- updateExpandedBubble();
- requestUpdate();
-
- logBubbleEvent(previouslySelected,
- FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__COLLAPSED);
- logBubbleEvent(bubbleToSelect,
- FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__EXPANDED);
- notifyExpansionChanged(previouslySelected, false /* expanded */);
- notifyExpansionChanged(bubbleToSelect, true /* expanded */);
- });
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl
index 9c2d3543..0a4d79a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl
@@ -17,8 +17,9 @@
package com.android.wm.shell.bubbles;
import android.content.Intent;
-import android.graphics.Rect;
import android.content.pm.ShortcutInfo;
+import android.graphics.Rect;
+import android.os.UserHandle;
import com.android.wm.shell.bubbles.IBubblesListener;
import com.android.wm.shell.shared.bubbles.BubbleBarLocation;
@@ -52,7 +53,7 @@
oneway void showShortcutBubble(in ShortcutInfo info) = 12;
- oneway void showAppBubble(in Intent intent) = 13;
+ oneway void showAppBubble(in Intent intent, in UserHandle user) = 13;
oneway void showExpandedView() = 14;
}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipMenuController.java
index 85353d3..bad4a93 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipMenuController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipMenuController.java
@@ -18,7 +18,6 @@
import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
import static android.view.WindowManager.LayoutParams.FLAG_SLIPPERY;
-import static android.view.WindowManager.LayoutParams.FLAG_SPLIT_TOUCH;
import static android.view.WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
@@ -111,7 +110,7 @@
int width, int height) {
final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(width, height,
TYPE_APPLICATION_OVERLAY,
- FLAG_WATCH_OUTSIDE_TOUCH | FLAG_SPLIT_TOUCH | FLAG_SLIPPERY | FLAG_NOT_TOUCHABLE,
+ FLAG_WATCH_OUTSIDE_TOUCH | FLAG_SLIPPERY | FLAG_NOT_TOUCHABLE,
PixelFormat.TRANSLUCENT);
lp.privateFlags |= PRIVATE_FLAG_TRUSTED_OVERLAY;
lp.setTitle(title);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitWindowManager.java
index 89573cc..84b710d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitWindowManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitWindowManager.java
@@ -19,7 +19,6 @@
import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
import static android.view.WindowManager.LayoutParams.FLAG_SLIPPERY;
-import static android.view.WindowManager.LayoutParams.FLAG_SPLIT_TOUCH;
import static android.view.WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
@@ -126,7 +125,7 @@
WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
dividerBounds.width(), dividerBounds.height(), TYPE_DOCK_DIVIDER,
FLAG_NOT_FOCUSABLE | FLAG_NOT_TOUCH_MODAL | FLAG_WATCH_OUTSIDE_TOUCH
- | FLAG_SPLIT_TOUCH | FLAG_SLIPPERY,
+ | FLAG_SLIPPERY,
PixelFormat.TRANSLUCENT);
lp.token = new Binder();
lp.setTitle(mWindowName);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index 999c879..67e3453 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -1031,8 +1031,9 @@
static CloseDesktopTaskTransitionHandler provideCloseDesktopTaskTransitionHandler(
Context context,
@ShellMainThread ShellExecutor mainExecutor,
- @ShellAnimationThread ShellExecutor animExecutor) {
- return new CloseDesktopTaskTransitionHandler(context, mainExecutor, animExecutor);
+ @ShellAnimationThread ShellExecutor animExecutor,
+ @ShellMainThread Handler handler) {
+ return new CloseDesktopTaskTransitionHandler(context, mainExecutor, animExecutor, handler);
}
@WMSingleton
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/CloseDesktopTaskTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/CloseDesktopTaskTransitionHandler.kt
index 9b5a289..1ce093e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/CloseDesktopTaskTransitionHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/CloseDesktopTaskTransitionHandler.kt
@@ -23,8 +23,10 @@
import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
import android.content.Context
import android.graphics.Rect
+import android.os.Handler
import android.os.IBinder
import android.util.TypedValue
+import android.view.Choreographer
import android.view.SurfaceControl.Transaction
import android.view.WindowManager
import android.window.TransitionInfo
@@ -32,7 +34,10 @@
import android.window.WindowContainerTransaction
import androidx.core.animation.addListener
import com.android.app.animation.Interpolators
+import com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_CLOSE_TASK
+import com.android.internal.jank.InteractionJankMonitor
import com.android.wm.shell.common.ShellExecutor
+import com.android.wm.shell.shared.annotations.ShellMainThread
import com.android.wm.shell.transition.Transitions
import java.util.function.Supplier
@@ -44,9 +49,11 @@
private val mainExecutor: ShellExecutor,
private val animExecutor: ShellExecutor,
private val transactionSupplier: Supplier<Transaction> = Supplier { Transaction() },
+ @ShellMainThread private val handler: Handler,
) : Transitions.TransitionHandler {
private val runningAnimations = mutableMapOf<IBinder, List<Animator>>()
+ private val interactionJankMonitor = InteractionJankMonitor.getInstance()
/** Returns null, as it only handles transitions started from Shell. */
override fun handleRequest(
@@ -71,18 +78,27 @@
// All animations completed, finish the transition
runningAnimations.remove(transition)
finishCallback.onTransitionFinished(/* wct= */ null)
+ interactionJankMonitor.end(CUJ_DESKTOP_MODE_CLOSE_TASK)
}
}
}
+ val closingChanges =
+ info.changes.filter {
+ it.mode == WindowManager.TRANSIT_CLOSE &&
+ it.taskInfo?.windowingMode == WINDOWING_MODE_FREEFORM
+ }
animations +=
- info.changes
- .filter {
- it.mode == WindowManager.TRANSIT_CLOSE &&
- it.taskInfo?.windowingMode == WINDOWING_MODE_FREEFORM
- }
- .map { createCloseAnimation(it, finishTransaction, onAnimFinish) }
+ closingChanges.map { createCloseAnimation(it, finishTransaction, onAnimFinish) }
if (animations.isEmpty()) return false
runningAnimations[transition] = animations
+ closingChanges.lastOrNull()?.leash?.let { lastChangeLeash ->
+ interactionJankMonitor.begin(
+ lastChangeLeash,
+ context,
+ handler,
+ CUJ_DESKTOP_MODE_CLOSE_TASK,
+ )
+ }
animExecutor.execute { animations.forEach(Animator::start) }
return true
}
@@ -127,6 +143,7 @@
.get()
.setPosition(change.leash, animBounds.left.toFloat(), animBounds.top.toFloat())
.setScale(change.leash, animScale, animScale)
+ .setFrameTimeline(Choreographer.getInstance().vsyncId)
.apply()
}
}
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/DesktopHeaderManageWindowsMenu.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopHeaderManageWindowsMenu.kt
index ff52a45..575aac38 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopHeaderManageWindowsMenu.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopHeaderManageWindowsMenu.kt
@@ -74,8 +74,7 @@
override fun addToContainer(menuView: ManageWindowsView) {
val menuPosition = Point(x, y)
val flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or
- WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH or
- WindowManager.LayoutParams.FLAG_SPLIT_TOUCH
+ WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
val desktopRepository = desktopUserRepositories.getProfile(callerTaskInfo.userId)
menuViewContainer = if (Flags.enableFullyImmersiveInDesktop()
&& desktopRepository.isTaskInFullImmersiveState(callerTaskInfo.taskId)) {
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/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
index 7d1471f..b531079 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
@@ -17,7 +17,6 @@
package com.android.wm.shell.windowdecor;
import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
-import static android.view.WindowManager.LayoutParams.FLAG_SPLIT_TOUCH;
import static android.view.WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL;
import static android.view.WindowManager.LayoutParams.INPUT_FEATURE_SPY;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
@@ -122,7 +121,7 @@
mDecorationSurface,
mClientToken,
null /* hostInputToken */,
- FLAG_NOT_FOCUSABLE | FLAG_SPLIT_TOUCH,
+ FLAG_NOT_FOCUSABLE,
PRIVATE_FLAG_TRUSTED_OVERLAY,
INPUT_FEATURE_SPY,
TYPE_APPLICATION,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt
index 9d73950..e5c989e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt
@@ -245,8 +245,7 @@
width = menuWidth,
height = menuHeight,
flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or
- WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH or
- WindowManager.LayoutParams.FLAG_SPLIT_TOUCH,
+ WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH,
view = handleMenuView.rootView,
forciblyShownTypes = if (forceShowSystemBars) { systemBars() } else { 0 },
ignoreCutouts = Flags.showAppHandleLargeScreens()
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt
index cc54d25..1ce0366 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt
@@ -178,8 +178,7 @@
menuHeight,
WindowManager.LayoutParams.TYPE_APPLICATION,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
- or WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
- or WindowManager.LayoutParams.FLAG_SPLIT_TOUCH,
+ or WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH,
PixelFormat.TRANSPARENT
)
lp.title = "Maximize Menu for Task=" + taskInfo.taskId
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
index fa7183ad..3fcb093 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
@@ -22,7 +22,6 @@
import static android.view.WindowInsets.Type.mandatorySystemGestures;
import static android.view.WindowInsets.Type.statusBars;
import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
-import static android.view.WindowManager.LayoutParams.FLAG_SPLIT_TOUCH;
import static android.view.WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
@@ -333,7 +332,7 @@
outResult.mCaptionWidth,
outResult.mCaptionHeight,
TYPE_APPLICATION,
- FLAG_NOT_FOCUSABLE | FLAG_SPLIT_TOUCH,
+ FLAG_NOT_FOCUSABLE,
PixelFormat.TRANSPARENT);
lp.setTitle("Caption of Task=" + mTaskInfo.taskId);
lp.setTrustedOverlay();
@@ -750,7 +749,7 @@
width,
height,
TYPE_APPLICATION,
- FLAG_NOT_FOCUSABLE | FLAG_WATCH_OUTSIDE_TOUCH | FLAG_SPLIT_TOUCH,
+ FLAG_NOT_FOCUSABLE | FLAG_WATCH_OUTSIDE_TOUCH,
PixelFormat.TRANSPARENT);
lp.setTitle("Additional window of Task=" + mTaskInfo.taskId);
lp.setTrustedOverlay();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/ReusableWindowDecorViewHost.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/ReusableWindowDecorViewHost.kt
index da41e1b..4a09614 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/ReusableWindowDecorViewHost.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/ReusableWindowDecorViewHost.kt
@@ -24,7 +24,6 @@
import android.view.View
import android.view.WindowManager
import android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
-import android.view.WindowManager.LayoutParams.FLAG_SPLIT_TOUCH
import android.view.WindowManager.LayoutParams.TYPE_APPLICATION
import android.widget.FrameLayout
import androidx.tracing.Trace
@@ -72,7 +71,7 @@
0 /* width*/,
0 /* height */,
TYPE_APPLICATION,
- FLAG_NOT_FOCUSABLE or FLAG_SPLIT_TOUCH,
+ FLAG_NOT_FOCUSABLE,
PixelFormat.TRANSPARENT,
)
.apply {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDividerWindowManager.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDividerWindowManager.kt
index 5832822..fbbf1a5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDividerWindowManager.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDividerWindowManager.kt
@@ -34,7 +34,6 @@
import android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
import android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
import android.view.WindowManager.LayoutParams.FLAG_SLIPPERY
-import android.view.WindowManager.LayoutParams.FLAG_SPLIT_TOUCH
import android.view.WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
import android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION
import android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY
@@ -240,7 +239,6 @@
FLAG_NOT_FOCUSABLE or
FLAG_NOT_TOUCH_MODAL or
FLAG_WATCH_OUTSIDE_TOUCH or
- FLAG_SPLIT_TOUCH or
FLAG_SLIPPERY,
PixelFormat.TRANSLUCENT,
)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java
index ce640b5..ffcc344 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java
@@ -445,6 +445,15 @@
assertThat(update.updatedBubble.showFlyout()).isFalse();
}
+ @Test
+ public void getOrCreateBubble_withIntent_usesCorrectUser() {
+ Intent intent = new Intent();
+ intent.setPackage(mContext.getPackageName());
+ Bubble b = mBubbleData.getOrCreateBubble(intent, UserHandle.of(/* userId= */ 10));
+
+ assertThat(b.getUser().getIdentifier()).isEqualTo(10);
+ }
+
//
// Overflow
//
@@ -1441,12 +1450,6 @@
assertWithMessage("selectedBubble").that(update.selectedBubble).isEqualTo(bubble);
}
- private void assertSelectionCleared() {
- BubbleData.Update update = mUpdateCaptor.getValue();
- assertWithMessage("selectionChanged").that(update.selectionChanged).isTrue();
- assertWithMessage("selectedBubble").that(update.selectedBubble).isNull();
- }
-
private void assertExpandedChangedTo(boolean expected) {
BubbleData.Update update = mUpdateCaptor.getValue();
assertWithMessage("expandedChanged").that(update.expandedChanged).isTrue();
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/CloseDesktopTaskTransitionHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/CloseDesktopTaskTransitionHandlerTest.kt
index 04f9ada..03aad1c 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/CloseDesktopTaskTransitionHandlerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/CloseDesktopTaskTransitionHandlerTest.kt
@@ -21,6 +21,7 @@
import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
import android.app.WindowConfiguration.WindowingMode
+import android.os.Handler
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
import android.view.SurfaceControl
@@ -52,6 +53,7 @@
@Mock lateinit var testExecutor: ShellExecutor
@Mock lateinit var closingTaskLeash: SurfaceControl
+ @Mock lateinit var mockHandler: Handler
private val transactionSupplier = Supplier { mock<SurfaceControl.Transaction>() }
@@ -65,6 +67,7 @@
testExecutor,
testExecutor,
transactionSupplier,
+ mockHandler,
)
}
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/media/java/android/media/MediaCodecInfo.java b/media/java/android/media/MediaCodecInfo.java
index 302969f..9bb31d0 100644
--- a/media/java/android/media/MediaCodecInfo.java
+++ b/media/java/android/media/MediaCodecInfo.java
@@ -908,13 +908,13 @@
/** @hide */
public String[] validFeatures() {
Feature[] features = getValidFeatures();
- String[] res = new String[features.length];
- for (int i = 0; i < res.length; i++) {
+ ArrayList<String> res = new ArrayList();
+ for (int i = 0; i < features.length; i++) {
if (!features[i].mInternal) {
- res[i] = features[i].mName;
+ res.add(features[i].mName);
}
}
- return res;
+ return res.toArray(new String[0]);
}
private Feature[] getValidFeatures() {
diff --git a/media/java/android/media/flags/media_better_together.aconfig b/media/java/android/media/flags/media_better_together.aconfig
index c48b5f4..312f78e 100644
--- a/media/java/android/media/flags/media_better_together.aconfig
+++ b/media/java/android/media/flags/media_better_together.aconfig
@@ -173,6 +173,18 @@
bug: "281072508"
}
+
+flag {
+ name: "enable_singleton_audio_manager_route_controller"
+ is_exported: true
+ namespace: "media_solutions"
+ description: "Use singleton AudioManagerRouteController shared across all users."
+ bug: "372868909"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
flag {
name: "enable_use_of_bluetooth_device_get_alias_for_mr2info_get_name"
namespace: "media_solutions"
diff --git a/media/java/android/media/tv/TvInputService.java b/media/java/android/media/tv/TvInputService.java
index abfc244..f352a41 100644
--- a/media/java/android/media/tv/TvInputService.java
+++ b/media/java/android/media/tv/TvInputService.java
@@ -161,11 +161,6 @@
new RemoteCallbackList<>();
private TvInputManager mTvInputManager;
- /**
- * @hide
- */
- protected TvInputServiceExtensionManager mTvInputServiceExtensionManager =
- new TvInputServiceExtensionManager();
@Override
public final IBinder onBind(Intent intent) {
@@ -230,12 +225,20 @@
@Override
public IBinder getExtensionInterface(String name) {
- if (tifExtensionStandardization() && name != null) {
- if (TvInputServiceExtensionManager.checkIsStandardizedInterfaces(name)) {
- return mTvInputServiceExtensionManager.getExtensionIBinder(name);
+ IBinder binder = TvInputService.this.getExtensionInterface(name);
+ if (tifExtensionStandardization()) {
+ if (name != null
+ && TvInputServiceExtensionManager.checkIsStandardizedInterfaces(name)) {
+ if (TvInputServiceExtensionManager.checkIsStandardizedIBinder(name,
+ binder)) {
+ return binder;
+ } else {
+ // binder with standardized name is not standardized
+ return null;
+ }
}
}
- return TvInputService.this.getExtensionInterface(name);
+ return binder;
}
@Override
diff --git a/media/java/android/media/tv/TvInputServiceExtensionManager.java b/media/java/android/media/tv/TvInputServiceExtensionManager.java
index d33ac92..02d2616 100644
--- a/media/java/android/media/tv/TvInputServiceExtensionManager.java
+++ b/media/java/android/media/tv/TvInputServiceExtensionManager.java
@@ -16,13 +16,9 @@
package android.media.tv;
-import android.annotation.FlaggedApi;
-import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.RequiresPermission;
import android.annotation.StringDef;
-import android.media.tv.flags.Flags;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
@@ -30,21 +26,17 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
-import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
-import java.util.Map;
import java.util.Set;
/**
* This class provides a list of available standardized TvInputService extension interface names
- * and a container storing IBinder objects that implement these interfaces created by SoC/OEMs.
- * It also provides an API for SoC/OEMs to register implemented IBinder objects.
+ * and checks if IBinder objects created by SoC/OEMs implement these interfaces.
*
* @hide
*/
-@FlaggedApi(Flags.FLAG_TIF_EXTENSION_STANDARDIZATION)
public final class TvInputServiceExtensionManager {
private static final String TAG = "TvInputServiceExtensionManager";
private static final String SCAN_PACKAGE = "android.media.tv.extension.scan.";
@@ -63,33 +55,6 @@
private static final String ANALOG_PACKAGE = "android.media.tv.extension.analog.";
private static final String TUNE_PACKAGE = "android.media.tv.extension.tune.";
- @IntDef(prefix = {"REGISTER_"}, value = {
- REGISTER_SUCCESS,
- REGISTER_FAIL_NAME_NOT_STANDARDIZED,
- REGISTER_FAIL_IMPLEMENTATION_NOT_STANDARDIZED,
- REGISTER_FAIL_REMOTE_EXCEPTION
- })
- @Retention(RetentionPolicy.SOURCE)
- public @interface RegisterResult {}
-
- /**
- * Registering binder returns success when it abides standardized interface structure
- */
- public static final int REGISTER_SUCCESS = 0;
- /**
- * Registering binder returns failure when the extension name is not in the standardization
- * list
- */
- public static final int REGISTER_FAIL_NAME_NOT_STANDARDIZED = 1;
- /**
- * Registering binder returns failure when the IBinder does not implement standardized interface
- */
- public static final int REGISTER_FAIL_IMPLEMENTATION_NOT_STANDARDIZED = 2;
- /**
- * Registering binder returns failure when remote server is not available
- */
- public static final int REGISTER_FAIL_REMOTE_EXCEPTION = 3;
-
@StringDef({
ISCAN_INTERFACE,
ISCAN_SESSION,
@@ -673,12 +638,6 @@
IMUX_TUNE
));
- // Store the mapping between interface names and IBinder
- private Map<String, IBinder> mExtensionInterfaceIBinderMapping = new HashMap<>();
-
- TvInputServiceExtensionManager() {
- }
-
/**
* Function to return available extension interface names
*/
@@ -694,43 +653,18 @@
}
/**
- * Registers IBinder objects that implement standardized AIDL interfaces.
- * <p>This function should be used by SoCs/OEMs
- *
- * @param extensionName Extension Interface Name
- * @param binder IBinder object to be registered
- * @return {@link #REGISTER_SUCCESS} on success of registering IBinder object
- * {@link #REGISTER_FAIL_NAME_NOT_STANDARDIZED} on failure due to registering extension
- * with non-standardized name
- * {@link #REGISTER_FAIL_IMPLEMENTATION_NOT_STANDARDIZED} on failure due to IBinder not
- * implementing standardized AIDL interface
- * {@link #REGISTER_FAIL_REMOTE_EXCEPTION} on failure due to remote exception
+ * Function check if the IBinder object implements standardized interface
*/
- @RequiresPermission(android.Manifest.permission.TV_INPUT_HARDWARE)
- @RegisterResult
- public int registerExtensionIBinder(@StandardizedExtensionName @NonNull String extensionName,
- @NonNull IBinder binder) {
- if (!checkIsStandardizedInterfaces(extensionName)) {
- return REGISTER_FAIL_NAME_NOT_STANDARDIZED;
- }
- try {
- if (binder.getInterfaceDescriptor().equals(extensionName)) {
- mExtensionInterfaceIBinderMapping.put(extensionName, binder);
- return REGISTER_SUCCESS;
- } else {
- return REGISTER_FAIL_IMPLEMENTATION_NOT_STANDARDIZED;
+ public static boolean checkIsStandardizedIBinder(@NonNull String extensionName,
+ @Nullable IBinder binder) {
+ if (binder != null) {
+ try {
+ return binder.getInterfaceDescriptor().equals(extensionName);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Fetching IBinder object failure due to " + e);
}
- } catch (RemoteException e) {
- Log.e(TAG, "Fetching IBinder object failure due to " + e);
- return REGISTER_FAIL_REMOTE_EXCEPTION;
}
- }
-
- /**
- * Function to get corresponding IBinder object
- */
- @Nullable IBinder getExtensionIBinder(@NonNull String extensionName) {
- return mExtensionInterfaceIBinderMapping.get(extensionName);
+ return false;
}
}
diff --git a/packages/SettingsLib/BannerMessagePreference/Android.bp b/packages/SettingsLib/BannerMessagePreference/Android.bp
index 3f671b9..77e2cc7 100644
--- a/packages/SettingsLib/BannerMessagePreference/Android.bp
+++ b/packages/SettingsLib/BannerMessagePreference/Android.bp
@@ -14,12 +14,16 @@
"SettingsLintDefaults",
],
- srcs: ["src/**/*.java"],
+ srcs: [
+ "src/**/*.java",
+ "src/**/*.kt",
+ ],
resource_dirs: ["res"],
static_libs: [
- "androidx.preference_preference",
+ "SettingsLibButtonPreference",
"SettingsLibSettingsTheme",
+ "androidx.preference_preference",
],
sdk_version: "system_current",
diff --git a/packages/SettingsLib/BannerMessagePreference/res/color/settingslib_banner_button_background_high.xml b/packages/SettingsLib/BannerMessagePreference/res/color/settingslib_banner_button_background_high.xml
new file mode 100644
index 0000000..d113b547
--- /dev/null
+++ b/packages/SettingsLib/BannerMessagePreference/res/color/settingslib_banner_button_background_high.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+ -->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_enabled="false"
+ android:alpha="@dimen/material_emphasis_disabled_background" android:color="@color/settingslib_colorBackgroundLevel_high"/>
+ <item android:state_checked="true" android:color="?attr/colorContainerChecked"/>
+ <item android:state_checkable="true" android:color="?attr/colorContainerUnchecked"/>
+ <item android:color="@color/settingslib_colorBackgroundLevel_high" />
+</selector>
\ No newline at end of file
diff --git a/packages/SettingsLib/BannerMessagePreference/res/color/settingslib_banner_button_background_low.xml b/packages/SettingsLib/BannerMessagePreference/res/color/settingslib_banner_button_background_low.xml
new file mode 100644
index 0000000..cb89d9a
--- /dev/null
+++ b/packages/SettingsLib/BannerMessagePreference/res/color/settingslib_banner_button_background_low.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+ -->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_enabled="false"
+ android:alpha="@dimen/material_emphasis_disabled_background" android:color="@color/settingslib_colorBackgroundLevel_low"/>
+ <item android:state_checked="true" android:color="?attr/colorContainerChecked"/>
+ <item android:state_checkable="true" android:color="?attr/colorContainerUnchecked"/>
+ <item android:color="@color/settingslib_colorBackgroundLevel_low" />
+</selector>
\ No newline at end of file
diff --git a/packages/SettingsLib/BannerMessagePreference/res/color/settingslib_banner_button_background_medium.xml b/packages/SettingsLib/BannerMessagePreference/res/color/settingslib_banner_button_background_medium.xml
new file mode 100644
index 0000000..f820c35
--- /dev/null
+++ b/packages/SettingsLib/BannerMessagePreference/res/color/settingslib_banner_button_background_medium.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+ -->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_enabled="false"
+ android:alpha="@dimen/material_emphasis_disabled_background" android:color="@color/settingslib_colorBackgroundLevel_medium"/>
+ <item android:state_checked="true" android:color="?attr/colorContainerChecked"/>
+ <item android:state_checkable="true" android:color="?attr/colorContainerUnchecked"/>
+ <item android:color="@color/settingslib_colorBackgroundLevel_medium" />
+</selector>
\ No newline at end of file
diff --git a/packages/SettingsLib/BannerMessagePreference/res/color/settingslib_banner_button_background_normal.xml b/packages/SettingsLib/BannerMessagePreference/res/color/settingslib_banner_button_background_normal.xml
new file mode 100644
index 0000000..8037a8b
--- /dev/null
+++ b/packages/SettingsLib/BannerMessagePreference/res/color/settingslib_banner_button_background_normal.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+ -->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_enabled="false"
+ android:alpha="@dimen/material_emphasis_disabled_background" android:color="?attr/colorOnSurface"/>
+ <item android:state_checked="true" android:color="?attr/colorContainerChecked"/>
+ <item android:state_checkable="true" android:color="?attr/colorContainerUnchecked"/>
+ <item android:color="?attr/colorContainer" />
+</selector>
\ No newline at end of file
diff --git a/packages/SettingsLib/BannerMessagePreference/res/drawable-v31/settingslib_card_background.xml b/packages/SettingsLib/BannerMessagePreference/res/drawable-v31/settingslib_card_background.xml
index 072eb58..3f806e1 100644
--- a/packages/SettingsLib/BannerMessagePreference/res/drawable-v31/settingslib_card_background.xml
+++ b/packages/SettingsLib/BannerMessagePreference/res/drawable-v31/settingslib_card_background.xml
@@ -16,6 +16,6 @@
-->
<shape xmlns:android="http://schemas.android.com/apk/res/android">
- <solid android:color="?android:attr/background" />
+ <solid android:color="@android:color/white" />
<corners android:radius="28dp"/>
</shape>
\ No newline at end of file
diff --git a/packages/SettingsLib/BannerMessagePreference/res/drawable-v35/settingslib_expressive_card_background.xml b/packages/SettingsLib/BannerMessagePreference/res/drawable-v35/settingslib_expressive_card_background.xml
new file mode 100644
index 0000000..a677a66
--- /dev/null
+++ b/packages/SettingsLib/BannerMessagePreference/res/drawable-v35/settingslib_expressive_card_background.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+ -->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+ <solid android:color="@color/settingslib_materialColorSurfaceBright" />
+ <corners android:radius="28dp"/>
+</shape>
\ No newline at end of file
diff --git a/packages/SettingsLib/BannerMessagePreference/res/layout-v31/settingslib_banner_message.xml b/packages/SettingsLib/BannerMessagePreference/res/layout-v31/settingslib_banner_message.xml
index 9d53e39..ca596d8 100644
--- a/packages/SettingsLib/BannerMessagePreference/res/layout-v31/settingslib_banner_message.xml
+++ b/packages/SettingsLib/BannerMessagePreference/res/layout-v31/settingslib_banner_message.xml
@@ -15,85 +15,92 @@
limitations under the License.
-->
-<com.android.settingslib.widget.BannerMessageView
- xmlns:android="http://schemas.android.com/apk/res/android"
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:orientation="vertical"
- style="@style/Banner.Preference.SettingsLib">
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+ android:paddingStart="?android:attr/listPreferredItemPaddingStart">
- <RelativeLayout
- android:id="@+id/top_row"
+ <com.android.settingslib.widget.BannerMessageView
+ android:id="@+id/banner_background"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:paddingBottom="8dp"
- android:orientation="horizontal">
+ android:orientation="vertical"
+ style="@style/Banner.Preference.SettingsLib">
- <ImageView
- android:id="@+id/banner_icon"
- android:layout_width="24dp"
- android:layout_height="24dp"
- android:layout_alignParentStart="true"
- android:importantForAccessibility="no" />
+ <RelativeLayout
+ android:id="@+id/top_row"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingBottom="8dp"
+ android:orientation="horizontal">
- <ImageButton
- android:id="@+id/banner_dismiss_btn"
+ <ImageView
+ android:id="@+id/banner_icon"
+ android:layout_width="24dp"
+ android:layout_height="24dp"
+ android:layout_alignParentStart="true"
+ android:importantForAccessibility="no" />
+
+ <ImageButton
+ android:id="@+id/banner_dismiss_btn"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:src="@drawable/settingslib_ic_cross"
+ android:layout_alignParentEnd="true"
+ android:contentDescription="@string/accessibility_banner_message_dismiss"
+ style="@style/Banner.Dismiss.SettingsLib" />
+ </RelativeLayout>
+
+ <TextView
+ android:id="@+id/banner_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:src="@drawable/settingslib_ic_cross"
- android:layout_alignParentEnd="true"
- android:contentDescription="@string/accessibility_banner_message_dismiss"
- style="@style/Banner.Dismiss.SettingsLib" />
- </RelativeLayout>
+ android:layout_gravity="start"
+ android:textAlignment="viewStart"
+ android:paddingTop="0dp"
+ android:paddingBottom="4dp"
+ android:textAppearance="@style/Banner.Title.SettingsLib"/>
- <TextView
- android:id="@+id/banner_title"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="start"
- android:textAlignment="viewStart"
- android:paddingTop="0dp"
- android:paddingBottom="4dp"
- android:textAppearance="@style/Banner.Title.SettingsLib"/>
-
- <TextView
- android:id="@+id/banner_subtitle"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="start"
- android:textAlignment="viewStart"
- android:paddingTop="0dp"
- android:paddingBottom="4dp"
- android:textAppearance="@style/Banner.Subtitle.SettingsLib"
- android:visibility="gone"/>
-
- <TextView
- android:id="@+id/banner_summary"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="start"
- android:textAlignment="viewStart"
- android:paddingTop="4dp"
- android:paddingBottom="8dp"
- android:textAppearance="@style/Banner.Summary.SettingsLib"/>
-
- <LinearLayout
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="horizontal"
- android:minHeight="8dp"
- android:gravity="end">
-
- <Button
- android:id="@+id/banner_negative_btn"
+ <TextView
+ android:id="@+id/banner_subtitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- style="@style/Banner.ButtonText.SettingsLib"/>
+ android:layout_gravity="start"
+ android:textAlignment="viewStart"
+ android:paddingTop="0dp"
+ android:paddingBottom="4dp"
+ android:textAppearance="@style/Banner.Subtitle.SettingsLib"
+ android:visibility="gone"/>
- <Button
- android:id="@+id/banner_positive_btn"
+ <TextView
+ android:id="@+id/banner_summary"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- style="@style/Banner.ButtonText.SettingsLib"/>
- </LinearLayout>
-</com.android.settingslib.widget.BannerMessageView>
\ No newline at end of file
+ android:layout_gravity="start"
+ android:textAlignment="viewStart"
+ android:paddingTop="4dp"
+ android:paddingBottom="8dp"
+ android:textAppearance="@style/Banner.Summary.SettingsLib"/>
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:minHeight="8dp"
+ android:gravity="end">
+
+ <Button
+ android:id="@+id/banner_negative_btn"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ style="@style/Banner.ButtonText.SettingsLib"/>
+
+ <Button
+ android:id="@+id/banner_positive_btn"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ style="@style/Banner.ButtonText.SettingsLib"/>
+ </LinearLayout>
+ </com.android.settingslib.widget.BannerMessageView>
+</LinearLayout>
\ No newline at end of file
diff --git a/packages/SettingsLib/BannerMessagePreference/res/layout-v35/settingslib_expressive_banner_message.xml b/packages/SettingsLib/BannerMessagePreference/res/layout-v35/settingslib_expressive_banner_message.xml
new file mode 100644
index 0000000..b10ef6e
--- /dev/null
+++ b/packages/SettingsLib/BannerMessagePreference/res/layout-v35/settingslib_expressive_banner_message.xml
@@ -0,0 +1,108 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+ -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+ android:paddingStart="?android:attr/listPreferredItemPaddingStart">
+
+ <com.android.settingslib.widget.BannerMessageView
+ android:id="@+id/banner_background"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ style="@style/Banner.Preference.SettingsLib.Expressive">
+
+ <RelativeLayout
+ android:id="@+id/top_row"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_alignParentStart="true"
+ android:layout_marginEnd="@dimen/settingslib_expressive_space_medium4"
+ android:orientation="vertical">
+ <TextView
+ android:id="@+id/banner_header"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ style="@style/Banner.Header.SettingsLib.Expressive"/>
+
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content">
+
+ <ImageView
+ android:id="@+id/banner_icon"
+ android:layout_width="@dimen/settingslib_expressive_space_small3"
+ android:layout_height="@dimen/settingslib_expressive_space_small3"
+ android:layout_gravity="center_vertical"
+ android:importantForAccessibility="no"
+ android:scaleType="fitCenter" />
+
+ <TextView
+ android:id="@+id/banner_title"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ style="@style/Banner.Title.SettingsLib.Expressive" />
+ </LinearLayout>
+
+ <TextView
+ android:id="@+id/banner_subtitle"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ style="@style/Banner.Subtitle.SettingsLib.Expressive"/>
+ </LinearLayout>
+
+ <ImageButton
+ android:id="@+id/banner_dismiss_btn"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ style="@style/Banner.Dismiss.SettingsLib.Expressive" />
+ </RelativeLayout>
+
+ <TextView
+ android:id="@+id/banner_summary"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ style="@style/Banner.Summary.SettingsLib.Expressive"/>
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:id="@+id/banner_buttons_frame"
+ android:paddingTop="@dimen/settingslib_expressive_space_extrasmall6"
+ android:orientation="horizontal">
+
+ <com.google.android.material.button.MaterialButton
+ android:id="@+id/banner_negative_btn"
+ android:layout_weight="1"
+ style="@style/Banner.NegativeButton.SettingsLib.Expressive"/>
+ <Space
+ android:layout_width="@dimen/settingslib_expressive_space_extrasmall4"
+ android:layout_height="@dimen/settingslib_expressive_space_small1"/>
+ <com.google.android.material.button.MaterialButton
+ android:id="@+id/banner_positive_btn"
+ android:layout_weight="1"
+ style="@style/Banner.PositiveButton.SettingsLib.Expressive"/>
+ </LinearLayout>
+ </com.android.settingslib.widget.BannerMessageView>
+</LinearLayout>
\ No newline at end of file
diff --git a/packages/SettingsLib/BannerMessagePreference/res/layout/settingslib_banner_message_preference_group.xml b/packages/SettingsLib/BannerMessagePreference/res/layout/settingslib_banner_message_preference_group.xml
new file mode 100644
index 0000000..c74e391
--- /dev/null
+++ b/packages/SettingsLib/BannerMessagePreference/res/layout/settingslib_banner_message_preference_group.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+-->
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:baselineAligned="false"
+ android:id="@+id/banner_group_layout"
+ android:importantForAccessibility="no"
+ android:filterTouchesWhenObscured="false"
+ android:orientation="horizontal">
+</LinearLayout>
\ No newline at end of file
diff --git a/packages/SettingsLib/BannerMessagePreference/res/values-v31/styles.xml b/packages/SettingsLib/BannerMessagePreference/res/values-v31/styles.xml
index fede44f..5909f8e 100644
--- a/packages/SettingsLib/BannerMessagePreference/res/values-v31/styles.xml
+++ b/packages/SettingsLib/BannerMessagePreference/res/values-v31/styles.xml
@@ -24,9 +24,7 @@
<item name="android:paddingTop">20dp</item>
<item name="android:paddingBottom">8dp</item>
<item name="android:layout_marginTop">8dp</item>
- <item name="android:layout_marginStart">16dp</item>
<item name="android:layout_marginBottom">8dp</item>
- <item name="android:layout_marginEnd">16dp</item>
<item name="android:background">@drawable/settingslib_card_background</item>
</style>
diff --git a/packages/SettingsLib/BannerMessagePreference/res/values-v35/styles_expressive.xml b/packages/SettingsLib/BannerMessagePreference/res/values-v35/styles_expressive.xml
new file mode 100644
index 0000000..b864311
--- /dev/null
+++ b/packages/SettingsLib/BannerMessagePreference/res/values-v35/styles_expressive.xml
@@ -0,0 +1,76 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+ -->
+
+<resources>
+
+ <style name="Banner.Preference.SettingsLib.Expressive">
+ <item name="android:padding">@dimen/settingslib_expressive_space_small1</item>
+ <item name="android:background">@drawable/settingslib_expressive_card_background</item>
+ </style>
+
+ <style name="Banner.Header.SettingsLib.Expressive"
+ parent="">
+ <item name="android:textAlignment">viewStart</item>
+ <item name="android:paddingBottom">@dimen/settingslib_expressive_space_extrasmall4</item>
+ <item name="android:textAppearance">@style/TextAppearance.SettingsLib.LabelMedium</item>
+ <item name="android:textColor">?android:attr/textColorPrimary</item>
+ </style>
+
+ <style name="Banner.Title.SettingsLib.Expressive"
+ parent="">
+ <item name="android:layout_gravity">start</item>
+ <item name="android:layout_marginLeft">@dimen/settingslib_expressive_space_extrasmall4</item>
+ <item name="android:textAlignment">viewStart</item>
+ <item name="android:textAppearance">@style/TextAppearance.SettingsLib.TitleLarge.Emphasized</item>
+ <item name="android:textColor">?android:attr/textColorPrimary</item>
+ </style>
+
+ <style name="Banner.Subtitle.SettingsLib.Expressive"
+ parent="">
+ <item name="android:layout_gravity">start</item>
+ <item name="android:textAlignment">viewStart</item>
+ <item name="android:paddingTop">@dimen/settingslib_expressive_space_extrasmall4</item>
+ <item name="android:textAppearance">@style/TextAppearance.SettingsLib.BodyMedium</item>
+ <item name="android:textColor">?android:attr/textColorPrimary</item>
+ </style>
+
+ <style name="Banner.Summary.SettingsLib.Expressive"
+ parent="">
+ <item name="android:layout_gravity">start</item>
+ <item name="android:textAlignment">viewStart</item>
+ <item name="android:paddingTop">@dimen/settingslib_expressive_space_extrasmall6</item>
+ <item name="android:textAppearance">@style/TextAppearance.SettingsLib.BodyMedium</item>
+ <item name="android:textColor">?android:attr/textColorPrimary</item>
+ </style>
+
+ <style name="Banner.Dismiss.SettingsLib.Expressive">
+ <item name="android:src">@drawable/settingslib_expressive_icon_cross</item>
+ <item name="android:layout_alignParentEnd">true</item>
+ <item name="android:contentDescription">@string/accessibility_banner_message_dismiss</item>
+ </style>
+
+ <style name="Banner.PositiveButton.SettingsLib.Expressive"
+ parent="@style/SettingsLibButtonStyle.Expressive.Filled.Extra">
+ <item name="android:textColor">?android:attr/textColorPrimaryInverse</item>
+ <item name="materialSizeOverlay">@style/SizeOverlay.Material3Expressive.Button.Small</item>
+ </style>
+
+ <style name="Banner.NegativeButton.SettingsLib.Expressive"
+ parent="@style/SettingsLibButtonStyle.Expressive.Outline.Extra">
+ <item name="materialSizeOverlay">@style/SizeOverlay.Material3Expressive.Button.Small</item>
+ </style>
+</resources>
diff --git a/packages/SettingsLib/BannerMessagePreference/res/values/attrs.xml b/packages/SettingsLib/BannerMessagePreference/res/values/attrs.xml
index 96634a5..86d5f47 100644
--- a/packages/SettingsLib/BannerMessagePreference/res/values/attrs.xml
+++ b/packages/SettingsLib/BannerMessagePreference/res/values/attrs.xml
@@ -21,7 +21,18 @@
<enum name="high" value="0"/>
<enum name="medium" value="1"/>
<enum name="low" value="2"/>
+ <enum name="normal" value="3"/>
</attr>
<attr format="string" name="subtitle" />
+ <attr format="string" name="bannerHeader" />
+ <attr format="integer" name="buttonOrientation" />
+ </declare-styleable>
+
+ <declare-styleable name="BannerMessagePreferenceGroup">
+ <attr format="string" name="expandKey" />
+ <attr format="string" name="expandTitle" />
+ <attr format="string" name="collapseKey" />
+ <attr format="string" name="collapseTitle" />
+ <attr format="reference" name="collapseIcon" />
</declare-styleable>
</resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/BannerMessagePreference/res/values/colors.xml b/packages/SettingsLib/BannerMessagePreference/res/values/colors.xml
index 53d72d1..891def1 100644
--- a/packages/SettingsLib/BannerMessagePreference/res/values/colors.xml
+++ b/packages/SettingsLib/BannerMessagePreference/res/values/colors.xml
@@ -19,7 +19,9 @@
<color name="banner_background_attention_high">#FFDAD5</color> <!-- red card background -->
<color name="banner_background_attention_medium">#F0E3A8</color> <!-- yellow card background -->
<color name="banner_background_attention_low">#CFEBC0</color> <!-- green card background -->
+ <color name="banner_background_attention_normal">@color/settingslib_materialColorSurfaceBright</color> <!-- normal card background -->
<color name="banner_accent_attention_high">#BB3322</color> <!-- red accent color -->
<color name="banner_accent_attention_medium">#895900</color> <!-- yellow accent color -->
<color name="banner_accent_attention_low">#1D7233</color> <!-- green accent color -->
+ <color name="banner_accent_attention_normal">@color/settingslib_materialColorPrimary</color> <!-- normal accent color -->
</resources>
diff --git a/packages/SettingsLib/BannerMessagePreference/src/com/android/settingslib/widget/BannerMessagePreference.java b/packages/SettingsLib/BannerMessagePreference/src/com/android/settingslib/widget/BannerMessagePreference.java
index 10769ec..60a9ebd 100644
--- a/packages/SettingsLib/BannerMessagePreference/src/com/android/settingslib/widget/BannerMessagePreference.java
+++ b/packages/SettingsLib/BannerMessagePreference/src/com/android/settingslib/widget/BannerMessagePreference.java
@@ -17,6 +17,7 @@
package com.android.settingslib.widget;
import android.content.Context;
+import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.PorterDuff;
@@ -29,6 +30,7 @@
import android.widget.Button;
import android.widget.ImageButton;
import android.widget.ImageView;
+import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.annotation.ColorInt;
@@ -39,6 +41,8 @@
import androidx.preference.PreferenceViewHolder;
import com.android.settingslib.widget.preference.banner.R;
+
+import com.google.android.material.button.MaterialButton;
/**
* Banner message is a banner displaying important information (permission request, page error etc),
* and provide actions for user to address. It requires a user action to be dismissed.
@@ -46,22 +50,36 @@
public class BannerMessagePreference extends Preference implements GroupSectionDividerMixin {
public enum AttentionLevel {
- HIGH(0, R.color.banner_background_attention_high, R.color.banner_accent_attention_high),
+ HIGH(0,
+ R.color.banner_background_attention_high,
+ R.color.banner_accent_attention_high,
+ R.color.settingslib_banner_button_background_high),
MEDIUM(1,
- R.color.banner_background_attention_medium,
- R.color.banner_accent_attention_medium),
- LOW(2, R.color.banner_background_attention_low, R.color.banner_accent_attention_low);
+ R.color.banner_background_attention_medium,
+ R.color.banner_accent_attention_medium,
+ R.color.settingslib_banner_button_background_medium),
+ LOW(2,
+ R.color.banner_background_attention_low,
+ R.color.banner_accent_attention_low,
+ R.color.settingslib_banner_button_background_low),
+ NORMAL(3,
+ R.color.banner_background_attention_normal,
+ R.color.banner_accent_attention_normal,
+ R.color.settingslib_banner_button_background_normal);
// Corresponds to the enum valye of R.attr.attentionLevel
private final int mAttrValue;
@ColorRes private final int mBackgroundColorResId;
@ColorRes private final int mAccentColorResId;
+ @ColorRes private final int mButtonBackgroundColorResId;
AttentionLevel(int attrValue, @ColorRes int backgroundColorResId,
- @ColorRes int accentColorResId) {
+ @ColorRes int accentColorResId,
+ @ColorRes int buttonBackgroundColorResId) {
mAttrValue = attrValue;
mBackgroundColorResId = backgroundColorResId;
mAccentColorResId = accentColorResId;
+ mButtonBackgroundColorResId = buttonBackgroundColorResId;
}
static AttentionLevel fromAttr(int attrValue) {
@@ -80,6 +98,10 @@
public @ColorRes int getBackgroundColorResId() {
return mBackgroundColorResId;
}
+
+ public @ColorRes int getButtonBackgroundColorResId() {
+ return mButtonBackgroundColorResId;
+ }
}
private static final String TAG = "BannerPreference";
@@ -95,6 +117,8 @@
// Default attention level is High.
private AttentionLevel mAttentionLevel = AttentionLevel.HIGH;
private String mSubtitle;
+ private String mHeader;
+ private int mButtonOrientation;
public BannerMessagePreference(Context context) {
super(context);
@@ -119,7 +143,10 @@
private void init(Context context, AttributeSet attrs) {
setSelectable(false);
- setLayoutResource(R.layout.settingslib_banner_message);
+ int resId = SettingsThemeHelper.isExpressiveTheme(context)
+ ? R.layout.settingslib_expressive_banner_message
+ : R.layout.settingslib_banner_message;
+ setLayoutResource(resId);
if (IS_AT_LEAST_S) {
if (attrs != null) {
@@ -130,6 +157,9 @@
a.getInt(R.styleable.BannerMessagePreference_attentionLevel, 0);
mAttentionLevel = AttentionLevel.fromAttr(mAttentionLevelValue);
mSubtitle = a.getString(R.styleable.BannerMessagePreference_subtitle);
+ mHeader = a.getString(R.styleable.BannerMessagePreference_bannerHeader);
+ mButtonOrientation = a.getInt(R.styleable.BannerMessagePreference_buttonOrientation,
+ LinearLayout.HORIZONTAL);
a.recycle();
}
}
@@ -142,11 +172,16 @@
final TextView titleView = (TextView) holder.findViewById(R.id.banner_title);
CharSequence title = getTitle();
- titleView.setText(title);
- titleView.setVisibility(title == null ? View.GONE : View.VISIBLE);
+ if (titleView != null) {
+ titleView.setText(title);
+ titleView.setVisibility(title == null ? View.GONE : View.VISIBLE);
+ }
final TextView summaryView = (TextView) holder.findViewById(R.id.banner_summary);
- summaryView.setText(getSummary());
+ if (summaryView != null) {
+ summaryView.setText(getSummary());
+ summaryView.setVisibility(TextUtils.isEmpty(getSummary()) ? View.GONE : View.VISIBLE);
+ }
mPositiveButtonInfo.mButton = (Button) holder.findViewById(R.id.banner_positive_btn);
mNegativeButtonInfo.mButton = (Button) holder.findViewById(R.id.banner_negative_btn);
@@ -162,8 +197,11 @@
icon == null
? getContext().getDrawable(R.drawable.ic_warning)
: icon);
- iconView.setColorFilter(
- new PorterDuffColorFilter(accentColor, PorterDuff.Mode.SRC_IN));
+ if (mAttentionLevel != AttentionLevel.NORMAL
+ && !SettingsThemeHelper.isExpressiveTheme(context)) {
+ iconView.setColorFilter(
+ new PorterDuffColorFilter(accentColor, PorterDuff.Mode.SRC_IN));
+ }
}
if (IS_AT_LEAST_S) {
@@ -171,12 +209,25 @@
context.getResources().getColor(
mAttentionLevel.getBackgroundColorResId(), theme);
+ @ColorInt final int btnBackgroundColor =
+ context.getResources().getColor(mAttentionLevel.getButtonBackgroundColorResId(),
+ theme);
+ ColorStateList strokeColor = context.getResources().getColorStateList(
+ mAttentionLevel.getButtonBackgroundColorResId(), theme);
+
holder.setDividerAllowedAbove(false);
holder.setDividerAllowedBelow(false);
- holder.itemView.getBackground().setTint(backgroundColor);
+ View backgroundView = holder.findViewById(R.id.banner_background);
+ if (backgroundView != null && !SettingsThemeHelper.isExpressiveTheme(context)) {
+ backgroundView.getBackground().setTint(backgroundColor);
+ }
mPositiveButtonInfo.mColor = accentColor;
mNegativeButtonInfo.mColor = accentColor;
+ if (mAttentionLevel != AttentionLevel.NORMAL) {
+ mPositiveButtonInfo.mBackgroundColor = btnBackgroundColor;
+ mNegativeButtonInfo.mStrokeColor = strokeColor;
+ }
mDismissButtonInfo.mButton = (ImageButton) holder.findViewById(R.id.banner_dismiss_btn);
mDismissButtonInfo.setUpButton();
@@ -185,6 +236,13 @@
subtitleView.setText(mSubtitle);
subtitleView.setVisibility(mSubtitle == null ? View.GONE : View.VISIBLE);
+ TextView headerView = (TextView) holder.findViewById(R.id.banner_header);
+ if (headerView != null) {
+ headerView.setText(mHeader);
+ headerView.setVisibility(TextUtils.isEmpty(mHeader) ? View.GONE : View.VISIBLE);
+ }
+
+
} else {
holder.setDividerAllowedAbove(true);
holder.setDividerAllowedBelow(true);
@@ -192,6 +250,24 @@
mPositiveButtonInfo.setUpButton();
mNegativeButtonInfo.setUpButton();
+ View buttonFrame = holder.findViewById(R.id.banner_buttons_frame);
+ if (buttonFrame != null) {
+ buttonFrame.setVisibility(
+ mPositiveButtonInfo.shouldBeVisible() || mNegativeButtonInfo.shouldBeVisible()
+ ? View.VISIBLE : View.GONE);
+
+ LinearLayout linearLayout = (LinearLayout) buttonFrame;
+ if (mButtonOrientation != linearLayout.getOrientation()) {
+ int childCount = linearLayout.getChildCount();
+ //reverse the order of the buttons
+ for (int i = childCount - 1; i >= 0; i--) {
+ View child = linearLayout.getChildAt(i);
+ linearLayout.removeViewAt(i);
+ linearLayout.addView(child);
+ }
+ linearLayout.setOrientation(mButtonOrientation);
+ }
+ }
}
/**
@@ -302,6 +378,18 @@
}
/**
+ * Sets button orientation.
+ */
+ @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
+ public BannerMessagePreference setButtonOrientation(int orientation) {
+ if (mButtonOrientation != orientation) {
+ mButtonOrientation = orientation;
+ notifyChanged();
+ }
+ return this;
+ }
+
+ /**
* Sets the subtitle.
*/
@RequiresApi(Build.VERSION_CODES.S)
@@ -322,6 +410,26 @@
}
/**
+ * Sets the header.
+ */
+ @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
+ public BannerMessagePreference setHeader(@StringRes int textResId) {
+ return setHeader(getContext().getString(textResId));
+ }
+
+ /**
+ * Sets the header.
+ */
+ @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
+ public BannerMessagePreference setHeader(String header) {
+ if (!TextUtils.equals(header, mSubtitle)) {
+ mHeader = header;
+ notifyChanged();
+ }
+ return this;
+ }
+
+ /**
* Sets the attention level. This will update the color theme of the preference.
*/
public BannerMessagePreference setAttentionLevel(AttentionLevel attentionLevel) {
@@ -342,13 +450,29 @@
private View.OnClickListener mListener;
private boolean mIsVisible = true;
@ColorInt private int mColor;
+ @ColorInt private int mBackgroundColor;
+ private ColorStateList mStrokeColor;
void setUpButton() {
mButton.setText(mText);
mButton.setOnClickListener(mListener);
+ MaterialButton btn = null;
+ if (mButton instanceof MaterialButton) {
+ btn = (MaterialButton) mButton;
+ }
+
if (IS_AT_LEAST_S) {
- mButton.setTextColor(mColor);
+ if (btn != null && SettingsThemeHelper.isExpressiveTheme(btn.getContext())) {
+ if (mBackgroundColor != 0) {
+ btn.setBackgroundColor(mBackgroundColor);
+ }
+ if (mStrokeColor != null) {
+ btn.setStrokeColor(mStrokeColor);
+ }
+ } else {
+ mButton.setTextColor(mColor);
+ }
}
if (shouldBeVisible()) {
diff --git a/packages/SettingsLib/BannerMessagePreference/src/com/android/settingslib/widget/BannerMessagePreferenceGroup.kt b/packages/SettingsLib/BannerMessagePreference/src/com/android/settingslib/widget/BannerMessagePreferenceGroup.kt
new file mode 100644
index 0000000..7545563
--- /dev/null
+++ b/packages/SettingsLib/BannerMessagePreference/src/com/android/settingslib/widget/BannerMessagePreferenceGroup.kt
@@ -0,0 +1,149 @@
+/*
+ * 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.settingslib.widget
+
+import android.content.Context
+import android.graphics.drawable.Drawable
+import android.util.AttributeSet
+import android.view.View
+import androidx.preference.Preference
+import androidx.preference.PreferenceGroup
+import androidx.preference.PreferenceViewHolder
+
+import com.android.settingslib.widget.preference.banner.R
+
+/**
+ * Custom PreferenceGroup that allows expanding and collapsing child preferences.
+ */
+class BannerMessagePreferenceGroup @JvmOverloads constructor(
+ context: Context,
+ attrs: AttributeSet? = null,
+ defStyleAttr: Int = 0,
+ defStyleRes: Int = 0
+) : PreferenceGroup(context, attrs, defStyleAttr, defStyleRes), GroupSectionDividerMixin {
+
+ private var isExpanded = false
+ private var expandPreference: NumberButtonPreference? = null
+ private var collapsePreference: SectionButtonPreference? = null
+ private val childPreferences = mutableListOf<BannerMessagePreference>()
+ private var expandKey: String? = null
+ private var expandTitle: String? = null
+ private var collapseKey: String? = null
+ private var collapseTitle: String? = null
+ private var collapseIcon: Drawable? = null
+ var expandContentDescription: Int = 0
+ set(value) {
+ field = value
+ expandPreference?.btnContentDescription = expandContentDescription
+ }
+
+ init {
+ isPersistent = false // This group doesn't store data
+ layoutResource = R.layout.settingslib_banner_message_preference_group
+
+ initAttributes(context, attrs, defStyleAttr)
+ }
+
+ override fun addPreference(preference: Preference): Boolean {
+ if (preference !is BannerMessagePreference) {
+ return false
+ }
+
+ if (childPreferences.size >= MAX_CHILDREN) {
+ return false
+ }
+
+ childPreferences.add(preference)
+ return super.addPreference(preference)
+ }
+
+ override fun removePreference(preference: Preference): Boolean {
+ if (preference !is BannerMessagePreference) {
+ return false
+ }
+ childPreferences.remove(preference)
+ return super.removePreference(preference)
+ }
+
+ override fun onBindViewHolder(holder: PreferenceViewHolder) {
+ super.onBindViewHolder(holder)
+ if (childPreferences.size >= MAX_CHILDREN - 1) {
+ if (expandPreference == null) {
+ expandPreference = NumberButtonPreference(context).apply {
+ key = expandKey
+ title = expandTitle
+ count = childPreferences.size - 1
+ btnContentDescription = expandContentDescription
+ clickListener = View.OnClickListener {
+ toggleExpansion()
+ }
+ }
+ super.addPreference(expandPreference!!)
+ }
+ if (collapsePreference == null) {
+ collapsePreference = SectionButtonPreference(context)
+ .apply {
+ key = collapseKey
+ title = collapseTitle
+ icon = collapseIcon
+ setOnClickListener {
+ toggleExpansion()
+ }
+ }
+ super.addPreference(collapsePreference!!)
+ }
+ }
+ updateExpandCollapsePreference()
+ updateChildrenVisibility()
+ }
+
+ private fun updateExpandCollapsePreference() {
+ expandPreference?.isVisible = !isExpanded
+ collapsePreference?.isVisible = isExpanded
+ }
+
+ private fun updateChildrenVisibility() {
+ for (i in 1 until childPreferences.size) {
+ val child = childPreferences[i]
+ child.isVisible = isExpanded
+ }
+ }
+
+ private fun toggleExpansion() {
+ isExpanded = !isExpanded
+ updateExpandCollapsePreference()
+ updateChildrenVisibility()
+ }
+
+ private fun initAttributes(context: Context, attrs: AttributeSet?, defStyleAttr: Int) {
+ context.obtainStyledAttributes(
+ attrs,
+ R.styleable.BannerMessagePreferenceGroup, defStyleAttr, 0
+ ).apply {
+ expandKey = getString(R.styleable.BannerMessagePreferenceGroup_expandKey)
+ expandTitle = getString(R.styleable.BannerMessagePreferenceGroup_expandTitle)
+ collapseKey = getString(R.styleable.BannerMessagePreferenceGroup_collapseKey)
+ collapseTitle = getString(R.styleable.BannerMessagePreferenceGroup_collapseTitle)
+ collapseIcon = getDrawable(R.styleable.BannerMessagePreferenceGroup_collapseIcon)
+ recycle()
+ }
+ }
+
+ companion object {
+ private const val MAX_CHILDREN = 3
+ }
+}
\ No newline at end of file
diff --git a/packages/SettingsLib/ButtonPreference/Android.bp b/packages/SettingsLib/ButtonPreference/Android.bp
index 08dd27f..a377f31 100644
--- a/packages/SettingsLib/ButtonPreference/Android.bp
+++ b/packages/SettingsLib/ButtonPreference/Android.bp
@@ -14,12 +14,15 @@
"SettingsLintDefaults",
],
- srcs: ["src/**/*.java"],
+ srcs: [
+ "src/**/*.java",
+ "src/**/*.kt",
+ ],
resource_dirs: ["res"],
static_libs: [
- "androidx.preference_preference",
"SettingsLibSettingsTheme",
+ "androidx.preference_preference",
],
sdk_version: "system_current",
diff --git a/packages/SettingsLib/ButtonPreference/res/color/settingslib_section_button_background.xml b/packages/SettingsLib/ButtonPreference/res/color/settingslib_section_button_background.xml
new file mode 100644
index 0000000..0972b62
--- /dev/null
+++ b/packages/SettingsLib/ButtonPreference/res/color/settingslib_section_button_background.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+ -->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_enabled="false"
+ android:alpha="@dimen/material_emphasis_disabled_background" android:color="@color/settingslib_materialColorSurfaceBright"/>
+ <item android:color="@color/settingslib_materialColorSurfaceBright" />
+</selector>
\ No newline at end of file
diff --git a/packages/SettingsLib/ButtonPreference/res/drawable/settingslib_number_button_background.xml b/packages/SettingsLib/ButtonPreference/res/drawable/settingslib_number_button_background.xml
new file mode 100644
index 0000000..9bf5c43
--- /dev/null
+++ b/packages/SettingsLib/ButtonPreference/res/drawable/settingslib_number_button_background.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+ -->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+ <solid android:color="@color/settingslib_materialColorSurfaceBright" />
+ <corners android:radius="@dimen/settingslib_expressive_radius_extralarge2"/>
+</shape>
\ No newline at end of file
diff --git a/packages/SettingsLib/ButtonPreference/res/drawable/settingslib_number_count_background.xml b/packages/SettingsLib/ButtonPreference/res/drawable/settingslib_number_count_background.xml
new file mode 100644
index 0000000..b993811
--- /dev/null
+++ b/packages/SettingsLib/ButtonPreference/res/drawable/settingslib_number_count_background.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+ -->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+ <solid android:color="@color/settingslib_materialColorSecondaryContainer" />
+ <corners android:radius="100dp"/>
+</shape>
\ No newline at end of file
diff --git a/packages/SettingsLib/ButtonPreference/res/layout/settingslib_number_button.xml b/packages/SettingsLib/ButtonPreference/res/layout/settingslib_number_button.xml
new file mode 100644
index 0000000..fa13b41
--- /dev/null
+++ b/packages/SettingsLib/ButtonPreference/res/layout/settingslib_number_button.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+ -->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+ android:paddingVertical="@dimen/settingslib_expressive_space_extrasmall4">
+
+ <LinearLayout
+ android:id="@+id/settingslib_number_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingVertical="@dimen/settingslib_expressive_space_small1"
+ android:paddingHorizontal="@dimen/settingslib_expressive_space_small4"
+ android:background="@drawable/settingslib_number_button_background">
+ <TextView
+ android:id="@+id/settingslib_number_count"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ style="@style/SettingsLibNumberButtonStyle.Number"/>
+ <TextView
+ android:id="@+id/settingslib_number_title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ style="@style/SettingsLibNumberButtonStyle.Title"/>
+ </LinearLayout>
+
+</LinearLayout>
\ No newline at end of file
diff --git a/packages/SettingsLib/ButtonPreference/res/layout/settingslib_section_button.xml b/packages/SettingsLib/ButtonPreference/res/layout/settingslib_section_button.xml
new file mode 100644
index 0000000..e7fb572
--- /dev/null
+++ b/packages/SettingsLib/ButtonPreference/res/layout/settingslib_section_button.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+-->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+ android:paddingVertical="@dimen/settingslib_expressive_space_extrasmall4">
+
+ <com.google.android.material.button.MaterialButton
+ android:id="@+id/settingslib_section_button"
+ style="@style/SettingsLibSectionButtonStyle.Expressive" />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/packages/SettingsLib/ButtonPreference/res/values/styles.xml b/packages/SettingsLib/ButtonPreference/res/values/styles.xml
index 3963732..5208e20 100644
--- a/packages/SettingsLib/ButtonPreference/res/values/styles.xml
+++ b/packages/SettingsLib/ButtonPreference/res/values/styles.xml
@@ -30,4 +30,33 @@
<item name="android:textColor">@color/settingslib_btn_colored_text_material</item>
<item name="android:background">@drawable/settingslib_btn_colored_material</item>
</style>
+
+ <style name="SettingsLibSectionButtonStyle.Expressive"
+ parent="@style/SettingsLibButtonStyle.Expressive.Filled.Large">
+ <item name="android:textColor">?android:attr/textColorPrimary</item>
+ <item name="backgroundTint">@color/settingslib_section_button_background</item>
+ <item name="iconTint">?android:attr/textColorPrimary</item>
+ </style>
+
+ <style name="SettingsLibNumberButtonStyle.Number"
+ parent="">
+ <item name="android:minWidth">@dimen/settingslib_expressive_space_small4</item>
+ <item name="android:minHeight">@dimen/settingslib_expressive_space_small4</item>
+ <item name="android:gravity">center</item>
+ <item name="android:background">@drawable/settingslib_number_count_background</item>
+ <item name="android:paddingStart">@dimen/settingslib_expressive_radius_extrasmall2</item>
+ <item name="android:paddingEnd">@dimen/settingslib_expressive_radius_extrasmall2</item>
+ <item name="android:layout_marginEnd">@dimen/settingslib_expressive_space_extrasmall4</item>
+ <item name="android:textAppearance">@style/TextAppearance.SettingsLib.TitleMedium</item>
+ <item name="android:textColor">@color/settingslib_materialColorOnSecondaryContainer</item>
+ <item name="android:importantForAccessibility">no</item>
+ </style>
+
+ <style name="SettingsLibNumberButtonStyle.Title"
+ parent="">
+ <item name="android:gravity">center</item>
+ <item name="android:textAppearance">@style/TextAppearance.SettingsLib.LabelLarge</item>
+ <item name="android:textColor">@color/settingslib_materialColorOnSurface</item>
+ <item name="android:importantForAccessibility">no</item>
+ </style>
</resources>
diff --git a/packages/SettingsLib/ButtonPreference/src/com/android/settingslib/widget/NumberButtonPreference.kt b/packages/SettingsLib/ButtonPreference/src/com/android/settingslib/widget/NumberButtonPreference.kt
new file mode 100644
index 0000000..a1772d5
--- /dev/null
+++ b/packages/SettingsLib/ButtonPreference/src/com/android/settingslib/widget/NumberButtonPreference.kt
@@ -0,0 +1,70 @@
+/*
+ * 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.settingslib.widget
+
+import android.content.Context
+import android.util.AttributeSet
+import android.view.View
+import android.widget.TextView
+import androidx.preference.Preference
+import androidx.preference.PreferenceViewHolder
+
+import com.android.settingslib.widget.preference.button.R
+
+class NumberButtonPreference @JvmOverloads constructor(
+ context: Context,
+ attrs: AttributeSet? = null,
+ defStyleAttr: Int = 0,
+ defStyleRes: Int = 0
+) : Preference(context, attrs, defStyleAttr, defStyleRes), GroupSectionDividerMixin {
+
+ var clickListener: View.OnClickListener? = null
+
+ var count: Int = 0
+ set(value) {
+ field = value
+ notifyChanged()
+ }
+
+ var btnContentDescription: Int = 0
+ set(value) {
+ field = value
+ notifyChanged()
+ }
+
+ init {
+ isPersistent = false // This preference doesn't store data
+ order = Int.MAX_VALUE
+ layoutResource = R.layout.settingslib_number_button
+ }
+
+ override fun onBindViewHolder(holder: PreferenceViewHolder) {
+ super.onBindViewHolder(holder)
+ holder.isDividerAllowedAbove = false
+ holder.isDividerAllowedBelow = false
+
+ holder.findViewById(R.id.settingslib_number_button)?.apply {
+ setOnClickListener(clickListener)
+ if (btnContentDescription != 0) {
+ setContentDescription(context.getString(btnContentDescription, count))
+ }
+ }
+ (holder.findViewById(R.id.settingslib_number_title) as? TextView)?.text = title
+
+ (holder.findViewById(R.id.settingslib_number_count) as? TextView)?.text = "$count"
+ }
+}
\ No newline at end of file
diff --git a/packages/SettingsLib/ButtonPreference/src/com/android/settingslib/widget/SectionButtonPreference.kt b/packages/SettingsLib/ButtonPreference/src/com/android/settingslib/widget/SectionButtonPreference.kt
new file mode 100644
index 0000000..b374dea
--- /dev/null
+++ b/packages/SettingsLib/ButtonPreference/src/com/android/settingslib/widget/SectionButtonPreference.kt
@@ -0,0 +1,71 @@
+/*
+ * 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.settingslib.widget
+
+import android.content.Context
+import android.util.AttributeSet
+import android.view.View
+import androidx.preference.Preference
+import androidx.preference.PreferenceViewHolder
+import com.android.settingslib.widget.preference.button.R
+import com.google.android.material.button.MaterialButton
+
+/**
+ * A Preference that displays a button with an optional icon.
+ */
+class SectionButtonPreference @JvmOverloads constructor(
+ context: Context,
+ attrs: AttributeSet? = null,
+ defStyleAttr: Int = 0,
+ defStyleRes: Int = 0
+) : Preference(context, attrs, defStyleAttr, defStyleRes), GroupSectionDividerMixin {
+
+ private var clickListener: ((View) -> Unit)? = null
+ set(value) {
+ field = value
+ notifyChanged()
+ }
+ private var button: MaterialButton? = null
+ init {
+ isPersistent = false // This preference doesn't store data
+ order = Int.MAX_VALUE
+ layoutResource = R.layout.settingslib_section_button
+ }
+
+ override fun onBindViewHolder(holder: PreferenceViewHolder) {
+ super.onBindViewHolder(holder)
+ holder.isDividerAllowedAbove = false
+ holder.isDividerAllowedBelow = false
+
+ button = holder.findViewById(R.id.settingslib_section_button) as? MaterialButton
+ button?.apply{
+ text = title
+ isFocusable = isSelectable
+ isClickable = isSelectable
+ setOnClickListener { view -> clickListener?.let { it(view) } }
+ }
+ button?.isEnabled = isEnabled
+ button?.icon = icon
+ }
+
+ /**
+ * Set a listener for button click
+ */
+ fun setOnClickListener(listener: (View) -> Unit) {
+ clickListener = listener
+ }
+}
\ No newline at end of file
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/SettingsTheme/res/drawable-v35/settingslib_expressive_icon_chevron.xml b/packages/SettingsLib/SettingsTheme/res/drawable/settingslib_expressive_icon_chevron.xml
similarity index 100%
rename from packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_expressive_icon_chevron.xml
rename to packages/SettingsLib/SettingsTheme/res/drawable/settingslib_expressive_icon_chevron.xml
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_expressive_icon_collapse.xml b/packages/SettingsLib/SettingsTheme/res/drawable/settingslib_expressive_icon_collapse.xml
similarity index 100%
rename from packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_expressive_icon_collapse.xml
rename to packages/SettingsLib/SettingsTheme/res/drawable/settingslib_expressive_icon_collapse.xml
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable/settingslib_expressive_icon_cross.xml b/packages/SettingsLib/SettingsTheme/res/drawable/settingslib_expressive_icon_cross.xml
new file mode 100644
index 0000000..3ba85a2
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/drawable/settingslib_expressive_icon_cross.xml
@@ -0,0 +1,36 @@
+<!--
+ 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.
+ -->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <item>
+ <shape
+ android:shape="oval">
+ <size android:width="28dp" android:height="28dp"/>
+ <solid android:color="@color/settingslib_materialColorSurfaceContainerHigh"/>
+ </shape>
+ </item>
+ <item android:gravity="center">
+ <vector
+ android:width="16dp"
+ android:height="16dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:fillColor="@color/settingslib_materialColorOnSurface"
+ android:pathData="M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12 19,6.41z"/>
+ </vector>
+ </item>
+</layer-list>
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_expressive_icon_expand.xml b/packages/SettingsLib/SettingsTheme/res/drawable/settingslib_expressive_icon_expand.xml
similarity index 100%
rename from packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_expressive_icon_expand.xml
rename to packages/SettingsLib/SettingsTheme/res/drawable/settingslib_expressive_icon_expand.xml
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable/settingslib_expressive_icon_level_high.xml b/packages/SettingsLib/SettingsTheme/res/drawable/settingslib_expressive_icon_level_high.xml
new file mode 100644
index 0000000..aa4155b
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/drawable/settingslib_expressive_icon_level_high.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+ -->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <item>
+ <vector
+ android:width="20dp"
+ android:height="20dp"
+ android:viewportWidth="20"
+ android:viewportHeight="20">
+ <path
+ android:pathData="M8.804,0.296C9.554,-0.099 10.446,-0.099 11.196,0.296L12.45,0.955C12.703,1.088 12.976,1.178 13.257,1.221L14.654,1.436C15.489,1.564 16.211,2.096 16.589,2.862L17.223,4.144C17.349,4.402 17.518,4.637 17.721,4.84L18.726,5.847C19.328,6.449 19.603,7.31 19.465,8.155L19.236,9.57C19.189,9.855 19.189,10.145 19.236,10.43L19.465,11.845C19.603,12.69 19.328,13.55 18.726,14.153L17.721,15.16C17.518,15.363 17.349,15.597 17.223,15.856L16.589,17.137C16.211,17.903 15.489,18.435 14.654,18.564L13.257,18.78C12.976,18.822 12.703,18.913 12.45,19.045L11.196,19.704C10.446,20.099 9.554,20.099 8.804,19.704L7.549,19.045C7.297,18.913 7.024,18.822 6.743,18.78L5.346,18.564C4.511,18.435 3.789,17.903 3.411,17.137L2.777,15.856C2.651,15.597 2.482,15.363 2.279,15.16L1.274,14.153C0.672,13.55 0.397,12.69 0.535,11.845L0.764,10.43C0.811,10.145 0.811,9.855 0.764,9.57L0.535,8.155C0.397,7.31 0.672,6.449 1.274,5.847L2.279,4.84C2.482,4.637 2.651,4.402 2.777,4.144L3.411,2.862C3.789,2.096 4.511,1.564 5.346,1.436L6.743,1.221C7.024,1.178 7.297,1.088 7.549,0.955L8.804,0.296Z"
+ android:fillColor="@color/settingslib_colorBackgroundLevel_high"/>
+ </vector>
+ </item>
+ <item android:gravity="center">
+ <vector
+ android:width="4dp"
+ android:height="12dp"
+ android:viewportWidth="4"
+ android:viewportHeight="12">
+ <path
+ android:pathData="M0.894,8.081V0.919H3.106V8.081H0.894ZM0.894,11.081V8.869H3.106V11.081H0.894Z"
+ android:fillColor="@color/settingslib_colorContentLevel_high"/>
+ </vector>
+ </item>
+</layer-list>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable/settingslib_expressive_icon_level_low.xml b/packages/SettingsLib/SettingsTheme/res/drawable/settingslib_expressive_icon_level_low.xml
new file mode 100644
index 0000000..9caa095
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/drawable/settingslib_expressive_icon_level_low.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+ -->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <item>
+ <vector
+ android:width="20dp"
+ android:height="20dp"
+ android:viewportWidth="20"
+ android:viewportHeight="20">
+ <path
+ android:pathData="M8.804,0.296C9.554,-0.099 10.446,-0.099 11.196,0.296L12.45,0.955C12.703,1.088 12.976,1.178 13.257,1.221L14.654,1.436C15.489,1.564 16.211,2.096 16.589,2.862L17.223,4.144C17.349,4.402 17.518,4.637 17.721,4.84L18.726,5.847C19.328,6.449 19.603,7.31 19.465,8.155L19.236,9.57C19.189,9.855 19.189,10.145 19.236,10.43L19.465,11.845C19.603,12.69 19.328,13.55 18.726,14.153L17.721,15.16C17.518,15.363 17.349,15.597 17.223,15.856L16.589,17.137C16.211,17.903 15.489,18.435 14.654,18.564L13.257,18.78C12.976,18.822 12.703,18.913 12.45,19.045L11.196,19.704C10.446,20.099 9.554,20.099 8.804,19.704L7.549,19.045C7.297,18.913 7.024,18.822 6.743,18.78L5.346,18.564C4.511,18.435 3.789,17.903 3.411,17.137L2.777,15.856C2.651,15.597 2.482,15.363 2.279,15.16L1.274,14.153C0.672,13.55 0.397,12.69 0.535,11.845L0.764,10.43C0.811,10.145 0.811,9.855 0.764,9.57L0.535,8.155C0.397,7.31 0.672,6.449 1.274,5.847L2.279,4.84C2.482,4.637 2.651,4.402 2.777,4.144L3.411,2.862C3.789,2.096 4.511,1.564 5.346,1.436L6.743,1.221C7.024,1.178 7.297,1.088 7.549,0.955L8.804,0.296Z"
+ android:fillColor="@color/settingslib_colorBackgroundLevel_low"/>
+ </vector>
+ </item>
+ <item android:gravity="center">
+ <vector
+ android:width="10dp"
+ android:height="9dp"
+ android:viewportWidth="10"
+ android:viewportHeight="9">
+ <path
+ android:pathData="M3.5,8.975L0.069,5.544L1.644,3.969L3.5,5.825L8.356,0.969L9.931,2.544L3.5,8.975Z"
+ android:fillColor="@color/settingslib_colorContentLevel_low"/>
+ </vector>
+ </item>
+</layer-list>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable/settingslib_expressive_icon_level_medium.xml b/packages/SettingsLib/SettingsTheme/res/drawable/settingslib_expressive_icon_level_medium.xml
new file mode 100644
index 0000000..cdcb982
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/drawable/settingslib_expressive_icon_level_medium.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+ -->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <item>
+ <vector
+ android:width="20dp"
+ android:height="20dp"
+ android:viewportWidth="20"
+ android:viewportHeight="20">
+ <path
+ android:pathData="M8.804,0.296C9.554,-0.099 10.446,-0.099 11.196,0.296L12.45,0.955C12.703,1.088 12.976,1.178 13.257,1.221L14.654,1.436C15.489,1.564 16.211,2.096 16.589,2.862L17.223,4.144C17.349,4.402 17.518,4.637 17.721,4.84L18.726,5.847C19.328,6.449 19.603,7.31 19.465,8.155L19.236,9.57C19.189,9.855 19.189,10.145 19.236,10.43L19.465,11.845C19.603,12.69 19.328,13.55 18.726,14.153L17.721,15.16C17.518,15.363 17.349,15.597 17.223,15.856L16.589,17.137C16.211,17.903 15.489,18.435 14.654,18.564L13.257,18.78C12.976,18.822 12.703,18.913 12.45,19.045L11.196,19.704C10.446,20.099 9.554,20.099 8.804,19.704L7.549,19.045C7.297,18.913 7.024,18.822 6.743,18.78L5.346,18.564C4.511,18.435 3.789,17.903 3.411,17.137L2.777,15.856C2.651,15.597 2.482,15.363 2.279,15.16L1.274,14.153C0.672,13.55 0.397,12.69 0.535,11.845L0.764,10.43C0.811,10.145 0.811,9.855 0.764,9.57L0.535,8.155C0.397,7.31 0.672,6.449 1.274,5.847L2.279,4.84C2.482,4.637 2.651,4.402 2.777,4.144L3.411,2.862C3.789,2.096 4.511,1.564 5.346,1.436L6.743,1.221C7.024,1.178 7.297,1.088 7.549,0.955L8.804,0.296Z"
+ android:fillColor="@color/settingslib_colorBackgroundLevel_medium"/>
+ </vector>
+ </item>
+ <item android:gravity="center">
+ <vector
+ android:width="4dp"
+ android:height="12dp"
+ android:viewportWidth="4"
+ android:viewportHeight="12">
+ <path
+ android:pathData="M0.894,8.081V0.919H3.106V8.081H0.894ZM0.894,11.081V8.869H3.106V11.081H0.894Z"
+ android:fillColor="@color/settingslib_colorContentLevel_medium"/>
+ </vector>
+ </item>
+</layer-list>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable/settingslib_expressive_icon_level_normal.xml b/packages/SettingsLib/SettingsTheme/res/drawable/settingslib_expressive_icon_level_normal.xml
new file mode 100644
index 0000000..448d596
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/drawable/settingslib_expressive_icon_level_normal.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+ -->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <item>
+ <vector
+ android:width="20dp"
+ android:height="20dp"
+ android:viewportWidth="20"
+ android:viewportHeight="20">
+ <path
+ android:pathData="M8.804,0.296C9.554,-0.099 10.446,-0.099 11.196,0.296L12.45,0.955C12.703,1.088 12.976,1.178 13.257,1.221L14.654,1.436C15.489,1.564 16.211,2.096 16.589,2.862L17.223,4.144C17.349,4.402 17.518,4.637 17.721,4.84L18.726,5.847C19.328,6.449 19.603,7.31 19.465,8.155L19.236,9.57C19.189,9.855 19.189,10.145 19.236,10.43L19.465,11.845C19.603,12.69 19.328,13.55 18.726,14.153L17.721,15.16C17.518,15.363 17.349,15.597 17.223,15.856L16.589,17.137C16.211,17.903 15.489,18.435 14.654,18.564L13.257,18.78C12.976,18.822 12.703,18.913 12.45,19.045L11.196,19.704C10.446,20.099 9.554,20.099 8.804,19.704L7.549,19.045C7.297,18.913 7.024,18.822 6.743,18.78L5.346,18.564C4.511,18.435 3.789,17.903 3.411,17.137L2.777,15.856C2.651,15.597 2.482,15.363 2.279,15.16L1.274,14.153C0.672,13.55 0.397,12.69 0.535,11.845L0.764,10.43C0.811,10.145 0.811,9.855 0.764,9.57L0.535,8.155C0.397,7.31 0.672,6.449 1.274,5.847L2.279,4.84C2.482,4.637 2.651,4.402 2.777,4.144L3.411,2.862C3.789,2.096 4.511,1.564 5.346,1.436L6.743,1.221C7.024,1.178 7.297,1.088 7.549,0.955L8.804,0.296Z"
+ android:fillColor="@color/settingslib_materialColorOnSurface"/>
+ </vector>
+ </item>
+ <item android:gravity="center">
+ <vector
+ android:width="14dp"
+ android:height="4dp"
+ android:viewportWidth="14"
+ android:viewportHeight="4">
+ <path
+ android:pathData="M0.962,3.106V0.894H13.038V3.106H0.962Z"
+ android:fillColor="@color/settingslib_materialColorSurface"/>
+ </vector>
+ </item>
+</layer-list>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable/settingslib_expressive_icon_up.xml b/packages/SettingsLib/SettingsTheme/res/drawable/settingslib_expressive_icon_up.xml
new file mode 100644
index 0000000..c387305
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/drawable/settingslib_expressive_icon_up.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="960"
+ android:viewportHeight="960">
+ <path
+ android:fillColor="@color/settingslib_materialColorOnSurface"
+ android:pathData="M480,432L296,616L240,560L480,320L720,560L664,616L480,432Z"/>
+</vector>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_preference.xml b/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_preference.xml
index 511e2bb..4ef747a 100644
--- a/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_preference.xml
+++ b/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_preference.xml
@@ -19,7 +19,7 @@
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:minHeight="?android:attr/listPreferredItemHeightSmall"
+ android:minHeight="72dp"
android:gravity="center_vertical"
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
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-night/colors.xml b/packages/SettingsLib/SettingsTheme/res/values-night/colors.xml
index e57fe4f..d677bba 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-night/colors.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-night/colors.xml
@@ -50,4 +50,9 @@
<color name="settingslib_materialColorSurfaceContainerLow">#0E0E0E</color>
<color name="settingslib_materialColorSurfaceContainerHigh">#2A2A2A</color>
<color name="settingslib_materialColorSurfaceContainerHighest">#343434</color>
+
+ <color name="settingslib_colorBackgroundLevel_high">@color/m3_ref_palette_red60</color>
+ <color name="settingslib_colorContentLevel_high">@color/m3_ref_palette_red10</color>
+ <color name="settingslib_colorBackgroundLevel_low">@color/m3_ref_palette_green70</color>
+ <color name="settingslib_colorContentLevel_low">@color/m3_ref_palette_green10</color>
</resources>
\ No newline at end of file
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/SettingsTheme/res/values/colors.xml b/packages/SettingsLib/SettingsTheme/res/values/colors.xml
index c5c613b..e8ab99e 100644
--- a/packages/SettingsLib/SettingsTheme/res/values/colors.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values/colors.xml
@@ -76,4 +76,11 @@
<color name="settingslib_materialColorSurfaceContainerLowest">#FFFFFF</color>
<color name="settingslib_materialColorSurfaceContainerHigh">#E8E8E8</color>
<color name="settingslib_materialColorSurfaceContainerHighest">#E3E3E3</color>
+
+ <color name="settingslib_colorBackgroundLevel_high">@color/m3_ref_palette_red50</color>
+ <color name="settingslib_colorContentLevel_high">@color/m3_ref_palette_red100</color>
+ <color name="settingslib_colorBackgroundLevel_medium">@color/m3_ref_palette_yellow80</color>
+ <color name="settingslib_colorContentLevel_medium">@color/m3_ref_palette_yellow10</color>
+ <color name="settingslib_colorBackgroundLevel_low">@color/m3_ref_palette_green50</color>
+ <color name="settingslib_colorContentLevel_low">@color/m3_ref_palette_green100</color>
</resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/Spa/.gitignore b/packages/SettingsLib/Spa/.gitignore
index b2ed268..5790fde 100644
--- a/packages/SettingsLib/Spa/.gitignore
+++ b/packages/SettingsLib/Spa/.gitignore
@@ -7,6 +7,7 @@
/.idea/workspace.xml
/.idea/navEditor.xml
/.idea/assetWizardSettings.xml
+/.kotlin
.DS_Store
build
/captures
diff --git a/packages/SettingsLib/Spa/build.gradle.kts b/packages/SettingsLib/Spa/build.gradle.kts
index cf695d0..5e72c43 100644
--- a/packages/SettingsLib/Spa/build.gradle.kts
+++ b/packages/SettingsLib/Spa/build.gradle.kts
@@ -28,7 +28,7 @@
allprojects {
extra["androidTop"] = androidTop
- extra["jetpackComposeVersion"] = "1.8.0-alpha06"
+ extra["jetpackComposeVersion"] = "1.8.0-alpha08"
}
subprojects {
diff --git a/packages/SettingsLib/Spa/gradle/libs.versions.toml b/packages/SettingsLib/Spa/gradle/libs.versions.toml
index 04ef96a..4113ad8 100644
--- a/packages/SettingsLib/Spa/gradle/libs.versions.toml
+++ b/packages/SettingsLib/Spa/gradle/libs.versions.toml
@@ -15,9 +15,9 @@
#
[versions]
-agp = "8.7.3"
+agp = "8.8.0"
dexmaker-mockito = "2.28.3"
-jvm = "17"
+jvm = "21"
kotlin = "2.0.21"
truth = "1.4.4"
diff --git a/packages/SettingsLib/Spa/spa/build.gradle.kts b/packages/SettingsLib/Spa/spa/build.gradle.kts
index a0bbb0c..1396629 100644
--- a/packages/SettingsLib/Spa/spa/build.gradle.kts
+++ b/packages/SettingsLib/Spa/spa/build.gradle.kts
@@ -52,14 +52,14 @@
dependencies {
api(project(":SettingsLibColor"))
api("androidx.appcompat:appcompat:1.7.0")
- api("androidx.compose.material3:material3:1.4.0-alpha04")
+ api("androidx.compose.material3:material3:1.4.0-alpha05")
api("androidx.compose.material:material-icons-extended")
api("androidx.compose.runtime:runtime-livedata:$jetpackComposeVersion")
api("androidx.compose.ui:ui-tooling-preview:$jetpackComposeVersion")
api("androidx.graphics:graphics-shapes-android:1.0.1")
api("androidx.lifecycle:lifecycle-livedata-ktx")
api("androidx.lifecycle:lifecycle-runtime-compose")
- api("androidx.navigation:navigation-compose:2.9.0-alpha03")
+ api("androidx.navigation:navigation-compose:2.9.0-alpha04")
api("com.github.PhilJay:MPAndroidChart:v3.1.0-alpha")
api("com.google.android.material:material:1.13.0-alpha08")
debugApi("androidx.compose.ui:ui-tooling:$jetpackComposeVersion")
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/DropdownTextBox.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/DropdownTextBox.kt
index bdbe62c..8e59fd7 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/DropdownTextBox.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/DropdownTextBox.kt
@@ -20,9 +20,9 @@
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.ExposedDropdownMenuAnchorType
import androidx.compose.material3.ExposedDropdownMenuBox
import androidx.compose.material3.ExposedDropdownMenuDefaults
-import androidx.compose.material3.MenuAnchorType
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
@@ -66,7 +66,7 @@
OutlinedTextField(
// The `menuAnchor` modifier must be passed to the text field for correctness.
modifier = Modifier
- .menuAnchor(MenuAnchorType.PrimaryNotEditable)
+ .menuAnchor(ExposedDropdownMenuAnchorType.PrimaryNotEditable)
.fillMaxWidth(),
value = text,
onValueChange = { },
diff --git a/packages/SettingsLib/Spa/testutils/build.gradle.kts b/packages/SettingsLib/Spa/testutils/build.gradle.kts
index 7dbd320..03cd243 100644
--- a/packages/SettingsLib/Spa/testutils/build.gradle.kts
+++ b/packages/SettingsLib/Spa/testutils/build.gradle.kts
@@ -36,7 +36,7 @@
dependencies {
api(project(":spa"))
- api("androidx.arch.core:core-testing:2.2.0-alpha01")
+ api("androidx.arch.core:core-testing:2.2.0")
api("androidx.compose.ui:ui-test-junit4:$jetpackComposeVersion")
api("androidx.lifecycle:lifecycle-runtime-testing")
api("org.mockito.kotlin:mockito-kotlin:2.2.11")
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/SettingsLib/res/values/config.xml b/packages/SettingsLib/res/values/config.xml
index 3c3de04..502eb6c 100644
--- a/packages/SettingsLib/res/values/config.xml
+++ b/packages/SettingsLib/res/values/config.xml
@@ -41,4 +41,15 @@
<string name="config_avatar_picker_class" translatable="false">
com.android.avatarpicker.ui.AvatarPickerActivity
</string>
+
+ <array name="config_override_carrier_5g_plus">
+ <item>@array/carrier_2032_5g_plus</item>
+ </array>
+
+ <integer-array name="carrier_2032_5g_plus">
+ <!-- carrier id: 2032 -->
+ <item>2032</item>
+ <!-- network type: "5G+" -->
+ <item>@string/data_connection_5g_plus_carrier_2032</item>
+ </integer-array>
</resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index 6cf9e83..3da2271 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -1723,6 +1723,8 @@
<!-- Content description of the data connection type 5G+. [CHAR LIMIT=NONE] -->
<string name="data_connection_5g_plus" translatable="false">5G+</string>
+ <!-- Content description of the data connection type 5G+ for carrier 2032. [CHAR LIMIT=NONE] -->
+ <string name="data_connection_5g_plus_carrier_2032" translatable="false">5G+</string>
<!-- Content description of the data connection type Carrier WiFi. [CHAR LIMIT=NONE] -->
<string name="data_connection_carrier_wifi">W+</string>
diff --git a/packages/SettingsLib/src/com/android/settingslib/mobile/MobileMappings.java b/packages/SettingsLib/src/com/android/settingslib/mobile/MobileMappings.java
index b7108c9..cf52eb3 100644
--- a/packages/SettingsLib/src/com/android/settingslib/mobile/MobileMappings.java
+++ b/packages/SettingsLib/src/com/android/settingslib/mobile/MobileMappings.java
@@ -15,11 +15,14 @@
*/
package com.android.settingslib.mobile;
+import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.Resources;
+import android.content.res.TypedArray;
import android.os.PersistableBundle;
import android.telephony.Annotation;
import android.telephony.CarrierConfigManager;
+import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyDisplayInfo;
import android.telephony.TelephonyManager;
@@ -196,9 +199,9 @@
networkToIconLookup.put(toDisplayIconKey(
TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA),
TelephonyIcons.NR_5G);
- networkToIconLookup.put(toDisplayIconKey(
- TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_ADVANCED),
- TelephonyIcons.NR_5G_PLUS);
+ networkToIconLookup.put(
+ toDisplayIconKey(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_ADVANCED),
+ config.mobileIconGroup5gPlus);
networkToIconLookup.put(toIconKey(
TelephonyManager.NETWORK_TYPE_NR),
TelephonyIcons.NR_5G);
@@ -217,6 +220,7 @@
public boolean hideLtePlus = false;
public boolean hspaDataDistinguishable;
public boolean alwaysShowDataRatIcon = false;
+ public MobileIconGroup mobileIconGroup5gPlus = TelephonyIcons.NR_5G_PLUS;
/**
* Reads the latest configs.
@@ -250,9 +254,54 @@
config.hideLtePlus = b.getBoolean(
CarrierConfigManager.KEY_HIDE_LTE_PLUS_DATA_ICON_BOOL);
}
+
+ SubscriptionManager subscriptionManager =
+ context.getSystemService(SubscriptionManager.class);
+ if (subscriptionManager != null) {
+ SubscriptionInfo subInfo = subscriptionManager.getDefaultDataSubscriptionInfo();
+ if (subInfo != null) {
+ readMobileIconGroup5gPlus(subInfo.getCarrierId(), res, config);
+ }
+ }
return config;
}
+ @SuppressLint("ResourceType")
+ private static void readMobileIconGroup5gPlus(int carrierId, Resources res, Config config) {
+ int networkTypeResId = 0;
+ TypedArray groupArray;
+ try {
+ groupArray = res.obtainTypedArray(R.array.config_override_carrier_5g_plus);
+ } catch (Resources.NotFoundException e) {
+ return;
+ }
+ for (int i = 0; i < groupArray.length() && networkTypeResId == 0; i++) {
+ int groupId = groupArray.getResourceId(i, 0);
+ if (groupId == 0) {
+ continue;
+ }
+ TypedArray carrierArray;
+ try {
+ carrierArray = res.obtainTypedArray(groupId);
+ } catch (Resources.NotFoundException e) {
+ continue;
+ }
+ int groupCarrierId = carrierArray.getInt(0, 0);
+ if (groupCarrierId == carrierId) {
+ networkTypeResId = carrierArray.getResourceId(1, 0);
+ }
+ carrierArray.recycle();
+ }
+ groupArray.recycle();
+
+ if (networkTypeResId != 0) {
+ config.mobileIconGroup5gPlus = new MobileIconGroup(
+ TelephonyIcons.NR_5G_PLUS.name,
+ networkTypeResId,
+ TelephonyIcons.NR_5G_PLUS.dataType);
+ }
+ }
+
/**
* Returns true if this config and the other config are semantically equal.
*
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
index d367748..d936a5c6 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
@@ -204,7 +204,6 @@
Settings.Secure.AWARE_TAP_PAUSE_TOUCH_COUNT,
Settings.Secure.PEOPLE_STRIP,
Settings.Secure.MEDIA_CONTROLS_RESUME,
- Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION,
Settings.Secure.MEDIA_CONTROLS_LOCK_SCREEN,
Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE,
Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS,
@@ -288,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 242bdce..919c3c4 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -312,7 +312,6 @@
VALIDATORS.put(Secure.TAP_GESTURE, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.PEOPLE_STRIP, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.MEDIA_CONTROLS_RESUME, BOOLEAN_VALIDATOR);
- VALIDATORS.put(Secure.MEDIA_CONTROLS_RECOMMENDATION, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.MEDIA_CONTROLS_LOCK_SCREEN, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.ACCESSIBILITY_MAGNIFICATION_MODE,
new InclusiveIntegerRangeValidator(
@@ -457,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 848ea0f..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",
@@ -534,7 +535,10 @@
"-Adagger.useBindingGraphFix=ENABLED",
"-Aroom.schemaLocation=frameworks/base/packages/SystemUI/schemas",
],
- kotlincflags: ["-Xjvm-default=all"],
+ kotlincflags: [
+ "-Xjvm-default=all",
+ "-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi",
+ ],
plugins: [
"androidx.room_room-compiler-plugin",
@@ -703,6 +707,7 @@
"androidx.lifecycle_lifecycle-viewmodel-compose",
"TraceurCommon",
"Traceur-res",
+ "aconfig_settings_flags_lib",
],
}
@@ -711,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",
@@ -757,6 +765,7 @@
"-Xjvm-default=all",
// TODO(b/352363800): Why do we need this?
"-J-Xmx8192M",
+ "-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi",
],
javacflags: [
"-Adagger.useBindingGraphFix=ENABLED",
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index df70b03..f753316 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -1933,3 +1933,17 @@
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "ui_rich_ongoing_force_expanded"
+ namespace: "systemui"
+ description: "Force promoted notifications to always be expanded"
+ bug: "380901479"
+}
+
+flag {
+ name: "aod_ui_rich_ongoing"
+ namespace: "systemui"
+ description: "Show a rich ongoing notification on the always-on display (depends on ui_rich_ongoing)"
+ bug: "369151941"
+}
\ No newline at end of file
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/SceneTransitionLayout.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
index a1117e1..431a376 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
@@ -159,6 +159,9 @@
/** The state of the [SceneTransitionLayout] in which this content is contained. */
val layoutState: SceneTransitionLayoutState
+ /** The [LookaheadScope] used by the [SceneTransitionLayout]. */
+ val lookaheadScope: LookaheadScope
+
/**
* Tag an element identified by [key].
*
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 59b4a09..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
@@ -27,6 +31,7 @@
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
+import androidx.compose.ui.layout.LookaheadScope
import androidx.compose.ui.layout.approachLayout
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.unit.IntSize
@@ -119,16 +124,28 @@
override val layoutState: SceneTransitionLayoutState = layoutImpl.state
+ 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/accessibility/fontscaling/FontScalingDialogDelegateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogDelegateTest.kt
index 73efea7..2713bb0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogDelegateTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogDelegateTest.kt
@@ -316,5 +316,7 @@
dialog.onConfigurationChanged(config)
testableLooper.processAllMessages()
assertThat(doneButton.isEnabled).isTrue()
+
+ dialog.dismiss()
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalDreamStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalDreamStartableTest.kt
index e531e65..00d5afe 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalDreamStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalDreamStartableTest.kt
@@ -16,14 +16,17 @@
package com.android.systemui.communal
+import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.FlagsParameterization
import android.service.dream.dreamManager
-import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.Flags
+import com.android.systemui.Flags.FLAG_GLANCEABLE_HUB_V2
import com.android.systemui.SysuiTestCase
import com.android.systemui.communal.domain.interactor.communalSceneInteractor
import com.android.systemui.communal.domain.interactor.communalSettingsInteractor
+import com.android.systemui.communal.domain.interactor.setCommunalV2Enabled
import com.android.systemui.flags.Flags.COMMUNAL_SERVICE_ENABLED
import com.android.systemui.flags.fakeFeatureFlagsClassic
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
@@ -48,12 +51,14 @@
import org.junit.runner.RunWith
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@EnableFlags(Flags.FLAG_COMMUNAL_HUB)
-@RunWith(AndroidJUnit4::class)
-class CommunalDreamStartableTest : SysuiTestCase() {
+@RunWith(ParameterizedAndroidJunit4::class)
+class CommunalDreamStartableTest(flags: FlagsParameterization) : SysuiTestCase() {
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
@@ -63,26 +68,50 @@
private val keyguardRepository by lazy { kosmos.fakeKeyguardRepository }
private val powerRepository by lazy { kosmos.fakePowerRepository }
+ init {
+ mSetFlagsRule.setFlagsParameterization(flags)
+ }
+
@Before
fun setUp() {
kosmos.fakeFeatureFlagsClassic.set(COMMUNAL_SERVICE_ENABLED, true)
underTest =
CommunalDreamStartable(
- powerInteractor = kosmos.powerInteractor,
- communalSettingsInteractor = kosmos.communalSettingsInteractor,
- keyguardInteractor = kosmos.keyguardInteractor,
- keyguardTransitionInteractor = kosmos.keyguardTransitionInteractor,
- dreamManager = dreamManager,
- communalSceneInteractor = kosmos.communalSceneInteractor,
- bgScope = kosmos.applicationCoroutineScope,
- )
- .apply { start() }
+ powerInteractor = kosmos.powerInteractor,
+ communalSettingsInteractor = kosmos.communalSettingsInteractor,
+ keyguardInteractor = kosmos.keyguardInteractor,
+ keyguardTransitionInteractor = kosmos.keyguardTransitionInteractor,
+ dreamManager = dreamManager,
+ communalSceneInteractor = kosmos.communalSceneInteractor,
+ bgScope = kosmos.applicationCoroutineScope,
+ )
}
+ @EnableFlags(FLAG_GLANCEABLE_HUB_V2)
+ @Test
+ fun dreamNotStartedWhenTransitioningToHub() =
+ testScope.runTest {
+ // Enable v2 flag and recreate + rerun start method.
+ kosmos.setCommunalV2Enabled(true)
+ underTest.start()
+
+ keyguardRepository.setKeyguardShowing(true)
+ keyguardRepository.setDreaming(false)
+ powerRepository.setScreenPowerState(ScreenPowerState.SCREEN_ON)
+ whenever(dreamManager.canStartDreaming(/* isScreenOn= */ true)).thenReturn(true)
+ runCurrent()
+
+ transition(from = KeyguardState.DREAMING, to = KeyguardState.GLANCEABLE_HUB)
+
+ verify(dreamManager, never()).startDream()
+ }
+
+ @DisableFlags(FLAG_GLANCEABLE_HUB_V2)
@Test
fun startDreamWhenTransitioningToHub() =
testScope.runTest {
+ underTest.start()
keyguardRepository.setKeyguardShowing(true)
keyguardRepository.setDreaming(false)
powerRepository.setScreenPowerState(ScreenPowerState.SCREEN_ON)
@@ -100,6 +129,7 @@
@EnableFlags(Flags.FLAG_RESTART_DREAM_ON_UNOCCLUDE)
fun restartDreamingWhenTransitioningFromDreamingToOccludedToDreaming() =
testScope.runTest {
+ underTest.start()
keyguardRepository.setDreaming(false)
powerRepository.setScreenPowerState(ScreenPowerState.SCREEN_ON)
whenever(dreamManager.canStartDreaming(/* isScreenOn= */ true)).thenReturn(true)
@@ -122,9 +152,11 @@
verify(dreamManager).startDream()
}
+ @DisableFlags(FLAG_GLANCEABLE_HUB_V2)
@Test
fun shouldNotStartDreamWhenIneligibleToDream() =
testScope.runTest {
+ underTest.start()
keyguardRepository.setDreaming(false)
powerRepository.setScreenPowerState(ScreenPowerState.SCREEN_ON)
// Not eligible to dream
@@ -134,9 +166,11 @@
verify(dreamManager, never()).startDream()
}
+ @DisableFlags(FLAG_GLANCEABLE_HUB_V2)
@Test
fun shouldNotStartDreamIfAlreadyDreaming() =
testScope.runTest {
+ underTest.start()
keyguardRepository.setDreaming(true)
powerRepository.setScreenPowerState(ScreenPowerState.SCREEN_ON)
whenever(dreamManager.canStartDreaming(/* isScreenOn= */ true)).thenReturn(true)
@@ -145,9 +179,11 @@
verify(dreamManager, never()).startDream()
}
+ @DisableFlags(FLAG_GLANCEABLE_HUB_V2)
@Test
fun shouldNotStartDreamForInvalidTransition() =
testScope.runTest {
+ underTest.start()
keyguardRepository.setDreaming(true)
powerRepository.setScreenPowerState(ScreenPowerState.SCREEN_ON)
whenever(dreamManager.canStartDreaming(/* isScreenOn= */ true)).thenReturn(true)
@@ -160,9 +196,11 @@
}
}
+ @DisableFlags(FLAG_GLANCEABLE_HUB_V2)
@Test
fun shouldNotStartDreamWhenLaunchingWidget() =
testScope.runTest {
+ underTest.start()
keyguardRepository.setKeyguardShowing(true)
keyguardRepository.setDreaming(false)
powerRepository.setScreenPowerState(ScreenPowerState.SCREEN_ON)
@@ -175,9 +213,11 @@
verify(dreamManager, never()).startDream()
}
+ @DisableFlags(FLAG_GLANCEABLE_HUB_V2)
@Test
fun shouldNotStartDreamWhenOccluded() =
testScope.runTest {
+ underTest.start()
keyguardRepository.setKeyguardShowing(true)
keyguardRepository.setDreaming(false)
powerRepository.setScreenPowerState(ScreenPowerState.SCREEN_ON)
@@ -194,8 +234,16 @@
kosmos.fakeKeyguardTransitionRepository.sendTransitionSteps(
from = from,
to = to,
- testScope = this
+ testScope = this,
)
runCurrent()
}
+
+ companion object {
+ @JvmStatic
+ @Parameters(name = "{0}")
+ fun getParams(): List<FlagsParameterization> {
+ return FlagsParameterization.allCombinationsOf(FLAG_GLANCEABLE_HUB_V2)
+ }
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt
index 5921e94..0df584f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt
@@ -58,6 +58,7 @@
import com.android.systemui.communal.domain.interactor.communalSettingsInteractor
import com.android.systemui.communal.domain.interactor.setCommunalAvailable
import com.android.systemui.communal.domain.interactor.setCommunalV2ConfigEnabled
+import com.android.systemui.communal.domain.interactor.setCommunalV2Enabled
import com.android.systemui.communal.shared.log.CommunalUiEvent
import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.complication.ComplicationHostViewController
@@ -747,7 +748,7 @@
@Test
@EnableFlags(Flags.FLAG_DREAM_WAKE_REDIRECT, FLAG_COMMUNAL_HUB)
- @DisableFlags(FLAG_SCENE_CONTAINER)
+ @DisableFlags(FLAG_SCENE_CONTAINER, FLAG_GLANCEABLE_HUB_V2)
@kotlin.Throws(RemoteException::class)
fun testTransitionToGlanceableHub() =
testScope.runTest {
@@ -774,6 +775,7 @@
@Test
@EnableFlags(Flags.FLAG_DREAM_WAKE_REDIRECT, FLAG_SCENE_CONTAINER, FLAG_COMMUNAL_HUB)
+ @DisableFlags(FLAG_GLANCEABLE_HUB_V2)
@kotlin.Throws(RemoteException::class)
fun testTransitionToGlanceableHub_sceneContainer() =
testScope.runTest {
@@ -802,7 +804,29 @@
}
@Test
+ @EnableFlags(Flags.FLAG_DREAM_WAKE_REDIRECT, FLAG_COMMUNAL_HUB, FLAG_GLANCEABLE_HUB_V2)
+ @Throws(RemoteException::class)
+ fun testRedirect_v2Enabled_notTriggered() =
+ testScope.runTest {
+ kosmos.setCommunalV2Enabled(true)
+ // Inform the overlay service of dream starting. Do not show dream complications.
+ client.startDream(
+ mWindowParams,
+ mDreamOverlayCallback,
+ DREAM_COMPONENT,
+ false /*isPreview*/,
+ false, /*shouldShowComplication*/
+ )
+ // Set communal available, verify that onRedirectWake is never called.
+ kosmos.setCommunalAvailable(true)
+ mMainExecutor.runAllReady()
+ runCurrent()
+ verify(mDreamOverlayCallback, never()).onRedirectWake(any())
+ }
+
+ @Test
@EnableFlags(Flags.FLAG_DREAM_WAKE_REDIRECT, FLAG_COMMUNAL_HUB)
+ @DisableFlags(FLAG_GLANCEABLE_HUB_V2)
@Throws(RemoteException::class)
fun testRedirectExit() =
testScope.runTest {
@@ -1347,7 +1371,11 @@
@JvmStatic
@Parameters(name = "{0}")
fun getParams(): List<FlagsParameterization> {
- return FlagsParameterization.allCombinationsOf(FLAG_COMMUNAL_HUB).andSceneContainer()
+ return FlagsParameterization.allCombinationsOf(
+ FLAG_COMMUNAL_HUB,
+ FLAG_GLANCEABLE_HUB_V2,
+ )
+ .andSceneContainer()
}
}
}
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/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
index fe9da0d..88c8b1f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
@@ -19,6 +19,7 @@
import android.app.admin.DevicePolicyManager
import android.os.UserHandle
+import android.view.accessibility.AccessibilityManager
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.internal.widget.LockPatternUtils
@@ -96,6 +97,7 @@
@Mock private lateinit var shadeInteractor: ShadeInteractor
@Mock private lateinit var logger: KeyguardQuickAffordancesLogger
@Mock private lateinit var metricsLogger: KeyguardQuickAffordancesMetricsLogger
+ @Mock private lateinit var accessibilityManager: AccessibilityManager
private lateinit var underTest: KeyguardQuickAffordanceInteractor
@@ -199,11 +201,13 @@
backgroundDispatcher = kosmos.testDispatcher,
appContext = context,
communalSettingsInteractor = kosmos.communalSettingsInteractor,
+ accessibilityManager = accessibilityManager,
sceneInteractor = { kosmos.sceneInteractor },
)
kosmos.keyguardQuickAffordanceInteractor = underTest
whenever(shadeInteractor.anyExpansion).thenReturn(MutableStateFlow(0f))
+ whenever(accessibilityManager.isEnabled()).thenReturn(false)
}
@Test
@@ -672,6 +676,22 @@
}
@Test
+ fun useLongPress_withA11yEnabled_isFalse() =
+ testScope.runTest {
+ whenever(accessibilityManager.isEnabled()).thenReturn(true)
+ val useLongPress by collectLastValue(underTest.useLongPress())
+ assertThat(useLongPress).isFalse()
+ }
+
+ @Test
+ fun useLongPress_withA11yDisabled_isFalse() =
+ testScope.runTest {
+ whenever(accessibilityManager.isEnabled()).thenReturn(false)
+ val useLongPress by collectLastValue(underTest.useLongPress())
+ assertThat(useLongPress).isTrue()
+ }
+
+ @Test
fun useLongPress_whenDocked_isFalse() =
testScope.runTest {
dockManager.setIsDocked(true)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelTest.kt
index b0af8b1..75262a4 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelTest.kt
@@ -23,6 +23,7 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.animation.Expandable
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.kosmos.Kosmos
@@ -35,7 +36,14 @@
import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
import com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer
import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
+import com.android.systemui.statusbar.core.StatusBarRootModernization
+import com.android.systemui.statusbar.notification.data.model.activeNotificationModel
+import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
+import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationsStore
+import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository
import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel
+import com.android.systemui.statusbar.notification.shared.CallType
+import com.android.systemui.statusbar.phone.ongoingcall.StatusBarChipsModernization
import com.android.systemui.statusbar.phone.ongoingcall.data.repository.ongoingCallRepository
import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallModel
import com.android.systemui.statusbar.phone.ongoingcall.shared.model.inCallModel
@@ -44,6 +52,7 @@
import kotlin.test.Test
import kotlinx.coroutines.test.runTest
import org.junit.runner.RunWith
+import org.mockito.kotlin.any
import org.mockito.kotlin.mock
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
@@ -52,6 +61,7 @@
@RunWith(AndroidJUnit4::class)
class CallChipViewModelTest : SysuiTestCase() {
private val kosmos = Kosmos()
+ private val notificationListRepository = kosmos.activeNotificationListRepository
private val testScope = kosmos.testScope
private val repo = kosmos.ongoingCallRepository
@@ -65,6 +75,8 @@
)
.thenReturn(chipBackgroundView)
}
+ private val mockExpandable: Expandable =
+ mock<Expandable>().apply { whenever(dialogTransitionController(any())).thenReturn(mock()) }
private val underTest by lazy { kosmos.callChipViewModel }
@@ -337,6 +349,7 @@
}
@Test
+ @DisableFlags(StatusBarChipsModernization.FLAG_NAME)
fun chip_inCall_nullIntent_nullClickListener() =
testScope.runTest {
val latest by collectLastValue(underTest.chip)
@@ -347,6 +360,7 @@
}
@Test
+ @DisableFlags(StatusBarChipsModernization.FLAG_NAME)
fun chip_inCall_positiveStartTime_validIntent_clickListenerLaunchesIntent() =
testScope.runTest {
val latest by collectLastValue(underTest.chip)
@@ -364,6 +378,7 @@
}
@Test
+ @DisableFlags(StatusBarChipsModernization.FLAG_NAME)
fun chip_inCall_zeroStartTime_validIntent_clickListenerLaunchesIntent() =
testScope.runTest {
val latest by collectLastValue(underTest.chip)
@@ -381,6 +396,72 @@
verify(kosmos.activityStarter).postStartActivityDismissingKeyguard(pendingIntent, null)
}
+ @Test
+ @EnableFlags(StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME)
+ fun chip_inCall_nullIntent_noneClickBehavior() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.chip)
+
+ postOngoingCallNotification(
+ repository = notificationListRepository,
+ startTimeMs = 1000L,
+ intent = null,
+ )
+
+ assertThat((latest as OngoingActivityChipModel.Shown).clickBehavior)
+ .isInstanceOf(OngoingActivityChipModel.ClickBehavior.None::class.java)
+ }
+
+ @Test
+ @EnableFlags(StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME)
+ fun chip_inCall_positiveStartTime_validIntent_clickBehaviorLaunchesIntent() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.chip)
+
+ val pendingIntent = mock<PendingIntent>()
+ postOngoingCallNotification(
+ repository = notificationListRepository,
+ startTimeMs = 1000L,
+ intent = pendingIntent,
+ )
+
+ val clickBehavior = (latest as OngoingActivityChipModel.Shown).clickBehavior
+ assertThat(clickBehavior)
+ .isInstanceOf(OngoingActivityChipModel.ClickBehavior.ExpandAction::class.java)
+ (clickBehavior as OngoingActivityChipModel.ClickBehavior.ExpandAction).onClick(
+ mockExpandable
+ )
+
+ // Ensure that the SysUI didn't modify the notification's intent by verifying it
+ // directly matches the `PendingIntent` set -- see b/212467440.
+ verify(kosmos.activityStarter).postStartActivityDismissingKeyguard(pendingIntent, null)
+ }
+
+ @Test
+ @EnableFlags(StatusBarRootModernization.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME)
+ fun chip_inCall_zeroStartTime_validIntent_clickBehaviorLaunchesIntent() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.chip)
+
+ val pendingIntent = mock<PendingIntent>()
+ postOngoingCallNotification(
+ repository = notificationListRepository,
+ startTimeMs = 0L,
+ intent = pendingIntent,
+ )
+
+ val clickBehavior = (latest as OngoingActivityChipModel.Shown).clickBehavior
+ assertThat(clickBehavior)
+ .isInstanceOf(OngoingActivityChipModel.ClickBehavior.ExpandAction::class.java)
+ (clickBehavior as OngoingActivityChipModel.ClickBehavior.ExpandAction).onClick(
+ mockExpandable
+ )
+
+ // Ensure that the SysUI didn't modify the notification's intent by verifying it
+ // directly matches the `PendingIntent` set -- see b/212467440.
+ verify(kosmos.activityStarter).postStartActivityDismissingKeyguard(pendingIntent, null)
+ }
+
companion object {
fun createStatusBarIconViewOrNull(): StatusBarIconView? =
if (StatusBarConnectedDisplays.isEnabled) {
@@ -389,6 +470,27 @@
mock<StatusBarIconView>()
}
+ fun postOngoingCallNotification(
+ repository: ActiveNotificationListRepository,
+ startTimeMs: Long,
+ intent: PendingIntent?,
+ ) {
+ repository.activeNotifications.value =
+ ActiveNotificationsStore.Builder()
+ .apply {
+ addIndividualNotif(
+ activeNotificationModel(
+ key = "notif1",
+ whenTime = startTimeMs,
+ callType = CallType.Ongoing,
+ statusBarChipIcon = null,
+ contentIntent = intent,
+ )
+ )
+ }
+ .build()
+ }
+
private val PROMOTED_CONTENT_WITH_COLOR =
PromotedNotificationContentModel.Builder("notif")
.apply {
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/common/ui/view/ViewExt.kt b/packages/SystemUI/src/com/android/systemui/common/ui/view/ViewExt.kt
index f7ea25c..b248043 100644
--- a/packages/SystemUI/src/com/android/systemui/common/ui/view/ViewExt.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/ui/view/ViewExt.kt
@@ -68,3 +68,11 @@
setOnTouchListener(listener)
return DisposableHandle { setOnTouchListener(null) }
}
+
+/** A null listener should also set the longClickable property to false */
+fun View.updateLongClickListener(listener: View.OnLongClickListener?) {
+ setOnLongClickListener(listener)
+ if (listener == null) {
+ setLongClickable(false)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/CommunalDreamStartable.kt b/packages/SystemUI/src/com/android/systemui/communal/CommunalDreamStartable.kt
index 1bd541e..6dc7c97 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/CommunalDreamStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/CommunalDreamStartable.kt
@@ -91,13 +91,19 @@
.launchIn(bgScope)
}
- // Restart the dream underneath the hub in order to support the ability to swipe
- // away the hub to enter the dream.
- startDream
- .sampleFilter(powerInteractor.isAwake) { isAwake ->
- !glanceableHubAllowKeyguardWhenDreaming() && dreamManager.canStartDreaming(isAwake)
- }
- .onEach { dreamManager.startDream() }
- .launchIn(bgScope)
+ // With hub v2, we no longer need to keep the dream running underneath the hub as there is
+ // no more swipe between the hub and dream. We can just start the dream on-demand when the
+ // user presses the dream coin.
+ if (!communalSettingsInteractor.isV2FlagEnabled()) {
+ // Restart the dream underneath the hub in order to support the ability to swipe away
+ // the hub to enter the dream.
+ startDream
+ .sampleFilter(powerInteractor.isAwake) { isAwake ->
+ !glanceableHubAllowKeyguardWhenDreaming() &&
+ dreamManager.canStartDreaming(isAwake)
+ }
+ .onEach { dreamManager.startDream() }
+ .launchIn(bgScope)
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
index 0b2b368..a56a63c 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
@@ -562,6 +562,13 @@
return;
}
+ if (mCommunalSettingsInteractor.isV2FlagEnabled()) {
+ // Dream wake redirect is not needed in V2 as we do not need to keep the dream awake
+ // underneath the hub anymore as there is no more swipe between the dream and hub. SysUI
+ // will automatically transition to the hub when the dream wakes.
+ return;
+ }
+
redirectWake(mCommunalAvailable && !glanceableHubAllowKeyguardWhenDreaming());
}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt b/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt
index 2ed0671..e588077 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt
@@ -30,6 +30,9 @@
import com.android.systemui.statusbar.notification.collection.SortBySectionTimeFlag
import com.android.systemui.statusbar.notification.emptyshade.shared.ModesEmptyShadeFix
import com.android.systemui.statusbar.notification.interruption.VisualInterruptionRefactor
+import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUi
+import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUiAod
+import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUiForceExpanded
import com.android.systemui.statusbar.notification.shared.NotificationAvalancheSuppression
import com.android.systemui.statusbar.notification.shared.NotificationMinimalism
import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun
@@ -52,6 +55,10 @@
NotificationMinimalism.token dependsOn NotificationThrottleHun.token
ModesEmptyShadeFix.token dependsOn modesUi
+ PromotedNotificationUiForceExpanded.token dependsOn PromotedNotificationUi.token
+
+ PromotedNotificationUiAod.token dependsOn PromotedNotificationUi.token
+
// SceneContainer dependencies
SceneContainerFlag.getFlagDependencies().forEach { (alpha, beta) -> alpha dependsOn beta }
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutCustomizer.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutCustomizer.kt
index d9e55f8..d8e2dab 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutCustomizer.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutCustomizer.kt
@@ -16,6 +16,7 @@
package com.android.systemui.keyboard.shortcut.ui.composable
+import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.interaction.MutableInteractionSource
@@ -27,6 +28,7 @@
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.sizeIn
@@ -211,19 +213,19 @@
shape = RoundedCornerShape(50.dp),
onClick = onCancel,
color = Color.Transparent,
- width = 80.dp,
+ modifier = Modifier.heightIn(40.dp),
contentColor = MaterialTheme.colorScheme.primary,
text = stringResource(R.string.shortcut_helper_customize_dialog_cancel_button_label),
+ border = BorderStroke(width = 1.dp, color = MaterialTheme.colorScheme.outlineVariant)
)
Spacer(modifier = Modifier.width(8.dp))
ShortcutHelperButton(
- modifier =
- Modifier.focusRequester(focusRequester).focusProperties {
- canFocus = true
- }, // enable focus on touch/click mode
+ modifier = Modifier
+ .heightIn(40.dp)
+ .focusRequester(focusRequester)
+ .focusProperties { canFocus = true }, // enable focus on touch/click mode
onClick = onConfirm,
color = MaterialTheme.colorScheme.primary,
- width = 116.dp,
contentColor = MaterialTheme.colorScheme.onPrimary,
text = confirmButtonText,
enabled = isConfirmButtonEnabled,
@@ -413,8 +415,7 @@
private fun ActionKeyContainer(defaultModifierKey: ShortcutKey.Icon.ResIdIcon) {
Row(
modifier =
- Modifier.height(48.dp)
- .width(105.dp)
+ Modifier.sizeIn(minWidth = 105.dp, minHeight = 48.dp)
.background(
color = MaterialTheme.colorScheme.surface,
shape = RoundedCornerShape(16.dp),
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt
index bf60c9a5..0054dd7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt
Binary files differ
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/Surfaces.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/Surfaces.kt
index 9a380f4..981a55c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/Surfaces.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/Surfaces.kt
@@ -32,7 +32,6 @@
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
-import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
@@ -45,7 +44,6 @@
import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.LocalTonalElevationEnabled
import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.contentColorFor
import androidx.compose.material3.minimumInteractiveComponentSize
@@ -74,13 +72,14 @@
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.semantics.role
import androidx.compose.ui.semantics.semantics
+import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.ui.zIndex
-import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.compose.modifiers.thenIf
import com.android.systemui.keyboard.shortcut.ui.model.IconSource
+import com.android.app.tracing.coroutines.launchTraced as launch
/**
* A selectable surface with no default focus/hover indications.
@@ -217,30 +216,37 @@
*/
@Composable
fun ShortcutHelperButton(
- modifier: Modifier = Modifier,
onClick: () -> Unit,
- shape: Shape = RoundedCornerShape(360.dp),
+ contentColor: Color,
color: Color,
- width: Dp,
- height: Dp = 40.dp,
+ modifier: Modifier = Modifier,
+ shape: Shape = RoundedCornerShape(360.dp),
iconSource: IconSource = IconSource(),
text: String? = null,
- contentColor: Color,
contentPaddingHorizontal: Dp = 16.dp,
contentPaddingVertical: Dp = 10.dp,
enabled: Boolean = true,
border: BorderStroke? = null,
contentDescription: String? = null,
) {
- ShortcutHelperButtonSurface(
+ ClickableShortcutSurface(
onClick = onClick,
shape = shape,
- color = color,
- modifier = modifier,
- enabled = enabled,
- width = width,
- height = height,
+ color = color.getDimmedColorIfDisabled(enabled),
border = border,
+ modifier = modifier.semantics { role = Role.Button },
+ interactionsConfig = InteractionsConfig(
+ hoverOverlayColor = MaterialTheme.colorScheme.onSurface,
+ hoverOverlayAlpha = 0.11f,
+ pressedOverlayColor = MaterialTheme.colorScheme.onSurface,
+ pressedOverlayAlpha = 0.15f,
+ focusOutlineColor = MaterialTheme.colorScheme.secondary,
+ focusOutlineStrokeWidth = 3.dp,
+ focusOutlinePadding = 2.dp,
+ surfaceCornerRadius = 28.dp,
+ focusOutlineCornerRadius = 33.dp,
+ ),
+ enabled = enabled
) {
Row(
modifier =
@@ -251,76 +257,45 @@
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Center,
) {
- if (iconSource.imageVector != null) {
- Icon(
- tint = contentColor,
- imageVector = iconSource.imageVector,
- contentDescription = contentDescription,
- modifier = Modifier.size(20.dp).wrapContentSize(Alignment.Center),
- )
- }
-
- if (iconSource.imageVector != null && text != null)
- Spacer(modifier = Modifier.weight(1f))
-
- if (text != null) {
- Text(
- text,
- color = contentColor,
- fontSize = 14.sp,
- style = MaterialTheme.typography.labelLarge,
- modifier = Modifier.wrapContentSize(Alignment.Center),
- )
- }
+ ShortcutHelperButtonContent(iconSource, contentColor, text, contentDescription)
}
}
}
@Composable
-private fun ShortcutHelperButtonSurface(
- onClick: () -> Unit,
- shape: Shape,
- color: Color,
- modifier: Modifier = Modifier,
- enabled: Boolean,
- width: Dp,
- height: Dp,
- border: BorderStroke?,
- content: @Composable () -> Unit,
+private fun ShortcutHelperButtonContent(
+ iconSource: IconSource,
+ contentColor: Color,
+ text: String?,
+ contentDescription: String?
) {
- if (enabled) {
- ClickableShortcutSurface(
- onClick = onClick,
- shape = shape,
- color = color,
- border = border,
- modifier = modifier.semantics { role = Role.Button }.width(width).height(height),
- interactionsConfig =
- InteractionsConfig(
- hoverOverlayColor = MaterialTheme.colorScheme.onSurface,
- hoverOverlayAlpha = 0.11f,
- pressedOverlayColor = MaterialTheme.colorScheme.onSurface,
- pressedOverlayAlpha = 0.15f,
- focusOutlineColor = MaterialTheme.colorScheme.secondary,
- focusOutlineStrokeWidth = 3.dp,
- focusOutlinePadding = 2.dp,
- surfaceCornerRadius = 28.dp,
- focusOutlineCornerRadius = 33.dp,
- ),
- ) {
- content()
- }
- } else {
- Surface(
- shape = shape,
- color = color.copy(0.38f),
- modifier = modifier.semantics { role = Role.Button }.width(width).height(height),
- ) {
- content()
- }
+ if (iconSource.imageVector != null) {
+ Icon(
+ tint = contentColor,
+ imageVector = iconSource.imageVector,
+ contentDescription = contentDescription,
+ modifier = Modifier.size(20.dp).wrapContentSize(Alignment.Center),
+ )
+ }
+
+ if (iconSource.imageVector != null && text != null)
+ Spacer(modifier = Modifier.width(8.dp))
+
+ if (text != null) {
+ Text(
+ text,
+ color = contentColor,
+ fontSize = 14.sp,
+ style = MaterialTheme.typography.labelLarge,
+ modifier = Modifier.wrapContentSize(Alignment.Center),
+ overflow = TextOverflow.Ellipsis,
+ )
}
}
+private fun Color.getDimmedColorIfDisabled(enabled: Boolean): Color =
+ if (enabled) this else copy(alpha = 0.38f)
+
@Composable
private fun surfaceColorAtElevation(color: Color, elevation: Dp): Color {
return MaterialTheme.colorScheme.applyTonalElevation(color, elevation)
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/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
index 7d8badd..b866fca 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
@@ -22,6 +22,7 @@
import android.content.Context
import android.content.Intent
import android.util.Log
+import android.view.accessibility.AccessibilityManager
import com.android.app.tracing.coroutines.withContextTraced as withContext
import com.android.compose.animation.scene.ObservableTransitionState
import com.android.internal.widget.LockPatternUtils
@@ -92,6 +93,7 @@
private val dockManager: DockManager,
private val biometricSettingsRepository: BiometricSettingsRepository,
private val communalSettingsInteractor: CommunalSettingsInteractor,
+ private val accessibilityManager: AccessibilityManager,
@Background private val backgroundDispatcher: CoroutineDispatcher,
@ShadeDisplayAware private val appContext: Context,
private val sceneInteractor: Lazy<SceneInteractor>,
@@ -115,7 +117,10 @@
*
* If `false`, the UI goes back to using single taps.
*/
- fun useLongPress(): Flow<Boolean> = dockManager.retrieveIsDocked().map { !it }
+ fun useLongPress(): Flow<Boolean> =
+ dockManager.retrieveIsDocked().map { isDocked ->
+ !isDocked && !accessibilityManager.isEnabled()
+ }
/** Returns an observable for the quick affordance at the given position. */
suspend fun quickAffordance(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt
index 8a2e3dd..f396cf9 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt
@@ -39,6 +39,7 @@
import com.android.systemui.animation.view.LaunchableImageView
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.common.ui.binder.IconViewBinder
+import com.android.systemui.common.ui.view.updateLongClickListener
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.ui.viewmodel.KeyguardQuickAffordanceHapticViewModel
import com.android.systemui.keyguard.ui.viewmodel.KeyguardQuickAffordanceViewModel
@@ -275,6 +276,7 @@
)
} else {
view.setOnClickListener(OnClickListener(viewModel, checkNotNull(falsingManager)))
+ view.updateLongClickListener(null)
}
} else {
view.onLongClickListener = null
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImpl.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImpl.kt
index 2191f37..f1f299a 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImpl.kt
@@ -45,16 +45,15 @@
import android.media.session.MediaSession
import android.media.session.PlaybackState
import android.net.Uri
-import android.os.Parcelable
import android.os.Process
import android.os.UserHandle
-import android.provider.Settings
import android.service.notification.StatusBarNotification
import android.support.v4.media.MediaMetadataCompat
import android.text.TextUtils
import android.util.Log
import android.util.Pair as APair
import androidx.media.utils.MediaConstants
+import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.app.tracing.traceSection
import com.android.internal.annotations.Keep
import com.android.internal.logging.InstanceId
@@ -86,11 +85,9 @@
import com.android.systemui.media.controls.util.MediaFlags
import com.android.systemui.media.controls.util.MediaUiEventLogger
import com.android.systemui.media.controls.util.SmallHash
-import com.android.systemui.plugins.BcSmartspaceDataPlugin
import com.android.systemui.res.R
import com.android.systemui.statusbar.NotificationMediaManager.isPlayingState
import com.android.systemui.statusbar.notification.row.HybridGroupManager
-import com.android.systemui.tuner.TunerService
import com.android.systemui.util.Assert
import com.android.systemui.util.Utils
import com.android.systemui.util.concurrency.DelayableExecutor
@@ -103,7 +100,6 @@
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
-import com.android.app.tracing.coroutines.launchTraced as launch
import kotlinx.coroutines.withContext
// URI fields to try loading album art from
@@ -152,22 +148,6 @@
expiryTimeMs = 0,
)
-const val MEDIA_TITLE_ERROR_MESSAGE = "Invalid media data: title is null or blank."
-
-/**
- * Allow recommendations from smartspace to show in media controls. Requires
- * [Utils.useQsMediaPlayer] to be enabled. On by default, but can be disabled by setting to 0
- */
-private fun allowMediaRecommendations(context: Context): Boolean {
- val flag =
- Settings.Secure.getInt(
- context.contentResolver,
- Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION,
- 1,
- )
- return Utils.useQsMediaPlayer(context) && flag > 0
-}
-
/** A class that facilitates management and loading of Media Data, ready for binding. */
@SysUISingleton
class LegacyMediaDataManagerImpl(
@@ -191,14 +171,13 @@
private var useMediaResumption: Boolean,
private val useQsMediaPlayer: Boolean,
private val systemClock: SystemClock,
- private val tunerService: TunerService,
private val mediaFlags: MediaFlags,
private val logger: MediaUiEventLogger,
private val smartspaceManager: SmartspaceManager?,
private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
private val mediaDataLoader: dagger.Lazy<MediaDataLoader>,
private val mediaLogger: MediaLogger,
-) : Dumpable, BcSmartspaceDataPlugin.SmartspaceTargetListener, MediaDataManager {
+) : Dumpable, MediaDataManager {
companion object {
// UI surface label for subscribing Smartspace updates.
@@ -238,7 +217,6 @@
// There should ONLY be at most one Smartspace media recommendation.
var smartspaceMediaData: SmartspaceMediaData = EMPTY_SMARTSPACE_MEDIA_DATA
@Keep private var smartspaceSession: SmartspaceSession? = null
- private var allowMediaRecommendations = allowMediaRecommendations(context)
private val artworkWidth =
context.resources.getDimensionPixelSize(
@@ -276,7 +254,6 @@
mediaDataFilter: LegacyMediaDataFilterImpl,
smartspaceMediaDataProvider: SmartspaceMediaDataProvider,
clock: SystemClock,
- tunerService: TunerService,
mediaFlags: MediaFlags,
logger: MediaUiEventLogger,
smartspaceManager: SmartspaceManager?,
@@ -306,7 +283,6 @@
Utils.useMediaResumption(context),
Utils.useQsMediaPlayer(context),
clock,
- tunerService,
mediaFlags,
logger,
smartspaceManager,
@@ -372,7 +348,7 @@
context.registerReceiver(appChangeReceiver, uninstallFilter)
// Register for Smartspace data updates.
- smartspaceMediaDataProvider.registerListener(this)
+ // TODO(b/382680767): remove
smartspaceSession =
smartspaceManager?.createSmartspaceSession(
SmartspaceConfig.Builder(context, SMARTSPACE_UI_SURFACE_LABEL).build()
@@ -391,24 +367,9 @@
)
}
smartspaceSession?.let { it.requestSmartspaceUpdate() }
- tunerService.addTunable(
- object : TunerService.Tunable {
- override fun onTuningChanged(key: String?, newValue: String?) {
- allowMediaRecommendations = allowMediaRecommendations(context)
- if (!allowMediaRecommendations) {
- dismissSmartspaceRecommendation(
- key = smartspaceMediaData.targetId,
- delay = 0L,
- )
- }
- }
- },
- Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION,
- )
}
override fun destroy() {
- smartspaceMediaDataProvider.unregisterListener(this)
smartspaceSession?.close()
smartspaceSession = null
context.unregisterReceiver(appChangeReceiver)
@@ -1328,61 +1289,6 @@
}
}
- override fun onSmartspaceTargetsUpdated(targets: List<Parcelable>) {
- if (!allowMediaRecommendations) {
- if (DEBUG) Log.d(TAG, "Smartspace recommendation is disabled in Settings.")
- return
- }
-
- val mediaTargets = targets.filterIsInstance<SmartspaceTarget>()
- when (mediaTargets.size) {
- 0 -> {
- if (!smartspaceMediaData.isActive) {
- return
- }
- if (DEBUG) {
- Log.d(TAG, "Set Smartspace media to be inactive for the data update")
- }
- if (mediaFlags.isPersistentSsCardEnabled()) {
- // Smartspace uses this signal to hide the card (e.g. when it expires or user
- // disconnects headphones), so treat as setting inactive when flag is on
- smartspaceMediaData = smartspaceMediaData.copy(isActive = false)
- notifySmartspaceMediaDataLoaded(
- smartspaceMediaData.targetId,
- smartspaceMediaData,
- )
- } else {
- smartspaceMediaData =
- EMPTY_SMARTSPACE_MEDIA_DATA.copy(
- targetId = smartspaceMediaData.targetId,
- instanceId = smartspaceMediaData.instanceId,
- )
- notifySmartspaceMediaDataRemoved(
- smartspaceMediaData.targetId,
- immediately = false,
- )
- }
- }
- 1 -> {
- val newMediaTarget = mediaTargets.get(0)
- if (smartspaceMediaData.targetId == newMediaTarget.smartspaceTargetId) {
- // The same Smartspace updates can be received. Skip the duplicate updates.
- return
- }
- if (DEBUG) Log.d(TAG, "Forwarding Smartspace media update.")
- smartspaceMediaData = toSmartspaceMediaData(newMediaTarget)
- notifySmartspaceMediaDataLoaded(smartspaceMediaData.targetId, smartspaceMediaData)
- }
- else -> {
- // There should NOT be more than 1 Smartspace media update. When it happens, it
- // indicates a bad state or an error. Reset the status accordingly.
- Log.wtf(TAG, "More than 1 Smartspace Media Update. Resetting the status...")
- notifySmartspaceMediaDataRemoved(smartspaceMediaData.targetId, immediately = false)
- smartspaceMediaData = EMPTY_SMARTSPACE_MEDIA_DATA
- }
- }
- }
-
override fun onNotificationRemoved(key: String) {
Assert.isMainThread()
val removed = mediaEntries.remove(key) ?: return
@@ -1641,7 +1547,6 @@
println("externalListeners: ${mediaDataFilter.listeners}")
println("mediaEntries: $mediaEntries")
println("useMediaResumption: $useMediaResumption")
- println("allowMediaRecommendations: $allowMediaRecommendations")
}
mediaDeviceManager.dump(pw)
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessor.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessor.kt
index 3821f3d..a524db4 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessor.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessor.kt
@@ -45,16 +45,15 @@
import android.media.session.MediaSession
import android.media.session.PlaybackState
import android.net.Uri
-import android.os.Parcelable
import android.os.Process
import android.os.UserHandle
-import android.provider.Settings
import android.service.notification.StatusBarNotification
import android.support.v4.media.MediaMetadataCompat
import android.text.TextUtils
import android.util.Log
import android.util.Pair as APair
import androidx.media.utils.MediaConstants
+import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.app.tracing.traceSection
import com.android.internal.annotations.Keep
import com.android.internal.logging.InstanceId
@@ -87,8 +86,6 @@
import com.android.systemui.media.controls.util.MediaFlags
import com.android.systemui.media.controls.util.MediaUiEventLogger
import com.android.systemui.media.controls.util.SmallHash
-import com.android.systemui.plugins.ActivityStarter
-import com.android.systemui.plugins.BcSmartspaceDataPlugin
import com.android.systemui.res.R
import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.statusbar.NotificationMediaManager.isPlayingState
@@ -97,8 +94,6 @@
import com.android.systemui.util.Utils
import com.android.systemui.util.concurrency.DelayableExecutor
import com.android.systemui.util.concurrency.ThreadFactory
-import com.android.systemui.util.settings.SecureSettings
-import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
import com.android.systemui.util.time.SystemClock
import java.io.IOException
import java.io.PrintWriter
@@ -106,12 +101,6 @@
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.collectLatest
-import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.flow.flowOn
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.onStart
-import com.android.app.tracing.coroutines.launchTraced as launch
import kotlinx.coroutines.withContext
// URI fields to try loading album art from
@@ -139,12 +128,10 @@
private val mediaControllerFactory: MediaControllerFactory,
private val broadcastDispatcher: BroadcastDispatcher,
private val dumpManager: DumpManager,
- private val activityStarter: ActivityStarter,
private val smartspaceMediaDataProvider: SmartspaceMediaDataProvider,
private var useMediaResumption: Boolean,
private val useQsMediaPlayer: Boolean,
private val systemClock: SystemClock,
- private val secureSettings: SecureSettings,
private val mediaFlags: MediaFlags,
private val logger: MediaUiEventLogger,
private val smartspaceManager: SmartspaceManager?,
@@ -152,7 +139,7 @@
private val mediaDataRepository: MediaDataRepository,
private val mediaDataLoader: dagger.Lazy<MediaDataLoader>,
private val mediaLogger: MediaLogger,
-) : CoreStartable, BcSmartspaceDataPlugin.SmartspaceTargetListener {
+) : CoreStartable {
companion object {
/**
@@ -191,7 +178,6 @@
// There should ONLY be at most one Smartspace media recommendation.
@Keep private var smartspaceSession: SmartspaceSession? = null
- private var allowMediaRecommendations = false
private val artworkWidth =
context.resources.getDimensionPixelSize(
@@ -221,10 +207,8 @@
mediaControllerFactory: MediaControllerFactory,
dumpManager: DumpManager,
broadcastDispatcher: BroadcastDispatcher,
- activityStarter: ActivityStarter,
smartspaceMediaDataProvider: SmartspaceMediaDataProvider,
clock: SystemClock,
- secureSettings: SecureSettings,
mediaFlags: MediaFlags,
logger: MediaUiEventLogger,
smartspaceManager: SmartspaceManager?,
@@ -245,12 +229,10 @@
mediaControllerFactory,
broadcastDispatcher,
dumpManager,
- activityStarter,
smartspaceMediaDataProvider,
Utils.useMediaResumption(context),
Utils.useQsMediaPlayer(context),
clock,
- secureSettings,
mediaFlags,
logger,
smartspaceManager,
@@ -296,7 +278,7 @@
context.registerReceiver(appChangeReceiver, uninstallFilter)
// Register for Smartspace data updates.
- smartspaceMediaDataProvider.registerListener(this)
+ // TODO(b/382680767): remove
smartspaceSession =
smartspaceManager?.createSmartspaceSession(
SmartspaceConfig.Builder(context, SMARTSPACE_UI_SURFACE_LABEL).build()
@@ -314,13 +296,9 @@
}
}
smartspaceSession?.requestSmartspaceUpdate()
-
- // Track media controls recommendation setting.
- applicationScope.launch { trackMediaControlsRecommendationSetting() }
}
fun destroy() {
- smartspaceMediaDataProvider.unregisterListener(this)
smartspaceSession?.close()
smartspaceSession = null
context.unregisterReceiver(appChangeReceiver)
@@ -357,43 +335,6 @@
}
}
- /**
- * Allow recommendations from smartspace to show in media controls. Requires
- * [Utils.useQsMediaPlayer] to be enabled. On by default, but can be disabled by setting to 0
- */
- private suspend fun allowMediaRecommendations(): Boolean {
- return withContext(backgroundDispatcher) {
- val flag =
- secureSettings.getBoolForUser(
- Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION,
- true,
- UserHandle.USER_CURRENT,
- )
-
- useQsMediaPlayer && flag
- }
- }
-
- private suspend fun trackMediaControlsRecommendationSetting() {
- secureSettings
- .observerFlow(UserHandle.USER_ALL, Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION)
- // perform a query at the beginning.
- .onStart { emit(Unit) }
- .map { allowMediaRecommendations() }
- .distinctUntilChanged()
- .flowOn(backgroundDispatcher)
- // only track the most recent emission
- .collectLatest {
- allowMediaRecommendations = it
- if (!allowMediaRecommendations) {
- dismissSmartspaceRecommendation(
- key = mediaDataRepository.smartspaceMediaData.value.targetId,
- delay = 0L,
- )
- }
- }
- }
-
private fun removeAllForPackage(packageName: String) {
Assert.isMainThread()
val toRemove =
@@ -1277,62 +1218,6 @@
}
}
- override fun onSmartspaceTargetsUpdated(targets: List<Parcelable>) {
- if (!allowMediaRecommendations) {
- if (DEBUG) Log.d(TAG, "Smartspace recommendation is disabled in Settings.")
- return
- }
-
- val mediaTargets = targets.filterIsInstance<SmartspaceTarget>()
- val smartspaceMediaData = mediaDataRepository.smartspaceMediaData.value
- when (mediaTargets.size) {
- 0 -> {
- if (!smartspaceMediaData.isActive) {
- return
- }
- if (DEBUG) {
- Log.d(TAG, "Set Smartspace media to be inactive for the data update")
- }
- if (mediaFlags.isPersistentSsCardEnabled()) {
- // Smartspace uses this signal to hide the card (e.g. when it expires or user
- // disconnects headphones), so treat as setting inactive when flag is on
- val recommendation = smartspaceMediaData.copy(isActive = false)
- mediaDataRepository.setRecommendation(recommendation)
- notifySmartspaceMediaDataLoaded(recommendation.targetId, recommendation)
- } else {
- notifySmartspaceMediaDataRemoved(
- smartspaceMediaData.targetId,
- immediately = false,
- )
- mediaDataRepository.setRecommendation(
- SmartspaceMediaData(
- targetId = smartspaceMediaData.targetId,
- instanceId = smartspaceMediaData.instanceId,
- )
- )
- }
- }
- 1 -> {
- val newMediaTarget = mediaTargets.get(0)
- if (smartspaceMediaData.targetId == newMediaTarget.smartspaceTargetId) {
- // The same Smartspace updates can be received. Skip the duplicate updates.
- return
- }
- if (DEBUG) Log.d(TAG, "Forwarding Smartspace media update.")
- val recommendation = toSmartspaceMediaData(newMediaTarget)
- mediaDataRepository.setRecommendation(recommendation)
- notifySmartspaceMediaDataLoaded(recommendation.targetId, recommendation)
- }
- else -> {
- // There should NOT be more than 1 Smartspace media update. When it happens, it
- // indicates a bad state or an error. Reset the status accordingly.
- Log.wtf(TAG, "More than 1 Smartspace Media Update. Resetting the status...")
- notifySmartspaceMediaDataRemoved(smartspaceMediaData.targetId, immediately = false)
- mediaDataRepository.setRecommendation(SmartspaceMediaData())
- }
- }
- }
-
fun onNotificationRemoved(key: String) {
Assert.isMainThread()
val removed = mediaDataRepository.removeMediaEntry(key) ?: return
@@ -1621,7 +1506,6 @@
pw.apply {
println("internalListeners: $internalListeners")
println("useMediaResumption: $useMediaResumption")
- println("allowMediaRecommendations: $allowMediaRecommendations")
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt
index 86e9294..975f8f4 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt
@@ -1042,6 +1042,18 @@
return null
}
+ if (state.expansion == 1.0f) {
+ val height =
+ if (state.expandedMatchesParentHeight) {
+ heightInSceneContainerPx
+ } else {
+ context.resources.getDimensionPixelSize(
+ R.dimen.qs_media_session_height_expanded
+ )
+ }
+ setBackgroundHeights(height)
+ }
+
// Similar to obtainViewState: Let's create a new measurement
val result =
transitionLayout?.calculateViewState(
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/privacy/PrivacyDialogV2.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogV2.kt
index f53b6cd..57d4063 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogV2.kt
+++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogV2.kt
@@ -39,7 +39,6 @@
import androidx.annotation.WorkerThread
import androidx.core.view.ViewCompat
import androidx.core.view.accessibility.AccessibilityNodeInfoCompat
-import com.android.settingslib.Utils
import com.android.systemui.animation.ViewHierarchyAnimator
import com.android.systemui.res.R
import com.android.systemui.statusbar.phone.SystemUIDialog
@@ -63,7 +62,7 @@
private val list: List<PrivacyElement>,
private val manageApp: (String, Int, Intent) -> Unit,
private val closeApp: (String, Int) -> Unit,
- private val openPrivacyDashboard: () -> Unit
+ private val openPrivacyDashboard: () -> Unit,
) : SystemUIDialog(context, R.style.Theme_PrivacyDialog) {
private val dismissListeners = mutableListOf<WeakReference<OnDialogDismissed>>()
@@ -192,11 +191,9 @@
return null
}
val closeAppButton =
- checkNotNull(window).layoutInflater.inflate(
- R.layout.privacy_dialog_card_button,
- expandedLayout,
- false
- ) as Button
+ checkNotNull(window)
+ .layoutInflater
+ .inflate(R.layout.privacy_dialog_card_button, expandedLayout, false) as Button
expandedLayout.addView(closeAppButton)
closeAppButton.id = R.id.privacy_dialog_close_app_button
closeAppButton.setText(R.string.privacy_dialog_close_app_button)
@@ -248,11 +245,9 @@
private fun configureManageButton(element: PrivacyElement, expandedLayout: ViewGroup): View {
val manageButton =
- checkNotNull(window).layoutInflater.inflate(
- R.layout.privacy_dialog_card_button,
- expandedLayout,
- false
- ) as Button
+ checkNotNull(window)
+ .layoutInflater
+ .inflate(R.layout.privacy_dialog_card_button, expandedLayout, false) as Button
expandedLayout.addView(manageButton)
manageButton.id = R.id.privacy_dialog_manage_app_button
manageButton.setText(
@@ -294,7 +289,7 @@
itemCard,
AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_CLICK,
context.getString(R.string.privacy_dialog_expand_action),
- null
+ null,
)
val expandedLayout =
@@ -311,7 +306,7 @@
it!!,
AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_CLICK,
context.getString(R.string.privacy_dialog_expand_action),
- null
+ null,
)
} else {
expandedLayout.visibility = View.VISIBLE
@@ -320,12 +315,12 @@
it!!,
AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_CLICK,
context.getString(R.string.privacy_dialog_collapse_action),
- null
+ null,
)
}
ViewHierarchyAnimator.animateNextUpdate(
rootView = window!!.decorView,
- excludedViews = setOf(expandedLayout)
+ excludedViews = setOf(expandedLayout),
)
}
}
@@ -345,19 +340,13 @@
@ColorInt
private fun getForegroundColor(active: Boolean) =
- Utils.getColorAttrDefaultColor(
- context,
- if (active) com.android.internal.R.color.materialColorOnPrimaryFixed
- else com.android.internal.R.color.materialColorOnSurface,
- )
+ if (active) context.getColor(com.android.internal.R.color.materialColorOnPrimaryFixed)
+ else context.getColor(com.android.internal.R.color.materialColorOnSurface)
@ColorInt
private fun getBackgroundColor(active: Boolean) =
- Utils.getColorAttrDefaultColor(
- context,
- if (active) com.android.internal.R.color.materialColorPrimaryFixed
- else com.android.internal.R.color.materialColorSurfaceContainerHigh,
- )
+ if (active) context.getColor(com.android.internal.R.color.materialColorPrimaryFixed)
+ else context.getColor(com.android.internal.R.color.materialColorSurfaceContainerHigh)
private fun getMutableDrawable(@DrawableRes resId: Int) = context.getDrawable(resId)!!.mutate()
@@ -379,7 +368,7 @@
context.getString(
singleUsageResId,
element.applicationName,
- element.attributionLabel ?: element.proxyLabel
+ element.attributionLabel ?: element.proxyLabel,
)
} else {
val doubleUsageResId: Int =
@@ -389,7 +378,7 @@
doubleUsageResId,
element.applicationName,
element.attributionLabel,
- element.proxyLabel
+ element.proxyLabel,
)
}
@@ -429,7 +418,7 @@
return groupInfo.loadSafeLabel(
this,
0f,
- TextUtils.SAFE_STRING_FLAG_FIRST_LINE or TextUtils.SAFE_STRING_FLAG_TRIM
+ TextUtils.SAFE_STRING_FLAG_FIRST_LINE or TextUtils.SAFE_STRING_FLAG_TRIM,
)
}
@@ -472,7 +461,7 @@
icon: Drawable,
iconSize: Int,
background: Drawable,
- backgroundSize: Int
+ backgroundSize: Int,
): Drawable {
val layered = LayerDrawable(arrayOf(background, icon))
layered.setLayerSize(0, backgroundSize, backgroundSize)
@@ -497,7 +486,7 @@
val isPhoneCall: Boolean,
val isService: Boolean,
val permGroupName: String,
- val navigationIntent: Intent
+ val navigationIntent: Intent,
) {
private val builder = StringBuilder("PrivacyElement(")
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 108d737..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
@@ -37,6 +37,7 @@
import com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer
import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipViewModel
import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
+import com.android.systemui.statusbar.phone.ongoingcall.StatusBarChipsModernization
import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallModel
import com.android.systemui.util.time.SystemClock
import javax.inject.Inject
@@ -93,9 +94,7 @@
icon = icon,
colors = colors,
onClickListenerLegacy = getOnClickListener(state),
- // TODO(b/372657935): Add click support for the call chip when
- // StatusBarChipModernization is enabled.
- clickBehavior = OngoingActivityChipModel.ClickBehavior.None,
+ clickBehavior = getClickBehavior(state),
)
} else {
val startTimeInElapsedRealtime =
@@ -106,9 +105,7 @@
colors = colors,
startTimeMs = startTimeInElapsedRealtime,
onClickListenerLegacy = getOnClickListener(state),
- // TODO(b/372657935): Add click support for the call chip when
- // StatusBarChipModernization is enabled.
- clickBehavior = OngoingActivityChipModel.ClickBehavior.None,
+ clickBehavior = getClickBehavior(state),
)
}
}
@@ -122,6 +119,7 @@
}
return View.OnClickListener { view ->
+ StatusBarChipsModernization.assertInLegacyMode()
logger.log(TAG, LogLevel.INFO, {}, { "Chip clicked" })
val backgroundView =
view.requireViewById<ChipBackgroundContainer>(R.id.ongoing_activity_chip_background)
@@ -130,12 +128,33 @@
state.intent,
ActivityTransitionAnimator.Controller.fromView(
backgroundView,
- InteractionJankMonitor.CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP,
+ Cuj.CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP,
),
)
}
}
+ private fun getClickBehavior(
+ state: OngoingCallModel.InCall
+ ): OngoingActivityChipModel.ClickBehavior =
+ if (state.intent == null) {
+ OngoingActivityChipModel.ClickBehavior.None
+ } else {
+ OngoingActivityChipModel.ClickBehavior.ExpandAction(
+ onClick = { expandable ->
+ StatusBarChipsModernization.assertInNewMode()
+ val animationController =
+ expandable.activityTransitionController(
+ Cuj.CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP
+ )
+ activityStarter.postStartActivityDismissingKeyguard(
+ state.intent,
+ animationController,
+ )
+ }
+ )
+ }
+
companion object {
private val phoneIcon =
Icon.Resource(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationUiAod.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationUiAod.kt
new file mode 100644
index 0000000..fa1f32c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationUiAod.kt
@@ -0,0 +1,60 @@
+/*
+ * 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.statusbar.notification.promoted
+
+import com.android.systemui.Flags
+import com.android.systemui.flags.FlagToken
+import com.android.systemui.flags.RefactorFlagUtils
+
+/** Helper for reading or using the promoted ongoing notifications AOD flag state. */
+object PromotedNotificationUiAod {
+ /** The aconfig flag name */
+ const val FLAG_NAME = Flags.FLAG_AOD_UI_RICH_ONGOING
+
+ /** A token used for dependency declaration */
+ val token: FlagToken
+ get() = FlagToken(FLAG_NAME, isEnabled)
+
+ /** Is the refactor enabled */
+ @JvmStatic
+ inline val isEnabled
+ get() = Flags.aodUiRichOngoing()
+
+ /**
+ * Called to ensure code is only run when the flag is enabled. This protects users from the
+ * unintended behaviors caused by accidentally running new logic, while also crashing on an eng
+ * build to ensure that the refactor author catches issues in testing.
+ */
+ @JvmStatic
+ inline fun isUnexpectedlyInLegacyMode() =
+ RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, FLAG_NAME)
+
+ /**
+ * Called to ensure code is only run when the flag is disabled. This will throw an exception if
+ * the flag is not enabled to ensure that the refactor author catches issues in testing.
+ * Caution!! Using this check incorrectly will cause crashes in nextfood builds!
+ */
+ @JvmStatic
+ inline fun assertInNewMode() = RefactorFlagUtils.assertInNewMode(isEnabled, FLAG_NAME)
+
+ /**
+ * Called to ensure code is only run when the flag is disabled. This will throw an exception if
+ * the flag is enabled to ensure that the refactor author catches issues in testing.
+ */
+ @JvmStatic
+ inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationUiForceExpanded.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationUiForceExpanded.kt
new file mode 100644
index 0000000..cb0d674
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationUiForceExpanded.kt
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.promoted
+
+import com.android.systemui.Flags
+import com.android.systemui.flags.FlagToken
+import com.android.systemui.flags.RefactorFlagUtils
+
+/** Helper for reading or using the expanded ui rich ongoing flag state. */
+@Suppress("NOTHING_TO_INLINE")
+object PromotedNotificationUiForceExpanded {
+ /** The aconfig flag name */
+ const val FLAG_NAME = Flags.FLAG_UI_RICH_ONGOING_FORCE_EXPANDED
+
+ /** A token used for dependency declaration */
+ val token: FlagToken
+ get() = FlagToken(FLAG_NAME, isEnabled)
+
+ /** Is the refactor enabled */
+ @JvmStatic
+ inline val isEnabled
+ get() = Flags.uiRichOngoingForceExpanded()
+
+ /**
+ * Called to ensure code is only run when the flag is enabled. This protects users from the
+ * unintended behaviors caused by accidentally running new logic, while also crashing on an eng
+ * build to ensure that the refactor author catches issues in testing.
+ */
+ @JvmStatic
+ inline fun isUnexpectedlyInLegacyMode() =
+ RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, FLAG_NAME)
+
+ /**
+ * Called to ensure code is only run when the flag is enabled. This will throw an exception if
+ * the flag is not enabled to ensure that the refactor author catches issues in testing.
+ * Caution!! Using this check incorrectly will cause crashes in nextfood builds!
+ */
+ @JvmStatic
+ inline fun assertInNewMode() = RefactorFlagUtils.assertInNewMode(isEnabled, FLAG_NAME)
+
+ /**
+ * Called to ensure code is only run when the flag is disabled. This will throw an exception if
+ * the flag is enabled to ensure that the refactor author catches issues in testing.
+ */
+ @JvmStatic
+ inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME)
+}
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/src/com/android/systemui/tuner/TunerServiceImpl.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java
index 05ee35b..8f5fccd 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java
@@ -75,8 +75,7 @@
private static final String[] RESET_EXCEPTION_LIST = new String[] {
QSHost.TILES_SETTING,
Settings.Secure.DOZE_ALWAYS_ON,
- Settings.Secure.MEDIA_CONTROLS_RESUME,
- Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION
+ Settings.Secure.MEDIA_CONTROLS_RESUME
};
private final Observer mObserver = new Observer();
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/keyguard/CustomizationProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt
index 82bf5e2..a3c3d2c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt
@@ -205,6 +205,7 @@
biometricSettingsRepository = biometricSettingsRepository,
backgroundDispatcher = testDispatcher,
appContext = mContext,
+ accessibilityManager = mock(),
communalSettingsInteractor = kosmos.communalSettingsInteractor,
sceneInteractor = { kosmos.sceneInteractor },
)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
index 111d819..21519b0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
@@ -322,6 +322,7 @@
biometricSettingsRepository = biometricSettingsRepository,
backgroundDispatcher = testDispatcher,
appContext = mContext,
+ accessibilityManager = mock(),
communalSettingsInteractor = kosmos.communalSettingsInteractor,
sceneInteractor = { kosmos.sceneInteractor },
)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorSceneContainerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorSceneContainerTest.kt
index 8c00047..caf08ef 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorSceneContainerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorSceneContainerTest.kt
@@ -325,6 +325,7 @@
biometricSettingsRepository = biometricSettingsRepository,
backgroundDispatcher = testDispatcher,
appContext = mContext,
+ accessibilityManager = mock(),
communalSettingsInteractor = kosmos.communalSettingsInteractor,
sceneInteractor = { kosmos.sceneInteractor },
)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt
index 0b2b867..b5a2271 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt
@@ -294,6 +294,7 @@
biometricSettingsRepository = biometricSettingsRepository,
backgroundDispatcher = kosmos.testDispatcher,
appContext = mContext,
+ accessibilityManager = mock(),
communalSettingsInteractor = kosmos.communalSettingsInteractor,
sceneInteractor = { kosmos.sceneInteractor },
),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImplTest.kt
index 3ddd4b5..2815b9769 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImplTest.kt
@@ -41,13 +41,11 @@
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import android.platform.test.flag.junit.FlagsParameterization
-import android.provider.Settings
import android.service.notification.StatusBarNotification
import android.testing.TestableLooper.RunWithLooper
import androidx.media.utils.MediaConstants
import androidx.test.filters.SmallTest
import com.android.dx.mockito.inline.extended.ExtendedMockito
-import com.android.internal.logging.InstanceId
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.Flags
import com.android.systemui.InstanceIdSequenceFake
@@ -65,10 +63,7 @@
import com.android.systemui.media.controls.domain.resume.ResumeMediaBrowser
import com.android.systemui.media.controls.shared.mediaLogger
import com.android.systemui.media.controls.shared.mockMediaLogger
-import com.android.systemui.media.controls.shared.model.EXTRA_KEY_TRIGGER_SOURCE
-import com.android.systemui.media.controls.shared.model.EXTRA_VALUE_TRIGGER_PERIODIC
import com.android.systemui.media.controls.shared.model.MediaData
-import com.android.systemui.media.controls.shared.model.SmartspaceMediaData
import com.android.systemui.media.controls.shared.model.SmartspaceMediaDataProvider
import com.android.systemui.media.controls.util.MediaUiEventLogger
import com.android.systemui.media.controls.util.fakeMediaControllerFactory
@@ -76,7 +71,6 @@
import com.android.systemui.res.R
import com.android.systemui.statusbar.SbnBuilder
import com.android.systemui.testKosmos
-import com.android.systemui.tuner.TunerService
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
@@ -95,12 +89,10 @@
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Captor
import org.mockito.Mock
-import org.mockito.Mockito
import org.mockito.Mockito.mock
import org.mockito.Mockito.never
import org.mockito.Mockito.reset
import org.mockito.Mockito.verify
-import org.mockito.Mockito.verifyNoMoreInteractions
import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoSession
import org.mockito.junit.MockitoJUnit
@@ -126,10 +118,6 @@
private const val USER_ID = 0
private val DISMISS_INTENT = Intent().apply { action = "dismiss" }
-private fun <T> anyObject(): T {
- return Mockito.anyObject<T>()
-}
-
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWithLooper(setAsMainLooper = true)
@@ -168,8 +156,6 @@
lateinit var remoteCastNotification: StatusBarNotification
@Captor lateinit var mediaDataCaptor: ArgumentCaptor<MediaData>
private val clock = FakeSystemClock()
- @Mock private lateinit var tunerService: TunerService
- @Captor lateinit var tunableCaptor: ArgumentCaptor<TunerService.Tunable>
@Captor lateinit var stateCallbackCaptor: ArgumentCaptor<(String, PlaybackState) -> Unit>
@Captor lateinit var sessionCallbackCaptor: ArgumentCaptor<(String) -> Unit>
@Captor lateinit var smartSpaceConfigBuilderCaptor: ArgumentCaptor<SmartspaceConfig>
@@ -197,13 +183,6 @@
private val mediaControllerFactory = kosmos.fakeMediaControllerFactory
private val instanceIdSequence = InstanceIdSequenceFake(1 shl 20)
- private val originalSmartspaceSetting =
- Settings.Secure.getInt(
- context.contentResolver,
- Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION,
- 1,
- )
-
private lateinit var staticMockSession: MockitoSession
@Before
@@ -219,11 +198,6 @@
backgroundExecutor = FakeExecutor(clock)
uiExecutor = FakeExecutor(clock)
smartspaceMediaDataProvider = SmartspaceMediaDataProvider()
- Settings.Secure.putInt(
- context.contentResolver,
- Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION,
- 1,
- )
mediaDataManager =
LegacyMediaDataManagerImpl(
context = context,
@@ -246,7 +220,6 @@
useMediaResumption = true,
useQsMediaPlayer = true,
systemClock = clock,
- tunerService = tunerService,
mediaFlags = kosmos.mediaFlags,
logger = logger,
smartspaceManager = smartspaceManager,
@@ -254,8 +227,6 @@
mediaDataLoader = { kosmos.mediaDataLoader },
mediaLogger = kosmos.mediaLogger,
)
- verify(tunerService)
- .addTunable(capture(tunableCaptor), eq(Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION))
verify(mediaTimeoutListener).stateCallback = capture(stateCallbackCaptor)
verify(mediaTimeoutListener).sessionCallback = capture(sessionCallbackCaptor)
session = MediaSession(context, "MediaDataManagerTestSession")
@@ -332,11 +303,6 @@
staticMockSession.finishMocking()
session.release()
mediaDataManager.destroy()
- Settings.Secure.putInt(
- context.contentResolver,
- Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION,
- originalSmartspaceSetting,
- )
}
@Test
@@ -1236,272 +1202,6 @@
}
@Test
- fun testOnSmartspaceMediaDataLoaded_hasNewValidMediaTarget_callsListener() {
- smartspaceMediaDataProvider.onTargetsAvailable(listOf(mediaSmartspaceTarget))
- verify(logger).getNewInstanceId()
- val instanceId = instanceIdSequence.lastInstanceId
-
- verify(listener)
- .onSmartspaceMediaDataLoaded(
- eq(KEY_MEDIA_SMARTSPACE),
- eq(
- SmartspaceMediaData(
- targetId = KEY_MEDIA_SMARTSPACE,
- isActive = true,
- packageName = PACKAGE_NAME,
- cardAction = mediaSmartspaceBaseAction,
- recommendations = validRecommendationList,
- dismissIntent = DISMISS_INTENT,
- headphoneConnectionTimeMillis = SMARTSPACE_CREATION_TIME,
- instanceId = InstanceId.fakeInstanceId(instanceId),
- expiryTimeMs = SMARTSPACE_EXPIRY_TIME,
- )
- ),
- eq(false),
- )
- }
-
- @Test
- fun testOnSmartspaceMediaDataLoaded_hasNewInvalidMediaTarget_callsListener() {
- whenever(mediaSmartspaceTarget.iconGrid).thenReturn(listOf())
- smartspaceMediaDataProvider.onTargetsAvailable(listOf(mediaSmartspaceTarget))
- verify(logger).getNewInstanceId()
- val instanceId = instanceIdSequence.lastInstanceId
-
- verify(listener)
- .onSmartspaceMediaDataLoaded(
- eq(KEY_MEDIA_SMARTSPACE),
- eq(
- EMPTY_SMARTSPACE_MEDIA_DATA.copy(
- targetId = KEY_MEDIA_SMARTSPACE,
- isActive = true,
- dismissIntent = DISMISS_INTENT,
- headphoneConnectionTimeMillis = SMARTSPACE_CREATION_TIME,
- instanceId = InstanceId.fakeInstanceId(instanceId),
- expiryTimeMs = SMARTSPACE_EXPIRY_TIME,
- )
- ),
- eq(false),
- )
- }
-
- @Test
- fun testOnSmartspaceMediaDataLoaded_hasNullIntent_callsListener() {
- val recommendationExtras =
- Bundle().apply {
- putString("package_name", PACKAGE_NAME)
- putParcelable("dismiss_intent", null)
- }
- whenever(mediaSmartspaceBaseAction.extras).thenReturn(recommendationExtras)
- whenever(mediaSmartspaceTarget.baseAction).thenReturn(mediaSmartspaceBaseAction)
- whenever(mediaSmartspaceTarget.iconGrid).thenReturn(listOf())
-
- smartspaceMediaDataProvider.onTargetsAvailable(listOf(mediaSmartspaceTarget))
- verify(logger).getNewInstanceId()
- val instanceId = instanceIdSequence.lastInstanceId
-
- verify(listener)
- .onSmartspaceMediaDataLoaded(
- eq(KEY_MEDIA_SMARTSPACE),
- eq(
- EMPTY_SMARTSPACE_MEDIA_DATA.copy(
- targetId = KEY_MEDIA_SMARTSPACE,
- isActive = true,
- dismissIntent = null,
- headphoneConnectionTimeMillis = SMARTSPACE_CREATION_TIME,
- instanceId = InstanceId.fakeInstanceId(instanceId),
- expiryTimeMs = SMARTSPACE_EXPIRY_TIME,
- )
- ),
- eq(false),
- )
- }
-
- @Test
- fun testOnSmartspaceMediaDataLoaded_hasNoneMediaTarget_notCallsListener() {
- smartspaceMediaDataProvider.onTargetsAvailable(listOf())
- verify(logger, never()).getNewInstanceId()
- verify(listener, never())
- .onSmartspaceMediaDataLoaded(anyObject(), anyObject(), anyBoolean())
- }
-
- @Test
- fun testOnSmartspaceMediaDataLoaded_hasNoneMediaTarget_callsRemoveListener() {
- smartspaceMediaDataProvider.onTargetsAvailable(listOf(mediaSmartspaceTarget))
- verify(logger).getNewInstanceId()
-
- smartspaceMediaDataProvider.onTargetsAvailable(listOf())
- uiExecutor.advanceClockToLast()
- uiExecutor.runAllReady()
-
- verify(listener).onSmartspaceMediaDataRemoved(eq(KEY_MEDIA_SMARTSPACE), eq(false))
- verifyNoMoreInteractions(logger)
- }
-
- @Test
- fun testOnSmartspaceMediaDataLoaded_persistentEnabled_headphoneTrigger_isActive() {
- fakeFeatureFlags.set(MEDIA_RETAIN_RECOMMENDATIONS, true)
- smartspaceMediaDataProvider.onTargetsAvailable(listOf(mediaSmartspaceTarget))
- val instanceId = instanceIdSequence.lastInstanceId
-
- verify(listener)
- .onSmartspaceMediaDataLoaded(
- eq(KEY_MEDIA_SMARTSPACE),
- eq(
- SmartspaceMediaData(
- targetId = KEY_MEDIA_SMARTSPACE,
- isActive = true,
- packageName = PACKAGE_NAME,
- cardAction = mediaSmartspaceBaseAction,
- recommendations = validRecommendationList,
- dismissIntent = DISMISS_INTENT,
- headphoneConnectionTimeMillis = SMARTSPACE_CREATION_TIME,
- instanceId = InstanceId.fakeInstanceId(instanceId),
- expiryTimeMs = SMARTSPACE_EXPIRY_TIME,
- )
- ),
- eq(false),
- )
- }
-
- @Test
- fun testOnSmartspaceMediaDataLoaded_persistentEnabled_periodicTrigger_notActive() {
- fakeFeatureFlags.set(MEDIA_RETAIN_RECOMMENDATIONS, true)
- val extras =
- Bundle().apply {
- putString("package_name", PACKAGE_NAME)
- putParcelable("dismiss_intent", DISMISS_INTENT)
- putString(EXTRA_KEY_TRIGGER_SOURCE, EXTRA_VALUE_TRIGGER_PERIODIC)
- }
- whenever(mediaSmartspaceBaseAction.extras).thenReturn(extras)
-
- smartspaceMediaDataProvider.onTargetsAvailable(listOf(mediaSmartspaceTarget))
- val instanceId = instanceIdSequence.lastInstanceId
-
- verify(listener)
- .onSmartspaceMediaDataLoaded(
- eq(KEY_MEDIA_SMARTSPACE),
- eq(
- SmartspaceMediaData(
- targetId = KEY_MEDIA_SMARTSPACE,
- isActive = false,
- packageName = PACKAGE_NAME,
- cardAction = mediaSmartspaceBaseAction,
- recommendations = validRecommendationList,
- dismissIntent = DISMISS_INTENT,
- headphoneConnectionTimeMillis = SMARTSPACE_CREATION_TIME,
- instanceId = InstanceId.fakeInstanceId(instanceId),
- expiryTimeMs = SMARTSPACE_EXPIRY_TIME,
- )
- ),
- eq(false),
- )
- }
-
- @Test
- fun testOnSmartspaceMediaDataLoaded_persistentEnabled_noTargets_inactive() {
- fakeFeatureFlags.set(MEDIA_RETAIN_RECOMMENDATIONS, true)
-
- smartspaceMediaDataProvider.onTargetsAvailable(listOf(mediaSmartspaceTarget))
- val instanceId = instanceIdSequence.lastInstanceId
-
- smartspaceMediaDataProvider.onTargetsAvailable(listOf())
- uiExecutor.advanceClockToLast()
- uiExecutor.runAllReady()
-
- verify(listener)
- .onSmartspaceMediaDataLoaded(
- eq(KEY_MEDIA_SMARTSPACE),
- eq(
- SmartspaceMediaData(
- targetId = KEY_MEDIA_SMARTSPACE,
- isActive = false,
- packageName = PACKAGE_NAME,
- cardAction = mediaSmartspaceBaseAction,
- recommendations = validRecommendationList,
- dismissIntent = DISMISS_INTENT,
- headphoneConnectionTimeMillis = SMARTSPACE_CREATION_TIME,
- instanceId = InstanceId.fakeInstanceId(instanceId),
- expiryTimeMs = SMARTSPACE_EXPIRY_TIME,
- )
- ),
- eq(false),
- )
- verify(listener, never()).onSmartspaceMediaDataRemoved(eq(KEY_MEDIA_SMARTSPACE), eq(false))
- }
-
- @Test
- fun testSetRecommendationInactive_notifiesListeners() {
- fakeFeatureFlags.set(MEDIA_RETAIN_RECOMMENDATIONS, true)
-
- smartspaceMediaDataProvider.onTargetsAvailable(listOf(mediaSmartspaceTarget))
- val instanceId = instanceIdSequence.lastInstanceId
-
- mediaDataManager.setRecommendationInactive(KEY_MEDIA_SMARTSPACE)
- uiExecutor.advanceClockToLast()
- uiExecutor.runAllReady()
-
- verify(listener)
- .onSmartspaceMediaDataLoaded(
- eq(KEY_MEDIA_SMARTSPACE),
- eq(
- SmartspaceMediaData(
- targetId = KEY_MEDIA_SMARTSPACE,
- isActive = false,
- packageName = PACKAGE_NAME,
- cardAction = mediaSmartspaceBaseAction,
- recommendations = validRecommendationList,
- dismissIntent = DISMISS_INTENT,
- headphoneConnectionTimeMillis = SMARTSPACE_CREATION_TIME,
- instanceId = InstanceId.fakeInstanceId(instanceId),
- expiryTimeMs = SMARTSPACE_EXPIRY_TIME,
- )
- ),
- eq(false),
- )
- }
-
- @Test
- fun testOnSmartspaceMediaDataLoaded_settingDisabled_doesNothing() {
- // WHEN media recommendation setting is off
- Settings.Secure.putInt(
- context.contentResolver,
- Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION,
- 0,
- )
- tunableCaptor.value.onTuningChanged(Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION, "0")
-
- smartspaceMediaDataProvider.onTargetsAvailable(listOf(mediaSmartspaceTarget))
-
- // THEN smartspace signal is ignored
- verify(listener, never())
- .onSmartspaceMediaDataLoaded(anyObject(), anyObject(), anyBoolean())
- }
-
- @Test
- fun testMediaRecommendationDisabled_removesSmartspaceData() {
- // GIVEN a media recommendation card is present
- smartspaceMediaDataProvider.onTargetsAvailable(listOf(mediaSmartspaceTarget))
- verify(listener)
- .onSmartspaceMediaDataLoaded(eq(KEY_MEDIA_SMARTSPACE), anyObject(), anyBoolean())
-
- // WHEN the media recommendation setting is turned off
- Settings.Secure.putInt(
- context.contentResolver,
- Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION,
- 0,
- )
- tunableCaptor.value.onTuningChanged(Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION, "0")
-
- // THEN listeners are notified
- uiExecutor.advanceClockToLast()
- foregroundExecutor.advanceClockToLast()
- uiExecutor.runAllReady()
- foregroundExecutor.runAllReady()
- verify(listener).onSmartspaceMediaDataRemoved(eq(KEY_MEDIA_SMARTSPACE), eq(true))
- }
-
- @Test
fun testOnMediaDataChanged_updatesLastActiveTime() {
val currentTime = clock.elapsedRealtime()
addNotificationAndLoad()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorTest.kt
index e5483c0..b9ebce8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorTest.kt
@@ -42,13 +42,11 @@
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import android.platform.test.flag.junit.FlagsParameterization
-import android.provider.Settings
import android.service.notification.StatusBarNotification
import android.testing.TestableLooper.RunWithLooper
import androidx.media.utils.MediaConstants
import androidx.test.filters.SmallTest
import com.android.dx.mockito.inline.extended.ExtendedMockito
-import com.android.internal.logging.InstanceId
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.Flags
import com.android.systemui.InstanceIdSequenceFake
@@ -71,21 +69,16 @@
import com.android.systemui.media.controls.domain.resume.ResumeMediaBrowser
import com.android.systemui.media.controls.shared.mediaLogger
import com.android.systemui.media.controls.shared.mockMediaLogger
-import com.android.systemui.media.controls.shared.model.EXTRA_KEY_TRIGGER_SOURCE
-import com.android.systemui.media.controls.shared.model.EXTRA_VALUE_TRIGGER_PERIODIC
import com.android.systemui.media.controls.shared.model.MediaData
-import com.android.systemui.media.controls.shared.model.SmartspaceMediaData
import com.android.systemui.media.controls.shared.model.SmartspaceMediaDataProvider
import com.android.systemui.media.controls.util.MediaUiEventLogger
import com.android.systemui.media.controls.util.fakeMediaControllerFactory
import com.android.systemui.media.controls.util.mediaFlags
-import com.android.systemui.plugins.activityStarter
import com.android.systemui.res.R
import com.android.systemui.statusbar.SbnBuilder
import com.android.systemui.statusbar.notificationLockscreenUserManager
import com.android.systemui.testKosmos
import com.android.systemui.util.concurrency.FakeExecutor
-import com.android.systemui.util.settings.fakeSettings
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -102,11 +95,9 @@
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Captor
import org.mockito.Mock
-import org.mockito.Mockito
import org.mockito.Mockito.never
import org.mockito.Mockito.reset
import org.mockito.Mockito.verify
-import org.mockito.Mockito.verifyNoMoreInteractions
import org.mockito.MockitoSession
import org.mockito.junit.MockitoJUnit
import org.mockito.kotlin.any
@@ -133,10 +124,6 @@
private const val USER_ID = 0
private val DISMISS_INTENT = Intent().apply { action = "dismiss" }
-private fun <T> anyObject(): T {
- return Mockito.anyObject<T>()
-}
-
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWithLooper(setAsMainLooper = true)
@@ -146,7 +133,6 @@
private val kosmos = testKosmos().apply { mediaLogger = mockMediaLogger }
private val testDispatcher = kosmos.testDispatcher
private val testScope = kosmos.testScope
- private val settings = kosmos.fakeSettings
@JvmField @Rule val mockito = MockitoJUnit.rule()
@Mock lateinit var controller: MediaController
@@ -201,7 +187,6 @@
}
private val fakeFeatureFlags = kosmos.fakeFeatureFlagsClassic
- private val activityStarter = kosmos.activityStarter
private val mediaControllerFactory = kosmos.fakeMediaControllerFactory
private val notificationLockscreenUserManager = kosmos.notificationLockscreenUserManager
private val mediaFilterRepository = kosmos.mediaFilterRepository
@@ -209,13 +194,6 @@
private val instanceIdSequence = InstanceIdSequenceFake(1 shl 20)
- private val originalSmartspaceSetting =
- Settings.Secure.getInt(
- context.contentResolver,
- Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION,
- 1,
- )
-
private lateinit var staticMockSession: MockitoSession
@Before
@@ -231,11 +209,6 @@
backgroundExecutor = FakeExecutor(clock)
uiExecutor = FakeExecutor(clock)
smartspaceMediaDataProvider = SmartspaceMediaDataProvider()
- Settings.Secure.putInt(
- context.contentResolver,
- Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION,
- 1,
- )
mediaDataProcessor =
MediaDataProcessor(
context = context,
@@ -248,12 +221,10 @@
mediaControllerFactory = mediaControllerFactory,
broadcastDispatcher = broadcastDispatcher,
dumpManager = dumpManager,
- activityStarter = activityStarter,
smartspaceMediaDataProvider = smartspaceMediaDataProvider,
useMediaResumption = true,
useQsMediaPlayer = true,
systemClock = clock,
- secureSettings = settings,
mediaFlags = kosmos.mediaFlags,
logger = logger,
smartspaceManager = smartspaceManager,
@@ -355,11 +326,6 @@
staticMockSession.finishMocking()
session.release()
mediaDataProcessor.destroy()
- Settings.Secure.putInt(
- context.contentResolver,
- Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION,
- originalSmartspaceSetting,
- )
}
@Test
@@ -1255,264 +1221,6 @@
}
@Test
- fun testOnSmartspaceMediaDataLoaded_hasNewValidMediaTarget_callsListener() {
- smartspaceMediaDataProvider.onTargetsAvailable(listOf(mediaSmartspaceTarget))
- verify(logger).getNewInstanceId()
- val instanceId = instanceIdSequence.lastInstanceId
-
- verify(listener)
- .onSmartspaceMediaDataLoaded(
- eq(KEY_MEDIA_SMARTSPACE),
- eq(
- SmartspaceMediaData(
- targetId = KEY_MEDIA_SMARTSPACE,
- isActive = true,
- packageName = PACKAGE_NAME,
- cardAction = mediaSmartspaceBaseAction,
- recommendations = validRecommendationList,
- dismissIntent = DISMISS_INTENT,
- headphoneConnectionTimeMillis = SMARTSPACE_CREATION_TIME,
- instanceId = InstanceId.fakeInstanceId(instanceId),
- expiryTimeMs = SMARTSPACE_EXPIRY_TIME,
- )
- ),
- eq(false),
- )
- }
-
- @Test
- fun testOnSmartspaceMediaDataLoaded_hasNewInvalidMediaTarget_callsListener() {
- whenever(mediaSmartspaceTarget.iconGrid).thenReturn(listOf())
- smartspaceMediaDataProvider.onTargetsAvailable(listOf(mediaSmartspaceTarget))
- verify(logger).getNewInstanceId()
- val instanceId = instanceIdSequence.lastInstanceId
-
- verify(listener)
- .onSmartspaceMediaDataLoaded(
- eq(KEY_MEDIA_SMARTSPACE),
- eq(
- SmartspaceMediaData(
- targetId = KEY_MEDIA_SMARTSPACE,
- isActive = true,
- dismissIntent = DISMISS_INTENT,
- headphoneConnectionTimeMillis = SMARTSPACE_CREATION_TIME,
- instanceId = InstanceId.fakeInstanceId(instanceId),
- expiryTimeMs = SMARTSPACE_EXPIRY_TIME,
- )
- ),
- eq(false),
- )
- }
-
- @Test
- fun testOnSmartspaceMediaDataLoaded_hasNullIntent_callsListener() {
- val recommendationExtras =
- Bundle().apply {
- putString("package_name", PACKAGE_NAME)
- putParcelable("dismiss_intent", null)
- }
- whenever(mediaSmartspaceBaseAction.extras).thenReturn(recommendationExtras)
- whenever(mediaSmartspaceTarget.baseAction).thenReturn(mediaSmartspaceBaseAction)
- whenever(mediaSmartspaceTarget.iconGrid).thenReturn(listOf())
-
- smartspaceMediaDataProvider.onTargetsAvailable(listOf(mediaSmartspaceTarget))
- verify(logger).getNewInstanceId()
- val instanceId = instanceIdSequence.lastInstanceId
-
- verify(listener)
- .onSmartspaceMediaDataLoaded(
- eq(KEY_MEDIA_SMARTSPACE),
- eq(
- SmartspaceMediaData(
- targetId = KEY_MEDIA_SMARTSPACE,
- isActive = true,
- dismissIntent = null,
- headphoneConnectionTimeMillis = SMARTSPACE_CREATION_TIME,
- instanceId = InstanceId.fakeInstanceId(instanceId),
- expiryTimeMs = SMARTSPACE_EXPIRY_TIME,
- )
- ),
- eq(false),
- )
- }
-
- @Test
- fun testOnSmartspaceMediaDataLoaded_hasNoneMediaTarget_notCallsListener() {
- smartspaceMediaDataProvider.onTargetsAvailable(listOf())
- verify(logger, never()).getNewInstanceId()
- verify(listener, never())
- .onSmartspaceMediaDataLoaded(anyObject(), anyObject(), anyBoolean())
- }
-
- @Test
- fun testOnSmartspaceMediaDataLoaded_hasNoneMediaTarget_callsRemoveListener() {
- smartspaceMediaDataProvider.onTargetsAvailable(listOf(mediaSmartspaceTarget))
- verify(logger).getNewInstanceId()
-
- smartspaceMediaDataProvider.onTargetsAvailable(listOf())
- uiExecutor.advanceClockToLast()
- uiExecutor.runAllReady()
-
- verify(listener).onSmartspaceMediaDataRemoved(eq(KEY_MEDIA_SMARTSPACE), eq(false))
- verifyNoMoreInteractions(logger)
- }
-
- @Test
- fun testOnSmartspaceMediaDataLoaded_persistentEnabled_headphoneTrigger_isActive() {
- fakeFeatureFlags.set(MEDIA_RETAIN_RECOMMENDATIONS, true)
- smartspaceMediaDataProvider.onTargetsAvailable(listOf(mediaSmartspaceTarget))
- val instanceId = instanceIdSequence.lastInstanceId
-
- verify(listener)
- .onSmartspaceMediaDataLoaded(
- eq(KEY_MEDIA_SMARTSPACE),
- eq(
- SmartspaceMediaData(
- targetId = KEY_MEDIA_SMARTSPACE,
- isActive = true,
- packageName = PACKAGE_NAME,
- cardAction = mediaSmartspaceBaseAction,
- recommendations = validRecommendationList,
- dismissIntent = DISMISS_INTENT,
- headphoneConnectionTimeMillis = SMARTSPACE_CREATION_TIME,
- instanceId = InstanceId.fakeInstanceId(instanceId),
- expiryTimeMs = SMARTSPACE_EXPIRY_TIME,
- )
- ),
- eq(false),
- )
- }
-
- @Test
- fun testOnSmartspaceMediaDataLoaded_persistentEnabled_periodicTrigger_notActive() {
- fakeFeatureFlags.set(MEDIA_RETAIN_RECOMMENDATIONS, true)
- val extras =
- Bundle().apply {
- putString("package_name", PACKAGE_NAME)
- putParcelable("dismiss_intent", DISMISS_INTENT)
- putString(EXTRA_KEY_TRIGGER_SOURCE, EXTRA_VALUE_TRIGGER_PERIODIC)
- }
- whenever(mediaSmartspaceBaseAction.extras).thenReturn(extras)
-
- smartspaceMediaDataProvider.onTargetsAvailable(listOf(mediaSmartspaceTarget))
- val instanceId = instanceIdSequence.lastInstanceId
-
- verify(listener)
- .onSmartspaceMediaDataLoaded(
- eq(KEY_MEDIA_SMARTSPACE),
- eq(
- SmartspaceMediaData(
- targetId = KEY_MEDIA_SMARTSPACE,
- isActive = false,
- packageName = PACKAGE_NAME,
- cardAction = mediaSmartspaceBaseAction,
- recommendations = validRecommendationList,
- dismissIntent = DISMISS_INTENT,
- headphoneConnectionTimeMillis = SMARTSPACE_CREATION_TIME,
- instanceId = InstanceId.fakeInstanceId(instanceId),
- expiryTimeMs = SMARTSPACE_EXPIRY_TIME,
- )
- ),
- eq(false),
- )
- }
-
- @Test
- fun testOnSmartspaceMediaDataLoaded_persistentEnabled_noTargets_inactive() {
- fakeFeatureFlags.set(MEDIA_RETAIN_RECOMMENDATIONS, true)
-
- smartspaceMediaDataProvider.onTargetsAvailable(listOf(mediaSmartspaceTarget))
- val instanceId = instanceIdSequence.lastInstanceId
-
- smartspaceMediaDataProvider.onTargetsAvailable(listOf())
- uiExecutor.advanceClockToLast()
- uiExecutor.runAllReady()
-
- verify(listener)
- .onSmartspaceMediaDataLoaded(
- eq(KEY_MEDIA_SMARTSPACE),
- eq(
- SmartspaceMediaData(
- targetId = KEY_MEDIA_SMARTSPACE,
- isActive = false,
- packageName = PACKAGE_NAME,
- cardAction = mediaSmartspaceBaseAction,
- recommendations = validRecommendationList,
- dismissIntent = DISMISS_INTENT,
- headphoneConnectionTimeMillis = SMARTSPACE_CREATION_TIME,
- instanceId = InstanceId.fakeInstanceId(instanceId),
- expiryTimeMs = SMARTSPACE_EXPIRY_TIME,
- )
- ),
- eq(false),
- )
- verify(listener, never()).onSmartspaceMediaDataRemoved(eq(KEY_MEDIA_SMARTSPACE), eq(false))
- }
-
- @Test
- fun testSetRecommendationInactive_notifiesListeners() {
- fakeFeatureFlags.set(MEDIA_RETAIN_RECOMMENDATIONS, true)
-
- smartspaceMediaDataProvider.onTargetsAvailable(listOf(mediaSmartspaceTarget))
- val instanceId = instanceIdSequence.lastInstanceId
-
- mediaDataProcessor.setRecommendationInactive(KEY_MEDIA_SMARTSPACE)
- uiExecutor.advanceClockToLast()
- uiExecutor.runAllReady()
-
- verify(listener)
- .onSmartspaceMediaDataLoaded(
- eq(KEY_MEDIA_SMARTSPACE),
- eq(
- SmartspaceMediaData(
- targetId = KEY_MEDIA_SMARTSPACE,
- isActive = false,
- packageName = PACKAGE_NAME,
- cardAction = mediaSmartspaceBaseAction,
- recommendations = validRecommendationList,
- dismissIntent = DISMISS_INTENT,
- headphoneConnectionTimeMillis = SMARTSPACE_CREATION_TIME,
- instanceId = InstanceId.fakeInstanceId(instanceId),
- expiryTimeMs = SMARTSPACE_EXPIRY_TIME,
- )
- ),
- eq(false),
- )
- }
-
- @Test
- fun testOnSmartspaceMediaDataLoaded_settingDisabled_doesNothing() {
- // WHEN media recommendation setting is off
- settings.putInt(Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION, 0)
- testScope.runCurrent()
-
- smartspaceMediaDataProvider.onTargetsAvailable(listOf(mediaSmartspaceTarget))
-
- // THEN smartspace signal is ignored
- verify(listener, never())
- .onSmartspaceMediaDataLoaded(anyObject(), anyObject(), anyBoolean())
- }
-
- @Test
- fun testMediaRecommendationDisabled_removesSmartspaceData() {
- // GIVEN a media recommendation card is present
- smartspaceMediaDataProvider.onTargetsAvailable(listOf(mediaSmartspaceTarget))
- verify(listener)
- .onSmartspaceMediaDataLoaded(eq(KEY_MEDIA_SMARTSPACE), anyObject(), anyBoolean())
-
- // WHEN the media recommendation setting is turned off
- settings.putInt(Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION, 0)
- testScope.runCurrent()
-
- // THEN listeners are notified
- uiExecutor.advanceClockToLast()
- foregroundExecutor.advanceClockToLast()
- uiExecutor.runAllReady()
- foregroundExecutor.runAllReady()
- verify(listener).onSmartspaceMediaDataRemoved(eq(KEY_MEDIA_SMARTSPACE), eq(true))
- }
-
- @Test
fun testOnMediaDataChanged_updatesLastActiveTime() {
val currentTime = clock.elapsedRealtime()
addNotificationAndLoad()
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/domain/interactor/KeyguardQuickAffordanceInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorKosmos.kt
index 3b1199a..ba64ed7 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorKosmos.kt
@@ -18,6 +18,7 @@
import android.app.admin.devicePolicyManager
import android.content.applicationContext
+import android.view.accessibility.AccessibilityManager
import com.android.internal.widget.lockPatternUtils
import com.android.keyguard.logging.KeyguardQuickAffordancesLogger
import com.android.systemui.animation.dialogTransitionAnimator
@@ -54,6 +55,7 @@
dockManager = dockManager,
biometricSettingsRepository = biometricSettingsRepository,
communalSettingsInteractor = communalSettingsInteractor,
+ accessibilityManager = mock<AccessibilityManager>(),
backgroundDispatcher = testDispatcher,
appContext = applicationContext,
sceneInteractor = { sceneInteractor },
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/media/controls/domain/pipeline/MediaDataProcessorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorKosmos.kt
index 174e653..fcaad6b 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorKosmos.kt
@@ -31,9 +31,7 @@
import com.android.systemui.media.controls.util.fakeMediaControllerFactory
import com.android.systemui.media.controls.util.mediaFlags
import com.android.systemui.media.controls.util.mediaUiEventLogger
-import com.android.systemui.plugins.activityStarter
import com.android.systemui.util.Utils
-import com.android.systemui.util.settings.fakeSettings
import com.android.systemui.util.time.systemClock
val Kosmos.mediaDataProcessor by
@@ -49,12 +47,10 @@
mediaControllerFactory = fakeMediaControllerFactory,
broadcastDispatcher = broadcastDispatcher,
dumpManager = dumpManager,
- activityStarter = activityStarter,
smartspaceMediaDataProvider = SmartspaceMediaDataProvider(),
useMediaResumption = Utils.useMediaResumption(applicationContext),
useQsMediaPlayer = Utils.useQsMediaPlayer(applicationContext),
systemClock = systemClock,
- secureSettings = fakeSettings,
mediaFlags = mediaFlags,
logger = mediaUiEventLogger,
smartspaceManager = SmartspaceManager(applicationContext),
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/packages/SystemUI/utils/Android.bp b/packages/SystemUI/utils/Android.bp
index 1ef3816..1efb11b 100644
--- a/packages/SystemUI/utils/Android.bp
+++ b/packages/SystemUI/utils/Android.bp
@@ -29,4 +29,5 @@
"kotlin-stdlib",
"kotlinx_coroutines",
],
+ kotlincflags: ["-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi"],
}
diff --git a/packages/SystemUI/utils/src/com/android/systemui/utils/coroutines/flow/LatestConflated.kt b/packages/SystemUI/utils/src/com/android/systemui/utils/coroutines/flow/LatestConflated.kt
index 5f8c660..4cfb7d5 100644
--- a/packages/SystemUI/utils/src/com/android/systemui/utils/coroutines/flow/LatestConflated.kt
+++ b/packages/SystemUI/utils/src/com/android/systemui/utils/coroutines/flow/LatestConflated.kt
@@ -14,12 +14,11 @@
* limitations under the License.
*/
-@file:OptIn(ExperimentalCoroutinesApi::class, ExperimentalTypeInference::class)
+@file:OptIn(ExperimentalTypeInference::class)
package com.android.systemui.utils.coroutines.flow
import kotlin.experimental.ExperimentalTypeInference
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.FlowCollector
import kotlinx.coroutines.flow.conflate
diff --git a/ravenwood/texts/ravenwood-annotation-allowed-classes.txt b/ravenwood/texts/ravenwood-annotation-allowed-classes.txt
index f8315fe..383e75b 100644
--- a/ravenwood/texts/ravenwood-annotation-allowed-classes.txt
+++ b/ravenwood/texts/ravenwood-annotation-allowed-classes.txt
@@ -115,6 +115,7 @@
android.util.Xml
android.util.proto.EncodedBuffer
+android.util.proto.ProtoFieldFilter
android.util.proto.ProtoInputStream
android.util.proto.ProtoOutputStream
android.util.proto.ProtoParseException
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/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
index 2804945..3508f2f 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
@@ -106,7 +106,7 @@
boolean selfManaged = getNextBooleanArg();
final MacAddress macAddress = MacAddress.fromString(address);
mAssociationRequestsProcessor.createAssociation(userId, packageName, macAddress,
- deviceProfile, deviceProfile, /* associatedDevice */ null, false,
+ deviceProfile, deviceProfile, /* associatedDevice */ null, selfManaged,
/* callback */ null, /* resultReceiver */ null, /* deviceIcon */ null);
}
break;
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
index 0b335d3..4dd8b14 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
@@ -820,13 +820,12 @@
@Override
public void onAuthenticationPrompt(int uid) {
- synchronized (mVirtualDeviceManagerLock) {
- for (int i = 0; i < mVirtualDevices.size(); i++) {
- VirtualDeviceImpl device = mVirtualDevices.valueAt(i);
- device.showToastWhereUidIsRunning(uid,
- R.string.app_streaming_blocked_message_for_fingerprint_dialog,
- Toast.LENGTH_LONG, Looper.getMainLooper());
- }
+ ArrayList<VirtualDeviceImpl> virtualDevicesSnapshot = getVirtualDevicesSnapshot();
+ for (int i = 0; i < virtualDevicesSnapshot.size(); i++) {
+ VirtualDeviceImpl device = virtualDevicesSnapshot.get(i);
+ device.showToastWhereUidIsRunning(uid,
+ R.string.app_streaming_blocked_message_for_fingerprint_dialog,
+ Toast.LENGTH_LONG, Looper.getMainLooper());
}
}
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/BootReceiver.java b/services/core/java/com/android/server/BootReceiver.java
index 1588e04..7a5b866 100644
--- a/services/core/java/com/android/server/BootReceiver.java
+++ b/services/core/java/com/android/server/BootReceiver.java
@@ -40,7 +40,9 @@
import android.util.EventLog;
import android.util.Slog;
import android.util.Xml;
+import android.util.proto.ProtoFieldFilter;
import android.util.proto.ProtoOutputStream;
+import android.util.proto.ProtoParseException;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.MetricsLogger;
@@ -49,10 +51,13 @@
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
import com.android.server.am.DropboxRateLimiter;
+import com.android.server.os.TombstoneProtos.Tombstone;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileDescriptor;
@@ -64,6 +69,7 @@
import java.nio.file.attribute.PosixFilePermissions;
import java.util.HashMap;
import java.util.Iterator;
+import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -392,6 +398,129 @@
writeTimestamps(timestamps);
}
+ /**
+ * Processes a tombstone file and adds it to the DropBox after filtering and applying
+ * rate limiting.
+ * Filtering removes memory sections from the tombstone proto to reduce size while preserving
+ * critical information. The filtered tombstone is then added to DropBox in both proto
+ * and text formats, with the text format derived from the filtered proto.
+ * Rate limiting is applied as it is the case with other crash types.
+ *
+ * @param ctx Context
+ * @param tombstone path to the tombstone
+ * @param processName the name of the process corresponding to the tombstone
+ * @param tmpFileLock the lock for reading/writing tmp files
+ */
+ public static void filterAndAddTombstoneToDropBox(
+ Context ctx, File tombstone, String processName, ReentrantLock tmpFileLock) {
+ final DropBoxManager db = ctx.getSystemService(DropBoxManager.class);
+ if (db == null) {
+ Slog.e(TAG, "Can't log tombstone: DropBoxManager not available");
+ return;
+ }
+ File filteredProto = null;
+ // Check if we should rate limit and abort early if needed.
+ DropboxRateLimiter.RateLimitResult rateLimitResult =
+ sDropboxRateLimiter.shouldRateLimit(TAG_TOMBSTONE_PROTO_WITH_HEADERS, processName);
+ if (rateLimitResult.shouldRateLimit()) return;
+
+ HashMap<String, Long> timestamps = readTimestamps();
+ try {
+ tmpFileLock.lock();
+ Slog.i(TAG, "Filtering tombstone file: " + tombstone.getName());
+ // Create a temporary tombstone without memory sections.
+ filteredProto = createTempTombstoneWithoutMemory(tombstone);
+ Slog.i(TAG, "Generated tombstone file: " + filteredProto.getName());
+
+ if (recordFileTimestamp(tombstone, timestamps)) {
+ // We need to attach the count indicating the number of dropped dropbox entries
+ // due to rate limiting. Do this by enclosing the proto tombsstone in a
+ // container proto that has the dropped entry count and the proto tombstone as
+ // bytes (to avoid the complexity of reading and writing nested protos).
+ Slog.i(TAG, "Adding tombstone " + filteredProto.getName() + " to dropbox");
+ addAugmentedProtoToDropbox(filteredProto, db, rateLimitResult);
+ }
+ // Always add the text version of the tombstone to the DropBox, in order to
+ // match the previous behaviour.
+ Slog.i(TAG, "Adding text tombstone version of " + filteredProto.getName()
+ + " to dropbox");
+ addTextTombstoneFromProtoToDropbox(filteredProto, db, timestamps, rateLimitResult);
+
+ } catch (IOException | ProtoParseException e) {
+ Slog.e(TAG, "Failed to log tombstone '" + tombstone.getName()
+ + "' to DropBox. Error during processing or writing: " + e.getMessage(), e);
+ } finally {
+ if (filteredProto != null) {
+ filteredProto.delete();
+ }
+ tmpFileLock.unlock();
+ }
+ writeTimestamps(timestamps);
+ }
+
+ /**
+ * Creates a temporary tombstone file by filtering out memory mapping fields.
+ * This ensures that the unneeded memory mapping data is removed from the tombstone
+ * before adding it to Dropbox
+ *
+ * @param tombstone the original tombstone file to process
+ * @return a temporary file containing the filtered tombstone data
+ * @throws IOException if an I/O error occurs during processing
+ */
+ private static File createTempTombstoneWithoutMemory(File tombstone) throws IOException {
+ // Process the proto tombstone file and write it to a temporary file
+ File tombstoneProto =
+ File.createTempFile(tombstone.getName(), ".pb.tmp", TOMBSTONE_TMP_DIR);
+ ProtoFieldFilter protoFilter =
+ new ProtoFieldFilter(fieldNumber -> fieldNumber != (int) Tombstone.MEMORY_MAPPINGS);
+
+ try (FileInputStream fis = new FileInputStream(tombstone);
+ BufferedInputStream bis = new BufferedInputStream(fis);
+ FileOutputStream fos = new FileOutputStream(tombstoneProto);
+ BufferedOutputStream bos = new BufferedOutputStream(fos)) {
+ protoFilter.filter(bis, bos);
+ return tombstoneProto;
+ }
+ }
+
+ private static void addTextTombstoneFromProtoToDropbox(File tombstone, DropBoxManager db,
+ HashMap<String, Long> timestamps, DropboxRateLimiter.RateLimitResult rateLimitResult) {
+ File tombstoneTextFile = null;
+
+ try {
+ tombstoneTextFile = File.createTempFile(tombstone.getName(),
+ ".pb.txt.tmp", TOMBSTONE_TMP_DIR);
+
+ // Create a ProcessBuilder to execute pbtombstone
+ ProcessBuilder pb = new ProcessBuilder("/system/bin/pbtombstone", tombstone.getPath());
+ pb.redirectOutput(tombstoneTextFile);
+ Process process = pb.start();
+
+ // Wait 10 seconds for the process to complete
+ if (!process.waitFor(10, TimeUnit.SECONDS)) {
+ Slog.e(TAG, "pbtombstone timed out");
+ process.destroyForcibly();
+ return;
+ }
+
+ int exitCode = process.exitValue();
+ if (exitCode != 0) {
+ Slog.e(TAG, "pbtombstone failed with exit code " + exitCode);
+ } else {
+ final String headers = getBootHeadersToLogAndUpdate()
+ + rateLimitResult.createHeader();
+ addFileToDropBox(db, timestamps, headers, tombstoneTextFile.getPath(), LOG_SIZE,
+ TAG_TOMBSTONE);
+ }
+ } catch (IOException | InterruptedException e) {
+ Slog.e(TAG, "Failed to process tombstone with pbtombstone", e);
+ } finally {
+ if (tombstoneTextFile != null) {
+ tombstoneTextFile.delete();
+ }
+ }
+ }
+
private static void addAugmentedProtoToDropbox(
File tombstone, DropBoxManager db,
DropboxRateLimiter.RateLimitResult rateLimitResult) throws IOException {
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index d3a5254..a54a663 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -16,12 +16,23 @@
package com.android.server.am;
+import static com.android.aconfig_new_storage.Flags.enableAconfigStorageDaemon;
+import static com.android.aconfig_new_storage.Flags.enableAconfigdFromMainline;
+import static com.android.aconfig_new_storage.Flags.supportClearLocalOverridesImmediately;
+import static com.android.aconfig_new_storage.Flags.supportImmediateLocalOverrides;
+
+import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor;
+
+import android.aconfigd.Aconfigd.StorageRequestMessage;
+import android.aconfigd.Aconfigd.StorageRequestMessages;
+import android.aconfigd.Aconfigd.StorageReturnMessage;
+import android.aconfigd.Aconfigd.StorageReturnMessages;
import android.annotation.NonNull;
import android.content.ContentResolver;
import android.database.ContentObserver;
-import android.net.Uri;
-import android.net.LocalSocketAddress;
import android.net.LocalSocket;
+import android.net.LocalSocketAddress;
+import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build;
import android.os.SystemProperties;
@@ -35,28 +46,13 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.providers.settings.Flags;
-import android.aconfigd.Aconfigd.StorageRequestMessage;
-import android.aconfigd.Aconfigd.StorageRequestMessages;
-import android.aconfigd.Aconfigd.StorageReturnMessage;
-import android.aconfigd.Aconfigd.StorageReturnMessages;
-import static com.android.aconfig_new_storage.Flags.enableAconfigStorageDaemon;
-import static com.android.aconfig_new_storage.Flags.supportImmediateLocalOverrides;
-import static com.android.aconfig_new_storage.Flags.supportClearLocalOverridesImmediately;
-import static com.android.aconfig_new_storage.Flags.enableAconfigdFromMainline;
-
+import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.DataOutputStream;
-import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.HashSet;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.List;
-import java.util.ArrayList;
-import java.util.Set;
-import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor;
/**
* Maps system settings to system properties.
@@ -271,6 +267,7 @@
"wear_sysui",
"wear_system_managed_surfaces",
"wear_watchfaces",
+ "web_apps_on_chromeos_and_android",
"window_surfaces",
"windowing_frontend",
"xr",
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/appop/DiscreteOpsDbHelper.java b/services/core/java/com/android/server/appop/DiscreteOpsDbHelper.java
index 695032e..86f5d9b 100644
--- a/services/core/java/com/android/server/appop/DiscreteOpsDbHelper.java
+++ b/services/core/java/com/android/server/appop/DiscreteOpsDbHelper.java
@@ -27,9 +27,13 @@
import android.database.sqlite.SQLiteOpenHelper;
import android.database.sqlite.SQLiteRawStatement;
import android.os.Environment;
+import android.os.SystemClock;
+import android.permission.flags.Flags;
import android.util.IntArray;
import android.util.Slog;
+import com.android.internal.util.FrameworkStatsLog;
+
import java.io.File;
import java.util.ArrayList;
import java.util.List;
@@ -76,6 +80,10 @@
if (opEvents.isEmpty()) {
return;
}
+ long startTime = 0;
+ if (Flags.sqliteDiscreteOpEventLoggingEnabled()) {
+ startTime = SystemClock.elapsedRealtime();
+ }
SQLiteDatabase db = getWritableDatabase();
// TODO (b/383157289) what if database is busy and can't start a transaction? will read
@@ -117,6 +125,11 @@
+ " file size (bytes) : " + getDatabaseFile().length(), exception);
}
}
+ if (Flags.sqliteDiscreteOpEventLoggingEnabled()) {
+ long timeTaken = SystemClock.elapsedRealtime() - startTime;
+ FrameworkStatsLog.write(FrameworkStatsLog.SQLITE_DISCRETE_OP_EVENT_REPORTED,
+ -1, timeTaken, getDatabaseFile().length());
+ }
}
private void bindTextOrNull(SQLiteRawStatement statement, int index, @Nullable String text) {
@@ -181,7 +194,10 @@
uidFilter, packageNameFilter,
attributionTagFilter, opCodesFilter, opFlagsFilter);
String sql = buildSql(conditions, orderByColumn, limit);
-
+ long startTime = 0;
+ if (Flags.sqliteDiscreteOpEventLoggingEnabled()) {
+ startTime = SystemClock.elapsedRealtime();
+ }
SQLiteDatabase db = getReadableDatabase();
List<DiscreteOpsSqlRegistry.DiscreteOp> results = new ArrayList<>();
db.beginTransactionReadOnly();
@@ -225,6 +241,11 @@
} finally {
db.endTransaction();
}
+ if (Flags.sqliteDiscreteOpEventLoggingEnabled()) {
+ long timeTaken = SystemClock.elapsedRealtime() - startTime;
+ FrameworkStatsLog.write(FrameworkStatsLog.SQLITE_DISCRETE_OP_EVENT_REPORTED,
+ timeTaken, -1, getDatabaseFile().length());
+ }
return results;
}
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/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index a37e9c3..3598b9b 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -29,6 +29,7 @@
import static android.Manifest.permission.RESTRICT_DISPLAY_MODES;
import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED;
import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_GONE;
+import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE;
import static android.hardware.display.DisplayManagerGlobal.InternalEventFlag;
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED;
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR;
@@ -380,6 +381,8 @@
private final SparseArray<DisplayPowerController> mDisplayPowerControllers =
new SparseArray<>();
+ private int mMaxImportanceForRRCallbacks = IMPORTANCE_VISIBLE;
+
/** {@link DisplayBlanker} used by all {@link DisplayPowerController}s. */
private final DisplayBlanker mDisplayBlanker = new DisplayBlanker() {
// Synchronized to avoid race conditions when updating multiple display states.
@@ -3445,8 +3448,11 @@
}
private void sendDisplayEventFrameRateOverrideLocked(int displayId) {
+ int event = (mFlags.isFramerateOverrideTriggersRrCallbacksEnabled())
+ ? DisplayManagerGlobal.EVENT_DISPLAY_REFRESH_RATE_CHANGED
+ : DisplayManagerGlobal.EVENT_DISPLAY_BASIC_CHANGED;
Message msg = mHandler.obtainMessage(MSG_DELIVER_DISPLAY_EVENT_FRAME_RATE_OVERRIDE,
- displayId, DisplayManagerGlobal.EVENT_DISPLAY_BASIC_CHANGED);
+ displayId, event);
mHandler.sendMessage(msg);
}
@@ -3633,6 +3639,7 @@
pw.println(" mWifiDisplayScanRequestCount=" + mWifiDisplayScanRequestCount);
pw.println(" mStableDisplaySize=" + mStableDisplaySize);
pw.println(" mMinimumBrightnessCurve=" + mMinimumBrightnessCurve);
+ pw.println(" mMaxImportanceForRRCallbacks=" + mMaxImportanceForRRCallbacks);
if (mUserPreferredMode != null) {
pw.println(" mUserPreferredMode=" + mUserPreferredMode);
@@ -3761,6 +3768,10 @@
}
}
+ void overrideMaxImportanceForRRCallbacks(int importance) {
+ mMaxImportanceForRRCallbacks = importance;
+ }
+
boolean requestDisplayPower(int displayId, int requestedState) {
synchronized (mSyncRoot) {
final var display = mLogicalDisplayMapper.getDisplayLocked(displayId);
@@ -4144,6 +4155,18 @@
mPackageName = packageNames == null ? null : packageNames[0];
}
+ public boolean shouldReceiveRefreshRateWithChangeUpdate(int event) {
+ if (mFlags.isRefreshRateEventForForegroundAppsEnabled()
+ && event == DisplayManagerGlobal.EVENT_DISPLAY_REFRESH_RATE_CHANGED) {
+ int procState = mActivityManagerInternal.getUidProcessState(mUid);
+ int importance = ActivityManager.RunningAppProcessInfo
+ .procStateToImportance(procState);
+ return importance <= mMaxImportanceForRRCallbacks || mUid <= Process.SYSTEM_UID;
+ }
+
+ return true;
+ }
+
public void updateEventFlagsMask(@InternalEventFlag long internalEventFlag) {
mInternalEventFlagsMask.set(internalEventFlag);
}
@@ -4251,6 +4274,11 @@
}
}
+ if (!shouldReceiveRefreshRateWithChangeUpdate(event)) {
+ // The client is not visible to the user and is not a system service, so do nothing.
+ return true;
+ }
+
try {
transmitDisplayEvent(displayId, event);
return true;
@@ -4402,6 +4430,11 @@
+ displayEvent.displayId + "/"
+ displayEvent.event + " to " + mUid + "/" + mPid);
}
+
+ if (!shouldReceiveRefreshRateWithChangeUpdate(displayEvent.event)) {
+ continue;
+ }
+
transmitDisplayEvent(displayEvent.displayId, displayEvent.event);
}
return true;
diff --git a/services/core/java/com/android/server/display/DisplayManagerShellCommand.java b/services/core/java/com/android/server/display/DisplayManagerShellCommand.java
index f6b2591..e23756f 100644
--- a/services/core/java/com/android/server/display/DisplayManagerShellCommand.java
+++ b/services/core/java/com/android/server/display/DisplayManagerShellCommand.java
@@ -112,6 +112,8 @@
return requestDisplayPower(Display.STATE_UNKNOWN);
case "power-off":
return requestDisplayPower(Display.STATE_OFF);
+ case "override-max-importance-rr-callbacks":
+ return overrideMaxImportanceForRRCallbacks();
default:
return handleDefaultCommands(cmd);
}
@@ -631,4 +633,21 @@
mService.requestDisplayPower(displayId, state);
return 0;
}
+
+ private int overrideMaxImportanceForRRCallbacks() {
+ final String importanceString = getNextArg();
+ if (importanceString == null) {
+ getErrPrintWriter().println("Error: no importance specified");
+ return 1;
+ }
+ final int importance;
+ try {
+ importance = Integer.parseInt(importanceString);
+ } catch (NumberFormatException e) {
+ getErrPrintWriter().println("Error: invalid importance: '" + importanceString + "'");
+ return 1;
+ }
+ mService.overrideMaxImportanceForRRCallbacks(importance);
+ return 0;
+ }
}
diff --git a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
index 43aa6f4..d435144 100644
--- a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
+++ b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
@@ -263,6 +263,16 @@
Flags::baseDensityForExternalDisplays
);
+ private final FlagState mFramerateOverrideTriggersRrCallbacks = new FlagState(
+ Flags.FLAG_FRAMERATE_OVERRIDE_TRIGGERS_RR_CALLBACKS,
+ Flags::framerateOverrideTriggersRrCallbacks
+ );
+
+ private final FlagState mRefreshRateEventForForegroundApps = new FlagState(
+ Flags.FLAG_REFRESH_RATE_EVENT_FOR_FOREGROUND_APPS,
+ Flags::refreshRateEventForForegroundApps
+ );
+
/**
* @return {@code true} if 'port' is allowed in display layout configuration file.
*/
@@ -564,6 +574,22 @@
return mBaseDensityForExternalDisplays.isEnabled();
}
+ /**
+ * @return {@code true} if the flag triggering refresh rate callbacks when framerate is
+ * overridden is enabled
+ */
+ public boolean isFramerateOverrideTriggersRrCallbacksEnabled() {
+ return mFramerateOverrideTriggersRrCallbacks.isEnabled();
+ }
+
+
+ /**
+ * @return {@code true} if the flag for sending refresh rate events only for the apps in
+ * foreground is enabled
+ */
+ public boolean isRefreshRateEventForForegroundAppsEnabled() {
+ return mRefreshRateEventForForegroundApps.isEnabled();
+ }
/**
* dumps all flagstates
@@ -620,6 +646,8 @@
pw.println(" " + mSubscribeGranularDisplayEvents);
pw.println(" " + mEnableDisplayContentModeManagementFlagState);
pw.println(" " + mBaseDensityForExternalDisplays);
+ pw.println(" " + mFramerateOverrideTriggersRrCallbacks);
+ pw.println(" " + mRefreshRateEventForForegroundApps);
}
private static class FlagState {
diff --git a/services/core/java/com/android/server/display/feature/display_flags.aconfig b/services/core/java/com/android/server/display/feature/display_flags.aconfig
index 00a9dcb..63cd2d7 100644
--- a/services/core/java/com/android/server/display/feature/display_flags.aconfig
+++ b/services/core/java/com/android/server/display/feature/display_flags.aconfig
@@ -479,3 +479,25 @@
bug: "382954433"
is_fixed_read_only: true
}
+
+flag {
+ name: "framerate_override_triggers_rr_callbacks"
+ namespace: "display_manager"
+ description: "Feature flag to trigger the RR callbacks when framerate overridding happens."
+ bug: "390113266"
+ is_fixed_read_only: true
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
+ name: "refresh_rate_event_for_foreground_apps"
+ namespace: "display_manager"
+ description: "Send Refresh Rate events only for the apps in foreground."
+ bug: "390107600"
+ is_fixed_read_only: true
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
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/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index ef39f18..588e879 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -356,6 +356,7 @@
import com.android.internal.util.XmlUtils;
import com.android.internal.util.function.TriPredicate;
import com.android.internal.widget.LockPatternUtils;
+import com.android.modules.expresslog.Counter;
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
import com.android.server.DeviceIdleInternal;
@@ -762,7 +763,7 @@
private int mWarnRemoteViewsSizeBytes;
private int mStripRemoteViewsSizeBytes;
- private String[] mDefaultUnsupportedAdjustments;
+ protected String[] mDefaultUnsupportedAdjustments;
@VisibleForTesting
protected boolean mShowReviewPermissionsNotification;
@@ -4377,8 +4378,8 @@
public @NonNull List<String> getUnsupportedAdjustmentTypes() {
checkCallerIsSystemOrSystemUiOrShell();
synchronized (mNotificationLock) {
- return new ArrayList(mAssistants.mNasUnsupported.getOrDefault(
- UserHandle.getUserId(Binder.getCallingUid()), new HashSet<>()));
+ return new ArrayList(mAssistants.getUnsupportedAdjustments(
+ UserHandle.getUserId(Binder.getCallingUid())));
}
}
@@ -7136,6 +7137,13 @@
Slog.e(TAG, "exiting pullStats: bad request");
return 0;
}
+
+ @Override
+ public void incrementCounter(String metricId) {
+ if (android.app.Flags.nmBinderPerfLogNmThrottling() && metricId != null) {
+ Counter.logIncrementWithUid(metricId, Binder.getCallingUid());
+ }
+ }
};
private void handleNotificationPermissionChange(String pkg, @UserIdInt int userId) {
@@ -7214,6 +7222,7 @@
toRemove.add(potentialKey);
}
if (notificationClassification() && adjustments.containsKey(KEY_TYPE)) {
+ mAssistants.setNasUnsupportedDefaults(r.getSbn().getNormalizedUserId());
if (!mAssistants.isAdjustmentKeyTypeAllowed(adjustments.getInt(KEY_TYPE))) {
toRemove.add(potentialKey);
} else if (notificationClassificationUi()
@@ -11867,9 +11876,9 @@
static final String TAG_ENABLED_NOTIFICATION_ASSISTANTS = "enabled_assistants";
private static final String ATT_TYPES = "types";
- private static final String ATT_DENIED = "denied_adjustments";
+ private static final String ATT_DENIED = "user_denied_adjustments";
private static final String ATT_ENABLED_TYPES = "enabled_key_types";
- private static final String ATT_NAS_UNSUPPORTED = "unsupported_adjustments";
+ private static final String ATT_NAS_UNSUPPORTED = "nas_unsupported_adjustments";
private static final String ATT_TYPES_DENIED_APPS = "types_denied_apps";
private final Object mLock = new Object();
@@ -11965,9 +11974,6 @@
}
} else {
mAllowedAdjustmentKeyTypes.addAll(List.of(DEFAULT_ALLOWED_ADJUSTMENT_KEY_TYPES));
- if (mDefaultUnsupportedAdjustments != null) {
- mAllowedAdjustments.removeAll(List.of(mDefaultUnsupportedAdjustments));
- }
}
}
@@ -12487,7 +12493,7 @@
}
} else {
if (android.service.notification.Flags.notificationClassification()) {
- mNasUnsupported.put(userId, new HashSet<>());
+ setNasUnsupportedDefaults(userId);
}
}
super.setPackageOrComponentEnabled(pkgOrComponent, userId, isPrimary, enabled, userSet);
@@ -12528,8 +12534,8 @@
if (!android.service.notification.Flags.notificationClassification()) {
return;
}
- HashSet<String> disabledAdjustments =
- mNasUnsupported.getOrDefault(info.userid, new HashSet<>());
+ setNasUnsupportedDefaults(info.userid);
+ HashSet<String> disabledAdjustments = mNasUnsupported.get(info.userid);
if (supported) {
disabledAdjustments.remove(key);
} else {
@@ -12545,7 +12551,15 @@
if (!android.service.notification.Flags.notificationClassification()) {
return new HashSet<>();
}
- return mNasUnsupported.getOrDefault(userId, new HashSet<>());
+ setNasUnsupportedDefaults(userId);
+ return mNasUnsupported.get(userId);
+ }
+
+ private void setNasUnsupportedDefaults(@UserIdInt int userId) {
+ if (mNasUnsupported != null && !mNasUnsupported.containsKey(userId)) {
+ mNasUnsupported.put(userId, new HashSet(List.of(mDefaultUnsupportedAdjustments)));
+ handleSavePolicyFile();
+ }
}
@Override
@@ -12658,7 +12672,7 @@
List<String> unsupportedAdjustments = new ArrayList(
mNasUnsupported.getOrDefault(
UserHandle.getUserId(Binder.getCallingUid()),
- new HashSet<>())
+ new HashSet(List.of(mDefaultUnsupportedAdjustments)))
);
bundlesAllowed = !unsupportedAdjustments.contains(Adjustment.KEY_TYPE);
}
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/os/NativeTombstoneManager.java b/services/core/java/com/android/server/os/NativeTombstoneManager.java
index f23d782..33c1229 100644
--- a/services/core/java/com/android/server/os/NativeTombstoneManager.java
+++ b/services/core/java/com/android/server/os/NativeTombstoneManager.java
@@ -137,16 +137,26 @@
return;
}
- String processName = "UNKNOWN";
final boolean isProtoFile = filename.endsWith(".pb");
+
+ // Only process the pb tombstone output, the text version will be generated in
+ // BootReceiver.filterAndAddTombstoneToDropBox through pbtombstone
+ if (Flags.protoTombstone() && !isProtoFile) {
+ return;
+ }
+
File protoPath = isProtoFile ? path : new File(path.getAbsolutePath() + ".pb");
- Optional<TombstoneFile> parsedTombstone = handleProtoTombstone(protoPath, isProtoFile);
- if (parsedTombstone.isPresent()) {
- processName = parsedTombstone.get().getProcessName();
- }
- BootReceiver.addTombstoneToDropBox(mContext, path, isProtoFile, processName, mTmpFileLock);
+ final String processName = handleProtoTombstone(protoPath, isProtoFile)
+ .map(TombstoneFile::getProcessName)
+ .orElse("UNKNOWN");
+ if (Flags.protoTombstone()) {
+ BootReceiver.filterAndAddTombstoneToDropBox(mContext, path, processName, mTmpFileLock);
+ } else {
+ BootReceiver.addTombstoneToDropBox(mContext, path, isProtoFile,
+ processName, mTmpFileLock);
+ }
// TODO(b/339371242): An optimizer on WearOS is misbehaving and this member is being garbage
// collected as it's never referenced inside this class outside of the constructor. But,
// it's a file watcher, and needs to stay alive to do its job. So, add a cheap check here to
diff --git a/services/core/java/com/android/server/os/core_os_flags.aconfig b/services/core/java/com/android/server/os/core_os_flags.aconfig
index efdc9b8..5e35cf5 100644
--- a/services/core/java/com/android/server/os/core_os_flags.aconfig
+++ b/services/core/java/com/android/server/os/core_os_flags.aconfig
@@ -3,7 +3,7 @@
flag {
name: "proto_tombstone"
- namespace: "proto_tombstone_ns"
+ namespace: "stability"
description: "Use proto tombstones as source of truth for adding to dropbox"
bug: "323857385"
}
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index b043d13..8eb5b6f 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -863,6 +863,9 @@
// restore if appropriate, then pass responsibility back to the
// Package Manager to run the post-install observer callbacks
// and broadcasts.
+ // Note: MUST close freezer before backup/restore, otherwise test
+ // of CtsBackupHostTestCases will fail.
+ request.closeFreezer();
doRestore = performBackupManagerRestore(userId, token, request);
}
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/power/OWNERS b/services/core/java/com/android/server/power/OWNERS
index c1fad33..a1531a8 100644
--- a/services/core/java/com/android/server/power/OWNERS
+++ b/services/core/java/com/android/server/power/OWNERS
@@ -2,6 +2,8 @@
santoscordon@google.com
petsjonkin@google.com
brup@google.com
+flc@google.com
+wilczynskip@google.com
per-file ThermalManagerService.java=file:/THERMAL_OWNERS
per-file LowPowerStandbyController.java=qingxun@google.com
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index b7b4cc0..48dd2eb 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -87,6 +87,7 @@
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.IndentingPrintWriter;
+import android.util.IntArray;
import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
@@ -124,6 +125,7 @@
import com.android.server.power.ShutdownCheckPoints;
import com.android.server.power.ShutdownThread;
import com.android.server.wm.ActivityTaskManagerInternal;
+import com.android.systemui.shared.Flags;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -341,15 +343,19 @@
@Override
public void onDisplayAdded(int displayId) {
- synchronized (mLock) {
- mDisplayUiState.put(displayId, new UiState());
+ if (Flags.statusBarConnectedDisplays()) {
+ synchronized (mLock) {
+ mDisplayUiState.put(displayId, new UiState());
+ }
}
}
@Override
public void onDisplayRemoved(int displayId) {
- synchronized (mLock) {
- mDisplayUiState.remove(displayId);
+ if (Flags.statusBarConnectedDisplays()) {
+ synchronized (mLock) {
+ mDisplayUiState.remove(displayId);
+ }
}
}
@@ -1320,53 +1326,66 @@
return mTracingEnabled;
}
- // TODO(b/117478341): make it aware of multi-display if needed.
@Override
public void disable(int what, IBinder token, String pkg) {
disableForUser(what, token, pkg, mCurrentUserId);
}
- // TODO(b/117478341): make it aware of multi-display if needed.
+ /**
+ * Disable additional status bar features for user for all displays. Pass the bitwise-or of the
+ * {@code #DISABLE_*} flags. To re-enable everything, pass {@code #DISABLE_NONE}.
+ *
+ * Warning: Only pass {@code #DISABLE_*} flags into this function, do not use
+ * {@code #DISABLE2_*} flags.
+ */
@Override
public void disableForUser(int what, IBinder token, String pkg, int userId) {
enforceStatusBar();
enforceValidCallingUser();
synchronized (mLock) {
- disableLocked(DEFAULT_DISPLAY, userId, what, token, pkg, 1);
+ IntArray displayIds = new IntArray();
+ for (int i = 0; i < mDisplayUiState.size(); i++) {
+ displayIds.add(mDisplayUiState.keyAt(i));
+ }
+ disableLocked(displayIds, userId, what, token, pkg, 1);
}
}
- // TODO(b/117478341): make it aware of multi-display if needed.
/**
- * Disable additional status bar features. Pass the bitwise-or of the DISABLE2_* flags.
- * To re-enable everything, pass {@link #DISABLE2_NONE}.
+ * Disable additional status bar features. Pass the bitwise-or of the {@code #DISABLE2_*} flags.
+ * To re-enable everything, pass {@code #DISABLE2_NONE}.
*
- * Warning: Only pass DISABLE2_* flags into this function, do not use DISABLE_* flags.
+ * Warning: Only pass {@code #DISABLE2_*} flags into this function, do not use
+ * {@code #DISABLE_*} flags.
*/
@Override
public void disable2(int what, IBinder token, String pkg) {
disable2ForUser(what, token, pkg, mCurrentUserId);
}
- // TODO(b/117478341): make it aware of multi-display if needed.
/**
- * Disable additional status bar features for a given user. Pass the bitwise-or of the
- * DISABLE2_* flags. To re-enable everything, pass {@link #DISABLE_NONE}.
+ * Disable additional status bar features for a given user for all displays. Pass the bitwise-or
+ * of the {@code #DISABLE2_*} flags. To re-enable everything, pass {@code #DISABLE2_NONE}.
*
- * Warning: Only pass DISABLE2_* flags into this function, do not use DISABLE_* flags.
+ * Warning: Only pass {@code #DISABLE2_*} flags into this function, do not use
+ * {@code #DISABLE_*} flags.
*/
@Override
public void disable2ForUser(int what, IBinder token, String pkg, int userId) {
enforceStatusBar();
synchronized (mLock) {
- disableLocked(DEFAULT_DISPLAY, userId, what, token, pkg, 2);
+ IntArray displayIds = new IntArray();
+ for (int i = 0; i < mDisplayUiState.size(); i++) {
+ displayIds.add(mDisplayUiState.keyAt(i));
+ }
+ disableLocked(displayIds, userId, what, token, pkg, 2);
}
}
- private void disableLocked(int displayId, int userId, int what, IBinder token, String pkg,
- int whichFlag) {
+ private void disableLocked(IntArray displayIds, int userId, int what, IBinder token,
+ String pkg, int whichFlag) {
// It's important that the the callback and the call to mBar get done
// in the same order when multiple threads are calling this function
// so they are paired correctly. The messages on the handler will be
@@ -1376,18 +1395,27 @@
// Ensure state for the current user is applied, even if passed a non-current user.
final int net1 = gatherDisableActionsLocked(mCurrentUserId, 1);
final int net2 = gatherDisableActionsLocked(mCurrentUserId, 2);
- final UiState state = getUiState(displayId);
- if (!state.disableEquals(net1, net2)) {
- state.setDisabled(net1, net2);
- mHandler.post(() -> mNotificationDelegate.onSetDisabled(net1));
- IStatusBar bar = mBar;
- if (bar != null) {
- try {
- bar.disable(displayId, net1, net2);
- } catch (RemoteException ex) {
+ boolean shouldCallNotificationOnSetDisabled = false;
+ IStatusBar bar = mBar;
+ for (int displayId : displayIds.toArray()) {
+ final UiState state = getUiState(displayId);
+ if (!state.disableEquals(net1, net2)) {
+ shouldCallNotificationOnSetDisabled = true;
+ state.setDisabled(net1, net2);
+ if (bar != null) {
+ try {
+ // TODO(b/388244660): Create IStatusBar#disableForAllDisplays to avoid
+ // multiple IPC calls.
+ bar.disable(displayId, net1, net2);
+ } catch (RemoteException ex) {
+ Slog.e(TAG, "Unable to disable Status bar.", ex);
+ }
}
}
}
+ if (shouldCallNotificationOnSetDisabled) {
+ mHandler.post(() -> mNotificationDelegate.onSetDisabled(net1));
+ }
}
/**
@@ -1539,7 +1567,8 @@
if (SPEW) Slog.d(TAG, "setDisableFlags(0x" + Integer.toHexString(flags) + ")");
synchronized (mLock) {
- disableLocked(displayId, mCurrentUserId, flags, mSysUiVisToken, cause, 1);
+ disableLocked(IntArray.wrap(new int[]{displayId}), mCurrentUserId, flags,
+ mSysUiVisToken, cause, 1);
}
}
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 42a47d4..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.
@@ -784,11 +781,6 @@
@NonNull
final AppCompatController mAppCompatController;
- // Whether the activity is eligible to be letterboxed for fixed orientation with respect to its
- // requested orientation, even when it's letterbox for another reason (e.g., size compat mode)
- // and therefore #isLetterboxedForFixedOrientationAndAspectRatio returns false.
- private boolean mIsEligibleForFixedOrientationLetterbox;
-
/**
* Whether the activity is to be displayed. See {@link android.R.attr#windowNoDisplay}.
*/
@@ -2829,7 +2821,10 @@
}
void removeStartingWindow() {
- boolean prevEligibleForLetterboxEducation = isEligibleForLetterboxEducation();
+ final AppCompatLetterboxPolicy letterboxPolicy = mAppCompatController
+ .getAppCompatLetterboxPolicy();
+ boolean prevEligibleForLetterboxEducation =
+ letterboxPolicy.isEligibleForLetterboxEducation();
if (mStartingData != null
&& mStartingData.mRemoveAfterTransaction == AFTER_TRANSITION_FINISH) {
@@ -2842,8 +2837,8 @@
removeStartingWindowAnimation(true /* prepareAnimation */);
final Task task = getTask();
- if (prevEligibleForLetterboxEducation != isEligibleForLetterboxEducation()
- && task != null) {
+ if (task != null && prevEligibleForLetterboxEducation
+ != letterboxPolicy.isEligibleForLetterboxEducation()) {
// Trigger TaskInfoChanged to update the letterbox education.
task.dispatchTaskInfoChangedIfNeeded(true /* force */);
}
@@ -4513,8 +4508,6 @@
removeIfPossible();
}
- stopFreezingScreen(true, true);
-
final DisplayContent dc = getDisplayContent();
if (dc.mFocusedApp == this) {
ProtoLog.v(WM_DEBUG_FOCUS_LIGHT,
@@ -5795,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()
@@ -5805,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.
@@ -6844,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
@@ -7103,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();
}
}
@@ -7206,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;
}
@@ -7250,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;
@@ -8201,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();
@@ -8443,7 +8297,8 @@
final AppCompatAspectRatioPolicy aspectRatioPolicy =
mAppCompatController.getAspectRatioPolicy();
aspectRatioPolicy.reset();
- mIsEligibleForFixedOrientationLetterbox = false;
+ mAppCompatController.getAppCompatLetterboxPolicy()
+ .resetFixedOrientationLetterboxEligibility();
mResolveConfigHint.resolveTmpOverrides(mDisplayContent, newParentConfiguration,
isFixedRotationTransforming());
@@ -8780,28 +8635,6 @@
}
/**
- * Whether this activity is eligible for letterbox eduction.
- *
- * <p>Conditions that need to be met:
- *
- * <ul>
- * <li>{@link AppCompatConfiguration#getIsEducationEnabled} is true.
- * <li>The activity is eligible for fixed orientation letterbox.
- * <li>The activity is in fullscreen.
- * <li>The activity is portrait-only.
- * <li>The activity doesn't have a starting window (education should only be displayed
- * once the starting window is removed in {@link #removeStartingWindow}).
- * </ul>
- */
- boolean isEligibleForLetterboxEducation() {
- return mWmService.mAppCompatConfiguration.getIsEducationEnabled()
- && mIsEligibleForFixedOrientationLetterbox
- && getWindowingMode() == WINDOWING_MODE_FULLSCREEN
- && getRequestedConfigurationOrientation() == ORIENTATION_PORTRAIT
- && mStartingWindow == null;
- }
-
- /**
* In some cases, applying insets to bounds changes the orientation. For example, if a
* close-to-square display rotates to portrait to respect a portrait orientation activity, after
* insets such as the status and nav bars are applied, the activity may actually have a
@@ -8905,11 +8738,11 @@
// If the activity requires a different orientation (either by override or activityInfo),
// make it fit the available bounds by scaling down its bounds.
final int forcedOrientation = getRequestedConfigurationOrientation();
+ final boolean isEligibleForFixedOrientationLetterbox = mAppCompatController
+ .getAppCompatLetterboxPolicy()
+ .resolveFixedOrientationLetterboxEligibility(forcedOrientation, parentOrientation);
- mIsEligibleForFixedOrientationLetterbox = forcedOrientation != ORIENTATION_UNDEFINED
- && forcedOrientation != parentOrientation;
-
- if (!mIsEligibleForFixedOrientationLetterbox && (forcedOrientation == ORIENTATION_UNDEFINED
+ if (!isEligibleForFixedOrientationLetterbox && (forcedOrientation == ORIENTATION_UNDEFINED
|| orientationRespectedWithInsets)) {
return;
}
@@ -9407,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(
@@ -9419,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();
@@ -9718,7 +9544,6 @@
scheduleStopForRestartProcess();
});
} else {
- startFreezingScreen();
scheduleStopForRestartProcess();
}
}
@@ -10105,7 +9930,7 @@
proto.write(OVERRIDE_ORIENTATION, getOverrideOrientation());
proto.write(SHOULD_SEND_COMPAT_FAKE_FOCUS, shouldSendCompatFakeFocus());
final AppCompatCameraOverrides cameraOverrides =
- mAppCompatController.getAppCompatCameraOverrides();
+ mAppCompatController.getCameraOverrides();
proto.write(SHOULD_FORCE_ROTATE_FOR_CAMERA_COMPAT,
cameraOverrides.shouldForceRotateForCameraCompat());
proto.write(SHOULD_REFRESH_ACTIVITY_FOR_CAMERA_COMPAT,
diff --git a/services/core/java/com/android/server/wm/ActivityRefresher.java b/services/core/java/com/android/server/wm/ActivityRefresher.java
index 597f75a..25e38b3 100644
--- a/services/core/java/com/android/server/wm/ActivityRefresher.java
+++ b/services/core/java/com/android/server/wm/ActivityRefresher.java
@@ -77,10 +77,10 @@
final boolean cycleThroughStop =
mWmService.mAppCompatConfiguration
.isCameraCompatRefreshCycleThroughStopEnabled()
- && !activity.mAppCompatController.getAppCompatCameraOverrides()
+ && !activity.mAppCompatController.getCameraOverrides()
.shouldRefreshActivityViaPauseForCameraCompat();
- activity.mAppCompatController.getAppCompatCameraOverrides().setIsRefreshRequested(true);
+ activity.mAppCompatController.getCameraOverrides().setIsRefreshRequested(true);
ProtoLog.v(WM_DEBUG_STATES,
"Refreshing activity for freeform camera compatibility treatment, "
+ "activityRecord=%s", activity);
@@ -97,25 +97,25 @@
}
}, REFRESH_CALLBACK_TIMEOUT_MS);
} catch (RemoteException e) {
- activity.mAppCompatController.getAppCompatCameraOverrides()
+ activity.mAppCompatController.getCameraOverrides()
.setIsRefreshRequested(false);
}
}
boolean isActivityRefreshing(@NonNull ActivityRecord activity) {
- return activity.mAppCompatController.getAppCompatCameraOverrides().isRefreshRequested();
+ return activity.mAppCompatController.getCameraOverrides().isRefreshRequested();
}
void onActivityRefreshed(@NonNull ActivityRecord activity) {
// TODO(b/333060789): can we tell that refresh did not happen by observing the activity
// state?
- activity.mAppCompatController.getAppCompatCameraOverrides().setIsRefreshRequested(false);
+ activity.mAppCompatController.getCameraOverrides().setIsRefreshRequested(false);
}
private boolean shouldRefreshActivity(@NonNull ActivityRecord activity,
@NonNull Configuration newConfig, @NonNull Configuration lastReportedConfig) {
return mWmService.mAppCompatConfiguration.isCameraCompatRefreshEnabled()
- && activity.mAppCompatController.getAppCompatCameraOverrides()
+ && activity.mAppCompatController.getCameraOverrides()
.shouldRefreshActivityForCameraCompat()
&& ArrayUtils.find(mEvaluators.toArray(), evaluator ->
((Evaluator) evaluator)
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/AppCompatAspectRatioOverrides.java b/services/core/java/com/android/server/wm/AppCompatAspectRatioOverrides.java
index 086b11c..fa04955 100644
--- a/services/core/java/com/android/server/wm/AppCompatAspectRatioOverrides.java
+++ b/services/core/java/com/android/server/wm/AppCompatAspectRatioOverrides.java
@@ -272,7 +272,7 @@
final boolean isLandscape = isFixedOrientationLandscape(
mActivityRecord.getOverrideOrientation());
final AppCompatCameraOverrides cameraOverrides =
- mActivityRecord.mAppCompatController.getAppCompatCameraOverrides();
+ mActivityRecord.mAppCompatController.getCameraOverrides();
// Don't resize to split screen size when in book mode if letterbox position is centered
return (isBookMode && isNotCenteredHorizontally || isTabletopMode && isLandscape)
|| cameraOverrides.isCameraCompatSplitScreenAspectRatioAllowed()
diff --git a/services/core/java/com/android/server/wm/AppCompatCameraPolicy.java b/services/core/java/com/android/server/wm/AppCompatCameraPolicy.java
index 6074608..276c7d2 100644
--- a/services/core/java/com/android/server/wm/AppCompatCameraPolicy.java
+++ b/services/core/java/com/android/server/wm/AppCompatCameraPolicy.java
@@ -277,7 +277,7 @@
*/
static boolean shouldOverrideMinAspectRatioForCamera(@NonNull ActivityRecord activityRecord) {
return AppCompatCameraPolicy.isCameraRunningAndWindowingModeEligible(activityRecord)
- && activityRecord.mAppCompatController.getAppCompatCameraOverrides()
+ && activityRecord.mAppCompatController.getCameraOverrides()
.isOverrideMinAspectRatioForCameraEnabled();
}
}
diff --git a/services/core/java/com/android/server/wm/AppCompatController.java b/services/core/java/com/android/server/wm/AppCompatController.java
index cc9cd90..b7d8aff 100644
--- a/services/core/java/com/android/server/wm/AppCompatController.java
+++ b/services/core/java/com/android/server/wm/AppCompatController.java
@@ -94,8 +94,8 @@
}
@NonNull
- AppCompatCameraOverrides getAppCompatCameraOverrides() {
- return mAppCompatOverrides.getAppCompatCameraOverrides();
+ AppCompatCameraOverrides getCameraOverrides() {
+ return mAppCompatOverrides.getCameraOverrides();
}
@NonNull
diff --git a/services/core/java/com/android/server/wm/AppCompatLetterboxPolicy.java b/services/core/java/com/android/server/wm/AppCompatLetterboxPolicy.java
index 4494586..6a8040a 100644
--- a/services/core/java/com/android/server/wm/AppCompatLetterboxPolicy.java
+++ b/services/core/java/com/android/server/wm/AppCompatLetterboxPolicy.java
@@ -16,6 +16,9 @@
package com.android.server.wm;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
+import static android.content.res.Configuration.ORIENTATION_UNDEFINED;
import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
@@ -28,6 +31,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.content.res.Configuration.Orientation;
import android.graphics.Point;
import android.graphics.Rect;
import android.view.SurfaceControl;
@@ -55,6 +59,11 @@
private boolean mLastShouldShowLetterboxUi;
+ // Whether the activity is eligible to be letterboxed for fixed orientation with respect to its
+ // requested orientation, even when it's letterbox for another reason (e.g., size compat mode)
+ // and therefore #isLetterboxedForFixedOrientationAndAspectRatio returns false.
+ private boolean mIsEligibleForFixedOrientationLetterbox;
+
AppCompatLetterboxPolicy(@NonNull ActivityRecord activityRecord,
@NonNull AppCompatConfiguration appCompatConfiguration) {
mActivityRecord = activityRecord;
@@ -66,6 +75,10 @@
mAppCompatConfiguration = appCompatConfiguration;
}
+ void resetFixedOrientationLetterboxEligibility() {
+ mIsEligibleForFixedOrientationLetterbox = false;
+ }
+
/** Cleans up {@link Letterbox} if it exists.*/
void stop() {
mLetterboxPolicyState.stop();
@@ -91,6 +104,43 @@
mLetterboxPolicyState.getLetterboxInnerBounds(outBounds);
}
+ /**
+ * Checks if the current activity is eligible to be letterboxed because of a fixed orientation.
+ *
+ * @param forcedOrientation The requeste orientation
+ * @param parentOrientation The orientation of the parent container.
+ * @return {@code true} if the activity can be letterboxed because of the requested fixed
+ * orientation.
+ */
+ boolean resolveFixedOrientationLetterboxEligibility(@Orientation int forcedOrientation,
+ @Orientation int parentOrientation) {
+ mIsEligibleForFixedOrientationLetterbox = forcedOrientation != ORIENTATION_UNDEFINED
+ && forcedOrientation != parentOrientation;
+ return mIsEligibleForFixedOrientationLetterbox;
+ }
+
+ /**
+ * Whether this activity is eligible for letterbox eduction.
+ *
+ * <p>Conditions that need to be met:
+ *
+ * <ul>
+ * <li>{@link AppCompatConfiguration#getIsEducationEnabled} is true.
+ * <li>The activity is eligible for fixed orientation letterbox.
+ * <li>The activity is in fullscreen.
+ * <li>The activity is portrait-only.
+ * <li>The activity doesn't have a starting window (education should only be displayed
+ * once the starting window is removed in {@link #removeStartingWindow}).
+ * </ul>
+ */
+ boolean isEligibleForLetterboxEducation() {
+ return mAppCompatConfiguration.getIsEducationEnabled()
+ && mIsEligibleForFixedOrientationLetterbox
+ && mActivityRecord.getWindowingMode() == WINDOWING_MODE_FULLSCREEN
+ && mActivityRecord.getRequestedConfigurationOrientation() == ORIENTATION_PORTRAIT
+ && mActivityRecord.mStartingWindow == null;
+ }
+
@Nullable
LetterboxDetails getLetterboxDetails() {
final WindowState w = mActivityRecord.findMainWindow();
diff --git a/services/core/java/com/android/server/wm/AppCompatOrientationPolicy.java b/services/core/java/com/android/server/wm/AppCompatOrientationPolicy.java
index 6202f80..35fa39d 100644
--- a/services/core/java/com/android/server/wm/AppCompatOrientationPolicy.java
+++ b/services/core/java/com/android/server/wm/AppCompatOrientationPolicy.java
@@ -100,7 +100,7 @@
}
if (displayContent != null
- && mAppCompatOverrides.getAppCompatCameraOverrides()
+ && mAppCompatOverrides.getCameraOverrides()
.isOverrideOrientationOnlyForCameraEnabled()
&& !AppCompatCameraPolicy
.isActivityEligibleForOrientationOverride(mActivityRecord)) {
diff --git a/services/core/java/com/android/server/wm/AppCompatOverrides.java b/services/core/java/com/android/server/wm/AppCompatOverrides.java
index 2d0ff9b..811a39c 100644
--- a/services/core/java/com/android/server/wm/AppCompatOverrides.java
+++ b/services/core/java/com/android/server/wm/AppCompatOverrides.java
@@ -29,7 +29,7 @@
@NonNull
private final AppCompatOrientationOverrides mOrientationOverrides;
@NonNull
- private final AppCompatCameraOverrides mAppCompatCameraOverrides;
+ private final AppCompatCameraOverrides mCameraOverrides;
@NonNull
private final AppCompatAspectRatioOverrides mAspectRatioOverrides;
@NonNull
@@ -46,10 +46,10 @@
@NonNull AppCompatConfiguration appCompatConfiguration,
@NonNull OptPropFactory optPropBuilder,
@NonNull AppCompatDeviceStateQuery appCompatDeviceStateQuery) {
- mAppCompatCameraOverrides = new AppCompatCameraOverrides(activityRecord,
+ mCameraOverrides = new AppCompatCameraOverrides(activityRecord,
appCompatConfiguration, optPropBuilder);
mOrientationOverrides = new AppCompatOrientationOverrides(activityRecord,
- appCompatConfiguration, optPropBuilder, mAppCompatCameraOverrides);
+ appCompatConfiguration, optPropBuilder, mCameraOverrides);
mReachabilityOverrides = new AppCompatReachabilityOverrides(activityRecord,
appCompatConfiguration, appCompatDeviceStateQuery);
mAspectRatioOverrides = new AppCompatAspectRatioOverrides(activityRecord,
@@ -69,8 +69,8 @@
}
@NonNull
- AppCompatCameraOverrides getAppCompatCameraOverrides() {
- return mAppCompatCameraOverrides;
+ AppCompatCameraOverrides getCameraOverrides() {
+ return mCameraOverrides;
}
@NonNull
diff --git a/services/core/java/com/android/server/wm/AppCompatUtils.java b/services/core/java/com/android/server/wm/AppCompatUtils.java
index 1ab0868..67f5b9b 100644
--- a/services/core/java/com/android/server/wm/AppCompatUtils.java
+++ b/services/core/java/com/android/server/wm/AppCompatUtils.java
@@ -150,10 +150,12 @@
appCompatTaskInfo.setTopActivityInSizeCompat(top.fillsParent());
}
// Whether the direct top activity is eligible for letterbox education.
- appCompatTaskInfo.setEligibleForLetterboxEducation(
- isTopActivityResumed && top.isEligibleForLetterboxEducation());
- appCompatTaskInfo.setLetterboxEducationEnabled(top.mAppCompatController
- .getAppCompatLetterboxOverrides().isLetterboxEducationEnabled());
+ appCompatTaskInfo.setEligibleForLetterboxEducation(isTopActivityResumed
+ && top.mAppCompatController.getAppCompatLetterboxPolicy()
+ .isEligibleForLetterboxEducation());
+ appCompatTaskInfo.setLetterboxEducationEnabled(
+ top.mAppCompatController.getAppCompatLetterboxOverrides()
+ .isLetterboxEducationEnabled());
final AppCompatAspectRatioOverrides aspectRatioOverrides =
top.mAppCompatController.getAspectRatioOverrides();
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/CameraCompatFreeformPolicy.java b/services/core/java/com/android/server/wm/CameraCompatFreeformPolicy.java
index f5bc9f0..230cd33 100644
--- a/services/core/java/com/android/server/wm/CameraCompatFreeformPolicy.java
+++ b/services/core/java/com/android/server/wm/CameraCompatFreeformPolicy.java
@@ -221,7 +221,7 @@
}
boolean isCameraRunningAndWindowingModeEligible(@NonNull ActivityRecord activity) {
- return activity.mAppCompatController.getAppCompatCameraOverrides()
+ return activity.mAppCompatController.getCameraOverrides()
.shouldApplyFreeformTreatmentForCameraCompat()
&& activity.inFreeformWindowingMode()
&& mCameraStateMonitor.isCameraRunningForActivity(activity);
@@ -232,7 +232,7 @@
// different camera compat aspect ratio set: this allows per-app camera compat override
// aspect ratio to be smaller than the default.
return isInFreeformCameraCompatMode(activity) && !activity.mAppCompatController
- .getAppCompatCameraOverrides().isOverrideMinAspectRatioForCameraEnabled();
+ .getCameraOverrides().isOverrideMinAspectRatioForCameraEnabled();
}
boolean isInFreeformCameraCompatMode(@NonNull ActivityRecord activity) {
@@ -307,7 +307,7 @@
boolean isTreatmentEnabledForActivity(@NonNull ActivityRecord activity,
boolean checkOrientation) {
int orientation = activity.getRequestedConfigurationOrientation();
- return activity.mAppCompatController.getAppCompatCameraOverrides()
+ return activity.mAppCompatController.getCameraOverrides()
.shouldApplyFreeformTreatmentForCameraCompat()
&& mCameraStateMonitor.isCameraRunningForActivity(activity)
&& (!checkOrientation || orientation != ORIENTATION_UNDEFINED)
@@ -333,6 +333,6 @@
|| !mCameraStateMonitor.isCameraWithIdRunningForActivity(topActivity, cameraId)) {
return false;
}
- return topActivity.mAppCompatController.getAppCompatCameraOverrides().isRefreshRequested();
+ return topActivity.mAppCompatController.getCameraOverrides().isRefreshRequested();
}
}
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/DisplayRotationCompatPolicy.java b/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java
index 3c199db..dceacc3 100644
--- a/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java
@@ -253,10 +253,10 @@
!= lastReportedConfig.windowConfiguration.getDisplayRotation());
return isTreatmentEnabledForDisplay()
&& isTreatmentEnabledForActivity(activity)
- && activity.mAppCompatController.getAppCompatCameraOverrides()
+ && activity.mAppCompatController.getCameraOverrides()
.shouldRefreshActivityForCameraCompat()
&& (displayRotationChanged
- || activity.mAppCompatController.getAppCompatCameraOverrides()
+ || activity.mAppCompatController.getCameraOverrides()
.isCameraCompatSplitScreenAspectRatioAllowed());
}
@@ -281,7 +281,7 @@
boolean isActivityEligibleForOrientationOverride(@NonNull ActivityRecord activity) {
return isTreatmentEnabledForDisplay()
&& isCameraRunningAndWindowingModeEligible(activity, /* mustBeFullscreen */ true)
- && activity.mAppCompatController.getAppCompatCameraOverrides()
+ && activity.mAppCompatController.getCameraOverrides()
.shouldForceRotateForCameraCompat();
}
@@ -325,7 +325,7 @@
// handle dynamic changes so we shouldn't force rotate them.
&& activity.getOverrideOrientation() != SCREEN_ORIENTATION_NOSENSOR
&& activity.getOverrideOrientation() != SCREEN_ORIENTATION_LOCKED
- && activity.mAppCompatController.getAppCompatCameraOverrides()
+ && activity.mAppCompatController.getCameraOverrides()
.shouldForceRotateForCameraCompat();
}
@@ -457,14 +457,14 @@
private boolean shouldRecomputeConfigurationForCameraCompat(
@NonNull ActivityRecord activityRecord) {
final AppCompatCameraOverrides overrides = activityRecord.mAppCompatController
- .getAppCompatCameraOverrides();
+ .getCameraOverrides();
return overrides.isOverrideOrientationOnlyForCameraEnabled()
|| overrides.isCameraCompatSplitScreenAspectRatioAllowed()
|| shouldOverrideMinAspectRatio(activityRecord);
}
private boolean shouldOverrideMinAspectRatio(@NonNull ActivityRecord activityRecord) {
- return activityRecord.mAppCompatController.getAppCompatCameraOverrides()
+ return activityRecord.mAppCompatController.getCameraOverrides()
.isOverrideMinAspectRatioForCameraEnabled()
&& isCameraRunningAndWindowingModeEligible(activityRecord,
/* mustBeFullscreen= */ true);
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/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index ad19b9a..a1755e4d 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -52,6 +52,7 @@
import static android.window.WindowContainerTransaction.Change.CHANGE_FORCE_TRANSLUCENT;
import static android.window.WindowContainerTransaction.Change.CHANGE_HIDDEN;
import static android.window.WindowContainerTransaction.Change.CHANGE_RELATIVE_BOUNDS;
+import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REMOVE_ROOT_TASK;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_KEYGUARD_STATE;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_ADD_INSETS_FRAME_PROVIDER;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_ADD_TASK_FRAGMENT_OPERATION;
@@ -1131,6 +1132,23 @@
}
break;
}
+ case HIERARCHY_OP_TYPE_REMOVE_ROOT_TASK: {
+ final WindowContainer wc = WindowContainer.fromBinder(hop.getContainer());
+ if (wc == null || wc.asTask() == null || !wc.isAttached()
+ || !wc.asTask().isRootTask() || !wc.asTask().mCreatedByOrganizer) {
+ Slog.e(TAG, "Attempt to remove invalid task: " + wc);
+ break;
+ }
+ final Task task = wc.asTask();
+ if (task.isVisibleRequested() || task.isVisible()) {
+ effects |= TRANSACT_EFFECTS_LIFECYCLE;
+ }
+ // Removes its leaves, but not itself.
+ mService.mTaskSupervisor.removeRootTask(task);
+ // Now that the root has no leaves, remove it too. .
+ task.remove(true /* withTransition */, "remove-root-task-through-hierarchyOp");
+ break;
+ }
case HIERARCHY_OP_TYPE_SET_LAUNCH_ROOT: {
final WindowContainer wc = WindowContainer.fromBinder(hop.getContainer());
if (wc == null || !wc.isAttached()) {
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/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java b/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java
index 76d16e1..a81a0b3 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java
@@ -73,9 +73,6 @@
class ActiveAdmin {
- private final int userId;
- public final boolean isPermissionBased;
-
private static final String TAG_DISABLE_KEYGUARD_FEATURES = "disable-keyguard-features";
private static final String TAG_TEST_ONLY_ADMIN = "test-only-admin";
private static final String TAG_DISABLE_CAMERA = "disable-camera";
@@ -364,23 +361,8 @@
private static final int PROVISIONING_CONTEXT_LENGTH_LIMIT = 1000;
ActiveAdmin(DeviceAdminInfo info, boolean isParent) {
- this.userId = -1;
this.info = info;
this.isParent = isParent;
- this.isPermissionBased = false;
- }
-
- ActiveAdmin(int userId, boolean permissionBased) {
- if (Flags.activeAdminCleanup()) {
- throw new UnsupportedOperationException("permission based admin no longer supported");
- }
- if (permissionBased == false) {
- throw new IllegalArgumentException("Can only pass true for permissionBased admin");
- }
- this.userId = userId;
- this.isPermissionBased = permissionBased;
- this.isParent = false;
- this.info = null;
}
ActiveAdmin getParentActiveAdmin() {
@@ -397,16 +379,10 @@
}
int getUid() {
- if (isPermissionBased) {
- return -1;
- }
return info.getActivityInfo().applicationInfo.uid;
}
public UserHandle getUserHandle() {
- if (isPermissionBased) {
- return UserHandle.of(userId);
- }
return UserHandle.of(UserHandle.getUserId(info.getActivityInfo().applicationInfo.uid));
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyData.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyData.java
index c937e10..89c8b56 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyData.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyData.java
@@ -21,7 +21,6 @@
import android.annotation.UserIdInt;
import android.app.admin.DeviceAdminInfo;
import android.app.admin.DevicePolicyManager;
-import android.app.admin.flags.Flags;
import android.content.ComponentName;
import android.os.FileUtils;
import android.os.PersistableBundle;
@@ -125,24 +124,6 @@
final ArrayList<ActiveAdmin> mAdminList = new ArrayList<>();
final ArrayList<ComponentName> mRemovingAdmins = new ArrayList<>();
- /**
- * @deprecated Do not use. Policies set by permission holders must go into DevicePolicyEngine.
- */
- @Deprecated
- ActiveAdmin mPermissionBasedAdmin;
-
- // Create or get the permission-based admin. The permission-based admin will not have a
- // DeviceAdminInfo or ComponentName.
- ActiveAdmin createOrGetPermissionBasedAdmin(int userId) {
- if (Flags.activeAdminCleanup()) {
- throw new UnsupportedOperationException("permission based admin no longer supported");
- }
- if (mPermissionBasedAdmin == null) {
- mPermissionBasedAdmin = new ActiveAdmin(userId, /* permissionBased= */ true);
- }
- return mPermissionBasedAdmin;
- }
-
// TODO(b/35385311): Keep track of metadata in TrustedCertificateStore instead.
final ArraySet<String> mAcceptedCaCertificates = new ArraySet<>();
@@ -282,12 +263,6 @@
}
}
- if (!Flags.activeAdminCleanup() && policyData.mPermissionBasedAdmin != null) {
- out.startTag(null, "permission-based-admin");
- policyData.mPermissionBasedAdmin.writeToXml(out);
- out.endTag(null, "permission-based-admin");
- }
-
if (policyData.mPasswordOwner >= 0) {
out.startTag(null, "password-owner");
out.attributeInt(null, "value", policyData.mPasswordOwner);
@@ -495,7 +470,6 @@
policy.mLockTaskPackages.clear();
policy.mAdminList.clear();
policy.mAdminMap.clear();
- policy.mPermissionBasedAdmin = null;
policy.mAffiliationIds.clear();
policy.mOwnerInstalledCaCerts.clear();
policy.mUserControlDisabledPackages = null;
@@ -523,11 +497,6 @@
} catch (RuntimeException e) {
Slogf.w(TAG, e, "Failed loading admin %s", name);
}
- } else if (!Flags.activeAdminCleanup() && "permission-based-admin".equals(tag)) {
-
- ActiveAdmin ap = new ActiveAdmin(policy.mUserId, /* permissionBased= */ true);
- ap.readFromXml(parser, /* overwritePolicies= */ false);
- policy.mPermissionBasedAdmin = ap;
} else if ("delegation".equals(tag)) {
// Parse delegation info.
final String delegatePackage = parser.getAttributeValue(null,
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index e960abd..4c2c858 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -3448,8 +3448,8 @@
EnforcingAdmin enforcingAdmin =
EnforcingAdmin.createEnterpriseEnforcingAdmin(
admin.info.getComponent(),
- admin.getUserHandle().getIdentifier(),
- admin);
+ admin.getUserHandle().getIdentifier()
+ );
mDevicePolicyEngine.setGlobalPolicy(
PolicyDefinition.SECURITY_LOGGING,
enforcingAdmin,
@@ -3692,8 +3692,8 @@
int userId = admin.getUserHandle().getIdentifier();
EnforcingAdmin enforcingAdmin = EnforcingAdmin.createEnterpriseEnforcingAdmin(
admin.info.getComponent(),
- userId,
- admin);
+ userId
+ );
Integer passwordComplexity = mDevicePolicyEngine.getLocalPolicySetByAdmin(
PolicyDefinition.PASSWORD_COMPLEXITY,
@@ -3985,8 +3985,7 @@
final int N = admins.size();
for (int i = 0; i < N; i++) {
ActiveAdmin admin = admins.get(i);
- if (((!Flags.activeAdminCleanup() && admin.isPermissionBased)
- || admin.info.usesPolicy(DeviceAdminInfo.USES_POLICY_EXPIRE_PASSWORD))
+ if ((admin.info.usesPolicy(DeviceAdminInfo.USES_POLICY_EXPIRE_PASSWORD))
&& admin.passwordExpirationTimeout > 0L
&& now >= admin.passwordExpirationDate - EXPIRATION_GRACE_PERIOD_MS
&& admin.passwordExpirationDate > 0L) {
@@ -4167,10 +4166,10 @@
EnforcingAdmin oldAdmin =
EnforcingAdmin.createEnterpriseEnforcingAdmin(
- outgoingReceiver, userHandle, adminToTransfer);
+ outgoingReceiver, userHandle);
EnforcingAdmin newAdmin =
EnforcingAdmin.createEnterpriseEnforcingAdmin(
- incomingReceiver, userHandle, adminToTransfer);
+ incomingReceiver, userHandle);
mDevicePolicyEngine.transferPolicies(oldAdmin, newAdmin);
@@ -4470,7 +4469,7 @@
}
mDevicePolicyEngine.removePoliciesForAdmin(
EnforcingAdmin.createEnterpriseEnforcingAdmin(
- adminReceiver, userHandle, admin));
+ adminReceiver, userHandle));
}
private boolean canSetPasswordQualityOnParent(String packageName, final CallerIdentity caller) {
@@ -4525,10 +4524,8 @@
who, DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD, parent);
if (Flags.unmanagedModeMigration()) {
- enforcingAdmin = EnforcingAdmin.createEnterpriseEnforcingAdmin(who,
- userId,
- getActiveAdminForCallerLocked(who,
- DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD));
+ getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD);
+ enforcingAdmin = EnforcingAdmin.createEnterpriseEnforcingAdmin(who, userId);
}
// If setPasswordQuality is called on the parent, ensure that
// the primary admin does not have password complexity state (this is an
@@ -5584,17 +5581,13 @@
Preconditions.checkArgument(!calledOnParent || isProfileOwner(caller));
final ActiveAdmin activeAdmin;
- if (Flags.activeAdminCleanup()) {
- if (admin.hasAuthority(EnforcingAdmin.DPC_AUTHORITY)) {
- synchronized (getLockObject()) {
- activeAdmin = getActiveAdminUncheckedLocked(
- admin.getComponentName(), admin.getUserId());
- }
- } else {
- activeAdmin = null;
+ if (admin.hasAuthority(EnforcingAdmin.DPC_AUTHORITY)) {
+ synchronized (getLockObject()) {
+ activeAdmin = getActiveAdminUncheckedLocked(
+ admin.getComponentName(), admin.getUserId());
}
} else {
- activeAdmin = admin.getActiveAdmin();
+ activeAdmin = null;
}
// We require the caller to explicitly clear any password quality requirements set
@@ -6331,12 +6324,7 @@
caller.getPackageName(),
getAffectedUser(parent)
);
- if (Flags.activeAdminCleanup()) {
- adminComponent = enforcingAdmin.getComponentName();
- } else {
- ActiveAdmin admin = enforcingAdmin.getActiveAdmin();
- adminComponent = admin == null ? null : admin.info.getComponent();
- }
+ adminComponent = enforcingAdmin.getComponentName();
} else {
ActiveAdmin admin = getActiveAdminOrCheckPermissionForCallerLocked(
null,
@@ -7824,19 +7812,9 @@
calledByProfileOwnerOnOrgOwnedDevice, calledOnParentInstance);
}
- int userId;
- ActiveAdmin admin = null;
- if (Flags.activeAdminCleanup()) {
- userId = enforcingAdmin.getUserId();
- Slogf.i(LOG_TAG, "wipeDataWithReason(%s): admin=%s, user=%d", wipeReasonForUser,
- enforcingAdmin, userId);
- } else {
- admin = enforcingAdmin.getActiveAdmin();
- userId = admin != null ? admin.getUserHandle().getIdentifier()
- : caller.getUserId();
- Slogf.i(LOG_TAG, "wipeDataWithReason(%s): admin=%s, user=%d", wipeReasonForUser, admin,
- userId);
- }
+ int userId = enforcingAdmin.getUserId();
+ Slogf.i(LOG_TAG, "wipeDataWithReason(%s): admin=%s, user=%d", wipeReasonForUser,
+ enforcingAdmin, userId);
if (calledByProfileOwnerOnOrgOwnedDevice) {
// When wipeData is called on the parent instance, it implies wiping the entire device.
@@ -7858,38 +7836,14 @@
final String adminName;
final ComponentName adminComp;
- if (Flags.activeAdminCleanup()) {
- adminComp = enforcingAdmin.getComponentName();
- adminName = adminComp != null
- ? adminComp.flattenToShortString()
- : enforcingAdmin.getPackageName();
- event.setAdmin(enforcingAdmin.getPackageName());
- // Not including any HSUM handling here because the "else" branch in the "flag off"
- // case below is unreachable under normal circumstances and for permission-based
- // callers admin won't be null.
- } else {
- if (admin != null) {
- if (admin.isPermissionBased) {
- adminComp = null;
- adminName = caller.getPackageName();
- event.setAdmin(adminName);
- } else {
- adminComp = admin.info.getComponent();
- adminName = adminComp.flattenToShortString();
- event.setAdmin(adminComp);
- }
- } else {
- adminComp = null;
- adminName = mInjector.getPackageManager().getPackagesForUid(caller.getUid())[0];
- Slogf.i(LOG_TAG, "Logging wipeData() event admin as " + adminName);
- event.setAdmin(adminName);
- if (mInjector.userManagerIsHeadlessSystemUserMode()) {
- // On headless system user mode, the call is meant to factory reset the whole
- // device, otherwise the caller could simply remove the current user.
- userId = UserHandle.USER_SYSTEM;
- }
- }
- }
+ adminComp = enforcingAdmin.getComponentName();
+ adminName = adminComp != null
+ ? adminComp.flattenToShortString()
+ : enforcingAdmin.getPackageName();
+ event.setAdmin(enforcingAdmin.getPackageName());
+ // Not including any HSUM handling here because the "else" branch in the "flag off"
+ // case below is unreachable under normal circumstances and for permission-based
+ // callers admin won't be null.
event.write();
String internalReason = String.format(
@@ -8375,8 +8329,7 @@
List<ActiveAdmin> admins = getActiveAdminsForLockscreenPoliciesLocked(userHandle);
for (int i = 0; i < admins.size(); i++) {
ActiveAdmin admin = admins.get(i);
- if ((!Flags.activeAdminCleanup() && admin.isPermissionBased)
- || admin.info.usesPolicy(DeviceAdminInfo.USES_POLICY_EXPIRE_PASSWORD)) {
+ if (admin.info.usesPolicy(DeviceAdminInfo.USES_POLICY_EXPIRE_PASSWORD)) {
affectedUserIds.add(admin.getUserHandle().getIdentifier());
long timeout = admin.passwordExpirationTimeout;
admin.passwordExpirationDate =
@@ -8470,9 +8423,6 @@
*/
private int getUserIdToWipeForFailedPasswords(ActiveAdmin admin) {
final int userId = admin.getUserHandle().getIdentifier();
- if (!Flags.activeAdminCleanup() && admin.isPermissionBased) {
- return userId;
- }
final ComponentName component = admin.info.getComponent();
return isProfileOwnerOfOrganizationOwnedDevice(component, userId)
? getProfileParentId(userId) : userId;
@@ -10282,8 +10232,7 @@
setGlobalSettingDeviceOwnerType(DEVICE_OWNER_TYPE_DEFAULT);
mDevicePolicyEngine.removePoliciesForAdmin(
- EnforcingAdmin.createEnterpriseEnforcingAdmin(
- admin.info.getComponent(), userId, admin));
+ EnforcingAdmin.createEnterpriseEnforcingAdmin(admin.info.getComponent(), userId));
}
private void clearApplicationRestrictions(int userId) {
@@ -10433,8 +10382,7 @@
setNetworkLoggingActiveInternal(false);
mDevicePolicyEngine.removePoliciesForAdmin(
- EnforcingAdmin.createEnterpriseEnforcingAdmin(
- admin.info.getComponent(), userId, admin));
+ EnforcingAdmin.createEnterpriseEnforcingAdmin(admin.info.getComponent(), userId));
}
@Override
@@ -16449,8 +16397,7 @@
if (admin.mPasswordPolicy.quality < minPasswordQuality) {
return false;
}
- return (!Flags.activeAdminCleanup() && admin.isPermissionBased)
- || admin.info.usesPolicy(DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD);
+ return admin.info.usesPolicy(DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD);
}
@Override
@@ -20918,8 +20865,7 @@
if (profileOwner != null) {
EnforcingAdmin admin = EnforcingAdmin.createEnterpriseEnforcingAdmin(
profileOwner.info.getComponent(),
- profileUserId,
- profileOwner);
+ profileUserId);
mDevicePolicyEngine.setLocalPolicy(
PolicyDefinition.PERSONAL_APPS_SUSPENDED,
admin,
@@ -23517,27 +23463,6 @@
*
* @param callerPackageName The package name of the calling application.
* @param adminPolicy The admin policy that should grant holders permission.
- * @param permission The name of the permission being checked.
- * @param targetUserId The userId of the user which the caller needs permission to act on.
- * @throws SecurityException if the caller has not been granted the given permission,
- * the associated cross-user permission if the caller's user is different to the target user.
- */
- private void enforcePermission(String permission, int adminPolicy,
- String callerPackageName, int targetUserId) throws SecurityException {
- if (hasAdminPolicy(adminPolicy, callerPackageName)) {
- return;
- }
- enforcePermission(permission, callerPackageName, targetUserId);
- }
-
- /**
- * Checks if the calling process has been granted permission to apply a device policy on a
- * specific user.
- * The given permission will be checked along with its associated cross-user permission if it
- * exists and the target user is different to the calling user.
- *
- * @param callerPackageName The package name of the calling application.
- * @param adminPolicy The admin policy that should grant holders permission.
* @param permissions The names of the permissions being checked.
* @param targetUserId The userId of the user which the caller needs permission to act on.
* @throws SecurityException if the caller has not been granted the given permission,
@@ -23670,24 +23595,21 @@
ComponentName component;
synchronized (getLockObject()) {
if (who != null) {
- admin = getActiveAdminUncheckedLocked(who, userId);
component = who;
} else {
admin = getDeviceOrProfileOwnerAdminLocked(userId);
component = admin.info.getComponent();
}
}
- return EnforcingAdmin.createEnterpriseEnforcingAdmin(component, userId, admin);
+ return EnforcingAdmin.createEnterpriseEnforcingAdmin(component, userId);
}
- // Check for non-DPC active admins.
+ // Check for DA active admins.
admin = getActiveAdminForCaller(who, caller);
if (admin != null) {
- return EnforcingAdmin.createDeviceAdminEnforcingAdmin(admin.info.getComponent(), userId,
- admin);
+ return EnforcingAdmin.createDeviceAdminEnforcingAdmin(
+ admin.info.getComponent(), userId);
}
- admin = Flags.activeAdminCleanup()
- ? null : getUserData(userId).createOrGetPermissionBasedAdmin(userId);
- return EnforcingAdmin.createEnforcingAdmin(caller.getPackageName(), userId, admin);
+ return EnforcingAdmin.createEnforcingAdmin(caller.getPackageName(), userId);
}
private EnforcingAdmin getEnforcingAdminForPackage(@Nullable ComponentName who,
@@ -23699,19 +23621,17 @@
admin = getActiveAdminUncheckedLocked(who, userId);
}
if (admin != null) {
- return EnforcingAdmin.createEnterpriseEnforcingAdmin(who, userId, admin);
+ return EnforcingAdmin.createEnterpriseEnforcingAdmin(who, userId);
}
} else {
- // Check for non-DPC active admins.
+ // Check for DA active admins.
admin = getActiveAdminUncheckedLocked(who, userId);
if (admin != null) {
- return EnforcingAdmin.createDeviceAdminEnforcingAdmin(who, userId, admin);
+ return EnforcingAdmin.createDeviceAdminEnforcingAdmin(who, userId);
}
}
}
- admin = Flags.activeAdminCleanup()
- ? null : getUserData(userId).createOrGetPermissionBasedAdmin(userId);
- return EnforcingAdmin.createEnforcingAdmin(packageName, userId, admin);
+ return EnforcingAdmin.createEnforcingAdmin(packageName, userId);
}
private int getAffectedUser(boolean calledOnParent) {
@@ -24427,9 +24347,7 @@
&& admin.getParentActiveAdmin().disableScreenCapture))) {
EnforcingAdmin enforcingAdmin = EnforcingAdmin.createEnterpriseEnforcingAdmin(
- admin.info.getComponent(),
- admin.getUserHandle().getIdentifier(),
- admin);
+ admin.info.getComponent(), admin.getUserHandle().getIdentifier());
mDevicePolicyEngine.setGlobalPolicy(
PolicyDefinition.SCREEN_CAPTURE_DISABLED,
enforcingAdmin,
@@ -24442,8 +24360,7 @@
if (profileOwner != null && profileOwner.disableScreenCapture) {
EnforcingAdmin enforcingAdmin = EnforcingAdmin.createEnterpriseEnforcingAdmin(
profileOwner.info.getComponent(),
- profileOwner.getUserHandle().getIdentifier(),
- profileOwner);
+ profileOwner.getUserHandle().getIdentifier());
mDevicePolicyEngine.setLocalPolicy(
PolicyDefinition.SCREEN_CAPTURE_DISABLED,
enforcingAdmin,
@@ -24485,10 +24402,7 @@
private void setLockTaskPolicyInPolicyEngine(
ActiveAdmin admin, int userId, List<String> packages, int features) {
EnforcingAdmin enforcingAdmin =
- EnforcingAdmin.createEnterpriseEnforcingAdmin(
- admin.info.getComponent(),
- userId,
- admin);
+ EnforcingAdmin.createEnterpriseEnforcingAdmin(admin.info.getComponent(), userId);
mDevicePolicyEngine.setLocalPolicy(
PolicyDefinition.LOCK_TASK,
enforcingAdmin,
@@ -24503,9 +24417,7 @@
ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(userInfo.id);
if (admin != null) {
EnforcingAdmin enforcingAdmin = EnforcingAdmin.createEnterpriseEnforcingAdmin(
- admin.info.getComponent(),
- admin.getUserHandle().getIdentifier(),
- admin);
+ admin.info.getComponent(), admin.getUserHandle().getIdentifier());
if (admin.permittedInputMethods != null) {
mDevicePolicyEngine.setLocalPolicy(
PolicyDefinition.PERMITTED_INPUT_METHODS,
@@ -24536,9 +24448,7 @@
ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(userInfo.id);
if (admin != null) {
EnforcingAdmin enforcingAdmin = EnforcingAdmin.createEnterpriseEnforcingAdmin(
- admin.info.getComponent(),
- admin.getUserHandle().getIdentifier(),
- admin);
+ admin.info.getComponent(), admin.getUserHandle().getIdentifier());
for (String accountType : admin.accountTypesWithManagementDisabled) {
mDevicePolicyEngine.setLocalPolicy(
PolicyDefinition.ACCOUNT_MANAGEMENT_DISABLED(accountType),
@@ -24569,9 +24479,7 @@
ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(userInfo.id);
if (admin != null && admin.protectedPackages != null) {
EnforcingAdmin enforcingAdmin = EnforcingAdmin.createEnterpriseEnforcingAdmin(
- admin.info.getComponent(),
- admin.getUserHandle().getIdentifier(),
- admin);
+ admin.info.getComponent(), admin.getUserHandle().getIdentifier());
if (isDeviceOwner(admin)) {
mDevicePolicyEngine.setGlobalPolicy(
PolicyDefinition.USER_CONTROLLED_DISABLED_PACKAGES,
@@ -24599,10 +24507,8 @@
if (admin == null) continue;
ComponentName adminComponent = admin.info.getComponent();
int userId = userInfo.id;
- EnforcingAdmin enforcingAdmin = EnforcingAdmin.createEnterpriseEnforcingAdmin(
- adminComponent,
- userId,
- admin);
+ EnforcingAdmin enforcingAdmin =
+ EnforcingAdmin.createEnterpriseEnforcingAdmin(adminComponent, userId);
int ownerType;
if (isDeviceOwner(admin)) {
ownerType = OWNER_TYPE_DEVICE_OWNER;
@@ -24635,9 +24541,7 @@
ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(userInfo.id);
if (admin == null) continue;
EnforcingAdmin enforcingAdmin = EnforcingAdmin.createEnterpriseEnforcingAdmin(
- admin.info.getComponent(),
- userInfo.id,
- admin);
+ admin.info.getComponent(), userInfo.id);
runner.accept(admin, enforcingAdmin);
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/EnforcingAdmin.java b/services/devicepolicy/java/com/android/server/devicepolicy/EnforcingAdmin.java
index 5a0b079..aca3315 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/EnforcingAdmin.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/EnforcingAdmin.java
@@ -23,7 +23,6 @@
import android.app.admin.DpcAuthority;
import android.app.admin.RoleAuthority;
import android.app.admin.UnknownAuthority;
-import android.app.admin.flags.Flags;
import android.content.ComponentName;
import android.os.UserHandle;
@@ -80,36 +79,24 @@
private final int mUserId;
private final boolean mIsRoleAuthority;
private final boolean mIsSystemAuthority;
- private final ActiveAdmin mActiveAdmin;
- static EnforcingAdmin createEnforcingAdmin(@NonNull String packageName, int userId,
- ActiveAdmin admin) {
+ static EnforcingAdmin createEnforcingAdmin(@NonNull String packageName, int userId) {
Objects.requireNonNull(packageName);
- return new EnforcingAdmin(packageName, userId, admin);
+ return new EnforcingAdmin(packageName, userId);
}
static EnforcingAdmin createEnterpriseEnforcingAdmin(
@NonNull ComponentName componentName, int userId) {
Objects.requireNonNull(componentName);
return new EnforcingAdmin(
- componentName.getPackageName(), componentName, Set.of(DPC_AUTHORITY), userId,
- /* activeAdmin=*/ null);
+ componentName.getPackageName(), componentName, Set.of(DPC_AUTHORITY), userId);
}
- static EnforcingAdmin createEnterpriseEnforcingAdmin(
- @NonNull ComponentName componentName, int userId, ActiveAdmin activeAdmin) {
- Objects.requireNonNull(componentName);
- return new EnforcingAdmin(
- componentName.getPackageName(), componentName, Set.of(DPC_AUTHORITY), userId,
- activeAdmin);
- }
-
- static EnforcingAdmin createDeviceAdminEnforcingAdmin(ComponentName componentName, int userId,
- ActiveAdmin activeAdmin) {
+ static EnforcingAdmin createDeviceAdminEnforcingAdmin(ComponentName componentName, int userId) {
Objects.requireNonNull(componentName);
return new EnforcingAdmin(
componentName.getPackageName(), componentName, Set.of(DEVICE_ADMIN_AUTHORITY),
- userId, activeAdmin);
+ userId);
}
static EnforcingAdmin createSystemEnforcingAdmin(@NonNull String systemEntity) {
@@ -124,24 +111,20 @@
if (DpcAuthority.DPC_AUTHORITY.equals(authority)) {
return new EnforcingAdmin(
admin.getPackageName(), admin.getComponentName(),
- Set.of(DPC_AUTHORITY), admin.getUserHandle().getIdentifier(),
- /* activeAdmin = */ null);
+ Set.of(DPC_AUTHORITY), admin.getUserHandle().getIdentifier());
} else if (DeviceAdminAuthority.DEVICE_ADMIN_AUTHORITY.equals(authority)) {
return new EnforcingAdmin(
admin.getPackageName(), admin.getComponentName(),
- Set.of(DEVICE_ADMIN_AUTHORITY), admin.getUserHandle().getIdentifier(),
- /* activeAdmin = */ null);
+ Set.of(DEVICE_ADMIN_AUTHORITY), admin.getUserHandle().getIdentifier());
} else if (authority instanceof RoleAuthority roleAuthority) {
return new EnforcingAdmin(
admin.getPackageName(), admin.getComponentName(),
Set.of(DEVICE_ADMIN_AUTHORITY), admin.getUserHandle().getIdentifier(),
- /* activeAdmin = */ null,
/* isRoleAuthority = */ true);
}
// TODO(b/324899199): Consider supporting android.app.admin.SystemAuthority.
return new EnforcingAdmin(admin.getPackageName(), admin.getComponentName(),
- Set.of(), admin.getUserHandle().getIdentifier(),
- /* activeAdmin = */ null);
+ Set.of(), admin.getUserHandle().getIdentifier());
}
static String getRoleAuthorityOf(String roleName) {
@@ -167,7 +150,7 @@
private EnforcingAdmin(
String packageName, @Nullable ComponentName componentName, Set<String> authorities,
- int userId, @Nullable ActiveAdmin activeAdmin) {
+ int userId) {
Objects.requireNonNull(packageName);
Objects.requireNonNull(authorities);
@@ -179,10 +162,9 @@
mComponentName = componentName;
mAuthorities = new HashSet<>(authorities);
mUserId = userId;
- mActiveAdmin = activeAdmin;
}
- private EnforcingAdmin(String packageName, int userId, ActiveAdmin activeAdmin) {
+ private EnforcingAdmin(String packageName, int userId) {
Objects.requireNonNull(packageName);
// Only role authorities use this constructor.
@@ -194,7 +176,6 @@
mComponentName = null;
// authorities will be loaded when needed
mAuthorities = null;
- mActiveAdmin = activeAdmin;
}
/** Constructor for System authorities. */
@@ -210,12 +191,11 @@
mUserId = UserHandle.USER_SYSTEM;
mComponentName = null;
mAuthorities = getSystemAuthority(systemEntity);
- mActiveAdmin = null;
}
private EnforcingAdmin(
String packageName, @Nullable ComponentName componentName, Set<String> authorities,
- int userId, @Nullable ActiveAdmin activeAdmin, boolean isRoleAuthority) {
+ int userId, boolean isRoleAuthority) {
Objects.requireNonNull(packageName);
Objects.requireNonNull(authorities);
@@ -226,7 +206,6 @@
mComponentName = componentName;
mAuthorities = new HashSet<>(authorities);
mUserId = userId;
- mActiveAdmin = activeAdmin;
}
private static Set<String> getRoleAuthoritiesOrDefault(String packageName, int userId) {
@@ -295,14 +274,6 @@
}
@Nullable
- public ActiveAdmin getActiveAdmin() {
- if (Flags.activeAdminCleanup()) {
- throw new UnsupportedOperationException("getActiveAdmin() no longer supported");
- }
- return mActiveAdmin;
- }
-
- @Nullable
ComponentName getComponentName() {
return mComponentName;
}
@@ -419,7 +390,7 @@
return null;
}
// TODO(b/281697976): load active admin
- return new EnforcingAdmin(packageName, userId, null);
+ return new EnforcingAdmin(packageName, userId);
} else if (isSystemAuthority) {
if (systemEntity == null) {
Slogf.wtf(TAG, "Error parsing EnforcingAdmin with SystemAuthority, "
@@ -439,7 +410,7 @@
? null : new ComponentName(packageName, className);
Set<String> authorities = Set.of(authoritiesStr.split(ATTR_AUTHORITIES_SEPARATOR));
// TODO(b/281697976): load active admin
- return new EnforcingAdmin(packageName, componentName, authorities, userId, null);
+ return new EnforcingAdmin(packageName, componentName, authorities, userId);
}
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
index 02e5470..e8b28ac 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
@@ -23,6 +23,7 @@
import static android.Manifest.permission.CONTROL_DISPLAY_BRIGHTNESS;
import static android.Manifest.permission.MANAGE_DISPLAYS;
import static android.Manifest.permission.MODIFY_USER_PREFERRED_DISPLAY_MODE;
+import static android.app.ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND;
import static android.hardware.display.DisplayManager.SWITCHING_TYPE_NONE;
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED;
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR;
@@ -2123,16 +2124,14 @@
}
}
- /**
- * Tests that there is a display change notification if the frame rate override
- * list is updated.
- */
@Test
- public void testShouldNotifyChangeWhenDisplayInfoFrameRateOverrideChanged() {
+ public void test_displayChangedNotified_displayInfoFramerateOverridden() {
DisplayManagerService displayManager =
new DisplayManagerService(mContext, mShortMockedInjector);
DisplayManagerService.BinderService displayManagerBinderService =
displayManager.new BinderService();
+ when(mMockFlags.isFramerateOverrideTriggersRrCallbacksEnabled()).thenReturn(false);
+
registerDefaultDisplays(displayManager);
displayManager.onBootPhase(SystemService.PHASE_WAIT_FOR_DEFAULT_DISPLAY);
@@ -2148,6 +2147,35 @@
waitForIdleHandler(displayManager.getDisplayHandler());
assertThat(callback.receivedEvents()).contains(EVENT_DISPLAY_BASIC_CHANGED);
callback.clear();
+ }
+
+ /**
+ * Tests that there is a display change notification if the frame rate override
+ * list is updated.
+ */
+ @Test
+ public void test_refreshRateChangedNotified_displayInfoFramerateOverridden() {
+ when(mMockFlags.isFramerateOverrideTriggersRrCallbacksEnabled()).thenReturn(true);
+
+ DisplayManagerService displayManager =
+ new DisplayManagerService(mContext, mBasicInjector);
+ DisplayManagerService.BinderService displayManagerBinderService =
+ displayManager.new BinderService();
+ registerDefaultDisplays(displayManager);
+ displayManager.onBootPhase(SystemService.PHASE_WAIT_FOR_DEFAULT_DISPLAY);
+
+ FakeDisplayDevice displayDevice = createFakeDisplayDevice(displayManager, new float[]{60f});
+ FakeDisplayManagerCallback callback = registerDisplayListenerCallback(displayManager,
+ displayManagerBinderService, displayDevice);
+
+ int myUid = Process.myUid();
+ updateFrameRateOverride(displayManager, displayDevice,
+ new DisplayEventReceiver.FrameRateOverride[]{
+ new DisplayEventReceiver.FrameRateOverride(myUid, 30f),
+ });
+ waitForIdleHandler(displayManager.getDisplayHandler());
+ assertThat(callback.receivedEvents()).contains(EVENT_DISPLAY_REFRESH_RATE_CHANGED);
+ callback.clear();
updateFrameRateOverride(displayManager, displayDevice,
new DisplayEventReceiver.FrameRateOverride[]{
@@ -2155,7 +2183,7 @@
new DisplayEventReceiver.FrameRateOverride(1234, 30f),
});
waitForIdleHandler(displayManager.getDisplayHandler());
- assertThat(callback.receivedEvents()).doesNotContain(EVENT_DISPLAY_BASIC_CHANGED);
+ assertThat(callback.receivedEvents()).doesNotContain(EVENT_DISPLAY_REFRESH_RATE_CHANGED);
updateFrameRateOverride(displayManager, displayDevice,
new DisplayEventReceiver.FrameRateOverride[]{
@@ -2164,7 +2192,7 @@
new DisplayEventReceiver.FrameRateOverride(5678, 30f),
});
waitForIdleHandler(displayManager.getDisplayHandler());
- assertThat(callback.receivedEvents()).contains(EVENT_DISPLAY_BASIC_CHANGED);
+ assertThat(callback.receivedEvents()).contains(EVENT_DISPLAY_REFRESH_RATE_CHANGED);
callback.clear();
updateFrameRateOverride(displayManager, displayDevice,
@@ -2173,7 +2201,7 @@
new DisplayEventReceiver.FrameRateOverride(5678, 30f),
});
waitForIdleHandler(displayManager.getDisplayHandler());
- assertThat(callback.receivedEvents()).contains(EVENT_DISPLAY_BASIC_CHANGED);
+ assertThat(callback.receivedEvents()).contains(EVENT_DISPLAY_REFRESH_RATE_CHANGED);
callback.clear();
updateFrameRateOverride(displayManager, displayDevice,
@@ -2181,7 +2209,7 @@
new DisplayEventReceiver.FrameRateOverride(5678, 30f),
});
waitForIdleHandler(displayManager.getDisplayHandler());
- assertThat(callback.receivedEvents()).doesNotContain(EVENT_DISPLAY_BASIC_CHANGED);
+ assertThat(callback.receivedEvents()).doesNotContain(EVENT_DISPLAY_REFRESH_RATE_CHANGED);
}
/**
@@ -2317,6 +2345,29 @@
callback.clear();
}
+ @Test
+ public void test_doesNotNotifyRefreshRateChanged_whenAppInBackground() {
+ when(mMockFlags.isRefreshRateEventForForegroundAppsEnabled()).thenReturn(true);
+ DisplayManagerService displayManager =
+ new DisplayManagerService(mContext, mBasicInjector);
+ DisplayManagerService.BinderService displayManagerBinderService =
+ displayManager.new BinderService();
+ displayManager.windowManagerAndInputReady();
+ registerDefaultDisplays(displayManager);
+ displayManager.onBootPhase(SystemService.PHASE_WAIT_FOR_DEFAULT_DISPLAY);
+
+ FakeDisplayDevice displayDevice = createFakeDisplayDevice(displayManager, new float[]{60f});
+ FakeDisplayManagerCallback callback = registerDisplayListenerCallback(displayManager,
+ displayManagerBinderService, displayDevice);
+
+ when(mMockActivityManagerInternal.getUidProcessState(Process.myUid()))
+ .thenReturn(PROCESS_STATE_TRANSIENT_BACKGROUND);
+ updateRenderFrameRate(displayManager, displayDevice, 30f);
+ waitForIdleHandler(displayManager.getDisplayHandler());
+ assertEquals(0, callback.receivedEvents().size());
+ callback.clear();
+ }
+
/**
* Tests that the DisplayInfo is updated correctly with a render frame rate
*/
diff --git a/services/tests/mockingservicestests/src/com/android/server/power/ScreenUndimDetectorTest.java b/services/tests/mockingservicestests/src/com/android/server/power/ScreenUndimDetectorTest.java
index e8e1dac..8ce05e2 100644
--- a/services/tests/mockingservicestests/src/com/android/server/power/ScreenUndimDetectorTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/power/ScreenUndimDetectorTest.java
@@ -40,6 +40,7 @@
import org.junit.After;
import org.junit.Before;
+import org.junit.Ignore;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
@@ -329,6 +330,7 @@
}
}
+ @Ignore("b/387389929")
@Test
public void recordScreenPolicy_otherTransitions_doesNotReset() {
DeviceConfig.setProperty(NAMESPACE_ATTENTION_MANAGER_SERVICE,
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/servicestests/src/com/android/server/statusbar/StatusBarManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/statusbar/StatusBarManagerServiceTest.java
index 148c968..263ada8 100644
--- a/services/tests/servicestests/src/com/android/server/statusbar/StatusBarManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/statusbar/StatusBarManagerServiceTest.java
@@ -69,6 +69,7 @@
import android.os.Looper;
import android.os.RemoteException;
import android.os.UserHandle;
+import android.platform.test.annotations.EnableFlags;
import android.service.quicksettings.TileService;
import android.testing.TestableContext;
@@ -79,6 +80,7 @@
import com.android.server.LocalServices;
import com.android.server.policy.GlobalActionsProvider;
import com.android.server.wm.ActivityTaskManagerInternal;
+import com.android.systemui.shared.Flags;
import libcore.junit.util.compat.CoreCompatChangeRule;
@@ -105,6 +107,7 @@
TEST_SERVICE);
private static final CharSequence APP_NAME = "AppName";
private static final CharSequence TILE_LABEL = "Tile label";
+ private static final int SECONDARY_DISPLAY_ID = 2;
@Rule
public final TestableContext mContext =
@@ -749,6 +752,29 @@
}
@Test
+ @EnableFlags(Flags.FLAG_STATUS_BAR_CONNECTED_DISPLAYS)
+ public void testDisableForAllDisplays() throws Exception {
+ int user1Id = 0;
+ mockUidCheck();
+ mockCurrentUserCheck(user1Id);
+
+ mStatusBarManagerService.onDisplayAdded(SECONDARY_DISPLAY_ID);
+
+ int expectedFlags = DISABLE_MASK & DISABLE_BACK;
+ String pkg = mContext.getPackageName();
+
+ // before disabling
+ assertEquals(DISABLE_NONE,
+ mStatusBarManagerService.getDisableFlags(mMockStatusBar, user1Id)[0]);
+
+ // disable
+ mStatusBarManagerService.disable(expectedFlags, mMockStatusBar, pkg);
+
+ verify(mMockStatusBar).disable(0, expectedFlags, 0);
+ verify(mMockStatusBar).disable(SECONDARY_DISPLAY_ID, expectedFlags, 0);
+ }
+
+ @Test
public void testSetHomeDisabled() throws Exception {
int expectedFlags = DISABLE_MASK & DISABLE_HOME;
String pkg = mContext.getPackageName();
@@ -851,6 +877,29 @@
}
@Test
+ @EnableFlags(Flags.FLAG_STATUS_BAR_CONNECTED_DISPLAYS)
+ public void testDisable2ForAllDisplays() throws Exception {
+ int user1Id = 0;
+ mockUidCheck();
+ mockCurrentUserCheck(user1Id);
+
+ mStatusBarManagerService.onDisplayAdded(SECONDARY_DISPLAY_ID);
+
+ int expectedFlags = DISABLE2_MASK & DISABLE2_NOTIFICATION_SHADE;
+ String pkg = mContext.getPackageName();
+
+ // before disabling
+ assertEquals(DISABLE_NONE,
+ mStatusBarManagerService.getDisableFlags(mMockStatusBar, user1Id)[0]);
+
+ // disable
+ mStatusBarManagerService.disable2(expectedFlags, mMockStatusBar, pkg);
+
+ verify(mMockStatusBar).disable(0, 0, expectedFlags);
+ verify(mMockStatusBar).disable(SECONDARY_DISPLAY_ID, 0, expectedFlags);
+ }
+
+ @Test
public void testSetQuickSettingsDisabled2() throws Exception {
int expectedFlags = DISABLE2_MASK & DISABLE2_QUICK_SETTINGS;
String pkg = mContext.getPackageName();
@@ -1092,6 +1141,7 @@
// disable
mStatusBarManagerService.disableForUser(expectedUser1Flags, mMockStatusBar, pkg, user1Id);
mStatusBarManagerService.disableForUser(expectedUser2Flags, mMockStatusBar, pkg, user2Id);
+
// check that right flag is disabled
assertEquals(expectedUser1Flags,
mStatusBarManagerService.getDisableFlags(mMockStatusBar, user1Id)[0]);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java
index d2f8d14..d1afa38 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java
@@ -17,6 +17,7 @@
import static android.os.UserHandle.USER_ALL;
import static android.service.notification.Adjustment.KEY_IMPORTANCE;
+import static android.service.notification.Adjustment.KEY_TYPE;
import static android.service.notification.Adjustment.TYPE_CONTENT_RECOMMENDATION;
import static android.service.notification.Adjustment.TYPE_NEWS;
import static android.service.notification.Adjustment.TYPE_PROMOTION;
@@ -165,6 +166,7 @@
mContext.getOrCreateTestableResources().addOverride(
com.android.internal.R.string.config_defaultAssistantAccessComponent,
mCn.flattenToString());
+ mNm.mDefaultUnsupportedAdjustments = new String[] {};
mAssistants = spy(mNm.new NotificationAssistants(mContext, mLock, mUserProfiles, miPm));
when(mNm.getBinderService()).thenReturn(mINm);
mContext.ensureTestableResources();
@@ -660,7 +662,7 @@
mAssistants.disallowAdjustmentType(Adjustment.KEY_RANKING_SCORE);
assertThat(mAssistants.getAllowedAssistantAdjustments())
.doesNotContain(Adjustment.KEY_RANKING_SCORE);
- assertThat(mAssistants.getAllowedAssistantAdjustments()).contains(Adjustment.KEY_TYPE);
+ assertThat(mAssistants.getAllowedAssistantAdjustments()).contains(KEY_TYPE);
}
@Test
@@ -856,7 +858,7 @@
mAssistants.new ManagedServiceInfo(null, mCn, userId, false, null, 35, 2345256);
// Ensure bundling is enabled
- mAssistants.setAdjustmentTypeSupportedState(info, Adjustment.KEY_TYPE, true);
+ mAssistants.setAdjustmentTypeSupportedState(info, KEY_TYPE, true);
// Enable these specific bundle types
mAssistants.setAssistantAdjustmentKeyTypeState(TYPE_PROMOTION, false);
mAssistants.setAssistantAdjustmentKeyTypeState(TYPE_NEWS, true);
@@ -890,7 +892,7 @@
.isEqualTo(NotificationProtoEnums.TYPE_CONTENT_RECOMMENDATION);
// Disable the top-level bundling setting
- mAssistants.setAdjustmentTypeSupportedState(info, Adjustment.KEY_TYPE, false);
+ mAssistants.setAdjustmentTypeSupportedState(info, KEY_TYPE, false);
// Enable these specific bundle types
mAssistants.setAssistantAdjustmentKeyTypeState(TYPE_PROMOTION, true);
mAssistants.setAssistantAdjustmentKeyTypeState(TYPE_NEWS, false);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 85c5920..4330226 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -790,6 +790,8 @@
TestableResources tr = mContext.getOrCreateTestableResources();
tr.addOverride(com.android.internal.R.string.config_defaultSearchSelectorPackageName,
SEARCH_SELECTOR_PKG);
+ tr.addOverride(R.array.config_notificationDefaultUnsupportedAdjustments,
+ new String[] {KEY_TYPE});
doAnswer(invocation -> {
mOnPermissionChangeListener = invocation.getArgument(2);
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/ActivityRefresherTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRefresherTests.java
index 63dafcd..c667d76 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRefresherTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRefresherTests.java
@@ -103,7 +103,7 @@
@Test
public void testShouldRefreshActivity_refreshDisabledForActivity() throws Exception {
configureActivityAndDisplay();
- when(mActivity.mAppCompatController.getAppCompatCameraOverrides()
+ when(mActivity.mAppCompatController.getCameraOverrides()
.shouldRefreshActivityForCameraCompat()).thenReturn(false);
mActivityRefresher.addEvaluator(mEvaluatorTrue);
@@ -161,7 +161,7 @@
configureActivityAndDisplay();
mActivityRefresher.addEvaluator(mEvaluatorTrue);
doReturn(true)
- .when(mActivity.mAppCompatController.getAppCompatCameraOverrides())
+ .when(mActivity.mAppCompatController.getCameraOverrides())
.shouldRefreshActivityViaPauseForCameraCompat();
mActivityRefresher.onActivityConfigurationChanging(mActivity, mNewConfig, mOldConfig);
@@ -174,7 +174,7 @@
configureActivityAndDisplay();
mActivityRefresher.addEvaluator(mEvaluatorTrue);
doReturn(true)
- .when(mActivity.mAppCompatController.getAppCompatCameraOverrides())
+ .when(mActivity.mAppCompatController.getCameraOverrides())
.shouldRefreshActivityViaPauseForCameraCompat();
mActivityRefresher.onActivityRefreshed(mActivity);
@@ -188,7 +188,7 @@
private void assertActivityRefreshRequested(boolean refreshRequested,
boolean cycleThroughStop) throws Exception {
- verify(mActivity.mAppCompatController.getAppCompatCameraOverrides(),
+ verify(mActivity.mAppCompatController.getCameraOverrides(),
times(refreshRequested ? 1 : 0)).setIsRefreshRequested(true);
final RefreshCallbackItem refreshCallbackItem =
@@ -212,9 +212,9 @@
.build()
.getTopMostActivity();
- spyOn(mActivity.mAppCompatController.getAppCompatCameraOverrides());
+ spyOn(mActivity.mAppCompatController.getCameraOverrides());
doReturn(true).when(mActivity).inFreeformWindowingMode();
doReturn(true).when(mActivity.mAppCompatController
- .getAppCompatCameraOverrides()).shouldRefreshActivityForCameraCompat();
+ .getCameraOverrides()).shouldRefreshActivityForCameraCompat();
}
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraOverridesTest.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraOverridesTest.java
index 1d138e4..4ad1cd1 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraOverridesTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraOverridesTest.java
@@ -393,7 +393,7 @@
}
private AppCompatCameraOverrides getAppCompatCameraOverrides() {
- return activity().top().mAppCompatController.getAppCompatCameraOverrides();
+ return activity().top().mAppCompatController.getCameraOverrides();
}
}
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/CameraCompatFreeformPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/CameraCompatFreeformPolicyTests.java
index 576d17a..716f864 100644
--- a/services/tests/wmtests/src/com/android/server/wm/CameraCompatFreeformPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/CameraCompatFreeformPolicyTests.java
@@ -401,7 +401,7 @@
throws Exception {
configureActivity(SCREEN_ORIENTATION_PORTRAIT);
- doReturn(false).when(mActivity.mAppCompatController.getAppCompatCameraOverrides())
+ doReturn(false).when(mActivity.mAppCompatController.getCameraOverrides())
.shouldRefreshActivityForCameraCompat();
mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
@@ -431,7 +431,7 @@
public void testOnActivityConfigurationChanging_cycleThroughStopDisabledForApp()
throws Exception {
configureActivity(SCREEN_ORIENTATION_PORTRAIT);
- doReturn(true).when(mActivity.mAppCompatController.getAppCompatCameraOverrides())
+ doReturn(true).when(mActivity.mAppCompatController.getCameraOverrides())
.shouldRefreshActivityViaPauseForCameraCompat();
mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
@@ -477,7 +477,7 @@
configureActivity(SCREEN_ORIENTATION_PORTRAIT);
final float configAspectRatio = 1.5f;
mWm.mAppCompatConfiguration.setCameraCompatAspectRatio(configAspectRatio);
- doReturn(true).when(mActivity.mAppCompatController.getAppCompatCameraOverrides())
+ doReturn(true).when(mActivity.mAppCompatController.getCameraOverrides())
.isOverrideMinAspectRatioForCameraEnabled();
mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
@@ -608,7 +608,7 @@
.build();
mActivity.mAppCompatController.getSizeCompatModePolicy().clearSizeCompatMode();
- spyOn(mActivity.mAppCompatController.getAppCompatCameraOverrides());
+ spyOn(mActivity.mAppCompatController.getCameraOverrides());
spyOn(mActivity.info);
doReturn(mActivity).when(mDisplayContent).topRunningActivity(anyBoolean());
@@ -630,7 +630,7 @@
private void assertActivityRefreshRequested(boolean refreshRequested,
boolean cycleThroughStop) throws Exception {
- verify(mActivity.mAppCompatController.getAppCompatCameraOverrides(),
+ verify(mActivity.mAppCompatController.getCameraOverrides(),
times(refreshRequested ? 1 : 0)).setIsRefreshRequested(true);
final RefreshCallbackItem refreshCallbackItem =
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/DisplayRotationCompatPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java
index 23c767c..02b796f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java
@@ -267,7 +267,7 @@
public void testTreatmentDisabledPerApp_noForceRotationOrRefresh()
throws Exception {
configureActivity(SCREEN_ORIENTATION_PORTRAIT);
- doReturn(false).when(mActivity.mAppCompatController.getAppCompatCameraOverrides())
+ doReturn(false).when(mActivity.mAppCompatController.getCameraOverrides())
.shouldForceRotateForCameraCompat();
mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
@@ -469,7 +469,7 @@
throws Exception {
configureActivity(SCREEN_ORIENTATION_PORTRAIT);
- doReturn(false).when(mActivity.mAppCompatController.getAppCompatCameraOverrides())
+ doReturn(false).when(mActivity.mAppCompatController.getCameraOverrides())
.shouldRefreshActivityForCameraCompat();
mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
@@ -496,7 +496,7 @@
throws Exception {
configureActivity(SCREEN_ORIENTATION_PORTRAIT);
doReturn(false).when(mActivity
- .mAppCompatController.getAppCompatCameraOverrides())
+ .mAppCompatController.getCameraOverrides())
.isCameraCompatSplitScreenAspectRatioAllowed();
mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
@@ -509,7 +509,7 @@
public void testOnActivityConfigurationChanging_splitScreenAspectRatioAllowed_refresh()
throws Exception {
configureActivity(SCREEN_ORIENTATION_PORTRAIT);
- doReturn(true).when(mActivity.mAppCompatController.getAppCompatCameraOverrides())
+ doReturn(true).when(mActivity.mAppCompatController.getCameraOverrides())
.isCameraCompatSplitScreenAspectRatioAllowed();
mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
@@ -535,7 +535,7 @@
public void testOnActivityConfigurationChanging_cycleThroughStopDisabledForApp()
throws Exception {
configureActivity(SCREEN_ORIENTATION_PORTRAIT);
- doReturn(true).when(mActivity.mAppCompatController.getAppCompatCameraOverrides())
+ doReturn(true).when(mActivity.mAppCompatController.getCameraOverrides())
.shouldRefreshActivityViaPauseForCameraCompat();
mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
@@ -599,7 +599,7 @@
ActivityInfo.UNIVERSAL_RESIZABLE_BY_DEFAULT);
spyOn(mActivity.mAtmService.getLifecycleManager());
- spyOn(mActivity.mAppCompatController.getAppCompatCameraOverrides());
+ spyOn(mActivity.mAppCompatController.getCameraOverrides());
doReturn(mActivity).when(mDisplayContent).topRunningActivity(anyBoolean());
doReturn(naturalOrientation).when(mDisplayContent).getNaturalOrientation();
@@ -611,7 +611,7 @@
private void assertActivityRefreshRequested(boolean refreshRequested,
boolean cycleThroughStop) throws Exception {
- verify(mActivity.mAppCompatController.getAppCompatCameraOverrides(),
+ verify(mActivity.mAppCompatController.getCameraOverrides(),
times(refreshRequested ? 1 : 0)).setIsRefreshRequested(true);
final RefreshCallbackItem refreshCallbackItem =
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index a84b374..2630565 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -4683,7 +4683,8 @@
prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT);
- assertFalse(mActivity.isEligibleForLetterboxEducation());
+ assertFalse(mActivity.mAppCompatController.getAppCompatLetterboxPolicy()
+ .isEligibleForLetterboxEducation());
}
@Test
@@ -4694,7 +4695,8 @@
prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT);
- assertFalse(mActivity.isEligibleForLetterboxEducation());
+ assertFalse(mActivity.mAppCompatController.getAppCompatLetterboxPolicy()
+ .isEligibleForLetterboxEducation());
}
@Test
@@ -4716,7 +4718,8 @@
false /*moveParents*/, "test");
organizer.mPrimary.setBounds(0, 0, 1000, 600);
- assertFalse(mActivity.isEligibleForLetterboxEducation());
+ assertFalse(mActivity.mAppCompatController.getAppCompatLetterboxPolicy()
+ .isEligibleForLetterboxEducation());
assertEquals(WINDOWING_MODE_MULTI_WINDOW, mActivity.getWindowingMode());
}
@@ -4728,7 +4731,8 @@
prepareUnresizable(mActivity, SCREEN_ORIENTATION_LANDSCAPE);
- assertFalse(mActivity.isEligibleForLetterboxEducation());
+ assertFalse(mActivity.mAppCompatController.getAppCompatLetterboxPolicy()
+ .isEligibleForLetterboxEducation());
assertTrue(mActivity.mAppCompatController.getAspectRatioPolicy()
.isLetterboxedForFixedOrientationAndAspectRatio());
}
@@ -4745,14 +4749,16 @@
createWindowState(new WindowManager.LayoutParams(TYPE_APPLICATION_STARTING),
mActivity));
- assertFalse(mActivity.isEligibleForLetterboxEducation());
+ assertFalse(mActivity.mAppCompatController.getAppCompatLetterboxPolicy()
+ .isEligibleForLetterboxEducation());
// Verify that after removing the starting window isEligibleForLetterboxEducation returns
// true and mTask.dispatchTaskInfoChangedIfNeeded is called.
spyOn(mTask);
mActivity.removeStartingWindow();
- assertTrue(mActivity.isEligibleForLetterboxEducation());
+ assertTrue(mActivity.mAppCompatController.getAppCompatLetterboxPolicy()
+ .isEligibleForLetterboxEducation());
verify(mTask).dispatchTaskInfoChangedIfNeeded(true);
}
@@ -4768,14 +4774,16 @@
createWindowState(new WindowManager.LayoutParams(TYPE_APPLICATION_STARTING),
mActivity));
- assertFalse(mActivity.isEligibleForLetterboxEducation());
+ assertFalse(mActivity.mAppCompatController.getAppCompatLetterboxPolicy()
+ .isEligibleForLetterboxEducation());
// Verify that after removing the starting window isEligibleForLetterboxEducation still
// returns false and mTask.dispatchTaskInfoChangedIfNeeded isn't called.
spyOn(mTask);
mActivity.removeStartingWindow();
- assertFalse(mActivity.isEligibleForLetterboxEducation());
+ assertFalse(mActivity.mAppCompatController.getAppCompatLetterboxPolicy()
+ .isEligibleForLetterboxEducation());
verify(mTask, never()).dispatchTaskInfoChangedIfNeeded(true);
}
@@ -4787,7 +4795,8 @@
prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT);
- assertTrue(mActivity.isEligibleForLetterboxEducation());
+ assertTrue(mActivity.mAppCompatController.getAppCompatLetterboxPolicy()
+ .isEligibleForLetterboxEducation());
assertTrue(mActivity.mAppCompatController.getAspectRatioPolicy()
.isLetterboxedForFixedOrientationAndAspectRatio());
}
@@ -4802,7 +4811,8 @@
rotateDisplay(mActivity.mDisplayContent, ROTATION_90);
- assertTrue(mActivity.isEligibleForLetterboxEducation());
+ assertTrue(mActivity.mAppCompatController.getAppCompatLetterboxPolicy()
+ .isEligibleForLetterboxEducation());
assertFalse(mActivity.mAppCompatController.getAspectRatioPolicy()
.isLetterboxedForFixedOrientationAndAspectRatio());
assertTrue(mActivity.inSizeCompatMode());
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTransactionTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTransactionTests.java
index e9ece5d..369600c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTransactionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTransactionTests.java
@@ -79,6 +79,34 @@
}
@Test
+ public void testRemoveRootTask() {
+ final Task rootTask = createTask(mDisplayContent);
+ final Task task = createTaskInRootTask(rootTask, 0 /* userId */);
+ final ActivityRecord activity = createActivityRecord(mDisplayContent, task);
+ final TaskDisplayArea taskDisplayArea = mDisplayContent.getDefaultTaskDisplayArea();
+
+ WindowContainerTransaction wct = new WindowContainerTransaction();
+ WindowContainerToken token = rootTask.getTaskInfo().token;
+ wct.removeTask(token);
+ applyTransaction(wct);
+
+ // There is still an activity to be destroyed, so the task is not removed immediately.
+ assertNotNull(task.getParent());
+ assertTrue(rootTask.hasChild());
+ assertTrue(task.hasChild());
+ assertTrue(activity.finishing);
+
+ activity.destroyed("testRemoveRootTask");
+ // Assert that the container was removed after the activity is destroyed.
+ assertNull(task.getParent());
+ assertEquals(0, task.getChildCount());
+ assertNull(activity.getParent());
+ assertNull(taskDisplayArea.getTask(task1 -> task1.mTaskId == rootTask.mTaskId));
+ verify(mAtm.getLockTaskController(), atLeast(1)).clearLockedTask(task);
+ verify(mAtm.getLockTaskController(), atLeast(1)).clearLockedTask(rootTask);
+ }
+
+ @Test
public void testDesktopMode_tasksAreBroughtToFront() {
final TestDesktopOrganizer desktopOrganizer = new TestDesktopOrganizer(mAtm);
TaskDisplayArea tda = desktopOrganizer.mDefaultTDA;
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);
diff --git a/telecomm/java/android/telecom/Call.java b/telecomm/java/android/telecom/Call.java
index a52614d..531f516 100644
--- a/telecomm/java/android/telecom/Call.java
+++ b/telecomm/java/android/telecom/Call.java
@@ -52,6 +52,8 @@
* Represents an ongoing phone call that the in-call app should present to the user.
*/
public final class Call {
+ private static final String LOG_TAG = "TelecomCall";
+
/**
* The state of a {@code Call} when newly created.
*/
@@ -2912,6 +2914,11 @@
}
} catch (BadParcelableException e) {
return false;
+ } catch (ClassCastException e) {
+ Log.e(LOG_TAG, e, "areBundlesEqual: failure comparing bundle key %s", key);
+ // until we know what is causing this, we should rethrow -- this is still not
+ // expected.
+ throw e;
}
}
}