Merge "Lockscreen E2E test improvements" into main
diff --git a/ProtoLibraries.bp b/ProtoLibraries.bp
index e7adf20..f6213b9 100644
--- a/ProtoLibraries.bp
+++ b/ProtoLibraries.bp
@@ -31,6 +31,7 @@
"&& $(location soong_zip) -jar -o $(out) -C $(genDir)/$(in) -D $(genDir)/$(in)",
srcs: [
+ ":aconfigd_protos",
":ipconnectivity-proto-src",
":libstats_atom_enum_protos",
":libstats_atom_message_protos",
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 624227d..14ae3f5 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -4260,6 +4260,12 @@
field @NonNull public static final android.os.Parcelable.Creator<android.window.TaskFragmentOrganizerToken> CREATOR;
}
+ public final class TaskFragmentParentInfo implements android.os.Parcelable {
+ method public int describeContents();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.window.TaskFragmentParentInfo> CREATOR;
+ }
+
public final class TaskFragmentTransaction implements android.os.Parcelable {
ctor public TaskFragmentTransaction();
method public void addChange(@Nullable android.window.TaskFragmentTransaction.Change);
@@ -4284,8 +4290,8 @@
method @Nullable public android.os.IBinder getActivityToken();
method @NonNull public android.os.Bundle getErrorBundle();
method @Nullable public android.os.IBinder getErrorCallbackToken();
- method @Nullable public android.content.res.Configuration getTaskConfiguration();
method @Nullable public android.window.TaskFragmentInfo getTaskFragmentInfo();
+ method @Nullable public android.window.TaskFragmentParentInfo getTaskFragmentParentInfo();
method @Nullable public android.os.IBinder getTaskFragmentToken();
method public int getTaskId();
method public int getType();
@@ -4293,7 +4299,6 @@
method @NonNull public android.window.TaskFragmentTransaction.Change setActivityToken(@NonNull android.os.IBinder);
method @NonNull public android.window.TaskFragmentTransaction.Change setErrorBundle(@NonNull android.os.Bundle);
method @NonNull public android.window.TaskFragmentTransaction.Change setErrorCallbackToken(@Nullable android.os.IBinder);
- method @NonNull public android.window.TaskFragmentTransaction.Change setTaskConfiguration(@NonNull android.content.res.Configuration);
method @NonNull public android.window.TaskFragmentTransaction.Change setTaskFragmentInfo(@NonNull android.window.TaskFragmentInfo);
method @NonNull public android.window.TaskFragmentTransaction.Change setTaskFragmentToken(@NonNull android.os.IBinder);
method @NonNull public android.window.TaskFragmentTransaction.Change setTaskId(int);
diff --git a/core/java/android/accounts/AccountManager.java b/core/java/android/accounts/AccountManager.java
index 7ee3413..497d47a 100644
--- a/core/java/android/accounts/AccountManager.java
+++ b/core/java/android/accounts/AccountManager.java
@@ -2015,7 +2015,7 @@
* null for no callback
* @param handler {@link Handler} identifying the callback thread,
* null for the main thread
- * @return An {@link AccountManagerFuture} which resolves to a Boolean indicated wether it
+ * @return An {@link AccountManagerFuture} which resolves to a Boolean indicated whether it
* succeeded.
* @hide
*/
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index a5dd4a7..e1cb630 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -5796,6 +5796,8 @@
* @see #onRequestPermissionsResult
*/
@FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED)
+ @SuppressLint("OnNameExpected")
+ // Suppress lint as this is an overload of the original API.
public boolean shouldShowRequestPermissionRationale(@NonNull String permission, int deviceId) {
final PackageManager packageManager = getDeviceId() == deviceId ? getPackageManager()
: createDeviceContext(deviceId).getPackageManager();
diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java
index 9ea55f5..c6a1546 100644
--- a/core/java/android/app/ActivityOptions.java
+++ b/core/java/android/app/ActivityOptions.java
@@ -103,7 +103,8 @@
@IntDef(prefix = {"MODE_BACKGROUND_ACTIVITY_START_"}, value = {
MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED,
MODE_BACKGROUND_ACTIVITY_START_ALLOWED,
- MODE_BACKGROUND_ACTIVITY_START_DENIED})
+ MODE_BACKGROUND_ACTIVITY_START_DENIED,
+ MODE_BACKGROUND_ACTIVITY_START_COMPAT})
public @interface BackgroundActivityStartMode {}
/**
* No explicit value chosen. The system will decide whether to grant privileges.
@@ -117,6 +118,13 @@
* Deny the {@link PendingIntent} to use the background activity start privileges.
*/
public static final int MODE_BACKGROUND_ACTIVITY_START_DENIED = 2;
+ /**
+ * Special behavior for compatibility.
+ * Similar to {@link #MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED}
+ *
+ * @hide
+ */
+ public static final int MODE_BACKGROUND_ACTIVITY_START_COMPAT = -1;
/**
* The package name that created the options.
diff --git a/core/java/android/app/ComponentOptions.java b/core/java/android/app/ComponentOptions.java
index 397477d..0e8e2e3 100644
--- a/core/java/android/app/ComponentOptions.java
+++ b/core/java/android/app/ComponentOptions.java
@@ -18,6 +18,7 @@
import static android.app.ActivityOptions.BackgroundActivityStartMode;
import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED;
+import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_COMPAT;
import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED;
import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED;
@@ -54,7 +55,7 @@
public static final String KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED_BY_PERMISSION =
"android.pendingIntent.backgroundActivityAllowedByPermission";
- private @Nullable Boolean mPendingIntentBalAllowed = null;
+ private Integer mPendingIntentBalAllowed = MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED;
private boolean mPendingIntentBalAllowedByPermission = false;
ComponentOptions() {
@@ -65,12 +66,9 @@
// results they want, which is their loss.
opts.setDefusable(true);
- boolean pendingIntentBalAllowedIsSetExplicitly =
- opts.containsKey(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED);
- if (pendingIntentBalAllowedIsSetExplicitly) {
- mPendingIntentBalAllowed =
- opts.getBoolean(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED);
- }
+ mPendingIntentBalAllowed =
+ opts.getInt(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED,
+ MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED);
setPendingIntentBackgroundActivityLaunchAllowedByPermission(
opts.getBoolean(
KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED_BY_PERMISSION, false));
@@ -85,7 +83,8 @@
* @hide
*/
@Deprecated public void setPendingIntentBackgroundActivityLaunchAllowed(boolean allowed) {
- mPendingIntentBalAllowed = allowed;
+ mPendingIntentBalAllowed = allowed ? MODE_BACKGROUND_ACTIVITY_START_ALLOWED
+ : MODE_BACKGROUND_ACTIVITY_START_DENIED;
}
/**
@@ -98,11 +97,8 @@
* @hide
*/
@Deprecated public boolean isPendingIntentBackgroundActivityLaunchAllowed() {
- if (mPendingIntentBalAllowed == null) {
- // cannot return null, so return the value used up to API level 33 for compatibility
- return true;
- }
- return mPendingIntentBalAllowed;
+ // cannot return all detail, so return the value used up to API level 33 for compatibility
+ return mPendingIntentBalAllowed != MODE_BACKGROUND_ACTIVITY_START_DENIED;
}
/**
@@ -119,16 +115,15 @@
@BackgroundActivityStartMode int state) {
switch (state) {
case MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED:
- mPendingIntentBalAllowed = null;
- break;
- case MODE_BACKGROUND_ACTIVITY_START_ALLOWED:
- mPendingIntentBalAllowed = true;
- break;
case MODE_BACKGROUND_ACTIVITY_START_DENIED:
- mPendingIntentBalAllowed = false;
+ case MODE_BACKGROUND_ACTIVITY_START_COMPAT:
+ case MODE_BACKGROUND_ACTIVITY_START_ALLOWED:
+ mPendingIntentBalAllowed = state;
break;
default:
- throw new IllegalArgumentException(state + " is not valid");
+ // Assume that future values are some variant of allowing the start.
+ mPendingIntentBalAllowed = MODE_BACKGROUND_ACTIVITY_START_ALLOWED;
+ break;
}
return this;
}
@@ -141,13 +136,7 @@
* @see #setPendingIntentBackgroundActivityStartMode(int)
*/
public @BackgroundActivityStartMode int getPendingIntentBackgroundActivityStartMode() {
- if (mPendingIntentBalAllowed == null) {
- return MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED;
- } else if (mPendingIntentBalAllowed) {
- return MODE_BACKGROUND_ACTIVITY_START_ALLOWED;
- } else {
- return MODE_BACKGROUND_ACTIVITY_START_DENIED;
- }
+ return mPendingIntentBalAllowed;
}
/**
@@ -170,8 +159,8 @@
/** @hide */
public Bundle toBundle() {
Bundle b = new Bundle();
- if (mPendingIntentBalAllowed != null) {
- b.putBoolean(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED, mPendingIntentBalAllowed);
+ if (mPendingIntentBalAllowed != MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED) {
+ b.putInt(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED, mPendingIntentBalAllowed);
}
if (mPendingIntentBalAllowedByPermission) {
b.putBoolean(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED_BY_PERMISSION,
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 8b19664..0672064 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -6492,8 +6492,13 @@
// visual regressions.
@SuppressWarnings("AndroidFrameworkCompatChange")
private boolean bigContentViewRequired() {
- if (!Flags.notificationExpansionOptional()
- && mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.S) {
+ if (Flags.notificationExpansionOptional()) {
+ // Notifications without a bigContentView, style, or actions do not need to expand
+ boolean exempt = mN.bigContentView == null
+ && mStyle == null && mActions.size() == 0;
+ return !exempt;
+ }
+ if (mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.S) {
return true;
}
// Notifications with contentView and without a bigContentView, style, or actions would
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index b82a1e3..e4310c1 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -2972,6 +2972,7 @@
android.Manifest.permission.INTERACT_ACROSS_USERS,
android.Manifest.permission.ACCESS_NOTIFICATIONS})
@FlaggedApi(android.service.notification.Flags.FLAG_CALLSTYLE_CALLBACK_API)
+ @SuppressLint("UserHandle")
public void registerCallNotificationEventListener(@NonNull String packageName,
@NonNull UserHandle userHandle, @NonNull @CallbackExecutor Executor executor,
@NonNull CallNotificationEventListener listener) {
diff --git a/core/java/android/app/TEST_MAPPING b/core/java/android/app/TEST_MAPPING
index a29c196..dc44764 100644
--- a/core/java/android/app/TEST_MAPPING
+++ b/core/java/android/app/TEST_MAPPING
@@ -392,6 +392,14 @@
{
"file_patterns": ["(/|^)AppOpsManager.java"],
"name": "CtsAppOpsTestCases"
+ },
+ {
+ "file_patterns": [
+ "(/|^)Activity.*.java",
+ "(/|^)PendingIntent.java",
+ "(/|^)ComtextImpl.java"
+ ],
+ "name": "CtsWindowManagerBackgroundActivityTestCases"
}
]
}
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 69f29f3..4bc1ce6 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -10427,7 +10427,7 @@
@WorkerThread
public void setApplicationRestrictions(@Nullable ComponentName admin, String packageName,
Bundle settings) {
- if (!Flags.dmrhCanSetAppRestriction()) {
+ if (!Flags.dmrhSetAppRestrictions()) {
throwIfParentInstance("setApplicationRestrictions");
}
@@ -11835,7 +11835,7 @@
@WorkerThread
public @NonNull Bundle getApplicationRestrictions(
@Nullable ComponentName admin, String packageName) {
- if (!Flags.dmrhCanSetAppRestriction()) {
+ if (!Flags.dmrhSetAppRestrictions()) {
throwIfParentInstance("getApplicationRestrictions");
}
@@ -14120,7 +14120,7 @@
public @NonNull DevicePolicyManager getParentProfileInstance(@NonNull ComponentName admin) {
throwIfParentInstance("getParentProfileInstance");
try {
- if (Flags.dmrhCanSetAppRestriction()) {
+ if (Flags.dmrhSetAppRestrictions()) {
UserManager um = mContext.getSystemService(UserManager.class);
if (!um.isManagedProfile()) {
throw new SecurityException("The current user does not have a parent profile.");
diff --git a/core/java/android/app/admin/flags/flags.aconfig b/core/java/android/app/admin/flags/flags.aconfig
index 3d6ec19..4154e66 100644
--- a/core/java/android/app/admin/flags/flags.aconfig
+++ b/core/java/android/app/admin/flags/flags.aconfig
@@ -244,10 +244,13 @@
}
flag {
- name: "dmrh_can_set_app_restriction"
+ name: "dmrh_set_app_restrictions"
namespace: "enterprise"
description: "Allow DMRH to set application restrictions (both on the profile and the parent)"
bug: "328758346"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
}
flag {
diff --git a/core/java/android/app/servertransaction/ClientTransactionListenerController.java b/core/java/android/app/servertransaction/ClientTransactionListenerController.java
index cda2867..9b53461 100644
--- a/core/java/android/app/servertransaction/ClientTransactionListenerController.java
+++ b/core/java/android/app/servertransaction/ClientTransactionListenerController.java
@@ -33,11 +33,13 @@
import android.os.IBinder;
import android.util.ArrayMap;
import android.util.ArraySet;
+import android.util.Log;
import android.window.ActivityWindowInfo;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import java.util.concurrent.RejectedExecutionException;
import java.util.function.BiConsumer;
/**
@@ -47,6 +49,8 @@
*/
public class ClientTransactionListenerController {
+ private static final String TAG = "ClientTransactionListenerController";
+
private static ClientTransactionListenerController sController;
private final Object mLock = new Object();
@@ -179,10 +183,14 @@
}
// Dispatch the display changed callbacks.
- final int displayCount = configUpdatedDisplayIds.size();
- for (int i = 0; i < displayCount; i++) {
- final int displayId = configUpdatedDisplayIds.valueAt(i);
- onDisplayChanged(displayId);
+ try {
+ final int displayCount = configUpdatedDisplayIds.size();
+ for (int i = 0; i < displayCount; i++) {
+ final int displayId = configUpdatedDisplayIds.valueAt(i);
+ onDisplayChanged(displayId);
+ }
+ } catch (RejectedExecutionException e) {
+ Log.w(TAG, "Failed to notify DisplayListener because the Handler is shutting down");
}
}
@@ -222,7 +230,11 @@
}
if (changedDisplayId != INVALID_DISPLAY) {
- onDisplayChanged(changedDisplayId);
+ try {
+ onDisplayChanged(changedDisplayId);
+ } catch (RejectedExecutionException e) {
+ Log.w(TAG, "Failed to notify DisplayListener because the Handler is shutting down");
+ }
}
}
@@ -235,9 +247,11 @@
/**
* Called when receives a {@link Configuration} changed event that is updating display-related
* window configuration.
+ *
+ * @throws RejectedExecutionException if the display listener handler is closing.
*/
@VisibleForTesting
- public void onDisplayChanged(int displayId) {
+ public void onDisplayChanged(int displayId) throws RejectedExecutionException {
mDisplayManager.handleDisplayChangeFromWindowManager(displayId);
}
}
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 9e316a2..c8cae82 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -4353,13 +4353,6 @@
"android.intent.extra.BRIGHTNESS_DIALOG_IS_FULL_WIDTH";
/**
- * Activity Action: Shows the contrast setting dialog.
- * @hide
- */
- public static final String ACTION_SHOW_CONTRAST_DIALOG =
- "com.android.intent.action.SHOW_CONTRAST_DIALOG";
-
- /**
* Broadcast Action: A global button was pressed. Includes a single
* extra field, {@link #EXTRA_KEY_EVENT}, containing the key event that
* caused the broadcast.
diff --git a/core/java/android/content/IntentSender.java b/core/java/android/content/IntentSender.java
index b074e8b..2e252c1 100644
--- a/core/java/android/content/IntentSender.java
+++ b/core/java/android/content/IntentSender.java
@@ -60,6 +60,10 @@
* {@link android.app.PendingIntent#getIntentSender() PendingIntent.getIntentSender()}.
*/
public class IntentSender implements Parcelable {
+ private static final Bundle SEND_INTENT_DEFAULT_OPTIONS =
+ ActivityOptions.makeBasic().setPendingIntentBackgroundActivityStartMode(
+ ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_COMPAT).toBundle();
+
@UnsupportedAppUsage
private final IIntentSender mTarget;
IBinder mWhitelistToken;
@@ -161,7 +165,8 @@
*/
public void sendIntent(Context context, int code, Intent intent,
OnFinished onFinished, Handler handler) throws SendIntentException {
- sendIntent(context, code, intent, onFinished, handler, null, null /* options */);
+ sendIntent(context, code, intent, onFinished, handler, null,
+ SEND_INTENT_DEFAULT_OPTIONS);
}
/**
@@ -194,7 +199,7 @@
OnFinished onFinished, Handler handler, String requiredPermission)
throws SendIntentException {
sendIntent(context, code, intent, onFinished, handler, requiredPermission,
- null /* options */);
+ SEND_INTENT_DEFAULT_OPTIONS);
}
/**
diff --git a/core/java/android/content/TEST_MAPPING b/core/java/android/content/TEST_MAPPING
index a2cfbf5..f08395a 100644
--- a/core/java/android/content/TEST_MAPPING
+++ b/core/java/android/content/TEST_MAPPING
@@ -63,5 +63,11 @@
"name": "CtsContentTestCasesRavenwood",
"host": true
}
+ ],
+ "postsubmit": [
+ {
+ "name": "CtsWindowManagerBackgroundActivityTestCases",
+ "file_patterns": ["(/|^)IntentSender.java"]
+ }
]
}
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 83285e0..c506c97 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -270,11 +270,20 @@
/**
* Application level {@link android.content.pm.PackageManager.Property PackageManager
* .Property} for a app to inform the installer that a file containing the app's android
- * safety label data is bundled into the APK at the given path.
+ * safety label data is bundled into the APK as a raw resource.
+ *
+ * <p>For example:
+ * <pre>
+ * <application>
+ * <property
+ * android:name="android.content.PROPERTY_ANDROID_SAFETY_LABEL"
+ * android:resource="@raw/app-metadata"/>
+ * </application>
+ * </pre>
* @hide
*/
- public static final String PROPERTY_ANDROID_SAFETY_LABEL_PATH =
- "android.content.SAFETY_LABEL_PATH";
+ public static final String PROPERTY_ANDROID_SAFETY_LABEL =
+ "android.content.PROPERTY_ANDROID_SAFETY_LABEL";
/**
* A property value set within the manifest.
diff --git a/core/java/android/content/pm/multiuser.aconfig b/core/java/android/content/pm/multiuser.aconfig
index 83742eb..e2a131c 100644
--- a/core/java/android/content/pm/multiuser.aconfig
+++ b/core/java/android/content/pm/multiuser.aconfig
@@ -247,3 +247,13 @@
description: "Allow MAIN user to access blocked number provider"
bug: "338579331"
}
+
+flag {
+ name: "restrict_quiet_mode_credential_bug_fix_to_managed_profiles"
+ namespace: "profile_experiences"
+ description: "Use user states to check the state of quiet mode for managed profiles only"
+ bug: "332812630"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/core/java/android/hardware/usb/DeviceFilter.java b/core/java/android/hardware/usb/DeviceFilter.java
index 66b0a42..3a271b4 100644
--- a/core/java/android/hardware/usb/DeviceFilter.java
+++ b/core/java/android/hardware/usb/DeviceFilter.java
@@ -18,6 +18,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.hardware.usb.flags.Flags;
import android.service.usb.UsbDeviceFilterProto;
import android.util.Slog;
@@ -57,9 +58,12 @@
public final String mProductName;
// USB device serial number string (or null for unspecified)
public final String mSerialNumber;
+ // USB interface name (or null for unspecified). This will be used when matching devices using
+ // the available interfaces.
+ public final String mInterfaceName;
public DeviceFilter(int vid, int pid, int clasz, int subclass, int protocol,
- String manufacturer, String product, String serialnum) {
+ String manufacturer, String product, String serialnum, String interfaceName) {
mVendorId = vid;
mProductId = pid;
mClass = clasz;
@@ -68,6 +72,7 @@
mManufacturerName = manufacturer;
mProductName = product;
mSerialNumber = serialnum;
+ mInterfaceName = interfaceName;
}
public DeviceFilter(UsbDevice device) {
@@ -79,6 +84,7 @@
mManufacturerName = device.getManufacturerName();
mProductName = device.getProductName();
mSerialNumber = device.getSerialNumber();
+ mInterfaceName = null;
}
public DeviceFilter(@NonNull DeviceFilter filter) {
@@ -90,6 +96,7 @@
mManufacturerName = filter.mManufacturerName;
mProductName = filter.mProductName;
mSerialNumber = filter.mSerialNumber;
+ mInterfaceName = filter.mInterfaceName;
}
public static DeviceFilter read(XmlPullParser parser)
@@ -102,7 +109,7 @@
String manufacturerName = null;
String productName = null;
String serialNumber = null;
-
+ String interfaceName = null;
int count = parser.getAttributeCount();
for (int i = 0; i < count; i++) {
String name = parser.getAttributeName(i);
@@ -114,6 +121,8 @@
productName = value;
} else if ("serial-number".equals(name)) {
serialNumber = value;
+ } else if ("interface-name".equals(name)) {
+ interfaceName = value;
} else {
int intValue;
int radix = 10;
@@ -144,7 +153,7 @@
}
return new DeviceFilter(vendorId, productId,
deviceClass, deviceSubclass, deviceProtocol,
- manufacturerName, productName, serialNumber);
+ manufacturerName, productName, serialNumber, interfaceName);
}
public void write(XmlSerializer serializer) throws IOException {
@@ -173,13 +182,25 @@
if (mSerialNumber != null) {
serializer.attribute(null, "serial-number", mSerialNumber);
}
+ if (mInterfaceName != null) {
+ serializer.attribute(null, "interface-name", mInterfaceName);
+ }
serializer.endTag(null, "usb-device");
}
- private boolean matches(int clasz, int subclass, int protocol) {
- return ((mClass == -1 || clasz == mClass) &&
- (mSubclass == -1 || subclass == mSubclass) &&
- (mProtocol == -1 || protocol == mProtocol));
+ private boolean matches(int usbClass, int subclass, int protocol) {
+ return ((mClass == -1 || usbClass == mClass)
+ && (mSubclass == -1 || subclass == mSubclass)
+ && (mProtocol == -1 || protocol == mProtocol));
+ }
+
+ private boolean matches(int usbClass, int subclass, int protocol, String interfaceName) {
+ if (Flags.enableInterfaceNameDeviceFilter()) {
+ return matches(usbClass, subclass, protocol)
+ && (mInterfaceName == null || mInterfaceName.equals(interfaceName));
+ } else {
+ return matches(usbClass, subclass, protocol);
+ }
}
public boolean matches(UsbDevice device) {
@@ -204,7 +225,7 @@
for (int i = 0; i < count; i++) {
UsbInterface intf = device.getInterface(i);
if (matches(intf.getInterfaceClass(), intf.getInterfaceSubclass(),
- intf.getInterfaceProtocol())) return true;
+ intf.getInterfaceProtocol(), intf.getName())) return true;
}
return false;
@@ -320,11 +341,12 @@
@Override
public String toString() {
- return "DeviceFilter[mVendorId=" + mVendorId + ",mProductId=" + mProductId +
- ",mClass=" + mClass + ",mSubclass=" + mSubclass +
- ",mProtocol=" + mProtocol + ",mManufacturerName=" + mManufacturerName +
- ",mProductName=" + mProductName + ",mSerialNumber=" + mSerialNumber +
- "]";
+ return "DeviceFilter[mVendorId=" + mVendorId + ",mProductId=" + mProductId
+ + ",mClass=" + mClass + ",mSubclass=" + mSubclass
+ + ",mProtocol=" + mProtocol + ",mManufacturerName=" + mManufacturerName
+ + ",mProductName=" + mProductName + ",mSerialNumber=" + mSerialNumber
+ + ",mInterfaceName=" + mInterfaceName
+ + "]";
}
/**
diff --git a/core/java/android/hardware/usb/flags/usb_framework_flags.aconfig b/core/java/android/hardware/usb/flags/usb_framework_flags.aconfig
index 94df160..40e5ffb 100644
--- a/core/java/android/hardware/usb/flags/usb_framework_flags.aconfig
+++ b/core/java/android/hardware/usb/flags/usb_framework_flags.aconfig
@@ -16,3 +16,11 @@
description: "Feature flag for the api to check if a port supports mode change"
bug: "323470419"
}
+
+flag {
+ name: "enable_interface_name_device_filter"
+ is_exported: true
+ namespace: "usb"
+ description: "Feature flag to enable interface name as a parameter for device filter"
+ bug: "312828160"
+}
diff --git a/core/java/android/permission/OWNERS b/core/java/android/permission/OWNERS
index d2f4b50..857bacd 100644
--- a/core/java/android/permission/OWNERS
+++ b/core/java/android/permission/OWNERS
@@ -1,14 +1,13 @@
# Bug component: 137825
-augale@google.com
evanseverson@google.com
fayey@google.com
jaysullivan@google.com
joecastro@google.com
-kvakil@google.com
mrulhania@google.com
ntmyren@google.com
rmacgregor@google.com
theianchen@google.com
yutingfang@google.com
zhanghai@google.com
+kiranmr@google.com
diff --git a/core/java/android/service/notification/NotificationAssistantService.java b/core/java/android/service/notification/NotificationAssistantService.java
index 76889df..88da8eb 100644
--- a/core/java/android/service/notification/NotificationAssistantService.java
+++ b/core/java/android/service/notification/NotificationAssistantService.java
@@ -22,6 +22,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SdkConstant;
+import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.app.Notification;
import android.app.NotificationChannel;
@@ -37,9 +38,7 @@
import android.os.Message;
import android.os.RemoteException;
import android.util.Log;
-
import com.android.internal.os.SomeArgs;
-
import java.lang.annotation.Retention;
import java.util.List;
@@ -116,6 +115,7 @@
*/
protected Handler mHandler;
+ @SuppressLint("OnNameExpected")
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
diff --git a/core/java/android/service/notification/NotificationStats.java b/core/java/android/service/notification/NotificationStats.java
index 07367df..caa0a9c 100644
--- a/core/java/android/service/notification/NotificationStats.java
+++ b/core/java/android/service/notification/NotificationStats.java
@@ -19,6 +19,7 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.app.Flags;
import android.app.RemoteInput;
@@ -229,6 +230,7 @@
/**
* Records that the user has replied to a notification that has a smart reply at least once.
*/
+ @SuppressLint("GetterSetterNames")
@FlaggedApi(Flags.FLAG_LIFETIME_EXTENSION_REFACTOR)
public void setSmartReplied() {
mSmartReplied = true;
diff --git a/core/java/android/service/notification/ZenPolicy.java b/core/java/android/service/notification/ZenPolicy.java
index 1d7091c..910c462 100644
--- a/core/java/android/service/notification/ZenPolicy.java
+++ b/core/java/android/service/notification/ZenPolicy.java
@@ -1007,6 +1007,7 @@
/**
* Set whether priority channels are permitted to break through DND.
*/
+ @SuppressLint("BuilderSetStyle")
@FlaggedApi(Flags.FLAG_MODES_API)
public @NonNull Builder allowPriorityChannels(boolean allow) {
mZenPolicy.mAllowChannels = allow ? CHANNEL_POLICY_PRIORITY : CHANNEL_POLICY_NONE;
diff --git a/core/java/android/view/FocusFinder.java b/core/java/android/view/FocusFinder.java
index 35b137a..c5b6aa7 100644
--- a/core/java/android/view/FocusFinder.java
+++ b/core/java/android/view/FocusFinder.java
@@ -129,7 +129,7 @@
}
ViewGroup effective = null;
ViewParent nextParent = focused.getParent();
- do {
+ while (nextParent instanceof ViewGroup) {
if (nextParent == root) {
return effective != null ? effective : root;
}
@@ -143,7 +143,7 @@
effective = vg;
}
nextParent = nextParent.getParent();
- } while (nextParent instanceof ViewGroup);
+ }
return root;
}
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index f1cb410..d7f2b01 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -1262,10 +1262,13 @@
mHost.getInputMethodManager(), null /* icProto */);
}
+ final var statsToken = ImeTracker.forLogging().onStart(ImeTracker.TYPE_USER,
+ ImeTracker.ORIGIN_CLIENT, SoftInputShowHideReason.CONTROL_WINDOW_INSETS_ANIMATION,
+ mHost.isHandlingPointerEvent() /* fromUser */);
controlAnimationUnchecked(types, cancellationSignal, listener, mFrame, fromIme, durationMs,
interpolator, animationType,
getLayoutInsetsDuringAnimationMode(types, fromPredictiveBack),
- false /* useInsetsAnimationThread */, null /* statsToken */);
+ false /* useInsetsAnimationThread */, statsToken);
}
private void controlAnimationUnchecked(@InsetsType int types,
@@ -1567,7 +1570,9 @@
return;
}
final ImeTracker.Token statsToken = runner.getStatsToken();
- if (shown) {
+ if (runner.getAnimationType() == ANIMATION_TYPE_USER) {
+ ImeTracker.forLogging().onUserFinished(statsToken, shown);
+ } else if (shown) {
ImeTracker.forLogging().onProgress(statsToken,
ImeTracker.PHASE_CLIENT_ANIMATION_FINISHED_SHOW);
ImeTracker.forLogging().onShown(statsToken);
@@ -1838,6 +1843,9 @@
Trace.asyncTraceEnd(TRACE_TAG_VIEW, "IC.pendingAnim", 0);
mHost.dispatchWindowInsetsAnimationStart(animation, bounds);
mStartingAnimation = true;
+ if (runner.getAnimationType() == ANIMATION_TYPE_USER) {
+ ImeTracker.forLogging().onDispatched(runner.getStatsToken());
+ }
runner.setReadyDispatched(true);
listener.onReady(runner, types);
mStartingAnimation = false;
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 1df851a..0715474 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -921,15 +921,6 @@
private String mFrameRateCategoryView;
/**
- * The resolved pointer icon type requested by this window.
- * A null value indicates the resolved pointer icon has not yet been calculated.
- */
- // TODO(b/293587049): Remove pointer icon tracking by type when refactor is complete.
- @Nullable
- private Integer mPointerIconType = null;
- private PointerIcon mCustomPointerIcon = null;
-
- /**
* The resolved pointer icon requested by this window.
* A null value indicates the resolved pointer icon has not yet been calculated.
*/
@@ -6430,7 +6421,6 @@
private static final int MSG_SYNTHESIZE_INPUT_EVENT = 24;
private static final int MSG_DISPATCH_WINDOW_SHOWN = 25;
private static final int MSG_REQUEST_KEYBOARD_SHORTCUTS = 26;
- private static final int MSG_UPDATE_POINTER_ICON = 27;
private static final int MSG_POINTER_CAPTURE_CHANGED = 28;
private static final int MSG_INSETS_CONTROL_CHANGED = 29;
private static final int MSG_SYSTEM_GESTURE_EXCLUSION_CHANGED = 30;
@@ -6495,8 +6485,6 @@
return "MSG_SYNTHESIZE_INPUT_EVENT";
case MSG_DISPATCH_WINDOW_SHOWN:
return "MSG_DISPATCH_WINDOW_SHOWN";
- case MSG_UPDATE_POINTER_ICON:
- return "MSG_UPDATE_POINTER_ICON";
case MSG_POINTER_CAPTURE_CHANGED:
return "MSG_POINTER_CAPTURE_CHANGED";
case MSG_INSETS_CONTROL_CHANGED:
@@ -6747,10 +6735,6 @@
final int deviceId = msg.arg1;
handleRequestKeyboardShortcuts(receiver, deviceId);
} break;
- case MSG_UPDATE_POINTER_ICON: {
- MotionEvent event = (MotionEvent) msg.obj;
- resetPointerIcon(event);
- } break;
case MSG_POINTER_CAPTURE_CHANGED: {
final boolean hasCapture = msg.arg1 != 0;
handlePointerCaptureChanged(hasCapture);
@@ -7836,14 +7820,12 @@
|| action == MotionEvent.ACTION_HOVER_EXIT) {
// Other apps or the window manager may change the icon type outside of
// this app, therefore the icon type has to be reset on enter/exit event.
- mPointerIconType = null;
mResolvedPointerIcon = null;
}
if (action != MotionEvent.ACTION_HOVER_EXIT) {
// Resolve the pointer icon
if (!updatePointerIcon(event) && action == MotionEvent.ACTION_HOVER_MOVE) {
- mPointerIconType = null;
mResolvedPointerIcon = null;
}
}
@@ -7906,12 +7888,6 @@
return mAttachInfo.mHandlingPointerEvent;
}
- private void resetPointerIcon(MotionEvent event) {
- mPointerIconType = null;
- mResolvedPointerIcon = null;
- updatePointerIcon(event);
- }
-
/**
* If there is pointer that is showing a PointerIcon in this window, refresh the icon for that
diff --git a/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java b/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java
index f454a6a..3091bf4 100644
--- a/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java
+++ b/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java
@@ -754,6 +754,19 @@
}
}
+ /** @see com.android.server.inputmethod.ImeTrackerService#onDispatched */
+ static void onDispatched(@NonNull ImeTracker.Token statsToken) {
+ final IImeTracker service = getImeTrackerService();
+ if (service == null) {
+ return;
+ }
+ try {
+ service.onDispatched(statsToken);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
/** @see com.android.server.inputmethod.ImeTrackerService#hasPendingImeVisibilityRequests */
@AnyThread
@RequiresPermission(Manifest.permission.TEST_INPUT_METHOD)
diff --git a/core/java/android/view/inputmethod/ImeTracker.java b/core/java/android/view/inputmethod/ImeTracker.java
index d992feb..edc9921 100644
--- a/core/java/android/view/inputmethod/ImeTracker.java
+++ b/core/java/android/view/inputmethod/ImeTracker.java
@@ -71,24 +71,40 @@
/** The type of the IME request. */
@IntDef(prefix = { "TYPE_" }, value = {
TYPE_SHOW,
- TYPE_HIDE
+ TYPE_HIDE,
+ TYPE_USER,
})
@Retention(RetentionPolicy.SOURCE)
@interface Type {}
- /** IME show request type. */
+ /**
+ * IME show request type.
+ *
+ * @see android.view.InsetsController#ANIMATION_TYPE_SHOW
+ */
int TYPE_SHOW = ImeProtoEnums.TYPE_SHOW;
- /** IME hide request type. */
+ /**
+ * IME hide request type.
+ *
+ * @see android.view.InsetsController#ANIMATION_TYPE_HIDE
+ */
int TYPE_HIDE = ImeProtoEnums.TYPE_HIDE;
+ /**
+ * IME user-controlled animation request type.
+ *
+ * @see android.view.InsetsController#ANIMATION_TYPE_USER
+ */
+ int TYPE_USER = ImeProtoEnums.TYPE_USER;
+
/** The status of the IME request. */
@IntDef(prefix = { "STATUS_" }, value = {
STATUS_RUN,
STATUS_CANCEL,
STATUS_FAIL,
STATUS_SUCCESS,
- STATUS_TIMEOUT
+ STATUS_TIMEOUT,
})
@Retention(RetentionPolicy.SOURCE)
@interface Status {}
@@ -117,7 +133,7 @@
@IntDef(prefix = { "ORIGIN_" }, value = {
ORIGIN_CLIENT,
ORIGIN_SERVER,
- ORIGIN_IME
+ ORIGIN_IME,
})
@Retention(RetentionPolicy.SOURCE)
@interface Origin {}
@@ -400,20 +416,36 @@
void onCancelled(@Nullable Token token, @Phase int phase);
/**
- * Called when the IME show request is successful.
+ * Called when the show IME request is successful.
*
* @param token the token tracking the current IME request or {@code null} otherwise.
*/
void onShown(@Nullable Token token);
/**
- * Called when the IME hide request is successful.
+ * Called when the hide IME request is successful.
*
* @param token the token tracking the current IME request or {@code null} otherwise.
*/
void onHidden(@Nullable Token token);
/**
+ * Called when the user-controlled IME request was dispatched to the requesting app. The
+ * user animation can take an undetermined amount of time, so it shouldn't be tracked.
+ *
+ * @param token the token tracking the current IME request or {@code null} otherwise.
+ */
+ void onDispatched(@Nullable Token token);
+
+ /**
+ * Called when the animation of the user-controlled IME request finished.
+ *
+ * @param token the token tracking the current IME request or {@code null} otherwise.
+ * @param shown whether the end state of the animation was shown or hidden.
+ */
+ void onUserFinished(@Nullable Token token, boolean shown);
+
+ /**
* Returns whether the current IME request was created due to a user interaction. This can
* only be {@code true} when running on the view's UI thread.
*
@@ -482,13 +514,6 @@
/** Whether the stack trace at the request call site should be logged. */
private boolean mLogStackTrace;
- private void reloadSystemProperties() {
- mLogProgress = SystemProperties.getBoolean(
- "persist.debug.imetracker", false);
- mLogStackTrace = SystemProperties.getBoolean(
- "persist.debug.imerequest.logstacktrace", false);
- }
-
@NonNull
@Override
public Token onStart(@NonNull String component, int uid, @Type int type, @Origin int origin,
@@ -497,7 +522,7 @@
final var token = IInputMethodManagerGlobalInvoker.onStart(tag, uid, type,
origin, reason, fromUser);
- Log.i(TAG, token.mTag + ": onRequest" + (type == TYPE_SHOW ? "Show" : "Hide")
+ Log.i(TAG, token.mTag + ": " + getOnStartPrefix(type)
+ " at " + Debug.originToString(origin)
+ " reason " + InputMethodDebug.softInputDisplayReasonToString(reason)
+ " fromUser " + fromUser,
@@ -552,6 +577,45 @@
Log.i(TAG, token.mTag + ": onHidden");
}
+
+ @Override
+ public void onDispatched(@Nullable Token token) {
+ if (token == null) return;
+ IInputMethodManagerGlobalInvoker.onDispatched(token);
+
+ Log.i(TAG, token.mTag + ": onDispatched");
+ }
+
+ @Override
+ public void onUserFinished(@Nullable Token token, boolean shown) {
+ if (token == null) return;
+ // This is already sent to ImeTrackerService to mark it finished during onDispatched.
+
+ Log.i(TAG, token.mTag + ": onUserFinished " + (shown ? "shown" : "hidden"));
+ }
+
+ /**
+ * Gets the prefix string for {@link #onStart} based on the given request type.
+ *
+ * @param type request type for which to create the prefix string with.
+ */
+ @NonNull
+ private static String getOnStartPrefix(@Type int type) {
+ return switch (type) {
+ case TYPE_SHOW -> "onRequestShow";
+ case TYPE_HIDE -> "onRequestHide";
+ case TYPE_USER -> "onRequestUser";
+ default -> "onRequestUnknown";
+ };
+ }
+
+ /** Reloads the system properties related to this class. */
+ private void reloadSystemProperties() {
+ mLogProgress = SystemProperties.getBoolean(
+ "persist.debug.imetracker", false);
+ mLogStackTrace = SystemProperties.getBoolean(
+ "persist.debug.imerequest.logstacktrace", false);
+ }
};
/** The singleton IME tracker instance for instrumenting jank metrics. */
diff --git a/core/java/android/window/TaskFragmentParentInfo.java b/core/java/android/window/TaskFragmentParentInfo.java
index a77c234..1555416 100644
--- a/core/java/android/window/TaskFragmentParentInfo.java
+++ b/core/java/android/window/TaskFragmentParentInfo.java
@@ -18,6 +18,8 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.annotation.TestApi;
import android.app.WindowConfiguration;
import android.content.res.Configuration;
import android.os.Parcel;
@@ -27,10 +29,13 @@
import java.util.Objects;
/**
- * The information about the parent Task of a particular TaskFragment
+ * The information about the parent Task of a particular TaskFragment.
+ *
* @hide
*/
-public class TaskFragmentParentInfo implements Parcelable {
+@SuppressLint("UnflaggedApi") // @TestApi to replace legacy usages.
+@TestApi
+public final class TaskFragmentParentInfo implements Parcelable {
@NonNull
private final Configuration mConfiguration = new Configuration();
@@ -42,6 +47,7 @@
@Nullable private final SurfaceControl mDecorSurface;
+ /** @hide */
public TaskFragmentParentInfo(@NonNull Configuration configuration, int displayId,
boolean visible, boolean hasDirectActivity, @Nullable SurfaceControl decorSurface) {
mConfiguration.setTo(configuration);
@@ -51,6 +57,7 @@
mDecorSurface = decorSurface;
}
+ /** @hide */
public TaskFragmentParentInfo(@NonNull TaskFragmentParentInfo info) {
mConfiguration.setTo(info.getConfiguration());
mDisplayId = info.mDisplayId;
@@ -59,7 +66,11 @@
mDecorSurface = info.mDecorSurface;
}
- /** The {@link Configuration} of the parent Task */
+ /**
+ * The {@link Configuration} of the parent Task
+ *
+ * @hide
+ */
@NonNull
public Configuration getConfiguration() {
return mConfiguration;
@@ -68,19 +79,27 @@
/**
* The display ID of the parent Task. {@link android.view.Display#INVALID_DISPLAY} means the
* Task is detached from previously associated display.
+ *
+ * @hide
*/
public int getDisplayId() {
return mDisplayId;
}
- /** Whether the parent Task is visible or not */
+ /**
+ * Whether the parent Task is visible or not
+ *
+ * @hide
+ */
public boolean isVisible() {
return mVisible;
}
/**
* Whether the parent Task has any direct child activity, which is not embedded in any
- * TaskFragment, or not
+ * TaskFragment, or not.
+ *
+ * @hide
*/
public boolean hasDirectActivity() {
return mHasDirectActivity;
@@ -93,6 +112,8 @@
* {@link com.android.server.wm.WindowOrganizerController#configurationsAreEqualForOrganizer(
* Configuration, Configuration)} to determine if this {@link TaskFragmentParentInfo} should
* be dispatched to the client.
+ *
+ * @hide
*/
public boolean equalsForTaskFragmentOrganizer(@Nullable TaskFragmentParentInfo that) {
if (that == null) {
@@ -103,6 +124,7 @@
&& mDecorSurface == that.mDecorSurface;
}
+ /** @hide */
@Nullable
public SurfaceControl getDecorSurface() {
return mDecorSurface;
@@ -156,6 +178,7 @@
return result;
}
+ @SuppressLint("UnflaggedApi") // @TestApi to replace legacy usages.
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
mConfiguration.writeToParcel(dest, flags);
@@ -173,6 +196,8 @@
mDecorSurface = in.readTypedObject(SurfaceControl.CREATOR);
}
+ @SuppressLint("UnflaggedApi") // @TestApi to replace legacy usages.
+ @NonNull
public static final Creator<TaskFragmentParentInfo> CREATOR =
new Creator<TaskFragmentParentInfo>() {
@Override
@@ -186,6 +211,7 @@
}
};
+ @SuppressLint("UnflaggedApi") // @TestApi to replace legacy usages.
@Override
public int describeContents() {
return 0;
diff --git a/core/java/android/window/TaskFragmentTransaction.java b/core/java/android/window/TaskFragmentTransaction.java
index 4dada10..32e3f5a 100644
--- a/core/java/android/window/TaskFragmentTransaction.java
+++ b/core/java/android/window/TaskFragmentTransaction.java
@@ -24,7 +24,6 @@
import android.annotation.SuppressLint;
import android.annotation.TestApi;
import android.content.Intent;
-import android.content.res.Configuration;
import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
@@ -248,13 +247,6 @@
return this;
}
- // TODO(b/241043377): Keep this API to prevent @TestApi changes. Remove in the next release.
- /** Configuration of the parent Task. */
- @NonNull
- public Change setTaskConfiguration(@NonNull Configuration configuration) {
- return this;
- }
-
/**
* If the {@link #TYPE_TASK_FRAGMENT_ERROR} is from a {@link WindowContainerTransaction}
* from the {@link TaskFragmentOrganizer}, it may come with an error callback token to
@@ -299,12 +291,11 @@
return this;
}
- // TODO(b/241043377): Hide this API to prevent @TestApi changes. Remove in the next release.
/**
* Sets info of the parent Task of the embedded TaskFragment.
* @see TaskFragmentParentInfo
*
- * @hide pending unhide
+ * @hide
*/
@NonNull
public Change setTaskFragmentParentInfo(@NonNull TaskFragmentParentInfo info) {
@@ -338,12 +329,6 @@
return mTaskId;
}
- // TODO(b/241043377): Keep this API to prevent @TestApi changes. Remove in the next release.
- @Nullable
- public Configuration getTaskConfiguration() {
- return mTaskFragmentParentInfo.getConfiguration();
- }
-
@Nullable
public IBinder getErrorCallbackToken() {
return mErrorCallbackToken;
@@ -365,8 +350,10 @@
return mActivityToken;
}
- // TODO(b/241043377): Hide this API to prevent @TestApi changes. Remove in the next release.
- /** @hide pending unhide */
+ /**
+ * Obtains the {@link TaskFragmentParentInfo} for this transaction.
+ */
+ @SuppressLint("UnflaggedApi") // @TestApi to replace legacy usages.
@Nullable
public TaskFragmentParentInfo getTaskFragmentParentInfo() {
return mTaskFragmentParentInfo;
diff --git a/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig b/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig
index cd13c4a..4b2beb9 100644
--- a/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig
+++ b/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig
@@ -9,6 +9,16 @@
}
flag {
+ name: "disable_thin_letterboxing_policy"
+ namespace: "large_screen_experiences_app_compat"
+ description: "Whether reachability is disabled in case of thin letterboxing"
+ bug: "341027847"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "allows_screen_size_decoupled_from_status_bar_and_cutout"
namespace: "large_screen_experiences_app_compat"
description: "When necessary, configuration decoupled from status bar and display cutout"
diff --git a/core/java/com/android/internal/content/om/OverlayConfigParser.java b/core/java/com/android/internal/content/om/OverlayConfigParser.java
index faaf7d5..8132652 100644
--- a/core/java/com/android/internal/content/om/OverlayConfigParser.java
+++ b/core/java/com/android/internal/content/om/OverlayConfigParser.java
@@ -24,6 +24,8 @@
import android.content.pm.PackagePartitions.SystemPartition;
import android.os.Build;
import android.os.FileUtils;
+import android.os.SystemProperties;
+import android.text.TextUtils;
import android.util.ArraySet;
import android.util.Log;
import android.util.Xml;
@@ -241,6 +243,18 @@
}
}
+ @FunctionalInterface
+ public interface SysPropWrapper{
+ /**
+ * Get system property
+ *
+ * @param property the key to look up.
+ *
+ * @return The property value if found, empty string otherwise.
+ */
+ String get(String property);
+ }
+
/**
* Retrieves overlays configured within the partition in increasing priority order.
*
@@ -320,6 +334,76 @@
}
/**
+ * Expand the property inside a rro configuration path.
+ *
+ * A RRO configuration can contain a property, this method expands
+ * the property to its value.
+ *
+ * Only read only properties allowed, prefixed with ro. Other
+ * properties will raise exception.
+ *
+ * Only a single property in the path is allowed.
+ *
+ * Example "${ro.boot.hardware.sku}/config.xml" would expand to
+ * "G020N/config.xml"
+ *
+ * @param configPath path to expand
+ * @param sysPropWrapper method used for reading properties
+ *
+ * @return The expanded path. Returns null if configPath is null.
+ */
+ @VisibleForTesting
+ public static String expandProperty(String configPath,
+ SysPropWrapper sysPropWrapper) {
+ if (configPath == null) {
+ return null;
+ }
+
+ int propStartPos = configPath.indexOf("${");
+ if (propStartPos == -1) {
+ // No properties inside the string, return as is
+ return configPath;
+ }
+
+ final StringBuilder sb = new StringBuilder();
+ sb.append(configPath.substring(0, propStartPos));
+
+ // Read out the end position
+ int propEndPos = configPath.indexOf("}", propStartPos);
+ if (propEndPos == -1) {
+ throw new IllegalStateException("Malformed property, unmatched braces, in: "
+ + configPath);
+ }
+
+ // Confirm that there is only one property inside the string
+ if (configPath.indexOf("${", propStartPos + 2) != -1) {
+ throw new IllegalStateException("Only a single property supported in path: "
+ + configPath);
+ }
+
+ final String propertyName = configPath.substring(propStartPos + 2, propEndPos);
+ if (!propertyName.startsWith("ro.")) {
+ throw new IllegalStateException("Only read only properties can be used when "
+ + "merging RRO config files: " + propertyName);
+ }
+ final String propertyValue = sysPropWrapper.get(propertyName);
+ if (TextUtils.isEmpty(propertyValue)) {
+ throw new IllegalStateException("Property is empty or doesn't exist: " + propertyName);
+ }
+ Log.d(TAG, String.format("Using property in overlay config path: \"%s\"", propertyName));
+ sb.append(propertyValue);
+
+ // propEndPos points to '}', need to step to next character, might be outside of string
+ propEndPos = propEndPos + 1;
+ // Append the remainder, if exists
+ if (propEndPos < configPath.length()) {
+ sb.append(configPath.substring(propEndPos));
+ }
+
+ return sb.toString();
+ }
+
+ /**
* Parses a <merge> tag within an overlay configuration file.
*
* Merge tags allow for other configuration files to be "merged" at the current parsing
@@ -331,10 +415,21 @@
@Nullable OverlayScanner scanner,
@Nullable Map<String, ParsedOverlayInfo> packageManagerOverlayInfos,
@NonNull ParsingContext parsingContext) {
- final String path = parser.getAttributeValue(null, "path");
+ final String path;
+
+ try {
+ SysPropWrapper sysPropWrapper = p -> {
+ return SystemProperties.get(p, "");
+ };
+ path = expandProperty(parser.getAttributeValue(null, "path"), sysPropWrapper);
+ } catch (IllegalStateException e) {
+ throw new IllegalStateException(String.format("<merge> path expand error in %s at %s",
+ configFile, parser.getPositionDescription()), e);
+ }
+
if (path == null) {
- throw new IllegalStateException(String.format("<merge> without path in %s at %s"
- + configFile, parser.getPositionDescription()));
+ throw new IllegalStateException(String.format("<merge> without path in %s at %s",
+ configFile, parser.getPositionDescription()));
}
if (path.startsWith("/")) {
diff --git a/core/java/com/android/internal/inputmethod/IImeTracker.aidl b/core/java/com/android/internal/inputmethod/IImeTracker.aidl
index ab4edb6..ebae39e 100644
--- a/core/java/com/android/internal/inputmethod/IImeTracker.aidl
+++ b/core/java/com/android/internal/inputmethod/IImeTracker.aidl
@@ -64,20 +64,28 @@
oneway void onCancelled(in ImeTracker.Token statsToken, int phase);
/**
- * Called when the IME show request is successful.
+ * Called when the show IME request is successful.
*
* @param statsToken the token tracking the current IME request.
*/
oneway void onShown(in ImeTracker.Token statsToken);
/**
- * Called when the IME hide request is successful.
+ * Called when the hide IME request is successful.
*
* @param statsToken the token tracking the current IME request.
*/
oneway void onHidden(in ImeTracker.Token statsToken);
/**
+ * Called when the user-controlled IME request was dispatched to the requesting app. The
+ * user animation can take an undetermined amount of time, so it shouldn't be tracked.
+ *
+ * @param statsToken the token tracking the current IME request.
+ */
+ oneway void onDispatched(in ImeTracker.Token statsToken);
+
+ /**
* Checks whether there are any pending IME visibility requests.
*
* @return {@code true} iff there are pending IME visibility requests.
diff --git a/core/java/com/android/internal/inputmethod/InputMethodDebug.java b/core/java/com/android/internal/inputmethod/InputMethodDebug.java
index a0aad31..2a5593f 100644
--- a/core/java/com/android/internal/inputmethod/InputMethodDebug.java
+++ b/core/java/com/android/internal/inputmethod/InputMethodDebug.java
@@ -297,6 +297,8 @@
return "SHOW_SOFT_INPUT_IME_TOGGLE_SOFT_INPUT";
case SoftInputShowHideReason.SHOW_SOFT_INPUT_IMM_DEPRECATION:
return "SHOW_SOFT_INPUT_IMM_DEPRECATION";
+ case SoftInputShowHideReason.CONTROL_WINDOW_INSETS_ANIMATION:
+ return "CONTROL_WINDOW_INSETS_ANIMATION";
default:
return "Unknown=" + reason;
}
diff --git a/core/java/com/android/internal/inputmethod/SoftInputShowHideReason.java b/core/java/com/android/internal/inputmethod/SoftInputShowHideReason.java
index da738a0..eb6a810 100644
--- a/core/java/com/android/internal/inputmethod/SoftInputShowHideReason.java
+++ b/core/java/com/android/internal/inputmethod/SoftInputShowHideReason.java
@@ -88,6 +88,7 @@
SoftInputShowHideReason.HIDE_SOFT_INPUT_REQUEST_HIDE_WITH_CONTROL,
SoftInputShowHideReason.SHOW_SOFT_INPUT_IME_TOGGLE_SOFT_INPUT,
SoftInputShowHideReason.SHOW_SOFT_INPUT_IMM_DEPRECATION,
+ SoftInputShowHideReason.CONTROL_WINDOW_INSETS_ANIMATION,
})
public @interface SoftInputShowHideReason {
/** Default, undefined reason. */
@@ -397,4 +398,10 @@
* {@link InputMethodManager#showSoftInputFromInputMethod(IBinder, int)}.
*/
int SHOW_SOFT_INPUT_IMM_DEPRECATION = ImeProtoEnums.REASON_SHOW_SOFT_INPUT_IMM_DEPRECATION;
+
+ /**
+ * Show / Hide soft input by application-controlled animation in
+ * {@link android.view.InsetsController#controlWindowInsetsAnimation}.
+ */
+ int CONTROL_WINDOW_INSETS_ANIMATION = ImeProtoEnums.REASON_CONTROL_WINDOW_INSETS_ANIMATION;
}
diff --git a/core/java/com/android/internal/os/PowerStats.java b/core/java/com/android/internal/os/PowerStats.java
index 9f9aae5..24971f5 100644
--- a/core/java/com/android/internal/os/PowerStats.java
+++ b/core/java/com/android/internal/os/PowerStats.java
@@ -19,6 +19,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.BatteryConsumer;
+import android.os.BatteryStats;
import android.os.Bundle;
import android.os.Parcel;
import android.os.PersistableBundle;
@@ -34,8 +35,12 @@
import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
+import java.util.ArrayList;
import java.util.Arrays;
+import java.util.List;
import java.util.Objects;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
/**
* Container for power stats, acquired by various PowerStatsCollector classes. See subclasses for
@@ -75,6 +80,10 @@
*/
@android.ravenwood.annotation.RavenwoodKeepWholeClass
public static class Descriptor {
+ public static final String EXTRA_DEVICE_STATS_FORMAT = "format-device";
+ public static final String EXTRA_STATE_STATS_FORMAT = "format-state";
+ public static final String EXTRA_UID_STATS_FORMAT = "format-uid";
+
public static final String XML_TAG_DESCRIPTOR = "descriptor";
private static final String XML_ATTR_ID = "id";
private static final String XML_ATTR_NAME = "name";
@@ -120,6 +129,10 @@
*/
public final PersistableBundle extras;
+ private PowerStatsFormatter mDeviceStatsFormatter;
+ private PowerStatsFormatter mStateStatsFormatter;
+ private PowerStatsFormatter mUidStatsFormatter;
+
public Descriptor(@BatteryConsumer.PowerComponent int powerComponentId,
int statsArrayLength, @Nullable SparseArray<String> stateLabels,
int stateStatsArrayLength, int uidStatsArrayLength,
@@ -131,7 +144,7 @@
public Descriptor(int customPowerComponentId, String name, int statsArrayLength,
@Nullable SparseArray<String> stateLabels, int stateStatsArrayLength,
- int uidStatsArrayLength, PersistableBundle extras) {
+ int uidStatsArrayLength, @NonNull PersistableBundle extras) {
if (statsArrayLength > MAX_STATS_ARRAY_LENGTH) {
throw new IllegalArgumentException(
"statsArrayLength is too high. Max = " + MAX_STATS_ARRAY_LENGTH);
@@ -154,6 +167,39 @@
}
/**
+ * Returns a custom formatter for this type of power stats.
+ */
+ public PowerStatsFormatter getDeviceStatsFormatter() {
+ if (mDeviceStatsFormatter == null) {
+ mDeviceStatsFormatter = new PowerStatsFormatter(
+ extras.getString(EXTRA_DEVICE_STATS_FORMAT));
+ }
+ return mDeviceStatsFormatter;
+ }
+
+ /**
+ * Returns a custom formatter for this type of power stats, specifically per-state stats.
+ */
+ public PowerStatsFormatter getStateStatsFormatter() {
+ if (mStateStatsFormatter == null) {
+ mStateStatsFormatter = new PowerStatsFormatter(
+ extras.getString(EXTRA_STATE_STATS_FORMAT));
+ }
+ return mStateStatsFormatter;
+ }
+
+ /**
+ * Returns a custom formatter for this type of power stats, specifically per-UID stats.
+ */
+ public PowerStatsFormatter getUidStatsFormatter() {
+ if (mUidStatsFormatter == null) {
+ mUidStatsFormatter = new PowerStatsFormatter(
+ extras.getString(EXTRA_UID_STATS_FORMAT));
+ }
+ return mUidStatsFormatter;
+ }
+
+ /**
* Returns the label associated with the give state key, e.g. "5G-high" for the
* state of Mobile Radio representing the 5G mode and high signal power.
*/
@@ -491,20 +537,22 @@
StringBuilder sb = new StringBuilder();
sb.append("duration=").append(durationMs).append(" ").append(descriptor.name);
if (stats.length > 0) {
- sb.append("=").append(Arrays.toString(stats));
+ sb.append("=").append(descriptor.getDeviceStatsFormatter().format(stats));
}
if (descriptor.stateStatsArrayLength != 0) {
+ PowerStatsFormatter formatter = descriptor.getStateStatsFormatter();
for (int i = 0; i < stateStats.size(); i++) {
- sb.append(" [");
+ sb.append(" (");
sb.append(descriptor.getStateLabel(stateStats.keyAt(i)));
- sb.append("]=");
- sb.append(Arrays.toString(stateStats.valueAt(i)));
+ sb.append(") ");
+ sb.append(formatter.format(stateStats.valueAt(i)));
}
}
+ PowerStatsFormatter uidStatsFormatter = descriptor.getUidStatsFormatter();
for (int i = 0; i < uidStats.size(); i++) {
sb.append(uidPrefix)
.append(UserHandle.formatUid(uidStats.keyAt(i)))
- .append(": ").append(Arrays.toString(uidStats.valueAt(i)));
+ .append(": ").append(uidStatsFormatter.format(uidStats.valueAt(i)));
}
return sb.toString();
}
@@ -513,26 +561,29 @@
* Prints the contents of the stats snapshot.
*/
public void dump(IndentingPrintWriter pw) {
- pw.println("PowerStats: " + descriptor.name + " (" + descriptor.powerComponentId + ')');
+ pw.println(descriptor.name + " (" + descriptor.powerComponentId + ')');
pw.increaseIndent();
pw.print("duration", durationMs).println();
+
if (descriptor.statsArrayLength != 0) {
- pw.print("stats", Arrays.toString(stats)).println();
+ pw.println(descriptor.getDeviceStatsFormatter().format(stats));
}
if (descriptor.stateStatsArrayLength != 0) {
+ PowerStatsFormatter formatter = descriptor.getStateStatsFormatter();
for (int i = 0; i < stateStats.size(); i++) {
- pw.print("state ");
+ pw.print(" (");
pw.print(descriptor.getStateLabel(stateStats.keyAt(i)));
- pw.print(": ");
- pw.print(Arrays.toString(stateStats.valueAt(i)));
+ pw.print(") ");
+ pw.print(formatter.format(stateStats.valueAt(i)));
pw.println();
}
}
+ PowerStatsFormatter uidStatsFormatter = descriptor.getUidStatsFormatter();
for (int i = 0; i < uidStats.size(); i++) {
pw.print("UID ");
- pw.print(uidStats.keyAt(i));
+ pw.print(UserHandle.formatUid(uidStats.keyAt(i)));
pw.print(": ");
- pw.print(Arrays.toString(uidStats.valueAt(i)));
+ pw.print(uidStatsFormatter.format(uidStats.valueAt(i)));
pw.println();
}
pw.decreaseIndent();
@@ -542,4 +593,126 @@
public String toString() {
return "PowerStats: " + formatForBatteryHistory(" UID ");
}
+
+ public static class PowerStatsFormatter {
+ private static class Section {
+ public String label;
+ public int position;
+ public int length;
+ public boolean optional;
+ public boolean typePower;
+ }
+
+ private static final double NANO_TO_MILLI_MULTIPLIER = 1.0 / 1000000.0;
+ private static final Pattern SECTION_PATTERN =
+ Pattern.compile("([^:]+):(\\d+)(\\[(?<L>\\d+)])?(?<F>\\S*)\\s*");
+ private final List<Section> mSections;
+
+ public PowerStatsFormatter(String format) {
+ mSections = parseFormat(format);
+ }
+
+ /**
+ * Produces a formatted string representing the supplied array, with labels
+ * and other adornments specific to the power stats layout.
+ */
+ public String format(long[] stats) {
+ return format(mSections, stats);
+ }
+
+ private List<Section> parseFormat(String format) {
+ if (format == null || format.isBlank()) {
+ return null;
+ }
+
+ ArrayList<Section> sections = new ArrayList<>();
+ Matcher matcher = SECTION_PATTERN.matcher(format);
+ for (int position = 0; position < format.length(); position = matcher.end()) {
+ if (!matcher.find() || matcher.start() != position) {
+ Slog.wtf(TAG, "Bad power stats format '" + format + "'");
+ return null;
+ }
+ Section section = new Section();
+ section.label = matcher.group(1);
+ section.position = Integer.parseUnsignedInt(matcher.group(2));
+ String length = matcher.group("L");
+ if (length != null) {
+ section.length = Integer.parseUnsignedInt(length);
+ } else {
+ section.length = 1;
+ }
+ String flags = matcher.group("F");
+ if (flags != null) {
+ for (int i = 0; i < flags.length(); i++) {
+ char flag = flags.charAt(i);
+ switch (flag) {
+ case '?':
+ section.optional = true;
+ break;
+ case 'p':
+ section.typePower = true;
+ break;
+ default:
+ Slog.e(TAG,
+ "Unsupported format option '" + flag + "' in " + format);
+ break;
+ }
+ }
+ }
+ sections.add(section);
+ }
+
+ return sections;
+ }
+
+ private String format(List<Section> sections, long[] stats) {
+ if (sections == null) {
+ return Arrays.toString(stats);
+ }
+
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0, count = sections.size(); i < count; i++) {
+ Section section = sections.get(i);
+ if (section.length == 0) {
+ continue;
+ }
+
+ if (section.optional) {
+ boolean nonZero = false;
+ for (int offset = 0; offset < section.length; offset++) {
+ if (stats[section.position + offset] != 0) {
+ nonZero = true;
+ break;
+ }
+ }
+ if (!nonZero) {
+ continue;
+ }
+ }
+
+ if (!sb.isEmpty()) {
+ sb.append(' ');
+ }
+ sb.append(section.label).append(": ");
+ if (section.length != 1) {
+ sb.append('[');
+ }
+ for (int offset = 0; offset < section.length; offset++) {
+ if (offset != 0) {
+ sb.append(", ");
+ }
+ if (section.typePower) {
+ sb.append(BatteryStats.formatCharge(
+ stats[section.position + offset] * NANO_TO_MILLI_MULTIPLIER));
+ } else {
+ sb.append(stats[section.position + offset]);
+ }
+ }
+ if (section.length != 1) {
+ sb.append(']');
+ }
+ }
+ return sb.toString();
+ }
+ }
}
diff --git a/core/res/res/values/config_battery_stats.xml b/core/res/res/values/config_battery_stats.xml
index 8d97362..80cf088 100644
--- a/core/res/res/values/config_battery_stats.xml
+++ b/core/res/res/values/config_battery_stats.xml
@@ -27,16 +27,18 @@
<!-- Whether to reset Battery Stats on unplug if the battery was significantly charged -->
<bool name="config_batteryStatsResetOnUnplugAfterSignificantCharge">true</bool>
- <!-- CPU power stats collection throttle period in milliseconds. Since power stats collection
- is a relatively expensive operation, this throttle period may need to be adjusted for low-power
- devices-->
- <integer name="config_defaultPowerStatsThrottlePeriodCpu">60000</integer>
+ <!-- Power stats collection throttle periods in milliseconds. Since power stats collection
+ is a relatively expensive operation, these throttle period may need to be adjusted for low-power
+ devices.
- <!-- Mobile Radio power stats collection throttle period in milliseconds. -->
- <integer name="config_defaultPowerStatsThrottlePeriodMobileRadio">3600000</integer>
-
- <!-- Mobile Radio power stats collection throttle period in milliseconds. -->
- <integer name="config_defaultPowerStatsThrottlePeriodWifi">3600000</integer>
+ The syntax of this config string is as follows:
+ <pre>
+ power-component-name1:throttle-period-millis1 power-component-name2:throttle-period-ms2 ...
+ </pre>
+ Use "*" for the power-component-name to represent the default for all power components
+ not mentioned by name.
+ -->
+ <string name="config_powerStatsThrottlePeriods">cpu:60000 *:300000</string>
<!-- PowerStats aggregation period in milliseconds. This is the interval at which the power
stats aggregation procedure is performed and the results stored in PowerStatsStore. -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index a9d03f5..acd3b37 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -5243,9 +5243,7 @@
<java-symbol type="bool" name="config_batteryStatsResetOnUnplugHighBatteryLevel" />
<java-symbol type="bool" name="config_batteryStatsResetOnUnplugAfterSignificantCharge" />
- <java-symbol type="integer" name="config_defaultPowerStatsThrottlePeriodCpu" />
- <java-symbol type="integer" name="config_defaultPowerStatsThrottlePeriodMobileRadio" />
- <java-symbol type="integer" name="config_defaultPowerStatsThrottlePeriodWifi" />
+ <java-symbol type="string" name="config_powerStatsThrottlePeriods" />
<java-symbol type="integer" name="config_powerStatsAggregationPeriod" />
<java-symbol type="integer" name="config_aggregatedPowerStatsSpanDuration" />
diff --git a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java
index b6f4429..ee1d1e1 100644
--- a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java
+++ b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java
@@ -29,6 +29,7 @@
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
@@ -65,6 +66,7 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.concurrent.RejectedExecutionException;
import java.util.function.BiConsumer;
/**
@@ -221,4 +223,17 @@
123 /* newDisplayId */, true /* shouldReportConfigChange*/);
inOrder.verify(mController).onContextConfigurationPostChanged(context);
}
+
+ @Test
+ public void testDisplayListenerHandlerClosed() {
+ doReturn(123).when(mActivity).getDisplayId();
+ doThrow(new RejectedExecutionException()).when(mController).onDisplayChanged(123);
+
+ mController.onContextConfigurationPreChanged(mActivity);
+ mConfiguration.windowConfiguration.setMaxBounds(new Rect(0, 0, 100, 200));
+ mController.onContextConfigurationPostChanged(mActivity);
+
+ // No crash
+ verify(mController).onDisplayChanged(123);
+ }
}
diff --git a/core/tests/coretests/src/com/android/internal/content/res/OverlayConfigParserTest.java b/core/tests/coretests/src/com/android/internal/content/res/OverlayConfigParserTest.java
new file mode 100644
index 0000000..4eccbe5
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/content/res/OverlayConfigParserTest.java
@@ -0,0 +1,119 @@
+/*
+ * 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.internal.content.res;
+
+import static com.android.internal.content.om.OverlayConfigParser.SysPropWrapper;
+
+import static org.junit.Assert.assertEquals;
+
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.content.om.OverlayConfigParser;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class OverlayConfigParserTest {
+ @Test(expected = IllegalStateException.class)
+ public void testMergePropNotRoProp() {
+ SysPropWrapper sysProp = p -> {
+ return "dummy_value";
+ };
+ OverlayConfigParser.expandProperty("${persist.value}/path", sysProp);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testMergePropMissingEndBracket() {
+ SysPropWrapper sysProp = p -> {
+ return "dummy_value";
+ };
+ OverlayConfigParser.expandProperty("${ro.value/path", sysProp);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testMergeOnlyPropStart() {
+ SysPropWrapper sysProp = p -> {
+ return "dummy_value";
+ };
+ OverlayConfigParser.expandProperty("path/${", sysProp);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testMergePropInProp() {
+ SysPropWrapper sysProp = p -> {
+ return "dummy_value";
+ };
+ OverlayConfigParser.expandProperty("path/${${ro.value}}", sysProp);
+ }
+
+ /**
+ * The path is only allowed to contain one property.
+ */
+ @Test(expected = IllegalStateException.class)
+ public void testMergePropMultipleProps() {
+ SysPropWrapper sysProp = p -> {
+ return "dummy_value";
+ };
+ OverlayConfigParser.expandProperty("${ro.value}/path${ro.value2}/path", sysProp);
+ }
+
+ @Test
+ public void testMergePropOneProp() {
+ final SysPropWrapper sysProp = p -> {
+ if ("ro.value".equals(p)) {
+ return "dummy_value";
+ } else {
+ return "invalid";
+ }
+ };
+
+ // Property in the beginnig of the string
+ String result = OverlayConfigParser.expandProperty("${ro.value}/path",
+ sysProp);
+ assertEquals("dummy_value/path", result);
+
+ // Property in the middle of the string
+ result = OverlayConfigParser.expandProperty("path/${ro.value}/file",
+ sysProp);
+ assertEquals("path/dummy_value/file", result);
+
+ // Property at the of the string
+ result = OverlayConfigParser.expandProperty("path/${ro.value}",
+ sysProp);
+ assertEquals("path/dummy_value", result);
+
+ // Property is the entire string
+ result = OverlayConfigParser.expandProperty("${ro.value}",
+ sysProp);
+ assertEquals("dummy_value", result);
+ }
+
+ @Test
+ public void testMergePropNoProp() {
+ final SysPropWrapper sysProp = p -> {
+ return "dummy_value";
+ };
+
+ final String path = "no_props/path";
+ String result = OverlayConfigParser.expandProperty(path, sysProp);
+ assertEquals(path, result);
+ }
+}
diff --git a/core/tests/coretests/src/com/android/internal/os/PowerStatsTest.java b/core/tests/coretests/src/com/android/internal/os/PowerStatsTest.java
index baab3b2..4846ed27 100644
--- a/core/tests/coretests/src/com/android/internal/os/PowerStatsTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/PowerStatsTest.java
@@ -22,6 +22,7 @@
import android.os.Parcel;
import android.os.PersistableBundle;
import android.platform.test.ravenwood.RavenwoodRule;
+import android.util.IndentingPrintWriter;
import android.util.SparseArray;
import android.util.Xml;
@@ -31,6 +32,8 @@
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
+import com.google.common.truth.StringSubject;
+
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -38,6 +41,7 @@
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
+import java.io.StringWriter;
import java.nio.charset.StandardCharsets;
@RunWith(AndroidJUnit4.class)
@@ -56,6 +60,9 @@
extras.putBoolean("hasPowerMonitor", true);
SparseArray<String> stateLabels = new SparseArray<>();
stateLabels.put(0x0F, "idle");
+ extras.putString(PowerStats.Descriptor.EXTRA_DEVICE_STATS_FORMAT, "device:0[3]");
+ extras.putString(PowerStats.Descriptor.EXTRA_STATE_STATS_FORMAT, "state:0");
+ extras.putString(PowerStats.Descriptor.EXTRA_UID_STATS_FORMAT, "a:0 b:1");
mDescriptor = new PowerStats.Descriptor(BatteryConsumer.POWER_COMPONENT_CPU, 3, stateLabels,
1, 2, extras);
mRegistry.register(mDescriptor);
@@ -191,4 +198,68 @@
newParcel.setDataPosition(0);
return newParcel;
}
+
+ @Test
+ public void formatForBatteryHistory() {
+ PowerStats stats = new PowerStats(mDescriptor);
+ stats.durationMs = 1234;
+ stats.stats[0] = 10;
+ stats.stats[1] = 20;
+ stats.stats[2] = 30;
+ stats.stateStats.put(0x0F, new long[]{16});
+ stats.stateStats.put(0xF0, new long[]{17});
+ stats.uidStats.put(42, new long[]{40, 50});
+ stats.uidStats.put(99, new long[]{60, 70});
+
+ assertThat(stats.formatForBatteryHistory(" #"))
+ .isEqualTo("duration=1234 cpu="
+ + "device: [10, 20, 30]"
+ + " (idle) state: 16"
+ + " (cpu-f0) state: 17"
+ + " #42: a: 40 b: 50"
+ + " #99: a: 60 b: 70");
+ }
+
+ @Test
+ public void dump() {
+ PowerStats stats = new PowerStats(mDescriptor);
+ stats.durationMs = 1234;
+ stats.stats[0] = 10;
+ stats.stats[1] = 20;
+ stats.stats[2] = 30;
+ stats.stateStats.put(0x0F, new long[]{16});
+ stats.stateStats.put(0xF0, new long[]{17});
+ stats.uidStats.put(42, new long[]{40, 50});
+ stats.uidStats.put(99, new long[]{60, 70});
+
+ StringWriter sw = new StringWriter();
+ IndentingPrintWriter pw = new IndentingPrintWriter(sw);
+ stats.dump(pw);
+ pw.flush();
+ String dump = sw.toString();
+
+ assertThat(dump).contains("duration=1234");
+ assertThat(dump).contains("device: [10, 20, 30]");
+ assertThat(dump).contains("(idle) state: 16");
+ assertThat(dump).contains("(cpu-f0) state: 17");
+ assertThat(dump).contains("UID 42: a: 40 b: 50");
+ assertThat(dump).contains("UID 99: a: 60 b: 70");
+ }
+
+ @Test
+ public void formatter() {
+ assertThatFormatted(new long[]{12, 34, 56}, "a:0 b:1[2]")
+ .isEqualTo("a: 12 b: [34, 56]");
+ assertThatFormatted(new long[]{12, 0, 0}, "a:0? b:1[2]?")
+ .isEqualTo("a: 12");
+ assertThatFormatted(new long[]{0, 34, 56}, "a:0? b:1[2]?")
+ .isEqualTo("b: [34, 56]");
+ assertThatFormatted(new long[]{3141592, 2000000, 1414213}, "pi:0p sqrt:1[2]p")
+ .isEqualTo("pi: 3.14 sqrt: [2.00, 1.41]");
+ }
+
+ private static StringSubject assertThatFormatted(long[] stats, String format) {
+ return assertThat(new PowerStats.PowerStatsFormatter(format)
+ .format(stats));
+ }
}
diff --git a/data/etc/core.protolog.pb b/data/etc/core.protolog.pb
index 000f6ef..b41a607 100644
--- a/data/etc/core.protolog.pb
+++ b/data/etc/core.protolog.pb
Binary files differ
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index 01deb49..b93cd46 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -583,6 +583,12 @@
"group": "WM_DEBUG_STATES",
"at": "com\/android\/server\/wm\/ActivityRecord.java"
},
+ "7211222997110112110": {
+ "message": "Refreshing activity for freeform camera compatibility treatment, activityRecord=%s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_STATES",
+ "at": "com\/android\/server\/wm\/ActivityRefresher.java"
+ },
"1665699123574159131": {
"message": "Starting activity when config will change = %b",
"level": "VERBOSE",
@@ -1771,12 +1777,6 @@
"group": "WM_DEBUG_ORIENTATION",
"at": "com\/android\/server\/wm\/DisplayRotationCompatPolicy.java"
},
- "-7756685416834187936": {
- "message": "Refreshing activity for camera compatibility treatment, activityRecord=%s",
- "level": "VERBOSE",
- "group": "WM_DEBUG_STATES",
- "at": "com\/android\/server\/wm\/DisplayRotationCompatPolicy.java"
- },
"-5176775281239247368": {
"message": "Reverting orientation after camera compat force rotation",
"level": "VERBOSE",
diff --git a/graphics/java/android/graphics/BitmapFactory.java b/graphics/java/android/graphics/BitmapFactory.java
index d915b74..1c20141 100644
--- a/graphics/java/android/graphics/BitmapFactory.java
+++ b/graphics/java/android/graphics/BitmapFactory.java
@@ -143,7 +143,7 @@
* the decoder will try to pick the best matching config based on the
* system's screen depth, and characteristics of the original image such
* as if it has per-pixel alpha (requiring a config that also does).
- *
+ *
* Image are loaded with the {@link Bitmap.Config#ARGB_8888} config by
* default.
*/
@@ -183,7 +183,7 @@
/**
* If true (which is the default), the resulting bitmap will have its
- * color channels pre-multipled by the alpha channel.
+ * color channels pre-multiplied by the alpha channel.
*
* <p>This should NOT be set to false for images to be directly drawn by
* the view system or through a {@link Canvas}. The view system and
@@ -221,9 +221,9 @@
* if {@link #inScaled} is set (which it is by default} and this
* density does not match {@link #inTargetDensity}, then the bitmap
* will be scaled to the target density before being returned.
- *
+ *
* <p>If this is 0,
- * {@link BitmapFactory#decodeResource(Resources, int)},
+ * {@link BitmapFactory#decodeResource(Resources, int)},
* {@link BitmapFactory#decodeResource(Resources, int, android.graphics.BitmapFactory.Options)},
* and {@link BitmapFactory#decodeResourceStream}
* will fill in the density associated with the resource. The other
@@ -242,29 +242,29 @@
* This is used in conjunction with {@link #inDensity} and
* {@link #inScaled} to determine if and how to scale the bitmap before
* returning it.
- *
+ *
* <p>If this is 0,
- * {@link BitmapFactory#decodeResource(Resources, int)},
+ * {@link BitmapFactory#decodeResource(Resources, int)},
* {@link BitmapFactory#decodeResource(Resources, int, android.graphics.BitmapFactory.Options)},
* and {@link BitmapFactory#decodeResourceStream}
* will fill in the density associated the Resources object's
* DisplayMetrics. The other
* functions will leave it as-is and no scaling for density will be
* performed.
- *
+ *
* @see #inDensity
* @see #inScreenDensity
* @see #inScaled
* @see android.util.DisplayMetrics#densityDpi
*/
public int inTargetDensity;
-
+
/**
* The pixel density of the actual screen that is being used. This is
* purely for applications running in density compatibility code, where
* {@link #inTargetDensity} is actually the density the application
* sees rather than the real screen density.
- *
+ *
* <p>By setting this, you
* allow the loading code to avoid scaling a bitmap that is currently
* in the screen density up/down to the compatibility density. Instead,
@@ -274,18 +274,18 @@
* Bitmap.getScaledWidth} and {@link Bitmap#getScaledHeight
* Bitmap.getScaledHeight} to account for any different between the
* bitmap's density and the target's density.
- *
+ *
* <p>This is never set automatically for the caller by
* {@link BitmapFactory} itself. It must be explicitly set, since the
* caller must deal with the resulting bitmap in a density-aware way.
- *
+ *
* @see #inDensity
* @see #inTargetDensity
* @see #inScaled
* @see android.util.DisplayMetrics#densityDpi
*/
public int inScreenDensity;
-
+
/**
* When this flag is set, if {@link #inDensity} and
* {@link #inTargetDensity} are not 0, the
@@ -345,7 +345,7 @@
* ignored.
*
* In {@link android.os.Build.VERSION_CODES#KITKAT} and below, this
- * field works in conjuction with inPurgeable. If inPurgeable is false,
+ * field works in conjunction with inPurgeable. If inPurgeable is false,
* then this field is ignored. If inPurgeable is true, then this field
* determines whether the bitmap can share a reference to the input
* data (inputstream, array, etc.) or if it must make a deep copy.
@@ -583,11 +583,11 @@
opts.inDensity = density;
}
}
-
+
if (opts.inTargetDensity == 0 && res != null) {
opts.inTargetDensity = res.getDisplayMetrics().densityDpi;
}
-
+
return decodeStream(is, pad, opts);
}
@@ -611,8 +611,8 @@
public static Bitmap decodeResource(Resources res, int id, Options opts) {
validate(opts);
Bitmap bm = null;
- InputStream is = null;
-
+ InputStream is = null;
+
try {
final TypedValue value = new TypedValue();
is = res.openRawResource(id, value);
diff --git a/graphics/java/android/graphics/Canvas.java b/graphics/java/android/graphics/Canvas.java
index e03a1da..0b3e545 100644
--- a/graphics/java/android/graphics/Canvas.java
+++ b/graphics/java/android/graphics/Canvas.java
@@ -55,7 +55,7 @@
* Canvas and Drawables</a> developer guide.</p></div>
*/
public class Canvas extends BaseCanvas {
- private static int sCompatiblityVersion = 0;
+ private static int sCompatibilityVersion = 0;
private static boolean sCompatibilityRestore = false;
private static boolean sCompatibilitySetBitmap = false;
@@ -74,7 +74,7 @@
// Maximum bitmap size as defined in Skia's native code
// (see SkCanvas.cpp, SkDraw.cpp)
- private static final int MAXMIMUM_BITMAP_SIZE = 32766;
+ private static final int MAXIMUM_BITMAP_SIZE = 32766;
// Use a Holder to allow static initialization of Canvas in the boot image.
private static class NoImagePreloadHolder {
@@ -331,7 +331,7 @@
* @see #getMaximumBitmapHeight()
*/
public int getMaximumBitmapWidth() {
- return MAXMIMUM_BITMAP_SIZE;
+ return MAXIMUM_BITMAP_SIZE;
}
/**
@@ -342,7 +342,7 @@
* @see #getMaximumBitmapWidth()
*/
public int getMaximumBitmapHeight() {
- return MAXMIMUM_BITMAP_SIZE;
+ return MAXIMUM_BITMAP_SIZE;
}
// the SAVE_FLAG constants must match their native equivalents
@@ -423,7 +423,7 @@
public static final int ALL_SAVE_FLAG = 0x1F;
private static void checkValidSaveFlags(int saveFlags) {
- if (sCompatiblityVersion >= Build.VERSION_CODES.P
+ if (sCompatibilityVersion >= Build.VERSION_CODES.P
&& saveFlags != ALL_SAVE_FLAG) {
throw new IllegalArgumentException(
"Invalid Layer Save Flag - only ALL_SAVE_FLAGS is allowed");
@@ -845,7 +845,7 @@
}
private static void checkValidClipOp(@NonNull Region.Op op) {
- if (sCompatiblityVersion >= Build.VERSION_CODES.P
+ if (sCompatibilityVersion >= Build.VERSION_CODES.P
&& op != Region.Op.INTERSECT && op != Region.Op.DIFFERENCE) {
throw new IllegalArgumentException(
"Invalid Region.Op - only INTERSECT and DIFFERENCE are allowed");
@@ -1435,7 +1435,7 @@
}
/*package*/ static void setCompatibilityVersion(int apiLevel) {
- sCompatiblityVersion = apiLevel;
+ sCompatibilityVersion = apiLevel;
sCompatibilityRestore = apiLevel < Build.VERSION_CODES.M;
sCompatibilitySetBitmap = apiLevel < Build.VERSION_CODES.O;
nSetCompatibilityVersion(apiLevel);
diff --git a/graphics/java/android/graphics/Color.java b/graphics/java/android/graphics/Color.java
index 0f2f879..c1edafc 100644
--- a/graphics/java/android/graphics/Color.java
+++ b/graphics/java/android/graphics/Color.java
@@ -289,6 +289,9 @@
*/
@AnyThread
@SuppressAutoDoc
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
+@android.ravenwood.annotation.RavenwoodClassLoadHook(
+ android.ravenwood.annotation.RavenwoodClassLoadHook.LIBANDROID_LOADING_HOOK)
public class Color {
@ColorInt public static final int BLACK = 0xFF000000;
@ColorInt public static final int DKGRAY = 0xFF444444;
diff --git a/graphics/java/android/graphics/ColorSpace.java b/graphics/java/android/graphics/ColorSpace.java
index a2319a5..4bc3ece 100644
--- a/graphics/java/android/graphics/ColorSpace.java
+++ b/graphics/java/android/graphics/ColorSpace.java
@@ -135,6 +135,9 @@
@AnyThread
@SuppressWarnings("StaticInitializerReferencesSubClass")
@SuppressAutoDoc
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
+@android.ravenwood.annotation.RavenwoodClassLoadHook(
+ android.ravenwood.annotation.RavenwoodClassLoadHook.LIBANDROID_LOADING_HOOK)
public abstract class ColorSpace {
/**
* Standard CIE 1931 2° illuminant A, encoded in xyY.
@@ -2490,9 +2493,16 @@
return mNativePtr;
}
- private static native long nativeGetNativeFinalizer();
- private static native long nativeCreate(float a, float b, float c, float d,
- float e, float f, float g, float[] xyz);
+ /**
+ * These methods can't be put in the Rgb class directly, because ColorSpace's
+ * static initializer instantiates Rgb, whose constructor needs them, which is a variation
+ * of b/337329128.
+ */
+ static class Native {
+ static native long nativeGetNativeFinalizer();
+ static native long nativeCreate(float a, float b, float c, float d,
+ float e, float f, float g, float[] xyz);
+ }
private static DoubleUnaryOperator generateOETF(TransferParameters function) {
if (function.isHLGish()) {
@@ -2959,7 +2969,7 @@
// This mimics the old code that was in native.
float[] nativeTransform = adaptToIlluminantD50(mWhitePoint, mTransform);
- mNativePtr = nativeCreate((float) mTransferParameters.a,
+ mNativePtr = Native.nativeCreate((float) mTransferParameters.a,
(float) mTransferParameters.b,
(float) mTransferParameters.c,
(float) mTransferParameters.d,
@@ -2975,7 +2985,7 @@
private static class NoImagePreloadHolder {
public static final NativeAllocationRegistry sRegistry = new NativeAllocationRegistry(
- ColorSpace.Rgb.class.getClassLoader(), nativeGetNativeFinalizer(), 0);
+ ColorSpace.Rgb.class.getClassLoader(), Native.nativeGetNativeFinalizer(), 0);
}
/**
diff --git a/graphics/java/android/graphics/ComposePathEffect.java b/graphics/java/android/graphics/ComposePathEffect.java
index 3fc9eb5..7d59ece 100644
--- a/graphics/java/android/graphics/ComposePathEffect.java
+++ b/graphics/java/android/graphics/ComposePathEffect.java
@@ -20,13 +20,13 @@
/**
* Construct a PathEffect whose effect is to apply first the inner effect
- * and the the outer pathEffect (e.g. outer(inner(path))).
+ * and the outer pathEffect (e.g. outer(inner(path))).
*/
public ComposePathEffect(PathEffect outerpe, PathEffect innerpe) {
native_instance = nativeCreate(outerpe.native_instance,
innerpe.native_instance);
}
-
+
private static native long nativeCreate(long nativeOuterpe,
long nativeInnerpe);
}
diff --git a/graphics/java/android/graphics/FontFamily.java b/graphics/java/android/graphics/FontFamily.java
index c86b744..88f0e8e 100644
--- a/graphics/java/android/graphics/FontFamily.java
+++ b/graphics/java/android/graphics/FontFamily.java
@@ -219,7 +219,7 @@
@CriticalNative
private static native long nGetFamilyReleaseFunc();
- // By passing -1 to weigth argument, the weight value is resolved by OS/2 table in the font.
+ // By passing -1 to weight argument, the weight value is resolved by OS/2 table in the font.
// By passing -1 to italic argument, the italic value is resolved by OS/2 table in the font.
private static native boolean nAddFont(long builderPtr, ByteBuffer font, int ttcIndex,
int weight, int isItalic);
diff --git a/graphics/java/android/graphics/FontListParser.java b/graphics/java/android/graphics/FontListParser.java
index 17c2dd9..13c4a94 100644
--- a/graphics/java/android/graphics/FontListParser.java
+++ b/graphics/java/android/graphics/FontListParser.java
@@ -127,7 +127,7 @@
parser.setInput(is, null);
parser.nextTag();
return readFamilies(parser, systemFontDir, oemCustomization, updatableFontMap,
- lastModifiedDate, configVersion, false /* filter out the non-exising files */);
+ lastModifiedDate, configVersion, false /* filter out the non-existing files */);
}
}
@@ -254,7 +254,7 @@
* @param parser An XML parser.
* @param fontDir a font directory name.
* @param updatableFontMap a updated font file map.
- * @param allowNonExistingFile true to allow font file that doesn't exists
+ * @param allowNonExistingFile true to allow font file that doesn't exist.
* @return a FontFamily instance. null if no font files are available in this FontFamily.
*/
public static @Nullable FontConfig.FontFamily readFamily(XmlPullParser parser, String fontDir,
diff --git a/graphics/java/android/graphics/FrameInfo.java b/graphics/java/android/graphics/FrameInfo.java
index b3615ff..8f12828 100644
--- a/graphics/java/android/graphics/FrameInfo.java
+++ b/graphics/java/android/graphics/FrameInfo.java
@@ -24,7 +24,7 @@
/**
* Class that contains all the timing information for the current frame. This
* is used in conjunction with the hardware renderer to provide
- * continous-monitoring jank events
+ * continuous-monitoring jank events
*
* All times in nanoseconds from CLOCK_MONOTONIC/System.nanoTime()
*
diff --git a/graphics/java/android/graphics/Interpolator.java b/graphics/java/android/graphics/Interpolator.java
index 994fb2d..28f296d 100644
--- a/graphics/java/android/graphics/Interpolator.java
+++ b/graphics/java/android/graphics/Interpolator.java
@@ -28,13 +28,13 @@
mFrameCount = 2;
native_instance = nativeConstructor(valueCount, 2);
}
-
+
public Interpolator(int valueCount, int frameCount) {
mValueCount = valueCount;
mFrameCount = frameCount;
native_instance = nativeConstructor(valueCount, frameCount);
}
-
+
/**
* Reset the Interpolator to have the specified number of values and an
* implicit keyFrame count of 2 (just a start and end). After this call the
@@ -43,7 +43,7 @@
public void reset(int valueCount) {
reset(valueCount, 2);
}
-
+
/**
* Reset the Interpolator to have the specified number of values and
* keyFrames. After this call the values for each keyFrame must be assigned
@@ -54,20 +54,20 @@
mFrameCount = frameCount;
nativeReset(native_instance, valueCount, frameCount);
}
-
+
public final int getKeyFrameCount() {
return mFrameCount;
}
-
+
public final int getValueCount() {
return mValueCount;
}
-
+
/**
* Assign the keyFrame (specified by index) a time value and an array of key
- * values (with an implicity blend array of [0, 0, 1, 1] giving linear
+ * values (with an implicitly blend array of [0, 0, 1, 1] giving linear
* transition to the next set of key values).
- *
+ *
* @param index The index of the key frame to assign
* @param msec The time (in mililiseconds) for this key frame. Based on the
* SystemClock.uptimeMillis() clock
@@ -80,7 +80,7 @@
/**
* Assign the keyFrame (specified by index) a time value and an array of key
* values and blend array.
- *
+ *
* @param index The index of the key frame to assign
* @param msec The time (in mililiseconds) for this key frame. Based on the
* SystemClock.uptimeMillis() clock
@@ -99,7 +99,7 @@
}
nativeSetKeyFrame(native_instance, index, msec, values, blend);
}
-
+
/**
* Set a repeat count (which may be fractional) for the interpolator, and
* whether the interpolator should mirror its repeats. The default settings
@@ -110,7 +110,7 @@
nativeSetRepeatMirror(native_instance, repeatCount, mirror);
}
}
-
+
public enum Result {
NORMAL,
FREEZE_START,
@@ -130,7 +130,7 @@
* return whether the specified time was within the range of key times
* (NORMAL), was before the first key time (FREEZE_START) or after the last
* key time (FREEZE_END). In any event, computed values are always returned.
- *
+ *
* @param msec The time (in milliseconds) used to sample into the
* Interpolator. Based on the SystemClock.uptimeMillis() clock
* @param values Where to write the computed values (may be NULL).
@@ -146,13 +146,13 @@
default: return Result.FREEZE_END;
}
}
-
+
@Override
protected void finalize() throws Throwable {
nativeDestructor(native_instance);
native_instance = 0; // Other finalizers can still call us.
}
-
+
private int mValueCount;
private int mFrameCount;
private long native_instance;
diff --git a/graphics/java/android/graphics/LightingColorFilter.java b/graphics/java/android/graphics/LightingColorFilter.java
index 0aa6f12..fe73a1a 100644
--- a/graphics/java/android/graphics/LightingColorFilter.java
+++ b/graphics/java/android/graphics/LightingColorFilter.java
@@ -16,8 +16,8 @@
// This file was generated from the C++ include file: SkColorFilter.h
// Any changes made to this file will be discarded by the build.
-// To change this file, either edit the include, or device/tools/gluemaker/main.cpp,
-// or one of the auxilary file specifications in device/tools/gluemaker.
+// To change this file, either edit the include, or device/tools/gluemaker/main.cpp,
+// or one of the auxiliary file specifications in device/tools/gluemaker.
package android.graphics;
diff --git a/graphics/java/android/graphics/LinearGradient.java b/graphics/java/android/graphics/LinearGradient.java
index 56d912b..0879371 100644
--- a/graphics/java/android/graphics/LinearGradient.java
+++ b/graphics/java/android/graphics/LinearGradient.java
@@ -63,7 +63,7 @@
* @param colors The sRGB colors to be distributed along the gradient line
* @param positions May be null. The relative positions [0..1] of
* each corresponding color in the colors array. If this is null,
- * the the colors are distributed evenly along the gradient line.
+ * the colors are distributed evenly along the gradient line.
* @param tile The Shader tiling mode
*/
public LinearGradient(float x0, float y0, float x1, float y1, @NonNull @ColorInt int[] colors,
@@ -82,7 +82,7 @@
* @param colors The colors to be distributed along the gradient line
* @param positions May be null. The relative positions [0..1] of
* each corresponding color in the colors array. If this is null,
- * the the colors are distributed evenly along the gradient line.
+ * the colors are distributed evenly along the gradient line.
* @param tile The Shader tiling mode
*
* @throws IllegalArgumentException if there are less than two colors, the colors do
diff --git a/graphics/java/android/graphics/Matrix.java b/graphics/java/android/graphics/Matrix.java
index fbb690c..748e9dd 100644
--- a/graphics/java/android/graphics/Matrix.java
+++ b/graphics/java/android/graphics/Matrix.java
@@ -232,7 +232,7 @@
private static class NoImagePreloadHolder {
public static final NativeAllocationRegistry sRegistry =
NativeAllocationRegistry.createMalloced(
- Matrix.class.getClassLoader(), nGetNativeFinalizerWrapper());
+ Matrix.class.getClassLoader(), ExtraNatives.nGetNativeFinalizer());
}
private final long native_instance;
@@ -241,7 +241,7 @@
* Create an identity matrix
*/
public Matrix() {
- native_instance = nCreateWrapper(0);
+ native_instance = ExtraNatives.nCreate(0);
NoImagePreloadHolder.sRegistry.registerNativeAllocation(this, native_instance);
}
@@ -251,7 +251,7 @@
* @param src The matrix to copy into this matrix
*/
public Matrix(Matrix src) {
- native_instance = nCreateWrapper(src != null ? src.native_instance : 0);
+ native_instance = ExtraNatives.nCreate(src != null ? src.native_instance : 0);
NoImagePreloadHolder.sRegistry.registerNativeAllocation(this, native_instance);
}
@@ -562,7 +562,7 @@
/**
* Set the matrix to the scale and translate values that map the source rectangle to the
- * destination rectangle, returning true if the the result can be represented.
+ * destination rectangle, returning true if the result can be represented.
*
* @param src the source rectangle to map from.
* @param dst the destination rectangle to map to.
@@ -849,40 +849,6 @@
return native_instance;
}
- /**
- * Wrapper method we use to switch to ExtraNatives.nCreate(src) only on Ravenwood.
- *
- * @see ExtraNatives
- */
- @android.ravenwood.annotation.RavenwoodReplace
- private static long nCreateWrapper(long src) {
- return nCreate(src);
- }
-
- private static long nCreateWrapper$ravenwood(long src) {
- return ExtraNatives.nCreate(src);
- }
-
- /**
- * Wrapper method we use to switch to ExtraNatives.nGetNativeFinalizer(src) only on Ravenwood.
- *
- * @see ExtraNatives
- */
- @android.ravenwood.annotation.RavenwoodReplace
- private static long nGetNativeFinalizerWrapper() {
- return nGetNativeFinalizer();
- }
-
- private static long nGetNativeFinalizerWrapper$ravenwood() {
- return ExtraNatives.nGetNativeFinalizer();
- }
-
- // ------------------ Regular JNI ------------------------
-
- private static native long nCreate(long nSrc_or_zero);
- private static native long nGetNativeFinalizer();
-
-
// ------------------ Fast JNI ------------------------
@FastNative
@@ -982,14 +948,6 @@
* There are two methods that are called by the static initializers (either directly or
* indirectly) in this class, namely nCreate() and nGetNativeFinalizer(). On Ravenwood
* these methods can't be on the Matrix class itself, so we use a nested class to host them.
- *
- * We still keep the original nCreate() method and call it on non-ravenwood environment,
- * in order to avoid problems in downstream (such as Android Studio).
- *
- * @see #nCreateWrapper(long)
- * @see #nGetNativeFinalizerWrapper()
- *
- * TODO(b/337110712) Clean it up somehow. (remove the original nCreate() and unify the code?)
*/
private static class ExtraNatives {
static native long nCreate(long nSrc_or_zero);
diff --git a/graphics/java/android/graphics/Mesh.java b/graphics/java/android/graphics/Mesh.java
index a4bce9e..6be8332 100644
--- a/graphics/java/android/graphics/Mesh.java
+++ b/graphics/java/android/graphics/Mesh.java
@@ -271,7 +271,7 @@
* not have a uniform with that name or if the uniform is declared with a type other than int
* or int[1] then an IllegalArgumentException is thrown.
*
- * @param uniformName name matching the int uniform delcared in the shader program.
+ * @param uniformName name matching the int uniform declared in the shader program.
* @param value value corresponding to the int uniform with the given name.
*/
public void setIntUniform(@NonNull String uniformName, int value) {
@@ -283,7 +283,7 @@
* not have a uniform with that name or if the uniform is declared with a type other than ivec2
* or int[2] then an IllegalArgumentException is thrown.
*
- * @param uniformName name matching the int uniform delcared in the shader program.
+ * @param uniformName name matching the int uniform declared in the shader program.
* @param value1 first value corresponding to the int uniform with the given name.
* @param value2 second value corresponding to the int uniform with the given name.
*/
@@ -296,7 +296,7 @@
* not have a uniform with that name or if the uniform is declared with a type other than ivec3
* or int[3] then an IllegalArgumentException is thrown.
*
- * @param uniformName name matching the int uniform delcared in the shader program.
+ * @param uniformName name matching the int uniform declared in the shader program.
* @param value1 first value corresponding to the int uniform with the given name.
* @param value2 second value corresponding to the int uniform with the given name.
* @param value3 third value corresponding to the int uniform with the given name.
@@ -310,7 +310,7 @@
* not have a uniform with that name or if the uniform is declared with a type other than ivec4
* or int[4] then an IllegalArgumentException is thrown.
*
- * @param uniformName name matching the int uniform delcared in the shader program.
+ * @param uniformName name matching the int uniform declared in the shader program.
* @param value1 first value corresponding to the int uniform with the given name.
* @param value2 second value corresponding to the int uniform with the given name.
* @param value3 third value corresponding to the int uniform with the given name.
@@ -327,7 +327,7 @@
* int (for N=1), ivecN, or int[N], where N is the length of the values param, then an
* IllegalArgumentException is thrown.
*
- * @param uniformName name matching the int uniform delcared in the shader program.
+ * @param uniformName name matching the int uniform declared in the shader program.
* @param values int values corresponding to the vec4 int uniform with the given name.
*/
public void setIntUniform(@NonNull String uniformName, @NonNull int[] values) {
diff --git a/graphics/java/android/graphics/NinePatch.java b/graphics/java/android/graphics/NinePatch.java
index af20957..382269f 100644
--- a/graphics/java/android/graphics/NinePatch.java
+++ b/graphics/java/android/graphics/NinePatch.java
@@ -22,12 +22,12 @@
* The NinePatch class permits drawing a bitmap in nine or more sections.
* Essentially, it allows the creation of custom graphics that will scale the
* way that you define, when content added within the image exceeds the normal
- * bounds of the graphic. For a thorough explanation of a NinePatch image,
- * read the discussion in the
+ * bounds of the graphic. For a thorough explanation of a NinePatch image,
+ * read the discussion in the
* <a href="{@docRoot}guide/topics/graphics/2d-graphics.html#nine-patch">2D
* Graphics</a> document.
* <p>
- * The <a href="{@docRoot}guide/developing/tools/draw9patch.html">Draw 9-Patch</a>
+ * The <a href="{@docRoot}guide/developing/tools/draw9patch.html">Draw 9-Patch</a>
* tool offers an extremely handy way to create your NinePatch images,
* using a WYSIWYG graphics editor.
* </p>
@@ -104,7 +104,7 @@
this(bitmap, chunk, null);
}
- /**
+ /**
* Create a drawable projection from a bitmap to nine patches.
*
* @param bitmap The bitmap describing the patches.
@@ -122,7 +122,7 @@
protected void finalize() throws Throwable {
try {
if (mNativeChunk != 0) {
- // only attempt to destroy correctly initilized chunks
+ // only attempt to destroy correctly initialized chunks
nativeFinalize(mNativeChunk);
mNativeChunk = 0;
}
@@ -169,8 +169,8 @@
public Bitmap getBitmap() {
return mBitmap;
}
-
- /**
+
+ /**
* Draws the NinePatch. This method will use the paint returned by {@link #getPaint()}.
*
* @param canvas A container for the current matrix and clip used to draw the NinePatch.
@@ -180,7 +180,7 @@
canvas.drawPatch(this, location, mPaint);
}
- /**
+ /**
* Draws the NinePatch. This method will use the paint returned by {@link #getPaint()}.
*
* @param canvas A container for the current matrix and clip used to draw the NinePatch.
@@ -190,7 +190,7 @@
canvas.drawPatch(this, location, mPaint);
}
- /**
+ /**
* Draws the NinePatch. This method will ignore the paint returned
* by {@link #getPaint()} and use the specified paint instead.
*
diff --git a/graphics/java/android/graphics/PathMeasure.java b/graphics/java/android/graphics/PathMeasure.java
index 5500c52..2c6cfa5 100644
--- a/graphics/java/android/graphics/PathMeasure.java
+++ b/graphics/java/android/graphics/PathMeasure.java
@@ -25,14 +25,14 @@
* setPath.
*
* Note that once a path is associated with the measure object, it is
- * undefined if the path is subsequently modified and the the measure object
+ * undefined if the path is subsequently modified and the measure object
* is used. If the path is modified, you must call setPath with the path.
*/
public PathMeasure() {
mPath = null;
native_instance = native_create(0, false);
}
-
+
/**
* Create a PathMeasure object associated with the specified path object
* (already created and specified). The measure object can now return the
@@ -40,7 +40,7 @@
* path.
*
* Note that once a path is associated with the measure object, it is
- * undefined if the path is subsequently modified and the the measure object
+ * undefined if the path is subsequently modified and the measure object
* is used. If the path is modified, you must call setPath with the path.
*
* @param path The path that will be measured by this object
@@ -121,7 +121,7 @@
* such as <code>dst.rLineTo(0, 0)</code>.</p>
*/
public boolean getSegment(float startD, float stopD, Path dst, boolean startWithMoveTo) {
- // Skia used to enforce this as part of it's API, but has since relaxed that restriction
+ // Skia used to enforce this as part of its API, but has since relaxed that restriction
// so to maintain consistency in our API we enforce the preconditions here.
float length = getLength();
if (startD < 0) {
diff --git a/graphics/java/android/graphics/Rasterizer.java b/graphics/java/android/graphics/Rasterizer.java
index 29d82fa..5750954 100644
--- a/graphics/java/android/graphics/Rasterizer.java
+++ b/graphics/java/android/graphics/Rasterizer.java
@@ -16,8 +16,8 @@
// This file was generated from the C++ include file: SkRasterizer.h
// Any changes made to this file will be discarded by the build.
-// To change this file, either edit the include, or device/tools/gluemaker/main.cpp,
-// or one of the auxilary file specifications in device/tools/gluemaker.
+// To change this file, either edit the include, or device/tools/gluemaker/main.cpp,
+// or one of the auxiliary file specifications in device/tools/gluemaker.
package android.graphics;
diff --git a/graphics/java/android/graphics/Rect.java b/graphics/java/android/graphics/Rect.java
index 411a10b..5211e3a 100644
--- a/graphics/java/android/graphics/Rect.java
+++ b/graphics/java/android/graphics/Rect.java
@@ -165,7 +165,7 @@
public String toShortString() {
return toShortString(new StringBuilder(32));
}
-
+
/**
* Return a string representation of the rectangle in a compact form.
* @hide
@@ -184,7 +184,7 @@
*
* <p>You can later recover the Rect from this string through
* {@link #unflattenFromString(String)}.
- *
+ *
* @return Returns a new String of the form "left top right bottom"
*/
@NonNull
@@ -314,7 +314,7 @@
public final int height() {
return bottom - top;
}
-
+
/**
* @return the horizontal center of the rectangle. If the computed value
* is fractional, this method returns the largest integer that is
@@ -323,7 +323,7 @@
public final int centerX() {
return (left + right) >> 1;
}
-
+
/**
* @return the vertical center of the rectangle. If the computed value
* is fractional, this method returns the largest integer that is
@@ -332,14 +332,14 @@
public final int centerY() {
return (top + bottom) >> 1;
}
-
+
/**
* @return the exact horizontal center of the rectangle as a float.
*/
public final float exactCenterX() {
return (left + right) * 0.5f;
}
-
+
/**
* @return the exact vertical center of the rectangle as a float.
*/
@@ -493,7 +493,7 @@
* @param top The top of the rectangle being tested for containment
* @param right The right side of the rectangle being tested for containment
* @param bottom The bottom of the rectangle being tested for containment
- * @return true iff the the 4 specified sides of a rectangle are inside or
+ * @return true iff the 4 specified sides of a rectangle are inside or
* equal to this rectangle
*/
public boolean contains(int left, int top, int right, int bottom) {
@@ -548,7 +548,7 @@
}
return false;
}
-
+
/**
* If the specified rectangle intersects this rectangle, return true and set
* this rectangle to that intersection, otherwise return false and do not
@@ -670,7 +670,7 @@
public void union(@NonNull Rect r) {
union(r.left, r.top, r.right, r.bottom);
}
-
+
/**
* Update this Rect to enclose itself and the [x,y] coordinate. There is no
* check to see that this rectangle is non-empty.
diff --git a/graphics/java/android/graphics/RectF.java b/graphics/java/android/graphics/RectF.java
index ff50a0c..5b9b764 100644
--- a/graphics/java/android/graphics/RectF.java
+++ b/graphics/java/android/graphics/RectF.java
@@ -38,7 +38,7 @@
public float top;
public float right;
public float bottom;
-
+
/**
* Create a new empty RectF. All coordinates are initialized to 0.
*/
@@ -78,7 +78,7 @@
bottom = r.bottom;
}
}
-
+
public RectF(@Nullable Rect r) {
if (r == null) {
left = top = right = bottom = 0.0f;
@@ -121,7 +121,7 @@
public String toShortString() {
return toShortString(new StringBuilder(32));
}
-
+
/**
* Return a string representation of the rectangle in a compact form.
* @hide
@@ -134,7 +134,7 @@
sb.append(','); sb.append(bottom); sb.append(']');
return sb.toString();
}
-
+
/**
* Print short representation to given writer.
* @hide
@@ -183,14 +183,14 @@
public final float centerY() {
return (top + bottom) * 0.5f;
}
-
+
/**
* Set the rectangle to (0,0,0,0)
*/
public void setEmpty() {
left = right = top = bottom = 0;
}
-
+
/**
* Set the rectangle's coordinates to the specified values. Note: no range
* checking is performed, so it is up to the caller to ensure that
@@ -220,7 +220,7 @@
this.right = src.right;
this.bottom = src.bottom;
}
-
+
/**
* Copy the coordinates from src into this rectangle.
*
@@ -261,7 +261,7 @@
left = newLeft;
top = newTop;
}
-
+
/**
* Inset the rectangle by (dx,dy). If dx is positive, then the sides are
* moved inwards, making the rectangle narrower. If dx is negative, then the
@@ -293,7 +293,7 @@
return left < right && top < bottom // check for empty first
&& x >= left && x < right && y >= top && y < bottom;
}
-
+
/**
* Returns true iff the 4 specified sides of a rectangle are inside or equal
* to this rectangle. i.e. is this rectangle a superset of the specified
@@ -303,7 +303,7 @@
* @param top The top of the rectangle being tested for containment
* @param right The right side of the rectangle being tested for containment
* @param bottom The bottom of the rectangle being tested for containment
- * @return true iff the the 4 specified sides of a rectangle are inside or
+ * @return true iff the 4 specified sides of a rectangle are inside or
* equal to this rectangle
*/
public boolean contains(float left, float top, float right, float bottom) {
@@ -313,7 +313,7 @@
&& this.left <= left && this.top <= top
&& this.right >= right && this.bottom >= bottom;
}
-
+
/**
* Returns true iff the specified rectangle r is inside or equal to this
* rectangle. An empty rectangle never contains another rectangle.
@@ -329,7 +329,7 @@
&& left <= r.left && top <= r.top
&& right >= r.right && bottom >= r.bottom;
}
-
+
/**
* If the rectangle specified by left,top,right,bottom intersects this
* rectangle, return true and set this rectangle to that intersection,
@@ -367,7 +367,7 @@
}
return false;
}
-
+
/**
* If the specified rectangle intersects this rectangle, return true and set
* this rectangle to that intersection, otherwise return false and do not
@@ -382,7 +382,7 @@
public boolean intersect(@NonNull RectF r) {
return intersect(r.left, r.top, r.right, r.bottom);
}
-
+
/**
* If rectangles a and b intersect, return true and set this rectangle to
* that intersection, otherwise return false and do not change this
@@ -406,7 +406,7 @@
}
return false;
}
-
+
/**
* Returns true if this rectangle intersects the specified rectangle.
* In no event is this rectangle modified. No check is performed to see
@@ -426,7 +426,7 @@
return this.left < right && left < this.right
&& this.top < bottom && top < this.bottom;
}
-
+
/**
* Returns true iff the two specified rectangles intersect. In no event are
* either of the rectangles modified. To record the intersection,
@@ -441,7 +441,7 @@
return a.left < b.right && b.left < a.right
&& a.top < b.bottom && b.top < a.bottom;
}
-
+
/**
* Set the dst integer Rect by rounding this rectangle's coordinates
* to their nearest integer values.
@@ -489,7 +489,7 @@
}
}
}
-
+
/**
* Update this Rect to enclose itself and the specified rectangle. If the
* specified rectangle is empty, nothing is done. If this rectangle is empty
@@ -500,7 +500,7 @@
public void union(@NonNull RectF r) {
union(r.left, r.top, r.right, r.bottom);
}
-
+
/**
* Update this Rect to enclose itself and the [x,y] coordinate. There is no
* check to see that this rectangle is non-empty.
@@ -520,7 +520,7 @@
bottom = y;
}
}
-
+
/**
* Swap top/bottom or left/right if there are flipped (i.e. left > right
* and/or top > bottom). This can be called if
@@ -548,7 +548,7 @@
public int describeContents() {
return 0;
}
-
+
/**
* Write this rectangle to the specified parcel. To restore a rectangle from
* a parcel, use readFromParcel()
@@ -561,7 +561,7 @@
out.writeFloat(right);
out.writeFloat(bottom);
}
-
+
public static final @android.annotation.NonNull Parcelable.Creator<RectF> CREATOR = new Parcelable.Creator<RectF>() {
/**
* Return a new rectangle from the data in the specified parcel.
@@ -572,7 +572,7 @@
r.readFromParcel(in);
return r;
}
-
+
/**
* Return an array of rectangles of the specified size.
*/
@@ -581,7 +581,7 @@
return new RectF[size];
}
};
-
+
/**
* Set the rectangle's coordinates from the data stored in the specified
* parcel. To write a rectangle to a parcel, call writeToParcel().
diff --git a/graphics/java/android/graphics/RenderNode.java b/graphics/java/android/graphics/RenderNode.java
index 2732569..0650b78 100644
--- a/graphics/java/android/graphics/RenderNode.java
+++ b/graphics/java/android/graphics/RenderNode.java
@@ -765,12 +765,12 @@
* Default value is false. See
* {@link #setProjectBackwards(boolean)} for a description of what this entails.
*
- * @param shouldRecieve True if this RenderNode is a projection receiver, false otherwise.
+ * @param shouldReceive True if this RenderNode is a projection receiver, false otherwise.
* Default is false.
* @return True if the value changed, false if the new value was the same as the previous value.
*/
- public boolean setProjectionReceiver(boolean shouldRecieve) {
- return nSetProjectionReceiver(mNativeRenderNode, shouldRecieve);
+ public boolean setProjectionReceiver(boolean shouldReceive) {
+ return nSetProjectionReceiver(mNativeRenderNode, shouldReceive);
}
/**
@@ -1799,7 +1799,7 @@
private static native boolean nSetProjectBackwards(long renderNode, boolean shouldProject);
@CriticalNative
- private static native boolean nSetProjectionReceiver(long renderNode, boolean shouldRecieve);
+ private static native boolean nSetProjectionReceiver(long renderNode, boolean shouldReceive);
@CriticalNative
private static native boolean nSetOutlineRoundRect(long renderNode, int left, int top,
diff --git a/graphics/java/android/graphics/SurfaceTexture.java b/graphics/java/android/graphics/SurfaceTexture.java
index 50b167e..3256f31 100644
--- a/graphics/java/android/graphics/SurfaceTexture.java
+++ b/graphics/java/android/graphics/SurfaceTexture.java
@@ -318,7 +318,7 @@
}
/**
- * Releases the the texture content. This is needed in single buffered mode to allow the image
+ * Releases the texture content. This is needed in single buffered mode to allow the image
* content producer to take ownership of the image buffer.
* <p>
* For more information see {@link #SurfaceTexture(int, boolean)}.
@@ -431,7 +431,7 @@
* error.
* <p>
* Note that while calling this method causes all the buffers to be freed
- * from the perspective of the the SurfaceTexture, if there are additional
+ * from the perspective of the SurfaceTexture, if there are additional
* references on the buffers (e.g. if a buffer is referenced by a client or
* by OpenGL ES as a texture) then those buffer will remain allocated.
* <p>
diff --git a/graphics/java/android/graphics/Typeface.java b/graphics/java/android/graphics/Typeface.java
index 4c4e8fa..fd78816 100644
--- a/graphics/java/android/graphics/Typeface.java
+++ b/graphics/java/android/graphics/Typeface.java
@@ -600,7 +600,7 @@
* {@link #setWeight} and {@link #setItalic}.
*
* If {@link #setWeight} is not called, the fallback family keeps the default weight.
- * Similary, if {@link #setItalic} is not called, the fallback family keeps the default
+ * Similarly, if {@link #setItalic} is not called, the fallback family keeps the default
* italic information. For example, calling {@code builder.setFallback("sans-serif-light")}
* is equivalent to calling {@code builder.setFallback("sans-serif").setWeight(300)} in
* terms of fallback. The default weight and italic information are overridden by calling
@@ -794,7 +794,7 @@
/**
* Returns the maximum capacity of custom fallback families.
*
- * This includes the the first font family passed to the constructor.
+ * This includes the first font family passed to the constructor.
* It is guaranteed that the value will be greater than or equal to 64.
*
* @return the maximum number of font families for the custom fallback
@@ -816,7 +816,7 @@
/**
* Sets a system fallback by name.
*
- * You can specify generic font familiy names or OEM specific family names. If the system
+ * You can specify generic font family names or OEM specific family names. If the system
* don't have a specified fallback, the default fallback is used instead.
* For more information about generic font families, see <a
* href="https://www.w3.org/TR/css-fonts-4/#generic-font-families">CSS specification</a>
diff --git a/graphics/java/android/graphics/Xfermode.java b/graphics/java/android/graphics/Xfermode.java
index 81769e2..6bb22a1 100644
--- a/graphics/java/android/graphics/Xfermode.java
+++ b/graphics/java/android/graphics/Xfermode.java
@@ -16,8 +16,8 @@
// This file was generated from the C++ include file: SkXfermode.h
// Any changes made to this file will be discarded by the build.
-// To change this file, either edit the include, or device/tools/gluemaker/main.cpp,
-// or one of the auxilary file specifications in device/tools/gluemaker.
+// To change this file, either edit the include, or device/tools/gluemaker/main.cpp,
+// or one of the auxiliary file specifications in device/tools/gluemaker.
package android.graphics;
@@ -28,7 +28,7 @@
* Xfermode is the base class for objects that are called to implement custom
* "transfer-modes" in the drawing pipeline. The static function Create(Modes)
* can be called to return an instance of any of the predefined subclasses as
- * specified in the Modes enum. When an Xfermode is assigned to an Paint, then
+ * specified in the Modes enum. When an Xfermode is assigned to a Paint, then
* objects drawn with that paint have the xfermode applied.
*/
public class Xfermode {
diff --git a/graphics/java/android/graphics/YuvImage.java b/graphics/java/android/graphics/YuvImage.java
index ce35b55..b0c7f20 100644
--- a/graphics/java/android/graphics/YuvImage.java
+++ b/graphics/java/android/graphics/YuvImage.java
@@ -63,7 +63,7 @@
private int mWidth;
/**
- * The height of the the image.
+ * The height of the image.
*/
private int mHeight;
diff --git a/graphics/java/android/graphics/drawable/AdaptiveIconDrawable.java b/graphics/java/android/graphics/drawable/AdaptiveIconDrawable.java
index 688425a..7ee7d6b 100644
--- a/graphics/java/android/graphics/drawable/AdaptiveIconDrawable.java
+++ b/graphics/java/android/graphics/drawable/AdaptiveIconDrawable.java
@@ -98,7 +98,7 @@
* extra content to reveal within the clip path when performing affine transformations on the
* layers.
*
- * Each layers will reserve 25% of it's width and height.
+ * Each layers will reserve 25% of its width and height.
*
* As a result, the view port of the layers is smaller than their intrinsic width and height.
*/
diff --git a/graphics/java/android/graphics/drawable/Drawable.java b/graphics/java/android/graphics/drawable/Drawable.java
index 4972e92..7f2feac 100644
--- a/graphics/java/android/graphics/drawable/Drawable.java
+++ b/graphics/java/android/graphics/drawable/Drawable.java
@@ -839,7 +839,7 @@
}
/**
- * Describes the current state, as a union of primitve states, such as
+ * Describes the current state, as a union of primitive states, such as
* {@link android.R.attr#state_focused},
* {@link android.R.attr#state_selected}, etc.
* Some drawables may modify their imagery based on the selected state.
diff --git a/graphics/java/android/graphics/drawable/GradientDrawable.java b/graphics/java/android/graphics/drawable/GradientDrawable.java
index 166a795..29d033e 100644
--- a/graphics/java/android/graphics/drawable/GradientDrawable.java
+++ b/graphics/java/android/graphics/drawable/GradientDrawable.java
@@ -936,7 +936,7 @@
}
/**
- * Retrn the inner radius of the ring
+ * Return the inner radius of the ring
*
* @see #setInnerRadius(int)
* @attr ref android.R.styleable#GradientDrawable_innerRadius
diff --git a/graphics/java/android/graphics/drawable/shapes/PathShape.java b/graphics/java/android/graphics/drawable/shapes/PathShape.java
index 393fdee..299f6d5 100644
--- a/graphics/java/android/graphics/drawable/shapes/PathShape.java
+++ b/graphics/java/android/graphics/drawable/shapes/PathShape.java
@@ -93,7 +93,7 @@
&& Float.compare(pathShape.mStdHeight, mStdHeight) == 0
&& Float.compare(pathShape.mScaleX, mScaleX) == 0
&& Float.compare(pathShape.mScaleY, mScaleY) == 0
- // Path does not have equals implementation but incase it gains one, use it here
+ // Path does not have equals implementation but in case it gains one, use it here
&& Objects.equals(mPath, pathShape.mPath);
}
diff --git a/graphics/java/android/graphics/fonts/Font.java b/graphics/java/android/graphics/fonts/Font.java
index 318aadd..2893177 100644
--- a/graphics/java/android/graphics/fonts/Font.java
+++ b/graphics/java/android/graphics/fonts/Font.java
@@ -789,7 +789,7 @@
return false;
}
- // ByteBuffer#equals compares all bytes which is not performant for e.g HashMap. Since
+ // ByteBuffer#equals compares all bytes which is not performant for e.g. HashMap. Since
// underlying native font object holds buffer address, check if this buffer points exactly
// the same address as a shortcut of equality. For being compatible with of API30 or before,
// check buffer position even if the buffer points the same address.
diff --git a/graphics/java/android/graphics/fonts/FontFileUtil.java b/graphics/java/android/graphics/fonts/FontFileUtil.java
index ff38282..abcafb6 100644
--- a/graphics/java/android/graphics/fonts/FontFileUtil.java
+++ b/graphics/java/android/graphics/fonts/FontFileUtil.java
@@ -34,7 +34,7 @@
*/
public class FontFileUtil {
- private FontFileUtil() {} // Do not instanciate
+ private FontFileUtil() {} // Do not instantiate
/**
* Unpack the weight value from packed integer.
@@ -87,7 +87,7 @@
}
if (weight != -1 && italic != -1) {
- // Both weight/italic style are specifeid by variation settings.
+ // Both weight/italic style are specified by variation settings.
// No need to look into OS/2 table.
// TODO: Good to look HVAR table to check if this font supports wght/ital axes.
return pack(weight, italic == 1);
diff --git a/graphics/java/android/graphics/fonts/SystemFonts.java b/graphics/java/android/graphics/fonts/SystemFonts.java
index a90961e..f727f5b 100644
--- a/graphics/java/android/graphics/fonts/SystemFonts.java
+++ b/graphics/java/android/graphics/fonts/SystemFonts.java
@@ -216,7 +216,7 @@
} else if (defaultFamily != null) {
familyListSet.familyList.add(defaultFamily);
} else {
- // There is no valid for for default fallback. Ignore.
+ // There is no valid for default fallback. Ignore.
}
}
}
diff --git a/graphics/java/android/graphics/text/LineBreaker.java b/graphics/java/android/graphics/text/LineBreaker.java
index 0c6d4bd..d8cf21e 100644
--- a/graphics/java/android/graphics/text/LineBreaker.java
+++ b/graphics/java/android/graphics/text/LineBreaker.java
@@ -376,8 +376,8 @@
* @see LineBreaker#computeLineBreaks
*/
public static class Result {
- // Following two contstant must be synced with minikin's line breaker.
- // TODO(nona): Remove these constatns by introducing native methods.
+ // Following two constants must be synced with minikin's line breaker.
+ // TODO(nona): Remove these constants by introducing native methods.
private static final int TAB_MASK = 0x20000000;
private static final int HYPHEN_MASK = 0xFF;
private static final int START_HYPHEN_MASK = 0x18; // 0b11000
diff --git a/graphics/java/android/graphics/text/PositionedGlyphs.java b/graphics/java/android/graphics/text/PositionedGlyphs.java
index f8328b1..671eb6e 100644
--- a/graphics/java/android/graphics/text/PositionedGlyphs.java
+++ b/graphics/java/android/graphics/text/PositionedGlyphs.java
@@ -139,7 +139,7 @@
* Returns the glyph ID used for drawing the glyph at the given index.
*
* @param index the glyph index
- * @return An glyph ID of the font.
+ * @return A glyph ID of the font.
*/
@IntRange(from = 0)
public int getGlyphId(@IntRange(from = 0) int index) {
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/TransitionUtil.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/TransitionUtil.java
index dcd4062..785e30d 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/TransitionUtil.java
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/TransitionUtil.java
@@ -69,8 +69,12 @@
/** Returns {@code true} if the transition is opening or closing mode. */
public static boolean isOpenOrCloseMode(@TransitionInfo.TransitionMode int mode) {
- return mode == TRANSIT_OPEN || mode == TRANSIT_CLOSE
- || mode == TRANSIT_TO_FRONT || mode == TRANSIT_TO_BACK;
+ return isOpeningMode(mode) || mode == TRANSIT_CLOSE || mode == TRANSIT_TO_BACK;
+ }
+
+ /** Returns {@code true} if the transition is opening mode. */
+ public static boolean isOpeningMode(@TransitionInfo.TransitionMode int mode) {
+ return mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT;
}
/** Returns {@code true} if the transition has a display change. */
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 9e6c5fb..d8c1cab 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
@@ -1170,7 +1170,9 @@
* @param bubbleKey key of the bubble being dragged
*/
public void startBubbleDrag(String bubbleKey) {
- onBubbleDrag(bubbleKey, true /* isBeingDragged */);
+ if (mBubbleData.getSelectedBubble() != null) {
+ mBubbleBarViewCallback.expansionChanged(/* isExpanded = */ false);
+ }
if (mBubbleStateListener != null) {
boolean overflow = BubbleOverflow.KEY.equals(bubbleKey);
Rect rect = new Rect();
@@ -1183,23 +1185,29 @@
}
/**
- * A bubble is no longer being dragged in Launcher. As was released in given location.
+ * A bubble is no longer being dragged in Launcher. And was released in given location.
* Will be called only when bubble bar is expanded.
*
- * @param bubbleKey key of the bubble being dragged
* @param location location where bubble was released
*/
- public void stopBubbleDrag(String bubbleKey, BubbleBarLocation location) {
+ public void stopBubbleDrag(BubbleBarLocation location) {
mBubblePositioner.setBubbleBarLocation(location);
- onBubbleDrag(bubbleKey, false /* isBeingDragged */);
+ if (mBubbleData.getSelectedBubble() != null) {
+ mBubbleBarViewCallback.expansionChanged(/* isExpanded = */ true);
+ }
}
- private void onBubbleDrag(String bubbleKey, boolean isBeingDragged) {
- // TODO(b/330585402): collapse stack if any bubble is dragged
- if (mBubbleData.getSelectedBubble() != null
- && mBubbleData.getSelectedBubble().getKey().equals(bubbleKey)) {
- // Should collapse/expand only if equals to selected bubble.
- mBubbleBarViewCallback.expansionChanged(/* isExpanded = */ !isBeingDragged);
+ /**
+ * A bubble was dragged and is released in dismiss target in Launcher.
+ *
+ * @param bubbleKey key of the bubble being dragged to dismiss target
+ */
+ public void dragBubbleToDismiss(String bubbleKey) {
+ String selectedBubbleKey = mBubbleData.getSelectedBubbleKey();
+ removeBubble(bubbleKey, Bubbles.DISMISS_USER_GESTURE);
+ if (selectedBubbleKey != null && !selectedBubbleKey.equals(bubbleKey)) {
+ // We did not remove the selected bubble. Expand it again
+ mBubbleBarViewCallback.expansionChanged(/* isExpanded = */ true);
}
}
@@ -2358,12 +2366,6 @@
}
@Override
- public void removeBubble(String key) {
- mMainExecutor.execute(
- () -> mController.removeBubble(key, Bubbles.DISMISS_USER_GESTURE));
- }
-
- @Override
public void removeAllBubbles() {
mMainExecutor.execute(() -> mController.removeAllBubbles(Bubbles.DISMISS_USER_GESTURE));
}
@@ -2379,8 +2381,13 @@
}
@Override
- public void stopBubbleDrag(String bubbleKey, BubbleBarLocation location) {
- mMainExecutor.execute(() -> mController.stopBubbleDrag(bubbleKey, location));
+ public void stopBubbleDrag(BubbleBarLocation location) {
+ mMainExecutor.execute(() -> mController.stopBubbleDrag(location));
+ }
+
+ @Override
+ public void dragBubbleToDismiss(String key) {
+ mMainExecutor.execute(() -> mController.dragBubbleToDismiss(key));
}
@Override
@@ -2397,7 +2404,10 @@
@Override
public void setBubbleBarBounds(Rect bubbleBarBounds) {
- mMainExecutor.execute(() -> mBubblePositioner.setBubbleBarBounds(bubbleBarBounds));
+ mMainExecutor.execute(() -> {
+ mBubblePositioner.setBubbleBarBounds(bubbleBarBounds);
+ if (mLayerView != null) mLayerView.updateExpandedView();
+ });
}
}
@@ -2709,6 +2719,15 @@
() -> BubbleController.this.onSensitiveNotificationProtectionStateChanged(
sensitiveNotificationProtectionActive));
}
+
+ @Override
+ public boolean canShowBubbleNotification() {
+ // in bubble bar mode, when the IME is visible we can't animate new bubbles.
+ if (BubbleController.this.isShowingAsBubbleBar()) {
+ return !BubbleController.this.mBubblePositioner.getIsImeVisible();
+ }
+ return true;
+ }
}
/**
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 ea30af5..26483c8 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
@@ -327,6 +327,14 @@
return mSelectedBubble;
}
+ /**
+ * Returns the key of the selected bubble, or null if no bubble is selected.
+ */
+ @Nullable
+ public String getSelectedBubbleKey() {
+ return mSelectedBubble != null ? mSelectedBubble.getKey() : null;
+ }
+
public BubbleOverflow getOverflow() {
return mOverflow;
}
@@ -1228,9 +1236,7 @@
public void dump(PrintWriter pw) {
pw.println("BubbleData state:");
pw.print(" selected: ");
- pw.println(mSelectedBubble != null
- ? mSelectedBubble.getKey()
- : "null");
+ pw.println(getSelectedBubbleKey());
pw.print(" expanded: ");
pw.println(mExpanded);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
index c4bbe32..a35a004 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
@@ -324,6 +324,11 @@
return 0;
}
+ /** Returns whether the IME is visible. */
+ public boolean getIsImeVisible() {
+ return mImeVisible;
+ }
+
/** Sets whether the IME is visible. **/
public void setImeVisible(boolean visible, int height) {
mImeVisible = visible;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
index 322088b..1d053f9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
@@ -297,6 +297,15 @@
boolean sensitiveNotificationProtectionActive);
/**
+ * Determines whether Bubbles can show notifications.
+ *
+ * <p>Normally bubble notifications are shown by Bubbles, but in some cases the bubble
+ * notification is suppressed and should be shown by the Notifications pipeline as regular
+ * notifications.
+ */
+ boolean canShowBubbleNotification();
+
+ /**
* A listener to be notified of bubble state changes, used by launcher to render bubbles in
* its process.
*/
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 66f77fa..1eff149 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
@@ -33,7 +33,7 @@
oneway void showBubble(in String key, in Rect bubbleBarBounds) = 3;
- oneway void removeBubble(in String key) = 4;
+ oneway void dragBubbleToDismiss(in String key) = 4;
oneway void removeAllBubbles() = 5;
@@ -47,5 +47,5 @@
oneway void setBubbleBarBounds(in Rect bubbleBarBounds) = 10;
- oneway void stopBubbleDrag(in String key, in BubbleBarLocation location) = 11;
+ oneway void stopBubbleDrag(in BubbleBarLocation location) = 11;
}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
index a351cef..123cc7e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
@@ -356,7 +356,7 @@
}
/** Updates the expanded view size and position. */
- private void updateExpandedView() {
+ public void updateExpandedView() {
if (mExpandedView == null || mExpandedBubble == null) return;
boolean isOverflowExpanded = mExpandedBubble.getKey().equals(BubbleOverflow.KEY);
mPositioner.getBubbleBarExpandedViewBounds(mPositioner.isBubbleBarOnLeft(),
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
index 414a9d1..01364d1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
@@ -133,6 +133,7 @@
PipBoundsAlgorithm pipBoundsAlgorithm,
@NonNull PipBoundsState pipBoundsState,
@NonNull PipTransitionState pipTransitionState,
+ @NonNull PipScheduler pipScheduler,
@NonNull SizeSpecSource sizeSpecSource,
PipMotionHelper pipMotionHelper,
FloatingContentCoordinator floatingContentCoordinator,
@@ -140,7 +141,7 @@
@ShellMainThread ShellExecutor mainExecutor,
Optional<PipPerfHintController> pipPerfHintControllerOptional) {
return new PipTouchHandler(context, shellInit, menuPhoneController, pipBoundsAlgorithm,
- pipBoundsState, pipTransitionState, sizeSpecSource, pipMotionHelper,
+ pipBoundsState, pipTransitionState, pipScheduler, sizeSpecSource, pipMotionHelper,
floatingContentCoordinator, pipUiEventLogger, mainExecutor,
pipPerfHintControllerOptional);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index e885262..e1657f9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -854,7 +854,8 @@
mPipUiEventLoggerLogger.log(uiEventEnum);
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "onTaskAppeared: %s, state=%s", mTaskInfo.topActivity, mPipTransitionState);
+ "onTaskAppeared: %s, state=%s, taskId=%s", mTaskInfo.topActivity,
+ mPipTransitionState, mTaskInfo.taskId);
if (mPipTransitionState.getInSwipePipToHomeTransition()) {
if (!mWaitForFixedRotation) {
onEndOfSwipePipToHomeTransition();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java
index a097a0f..be10151 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java
@@ -58,7 +58,8 @@
* A helper to animate and manipulate the PiP.
*/
public class PipMotionHelper implements PipAppOpsListener.Callback,
- FloatingContentCoordinator.FloatingContent {
+ FloatingContentCoordinator.FloatingContent,
+ PipTransitionState.PipTransitionStateChangedListener {
private static final String TAG = "PipMotionHelper";
private static final String FLING_BOUNDS_CHANGE = "fling_bounds_change";
private static final boolean DEBUG = false;
@@ -181,7 +182,7 @@
}
};
mPipTransitionState = pipTransitionState;
- mPipTransitionState.addPipTransitionStateChangedListener(this::onPipTransitionStateChanged);
+ mPipTransitionState.addPipTransitionStateChangedListener(this);
}
void init() {
@@ -687,7 +688,8 @@
// setAnimatingToBounds(toBounds);
}
- private void onPipTransitionStateChanged(@PipTransitionState.TransitionState int oldState,
+ @Override
+ public void onPipTransitionStateChanged(@PipTransitionState.TransitionState int oldState,
@PipTransitionState.TransitionState int newState,
@Nullable Bundle extra) {
switch (newState) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipResizeGestureHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipResizeGestureHandler.java
index 04cf350..b55a41d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipResizeGestureHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipResizeGestureHandler.java
@@ -24,6 +24,7 @@
import android.graphics.PointF;
import android.graphics.Rect;
import android.hardware.input.InputManager;
+import android.os.Bundle;
import android.os.Looper;
import android.view.BatchedInputEventReceiver;
import android.view.Choreographer;
@@ -32,6 +33,7 @@
import android.view.InputEventReceiver;
import android.view.InputMonitor;
import android.view.MotionEvent;
+import android.view.SurfaceControl;
import android.view.ViewConfiguration;
import androidx.annotation.VisibleForTesting;
@@ -51,16 +53,20 @@
* Helper on top of PipTouchHandler that handles inputs OUTSIDE of the PIP window, which is used to
* trigger dynamic resize.
*/
-public class PipResizeGestureHandler {
+public class PipResizeGestureHandler implements
+ PipTransitionState.PipTransitionStateChangedListener {
private static final String TAG = "PipResizeGestureHandler";
private static final int PINCH_RESIZE_SNAP_DURATION = 250;
private static final float PINCH_RESIZE_AUTO_MAX_RATIO = 0.9f;
+ private static final String RESIZE_BOUNDS_CHANGE = "resize_bounds_change";
private final Context mContext;
private final PipBoundsAlgorithm mPipBoundsAlgorithm;
private final PipBoundsState mPipBoundsState;
private final PipTouchState mPipTouchState;
+ private final PipScheduler mPipScheduler;
+ private final PipTransitionState mPipTransitionState;
private final PhonePipMenuController mPhonePipMenuController;
private final PipUiEventLogger mPipUiEventLogger;
private final PipPinchResizingAlgorithm mPinchResizingAlgorithm;
@@ -88,6 +94,7 @@
private boolean mIsSysUiStateValid;
private boolean mThresholdCrossed;
private boolean mOngoingPinchToResize = false;
+ private boolean mWaitingForBoundsChangeTransition = false;
private float mAngle = 0;
int mFirstIndex = -1;
int mSecondIndex = -1;
@@ -104,11 +111,17 @@
private int mCtrlType;
private int mOhmOffset;
- public PipResizeGestureHandler(Context context, PipBoundsAlgorithm pipBoundsAlgorithm,
- PipBoundsState pipBoundsState, PipTouchState pipTouchState,
+ public PipResizeGestureHandler(Context context,
+ PipBoundsAlgorithm pipBoundsAlgorithm,
+ PipBoundsState pipBoundsState,
+ PipTouchState pipTouchState,
+ PipScheduler pipScheduler,
+ PipTransitionState pipTransitionState,
Runnable updateMovementBoundsRunnable,
- PipUiEventLogger pipUiEventLogger, PhonePipMenuController menuActivityController,
- ShellExecutor mainExecutor, @Nullable PipPerfHintController pipPerfHintController) {
+ PipUiEventLogger pipUiEventLogger,
+ PhonePipMenuController menuActivityController,
+ ShellExecutor mainExecutor,
+ @Nullable PipPerfHintController pipPerfHintController) {
mContext = context;
mDisplayId = context.getDisplayId();
mMainExecutor = mainExecutor;
@@ -116,6 +129,11 @@
mPipBoundsAlgorithm = pipBoundsAlgorithm;
mPipBoundsState = pipBoundsState;
mPipTouchState = pipTouchState;
+ mPipScheduler = pipScheduler;
+
+ mPipTransitionState = pipTransitionState;
+ mPipTransitionState.addPipTransitionStateChangedListener(this);
+
mUpdateMovementBoundsRunnable = updateMovementBoundsRunnable;
mPhonePipMenuController = menuActivityController;
mPipUiEventLogger = pipUiEventLogger;
@@ -125,6 +143,7 @@
mUserResizeBounds.set(rect);
// mMotionHelper.synchronizePinnedStackBounds();
mUpdateMovementBoundsRunnable.run();
+ mPipBoundsState.setBounds(rect);
resetState();
};
}
@@ -202,7 +221,7 @@
@VisibleForTesting
void onInputEvent(InputEvent ev) {
if (!mEnablePinchResize) {
- // No need to handle anything if neither form of resizing is enabled.
+ // No need to handle anything if resizing isn't enabled.
return;
}
@@ -227,7 +246,7 @@
}
}
- if (mEnablePinchResize && mOngoingPinchToResize) {
+ if (mOngoingPinchToResize) {
onPinchResize(mv);
}
}
@@ -249,13 +268,11 @@
}
boolean willStartResizeGesture(MotionEvent ev) {
- if (isInValidSysUiState()) {
- if (ev.getActionMasked() == MotionEvent.ACTION_POINTER_DOWN) {
- if (mEnablePinchResize && ev.getPointerCount() == 2) {
- onPinchResize(ev);
- mOngoingPinchToResize = mAllowGesture;
- return mAllowGesture;
- }
+ if (ev.getActionMasked() == MotionEvent.ACTION_POINTER_DOWN) {
+ if (mEnablePinchResize && ev.getPointerCount() == 2) {
+ onPinchResize(ev);
+ mOngoingPinchToResize = mAllowGesture;
+ return mAllowGesture;
}
}
return false;
@@ -284,7 +301,6 @@
mSecondIndex = -1;
mAllowGesture = false;
finishResize();
- cleanUpHighPerfSessionMaybe();
}
if (ev.getPointerCount() != 2) {
@@ -347,10 +363,7 @@
mDownSecondPoint, mLastPoint, mLastSecondPoint, mMinSize, mMaxSize,
mDownBounds, mLastResizeBounds);
- /*
- mPipTaskOrganizer.scheduleUserResizePip(mDownBounds, mLastResizeBounds,
- mAngle, null);
- */
+ mPipScheduler.scheduleUserResizePip(mLastResizeBounds, mAngle);
mPipBoundsState.setHasUserResizedPip(true);
}
}
@@ -399,57 +412,43 @@
}
private void finishResize() {
- if (!mLastResizeBounds.isEmpty()) {
- // Pinch-to-resize needs to re-calculate snap fraction and animate to the snapped
- // position correctly. Drag-resize does not need to move, so just finalize resize.
- if (mOngoingPinchToResize) {
- final Rect startBounds = new Rect(mLastResizeBounds);
- // If user resize is pretty close to max size, just auto resize to max.
- if (mLastResizeBounds.width() >= PINCH_RESIZE_AUTO_MAX_RATIO * mMaxSize.x
- || mLastResizeBounds.height() >= PINCH_RESIZE_AUTO_MAX_RATIO * mMaxSize.y) {
- resizeRectAboutCenter(mLastResizeBounds, mMaxSize.x, mMaxSize.y);
- }
-
- // If user resize is smaller than min size, auto resize to min
- if (mLastResizeBounds.width() < mMinSize.x
- || mLastResizeBounds.height() < mMinSize.y) {
- resizeRectAboutCenter(mLastResizeBounds, mMinSize.x, mMinSize.y);
- }
-
- // get the current movement bounds
- final Rect movementBounds = mPipBoundsAlgorithm
- .getMovementBounds(mLastResizeBounds);
-
- // snap mLastResizeBounds to the correct edge based on movement bounds
- snapToMovementBoundsEdge(mLastResizeBounds, movementBounds);
-
- final float snapFraction = mPipBoundsAlgorithm.getSnapFraction(
- mLastResizeBounds, movementBounds);
- mPipBoundsAlgorithm.applySnapFraction(mLastResizeBounds, snapFraction);
-
- // disable any touch events beyond resizing too
- mPipTouchState.setAllowInputEvents(false);
-
- /*
- mPipTaskOrganizer.scheduleAnimateResizePip(startBounds, mLastResizeBounds,
- PINCH_RESIZE_SNAP_DURATION, mAngle, mUpdateResizeBoundsCallback, () -> {
- // enable touch events
- mPipTouchState.setAllowInputEvents(true);
- });
- */
- } else {
- /*
- mPipTaskOrganizer.scheduleFinishResizePip(mLastResizeBounds,
- TRANSITION_DIRECTION_USER_RESIZE,
- mUpdateResizeBoundsCallback);
- */
- }
- final float magnetRadiusPercent = (float) mLastResizeBounds.width() / mMinSize.x / 2.f;
- mPipUiEventLogger.log(
- PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_RESIZE);
- } else {
+ if (mLastResizeBounds.isEmpty()) {
resetState();
}
+ if (!mOngoingPinchToResize) {
+ return;
+ }
+ final Rect startBounds = new Rect(mLastResizeBounds);
+
+ // If user resize is pretty close to max size, just auto resize to max.
+ if (mLastResizeBounds.width() >= PINCH_RESIZE_AUTO_MAX_RATIO * mMaxSize.x
+ || mLastResizeBounds.height() >= PINCH_RESIZE_AUTO_MAX_RATIO * mMaxSize.y) {
+ resizeRectAboutCenter(mLastResizeBounds, mMaxSize.x, mMaxSize.y);
+ }
+
+ // If user resize is smaller than min size, auto resize to min
+ if (mLastResizeBounds.width() < mMinSize.x
+ || mLastResizeBounds.height() < mMinSize.y) {
+ resizeRectAboutCenter(mLastResizeBounds, mMinSize.x, mMinSize.y);
+ }
+
+ // get the current movement bounds
+ final Rect movementBounds = mPipBoundsAlgorithm
+ .getMovementBounds(mLastResizeBounds);
+
+ // snap mLastResizeBounds to the correct edge based on movement bounds
+ snapToMovementBoundsEdge(mLastResizeBounds, movementBounds);
+
+ final float snapFraction = mPipBoundsAlgorithm.getSnapFraction(
+ mLastResizeBounds, movementBounds);
+ mPipBoundsAlgorithm.applySnapFraction(mLastResizeBounds, snapFraction);
+
+ // Update the transition state to schedule a resize transition.
+ Bundle extra = new Bundle();
+ extra.putBoolean(RESIZE_BOUNDS_CHANGE, true);
+ mPipTransitionState.setState(PipTransitionState.SCHEDULED_BOUNDS_CHANGE, extra);
+
+ mPipUiEventLogger.log(PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_RESIZE);
}
private void resetState() {
@@ -509,6 +508,40 @@
rect.set(l, t, r, b);
}
+ @Override
+ public void onPipTransitionStateChanged(@PipTransitionState.TransitionState int oldState,
+ @PipTransitionState.TransitionState int newState, @Nullable Bundle extra) {
+ switch (newState) {
+ case PipTransitionState.SCHEDULED_BOUNDS_CHANGE:
+ if (!extra.getBoolean(RESIZE_BOUNDS_CHANGE)) break;
+ mWaitingForBoundsChangeTransition = true;
+ mPipScheduler.scheduleAnimateResizePip(mLastResizeBounds);
+ break;
+ case PipTransitionState.CHANGING_PIP_BOUNDS:
+ if (!mWaitingForBoundsChangeTransition) break;
+
+ // If bounds change transition was scheduled from this class, handle leash updates.
+ mWaitingForBoundsChangeTransition = false;
+
+ SurfaceControl.Transaction startTx = extra.getParcelable(
+ PipTransition.PIP_START_TX, SurfaceControl.Transaction.class);
+ Rect destinationBounds = extra.getParcelable(
+ PipTransition.PIP_DESTINATION_BOUNDS, Rect.class);
+ startTx.setPosition(mPipTransitionState.mPinnedTaskLeash,
+ destinationBounds.left, destinationBounds.top);
+ startTx.apply();
+
+ // All motion operations have actually finished, so make bounds cache updates.
+ cleanUpHighPerfSessionMaybe();
+
+ // Setting state to CHANGED_PIP_BOUNDS applies finishTx and notifies Core.
+ mPipTransitionState.setState(PipTransitionState.CHANGED_PIP_BOUNDS);
+
+ mUpdateResizeBoundsCallback.accept(destinationBounds);
+ break;
+ }
+ }
+
/**
* Dumps the {@link PipResizeGestureHandler} state.
*/
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java
index c5b0de3..4947507 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java
@@ -24,6 +24,7 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.graphics.Matrix;
import android.graphics.Rect;
import android.view.SurfaceControl;
import android.window.WindowContainerTransaction;
@@ -165,6 +166,16 @@
* {@link WindowContainerTransaction}.
*/
public void scheduleUserResizePip(Rect toBounds) {
+ scheduleUserResizePip(toBounds, 0f /* degrees */);
+ }
+
+ /**
+ * Directly perform a scaled matrix transformation on the leash. This will not perform any
+ * {@link WindowContainerTransaction}.
+ *
+ * @param degrees the angle to rotate the bounds to.
+ */
+ public void scheduleUserResizePip(Rect toBounds, float degrees) {
if (toBounds.isEmpty()) {
ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: Attempted to user resize PIP to empty bounds, aborting.", TAG);
@@ -172,7 +183,16 @@
}
SurfaceControl leash = mPipTransitionState.mPinnedTaskLeash;
final SurfaceControl.Transaction tx = new SurfaceControl.Transaction();
- tx.setPosition(leash, toBounds.left, toBounds.top);
+
+ Matrix transformTensor = new Matrix();
+ final float[] mMatrixTmp = new float[9];
+ final float scale = (float) toBounds.width() / mPipBoundsState.getBounds().width();
+
+ transformTensor.setScale(scale, scale);
+ transformTensor.postTranslate(toBounds.left, toBounds.top);
+ transformTensor.postRotate(degrees, toBounds.centerX(), toBounds.centerY());
+
+ tx.setMatrix(leash, transformTensor, mMatrixTmp);
tx.apply();
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java
index 9c6e3ea..319d199 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java
@@ -73,7 +73,7 @@
* Manages all the touch handling for PIP on the Phone, including moving, dismissing and expanding
* the PIP.
*/
-public class PipTouchHandler {
+public class PipTouchHandler implements PipTransitionState.PipTransitionStateChangedListener {
private static final String TAG = "PipTouchHandler";
private static final float DEFAULT_STASH_VELOCITY_THRESHOLD = 18000.f;
@@ -84,6 +84,7 @@
private final PipBoundsAlgorithm mPipBoundsAlgorithm;
@NonNull private final PipBoundsState mPipBoundsState;
@NonNull private final PipTransitionState mPipTransitionState;
+ @NonNull private final PipScheduler mPipScheduler;
@NonNull private final SizeSpecSource mSizeSpecSource;
private final PipUiEventLogger mPipUiEventLogger;
private final PipDismissTargetHandler mPipDismissTargetHandler;
@@ -173,6 +174,7 @@
PipBoundsAlgorithm pipBoundsAlgorithm,
@NonNull PipBoundsState pipBoundsState,
@NonNull PipTransitionState pipTransitionState,
+ @NonNull PipScheduler pipScheduler,
@NonNull SizeSpecSource sizeSpecSource,
PipMotionHelper pipMotionHelper,
FloatingContentCoordinator floatingContentCoordinator,
@@ -188,6 +190,7 @@
mPipTransitionState = pipTransitionState;
mPipTransitionState.addPipTransitionStateChangedListener(this::onPipTransitionStateChanged);
+ mPipScheduler = pipScheduler;
mSizeSpecSource = sizeSpecSource;
mMenuController = menuController;
mPipUiEventLogger = pipUiEventLogger;
@@ -213,10 +216,10 @@
},
menuController::hideMenu,
mainExecutor);
- mPipResizeGestureHandler =
- new PipResizeGestureHandler(context, pipBoundsAlgorithm, pipBoundsState,
- mTouchState, this::updateMovementBounds, pipUiEventLogger,
- menuController, mainExecutor, mPipPerfHintController);
+ mPipResizeGestureHandler = new PipResizeGestureHandler(context, pipBoundsAlgorithm,
+ pipBoundsState, mTouchState, mPipScheduler, mPipTransitionState,
+ this::updateMovementBounds, pipUiEventLogger, menuController, mainExecutor,
+ mPipPerfHintController);
mPipBoundsState.addOnAspectRatioChangedCallback(this::updateMinMaxSize);
if (PipUtils.isPip2ExperimentEnabled()) {
@@ -1075,7 +1078,8 @@
mPipResizeGestureHandler.setOhmOffset(offset);
}
- private void onPipTransitionStateChanged(@PipTransitionState.TransitionState int oldState,
+ @Override
+ public void onPipTransitionStateChanged(@PipTransitionState.TransitionState int oldState,
@PipTransitionState.TransitionState int newState,
@Nullable Bundle extra) {
switch (newState) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index b10176d..8e97068 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -47,6 +47,7 @@
import static com.android.wm.shell.common.split.SplitScreenUtils.splitFailureMessage;
import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN;
import static com.android.wm.shell.shared.TransitionUtil.isClosingType;
+import static com.android.wm.shell.shared.TransitionUtil.isOpeningMode;
import static com.android.wm.shell.shared.TransitionUtil.isOpeningType;
import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_MAIN;
import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_SIDE;
@@ -67,6 +68,7 @@
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP;
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_UNKNOWN;
import static com.android.wm.shell.splitscreen.SplitScreenController.exitReasonToString;
+import static com.android.wm.shell.transition.MixedTransitionHelper.getPipReplacingChange;
import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS;
import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE;
import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_SCREEN_PAIR_OPEN;
@@ -2836,7 +2838,7 @@
mSplitLayout.setFreezeDividerWindow(false);
final StageChangeRecord record = new StageChangeRecord();
final int transitType = info.getType();
- boolean hasEnteringPip = false;
+ TransitionInfo.Change pipChange = null;
for (int iC = 0; iC < info.getChanges().size(); ++iC) {
final TransitionInfo.Change change = info.getChanges().get(iC);
if (change.getMode() == TRANSIT_CHANGE
@@ -2847,7 +2849,7 @@
}
if (mMixedHandler.isEnteringPip(change, transitType)) {
- hasEnteringPip = true;
+ pipChange = change;
}
final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
@@ -2899,9 +2901,19 @@
}
}
- if (hasEnteringPip) {
+ if (pipChange != null) {
+ TransitionInfo.Change pipReplacingChange = getPipReplacingChange(info, pipChange,
+ mMainStage.mRootTaskInfo.taskId, mSideStage.mRootTaskInfo.taskId,
+ getSplitItemStage(pipChange.getLastParent()));
+ if (pipReplacingChange != null) {
+ // Set an enter transition for when startAnimation gets called again
+ mSplitTransitions.setEnterTransition(transition, /*remoteTransition*/ null,
+ TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE, /*resizeAnim*/ false);
+ }
+
mMixedHandler.animatePendingEnterPipFromSplit(transition, info,
- startTransaction, finishTransaction, finishCallback);
+ startTransaction, finishTransaction, finishCallback,
+ pipReplacingChange != null);
notifySplitAnimationFinished();
return true;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
index 1e305c5..f77c80d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
@@ -177,9 +177,11 @@
@Override
@CallSuper
public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) {
- ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onTaskAppeared: task=%d taskParent=%d rootTask=%d",
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onTaskAppeared: taskId=%d taskParent=%d rootTask=%d "
+ + "taskActivity=%s",
taskInfo.taskId, taskInfo.parentTaskId,
- mRootTaskInfo != null ? mRootTaskInfo.taskId : -1);
+ mRootTaskInfo != null ? mRootTaskInfo.taskId : -1,
+ taskInfo.baseActivity);
if (mRootTaskInfo == null) {
mRootLeash = leash;
mRootTaskInfo = taskInfo;
@@ -213,6 +215,8 @@
@Override
@CallSuper
public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) {
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onTaskInfoChanged: taskId=%d taskAct=%s",
+ taskInfo.taskId, taskInfo.baseActivity);
mWindowDecorViewModel.ifPresent(viewModel -> viewModel.onTaskInfoChanged(taskInfo));
if (mRootTaskInfo.taskId == taskInfo.taskId) {
// Inflates split decor view only when the root task is visible.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
index 968b27b..bcacecb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
@@ -77,6 +77,7 @@
private ActivityEmbeddingController mActivityEmbeddingController;
abstract static class MixedTransition {
+ /** Entering Pip from split, breaks split. */
static final int TYPE_ENTER_PIP_FROM_SPLIT = 1;
/** Both the display and split-state (enter/exit) is changing */
@@ -103,6 +104,9 @@
/** Enter pip from one of the Activity Embedding windows. */
static final int TYPE_ENTER_PIP_FROM_ACTIVITY_EMBEDDING = 9;
+ /** Entering Pip from split, but replace the Pip stage instead of breaking split. */
+ static final int TYPE_ENTER_PIP_REPLACE_FROM_SPLIT = 10;
+
/** The default animation for this mixed transition. */
static final int ANIM_TYPE_DEFAULT = 0;
@@ -484,9 +488,11 @@
// TODO(b/287704263): Remove when split/mixed are reversed.
public boolean animatePendingEnterPipFromSplit(IBinder transition, TransitionInfo info,
SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT,
- Transitions.TransitionFinishCallback finishCallback) {
- final MixedTransition mixed = createDefaultMixedTransition(
- MixedTransition.TYPE_ENTER_PIP_FROM_SPLIT, transition);
+ Transitions.TransitionFinishCallback finishCallback, boolean replacingPip) {
+ int type = replacingPip
+ ? MixedTransition.TYPE_ENTER_PIP_REPLACE_FROM_SPLIT
+ : MixedTransition.TYPE_ENTER_PIP_FROM_SPLIT;
+ final MixedTransition mixed = createDefaultMixedTransition(type, transition);
mActiveTransitions.add(mixed);
Transitions.TransitionFinishCallback callback = wct -> {
mActiveTransitions.remove(mixed);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedTransition.java
index b028bd6..0ada749 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedTransition.java
@@ -76,7 +76,12 @@
info, startTransaction, finishTransaction, finishCallback);
case TYPE_ENTER_PIP_FROM_SPLIT ->
animateEnterPipFromSplit(this, info, startTransaction, finishTransaction,
- finishCallback, mPlayer, mMixedHandler, mPipHandler, mSplitHandler);
+ finishCallback, mPlayer, mMixedHandler, mPipHandler, mSplitHandler,
+ /*replacingPip*/ false);
+ case TYPE_ENTER_PIP_REPLACE_FROM_SPLIT ->
+ animateEnterPipFromSplit(this, info, startTransaction, finishTransaction,
+ finishCallback, mPlayer, mMixedHandler, mPipHandler, mSplitHandler,
+ /*replacingPip*/ true);
case TYPE_KEYGUARD ->
animateKeyguard(this, info, startTransaction, finishTransaction, finishCallback,
mKeyguardHandler, mPipHandler);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/MixedTransitionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/MixedTransitionHelper.java
index ffc0b76..e8b01b5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/MixedTransitionHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/MixedTransitionHelper.java
@@ -23,11 +23,15 @@
import static com.android.wm.shell.common.split.SplitScreenConstants.FLAG_IS_DIVIDER_BAR;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
import static com.android.wm.shell.pip.PipAnimationController.ANIM_TYPE_ALPHA;
+import static com.android.wm.shell.shared.TransitionUtil.isOpeningMode;
+import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_MAIN;
+import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_SIDE;
import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED;
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_CHILD_TASK_ENTER_PIP;
import static com.android.wm.shell.transition.DefaultMixedHandler.subCopy;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.view.SurfaceControl;
import android.window.TransitionInfo;
@@ -45,7 +49,8 @@
@NonNull SurfaceControl.Transaction finishTransaction,
@NonNull Transitions.TransitionFinishCallback finishCallback,
@NonNull Transitions player, @NonNull MixedTransitionHandler mixedHandler,
- @NonNull PipTransitionController pipHandler, @NonNull StageCoordinator splitHandler) {
+ @NonNull PipTransitionController pipHandler, @NonNull StageCoordinator splitHandler,
+ boolean replacingPip) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Animating a mixed transition for "
+ "entering PIP while Split-Screen is foreground.");
TransitionInfo.Change pipChange = null;
@@ -99,7 +104,7 @@
// we need a separate one to send over to launcher.
SurfaceControl.Transaction otherStartT = new SurfaceControl.Transaction();
@SplitScreen.StageType int topStageToKeep = STAGE_TYPE_UNDEFINED;
- if (splitHandler.isSplitScreenVisible()) {
+ if (splitHandler.isSplitScreenVisible() && !replacingPip) {
// The non-going home case, we could be pip-ing one of the split stages and keep
// showing the other
for (int i = info.getChanges().size() - 1; i >= 0; --i) {
@@ -115,11 +120,12 @@
break;
}
}
+
+ // Let split update internal state for dismiss.
+ splitHandler.prepareDismissAnimation(topStageToKeep,
+ EXIT_REASON_CHILD_TASK_ENTER_PIP, everythingElse, otherStartT,
+ finishTransaction);
}
- // Let split update internal state for dismiss.
- splitHandler.prepareDismissAnimation(topStageToKeep,
- EXIT_REASON_CHILD_TASK_ENTER_PIP, everythingElse, otherStartT,
- finishTransaction);
// We are trying to accommodate launcher's close animation which can't handle the
// divider-bar, so if split-handler is closing the divider-bar, just hide it and
@@ -152,6 +158,44 @@
return true;
}
+ /**
+ * Check to see if we're only closing split to enter pip or if we're replacing pip with
+ * another task. If we are replacing, this will return the change for the task we are replacing
+ * pip with
+ *
+ * @param info Any number of changes
+ * @param pipChange TransitionInfo.Change indicating the task that is being pipped
+ * @param splitMainStageRootId MainStage's rootTaskInfo's id
+ * @param splitSideStageRootId SideStage's rootTaskInfo's id
+ * @param lastPipSplitStage The last stage that {@param pipChange} was in
+ * @return The change from {@param info} that is replacing the {@param pipChange}, {@code null}
+ * otherwise
+ */
+ @Nullable
+ public static TransitionInfo.Change getPipReplacingChange(TransitionInfo info,
+ TransitionInfo.Change pipChange, int splitMainStageRootId, int splitSideStageRootId,
+ @SplitScreen.StageType int lastPipSplitStage) {
+ int lastPipParentTask = -1;
+ if (lastPipSplitStage == STAGE_TYPE_MAIN) {
+ lastPipParentTask = splitMainStageRootId;
+ } else if (lastPipSplitStage == STAGE_TYPE_SIDE) {
+ lastPipParentTask = splitSideStageRootId;
+ }
+
+ for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+ TransitionInfo.Change change = info.getChanges().get(i);
+ if (change == pipChange || !isOpeningMode(change.getMode())) {
+ // Ignore the change/task that's going into Pip or not opening
+ continue;
+ }
+
+ if (change.getTaskInfo().parentTaskId == lastPipParentTask) {
+ return change;
+ }
+ }
+ return null;
+ }
+
private static boolean isHomeOpening(@NonNull TransitionInfo.Change change) {
return change.getTaskInfo() != null
&& change.getTaskInfo().getActivityType() == ACTIVITY_TYPE_HOME;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RecentsMixedTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RecentsMixedTransition.java
index d6e64cf..9fc6702 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RecentsMixedTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RecentsMixedTransition.java
@@ -142,7 +142,8 @@
&& mSplitHandler.getSplitItemPosition(change.getLastParent())
!= SPLIT_POSITION_UNDEFINED) {
return animateEnterPipFromSplit(this, info, startTransaction, finishTransaction,
- finishCallback, mPlayer, mMixedHandler, mPipHandler, mSplitHandler);
+ finishCallback, mPlayer, mMixedHandler, mPipHandler, mSplitHandler,
+ /*replacingPip*/ false);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
index 4d3c763..6224543 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
@@ -31,7 +31,6 @@
import static android.window.TransitionInfo.FLAG_BACK_GESTURE_ANIMATED;
import static android.window.TransitionInfo.FLAG_IS_BEHIND_STARTING_WINDOW;
import static android.window.TransitionInfo.FLAG_IS_OCCLUDED;
-import static android.window.TransitionInfo.FLAG_MOVED_TO_TOP;
import static android.window.TransitionInfo.FLAG_NO_ANIMATION;
import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT;
@@ -567,15 +566,15 @@
final int mode = change.getMode();
// Put all the OPEN/SHOW on top
if (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT) {
- if (isOpening
- // This is for when an activity launches while a different transition is
- // collecting.
- || change.hasFlags(FLAG_MOVED_TO_TOP)) {
+ if (isOpening) {
// put on top
return zSplitLine + numChanges - i;
- } else {
+ } else if (isClosing) {
// put on bottom
return zSplitLine - i;
+ } else {
+ // maintain relative ordering (put all changes in the animating layer)
+ return zSplitLine + numChanges - i;
}
} else if (mode == TRANSIT_CLOSE || mode == TRANSIT_TO_BACK) {
if (isOpening) {
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index 753a699..7c1c5b4 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -336,6 +336,7 @@
"jni/android_graphics_animation_NativeInterpolatorFactory.cpp",
"jni/android_graphics_animation_RenderNodeAnimator.cpp",
"jni/android_graphics_Canvas.cpp",
+ "jni/android_graphics_Color.cpp",
"jni/android_graphics_ColorSpace.cpp",
"jni/android_graphics_drawable_AnimatedVectorDrawable.cpp",
"jni/android_graphics_drawable_VectorDrawable.cpp",
diff --git a/libs/hwui/apex/LayoutlibLoader.cpp b/libs/hwui/apex/LayoutlibLoader.cpp
index fd9915a..70a9ef0 100644
--- a/libs/hwui/apex/LayoutlibLoader.cpp
+++ b/libs/hwui/apex/LayoutlibLoader.cpp
@@ -46,6 +46,7 @@
extern int register_android_graphics_Canvas(JNIEnv* env);
extern int register_android_graphics_CanvasProperty(JNIEnv* env);
+extern int register_android_graphics_Color(JNIEnv* env);
extern int register_android_graphics_ColorFilter(JNIEnv* env);
extern int register_android_graphics_ColorSpace(JNIEnv* env);
extern int register_android_graphics_DrawFilter(JNIEnv* env);
@@ -87,6 +88,7 @@
{"android.graphics.Camera", REG_JNI(register_android_graphics_Camera)},
{"android.graphics.Canvas", REG_JNI(register_android_graphics_Canvas)},
{"android.graphics.CanvasProperty", REG_JNI(register_android_graphics_CanvasProperty)},
+ {"android.graphics.Color", REG_JNI(register_android_graphics_Color)},
{"android.graphics.ColorFilter", REG_JNI(register_android_graphics_ColorFilter)},
{"android.graphics.ColorSpace", REG_JNI(register_android_graphics_ColorSpace)},
{"android.graphics.CreateJavaOutputStreamAdaptor",
diff --git a/libs/hwui/apex/jni_runtime.cpp b/libs/hwui/apex/jni_runtime.cpp
index fb0cdb0..6ace396 100644
--- a/libs/hwui/apex/jni_runtime.cpp
+++ b/libs/hwui/apex/jni_runtime.cpp
@@ -49,6 +49,7 @@
extern int register_android_graphics_Canvas(JNIEnv* env);
extern int register_android_graphics_CanvasProperty(JNIEnv* env);
extern int register_android_graphics_ColorFilter(JNIEnv* env);
+extern int register_android_graphics_Color(JNIEnv* env);
extern int register_android_graphics_ColorSpace(JNIEnv* env);
extern int register_android_graphics_DrawFilter(JNIEnv* env);
extern int register_android_graphics_FontFamily(JNIEnv* env);
@@ -98,6 +99,7 @@
static const RegJNIRec gRegJNI[] = {
REG_JNI(register_android_graphics_Canvas),
+ REG_JNI(register_android_graphics_Color),
// This needs to be before register_android_graphics_Graphics, or the latter
// will not be able to find the jmethodID for ColorSpace.get().
REG_JNI(register_android_graphics_ColorSpace),
diff --git a/libs/hwui/jni/Shader.cpp b/libs/hwui/jni/Shader.cpp
index a952be0..2a057e7 100644
--- a/libs/hwui/jni/Shader.cpp
+++ b/libs/hwui/jni/Shader.cpp
@@ -36,25 +36,6 @@
return 0; \
}
-static void Color_RGBToHSV(JNIEnv* env, jobject, jint red, jint green, jint blue, jfloatArray hsvArray)
-{
- SkScalar hsv[3];
- SkRGBToHSV(red, green, blue, hsv);
-
- AutoJavaFloatArray autoHSV(env, hsvArray, 3);
- float* values = autoHSV.ptr();
- for (int i = 0; i < 3; i++) {
- values[i] = SkScalarToFloat(hsv[i]);
- }
-}
-
-static jint Color_HSVToColor(JNIEnv* env, jobject, jint alpha, jfloatArray hsvArray)
-{
- AutoJavaFloatArray autoHSV(env, hsvArray, 3);
- SkScalar* hsv = autoHSV.ptr();
- return static_cast<jint>(SkHSVToColor(alpha, hsv));
-}
-
///////////////////////////////////////////////////////////////////////////////////////////////
static void Shader_safeUnref(SkShader* shader) {
@@ -409,11 +390,6 @@
///////////////////////////////////////////////////////////////////////////////////////////////
-static const JNINativeMethod gColorMethods[] = {
- { "nativeRGBToHSV", "(III[F)V", (void*)Color_RGBToHSV },
- { "nativeHSVToColor", "(I[F)I", (void*)Color_HSVToColor }
-};
-
static const JNINativeMethod gShaderMethods[] = {
{ "nativeGetFinalizer", "()J", (void*)Shader_getNativeFinalizer },
};
@@ -456,8 +432,6 @@
int register_android_graphics_Shader(JNIEnv* env)
{
- android::RegisterMethodsOrDie(env, "android/graphics/Color", gColorMethods,
- NELEM(gColorMethods));
android::RegisterMethodsOrDie(env, "android/graphics/Shader", gShaderMethods,
NELEM(gShaderMethods));
android::RegisterMethodsOrDie(env, "android/graphics/BitmapShader", gBitmapShaderMethods,
diff --git a/libs/hwui/jni/android_graphics_Color.cpp b/libs/hwui/jni/android_graphics_Color.cpp
new file mode 100644
index 0000000..c22b8b9
--- /dev/null
+++ b/libs/hwui/jni/android_graphics_Color.cpp
@@ -0,0 +1,55 @@
+/*
+ * 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.
+ */
+
+#include "GraphicsJNI.h"
+
+#include "SkColor.h"
+
+using namespace android;
+
+static void Color_RGBToHSV(JNIEnv* env, jobject, jint red, jint green, jint blue,
+ jfloatArray hsvArray)
+{
+ SkScalar hsv[3];
+ SkRGBToHSV(red, green, blue, hsv);
+
+ AutoJavaFloatArray autoHSV(env, hsvArray, 3);
+ float* values = autoHSV.ptr();
+ for (int i = 0; i < 3; i++) {
+ values[i] = SkScalarToFloat(hsv[i]);
+ }
+}
+
+static jint Color_HSVToColor(JNIEnv* env, jobject, jint alpha, jfloatArray hsvArray)
+{
+ AutoJavaFloatArray autoHSV(env, hsvArray, 3);
+ SkScalar* hsv = autoHSV.ptr();
+ return static_cast<jint>(SkHSVToColor(alpha, hsv));
+}
+
+static const JNINativeMethod gColorMethods[] = {
+ { "nativeRGBToHSV", "(III[F)V", (void*)Color_RGBToHSV },
+ { "nativeHSVToColor", "(I[F)I", (void*)Color_HSVToColor }
+};
+
+namespace android {
+
+int register_android_graphics_Color(JNIEnv* env) {
+ return android::RegisterMethodsOrDie(env, "android/graphics/Color", gColorMethods,
+ NELEM(gColorMethods));
+}
+
+}; // namespace android
diff --git a/libs/hwui/jni/android_graphics_ColorSpace.cpp b/libs/hwui/jni/android_graphics_ColorSpace.cpp
index 63d3f83..d06206b 100644
--- a/libs/hwui/jni/android_graphics_ColorSpace.cpp
+++ b/libs/hwui/jni/android_graphics_ColorSpace.cpp
@@ -148,7 +148,7 @@
namespace android {
int register_android_graphics_ColorSpace(JNIEnv* env) {
- return android::RegisterMethodsOrDie(env, "android/graphics/ColorSpace$Rgb",
+ return android::RegisterMethodsOrDie(env, "android/graphics/ColorSpace$Rgb$Native",
gColorSpaceRgbMethods, NELEM(gColorSpaceRgbMethods));
}
diff --git a/libs/hwui/jni/android_graphics_Matrix.cpp b/libs/hwui/jni/android_graphics_Matrix.cpp
index c0d791a..eedc069 100644
--- a/libs/hwui/jni/android_graphics_Matrix.cpp
+++ b/libs/hwui/jni/android_graphics_Matrix.cpp
@@ -326,9 +326,6 @@
};
static const JNINativeMethod methods[] = {
- {"nGetNativeFinalizer", "()J", (void*) SkMatrixGlue::getNativeFinalizer},
- {"nCreate","(J)J", (void*) SkMatrixGlue::create},
-
// ------- @FastNative below here ---------------
{"nMapPoints","(J[FI[FIIZ)V", (void*) SkMatrixGlue::mapPoints},
{"nMapRect","(JLandroid/graphics/RectF;Landroid/graphics/RectF;)Z",
@@ -388,9 +385,6 @@
int register_android_graphics_Matrix(JNIEnv* env) {
// Methods only used on Ravenwood (for now). See the javadoc on Matrix$ExtraNativesx
// for why we need it.
- //
- // We don't need it on non-ravenwood, but we don't (yet) have a way to detect ravenwood
- // environment, so we just always run it.
RegisterMethodsOrDie(env, "android/graphics/Matrix$ExtraNatives", extra_methods,
NELEM(extra_methods));
diff --git a/nfc/java/android/nfc/cardemulation/HostApduService.java b/nfc/java/android/nfc/cardemulation/HostApduService.java
index f674b06a..c3c74a6 100644
--- a/nfc/java/android/nfc/cardemulation/HostApduService.java
+++ b/nfc/java/android/nfc/cardemulation/HostApduService.java
@@ -20,6 +20,7 @@
import android.annotation.NonNull;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
+import android.annotation.SuppressLint;
import android.app.Service;
import android.content.Intent;
import android.content.pm.PackageManager;
@@ -404,6 +405,7 @@
*
* @param frame A description of the polling frame.
*/
+ @SuppressLint("OnNameExpected")
@FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP)
public void processPollingFrames(@NonNull List<PollingFrame> frame) {
}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
index d4a8110..7bc25ed 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
@@ -174,11 +174,8 @@
onUserCancel()
} else {
Log.d(Constants.LOG_TAG, "The provider activity was cancelled," +
- " re-displaying our UI.")
- uiState = uiState.copy(
- selectedEntry = null,
- providerActivityState = ProviderActivityState.NOT_APPLICABLE,
- )
+ " re-displaying our UI.")
+ resetUiStateForReLaunch()
}
} else {
if (entry != null) {
@@ -202,6 +199,15 @@
}
}
+ // Resets UI states for any situation that re-launches the UI
+ private fun resetUiStateForReLaunch() {
+ onBiometricPromptStateChange(BiometricPromptState.INACTIVE)
+ uiState = uiState.copy(
+ selectedEntry = null,
+ providerActivityState = ProviderActivityState.NOT_APPLICABLE,
+ )
+ }
+
fun onLastLockedAuthEntryNotFoundError() {
Log.d(Constants.LOG_TAG, "Unable to find the last unlocked entry")
onInternalError()
@@ -502,4 +508,4 @@
fun logUiEvent(uiEventEnum: UiEventEnum) {
this.uiMetrics.log(uiEventEnum, credManRepo.requestInfo?.packageName)
}
-}
\ No newline at end of file
+}
diff --git a/packages/PrintSpooler/TEST_MAPPING b/packages/PrintSpooler/TEST_MAPPING
index 4fa8822..ad3b44f 100644
--- a/packages/PrintSpooler/TEST_MAPPING
+++ b/packages/PrintSpooler/TEST_MAPPING
@@ -8,5 +8,10 @@
}
]
}
+ ],
+ "postsubmit": [
+ {
+ "name": "PrintSpoolerOutOfProcessTests"
+ }
]
}
diff --git a/packages/SettingsLib/DataStore/README.md b/packages/SettingsLib/DataStore/README.md
index 30cb993..a762ad3 100644
--- a/packages/SettingsLib/DataStore/README.md
+++ b/packages/SettingsLib/DataStore/README.md
@@ -1,55 +1,93 @@
# Datastore library
-This library aims to manage datastore in a consistent way.
+This library provides consistent API for data management (including backup,
+restore, and metrics logging) on Android platform.
+
+Notably, it is designed to be flexible and could be utilized for a wide range of
+data store besides the settings preferences.
## Overview
-A datastore is required to extend the `BackupRestoreStorage` class and implement
-either `Observable` or `KeyedObservable` interface, which enforces:
+In the high-level design, a persistent datastore aims to support two key
+characteristics:
-- Backup and restore: Datastore should support
- [data backup](https://developer.android.com/guide/topics/data/backup) to
- preserve user experiences on a new device.
-- Observer pattern: The
- [observer pattern](https://en.wikipedia.org/wiki/Observer_pattern) allows to
- monitor data change in the datastore and
- - trigger
- [BackupManager.dataChanged](https://developer.android.com/reference/android/app/backup/BackupManager#dataChanged\(\))
- automatically.
- - track data change event to log metrics.
- - update internal state and take action.
+- **observable**: triggers backup and metrics logging whenever data is
+ changed.
+- **transferable**: offers users with a seamless experience by backing up and
+ restoring data on to new devices.
+
+More specifically, Android framework supports
+[data backup](https://developer.android.com/guide/topics/data/backup) to
+preserve user experiences on a new device. And the
+[observer pattern](https://en.wikipedia.org/wiki/Observer_pattern) allows to
+monitor data change.
### Backup and restore
-The Android backup framework provides
+Currently, the Android backup framework provides
[BackupAgentHelper](https://developer.android.com/reference/android/app/backup/BackupAgentHelper)
and
[BackupHelper](https://developer.android.com/reference/android/app/backup/BackupHelper)
-to back up a datastore. However, there are several caveats when implement
-`BackupHelper`:
+to facilitate data backup. However, there are several caveats to consider when
+implementing `BackupHelper`:
-- performBackup: The data is updated incrementally but it is not well
+- *performBackup*: The data is updated incrementally but it is not well
documented. The `ParcelFileDescriptor` state parameters are normally ignored
and data is updated even there is no change.
-- restoreEntity: The implementation must take care not to seek or close the
- underlying data source, nor read more than size() bytes from the stream when
- restore (see
+- *restoreEntity*: The implementation must take care not to seek or close the
+ underlying data source, nor read more than `size()` bytes from the stream
+ when restore (see
[BackupDataInputStream](https://developer.android.com/reference/android/app/backup/BackupDataInputStream)).
- It is possible a `BackupHelper` prevents other `BackupHelper`s from
- restoring data.
-- writeNewStateDescription: Existing implementations rarely notice that this
- callback is invoked after all entities are restored, and check if necessary
- data are all restored in `restoreEntity` (e.g.
+ It is possible that a `BackupHelper` interferes with the restore process of
+ other `BackupHelper`s.
+- *writeNewStateDescription*: Existing implementations rarely notice that this
+ callback is invoked after *all* entities are restored. Instead, they check
+ if necessary data are all restored in the `restoreEntity` (e.g.
[BatteryBackupHelper](https://cs.android.com/android/platform/superproject/main/+/main:packages/apps/Settings/src/com/android/settings/fuelgauge/BatteryBackupHelper.java;l=144;drc=cca804e1ed504e2d477be1e3db00fb881ca32736)),
which is not robust sometimes.
-This library provides more clear API and offers some improvements:
+The datastore library will mitigate these problems by providing alternative
+APIs. For instance, library users make use of `InputStream` / `OutputStream` to
+back up and restore data directly.
-- The implementation only needs to focus on the `BackupRestoreEntity`
- interface. The `InputStream` of restore will ensure bounded data are read,
- and close the stream will be no-op.
-- The library computes checksum of the backup data automatically, so that
- unchanged data will not be sent to Android backup system.
+### Observer pattern
+
+In the current implementation, the Android backup framework requires a manual
+call to
+[BackupManager.dataChanged](https://developer.android.com/reference/android/app/backup/BackupManager#dataChanged\(\)).
+However, it's often observed that this API call is forgotten when using
+`SharedPreferences`. Additionally, there's a common need to log metrics when
+data changed. To address these limitations, datastore API employed the observer
+pattern.
+
+### API design and advantages
+
+Datastore must extend the `BackupRestoreStorage` class (subclass of
+[BackupHelper](https://developer.android.com/reference/android/app/backup/BackupHelper)).
+The data in a datastore is group by entity, which is represented by
+`BackupRestoreEntity`. Basically, a datastore implementation only needs to focus
+on the `BackupRestoreEntity`.
+
+If the datastore is key-value based (e.g. `SharedPreferences`), implements the
+`KeyedObservable` interface to offer fine-grained observer. Otherwise,
+implements `Observable`. There are builtin thread-safe implementations of the
+two interfaces (`KeyedDataObservable` / `DataObservable`). If it is Kotlin, use
+delegation to simplify the code.
+
+Keep in mind that the implementation should call `KeyedObservable.notifyChange`
+/ `Observable.notifyChange` whenever internal data is changed, so that the
+registered observer will be notified properly.
+
+For `SharedPreferences` use case, leverage the `SharedPreferencesStorage`
+directly. To back up other file based storage, extend the
+`BackupRestoreFileStorage` class.
+
+Here are some highlights of the library:
+
+- The restore `InputStream` will ensure bounded data are read, and close the
+ stream is no-op. That being said, all entities are isolated.
+- Data checksum is computed automatically, unchanged data will not be sent to
+ Android backup system.
- Data compression is supported:
- ZIP best compression is enabled by default, no extra effort needs to be
taken.
@@ -67,98 +105,159 @@
successfully restored in those older versions. This is achieved by extending
the `BackupRestoreFileStorage` class, and `BackupRestoreFileArchiver` will
treat each file as an entity and do the backup / restore.
-- Manual `BackupManager.dataChanged` call is unnecessary now, the library will
- do the invocation (see next section).
+- Manual `BackupManager.dataChanged` call is unnecessary now, the framework
+ will invoke the API automatically.
-### Observer pattern
+## Usages
-Manual `BackupManager.dataChanged` call is required by current backup framework.
-In practice, it is found that `SharedPreferences` usages foget to invoke the
-API. Besides, there are common use cases to log metrics when data is changed.
-Consequently, observer pattern is employed to resolve the issues.
+This section provides [examples](example/ExampleStorage.kt) of datastore.
-If the datastore is key-value based (e.g. `SharedPreferences`), implements the
-`KeyedObservable` interface to offer fine-grained observer. Otherwise,
-implements `Observable`. The library provides thread-safe implementations
-(`KeyedDataObservable` / `DataObservable`), and Kotlin delegation will be
-helpful.
-
-Keep in mind that the implementation should call `KeyedObservable.notifyChange`
-/ `Observable.notifyChange` whenever internal data is changed, so that the
-registered observer will be notified properly.
-
-## Usage and example
-
-For `SharedPreferences` use case, leverage the `SharedPreferencesStorage`. To
-back up other file based storage, extend the `BackupRestoreFileStorage` class.
-
-Here is an example of customized datastore, which has a string to back up:
+Here is a datastore with a string data:
```kotlin
-class MyDataStore : ObservableBackupRestoreStorage() {
- // Another option is make it a StringEntity type and maintain a String field inside StringEntity
- @Volatile // backup/restore happens on Binder thread
- var data: String? = null
- private set
+class ExampleStorage : ObservableBackupRestoreStorage() {
+ @Volatile // field is manipulated by multiple threads, synchronization might be needed
+ var data: String? = null
+ private set
- fun setData(data: String?) {
- this.data = data
- notifyChange(ChangeReason.UPDATE)
+ @AnyThread
+ fun setData(data: String?) {
+ this.data = data
+ // call notifyChange to trigger backup and metrics logging whenever data is changed
+ if (data != null) {
+ notifyChange(ChangeReason.UPDATE)
+ } else {
+ notifyChange(ChangeReason.DELETE)
}
-
- override val name: String
- get() = "MyData"
-
- override fun createBackupRestoreEntities(): List<BackupRestoreEntity> =
- listOf(StringEntity("data"))
-
- private inner class StringEntity(override val key: String) : BackupRestoreEntity {
- override fun backup(
- backupContext: BackupContext,
- outputStream: OutputStream,
- ) =
- if (data != null) {
- outputStream.write(data!!.toByteArray(UTF_8))
- EntityBackupResult.UPDATE
- } else {
- EntityBackupResult.DELETE
- }
-
- override fun restore(restoreContext: RestoreContext, inputStream: InputStream) {
- data = String(inputStream.readAllBytes(), UTF_8)
- // NOTE: The library will call notifyChange(ChangeReason.RESTORE) for you
- }
- }
-
- override fun onRestoreFinished() {
- // TODO: Update state with the restored data. Use this callback instead "restore()" in case
- // the restore action involves several entities.
- // NOTE: The library will call notifyChange(ChangeReason.RESTORE) for you
- }
-}
-```
-
-In the application class:
-
-```kotlin
-class MyApplication : Application() {
- override fun onCreate() {
- super.onCreate();
- BackupRestoreStorageManager.getInstance(this).add(MyDataStore());
}
-}
-```
-In the custom `BackupAgentHelper` class:
+ override val name: String
+ get() = "ExampleStorage"
-```kotlin
-class MyBackupAgentHelper : BackupAgentHelper() {
- override fun onCreate() {
- BackupRestoreStorageManager.getInstance(this).addBackupAgentHelpers(this);
+ override fun createBackupRestoreEntities(): List<BackupRestoreEntity> =
+ listOf(StringEntity("data"))
+
+ override fun enableRestore(): Boolean {
+ return true // check condition like flag, environment, etc.
+ }
+
+ override fun enableBackup(backupContext: BackupContext): Boolean {
+ return true // check condition like flag, environment, etc.
+ }
+
+ @BinderThread
+ private inner class StringEntity(override val key: String) : BackupRestoreEntity {
+ override fun backup(backupContext: BackupContext, outputStream: OutputStream) =
+ if (data != null) {
+ outputStream.write(data!!.toByteArray(UTF_8))
+ EntityBackupResult.UPDATE
+ } else {
+ EntityBackupResult.DELETE // delete existing backup data
+ }
+
+ override fun restore(restoreContext: RestoreContext, inputStream: InputStream) {
+ // DO NOT call setData API here, which will trigger notifyChange unexpectedly.
+ // Under the hood, the datastore library will call notifyChange(ChangeReason.RESTORE)
+ // later to notify observers.
+ data = String(inputStream.readBytes(), UTF_8)
+ // Handle restored data in onRestoreFinished() callback
+ }
}
override fun onRestoreFinished() {
- BackupRestoreStorageManager.getInstance(this).onRestoreFinished();
+ // TODO: Update state with the restored data. Use this callback instead of "restore()" in
+ // case the restore action involves several entities.
+ // NOTE: The library will call notifyChange(ChangeReason.RESTORE) for you
}
}
```
+
+And this is a datastore with key value data:
+
+```kotlin
+class ExampleKeyValueStorage :
+ BackupRestoreStorage(), KeyedObservable<String> by KeyedDataObservable() {
+ // thread safe data structure
+ private val map = ConcurrentHashMap<String, String>()
+
+ override val name: String
+ get() = "ExampleKeyValueStorage"
+
+ fun updateData(key: String, value: String?) {
+ if (value != null) {
+ map[key] = value
+ notifyChange(ChangeReason.UPDATE)
+ } else {
+ map.remove(key)
+ notifyChange(ChangeReason.DELETE)
+ }
+ }
+
+ override fun createBackupRestoreEntities(): List<BackupRestoreEntity> =
+ listOf(createMapBackupRestoreEntity())
+
+ private fun createMapBackupRestoreEntity() =
+ object : BackupRestoreEntity {
+ override val key: String
+ get() = "map"
+
+ override fun backup(
+ backupContext: BackupContext,
+ outputStream: OutputStream,
+ ): EntityBackupResult {
+ // Use TreeMap to achieve predictable and stable order, so that data will not be
+ // updated to Android backup backend if there is only order change.
+ val copy = TreeMap(map)
+ if (copy.isEmpty()) return EntityBackupResult.DELETE
+ val dataOutputStream = DataOutputStream(outputStream)
+ dataOutputStream.writeInt(copy.size)
+ for ((key, value) in copy) {
+ dataOutputStream.writeUTF(key)
+ dataOutputStream.writeUTF(value)
+ }
+ return EntityBackupResult.UPDATE
+ }
+
+ override fun restore(restoreContext: RestoreContext, inputStream: InputStream) {
+ val dataInputString = DataInputStream(inputStream)
+ repeat(dataInputString.readInt()) {
+ val key = dataInputString.readUTF()
+ val value = dataInputString.readUTF()
+ map[key] = value
+ }
+ }
+ }
+}
+```
+
+All the datastore should be added in the application class:
+
+```kotlin
+class ExampleApplication : Application() {
+ override fun onCreate() {
+ super.onCreate()
+ BackupRestoreStorageManager.getInstance(this)
+ .add(ExampleStorage(), ExampleKeyValueStorage())
+ }
+}
+```
+
+Additionally, inject datastore to the custom `BackupAgentHelper` class:
+
+```kotlin
+class ExampleBackupAgent : BackupAgentHelper() {
+ override fun onCreate() {
+ super.onCreate()
+ BackupRestoreStorageManager.getInstance(this).addBackupAgentHelpers(this)
+ }
+
+ override fun onRestoreFinished() {
+ BackupRestoreStorageManager.getInstance(this).onRestoreFinished()
+ }
+}
+```
+
+## Development
+
+Please preserve the code coverage ratio during development. The current line
+coverage is **100% (444/444)** and branch coverage is **93.6% (176/188)**.
diff --git a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreEntity.kt b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreEntity.kt
index 817ee4c..6720e5c 100644
--- a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreEntity.kt
+++ b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreEntity.kt
@@ -23,7 +23,11 @@
import java.io.InputStream
import java.io.OutputStream
-/** Entity for back up and restore. */
+/**
+ * Entity for back up and restore.
+ *
+ * Note that backup/restore callback is invoked on the binder thread.
+ */
interface BackupRestoreEntity {
/**
* Key of the entity.
@@ -45,9 +49,12 @@
/**
* Backs up the entity.
*
+ * Back up data in predictable order (e.g. use `TreeMap` instead of `HashMap`), otherwise data
+ * will be backed up needlessly.
+ *
* @param backupContext context for backup
* @param outputStream output stream to back up data
- * @return false if backup file is deleted, otherwise true
+ * @return backup result
*/
@BinderThread
@Throws(IOException::class)
diff --git a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreStorage.kt b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreStorage.kt
index 935f9cc..284c97b 100644
--- a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreStorage.kt
+++ b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreStorage.kt
@@ -22,6 +22,7 @@
import android.app.backup.BackupHelper
import android.os.ParcelFileDescriptor
import android.util.Log
+import androidx.annotation.BinderThread
import androidx.annotation.VisibleForTesting
import androidx.collection.MutableScatterMap
import com.google.common.io.ByteStreams
@@ -38,16 +39,22 @@
import java.util.zip.CheckedInputStream
import java.util.zip.CheckedOutputStream
import java.util.zip.Checksum
+import javax.annotation.concurrent.ThreadSafe
internal const val LOG_TAG = "BackupRestoreStorage"
/**
- * Storage with backup and restore support. Subclass must implement either [Observable] or
- * [KeyedObservable] interface.
+ * Storage with backup and restore support.
+ *
+ * Subclass MUST
+ * - implement either [Observable] or [KeyedObservable] interface.
+ * - be thread safe, backup/restore happens on Binder thread, while general data read/write
+ * operations occur on other threads.
*
* The storage is identified by a unique string [name] and data set is split into entities
* ([BackupRestoreEntity]).
*/
+@ThreadSafe
abstract class BackupRestoreStorage : BackupHelper {
/**
* A unique string used to disambiguate the various storages within backup agent.
@@ -68,7 +75,7 @@
@VisibleForTesting internal var entities: List<BackupRestoreEntity>? = null
/** Entities to back up and restore. */
- abstract fun createBackupRestoreEntities(): List<BackupRestoreEntity>
+ @BinderThread abstract fun createBackupRestoreEntities(): List<BackupRestoreEntity>
/** Default codec used to encode/decode the entity data. */
open fun defaultCodec(): BackupCodec = BackupZipCodec.BEST_COMPRESSION
@@ -134,7 +141,11 @@
Log.i(LOG_TAG, "[$name] Backup end")
}
- /** Returns if backup is enabled. */
+ /**
+ * Returns if backup is enabled.
+ *
+ * If disabled, [performBackup] will be no-op, all entities backup are skipped.
+ */
open fun enableBackup(backupContext: BackupContext): Boolean = true
open fun wrapBackupOutputStream(codec: BackupCodec, outputStream: OutputStream): OutputStream {
@@ -172,7 +183,11 @@
private fun ensureEntities(): List<BackupRestoreEntity> =
entities ?: createBackupRestoreEntities().also { entities = it }
- /** Returns if restore is enabled. */
+ /**
+ * Returns if restore is enabled.
+ *
+ * If disabled, [restoreEntity] will be no-op, all entities restore are skipped.
+ */
open fun enableRestore(): Boolean = true
open fun wrapRestoreInputStream(
@@ -188,12 +203,13 @@
}
final override fun writeNewStateDescription(newState: ParcelFileDescriptor) {
+ if (!enableRestore()) return
entities = null // clear to reduce memory footprint
newState.writeAndClearEntityStates()
onRestoreFinished()
}
- /** Callbacks when restore finished. */
+ /** Callbacks when entity data are all restored. */
open fun onRestoreFinished() {}
@VisibleForTesting
diff --git a/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/BackupRestoreStorageTest.kt b/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/BackupRestoreStorageTest.kt
index 99998ff..26534ba 100644
--- a/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/BackupRestoreStorageTest.kt
+++ b/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/BackupRestoreStorageTest.kt
@@ -248,6 +248,15 @@
}
@Test
+ fun writeNewStateDescription_restoreDisabled() {
+ val storage = spy(TestStorage().apply { enabled = false })
+ temporaryFolder.newFile().toParcelFileDescriptor(MODE_WRITE_ONLY or MODE_APPEND).use {
+ storage.writeNewStateDescription(it)
+ }
+ verify(storage, never()).onRestoreFinished()
+ }
+
+ @Test
fun backupAndRestore() {
val storage = spy(TestStorage(entity1, entity2))
val backupAgentHelper = BackupAgentHelper()
diff --git a/packages/SettingsLib/SettingsTheme/res/color-v35/settingslib_preference_bg_color.xml b/packages/SettingsLib/SettingsTheme/res/color-v35/settingslib_preference_bg_color.xml
index 4ced9f2..cece966 100644
--- a/packages/SettingsLib/SettingsTheme/res/color-v35/settingslib_preference_bg_color.xml
+++ b/packages/SettingsLib/SettingsTheme/res/color-v35/settingslib_preference_bg_color.xml
@@ -16,8 +16,8 @@
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:state_pressed="true" android:color="@color/settingslib_materialColorSecondaryContainer"/>
- <item android:state_selected="true" android:color="@color/settingslib_materialColorSecondaryContainer"/>
- <item android:state_activated="true" android:color="@color/settingslib_materialColorSecondaryContainer"/>
- <item android:color="@color/settingslib_materialColorSurfaceContainerHighest"/> <!-- not selected -->
+ <item android:state_pressed="true" android:color="@color/settingslib_materialColorSurfaceContainerHigh"/>
+ <item android:state_selected="true" android:color="@color/settingslib_materialColorSurfaceContainerHigh"/>
+ <item android:state_activated="true" android:color="@color/settingslib_materialColorSurfaceContainerHigh"/>
+ <item android:color="@color/settingslib_materialColorSurfaceBright"/> <!-- not selected -->
</selector>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background.xml
index 285ab73..eba9c2c 100644
--- a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background.xml
+++ b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background.xml
@@ -19,12 +19,12 @@
<item
android:left="?android:attr/listPreferredItemPaddingStart"
android:right="?android:attr/listPreferredItemPaddingEnd"
- android:top="1dp">
+ android:top="2dp">
<shape android:shape="rectangle">
<solid
android:color="@color/settingslib_preference_bg_color" />
<corners
- android:radius="?android:attr/dialogCornerRadius" />
+ android:radius="@dimen/settingslib_preference_corner_radius" />
</shape>
</item>
</layer-list>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_bottom.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_bottom.xml
index e417307..5c60f37 100644
--- a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_bottom.xml
+++ b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_bottom.xml
@@ -19,15 +19,15 @@
<item
android:left="?android:attr/listPreferredItemPaddingStart"
android:right="?android:attr/listPreferredItemPaddingEnd"
- android:top="1dp">
+ android:top="2dp">
<shape android:shape="rectangle">
<solid
android:color="@color/settingslib_preference_bg_color" />
<corners
- android:topLeftRadius="0dp"
- android:bottomLeftRadius="?android:attr/dialogCornerRadius"
- android:topRightRadius="0dp"
- android:bottomRightRadius="?android:attr/dialogCornerRadius" />
+ android:topLeftRadius="4dp"
+ android:bottomLeftRadius="@dimen/settingslib_preference_corner_radius"
+ android:topRightRadius="4dp"
+ android:bottomRightRadius="@dimen/settingslib_preference_corner_radius" />
</shape>
</item>
</layer-list>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_bottom_selected.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_bottom_selected.xml
new file mode 100644
index 0000000..de64efd
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_bottom_selected.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+ -->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <item
+ android:left="?android:attr/listPreferredItemPaddingStart"
+ android:right="?android:attr/listPreferredItemPaddingEnd"
+ android:top="2dp">
+ <shape android:shape="rectangle">
+ <solid
+ android:color="@color/settingslib_materialColorSurfaceContainerHigh" />
+ <corners
+ android:topLeftRadius="4dp"
+ android:bottomLeftRadius="@dimen/settingslib_preference_corner_radius"
+ android:topRightRadius="4dp"
+ android:bottomRightRadius="@dimen/settingslib_preference_corner_radius" />
+ </shape>
+ </item>
+</layer-list>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_center.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_center.xml
index e964657..dd70f4f 100644
--- a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_center.xml
+++ b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_center.xml
@@ -19,12 +19,12 @@
<item
android:left="?android:attr/listPreferredItemPaddingStart"
android:right="?android:attr/listPreferredItemPaddingEnd"
- android:top="1dp">
+ android:top="2dp">
<shape android:shape="rectangle">
<solid
android:color="@color/settingslib_preference_bg_color" />
<corners
- android:radius="1dp" />
+ android:radius="4dp" />
</shape>
</item>
</layer-list>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_center_selected.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_center_selected.xml
new file mode 100644
index 0000000..fffc6c8
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_center_selected.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+ -->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <item
+ android:left="?android:attr/listPreferredItemPaddingStart"
+ android:right="?android:attr/listPreferredItemPaddingEnd"
+ android:top="2dp">
+ <shape android:shape="rectangle">
+ <solid
+ android:color="@color/settingslib_materialColorSurfaceContainerHigh" />
+ <corners
+ android:radius="4dp" />
+ </shape>
+ </item>
+</layer-list>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_selected.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_selected.xml
new file mode 100644
index 0000000..f83e3b1
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_selected.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+ -->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <item
+ android:left="?android:attr/listPreferredItemPaddingStart"
+ android:right="?android:attr/listPreferredItemPaddingEnd"
+ android:top="2dp">
+ <shape android:shape="rectangle">
+ <solid
+ android:color="@color/settingslib_materialColorSurfaceContainerHigh" />
+ <corners
+ android:radius="@dimen/settingslib_preference_corner_radius" />
+ </shape>
+ </item>
+</layer-list>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_top.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_top.xml
index a9d69c2..ab79d18 100644
--- a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_top.xml
+++ b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_top.xml
@@ -19,15 +19,15 @@
<item
android:left="?android:attr/listPreferredItemPaddingStart"
android:right="?android:attr/listPreferredItemPaddingEnd"
- android:top="1dp">
+ android:top="2dp">
<shape android:shape="rectangle">
<solid
android:color="@color/settingslib_preference_bg_color" />
<corners
- android:topLeftRadius="?android:attr/dialogCornerRadius"
- android:bottomLeftRadius="0dp"
- android:topRightRadius="?android:attr/dialogCornerRadius"
- android:bottomRightRadius="0dp" />
+ android:topLeftRadius="@dimen/settingslib_preference_corner_radius"
+ android:bottomLeftRadius="4dp"
+ android:topRightRadius="@dimen/settingslib_preference_corner_radius"
+ android:bottomRightRadius="4dp" />
</shape>
</item>
</layer-list>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_top_selected.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_top_selected.xml
new file mode 100644
index 0000000..112ec73
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_top_selected.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+ -->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <item
+ android:left="?android:attr/listPreferredItemPaddingStart"
+ android:right="?android:attr/listPreferredItemPaddingEnd"
+ android:top="2dp">
+ <shape android:shape="rectangle">
+ <solid
+ android:color="@color/settingslib_materialColorSurfaceContainerHigh" />
+ <corners
+ android:topLeftRadius="@dimen/settingslib_preference_corner_radius"
+ android:bottomLeftRadius="4dp"
+ android:topRightRadius="@dimen/settingslib_preference_corner_radius"
+ android:bottomRightRadius="4dp" />
+ </shape>
+ </item>
+</layer-list>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/values-night-v35/colors.xml b/packages/SettingsLib/SettingsTheme/res/values-night-v35/colors.xml
index 221e8f5..94ff02d 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-night-v35/colors.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-night-v35/colors.xml
@@ -37,8 +37,11 @@
<!-- Material next track off color-->
<color name="settingslib_track_off_color">@color/settingslib_materialColorSurfaceContainerHighest</color>
+ <!-- Dialog text button color. -->
+ <color name="settingslib_dialog_accent">@color/settingslib_materialColorPrimary</color>
+
<!-- Dialog background color. -->
- <color name="settingslib_dialog_background">@color/settingslib_materialColorSurfaceContainer</color>
+ <color name="settingslib_dialog_background">@color/settingslib_materialColorSurfaceContainerHigh</color>
<color name="settingslib_colorSurfaceHeader">@color/settingslib_materialColorSurfaceVariant</color>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v35/colors.xml b/packages/SettingsLib/SettingsTheme/res/values-v35/colors.xml
index dc2d3dc..8b95016 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-v35/colors.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-v35/colors.xml
@@ -37,8 +37,11 @@
<!-- Material next track off color-->
<color name="settingslib_track_off_color">@color/settingslib_materialColorSurfaceContainerHighest</color>
+ <!-- Dialog text button color. -->
+ <color name="settingslib_dialog_accent">@color/settingslib_materialColorPrimary</color>
+
<!-- Dialog background color. -->
- <color name="settingslib_dialog_background">@color/settingslib_materialColorSurfaceContainer</color>
+ <color name="settingslib_dialog_background">@color/settingslib_materialColorSurfaceContainerHigh</color>
<!-- Material next track outline color-->
<color name="settingslib_track_online_color">@color/settingslib_switch_track_outline_color</color>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v35/dimens.xml b/packages/SettingsLib/SettingsTheme/res/values-v35/dimens.xml
new file mode 100644
index 0000000..d783956
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-v35/dimens.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+ -->
+
+<resources>
+ <dimen name="settingslib_preference_corner_radius">20dp</dimen>
+</resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/src/com/android/settingslib/satellite/SatelliteDialogUtils.kt b/packages/SettingsLib/src/com/android/settingslib/satellite/SatelliteDialogUtils.kt
new file mode 100644
index 0000000..d69c87b
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/satellite/SatelliteDialogUtils.kt
@@ -0,0 +1,145 @@
+/*
+ * 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.settingslib.satellite
+
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.os.OutcomeReceiver
+import android.telephony.satellite.SatelliteManager
+import android.util.Log
+import android.view.WindowManager
+import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.lifecycleScope
+import com.android.settingslib.wifi.WifiUtils
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.Dispatchers.Default
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.asExecutor
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.suspendCancellableCoroutine
+import kotlinx.coroutines.withContext
+import java.util.concurrent.ExecutionException
+import java.util.concurrent.TimeoutException
+import kotlin.coroutines.resume
+
+/** A util for Satellite dialog */
+object SatelliteDialogUtils {
+
+ /**
+ * Uses to start Satellite dialog to prevent users from using the BT, Airplane Mode, and
+ * Wifi during the satellite mode is on.
+ */
+ @JvmStatic
+ fun mayStartSatelliteWarningDialog(
+ context: Context,
+ lifecycleOwner: LifecycleOwner,
+ type: Int,
+ allowClick: (isAllowed: Boolean) -> Unit
+ ): Job {
+ return mayStartSatelliteWarningDialog(
+ context, lifecycleOwner.lifecycleScope, type, allowClick)
+ }
+
+ /**
+ * Uses to start Satellite dialog to prevent users from using the BT, Airplane Mode, and
+ * Wifi during the satellite mode is on.
+ */
+ @JvmStatic
+ fun mayStartSatelliteWarningDialog(
+ context: Context,
+ coroutineScope: CoroutineScope,
+ type: Int,
+ allowClick: (isAllowed: Boolean) -> Unit
+ ): Job =
+ coroutineScope.launch {
+ var isSatelliteModeOn = false
+ try {
+ isSatelliteModeOn = requestIsEnabled(context)
+ } catch (e: InterruptedException) {
+ Log.w(TAG, "Error to get satellite status : $e")
+ } catch (e: ExecutionException) {
+ Log.w(TAG, "Error to get satellite status : $e")
+ } catch (e: TimeoutException) {
+ Log.w(TAG, "Error to get satellite status : $e")
+ }
+
+ if (isSatelliteModeOn) {
+ startSatelliteWarningDialog(context, type)
+ }
+ withContext(Dispatchers.Main) {
+ allowClick(!isSatelliteModeOn)
+ }
+ }
+
+ private fun startSatelliteWarningDialog(context: Context, type: Int) {
+ context.startActivity(Intent(Intent.ACTION_MAIN).apply {
+ component = ComponentName(
+ "com.android.settings",
+ "com.android.settings.network.SatelliteWarningDialogActivity"
+ )
+ putExtra(WifiUtils.DIALOG_WINDOW_TYPE,
+ WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG)
+ putExtra(EXTRA_TYPE_OF_SATELLITE_WARNING_DIALOG, type)
+ addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_SINGLE_TOP)
+ })
+ }
+
+ /**
+ * Checks if the satellite modem is enabled.
+ *
+ * @param executor The executor to run the asynchronous operation on
+ * @return A ListenableFuture that will resolve to `true` if the satellite modem enabled,
+ * `false` otherwise.
+ */
+ private suspend fun requestIsEnabled(
+ context: Context,
+ ): Boolean = withContext(Default) {
+ val satelliteManager: SatelliteManager? =
+ context.getSystemService(SatelliteManager::class.java)
+ if (satelliteManager == null) {
+ Log.w(TAG, "SatelliteManager is null")
+ return@withContext false
+ }
+
+ suspendCancellableCoroutine {continuation ->
+ satelliteManager?.requestIsEnabled(Default.asExecutor(),
+ object : OutcomeReceiver<Boolean, SatelliteManager.SatelliteException> {
+ override fun onResult(result: Boolean) {
+ Log.i(TAG, "Satellite modem enabled status: $result")
+ continuation.resume(result)
+ }
+
+ override fun onError(error: SatelliteManager.SatelliteException) {
+ super.onError(error)
+ Log.w(TAG, "Can't get satellite modem enabled status", error)
+ continuation.resume(false)
+ }
+ })
+ }
+ }
+
+ const val TAG = "SatelliteDialogUtils"
+
+ const val EXTRA_TYPE_OF_SATELLITE_WARNING_DIALOG: String =
+ "extra_type_of_satellite_warning_dialog"
+ const val TYPE_IS_UNKNOWN = -1
+ const val TYPE_IS_WIFI = 0
+ const val TYPE_IS_BLUETOOTH = 1
+ const val TYPE_IS_AIRPLANE_MODE = 2
+}
\ No newline at end of file
diff --git a/packages/SettingsLib/src/com/android/settingslib/statusbar/notification/data/repository/FakeNotificationsSoundPolicyRepository.kt b/packages/SettingsLib/src/com/android/settingslib/statusbar/notification/data/repository/FakeNotificationsSoundPolicyRepository.kt
index 2a44511..a939ed1 100644
--- a/packages/SettingsLib/src/com/android/settingslib/statusbar/notification/data/repository/FakeNotificationsSoundPolicyRepository.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/statusbar/notification/data/repository/FakeNotificationsSoundPolicyRepository.kt
@@ -17,6 +17,7 @@
package com.android.settingslib.statusbar.notification.data.repository
import android.app.NotificationManager
+import android.provider.Settings
import com.android.settingslib.statusbar.notification.data.model.ZenMode
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
@@ -28,10 +29,14 @@
override val notificationPolicy: StateFlow<NotificationManager.Policy?>
get() = mutableNotificationPolicy.asStateFlow()
- private val mutableZenMode = MutableStateFlow<ZenMode?>(null)
+ private val mutableZenMode = MutableStateFlow<ZenMode?>(ZenMode(Settings.Global.ZEN_MODE_OFF))
override val zenMode: StateFlow<ZenMode?>
get() = mutableZenMode.asStateFlow()
+ init {
+ updateNotificationPolicy()
+ }
+
fun updateNotificationPolicy(policy: NotificationManager.Policy?) {
mutableNotificationPolicy.value = policy
}
@@ -48,13 +53,14 @@
suppressedVisualEffects: Int = NotificationManager.Policy.SUPPRESSED_EFFECTS_UNSET,
state: Int = NotificationManager.Policy.STATE_UNSET,
priorityConversationSenders: Int = NotificationManager.Policy.CONVERSATION_SENDERS_NONE,
-) = updateNotificationPolicy(
- NotificationManager.Policy(
- priorityCategories,
- priorityCallSenders,
- priorityMessageSenders,
- suppressedVisualEffects,
- state,
- priorityConversationSenders,
+) =
+ updateNotificationPolicy(
+ NotificationManager.Policy(
+ priorityCategories,
+ priorityCallSenders,
+ priorityMessageSenders,
+ suppressedVisualEffects,
+ state,
+ priorityConversationSenders,
+ )
)
-)
\ No newline at end of file
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt
index 65a5317..36e396fb 100644
--- a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt
@@ -72,7 +72,11 @@
suspend fun setVolume(audioStream: AudioStream, volume: Int)
- suspend fun setMuted(audioStream: AudioStream, isMuted: Boolean)
+ /**
+ * Mutes and un-mutes [audioStream]. Returns true when the state changes and false the
+ * otherwise.
+ */
+ suspend fun setMuted(audioStream: AudioStream, isMuted: Boolean): Boolean
suspend fun setRingerMode(audioStream: AudioStream, mode: RingerMode)
@@ -164,14 +168,20 @@
audioManager.setStreamVolume(audioStream.value, volume, 0)
}
- override suspend fun setMuted(audioStream: AudioStream, isMuted: Boolean) =
- withContext(backgroundCoroutineContext) {
- audioManager.adjustStreamVolume(
- audioStream.value,
- if (isMuted) AudioManager.ADJUST_MUTE else AudioManager.ADJUST_UNMUTE,
- 0,
- )
+ override suspend fun setMuted(audioStream: AudioStream, isMuted: Boolean): Boolean {
+ return withContext(backgroundCoroutineContext) {
+ if (isMuted == audioManager.isStreamMute(audioStream.value)) {
+ false
+ } else {
+ audioManager.adjustStreamVolume(
+ audioStream.value,
+ if (isMuted) AudioManager.ADJUST_MUTE else AudioManager.ADJUST_UNMUTE,
+ 0,
+ )
+ true
+ }
}
+ }
override suspend fun setRingerMode(audioStream: AudioStream, mode: RingerMode) {
withContext(backgroundCoroutineContext) { audioManager.ringerMode = mode.value }
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/domain/interactor/AudioVolumeInteractor.kt b/packages/SettingsLib/src/com/android/settingslib/volume/domain/interactor/AudioVolumeInteractor.kt
index 33f917e..0e5ebda 100644
--- a/packages/SettingsLib/src/com/android/settingslib/volume/domain/interactor/AudioVolumeInteractor.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/domain/interactor/AudioVolumeInteractor.kt
@@ -25,6 +25,7 @@
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map
/** Provides audio stream state and an ability to change it */
@@ -46,8 +47,16 @@
val ringerMode: StateFlow<RingerMode>
get() = audioRepository.ringerMode
- suspend fun setVolume(audioStream: AudioStream, volume: Int) =
+ suspend fun setVolume(audioStream: AudioStream, volume: Int) {
+ val streamModel = getAudioStream(audioStream).first()
+ val oldVolume = streamModel.volume
audioRepository.setVolume(audioStream, volume)
+ when {
+ volume == streamModel.minVolume -> setMuted(audioStream, true)
+ oldVolume == streamModel.minVolume && volume > streamModel.minVolume ->
+ setMuted(audioStream, false)
+ }
+ }
suspend fun setMuted(audioStream: AudioStream, isMuted: Boolean) {
if (audioStream.value == AudioManager.STREAM_RING) {
@@ -55,7 +64,16 @@
if (isMuted) AudioManager.RINGER_MODE_VIBRATE else AudioManager.RINGER_MODE_NORMAL
audioRepository.setRingerMode(audioStream, RingerMode(mode))
}
- audioRepository.setMuted(audioStream, isMuted)
+ val mutedChanged = audioRepository.setMuted(audioStream, isMuted)
+ if (mutedChanged && !isMuted) {
+ with(getAudioStream(audioStream).first()) {
+ if (volume == minVolume) {
+ // Slightly increase volume when user un-mutes the stream that is lowered
+ // down to its minimum
+ setVolume(audioStream, volume + 1)
+ }
+ }
+ }
}
/** Checks if the volume can be changed via the UI. */
diff --git a/packages/SettingsLib/tests/robotests/Android.bp b/packages/SettingsLib/tests/robotests/Android.bp
index f87b519..e125083 100644
--- a/packages/SettingsLib/tests/robotests/Android.bp
+++ b/packages/SettingsLib/tests/robotests/Android.bp
@@ -41,7 +41,10 @@
//###########################################################
android_robolectric_test {
name: "SettingsLibRoboTests",
- srcs: ["src/**/*.java"],
+ srcs: [
+ "src/**/*.java",
+ "src/**/*.kt",
+ ],
static_libs: [
"Settings_robolectric_meta_service_file",
"Robolectric_shadows_androidx_fragment_upstream",
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/satellite/SatelliteDialogUtilsTest.kt b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/satellite/SatelliteDialogUtilsTest.kt
new file mode 100644
index 0000000..aeda1ed6
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/satellite/SatelliteDialogUtilsTest.kt
@@ -0,0 +1,162 @@
+/*
+ * 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.settingslib.satellite
+
+import android.content.Context
+import android.content.Intent
+import android.os.OutcomeReceiver
+import android.platform.test.annotations.RequiresFlagsEnabled
+import android.telephony.satellite.SatelliteManager
+import android.telephony.satellite.SatelliteManager.SatelliteException
+import android.util.AndroidRuntimeException
+import androidx.test.core.app.ApplicationProvider
+import com.android.internal.telephony.flags.Flags
+import com.android.settingslib.satellite.SatelliteDialogUtils.TYPE_IS_WIFI
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.runBlocking
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.any
+import org.mockito.Mockito.`when`
+import org.mockito.Spy
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoRule
+import org.mockito.Mockito.verify
+import org.mockito.internal.verification.Times
+import org.robolectric.RobolectricTestRunner
+
+@RunWith(RobolectricTestRunner::class)
+class SatelliteDialogUtilsTest {
+ @JvmField
+ @Rule
+ val mockitoRule: MockitoRule = MockitoJUnit.rule()
+
+ @Spy
+ var context: Context = ApplicationProvider.getApplicationContext()
+ @Mock
+ private lateinit var satelliteManager: SatelliteManager
+
+ private val coroutineScope = CoroutineScope(Dispatchers.Main)
+
+ @Before
+ fun setUp() {
+ `when`(context.getSystemService(SatelliteManager::class.java))
+ .thenReturn(satelliteManager)
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+ fun mayStartSatelliteWarningDialog_satelliteIsOn_showWarningDialog() = runBlocking {
+ `when`(
+ satelliteManager.requestIsEnabled(
+ any(), any<OutcomeReceiver<Boolean, SatelliteManager.SatelliteException>>()
+ )
+ )
+ .thenAnswer { invocation ->
+ val receiver = invocation
+ .getArgument<
+ OutcomeReceiver<Boolean, SatelliteManager.SatelliteException>>(
+ 1
+ )
+ receiver.onResult(true)
+ null
+ }
+
+ try {
+ SatelliteDialogUtils.mayStartSatelliteWarningDialog(
+ context, coroutineScope, TYPE_IS_WIFI, allowClick = {
+ assertTrue(it)
+ })
+ } catch (e: AndroidRuntimeException) {
+ // Catch exception of starting activity .
+ }
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+ fun mayStartSatelliteWarningDialog_satelliteIsOff_notShowWarningDialog() = runBlocking {
+ `when`(
+ satelliteManager.requestIsEnabled(
+ any(), any<OutcomeReceiver<Boolean, SatelliteManager.SatelliteException>>()
+ )
+ )
+ .thenAnswer { invocation ->
+ val receiver = invocation
+ .getArgument<
+ OutcomeReceiver<Boolean, SatelliteManager.SatelliteException>>(
+ 1
+ )
+ receiver.onResult(false)
+ null
+ }
+
+
+ SatelliteDialogUtils.mayStartSatelliteWarningDialog(
+ context, coroutineScope, TYPE_IS_WIFI, allowClick = {
+ assertFalse(it)
+ })
+
+ verify(context, Times(0)).startActivity(any<Intent>())
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+ fun mayStartSatelliteWarningDialog_noSatelliteManager_notShowWarningDialog() = runBlocking {
+ `when`(context.getSystemService(SatelliteManager::class.java))
+ .thenReturn(null)
+
+ SatelliteDialogUtils.mayStartSatelliteWarningDialog(
+ context, coroutineScope, TYPE_IS_WIFI, allowClick = {
+ assertFalse(it)
+ })
+
+ verify(context, Times(0)).startActivity(any<Intent>())
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+ fun mayStartSatelliteWarningDialog_satelliteErrorResult_notShowWarningDialog() = runBlocking {
+ `when`(
+ satelliteManager.requestIsEnabled(
+ any(), any<OutcomeReceiver<Boolean, SatelliteManager.SatelliteException>>()
+ )
+ )
+ .thenAnswer { invocation ->
+ val receiver = invocation
+ .getArgument<
+ OutcomeReceiver<Boolean, SatelliteManager.SatelliteException>>(
+ 1
+ )
+ receiver.onError(SatelliteException(SatelliteManager.SATELLITE_RESULT_ERROR))
+ null
+ }
+
+
+ SatelliteDialogUtils.mayStartSatelliteWarningDialog(
+ context, coroutineScope, TYPE_IS_WIFI, allowClick = {
+ assertFalse(it)
+ })
+
+ verify(context, Times(0)).startActivity(any<Intent>())
+ }
+}
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 46bf494..374240b 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -347,6 +347,7 @@
<uses-permission android:name="android.permission.REQUEST_COMPANION_SELF_MANAGED" />
<uses-permission android:name="android.permission.USE_COMPANION_TRANSPORTS" />
<uses-permission android:name="android.permission.REQUEST_OBSERVE_DEVICE_UUID_PRESENCE" />
+ <uses-permission android:name="android.permission.DELIVER_COMPANION_MESSAGES" />
<uses-permission android:name="android.permission.MANAGE_APPOPS" />
<uses-permission android:name="android.permission.WATCH_APPOPS" />
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index b9e70ef..9c58371 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -826,20 +826,6 @@
</intent-filter>
</activity>
- <activity
- android:name=".contrast.ContrastDialogActivity"
- android:label="@string/quick_settings_contrast_label"
- android:theme="@style/Theme.SystemUI.ContrastDialog"
- android:finishOnCloseSystemDialogs="true"
- android:launchMode="singleInstance"
- android:excludeFromRecents="true"
- android:exported="true">
- <intent-filter>
- <action android:name="com.android.intent.action.SHOW_CONTRAST_DIALOG" />
- <category android:name="android.intent.category.DEFAULT" />
- </intent-filter>
- </activity>
-
<activity android:name=".ForegroundServicesDialog"
android:process=":fgservices"
android:excludeFromRecents="true"
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index aff86e8a1..10ba7bd 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -204,7 +204,16 @@
description: "Re-enable the codepath that removed tinting of notifications when the"
" standard background color is desired. This was the behavior before we discovered"
" a resources threading issue, which we worked around by tinting the notification"
- " backgrounds and footer buttons."
+ " backgrounds."
+ bug: "294830092"
+}
+
+flag {
+ name: "notification_footer_background_tint_optimization"
+ namespace: "systemui"
+ description: "Remove duplicative tinting of notification footer buttons. This was the behavior"
+ " before we discovered a resources threading issue, which we worked around by applying the"
+ " same color as a tint to the background drawable of footer buttons."
bug: "294830092"
}
@@ -355,6 +364,14 @@
}
flag {
+ name: "status_bar_screen_sharing_chips"
+ namespace: "systemui"
+ description: "Show chips on the left side of the status bar when a user is screen sharing, "
+ "recording, or casting"
+ bug: "332662551"
+}
+
+flag {
name: "compose_bouncer"
namespace: "systemui"
description: "Use the new compose bouncer in SystemUI"
@@ -496,6 +513,15 @@
}
}
+flag {
+ name: "screenshot_scroll_crop_view_crash_fix"
+ namespace: "systemui"
+ description: "Mitigate crash on invalid computed range in CropView"
+ bug: "232633995"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
flag {
name: "screenshot_private_profile_behavior_fix"
@@ -943,3 +969,10 @@
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "glanceable_hub_gesture_handle"
+ namespace: "systemui"
+ description: "Shows a vertical bar at the right edge to indicate the user can swipe to open the glanceable hub"
+ bug: "339667383"
+}
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 8ee8ea4..feb1f5b 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
@@ -2,16 +2,24 @@
import androidx.compose.animation.core.tween
import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.dimensionResource
+import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.compose.animation.scene.Edge
import com.android.compose.animation.scene.ElementKey
@@ -26,6 +34,7 @@
import com.android.compose.animation.scene.SwipeDirection
import com.android.compose.animation.scene.observableTransitionState
import com.android.compose.animation.scene.transitions
+import com.android.systemui.Flags
import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.communal.shared.model.CommunalTransitionKeys
import com.android.systemui.communal.ui.compose.extensions.allowGestures
@@ -88,6 +97,8 @@
val currentSceneKey: SceneKey by
viewModel.currentScene.collectAsStateWithLifecycle(CommunalScenes.Blank)
val touchesAllowed by viewModel.touchesAllowed.collectAsStateWithLifecycle(initialValue = false)
+ val showGestureIndicator by
+ viewModel.showGestureIndicator.collectAsStateWithLifecycle(initialValue = false)
val state: MutableSceneTransitionLayoutState = remember {
MutableSceneTransitionLayoutState(
initialScene = currentSceneKey,
@@ -126,7 +137,19 @@
)
) {
// This scene shows nothing only allowing for transitions to the communal scene.
- Box(modifier = Modifier.fillMaxSize())
+ // TODO(b/339667383): remove this temporary swipe gesture handle
+ Row(modifier = Modifier.fillMaxSize(), horizontalArrangement = Arrangement.End) {
+ if (showGestureIndicator && Flags.glanceableHubGestureHandle()) {
+ Box(
+ modifier =
+ Modifier.height(220.dp)
+ .width(4.dp)
+ .align(Alignment.CenterVertically)
+ .background(color = Color.White, RoundedCornerShape(4.dp))
+ )
+ Spacer(modifier = Modifier.width(12.dp))
+ }
+ }
}
scene(
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
index 2a52c60..cd27d57 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
@@ -48,6 +48,7 @@
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.requiredSize
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.wrapContentHeight
@@ -87,8 +88,6 @@
import androidx.compose.ui.Alignment
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
-import androidx.compose.ui.draw.alpha
-import androidx.compose.ui.draw.scale
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ColorFilter
@@ -120,9 +119,10 @@
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import androidx.compose.ui.unit.times
import androidx.compose.ui.viewinterop.AndroidView
import androidx.compose.ui.window.Popup
-import androidx.core.view.setPadding
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.window.layout.WindowMetricsCalculator
import com.android.compose.modifiers.thenIf
@@ -427,8 +427,8 @@
state = gridState,
rows = GridCells.Fixed(CommunalContentSize.FULL.span),
contentPadding = contentPadding,
- horizontalArrangement = Arrangement.spacedBy(32.dp),
- verticalArrangement = Arrangement.spacedBy(32.dp),
+ horizontalArrangement = Arrangement.spacedBy(Dimensions.ItemSpacing),
+ verticalArrangement = Arrangement.spacedBy(Dimensions.ItemSpacing),
) {
items(
count = list.size,
@@ -441,7 +441,7 @@
Dimensions.CardWidth.value,
list[index].size.dp().value,
)
- val cardModifier = Modifier.size(width = size.width.dp, height = size.height.dp)
+ val cardModifier = Modifier.requiredSize(width = size.width.dp, height = size.height.dp)
if (viewModel.isEditMode && dragDropState != null) {
val selected by
remember(index) { derivedStateOf { list[index].key == selectedKey.value } }
@@ -795,12 +795,10 @@
containerColor = colors.primary,
contentColor = colors.onPrimary,
),
- shape = RoundedCornerShape(80.dp, 40.dp, 80.dp, 40.dp)
+ shape = RoundedCornerShape(68.dp, 34.dp, 68.dp, 34.dp)
) {
Column(
- modifier = Modifier.fillMaxSize().padding(horizontal = 82.dp),
- verticalArrangement =
- Arrangement.spacedBy(Dimensions.Spacing, Alignment.CenterVertically),
+ modifier = Modifier.fillMaxSize().padding(vertical = 38.dp, horizontal = 70.dp),
horizontalAlignment = Alignment.CenterHorizontally,
) {
Icon(
@@ -808,11 +806,13 @@
contentDescription = stringResource(R.string.cta_label_to_open_widget_picker),
modifier = Modifier.size(Dimensions.IconSize),
)
+ Spacer(modifier = Modifier.size(6.dp))
Text(
text = stringResource(R.string.cta_label_to_edit_widget),
- style = MaterialTheme.typography.titleLarge,
+ style = MaterialTheme.typography.titleMedium,
textAlign = TextAlign.Center,
)
+ Spacer(modifier = Modifier.size(20.dp))
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.Center,
@@ -828,9 +828,10 @@
) {
Text(
text = stringResource(R.string.cta_tile_button_to_dismiss),
+ fontSize = 12.sp,
)
}
- Spacer(modifier = Modifier.size(Dimensions.Spacing))
+ Spacer(modifier = Modifier.size(14.dp))
Button(
colors =
ButtonDefaults.buttonColors(
@@ -842,6 +843,7 @@
) {
Text(
text = stringResource(R.string.cta_tile_button_to_open_widget_editor),
+ fontSize = 12.sp,
)
}
}
@@ -927,10 +929,14 @@
model.appWidgetHost
.createViewForCommunal(context, model.appWidgetId, model.providerInfo)
.apply {
- updateAppWidgetSize(Bundle.EMPTY, listOf(size))
- // Remove the extra padding applied to AppWidgetHostView to allow widgets to
- // occupy the entire box.
- setPadding(0)
+ updateAppWidgetSize(
+ /* newOptions = */ Bundle(),
+ /* minWidth = */ size.width.toInt(),
+ /* minHeight = */ size.height.toInt(),
+ /* maxWidth = */ size.width.toInt(),
+ /* maxHeight = */ size.height.toInt(),
+ /* ignorePadding = */ true
+ )
accessibilityDelegate = viewModel.widgetAccessibilityDelegate
}
},
@@ -1153,7 +1159,11 @@
@Composable
private fun gridContentPadding(isEditMode: Boolean, toolbarSize: IntSize?): PaddingValues {
if (!isEditMode || toolbarSize == null) {
- return PaddingValues(start = 48.dp, end = 48.dp, top = Dimensions.GridTopSpacing)
+ return PaddingValues(
+ start = Dimensions.ItemSpacing,
+ end = Dimensions.ItemSpacing,
+ top = Dimensions.GridTopSpacing,
+ )
}
val context = LocalContext.current
val density = LocalDensity.current
@@ -1216,18 +1226,19 @@
}
object Dimensions {
- val CardWidth = 424.dp
- val CardHeightFull = 596.dp
- val CardHeightHalf = 282.dp
- val CardHeightThird = 177.33.dp
- val CardOutlineWidth = 3.dp
- val GridTopSpacing = 64.dp
+ val CardHeightFull = 530.dp
+ val GridTopSpacing = 114.dp
val GridHeight = CardHeightFull + GridTopSpacing
- val Spacing = 16.dp
+ val ItemSpacing = 50.dp
+ val CardHeightHalf = (CardHeightFull - ItemSpacing) / 2
+ val CardHeightThird = (CardHeightFull - (2 * ItemSpacing)) / 3
+ val CardWidth = 360.dp
+ val CardOutlineWidth = 3.dp
+ val Spacing = ItemSpacing / 2
// The sizing/padding of the toolbar in glanceable hub edit mode
val ToolbarPaddingTop = 27.dp
- val ToolbarPaddingHorizontal = 16.dp
+ val ToolbarPaddingHorizontal = ItemSpacing
val ToolbarButtonPaddingHorizontal = 24.dp
val ToolbarButtonPaddingVertical = 16.dp
val ButtonPadding =
@@ -1235,10 +1246,7 @@
vertical = ToolbarButtonPaddingVertical,
horizontal = ToolbarButtonPaddingHorizontal,
)
- val IconSize = 48.dp
-
- val LockIconSize = 52.dp
- val LockIconBottomPadding = 70.dp
+ val IconSize = 40.dp
}
private object Colors {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt
index cb3867f..271eb96 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt
@@ -78,12 +78,7 @@
}
state.a11yStateDescription?.let { stateDescription = it }
- ?: run {
- // provide a not animated value to the a11y because it fails to announce
- // the settled value when it changes rapidly.
- progressBarRangeInfo =
- ProgressBarRangeInfo(state.value, state.valueRange)
- }
+ progressBarRangeInfo = ProgressBarRangeInfo(state.value, state.valueRange)
} else {
disabled()
contentDescription =
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt
index ecfcc90..a5acf72 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt
@@ -66,15 +66,12 @@
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
- private val authenticationInteractor by lazy { kosmos.authenticationInteractor }
- private val bouncerInteractor by lazy { kosmos.bouncerInteractor }
- private val sceneContainerStartable = kosmos.sceneContainerStartable
private lateinit var underTest: BouncerViewModel
@Before
fun setUp() {
- sceneContainerStartable.start()
+ kosmos.sceneContainerStartable.start()
underTest = kosmos.bouncerViewModel
}
@@ -164,11 +161,11 @@
assertThat(isInputEnabled).isTrue()
repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT) {
- bouncerInteractor.authenticate(WRONG_PIN)
+ kosmos.bouncerInteractor.authenticate(WRONG_PIN)
}
assertThat(isInputEnabled).isFalse()
- val lockoutEndMs = authenticationInteractor.lockoutEndTimestamp ?: 0
+ val lockoutEndMs = kosmos.authenticationInteractor.lockoutEndTimestamp ?: 0
advanceTimeBy(lockoutEndMs - testScope.currentTime)
assertThat(isInputEnabled).isTrue()
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/data/repository/BrightnessPolicyRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/data/repository/BrightnessPolicyRepositoryImplTest.kt
index 312c14d..fec56ed 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/data/repository/BrightnessPolicyRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/data/repository/BrightnessPolicyRepositoryImplTest.kt
@@ -18,9 +18,13 @@
import android.content.applicationContext
import android.os.UserManager
-import androidx.test.ext.junit.runners.AndroidJUnit4
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.FlagsParameterization
+import android.platform.test.flag.junit.FlagsParameterization.allCombinationsOf
import androidx.test.filters.SmallTest
import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin
+import com.android.systemui.Flags.FLAG_ENFORCE_BRIGHTNESS_BASE_USER_RESTRICTION
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.kosmos.testDispatcher
@@ -40,15 +44,19 @@
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.ArgumentMatchers.anyString
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
@SmallTest
-@RunWith(AndroidJUnit4::class)
-class BrightnessPolicyRepositoryImplTest : SysuiTestCase() {
+@RunWith(ParameterizedAndroidJunit4::class)
+class BrightnessPolicyRepositoryImplTest(flags: FlagsParameterization) : SysuiTestCase() {
+
+ init {
+ mSetFlagsRule.setFlagsParameterization(flags)
+ }
private val kosmos = testKosmos()
- private val fakeUserRepository = kosmos.fakeUserRepository
-
private val mockUserRestrictionChecker: UserRestrictionChecker = mock {
whenever(checkIfRestrictionEnforced(any(), anyString(), anyInt())).thenReturn(null)
whenever(hasBaseUserRestriction(any(), anyString(), anyInt())).thenReturn(false)
@@ -130,7 +138,83 @@
}
}
- private companion object {
- val RESTRICTION = UserManager.DISALLOW_CONFIG_BRIGHTNESS
+ @Test
+ @DisableFlags(FLAG_ENFORCE_BRIGHTNESS_BASE_USER_RESTRICTION)
+ fun brightnessBaseUserRestriction_flagOff_noRestriction() =
+ with(kosmos) {
+ testScope.runTest {
+ whenever(
+ mockUserRestrictionChecker.hasBaseUserRestriction(
+ any(),
+ eq(RESTRICTION),
+ eq(userRepository.getSelectedUserInfo().id)
+ )
+ )
+ .thenReturn(true)
+
+ val restrictions by collectLastValue(underTest.restrictionPolicy)
+
+ assertThat(restrictions).isEqualTo(PolicyRestriction.NoRestriction)
+ }
+ }
+
+ @Test
+ fun bothRestrictions_returnsSetEnforcedAdminFromCheck() =
+ with(kosmos) {
+ testScope.runTest {
+ val enforcedAdmin: EnforcedAdmin =
+ EnforcedAdmin.createDefaultEnforcedAdminWithRestriction(RESTRICTION)
+
+ whenever(
+ mockUserRestrictionChecker.checkIfRestrictionEnforced(
+ any(),
+ eq(RESTRICTION),
+ eq(userRepository.getSelectedUserInfo().id)
+ )
+ )
+ .thenReturn(enforcedAdmin)
+
+ whenever(
+ mockUserRestrictionChecker.hasBaseUserRestriction(
+ any(),
+ eq(RESTRICTION),
+ eq(userRepository.getSelectedUserInfo().id)
+ )
+ )
+ .thenReturn(true)
+
+ val restrictions by collectLastValue(underTest.restrictionPolicy)
+
+ assertThat(restrictions).isEqualTo(PolicyRestriction.Restricted(enforcedAdmin))
+ }
+ }
+
+ @Test
+ @EnableFlags(FLAG_ENFORCE_BRIGHTNESS_BASE_USER_RESTRICTION)
+ fun brightnessBaseUserRestriction_flagOn_emptyRestriction() =
+ with(kosmos) {
+ testScope.runTest {
+ whenever(
+ mockUserRestrictionChecker.hasBaseUserRestriction(
+ any(),
+ eq(RESTRICTION),
+ eq(userRepository.getSelectedUserInfo().id)
+ )
+ )
+ .thenReturn(true)
+
+ val restrictions by collectLastValue(underTest.restrictionPolicy)
+
+ assertThat(restrictions).isEqualTo(PolicyRestriction.Restricted(EnforcedAdmin()))
+ }
+ }
+
+ companion object {
+ private const val RESTRICTION = UserManager.DISALLOW_CONFIG_BRIGHTNESS
+ @JvmStatic
+ @Parameters(name = "{0}")
+ fun getParams(): List<FlagsParameterization> {
+ return allCombinationsOf(FLAG_ENFORCE_BRIGHTNESS_BASE_USER_RESTRICTION)
+ }
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/domain/interactor/BrightnessPolicyEnforcementInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/domain/interactor/BrightnessPolicyEnforcementInteractorTest.kt
index 85a4bcf..11f5238 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/domain/interactor/BrightnessPolicyEnforcementInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/domain/interactor/BrightnessPolicyEnforcementInteractorTest.kt
@@ -48,7 +48,6 @@
private val kosmos = testKosmos()
private val mockActivityStarter = kosmos.activityStarter
- private val fakeBrightnessPolicyEnforcementInteractor = kosmos.fakeBrightnessPolicyRepository
private val underTest =
with(kosmos) {
@@ -70,7 +69,18 @@
fakeBrightnessPolicyRepository.setCurrentUserRestricted()
- assertThat(restriction).isInstanceOf(PolicyRestriction.Restricted::class.java)
+ assertThat(restriction)
+ .isEqualTo(
+ PolicyRestriction.Restricted(
+ EnforcedAdmin.createDefaultEnforcedAdminWithRestriction(
+ BrightnessPolicyRepository.RESTRICTION
+ )
+ )
+ )
+
+ fakeBrightnessPolicyRepository.setBaseUserRestriction()
+
+ assertThat(restriction).isEqualTo(PolicyRestriction.Restricted(EnforcedAdmin()))
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
index 2d079d7..be44339 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
@@ -51,6 +51,7 @@
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.StatusBarState
@@ -141,6 +142,7 @@
testScope,
context.resources,
kosmos.keyguardTransitionInteractor,
+ kosmos.keyguardInteractor,
kosmos.communalInteractor,
kosmos.communalTutorialInteractor,
kosmos.shadeInteractor,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
index 8bfa5cf..f5c86e0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
@@ -31,8 +31,11 @@
import android.content.res.Resources;
import android.graphics.Region;
import android.os.Handler;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
import android.testing.TestableLooper.RunWithLooper;
import android.view.AttachedSurfaceControl;
+import android.view.View;
import android.view.ViewGroup;
import android.view.ViewRootImpl;
import android.view.ViewTreeObserver;
@@ -42,6 +45,7 @@
import com.android.dream.lowlight.LowLightTransitionCoordinator;
import com.android.keyguard.BouncerPanelExpansionCalculator;
+import com.android.systemui.Flags;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.ambient.touch.scrim.BouncerlessScrimController;
import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerCallbackInteractor;
@@ -94,6 +98,9 @@
ViewGroup mDreamOverlayContentView;
@Mock
+ View mHubGestureIndicatorView;
+
+ @Mock
Handler mHandler;
@Mock
@@ -142,6 +149,7 @@
mDreamOverlayContainerView,
mComplicationHostViewController,
mDreamOverlayContentView,
+ mHubGestureIndicatorView,
mDreamOverlayStatusBarViewController,
mLowLightTransitionCoordinator,
mBlurUtils,
@@ -161,6 +169,18 @@
mDreamManager);
}
+ @DisableFlags(Flags.FLAG_COMMUNAL_HUB)
+ @Test
+ public void testHubGestureIndicatorGoneWhenFlagOff() {
+ verify(mHubGestureIndicatorView, never()).setVisibility(View.VISIBLE);
+ }
+
+ @EnableFlags({Flags.FLAG_COMMUNAL_HUB, Flags.FLAG_GLANCEABLE_HUB_GESTURE_HANDLE})
+ @Test
+ public void testHubGestureIndicatorVisibleWhenFlagOn() {
+ verify(mHubGestureIndicatorView).setVisibility(View.VISIBLE);
+ }
+
@Test
public void testRootSurfaceControlInsetSetOnAttach() {
mController.onViewAttached();
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/FingerprintPropertyRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/FingerprintPropertyRepositoryTest.kt
new file mode 100644
index 0000000..1e7ed63
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/FingerprintPropertyRepositoryTest.kt
@@ -0,0 +1,103 @@
+/*
+ * 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.keyguard.data.repository
+
+import android.hardware.fingerprint.FingerprintManager
+import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.data.repository.FingerprintPropertyRepositoryImpl
+import com.android.systemui.coroutines.collectLastValue
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoRule
+
+@ExperimentalCoroutinesApi
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class FingerprintPropertyRepositoryTest : SysuiTestCase() {
+ @JvmField @Rule val mockitoRule: MockitoRule = MockitoJUnit.rule()
+ private val testScope = TestScope()
+ private lateinit var underTest: FingerprintPropertyRepositoryImpl
+ @Mock private lateinit var fingerprintManager: FingerprintManager
+ @Captor
+ private lateinit var fingerprintCallbackCaptor:
+ ArgumentCaptor<IFingerprintAuthenticatorsRegisteredCallback>
+
+ @Before
+ fun setup() {
+ underTest =
+ FingerprintPropertyRepositoryImpl(
+ testScope.backgroundScope,
+ Dispatchers.Main.immediate,
+ fingerprintManager,
+ )
+ }
+
+ @Test
+ fun propertiesInitialized_onStartFalse() =
+ testScope.runTest {
+ val propertiesInitialized by collectLastValue(underTest.propertiesInitialized)
+ assertThat(propertiesInitialized).isFalse()
+ }
+
+ @Test
+ fun propertiesInitialized_onStartTrue() =
+ testScope.runTest {
+ // // collect sensorType to update fingerprintCallback before
+ // propertiesInitialized
+ // // is listened for
+ val sensorType by collectLastValue(underTest.sensorType)
+ runCurrent()
+ captureFingerprintCallback()
+
+ fingerprintCallbackCaptor.value.onAllAuthenticatorsRegistered(emptyList())
+ val propertiesInitialized by collectLastValue(underTest.propertiesInitialized)
+ assertThat(propertiesInitialized).isTrue()
+ }
+
+ @Test
+ fun propertiesInitialized_updatedToTrue() =
+ testScope.runTest {
+ val propertiesInitialized by collectLastValue(underTest.propertiesInitialized)
+ assertThat(propertiesInitialized).isFalse()
+
+ captureFingerprintCallback()
+ fingerprintCallbackCaptor.value.onAllAuthenticatorsRegistered(emptyList())
+ assertThat(propertiesInitialized).isTrue()
+ }
+
+ private fun captureFingerprintCallback() {
+ verify(fingerprintManager)
+ .addAuthenticatorsRegisteredCallback(fingerprintCallbackCaptor.capture())
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
index cb2d4e0..addbdb6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
@@ -60,17 +60,19 @@
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
- private val repository = kosmos.fakeKeyguardRepository
- private val sceneInteractor = kosmos.sceneInteractor
- private val fromGoneTransitionInteractor = kosmos.fromGoneTransitionInteractor
- private val commandQueue = kosmos.fakeCommandQueue
- private val configRepository = kosmos.fakeConfigurationRepository
- private val bouncerRepository = kosmos.keyguardBouncerRepository
- private val shadeRepository = kosmos.shadeRepository
- private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
+ private val repository by lazy { kosmos.fakeKeyguardRepository }
+ private val sceneInteractor by lazy { kosmos.sceneInteractor }
+ private val fromGoneTransitionInteractor by lazy { kosmos.fromGoneTransitionInteractor }
+ private val commandQueue by lazy { kosmos.fakeCommandQueue }
+ private val configRepository by lazy { kosmos.fakeConfigurationRepository }
+ private val bouncerRepository by lazy { kosmos.keyguardBouncerRepository }
+ private val shadeRepository by lazy { kosmos.shadeRepository }
+ private val keyguardTransitionRepository by lazy { kosmos.fakeKeyguardTransitionRepository }
+
private val transitionState: MutableStateFlow<ObservableTransitionState> =
MutableStateFlow(ObservableTransitionState.Idle(Scenes.Gone))
- private val underTest = kosmos.keyguardInteractor
+
+ private val underTest by lazy { kosmos.keyguardInteractor }
@Before
fun setUp() {
@@ -275,6 +277,28 @@
}
@Test
+ fun keyguardTranslationY_whenNotGoneAndShadeIsReesetEmitsZero() =
+ testScope.runTest {
+ val keyguardTranslationY by collectLastValue(underTest.keyguardTranslationY)
+
+ configRepository.setDimensionPixelSize(
+ R.dimen.keyguard_translate_distance_on_swipe_up,
+ 100
+ )
+ configRepository.onAnyConfigurationChange()
+
+ shadeRepository.setLegacyShadeExpansion(1f)
+
+ keyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.AOD,
+ to = KeyguardState.LOCKSCREEN,
+ testScope,
+ )
+
+ assertThat(keyguardTranslationY).isEqualTo(0f)
+ }
+
+ @Test
fun keyguardTranslationY_whenTransitioningToGoneAndShadeIsExpandingEmitsNonZero() =
testScope.runTest {
val keyguardTranslationY by collectLastValue(underTest.keyguardTranslationY)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/viewmodel/MediaCarouselViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/viewmodel/MediaCarouselViewModelTest.kt
index 4226a9d..0551bfb 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/viewmodel/MediaCarouselViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/viewmodel/MediaCarouselViewModelTest.kt
@@ -109,7 +109,7 @@
assertThat(mediaControl2.instanceId).isEqualTo(instanceId2)
assertThat(mediaControl1.instanceId).isEqualTo(instanceId1)
- underTest.onAttached()
+ underTest.onReorderingAllowed()
mediaControl1 = sortedMedia?.get(0) as MediaCommonViewModel.MediaControl
mediaControl2 = sortedMedia?.get(1) as MediaCommonViewModel.MediaControl
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeSceneViewModelTest.kt
index 5661bd3..9d8ec95 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeSceneViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeSceneViewModelTest.kt
@@ -51,8 +51,8 @@
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
- private val sceneInteractor = kosmos.sceneInteractor
- private val deviceUnlockedInteractor = kosmos.deviceUnlockedInteractor
+ private val sceneInteractor by lazy { kosmos.sceneInteractor }
+ private val deviceUnlockedInteractor by lazy { kosmos.deviceUnlockedInteractor }
private val underTest = kosmos.notificationsShadeSceneViewModel
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandlerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandlerTest.kt
index bf48784..02a8141 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandlerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandlerTest.kt
@@ -69,6 +69,15 @@
}
@Test
+ fun testPassesIntentToStarter_dismissShadeAndShowOverLockScreenWhenLocked() {
+ val intent = Intent("test.ACTION")
+
+ underTest.handle(null, intent, true)
+
+ verify(activityStarter).startActivity(eq(intent), eq(true), any(), eq(true))
+ }
+
+ @Test
fun testPassesActivityPendingIntentToStarterAsPendingIntent() {
val pendingIntent = mock<PendingIntent> { whenever(isActivity).thenReturn(true) }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/qr/domain/interactor/QRCodeScannerTileDataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/qr/domain/interactor/QRCodeScannerTileDataInteractorTest.kt
new file mode 100644
index 0000000..c41ce2f
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/qr/domain/interactor/QRCodeScannerTileDataInteractorTest.kt
@@ -0,0 +1,112 @@
+/*
+ * 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.qs.tiles.impl.qr.domain.interactor
+
+import android.content.Intent
+import android.os.UserHandle
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.qrcodescanner.controller.QRCodeScannerController
+import com.android.systemui.qrcodescanner.controller.QRCodeScannerController.Callback
+import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
+import com.android.systemui.qs.tiles.impl.qr.domain.model.QRCodeScannerTileModel
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.verify
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class QRCodeScannerTileDataInteractorTest : SysuiTestCase() {
+
+ private val testUser = UserHandle.of(1)!!
+ private val testDispatcher = StandardTestDispatcher()
+ private val scope = TestScope(testDispatcher)
+ private val testIntent = mock<Intent>()
+ private val qrCodeScannerController =
+ mock<QRCodeScannerController> {
+ whenever(intent).thenReturn(testIntent)
+ whenever(isAbleToLaunchScannerActivity).thenReturn(false)
+ }
+ private val testAvailableModel = QRCodeScannerTileModel.Available(testIntent)
+ private val testUnavailableModel = QRCodeScannerTileModel.TemporarilyUnavailable
+
+ private val underTest: QRCodeScannerTileDataInteractor =
+ QRCodeScannerTileDataInteractor(
+ testDispatcher,
+ scope.backgroundScope,
+ qrCodeScannerController,
+ )
+
+ @Test
+ fun availability_matchesController_cameraNotAvailable() =
+ scope.runTest {
+ val expectedAvailability = false
+ whenever(qrCodeScannerController.isCameraAvailable).thenReturn(false)
+
+ val availability by collectLastValue(underTest.availability(testUser))
+
+ assertThat(availability).isEqualTo(expectedAvailability)
+ }
+
+ @Test
+ fun availability_matchesController_cameraIsAvailable() =
+ scope.runTest {
+ val expectedAvailability = true
+ whenever(qrCodeScannerController.isCameraAvailable).thenReturn(true)
+
+ val availability by collectLastValue(underTest.availability(testUser))
+
+ assertThat(availability).isEqualTo(expectedAvailability)
+ }
+
+ @Test
+ fun data_matchesController() =
+ scope.runTest {
+ val captor = argumentCaptor<Callback>()
+ val lastData by
+ collectLastValue(
+ underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest))
+ )
+ runCurrent()
+
+ verify(qrCodeScannerController).addCallback(captor.capture())
+ val callback = captor.value
+
+ assertThat(lastData!!).isEqualTo(testUnavailableModel)
+
+ whenever(qrCodeScannerController.isAbleToLaunchScannerActivity).thenReturn(true)
+ callback.onQRCodeScannerActivityChanged()
+ runCurrent()
+ assertThat(lastData!!).isEqualTo(testAvailableModel)
+
+ whenever(qrCodeScannerController.isAbleToLaunchScannerActivity).thenReturn(false)
+ callback.onQRCodeScannerActivityChanged()
+ runCurrent()
+ assertThat(lastData!!).isEqualTo(testUnavailableModel)
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/qr/domain/interactor/QRCodeScannerTileUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/qr/domain/interactor/QRCodeScannerTileUserActionInteractorTest.kt
new file mode 100644
index 0000000..312f180
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/qr/domain/interactor/QRCodeScannerTileUserActionInteractorTest.kt
@@ -0,0 +1,81 @@
+/*
+ * 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.qs.tiles.impl.qr.domain.interactor
+
+import android.content.Intent
+import android.platform.test.annotations.EnabledOnRavenwood
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandlerSubject
+import com.android.systemui.qs.tiles.base.actions.qsTileIntentUserInputHandler
+import com.android.systemui.qs.tiles.base.interactor.QSTileInputTestKtx
+import com.android.systemui.qs.tiles.impl.qr.domain.model.QRCodeScannerTileModel
+import com.android.systemui.qs.tiles.impl.qr.qrCodeScannerTileUserActionInteractor
+import com.android.systemui.util.mockito.mock
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@EnabledOnRavenwood
+@RunWith(AndroidJUnit4::class)
+class QRCodeScannerTileUserActionInteractorTest : SysuiTestCase() {
+ val kosmos = Kosmos()
+ private val inputHandler = kosmos.qsTileIntentUserInputHandler
+ private val underTest = kosmos.qrCodeScannerTileUserActionInteractor
+ private val intent = mock<Intent>()
+
+ @Test
+ fun handleClick_available() = runTest {
+ val inputModel = QRCodeScannerTileModel.Available(intent)
+
+ underTest.handleInput(QSTileInputTestKtx.click(inputModel))
+
+ QSTileIntentUserInputHandlerSubject.assertThat(inputHandler).handledOneIntentInput {
+ intent
+ }
+ }
+
+ @Test
+ fun handleClick_temporarilyUnavailable() = runTest {
+ val inputModel = QRCodeScannerTileModel.TemporarilyUnavailable
+
+ underTest.handleInput(QSTileInputTestKtx.click(inputModel))
+
+ QSTileIntentUserInputHandlerSubject.assertThat(inputHandler).handledNoInputs()
+ }
+
+ @Test
+ fun handleLongClick_available() = runTest {
+ val inputModel = QRCodeScannerTileModel.Available(intent)
+
+ underTest.handleInput(QSTileInputTestKtx.longClick(inputModel))
+
+ QSTileIntentUserInputHandlerSubject.assertThat(inputHandler).handledNoInputs()
+ }
+
+ @Test
+ fun handleLongClick_temporarilyUnavailable() = runTest {
+ val inputModel = QRCodeScannerTileModel.TemporarilyUnavailable
+
+ underTest.handleInput(QSTileInputTestKtx.longClick(inputModel))
+
+ QSTileIntentUserInputHandlerSubject.assertThat(inputHandler).handledNoInputs()
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/qr/ui/QRCodeScannerTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/qr/ui/QRCodeScannerTileMapperTest.kt
new file mode 100644
index 0000000..d26a213
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/qr/ui/QRCodeScannerTileMapperTest.kt
@@ -0,0 +1,114 @@
+/*
+ * 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.qs.tiles.impl.qr.ui
+
+import android.content.Intent
+import android.graphics.drawable.TestStubDrawable
+import android.widget.Switch
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.tiles.impl.custom.QSTileStateSubject
+import com.android.systemui.qs.tiles.impl.qr.domain.model.QRCodeScannerTileModel
+import com.android.systemui.qs.tiles.impl.qr.qsQRCodeScannerTileConfig
+import com.android.systemui.qs.tiles.viewmodel.QSTileState
+import com.android.systemui.util.mockito.mock
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class QRCodeScannerTileMapperTest : SysuiTestCase() {
+ private val kosmos = Kosmos()
+ private val config = kosmos.qsQRCodeScannerTileConfig
+
+ private lateinit var mapper: QRCodeScannerTileMapper
+
+ @Before
+ fun setup() {
+ mapper =
+ QRCodeScannerTileMapper(
+ context.orCreateTestableResources
+ .apply {
+ addOverride(
+ com.android.systemui.res.R.drawable.ic_qr_code_scanner,
+ TestStubDrawable()
+ )
+ }
+ .resources,
+ context.theme
+ )
+ }
+
+ @Test
+ fun availableModel() {
+ val mockIntent = mock<Intent>()
+ val inputModel = QRCodeScannerTileModel.Available(mockIntent)
+
+ val outputState = mapper.map(config, inputModel)
+
+ val expectedState =
+ createQRCodeScannerTileState(
+ QSTileState.ActivationState.INACTIVE,
+ null,
+ )
+ QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+ }
+
+ @Test
+ fun temporarilyUnavailableModel() {
+ val inputModel = QRCodeScannerTileModel.TemporarilyUnavailable
+
+ val outputState = mapper.map(config, inputModel)
+
+ val expectedState =
+ createQRCodeScannerTileState(
+ QSTileState.ActivationState.UNAVAILABLE,
+ context.getString(
+ com.android.systemui.res.R.string.qr_code_scanner_updating_secondary_label
+ )
+ )
+ QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+ }
+
+ private fun createQRCodeScannerTileState(
+ activationState: QSTileState.ActivationState,
+ secondaryLabel: String?,
+ ): QSTileState {
+ val label = context.getString(com.android.systemui.res.R.string.qr_code_scanner_title)
+ return QSTileState(
+ {
+ Icon.Loaded(
+ context.getDrawable(com.android.systemui.res.R.drawable.ic_qr_code_scanner)!!,
+ null
+ )
+ },
+ label,
+ activationState,
+ secondaryLabel,
+ setOf(QSTileState.UserAction.CLICK),
+ label,
+ null,
+ QSTileState.SideViewIcon.Chevron,
+ QSTileState.EnabledState.ENABLED,
+ Switch::class.qualifiedName
+ )
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneBackInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneBackInteractorTest.kt
index c75e297..e3108ad 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneBackInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneBackInteractorTest.kt
@@ -45,11 +45,11 @@
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
- private val sceneInteractor = kosmos.sceneInteractor
- private val sceneContainerStartable = kosmos.sceneContainerStartable
- private val authenticationInteractor = kosmos.authenticationInteractor
+ private val sceneInteractor by lazy { kosmos.sceneInteractor }
+ private val sceneContainerStartable by lazy { kosmos.sceneContainerStartable }
+ private val authenticationInteractor by lazy { kosmos.authenticationInteractor }
- private val underTest = kosmos.sceneBackInteractor
+ private val underTest by lazy { kosmos.sceneBackInteractor }
@Test
@EnableSceneContainer
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorImplTest.kt
index e11a8f1..851b7b9 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorImplTest.kt
@@ -52,9 +52,9 @@
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
- private val deviceUnlockedInteractor = kosmos.deviceUnlockedInteractor
- private val sceneInteractor = kosmos.sceneInteractor
- private val shadeAnimationInteractor = kosmos.shadeAnimationInteractor
+ private val deviceUnlockedInteractor by lazy { kosmos.deviceUnlockedInteractor }
+ private val sceneInteractor by lazy { kosmos.sceneInteractor }
+ private val shadeAnimationInteractor by lazy { kosmos.shadeAnimationInteractor }
private val transitionState =
MutableStateFlow<ObservableTransitionState>(
ObservableTransitionState.Idle(Scenes.Lockscreen)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/DozeServiceHostCoroutinesTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/DozeServiceHostCoroutinesTest.kt
index 1cd12f0..7bc6948 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/DozeServiceHostCoroutinesTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/DozeServiceHostCoroutinesTest.kt
@@ -35,6 +35,7 @@
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
+import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito.verify
@@ -46,30 +47,32 @@
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
- private val sceneContainerRepository = kosmos.sceneContainerRepository
- private val keyguardInteractor = kosmos.keyguardInteractor
+ lateinit var underTest: DozeServiceHost
- val underTest =
- kosmos.dozeServiceHost.apply {
- initialize(
- /* centralSurfaces = */ mock(),
- /* statusBarKeyguardViewManager = */ mock(),
- /* notificationShadeWindowViewController = */ mock(),
- /* ambientIndicationContainer = */ mock(),
- )
- }
+ @Before
+ fun setup() {
+ underTest =
+ kosmos.dozeServiceHost.apply {
+ initialize(
+ /* centralSurfaces = */ mock(),
+ /* statusBarKeyguardViewManager = */ mock(),
+ /* notificationShadeWindowViewController = */ mock(),
+ /* ambientIndicationContainer = */ mock(),
+ )
+ }
+ }
@Test
@EnableSceneContainer
fun startStopDozing() =
testScope.runTest {
- val isDozing by collectLastValue(keyguardInteractor.isDozing)
+ val isDozing by collectLastValue(kosmos.keyguardInteractor.isDozing)
// GIVEN a callback is set
val callback: DozeHost.Callback = mock()
underTest.addCallback(callback)
// AND we are on the lock screen
- sceneContainerRepository.changeScene(Scenes.Lockscreen)
+ kosmos.sceneContainerRepository.changeScene(Scenes.Lockscreen)
// AND dozing is not requested yet
assertThat(underTest.dozingRequested).isFalse()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioVolumeInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioVolumeInteractorTest.kt
index 675136c..a163ca0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioVolumeInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioVolumeInteractorTest.kt
@@ -36,7 +36,6 @@
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
-import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -47,19 +46,8 @@
private val kosmos = testKosmos()
- private lateinit var underTest: AudioVolumeInteractor
-
- @Before
- fun setup() {
- with(kosmos) {
- underTest = AudioVolumeInteractor(audioRepository, notificationsSoundPolicyInteractor)
-
- audioRepository.setRingerMode(RingerMode(AudioManager.RINGER_MODE_NORMAL))
-
- notificationsSoundPolicyRepository.updateNotificationPolicy()
- notificationsSoundPolicyRepository.updateZenMode(ZenMode(Settings.Global.ZEN_MODE_OFF))
- }
- }
+ private val underTest: AudioVolumeInteractor =
+ with(kosmos) { AudioVolumeInteractor(audioRepository, notificationsSoundPolicyInteractor) }
@Test
fun setMuted_mutesStream() {
@@ -236,6 +224,55 @@
}
}
+ @Test
+ fun testReducingVolumeToMin_mutes() =
+ with(kosmos) {
+ testScope.runTest {
+ val audioStreamModel by
+ collectLastValue(audioRepository.getAudioStream(audioStream))
+ runCurrent()
+
+ underTest.setVolume(audioStream, audioStreamModel!!.minVolume)
+ runCurrent()
+
+ assertThat(audioStreamModel!!.isMuted).isTrue()
+ }
+ }
+
+ @Test
+ fun testIncreasingVolumeFromMin_unmutes() =
+ with(kosmos) {
+ testScope.runTest {
+ val audioStreamModel by
+ collectLastValue(audioRepository.getAudioStream(audioStream))
+ audioRepository.setMuted(audioStream, true)
+ audioRepository.setVolume(audioStream, audioStreamModel!!.minVolume)
+ runCurrent()
+
+ underTest.setVolume(audioStream, audioStreamModel!!.maxVolume)
+ runCurrent()
+
+ assertThat(audioStreamModel!!.isMuted).isFalse()
+ }
+ }
+
+ @Test
+ fun testUnmutingMinVolume_increasesVolume() =
+ with(kosmos) {
+ testScope.runTest {
+ val audioStreamModel by
+ collectLastValue(audioRepository.getAudioStream(audioStream))
+ audioRepository.setMuted(audioStream, true)
+ audioRepository.setVolume(audioStream, audioStreamModel!!.minVolume)
+ runCurrent()
+
+ underTest.setMuted(audioStream, false)
+ runCurrent()
+
+ assertThat(audioStreamModel!!.volume).isGreaterThan(audioStreamModel!!.minVolume)
+ }
+ }
+
private companion object {
val audioStream = AudioStream(AudioManager.STREAM_SYSTEM)
}
diff --git a/packages/SystemUI/res-keyguard/drawable/status_bar_user_chip_bg.xml b/packages/SystemUI/res-keyguard/drawable/status_bar_user_chip_bg.xml
index a751f58..370677ac 100644
--- a/packages/SystemUI/res-keyguard/drawable/status_bar_user_chip_bg.xml
+++ b/packages/SystemUI/res-keyguard/drawable/status_bar_user_chip_bg.xml
@@ -16,5 +16,5 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/material_dynamic_neutral20" />
- <corners android:radius="@dimen/ongoing_call_chip_corner_radius" />
+ <corners android:radius="@dimen/ongoing_activity_chip_corner_radius" />
</shape>
diff --git a/packages/SystemUI/res/drawable/contrast_dialog_button_background.xml b/packages/SystemUI/res/drawable/contrast_dialog_button_background.xml
deleted file mode 100644
index 4181220..0000000
--- a/packages/SystemUI/res/drawable/contrast_dialog_button_background.xml
+++ /dev/null
@@ -1,46 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-* Copyright 2023, The Android Open Source Project
-*
-* Licensed under the Apache License, Version 2.0 (the "License");
-* you may not use this file except in compliance with the License.
-* You may obtain a copy of the License at
-*
-* http://www.apache.org/licenses/LICENSE-2.0
-*
-* Unless required by applicable law or agreed to in writing, software
-* distributed under the License is distributed on an "AS IS" BASIS,
-* WITHOUT 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"
- xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
-
- <item android:state_selected="true">
- <shape android:shape="rectangle">
- <solid android:color="?androidprv:attr/colorSurfaceHighlight" />
- <stroke
- android:color="?androidprv:attr/colorAccentPrimary"
- android:width="@dimen/contrast_dialog_button_stroke_width" />
- <corners android:radius="@dimen/contrast_dialog_button_radius"/>
- </shape>
- </item>
-
- <item>
- <layer-list>
- <item android:top="@dimen/contrast_dialog_button_stroke_width"
- android:bottom="@dimen/contrast_dialog_button_stroke_width"
- android:left="@dimen/contrast_dialog_button_stroke_width"
- android:right="@dimen/contrast_dialog_button_stroke_width">
- <shape android:shape="rectangle">
- <solid android:color="?androidprv:attr/colorSurfaceHighlight" />
- <corners android:radius="@dimen/contrast_dialog_button_radius"/>
- </shape>
- </item>
- </layer-list>
- </item>
-</selector>
diff --git a/packages/SystemUI/res/drawable/ongoing_call_chip_bg.xml b/packages/SystemUI/res/drawable/hub_handle.xml
similarity index 77%
copy from packages/SystemUI/res/drawable/ongoing_call_chip_bg.xml
copy to packages/SystemUI/res/drawable/hub_handle.xml
index bdd6270..8bc276f 100644
--- a/packages/SystemUI/res/drawable/ongoing_call_chip_bg.xml
+++ b/packages/SystemUI/res/drawable/hub_handle.xml
@@ -1,5 +1,5 @@
-<!--
- ~ Copyright (C) 2021 The Android Open Source Project
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ 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.
@@ -15,6 +15,6 @@
-->
<shape xmlns:android="http://schemas.android.com/apk/res/android">
- <solid android:color="?android:attr/colorAccent" />
- <corners android:radius="@dimen/ongoing_call_chip_corner_radius" />
+ <corners android:radius="4dp" />
+ <solid android:color="#FFFFFF" />
</shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/ic_contrast_high.xml b/packages/SystemUI/res/drawable/ic_contrast_high.xml
deleted file mode 100644
index aa5b5ab..0000000
--- a/packages/SystemUI/res/drawable/ic_contrast_high.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<!--
- ~ Copyright (C) 2023 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT 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 android:autoMirrored="true" android:height="20dp"
- android:viewportHeight="20" android:viewportWidth="66"
- android:width="66dp" xmlns:android="http://schemas.android.com/apk/res/android">
- <path android:fillColor="#F2F1E8"
- android:pathData="M0.5,8C0.5,3.858 3.858,0.5 8,0.5H58C62.142,0.5 65.5,3.858 65.5,8V12C65.5,16.142 62.142,19.5 58,19.5H8C3.858,19.5 0.5,16.142 0.5,12V8Z"
- android:strokeColor="#1B1C17" android:strokeWidth="1"/>
- <path android:fillColor="#1B1C17" android:pathData="M11,10m-6,0a6,6 0,1 1,12 0a6,6 0,1 1,-12 0"/>
- <path android:fillColor="#1B1C17" android:pathData="M23,5L43,5A2,2 0,0 1,45 7L45,7A2,2 0,0 1,43 9L23,9A2,2 0,0 1,21 7L21,7A2,2 0,0 1,23 5z"/>
- <path android:fillColor="#1B1C17" android:pathData="M23,11L55,11A2,2 0,0 1,57 13L57,13A2,2 0,0 1,55 15L23,15A2,2 0,0 1,21 13L21,13A2,2 0,0 1,23 11z"/>
-</vector>
diff --git a/packages/SystemUI/res/drawable/ic_contrast_medium.xml b/packages/SystemUI/res/drawable/ic_contrast_medium.xml
deleted file mode 100644
index 89519b8..0000000
--- a/packages/SystemUI/res/drawable/ic_contrast_medium.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<!--
- ~ Copyright (C) 2023 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT 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 android:autoMirrored="true" android:height="20dp"
- android:viewportHeight="20" android:viewportWidth="66"
- android:width="66dp" xmlns:android="http://schemas.android.com/apk/res/android">
- <path android:fillColor="#F2F1E8" android:pathData="M0,8C0,3.582 3.582,0 8,0H58C62.418,0 66,3.582 66,8V12C66,16.418 62.418,20 58,20H8C3.582,20 0,16.418 0,12V8Z"/>
- <path android:fillColor="#919283" android:pathData="M11,10m-6,0a6,6 0,1 1,12 0a6,6 0,1 1,-12 0"/>
- <path android:fillColor="#919283" android:pathData="M23,5L43,5A2,2 0,0 1,45 7L45,7A2,2 0,0 1,43 9L23,9A2,2 0,0 1,21 7L21,7A2,2 0,0 1,23 5z"/>
- <path android:fillColor="#919283" android:pathData="M23,11L55,11A2,2 0,0 1,57 13L57,13A2,2 0,0 1,55 15L23,15A2,2 0,0 1,21 13L21,13A2,2 0,0 1,23 11z"/>
-</vector>
diff --git a/packages/SystemUI/res/drawable/ic_contrast_standard.xml b/packages/SystemUI/res/drawable/ic_contrast_standard.xml
deleted file mode 100644
index f914975..0000000
--- a/packages/SystemUI/res/drawable/ic_contrast_standard.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<!--
- ~ Copyright (C) 2023 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT 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 android:autoMirrored="true" android:height="20dp"
- android:viewportHeight="20" android:viewportWidth="66"
- android:width="66dp" xmlns:android="http://schemas.android.com/apk/res/android">
- <path android:fillColor="#C7C8B7" android:pathData="M0,8C0,3.582 3.582,0 8,0H58C62.418,0 66,3.582 66,8V12C66,16.418 62.418,20 58,20H8C3.582,20 0,16.418 0,12V8Z"/>
- <path android:fillColor="#919283" android:pathData="M11,10m-6,0a6,6 0,1 1,12 0a6,6 0,1 1,-12 0"/>
- <path android:fillColor="#919283" android:pathData="M23,5L43,5A2,2 0,0 1,45 7L45,7A2,2 0,0 1,43 9L23,9A2,2 0,0 1,21 7L21,7A2,2 0,0 1,23 5z"/>
- <path android:fillColor="#919283" android:pathData="M23,11L55,11A2,2 0,0 1,57 13L57,13A2,2 0,0 1,55 15L23,15A2,2 0,0 1,21 13L21,13A2,2 0,0 1,23 11z"/>
-</vector>
diff --git a/packages/SystemUI/res/drawable/ongoing_call_chip_bg.xml b/packages/SystemUI/res/drawable/ongoing_activity_chip_bg.xml
similarity index 90%
rename from packages/SystemUI/res/drawable/ongoing_call_chip_bg.xml
rename to packages/SystemUI/res/drawable/ongoing_activity_chip_bg.xml
index bdd6270..b9a4cbf 100644
--- a/packages/SystemUI/res/drawable/ongoing_call_chip_bg.xml
+++ b/packages/SystemUI/res/drawable/ongoing_activity_chip_bg.xml
@@ -16,5 +16,5 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="?android:attr/colorAccent" />
- <corners android:radius="@dimen/ongoing_call_chip_corner_radius" />
+ <corners android:radius="@dimen/ongoing_activity_chip_corner_radius" />
</shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout-land/biometric_prompt_constraint_layout.xml b/packages/SystemUI/res/layout-land/biometric_prompt_constraint_layout.xml
index 5755dcd..01b9f7e 100644
--- a/packages/SystemUI/res/layout-land/biometric_prompt_constraint_layout.xml
+++ b/packages/SystemUI/res/layout-land/biometric_prompt_constraint_layout.xml
@@ -26,8 +26,8 @@
android:paddingVertical="16dp"
android:visibility="visible"
app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintEnd_toStartOf="@+id/rightGuideline"
- app:layout_constraintStart_toStartOf="@+id/leftGuideline"
+ app:layout_constraintRight_toLeftOf="@+id/rightGuideline"
+ app:layout_constraintLeft_toLeftOf="@+id/leftGuideline"
app:layout_constraintTop_toTopOf="@+id/topGuideline" />
<com.android.systemui.biometrics.BiometricPromptLottieViewWrapper
@@ -35,8 +35,8 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintEnd_toEndOf="parent"
- app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintRight_toRightOf="parent"
+ app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:srcCompat="@tools:sample/avatars" />
@@ -63,8 +63,8 @@
android:paddingTop="24dp"
android:fadeScrollbars="false"
app:layout_constraintBottom_toTopOf="@+id/button_bar"
- app:layout_constraintEnd_toStartOf="@+id/midGuideline"
- app:layout_constraintStart_toStartOf="@id/leftGuideline"
+ app:layout_constraintRight_toLeftOf="@+id/midGuideline"
+ app:layout_constraintLeft_toLeftOf="@id/leftGuideline"
app:layout_constraintTop_toTopOf="@+id/topGuideline">
<androidx.constraintlayout.widget.ConstraintLayout
@@ -89,7 +89,7 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:textAlignment="viewStart"
- android:paddingLeft="16dp"
+ android:paddingStart="16dp"
app:layout_constraintBottom_toBottomOf="@+id/logo"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/logo"
@@ -209,6 +209,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
+ app:guidelineUseRtl="false"
app:layout_constraintGuide_begin="@dimen/biometric_dialog_border_padding" />
<androidx.constraintlayout.widget.Guideline
@@ -216,6 +217,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
+ app:guidelineUseRtl="false"
app:layout_constraintGuide_end="@dimen/biometric_dialog_border_padding" />
<androidx.constraintlayout.widget.Guideline
@@ -223,6 +225,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
+ app:guidelineUseRtl="false"
app:layout_constraintGuide_begin="406dp" />
<androidx.constraintlayout.widget.Guideline
diff --git a/packages/SystemUI/res/layout/biometric_prompt_button_bar.xml b/packages/SystemUI/res/layout/biometric_prompt_button_bar.xml
index 4d2310a..0bbe73c 100644
--- a/packages/SystemUI/res/layout/biometric_prompt_button_bar.xml
+++ b/packages/SystemUI/res/layout/biometric_prompt_button_bar.xml
@@ -27,7 +27,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
- android:layout_marginLeft="24dp"
+ android:layout_marginStart="24dp"
android:ellipsize="end"
android:maxLines="2"
android:visibility="invisible"
@@ -41,7 +41,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
- android:layout_marginLeft="24dp"
+ android:layout_marginStart="24dp"
android:text="@string/cancel"
android:visibility="invisible"
app:layout_constraintBottom_toBottomOf="parent"
@@ -54,7 +54,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
- android:layout_marginLeft="24dp"
+ android:layout_marginStart="24dp"
android:visibility="invisible"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent" />
@@ -66,7 +66,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
- android:layout_marginRight="24dp"
+ android:layout_marginEnd="24dp"
android:ellipsize="end"
android:maxLines="2"
android:text="@string/biometric_dialog_confirm"
@@ -81,7 +81,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
- android:layout_marginRight="24dp"
+ android:layout_marginEnd="24dp"
android:ellipsize="end"
android:maxLines="2"
android:text="@string/biometric_dialog_try_again"
diff --git a/packages/SystemUI/res/layout/contrast_dialog.xml b/packages/SystemUI/res/layout/contrast_dialog.xml
deleted file mode 100644
index 8e885cf..0000000
--- a/packages/SystemUI/res/layout/contrast_dialog.xml
+++ /dev/null
@@ -1,127 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2023 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
- -->
-
-<LinearLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="horizontal">
-
- <Space
- android:layout_width="0dp"
- android:layout_height="match_parent"
- android:layout_weight="1"/>
-
- <LinearLayout
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:orientation="vertical">
-
- <FrameLayout
- android:id="@+id/contrast_button_standard"
- android:layout_width="@dimen/contrast_dialog_button_total_size"
- android:layout_height="@dimen/contrast_dialog_button_total_size"
- android:background="@drawable/contrast_dialog_button_background">
-
- <ImageView
- android:layout_gravity="center"
- android:layout_height="wrap_content"
- android:layout_width="wrap_content"
- android:src="@drawable/ic_contrast_standard"/>
- </FrameLayout>
-
- <TextView
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginTop="@dimen/contrast_dialog_button_text_spacing"
- android:gravity="center_horizontal|top"
- android:textSize="@dimen/contrast_dialog_button_text_size"
- android:text="@string/quick_settings_contrast_standard"
- android:textColor="?androidprv:attr/textColorPrimary"/>
- </LinearLayout>
-
- <Space
- android:layout_width="@dimen/contrast_dialog_button_horizontal_spacing"
- android:layout_height="match_parent" />
-
- <LinearLayout
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:orientation="vertical">
-
- <FrameLayout
- android:id="@+id/contrast_button_medium"
- android:layout_width="@dimen/contrast_dialog_button_total_size"
- android:layout_height="@dimen/contrast_dialog_button_total_size"
- android:background="@drawable/contrast_dialog_button_background">
-
- <ImageView
- android:layout_gravity="center"
- android:layout_height="wrap_content"
- android:layout_width="wrap_content"
- android:src="@drawable/ic_contrast_medium"/>
- </FrameLayout>
-
- <TextView
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginTop="@dimen/contrast_dialog_button_text_spacing"
- android:gravity="center_horizontal|top"
- android:textSize="@dimen/contrast_dialog_button_text_size"
- android:text="@string/quick_settings_contrast_medium"
- android:textColor="?androidprv:attr/textColorPrimary"/>
- </LinearLayout>
-
- <Space
- android:layout_width="@dimen/contrast_dialog_button_horizontal_spacing"
- android:layout_height="match_parent" />
-
- <LinearLayout
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:orientation="vertical">
-
- <FrameLayout
- android:id="@+id/contrast_button_high"
- android:layout_width="@dimen/contrast_dialog_button_total_size"
- android:layout_height="@dimen/contrast_dialog_button_total_size"
- android:background="@drawable/contrast_dialog_button_background">
-
- <ImageView
- android:layout_gravity="center"
- android:layout_height="wrap_content"
- android:layout_width="wrap_content"
- android:src="@drawable/ic_contrast_high"/>
-
- </FrameLayout>
-
- <TextView
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginTop="@dimen/contrast_dialog_button_text_spacing"
- android:gravity="center_horizontal|top"
- android:textSize="@dimen/contrast_dialog_button_text_size"
- android:text="@string/quick_settings_contrast_high"
- android:textColor="?androidprv:attr/textColorPrimary"/>
- </LinearLayout>
-
- <Space
- android:layout_width="0dp"
- android:layout_height="match_parent"
- android:layout_weight="1"/>
-</LinearLayout>
diff --git a/packages/SystemUI/res/layout/dream_overlay_container.xml b/packages/SystemUI/res/layout/dream_overlay_container.xml
index 19fb874..4234fca5 100644
--- a/packages/SystemUI/res/layout/dream_overlay_container.xml
+++ b/packages/SystemUI/res/layout/dream_overlay_container.xml
@@ -21,6 +21,19 @@
android:layout_width="match_parent"
android:layout_height="match_parent">
+ <ImageView
+ android:id="@+id/glanceable_hub_handle"
+ android:layout_width="4dp"
+ android:layout_height="220dp"
+ android:layout_centerVertical="true"
+ android:layout_marginEnd="12dp"
+ android:background="@drawable/hub_handle"
+ android:visibility="gone"
+ android:contentDescription="UI indicator for swiping open the glanceable hub"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintTop_toTopOf="parent" />
+
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/dream_overlay_content"
android:layout_width="match_parent"
diff --git a/packages/SystemUI/res/layout/ongoing_call_chip.xml b/packages/SystemUI/res/layout/ongoing_activity_chip.xml
similarity index 66%
rename from packages/SystemUI/res/layout/ongoing_call_chip.xml
rename to packages/SystemUI/res/layout/ongoing_activity_chip.xml
index 6a0217ec..a33be12 100644
--- a/packages/SystemUI/res/layout/ongoing_call_chip.xml
+++ b/packages/SystemUI/res/layout/ongoing_activity_chip.xml
@@ -17,43 +17,45 @@
the chip. -->
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/ongoing_call_chip"
+ android:id="@+id/ongoing_activity_chip"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="center_vertical|start"
android:layout_marginStart="5dp"
>
- <com.android.systemui.statusbar.phone.ongoingcall.OngoingCallBackgroundContainer
- android:id="@+id/ongoing_call_chip_background"
+ <!-- TODO(b/332662551): Update this content description when this supports more than just
+ phone calls. -->
+ <com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer
+ android:id="@+id/ongoing_activity_chip_background"
android:layout_width="wrap_content"
android:layout_height="@dimen/ongoing_appops_chip_height"
android:layout_gravity="center_vertical"
android:gravity="center"
- android:background="@drawable/ongoing_call_chip_bg"
- android:paddingStart="@dimen/ongoing_call_chip_side_padding"
- android:paddingEnd="@dimen/ongoing_call_chip_side_padding"
+ android:background="@drawable/ongoing_activity_chip_bg"
+ android:paddingStart="@dimen/ongoing_activity_chip_side_padding"
+ android:paddingEnd="@dimen/ongoing_activity_chip_side_padding"
android:contentDescription="@string/ongoing_phone_call_content_description"
android:minWidth="@dimen/min_clickable_item_size"
>
<ImageView
android:src="@*android:drawable/ic_phone"
- android:layout_width="@dimen/ongoing_call_chip_icon_size"
- android:layout_height="@dimen/ongoing_call_chip_icon_size"
+ android:layout_width="@dimen/ongoing_activity_chip_icon_size"
+ android:layout_height="@dimen/ongoing_activity_chip_icon_size"
android:tint="?android:attr/colorPrimary"
/>
- <com.android.systemui.statusbar.phone.ongoingcall.OngoingCallChronometer
- android:id="@+id/ongoing_call_chip_time"
+ <com.android.systemui.statusbar.chips.ui.view.ChipChronometer
+ android:id="@+id/ongoing_activity_chip_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:singleLine="true"
android:gravity="center|start"
- android:paddingStart="@dimen/ongoing_call_chip_icon_text_padding"
+ android:paddingStart="@dimen/ongoing_activity_chip_icon_text_padding"
android:textAppearance="@android:style/TextAppearance.Material.Small"
android:fontFamily="@*android:string/config_headlineFontFamily"
android:textColor="?android:attr/colorPrimary"
/>
- </com.android.systemui.statusbar.phone.ongoingcall.OngoingCallBackgroundContainer>
+ </com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer>
</FrameLayout>
diff --git a/packages/SystemUI/res/layout/status_bar.xml b/packages/SystemUI/res/layout/status_bar.xml
index 452bc31..4247c7e 100644
--- a/packages/SystemUI/res/layout/status_bar.xml
+++ b/packages/SystemUI/res/layout/status_bar.xml
@@ -99,7 +99,7 @@
android:gravity="center_vertical|start"
/>
- <include layout="@layout/ongoing_call_chip" />
+ <include layout="@layout/ongoing_activity_chip" />
<com.android.systemui.statusbar.AlphaOptimizedFrameLayout
android:id="@+id/notification_icon_area"
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 517b44f..9d0319c 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -914,8 +914,8 @@
<dimen name="communal_enforced_rounded_corner_max_radius">28dp</dimen>
<!-- Width and height used to filter widgets displayed in the communal widget picker -->
- <dimen name="communal_widget_picker_desired_width">424dp</dimen>
- <dimen name="communal_widget_picker_desired_height">282dp</dimen>
+ <dimen name="communal_widget_picker_desired_width">360dp</dimen>
+ <dimen name="communal_widget_picker_desired_height">240dp</dimen>
<!-- The width/height of the unlock icon view on keyguard. -->
<dimen name="keyguard_lock_height">42dp</dimen>
@@ -1713,12 +1713,12 @@
<dimen name="wallet_button_horizontal_padding">24dp</dimen>
<dimen name="wallet_button_vertical_padding">8dp</dimen>
- <!-- Ongoing call chip -->
- <dimen name="ongoing_call_chip_side_padding">12dp</dimen>
- <dimen name="ongoing_call_chip_icon_size">16dp</dimen>
+ <!-- Ongoing activity chip -->
+ <dimen name="ongoing_activity_chip_side_padding">12dp</dimen>
+ <dimen name="ongoing_activity_chip_icon_size">16dp</dimen>
<!-- The padding between the icon and the text. -->
- <dimen name="ongoing_call_chip_icon_text_padding">4dp</dimen>
- <dimen name="ongoing_call_chip_corner_radius">28dp</dimen>
+ <dimen name="ongoing_activity_chip_icon_text_padding">4dp</dimen>
+ <dimen name="ongoing_activity_chip_corner_radius">28dp</dimen>
<!-- Status bar user chip -->
<dimen name="status_bar_user_chip_avatar_size">16dp</dimen>
@@ -1963,15 +1963,6 @@
<dimen name="broadcast_dialog_btn_minHeight">44dp</dimen>
<dimen name="broadcast_dialog_margin">16dp</dimen>
- <!-- Contrast dialog -->
- <dimen name="contrast_dialog_button_total_size">90dp</dimen>
- <dimen name="contrast_dialog_button_inner_size">82dp</dimen>
- <dimen name="contrast_dialog_button_radius">20dp</dimen>
- <dimen name="contrast_dialog_button_stroke_width">4dp</dimen>
- <dimen name="contrast_dialog_button_text_size">14sp</dimen>
- <dimen name="contrast_dialog_button_text_spacing">4dp</dimen>
- <dimen name="contrast_dialog_button_horizontal_spacing">16dp</dimen>
-
<!-- Shadow for dream overlay clock complication -->
<dimen name="dream_overlay_clock_key_text_shadow_dx">0dp</dimen>
<dimen name="dream_overlay_clock_key_text_shadow_dy">0dp</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index aecc906..8da8316f 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -902,15 +902,6 @@
<!-- QuickSettings: Label for the toggle that controls whether One-handed mode is enabled. [CHAR LIMIT=NONE] -->
<string name="quick_settings_onehanded_label">One-handed mode</string>
- <!-- QuickSettings: Contrast tile [CHAR LIMIT=NONE] -->
- <string name="quick_settings_contrast_label">Contrast</string>
- <!-- QuickSettings: Contrast tile description: standard [CHAR LIMIT=NONE] -->
- <string name="quick_settings_contrast_standard">Standard</string>
- <!-- QuickSettings: Contrast tile description: medium [CHAR LIMIT=NONE] -->
- <string name="quick_settings_contrast_medium">Medium</string>
- <!-- QuickSettings: Contrast tile description: high [CHAR LIMIT=NONE] -->
- <string name="quick_settings_contrast_high">High</string>
-
<!-- Hearing devices -->
<!-- QuickSettings: Hearing devices [CHAR LIMIT=NONE] -->
<string name="quick_settings_hearing_devices_label">Hearing devices</string>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 2c4cdb9..393a1aa 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -523,10 +523,6 @@
<item name="android:windowExitAnimation">@anim/instant_fade_out</item>
</style>
- <style name="Theme.SystemUI.ContrastDialog" parent="@android:style/Theme.DeviceDefault.Dialog">
- <item name="android:windowBackground">@android:color/transparent</item>
- </style>
-
<style name="Theme.SystemUI.QuickSettings.Dialog" parent="@style/Theme.SystemUI.Dialog.QuickSettings">
</style>
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepository.kt b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepository.kt
index cc52484..ca03a00 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepository.kt
@@ -34,6 +34,7 @@
import android.hardware.biometrics.events.AuthenticationSucceededInfo
import android.hardware.face.FaceManager
import android.hardware.fingerprint.FingerprintManager
+import android.util.Log
import com.android.systemui.biometrics.shared.model.AuthenticationReason
import com.android.systemui.biometrics.shared.model.AuthenticationReason.SettingsOperations
import com.android.systemui.biometrics.shared.model.AuthenticationState
@@ -52,6 +53,7 @@
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.filterIsInstance
import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.shareIn
/** A repository for the state of biometric authentication. */
@@ -85,6 +87,7 @@
private val authenticationState: Flow<AuthenticationState> =
conflatedCallbackFlow {
val updateAuthenticationState = { state: AuthenticationState ->
+ Log.d(TAG, "authenticationState updated: $state")
trySendWithFailureLogging(state, TAG, "Error sending AuthenticationState state")
}
@@ -187,6 +190,7 @@
it.biometricSourceType == BiometricSourceType.FINGERPRINT)
}
.map { it.requestReason }
+ .onEach { Log.d(TAG, "fingerprintAuthenticationReason updated: $it") }
override val fingerprintAcquiredStatus: Flow<FingerprintAuthenticationStatus> =
authenticationState
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepository.kt b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepository.kt
index 40d38dd..6b61adc 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepository.kt
@@ -30,10 +30,10 @@
import com.android.systemui.biometrics.shared.model.toSensorStrength
import com.android.systemui.biometrics.shared.model.toSensorType
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
@@ -42,6 +42,7 @@
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.withContext
@@ -52,7 +53,7 @@
*/
interface FingerprintPropertyRepository {
/** Whether the fingerprint properties have been initialized yet. */
- val propertiesInitialized: StateFlow<Boolean>
+ val propertiesInitialized: Flow<Boolean>
/** The id of fingerprint sensor. */
val sensorId: Flow<Int>
@@ -110,14 +111,8 @@
initialValue = UNINITIALIZED_PROPS,
)
- override val propertiesInitialized: StateFlow<Boolean> =
- props
- .map { it != UNINITIALIZED_PROPS }
- .stateIn(
- applicationScope,
- started = SharingStarted.WhileSubscribed(),
- initialValue = props.value != UNINITIALIZED_PROPS,
- )
+ override val propertiesInitialized: Flow<Boolean> =
+ props.map { it != UNINITIALIZED_PROPS }.onStart { emit(props.value != UNINITIALIZED_PROPS) }
override val sensorId: Flow<Int> = props.map { it.sensorId }
@@ -141,7 +136,7 @@
companion object {
private const val TAG = "FingerprintPropertyRepositoryImpl"
- private val UNINITIALIZED_PROPS =
+ val UNINITIALIZED_PROPS =
FingerprintSensorPropertiesInternal(
-2 /* sensorId */,
SensorProperties.STRENGTH_CONVENIENCE,
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/BiometricStatusInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/BiometricStatusInteractor.kt
index 6e79e46..83aefca 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/BiometricStatusInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/BiometricStatusInteractor.kt
@@ -17,6 +17,7 @@
package com.android.systemui.biometrics.domain.interactor
import android.app.ActivityTaskManager
+import android.util.Log
import com.android.systemui.biometrics.data.repository.BiometricStatusRepository
import com.android.systemui.biometrics.data.repository.FingerprintPropertyRepository
import com.android.systemui.biometrics.shared.model.AuthenticationReason
@@ -26,6 +27,7 @@
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.onEach
/** Encapsulates business logic for interacting with biometric authentication state. */
interface BiometricStatusInteractor {
@@ -49,15 +51,20 @@
override val sfpsAuthenticationReason: Flow<AuthenticationReason> =
combine(
- biometricStatusRepository.fingerprintAuthenticationReason,
- fingerprintPropertyRepository.sensorType
- ) { reason: AuthenticationReason, sensorType ->
- if (sensorType.isPowerButton() && reason.isReasonToAlwaysUpdateSfpsOverlay(activityTaskManager)) {
- reason
- } else {
- AuthenticationReason.NotRunning
+ biometricStatusRepository.fingerprintAuthenticationReason,
+ fingerprintPropertyRepository.sensorType
+ ) { reason: AuthenticationReason, sensorType ->
+ if (
+ sensorType.isPowerButton() &&
+ reason.isReasonToAlwaysUpdateSfpsOverlay(activityTaskManager)
+ ) {
+ reason
+ } else {
+ AuthenticationReason.NotRunning
+ }
}
- }.distinctUntilChanged()
+ .distinctUntilChanged()
+ .onEach { Log.d(TAG, "sfpsAuthenticationReason updated: $it") }
override val fingerprintAcquiredStatus: Flow<FingerprintAuthenticationStatus> =
biometricStatusRepository.fingerprintAcquiredStatus
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/FingerprintPropertyInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/FingerprintPropertyInteractor.kt
index 3112b67..d5b450d 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/FingerprintPropertyInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/FingerprintPropertyInteractor.kt
@@ -46,7 +46,7 @@
displayStateInteractor: DisplayStateInteractor,
udfpsOverlayInteractor: UdfpsOverlayInteractor,
) {
- val propertiesInitialized: StateFlow<Boolean> = repository.propertiesInitialized
+ val propertiesInitialized: Flow<Boolean> = repository.propertiesInitialized
val isUdfps: StateFlow<Boolean> =
repository.sensorType
.map { it.isUdfps() }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt
index f0969ed..13ea3f5 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt
@@ -199,29 +199,32 @@
iconParams.leftMargin = position.left
mediumConstraintSet.clear(
R.id.biometric_icon,
- ConstraintSet.END
+ ConstraintSet.RIGHT
)
mediumConstraintSet.connect(
R.id.biometric_icon,
- ConstraintSet.START,
+ ConstraintSet.LEFT,
ConstraintSet.PARENT_ID,
- ConstraintSet.START
+ ConstraintSet.LEFT
)
mediumConstraintSet.setMargin(
R.id.biometric_icon,
- ConstraintSet.START,
+ ConstraintSet.LEFT,
position.left
)
- smallConstraintSet.clear(R.id.biometric_icon, ConstraintSet.END)
+ smallConstraintSet.clear(
+ R.id.biometric_icon,
+ ConstraintSet.RIGHT
+ )
smallConstraintSet.connect(
R.id.biometric_icon,
- ConstraintSet.START,
+ ConstraintSet.LEFT,
ConstraintSet.PARENT_ID,
- ConstraintSet.START
+ ConstraintSet.LEFT
)
smallConstraintSet.setMargin(
R.id.biometric_icon,
- ConstraintSet.START,
+ ConstraintSet.LEFT,
position.left
)
}
@@ -252,32 +255,32 @@
iconParams.rightMargin = position.right
mediumConstraintSet.clear(
R.id.biometric_icon,
- ConstraintSet.START
+ ConstraintSet.LEFT
)
mediumConstraintSet.connect(
R.id.biometric_icon,
- ConstraintSet.END,
+ ConstraintSet.RIGHT,
ConstraintSet.PARENT_ID,
- ConstraintSet.END
+ ConstraintSet.RIGHT
)
mediumConstraintSet.setMargin(
R.id.biometric_icon,
- ConstraintSet.END,
+ ConstraintSet.RIGHT,
position.right
)
smallConstraintSet.clear(
R.id.biometric_icon,
- ConstraintSet.START
+ ConstraintSet.LEFT
)
smallConstraintSet.connect(
R.id.biometric_icon,
- ConstraintSet.END,
+ ConstraintSet.RIGHT,
ConstraintSet.PARENT_ID,
- ConstraintSet.END
+ ConstraintSet.RIGHT
)
smallConstraintSet.setMargin(
R.id.biometric_icon,
- ConstraintSet.END,
+ ConstraintSet.RIGHT,
position.right
)
}
@@ -383,15 +386,15 @@
// Move all content to other panel
flipConstraintSet.connect(
R.id.scrollView,
- ConstraintSet.START,
+ ConstraintSet.LEFT,
R.id.midGuideline,
- ConstraintSet.START
+ ConstraintSet.LEFT
)
flipConstraintSet.connect(
R.id.scrollView,
- ConstraintSet.END,
+ ConstraintSet.RIGHT,
R.id.rightGuideline,
- ConstraintSet.END
+ ConstraintSet.RIGHT
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt
index 4bdbfa2..ff7ac35 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt
@@ -20,6 +20,7 @@
import android.content.Context
import android.graphics.PorterDuff
import android.graphics.PorterDuffColorFilter
+import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.WindowManager
@@ -91,6 +92,13 @@
showIndicatorForDeviceEntry,
progressBarIsVisible) =
combinedFlows
+ Log.d(
+ TAG,
+ "systemServerAuthReason = $systemServerAuthReason, " +
+ "showIndicatorForDeviceEntry = " +
+ "$showIndicatorForDeviceEntry, " +
+ "progressBarIsVisible = $progressBarIsVisible"
+ )
if (!isInRearDisplayMode) {
if (progressBarIsVisible) {
hide()
@@ -114,6 +122,10 @@
/** Show the side fingerprint sensor indicator */
private fun show() {
if (overlayView?.isAttachedToWindow == true) {
+ Log.d(
+ TAG,
+ "show(): overlayView $overlayView isAttachedToWindow already, ignoring show request"
+ )
return
}
@@ -128,6 +140,7 @@
)
bind(overlayView!!, overlayViewModel, fpsUnlockTracker.get(), windowManager.get())
overlayView!!.visibility = View.INVISIBLE
+ Log.d(TAG, "show(): adding overlayView $overlayView")
windowManager.get().addView(overlayView, overlayViewModel.defaultOverlayViewParams)
}
@@ -137,6 +150,7 @@
val lottie = overlayView!!.requireViewById<LottieAnimationView>(R.id.sidefps_animation)
lottie.pauseAnimation()
lottie.removeAllLottieOnCompositionLoadedListener()
+ Log.d(TAG, "hide(): removing overlayView $overlayView, setting to null")
windowManager.get().removeView(overlayView)
overlayView = null
}
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegate.kt
index f0230be..911145b 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegate.kt
@@ -419,8 +419,7 @@
const val ACTION_PREVIOUSLY_CONNECTED_DEVICE =
"com.android.settings.PREVIOUSLY_CONNECTED_DEVICE"
const val ACTION_PAIR_NEW_DEVICE = "android.settings.BLUETOOTH_PAIRING_SETTINGS"
- const val ACTION_AUDIO_SHARING =
- "com.google.android.settings.BLUETOOTH_AUDIO_SHARING_SETTINGS"
+ const val ACTION_AUDIO_SHARING = "com.android.settings.BLUETOOTH_AUDIO_SHARING_SETTINGS"
const val DISABLED_ALPHA = 0.3f
const val ENABLED_ALPHA = 1f
const val PROGRESS_BAR_ANIMATION_DURATION_MS = 1500L
diff --git a/packages/SystemUI/src/com/android/systemui/brightness/data/repository/BrightnessPolicyRepository.kt b/packages/SystemUI/src/com/android/systemui/brightness/data/repository/BrightnessPolicyRepository.kt
index c018ecb..0544a4f 100644
--- a/packages/SystemUI/src/com/android/systemui/brightness/data/repository/BrightnessPolicyRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/brightness/data/repository/BrightnessPolicyRepository.kt
@@ -18,6 +18,8 @@
import android.content.Context
import android.os.UserManager
+import com.android.settingslib.RestrictedLockUtils
+import com.android.systemui.Flags.enforceBrightnessBaseUserRestriction
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
@@ -66,7 +68,18 @@
user.id
)
?.let { PolicyRestriction.Restricted(it) }
- ?: PolicyRestriction.NoRestriction
+ ?: if (
+ enforceBrightnessBaseUserRestriction() &&
+ userRestrictionChecker.hasBaseUserRestriction(
+ applicationContext,
+ UserManager.DISALLOW_CONFIG_BRIGHTNESS,
+ user.id
+ )
+ ) {
+ PolicyRestriction.Restricted(RestrictedLockUtils.EnforcedAdmin())
+ } else {
+ PolicyRestriction.NoRestriction
+ }
}
.flowOn(backgroundDispatcher)
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
index 656e5cb..97db43b 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
@@ -25,6 +25,7 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.log.LogBuffer
@@ -63,6 +64,7 @@
@Application private val scope: CoroutineScope,
@Main private val resources: Resources,
keyguardTransitionInteractor: KeyguardTransitionInteractor,
+ keyguardInteractor: KeyguardInteractor,
private val communalInteractor: CommunalInteractor,
tutorialInteractor: CommunalTutorialInteractor,
private val shadeInteractor: ShadeInteractor,
@@ -236,6 +238,14 @@
*/
val touchesAllowed: Flow<Boolean> = not(shadeInteractor.isAnyFullyExpanded)
+ // TODO(b/339667383): remove this temporary swipe gesture handle
+ /**
+ * The dream overlay has its own gesture handle as the SysUI window is not visible above the
+ * dream. This flow will be false when dreaming so that we don't show a duplicate handle when
+ * opening the hub over the dream.
+ */
+ val showGestureIndicator: Flow<Boolean> = not(keyguardInteractor.isDreaming)
+
companion object {
const val POPUP_AUTO_HIDE_TIMEOUT_MS = 12000L
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostView.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostView.kt
index 840c3a8..2559137 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostView.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostView.kt
@@ -17,6 +17,7 @@
package com.android.systemui.communal.widgets
import android.appwidget.AppWidgetHostView
+import android.appwidget.AppWidgetProviderInfo
import android.content.Context
import android.graphics.Outline
import android.graphics.Rect
@@ -50,6 +51,11 @@
enforceRoundedCorners()
}
+ override fun setAppWidget(appWidgetId: Int, info: AppWidgetProviderInfo?) {
+ super.setAppWidget(appWidgetId, info)
+ setPadding(0, 0, 0, 0)
+ }
+
private val cornerRadiusEnforcementOutline: ViewOutlineProvider =
object : ViewOutlineProvider() {
override fun getOutline(view: View?, outline: Outline) {
diff --git a/packages/SystemUI/src/com/android/systemui/contrast/ContrastDialogActivity.kt b/packages/SystemUI/src/com/android/systemui/contrast/ContrastDialogActivity.kt
deleted file mode 100644
index 4e40042..0000000
--- a/packages/SystemUI/src/com/android/systemui/contrast/ContrastDialogActivity.kt
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.contrast
-
-import android.app.Activity
-import android.os.Bundle
-import javax.inject.Inject
-
-/** Trampoline activity responsible for creating a [ContrastDialogDelegate] */
-class ContrastDialogActivity
-@Inject
-constructor(
- private val contrastDialogDelegate : ContrastDialogDelegate
-) : Activity() {
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- contrastDialogDelegate.createDialog().show()
- finish()
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/contrast/ContrastDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/contrast/ContrastDialogDelegate.kt
deleted file mode 100644
index 0daa058..0000000
--- a/packages/SystemUI/src/com/android/systemui/contrast/ContrastDialogDelegate.kt
+++ /dev/null
@@ -1,111 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.contrast
-
-import android.app.UiModeManager
-import android.app.UiModeManager.ContrastUtils.CONTRAST_LEVEL_HIGH
-import android.app.UiModeManager.ContrastUtils.CONTRAST_LEVEL_MEDIUM
-import android.app.UiModeManager.ContrastUtils.CONTRAST_LEVEL_STANDARD
-import android.app.UiModeManager.ContrastUtils.fromContrastLevel
-import android.app.UiModeManager.ContrastUtils.toContrastLevel
-import android.os.Bundle
-import android.provider.Settings
-import android.view.View
-import android.widget.FrameLayout
-import com.android.internal.annotations.VisibleForTesting
-import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.res.R
-import com.android.systemui.settings.UserTracker
-import com.android.systemui.statusbar.phone.SystemUIDialog
-import com.android.systemui.util.settings.SecureSettings
-import java.util.concurrent.Executor
-import javax.inject.Inject
-
-/** Dialog to select contrast options */
-class ContrastDialogDelegate
-@Inject
-constructor(
- private val sysuiDialogFactory: SystemUIDialog.Factory,
- @Main private val mainExecutor: Executor,
- private val uiModeManager: UiModeManager,
- private val userTracker: UserTracker,
- private val secureSettings: SecureSettings,
-) : SystemUIDialog.Delegate, UiModeManager.ContrastChangeListener {
-
- @VisibleForTesting lateinit var contrastButtons: Map<Int, FrameLayout>
- lateinit var dialogView: View
- @VisibleForTesting var initialContrast: Float = fromContrastLevel(CONTRAST_LEVEL_STANDARD)
-
- override fun createDialog(): SystemUIDialog {
- val dialog = sysuiDialogFactory.create(this)
- dialogView = dialog.layoutInflater.inflate(R.layout.contrast_dialog, null)
- with(dialog) {
- setView(dialogView)
-
- setTitle(R.string.quick_settings_contrast_label)
- setNeutralButton(R.string.cancel) { _, _ ->
- secureSettings.putFloatForUser(
- Settings.Secure.CONTRAST_LEVEL,
- initialContrast,
- userTracker.userId
- )
- dialog.dismiss()
- }
- setPositiveButton(com.android.settingslib.R.string.done) { _, _ -> dialog.dismiss() }
- }
-
- return dialog
- }
-
- override fun onCreate(dialog: SystemUIDialog, savedInstanceState: Bundle?) {
- contrastButtons =
- mapOf(
- CONTRAST_LEVEL_STANDARD to dialog.requireViewById(R.id.contrast_button_standard),
- CONTRAST_LEVEL_MEDIUM to dialog.requireViewById(R.id.contrast_button_medium),
- CONTRAST_LEVEL_HIGH to dialog.requireViewById(R.id.contrast_button_high)
- )
-
- contrastButtons.forEach { (contrastLevel, contrastButton) ->
- contrastButton.setOnClickListener {
- val contrastValue = fromContrastLevel(contrastLevel)
- secureSettings.putFloatForUser(
- Settings.Secure.CONTRAST_LEVEL,
- contrastValue,
- userTracker.userId
- )
- }
- }
-
- initialContrast = uiModeManager.contrast
- highlightContrast(toContrastLevel(initialContrast))
- }
-
- override fun onStart(dialog: SystemUIDialog) {
- uiModeManager.addContrastChangeListener(mainExecutor, this)
- }
-
- override fun onStop(dialog: SystemUIDialog) {
- uiModeManager.removeContrastChangeListener(this)
- }
-
- override fun onContrastChanged(contrast: Float) {
- highlightContrast(toContrastLevel(contrast))
- }
-
- private fun highlightContrast(contrast: Int) {
- contrastButtons.forEach { (level, button) -> button.isSelected = level == contrast }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java
index d2df276..c2e1e33 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java
@@ -20,7 +20,6 @@
import com.android.systemui.ForegroundServicesDialog;
import com.android.systemui.communal.widgets.EditWidgetsActivity;
-import com.android.systemui.contrast.ContrastDialogActivity;
import com.android.systemui.keyguard.WorkLockActivity;
import com.android.systemui.people.PeopleSpaceActivity;
import com.android.systemui.people.widget.LaunchConversationActivity;
@@ -72,12 +71,6 @@
@ClassKey(BrightnessDialog.class)
public abstract Activity bindBrightnessDialog(BrightnessDialog activity);
- /** Inject into ContrastDialogActivity. */
- @Binds
- @IntoMap
- @ClassKey(ContrastDialogActivity.class)
- public abstract Activity bindContrastDialogActivity(ContrastDialogActivity activity);
-
/** Inject into UsbDebuggingActivity. */
@Binds
@IntoMap
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
index 60006c6..1e725eb 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
@@ -21,6 +21,8 @@
import static com.android.keyguard.BouncerPanelExpansionCalculator.aboutToShowBouncerProgress;
import static com.android.keyguard.BouncerPanelExpansionCalculator.getDreamAlphaScaledExpansion;
import static com.android.keyguard.BouncerPanelExpansionCalculator.getDreamYPositionScaledExpansion;
+import static com.android.systemui.Flags.communalHub;
+import static com.android.systemui.Flags.glanceableHubGestureHandle;
import static com.android.systemui.complication.ComplicationLayoutParams.POSITION_BOTTOM;
import static com.android.systemui.complication.ComplicationLayoutParams.POSITION_TOP;
import static com.android.systemui.doze.util.BurnInHelperKt.getBurnInOffset;
@@ -185,6 +187,7 @@
DreamOverlayContainerView containerView,
ComplicationHostViewController complicationHostViewController,
@Named(DreamOverlayModule.DREAM_OVERLAY_CONTENT_VIEW) ViewGroup contentView,
+ @Named(DreamOverlayModule.HUB_GESTURE_INDICATOR_VIEW) View hubGestureIndicatorView,
DreamOverlayStatusBarViewController statusBarViewController,
LowLightTransitionCoordinator lowLightTransitionCoordinator,
BlurUtils blurUtils,
@@ -220,6 +223,12 @@
mComplicationHostViewController = complicationHostViewController;
mDreamOverlayMaxTranslationY = resources.getDimensionPixelSize(
R.dimen.dream_overlay_y_offset);
+
+ if (communalHub() && glanceableHubGestureHandle()) {
+ // TODO(b/339667383): remove this temporary swipe gesture handle
+ hubGestureIndicatorView.setVisibility(View.VISIBLE);
+ }
+
final View view = mComplicationHostViewController.getView();
mDreamOverlayContentView.addView(view,
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java
index 999e681..789b7f8 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java
@@ -18,6 +18,7 @@
import android.content.res.Resources;
import android.view.LayoutInflater;
+import android.view.View;
import android.view.ViewGroup;
import androidx.lifecycle.Lifecycle;
@@ -39,6 +40,7 @@
@Module
public abstract class DreamOverlayModule {
public static final String DREAM_OVERLAY_CONTENT_VIEW = "dream_overlay_content_view";
+ public static final String HUB_GESTURE_INDICATOR_VIEW = "hub_gesture_indicator_view";
public static final String MAX_BURN_IN_OFFSET = "max_burn_in_offset";
public static final String BURN_IN_PROTECTION_UPDATE_INTERVAL =
"burn_in_protection_update_interval";
@@ -71,6 +73,18 @@
"R.id.dream_overlay_content must not be null");
}
+ /**
+ * Gesture indicator bar on the right edge of the screen to indicate to users that they can
+ * swipe to see their widgets on lock screen.
+ */
+ @Provides
+ @DreamOverlayComponent.DreamOverlayScope
+ @Named(HUB_GESTURE_INDICATOR_VIEW)
+ public static View providesHubGestureIndicatorView(DreamOverlayContainerView view) {
+ return Preconditions.checkNotNull(view.findViewById(R.id.glanceable_hub_handle),
+ "R.id.glanceable_hub_handle must not be null");
+ }
+
/** */
@Provides
public static TouchInsetManager.TouchInsetSession providesTouchInsetSession(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ResourceTrimmer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ResourceTrimmer.kt
index 6e364d1..3cbcb2c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ResourceTrimmer.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ResourceTrimmer.kt
@@ -33,12 +33,16 @@
import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.scene.domain.interactor.SceneInteractor
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
+import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.utils.GlobalWindowManager
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
@@ -60,6 +64,7 @@
@Application private val applicationScope: CoroutineScope,
@Background private val bgDispatcher: CoroutineDispatcher,
private val featureFlags: FeatureFlags,
+ private val sceneInteractor: SceneInteractor,
) : CoreStartable, WakefulnessLifecycle.Observer {
override fun start() {
@@ -85,9 +90,15 @@
applicationScope.launch(bgDispatcher) {
// We drop 1 to avoid triggering on initial collect().
- keyguardTransitionInteractor.transition(Edge.create(to = GONE)).collect { transition ->
- if (transition.transitionState == TransitionState.FINISHED) {
- onKeyguardGone()
+ if (SceneContainerFlag.isEnabled) {
+ sceneInteractor.transitionState
+ .filter { it.isIdle(Scenes.Gone) }
+ .collect { onKeyguardGone() }
+ } else {
+ keyguardTransitionInteractor.transition(Edge.create(to = GONE)).collect {
+ if (it.transitionState == TransitionState.FINISHED) {
+ onKeyguardGone()
+ }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractor.kt
index eef4b97..9626077 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractor.kt
@@ -17,6 +17,7 @@
package com.android.systemui.keyguard.domain.interactor
import android.content.Context
+import android.util.Log
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
@@ -39,6 +40,7 @@
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.merge
+import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
/**
@@ -96,10 +98,13 @@
keyguardUpdateMonitor.isFingerprintDetectionRunning &&
keyguardUpdateMonitor.isUnlockingWithFingerprintAllowed
}
+ .onEach { Log.d(TAG, "showIndicatorForPrimaryBouncer updated: $it") }
private val showIndicatorForAlternateBouncer: Flow<Boolean> =
// Note: this interactor internally verifies that SideFPS is enabled and running.
- alternateBouncerInteractor.isVisible
+ alternateBouncerInteractor.isVisible.onEach {
+ Log.d(TAG, "showIndicatorForAlternateBouncer updated: $it")
+ }
/**
* Indicates whether the primary or alternate bouncers request showing the side fingerprint
@@ -112,6 +117,7 @@
showForPrimaryBouncer || showForAlternateBouncer
}
.distinctUntilChanged()
+ .onEach { Log.d(TAG, "showIndicatorForDeviceEntry updated: $it") }
private fun isBouncerActive(): Boolean {
if (SceneContainerFlag.isEnabled) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractor.kt
index 857096e..b1ef76e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractor.kt
@@ -20,6 +20,7 @@
package com.android.systemui.keyguard.domain.interactor
import android.content.Context
+import android.util.Log
import com.android.systemui.biometrics.domain.interactor.FingerprintPropertyInteractor
import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
import com.android.systemui.dagger.SysUISingleton
@@ -42,6 +43,7 @@
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.merge
+import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
@SysUISingleton
@@ -78,7 +80,15 @@
private val refreshEvents: Flow<Unit> =
merge(
configurationInteractor.onAnyConfigurationChange,
- fingerprintPropertyInteractor.propertiesInitialized.filter { it }.map { Unit },
+ fingerprintPropertyInteractor.propertiesInitialized
+ .filter { it }
+ .map { Unit }
+ .onEach {
+ Log.d(
+ "KeyguardBlueprintInteractor",
+ "triggering refreshEvent from fpPropertiesInitialized"
+ )
+ },
)
init {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractor.kt
index 08d29d4..1aac1c5 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractor.kt
@@ -25,6 +25,9 @@
import com.android.systemui.keyguard.shared.model.KeyguardState.ALTERNATE_BOUNCER
import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER
+import com.android.systemui.scene.domain.interactor.SceneInteractor
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
+import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.util.kotlin.sample
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
@@ -48,6 +51,7 @@
transitionInteractor: KeyguardTransitionInteractor,
val dismissInteractor: KeyguardDismissInteractor,
@Application private val applicationScope: CoroutineScope,
+ sceneInteractor: SceneInteractor,
) {
val dismissAction: Flow<DismissAction> = repository.dismissAction
@@ -72,7 +76,12 @@
)
private val finishedTransitionToGone: Flow<Unit> =
- transitionInteractor.finishedKeyguardState.filter { it == GONE }.map {} // map to Unit
+ if (SceneContainerFlag.isEnabled) {
+ sceneInteractor.transitionState.filter { it.isIdle(Scenes.Gone) }.map {}
+ } else {
+ transitionInteractor.finishedKeyguardState.filter { it == GONE }.map {}
+ }
+
val executeDismissAction: Flow<() -> KeyguardDone> =
merge(
finishedTransitionToGone,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
index 8065c0f..c44a40f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
@@ -55,6 +55,7 @@
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
@@ -181,7 +182,11 @@
}
.sample(powerInteractor.isAwake) { isAbleToDream, isAwake -> isAbleToDream && isAwake }
.debounce(50L)
- .distinctUntilChanged()
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = false,
+ )
/** Whether the keyguard is showing or not. */
@Deprecated("Use KeyguardTransitionInteractor + KeyguardState")
@@ -225,7 +230,19 @@
@JvmField val primaryBouncerShowing: Flow<Boolean> = bouncerRepository.primaryBouncerShow
/** Whether the alternate bouncer is showing or not. */
- val alternateBouncerShowing: Flow<Boolean> = bouncerRepository.alternateBouncerVisible
+ val alternateBouncerShowing: Flow<Boolean> =
+ bouncerRepository.alternateBouncerVisible.sample(isAbleToDream) {
+ alternateBouncerVisible,
+ isAbleToDream ->
+ if (isAbleToDream) {
+ // If the alternate bouncer will show over a dream, it is likely that the dream has
+ // requested a dismissal, which will stop the dream. By delaying this slightly, the
+ // DREAMING->LOCKSCREEN transition will now happen first, followed by
+ // LOCKSCREEN->ALTERNATE_BOUNCER.
+ delay(600L)
+ }
+ alternateBouncerVisible
+ }
/** Observable for the [StatusBarState] */
val statusBarState: Flow<StatusBarState> = repository.statusBarState
@@ -301,10 +318,12 @@
shadeRepository.legacyShadeExpansion.onStart { emit(0f) },
keyguardTransitionInteractor.transitionValue(GONE).onStart { emit(0f) },
) { legacyShadeExpansion, goneValue ->
- if (goneValue == 1f || (goneValue == 0f && legacyShadeExpansion == 0f)) {
+ val isLegacyShadeInResetPosition =
+ legacyShadeExpansion == 0f || legacyShadeExpansion == 1f
+ if (goneValue == 1f || (goneValue == 0f && isLegacyShadeInResetPosition)) {
// Reset the translation value
emit(0f)
- } else if (legacyShadeExpansion > 0f && legacyShadeExpansion < 1f) {
+ } else if (!isLegacyShadeInResetPosition) {
// On swipe up, translate the keyguard to reveal the bouncer, OR a GONE
// transition is running, which means this is a swipe to dismiss. Values of
// 0f and 1f need to be ignored in the legacy shade expansion. These can
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt
index 188516c..19e3e07 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt
@@ -74,6 +74,9 @@
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.qs.PageIndicator
import com.android.systemui.res.R
+import com.android.systemui.scene.domain.interactor.SceneInteractor
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
+import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.shared.system.SysUiStatsLog
import com.android.systemui.shared.system.SysUiStatsLog.SMARTSPACE_CARD_REPORTED
import com.android.systemui.shared.system.SysUiStatsLog.SMART_SPACE_CARD_REPORTED__CARD_TYPE__UNKNOWN_CARD
@@ -143,6 +146,7 @@
private val secureSettings: SecureSettings,
private val mediaCarouselViewModel: MediaCarouselViewModel,
private val mediaViewControllerFactory: Provider<MediaViewController>,
+ private val sceneInteractor: SceneInteractor,
) : Dumpable {
/** The current width of the carousel */
var currentCarouselWidth: Int = 0
@@ -304,7 +308,11 @@
* It will be called when the container is out of view.
*/
lateinit var updateUserVisibility: () -> Unit
- lateinit var updateHostVisibility: () -> Unit
+ var updateHostVisibility: () -> Unit = {}
+ set(value) {
+ field = value
+ mediaCarouselViewModel.updateHostVisibility = value
+ }
private val isReorderingAllowed: Boolean
get() = visualStabilityProvider.isReorderingAllowed
@@ -341,6 +349,20 @@
configurationController.addCallback(configListener)
if (!mediaFlags.isMediaControlsRefactorEnabled()) {
setUpListeners()
+ } else {
+ val visualStabilityCallback = OnReorderingAllowedListener {
+ mediaCarouselViewModel.onReorderingAllowed()
+
+ // Update user visibility so that no extra impression will be logged when
+ // activeMediaIndex resets to 0
+ if (this::updateUserVisibility.isInitialized) {
+ updateUserVisibility()
+ }
+
+ // Let's reset our scroll position
+ mediaCarouselScrollHandler.scrollToStart()
+ }
+ visualStabilityProvider.addPersistentReorderingAllowedListener(visualStabilityCallback)
}
mediaFrame.addOnLayoutChangeListener { _, _, _, _, _, _, _, _, _ ->
// The pageIndicator is not laid out yet when we get the current state update,
@@ -362,10 +384,6 @@
)
keyguardUpdateMonitor.registerCallback(keyguardUpdateMonitorCallback)
mediaCarousel.repeatWhenAttached {
- if (mediaFlags.isMediaControlsRefactorEnabled()) {
- mediaCarouselViewModel.onAttached()
- mediaCarouselScrollHandler.scrollToStart()
- }
repeatOnLifecycle(Lifecycle.State.STARTED) {
listenForAnyStateToGoneKeyguardTransition(this)
listenForAnyStateToLockscreenTransition(this)
@@ -588,9 +606,7 @@
if (!immediately) {
// Although it wasn't requested, we were able to process the removal
// immediately since reordering is allowed. So, notify hosts to update
- if (this@MediaCarouselController::updateHostVisibility.isInitialized) {
- updateHostVisibility()
- }
+ updateHostVisibility()
}
} else {
keysNeedRemoval.add(key)
@@ -641,9 +657,13 @@
@VisibleForTesting
internal fun listenForAnyStateToGoneKeyguardTransition(scope: CoroutineScope): Job {
return scope.launch {
- keyguardTransitionInteractor
- .transition(Edge.create(to = GONE))
- .filter { it.transitionState == TransitionState.FINISHED }
+ if (SceneContainerFlag.isEnabled) {
+ sceneInteractor.transitionState.filter { it.isIdle(Scenes.Gone) }
+ } else {
+ keyguardTransitionInteractor.transition(Edge.create(to = GONE)).filter {
+ it.transitionState == TransitionState.FINISHED
+ }
+ }
.collect {
showMediaCarousel()
updateHostVisibility()
@@ -743,6 +763,7 @@
}
}
viewController.setListening(mediaCarouselScrollHandler.visibleToUser && currentlyExpanded)
+ controllerByViewModel[commonViewModel] = viewController
updateViewControllerToState(viewController, noAnimation = true)
updatePageIndicator()
if (
@@ -756,7 +777,6 @@
mediaCarouselScrollHandler.onPlayersChanged()
mediaFrame.requiresRemeasuring = true
commonViewModel.onAdded(commonViewModel)
- controllerByViewModel[commonViewModel] = viewController
}
private fun onUpdated(commonViewModel: MediaCommonViewModel) {
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaCarouselViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaCarouselViewModel.kt
index fd5f445..4e90936 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaCarouselViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaCarouselViewModel.kt
@@ -57,12 +57,12 @@
val mediaItems: StateFlow<List<MediaCommonViewModel>> =
interactor.currentMedia
.map { sortedItems ->
- buildList {
+ val mediaList = buildList {
sortedItems.forEach { commonModel ->
// When view is started we should make sure to clean models that are pending
// removal.
// This action should only be triggered once.
- if (!isAttached || !modelsPendingRemoval.contains(commonModel)) {
+ if (!allowReorder || !modelsPendingRemoval.contains(commonModel)) {
when (commonModel) {
is MediaCommonModel.MediaControl -> add(toViewModel(commonModel))
is MediaCommonModel.MediaRecommendations ->
@@ -70,11 +70,16 @@
}
}
}
- if (isAttached) {
- modelsPendingRemoval.clear()
- }
- isAttached = false
}
+ if (allowReorder) {
+ if (modelsPendingRemoval.size > 0) {
+ updateHostVisibility()
+ }
+ modelsPendingRemoval.clear()
+ }
+ allowReorder = false
+
+ mediaList
}
.stateIn(
scope = applicationScope,
@@ -82,6 +87,8 @@
initialValue = emptyList(),
)
+ var updateHostVisibility: () -> Unit = {}
+
private val mediaControlByInstanceId =
mutableMapOf<InstanceId, MediaCommonViewModel.MediaControl>()
@@ -89,15 +96,15 @@
private var modelsPendingRemoval: MutableSet<MediaCommonModel> = mutableSetOf()
- private var isAttached = false
+ private var allowReorder = false
fun onSwipeToDismiss() {
logger.logSwipeDismiss()
interactor.onSwipeToDismiss()
}
- fun onAttached() {
- isAttached = true
+ fun onReorderingAllowed() {
+ allowReorder = true
interactor.reorderMedia()
}
@@ -194,7 +201,11 @@
) {
if (immediatelyRemove || isReorderingAllowed()) {
interactor.dismissSmartspaceRecommendation(commonModel.recsLoadingModel.key, 0L)
- // TODO if not immediate remove update host visibility
+ if (!immediatelyRemove) {
+ // Although it wasn't requested, we were able to process the removal
+ // immediately since reordering is allowed. So, notify hosts to update
+ updateHostVisibility()
+ }
} else {
modelsPendingRemoval.add(commonModel)
}
diff --git a/packages/SystemUI/src/com/android/systemui/qrcodescanner/dagger/QRCodeScannerModule.kt b/packages/SystemUI/src/com/android/systemui/qrcodescanner/dagger/QRCodeScannerModule.kt
index 3907a72..5e6ee4d 100644
--- a/packages/SystemUI/src/com/android/systemui/qrcodescanner/dagger/QRCodeScannerModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/qrcodescanner/dagger/QRCodeScannerModule.kt
@@ -16,12 +16,20 @@
package com.android.systemui.qrcodescanner.dagger
+import com.android.systemui.Flags
import com.android.systemui.qs.QsEventLogger
import com.android.systemui.qs.pipeline.shared.TileSpec
import com.android.systemui.qs.tileimpl.QSTileImpl
import com.android.systemui.qs.tiles.QRCodeScannerTile
+import com.android.systemui.qs.tiles.base.viewmodel.QSTileViewModelFactory
+import com.android.systemui.qs.tiles.impl.qr.domain.interactor.QRCodeScannerTileDataInteractor
+import com.android.systemui.qs.tiles.impl.qr.domain.interactor.QRCodeScannerTileUserActionInteractor
+import com.android.systemui.qs.tiles.impl.qr.domain.model.QRCodeScannerTileModel
+import com.android.systemui.qs.tiles.impl.qr.ui.QRCodeScannerTileMapper
import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
import com.android.systemui.qs.tiles.viewmodel.QSTileUIConfig
+import com.android.systemui.qs.tiles.viewmodel.QSTileViewModel
+import com.android.systemui.qs.tiles.viewmodel.StubQSTileViewModel
import com.android.systemui.res.R
import dagger.Binds
import dagger.Module
@@ -54,5 +62,24 @@
),
instanceId = uiEventLogger.getNewInstanceId(),
)
+
+ /** Inject QR Code Scanner Tile into tileViewModelMap in QSModule. */
+ @Provides
+ @IntoMap
+ @StringKey(QR_CODE_SCANNER_TILE_SPEC)
+ fun provideQRCodeScannerTileViewModel(
+ factory: QSTileViewModelFactory.Static<QRCodeScannerTileModel>,
+ mapper: QRCodeScannerTileMapper,
+ stateInteractor: QRCodeScannerTileDataInteractor,
+ userActionInteractor: QRCodeScannerTileUserActionInteractor
+ ): QSTileViewModel =
+ if (Flags.qsNewTilesFuture())
+ factory.create(
+ TileSpec.create(QR_CODE_SCANNER_TILE_SPEC),
+ userActionInteractor,
+ stateInteractor,
+ mapper,
+ )
+ else StubQSTileViewModel
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/TileStateToProto.kt b/packages/SystemUI/src/com/android/systemui/qs/TileStateToProto.kt
index 2c8a5a4..1336d64 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/TileStateToProto.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/TileStateToProto.kt
@@ -18,6 +18,7 @@
import android.service.quicksettings.Tile
import android.text.TextUtils
+import android.widget.Switch
import com.android.systemui.plugins.qs.QSTile
import com.android.systemui.qs.external.CustomTile
import com.android.systemui.qs.nano.QsTileState
@@ -44,8 +45,8 @@
}
label?.let { state.label = it.toString() }
secondaryLabel?.let { state.secondaryLabel = it.toString() }
- if (this is QSTile.BooleanState) {
- state.booleanState = value
+ if (expandedAccessibilityClassName == Switch::class.java.name) {
+ state.booleanState = state.state == QsTileState.ACTIVE
}
return state
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
index d26ae0a..5d35a69 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
@@ -42,6 +42,7 @@
import android.util.Log;
import android.view.IWindowManager;
import android.view.WindowManagerGlobal;
+import android.widget.Button;
import android.widget.Switch;
import androidx.annotation.Nullable;
@@ -502,6 +503,8 @@
if (state instanceof BooleanState) {
state.expandedAccessibilityClassName = Switch.class.getName();
((BooleanState) state).value = (state.state == Tile.STATE_ACTIVE);
+ } else {
+ state.expandedAccessibilityClassName = Button.class.getName();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/dagger/PanelsModule.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/dagger/PanelsModule.kt
index 0696fbe..2cc3985 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/dagger/PanelsModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/dagger/PanelsModule.kt
@@ -29,8 +29,10 @@
import com.android.systemui.qs.panels.shared.model.GridConsistencyLog
import com.android.systemui.qs.panels.shared.model.GridLayoutType
import com.android.systemui.qs.panels.shared.model.InfiniteGridLayoutType
+import com.android.systemui.qs.panels.shared.model.StretchedGridLayoutType
import com.android.systemui.qs.panels.ui.compose.GridLayout
import com.android.systemui.qs.panels.ui.compose.InfiniteGridLayout
+import com.android.systemui.qs.panels.ui.compose.StretchedGridLayout
import dagger.Binds
import dagger.Module
import dagger.Provides
@@ -63,6 +65,14 @@
}
@Provides
+ @IntoSet
+ fun provideStretchedGridLayout(
+ gridLayout: StretchedGridLayout
+ ): Pair<GridLayoutType, GridLayout> {
+ return Pair(StretchedGridLayoutType, gridLayout)
+ }
+
+ @Provides
fun provideGridLayoutMap(
entries: Set<@JvmSuppressWildcards Pair<GridLayoutType, GridLayout>>
): Map<GridLayoutType, GridLayout> {
@@ -70,6 +80,13 @@
}
@Provides
+ fun provideGridLayoutTypes(
+ entries: Set<@JvmSuppressWildcards Pair<GridLayoutType, GridLayout>>
+ ): Set<GridLayoutType> {
+ return entries.map { it.first }.toSet()
+ }
+
+ @Provides
@IntoSet
fun provideGridConsistencyInteractor(
consistencyInteractor: InfiniteGridConsistencyInteractor
@@ -78,6 +95,14 @@
}
@Provides
+ @IntoSet
+ fun provideStretchedGridConsistencyInteractor(
+ consistencyInteractor: NoopGridConsistencyInteractor
+ ): Pair<GridLayoutType, GridTypeConsistencyInteractor> {
+ return Pair(StretchedGridLayoutType, consistencyInteractor)
+ }
+
+ @Provides
fun provideGridConsistencyInteractorMap(
entries: Set<@JvmSuppressWildcards Pair<GridLayoutType, GridTypeConsistencyInteractor>>
): Map<GridLayoutType, GridTypeConsistencyInteractor> {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/GridLayoutTypeRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/GridLayoutTypeRepository.kt
index 542d0cb..31795d5 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/GridLayoutTypeRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/GridLayoutTypeRepository.kt
@@ -26,10 +26,17 @@
interface GridLayoutTypeRepository {
val layout: StateFlow<GridLayoutType>
+ fun setLayout(type: GridLayoutType)
}
@SysUISingleton
class GridLayoutTypeRepositoryImpl @Inject constructor() : GridLayoutTypeRepository {
private val _layout: MutableStateFlow<GridLayoutType> = MutableStateFlow(InfiniteGridLayoutType)
override val layout = _layout.asStateFlow()
+
+ override fun setLayout(type: GridLayoutType) {
+ if (_layout.value != type) {
+ _layout.value = type
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/GridLayoutTypeInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/GridLayoutTypeInteractor.kt
index b6be578..4af1b22 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/GridLayoutTypeInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/GridLayoutTypeInteractor.kt
@@ -20,9 +20,13 @@
import com.android.systemui.qs.panels.data.repository.GridLayoutTypeRepository
import com.android.systemui.qs.panels.shared.model.GridLayoutType
import javax.inject.Inject
-import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.StateFlow
@SysUISingleton
-class GridLayoutTypeInteractor @Inject constructor(repo: GridLayoutTypeRepository) {
- val layout: Flow<GridLayoutType> = repo.layout
+class GridLayoutTypeInteractor @Inject constructor(private val repo: GridLayoutTypeRepository) {
+ val layout: StateFlow<GridLayoutType> = repo.layout
+
+ fun setLayoutType(type: GridLayoutType) {
+ repo.setLayout(type)
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractor.kt
index 74e906c..b437f64 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractor.kt
@@ -18,6 +18,8 @@
import android.util.Log
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.qs.panels.shared.model.SizedTile
+import com.android.systemui.qs.panels.shared.model.TileRow
import com.android.systemui.qs.pipeline.shared.TileSpec
import javax.inject.Inject
@@ -35,7 +37,7 @@
*/
override fun reconcileTiles(tiles: List<TileSpec>): List<TileSpec> {
val newTiles: MutableList<TileSpec> = mutableListOf()
- val row = TileRow(columns = gridSizeInteractor.columns.value)
+ val row = TileRow<TileSpec>(columns = gridSizeInteractor.columns.value)
val iconTilesSet = iconTilesInteractor.iconTilesSpecs.value
val tilesQueue =
ArrayDeque(
@@ -54,7 +56,7 @@
while (tilesQueue.isNotEmpty()) {
if (row.isFull()) {
- newTiles.addAll(row.tileSpecs())
+ newTiles.addAll(row.tiles.map { it.tile })
row.clear()
}
@@ -66,13 +68,13 @@
// We'll try to either add an icon tile from the queue to complete the row, or
// remove an icon tile from the current row to free up space.
- val iconTile: SizedTile? = tilesQueue.firstOrNull { it.width == 1 }
+ val iconTile: SizedTile<TileSpec>? = tilesQueue.firstOrNull { it.width == 1 }
if (iconTile != null) {
tilesQueue.remove(iconTile)
tilesQueue.addFirst(tile)
row.maybeAddTile(iconTile)
} else {
- val tileToRemove: SizedTile? = row.findLastIconTile()
+ val tileToRemove: SizedTile<TileSpec>? = row.findLastIconTile()
if (tileToRemove != null) {
row.removeTile(tileToRemove)
row.maybeAddTile(tile)
@@ -84,7 +86,7 @@
// If the row does not have an icon tile, add the incomplete row.
// Note: this shouldn't happen because an icon tile is guaranteed to be in a
// row that doesn't have enough space for a large tile.
- val tileSpecs = row.tileSpecs()
+ val tileSpecs = row.tiles.map { it.tile }
Log.wtf(TAG, "Uneven row does not have an icon tile to remove: $tileSpecs")
newTiles.addAll(tileSpecs)
row.clear()
@@ -95,48 +97,11 @@
}
// Add last row that might be incomplete
- newTiles.addAll(row.tileSpecs())
+ newTiles.addAll(row.tiles.map { it.tile })
return newTiles.toList()
}
- /** Tile with a width representing the number of columns it should take. */
- private data class SizedTile(val spec: TileSpec, val width: Int)
-
- private class TileRow(private val columns: Int) {
- private var availableColumns = columns
- private val tiles: MutableList<SizedTile> = mutableListOf()
-
- fun tileSpecs(): List<TileSpec> {
- return tiles.map { it.spec }
- }
-
- fun maybeAddTile(tile: SizedTile): Boolean {
- if (availableColumns - tile.width >= 0) {
- tiles.add(tile)
- availableColumns -= tile.width
- return true
- }
- return false
- }
-
- fun findLastIconTile(): SizedTile? {
- return tiles.findLast { it.width == 1 }
- }
-
- fun removeTile(tile: SizedTile) {
- tiles.remove(tile)
- availableColumns += tile.width
- }
-
- fun clear() {
- tiles.clear()
- availableColumns = columns
- }
-
- fun isFull(): Boolean = availableColumns == 0
- }
-
private companion object {
const val TAG = "InfiniteGridConsistencyInteractor"
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/NoopConsistencyInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/NoopConsistencyInteractor.kt
new file mode 100644
index 0000000..97ceacc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/NoopConsistencyInteractor.kt
@@ -0,0 +1,26 @@
+/*
+ * 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.qs.panels.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import javax.inject.Inject
+
+@SysUISingleton
+class NoopConsistencyInteractor @Inject constructor() : GridTypeConsistencyInteractor {
+ override fun reconcileTiles(tiles: List<TileSpec>): List<TileSpec> = tiles
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/GridLayoutType.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/GridLayoutType.kt
index 23110dc..501730a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/GridLayoutType.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/GridLayoutType.kt
@@ -25,3 +25,9 @@
/** Grid type representing a scrollable vertical grid. */
data object InfiniteGridLayoutType : GridLayoutType
+
+/**
+ * Grid type representing a scrollable vertical grid where tiles will stretch to fill in empty
+ * spaces.
+ */
+data object StretchedGridLayoutType : GridLayoutType
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/TileRow.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/TileRow.kt
new file mode 100644
index 0000000..7e4381b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/TileRow.kt
@@ -0,0 +1,53 @@
+/*
+ * 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.qs.panels.shared.model
+
+/** Represents a tile of type [T] associated with a width */
+data class SizedTile<T>(val tile: T, val width: Int)
+
+/** Represents a row of [SizedTile] with a maximum width of [columns] */
+class TileRow<T>(private val columns: Int) {
+ private var availableColumns = columns
+ private val _tiles: MutableList<SizedTile<T>> = mutableListOf()
+ val tiles: List<SizedTile<T>>
+ get() = _tiles.toList()
+
+ fun maybeAddTile(tile: SizedTile<T>): Boolean {
+ if (availableColumns - tile.width >= 0) {
+ _tiles.add(tile)
+ availableColumns -= tile.width
+ return true
+ }
+ return false
+ }
+
+ fun findLastIconTile(): SizedTile<T>? {
+ return _tiles.findLast { it.width == 1 }
+ }
+
+ fun removeTile(tile: SizedTile<T>) {
+ _tiles.remove(tile)
+ availableColumns += tile.width
+ }
+
+ fun clear() {
+ _tiles.clear()
+ availableColumns = columns
+ }
+
+ fun isFull(): Boolean = availableColumns == 0
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayout.kt
index bac0f60..f5ee720 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayout.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayout.kt
@@ -16,85 +16,23 @@
package com.android.systemui.qs.panels.ui.compose
-import android.graphics.drawable.Animatable
-import android.text.TextUtils
-import androidx.appcompat.content.res.AppCompatResources
-import androidx.compose.animation.graphics.ExperimentalAnimationGraphicsApi
-import androidx.compose.animation.graphics.res.animatedVectorResource
-import androidx.compose.animation.graphics.res.rememberAnimatedVectorPainter
-import androidx.compose.animation.graphics.vector.AnimatedImageVector
-import androidx.compose.foundation.ExperimentalFoundationApi
-import androidx.compose.foundation.Image
-import androidx.compose.foundation.background
-import androidx.compose.foundation.basicMarquee
-import androidx.compose.foundation.clickable
-import androidx.compose.foundation.combinedClickable
-import androidx.compose.foundation.layout.Arrangement
-import androidx.compose.foundation.layout.Arrangement.spacedBy
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.fillMaxHeight
-import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
-import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.GridItemSpan
-import androidx.compose.foundation.lazy.grid.LazyGridScope
-import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
-import androidx.compose.foundation.shape.CircleShape
-import androidx.compose.foundation.shape.RoundedCornerShape
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.Add
-import androidx.compose.material.icons.filled.Remove
-import androidx.compose.material3.Icon
-import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
-import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.rememberUpdatedState
-import androidx.compose.runtime.setValue
-import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
-import androidx.compose.ui.draw.clip
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.graphics.ColorFilter
-import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.dimensionResource
-import androidx.compose.ui.res.stringResource
-import androidx.compose.ui.semantics.contentDescription
-import androidx.compose.ui.semantics.onClick
-import androidx.compose.ui.semantics.semantics
-import androidx.compose.ui.semantics.stateDescription
-import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
-import com.android.compose.animation.Expandable
-import com.android.compose.theme.colorAttr
-import com.android.systemui.common.shared.model.Icon
-import com.android.systemui.common.ui.compose.Icon
-import com.android.systemui.common.ui.compose.load
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.qs.panels.domain.interactor.IconTilesInteractor
import com.android.systemui.qs.panels.domain.interactor.InfiniteGridSizeInteractor
-import com.android.systemui.qs.panels.ui.viewmodel.ActiveTileColorAttributes
-import com.android.systemui.qs.panels.ui.viewmodel.AvailableEditActions
import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel
-import com.android.systemui.qs.panels.ui.viewmodel.TileColorAttributes
-import com.android.systemui.qs.panels.ui.viewmodel.TileUiState
import com.android.systemui.qs.panels.ui.viewmodel.TileViewModel
-import com.android.systemui.qs.panels.ui.viewmodel.toUiState
-import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractor.Companion.POSITION_AT_END
import com.android.systemui.qs.pipeline.shared.TileSpec
-import com.android.systemui.qs.tileimpl.QSTileImpl
import com.android.systemui.res.R
import javax.inject.Inject
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.delay
-import kotlinx.coroutines.flow.mapLatest
@SysUISingleton
class InfiniteGridLayout
@@ -104,8 +42,6 @@
private val gridSizeInteractor: InfiniteGridSizeInteractor
) : GridLayout {
- private object TileType
-
@Composable
override fun TileGrid(
tiles: List<TileViewModel>,
@@ -140,55 +76,6 @@
}
}
- @OptIn(ExperimentalCoroutinesApi::class, ExperimentalFoundationApi::class)
- @Composable
- private fun Tile(
- tile: TileViewModel,
- iconOnly: Boolean,
- modifier: Modifier,
- ) {
- val state: TileUiState by
- tile.state
- .mapLatest { it.toUiState() }
- .collectAsStateWithLifecycle(initialValue = tile.currentState.toUiState())
- val context = LocalContext.current
-
- Expandable(
- color = colorAttr(state.colors.background),
- shape = RoundedCornerShape(dimensionResource(R.dimen.qs_corner_radius)),
- ) {
- Row(
- modifier =
- modifier
- .combinedClickable(
- onClick = { tile.onClick(it) },
- onLongClick = { tile.onLongClick(it) }
- )
- .tileModifier(state.colors),
- verticalAlignment = Alignment.CenterVertically,
- horizontalArrangement = tileHorizontalArrangement(iconOnly),
- ) {
- val icon =
- remember(state.icon) {
- state.icon.get().let {
- if (it is QSTileImpl.ResourceIcon) {
- Icon.Resource(it.resId, null)
- } else {
- Icon.Loaded(it.getDrawable(context), null)
- }
- }
- }
- TileContent(
- label = state.label.toString(),
- secondaryLabel = state.secondaryLabel?.toString(),
- icon = icon,
- colors = state.colors,
- iconOnly = iconOnly
- )
- }
- }
- }
-
@Composable
override fun EditTileGrid(
tiles: List<EditTileViewModel>,
@@ -196,262 +83,16 @@
onAddTile: (TileSpec, Int) -> Unit,
onRemoveTile: (TileSpec) -> Unit,
) {
- val (currentTiles, otherTiles) = tiles.partition { it.isCurrent }
- val (otherTilesStock, otherTilesCustom) = otherTiles.partition { it.appName == null }
- val addTileToEnd: (TileSpec) -> Unit by rememberUpdatedState {
- onAddTile(it, POSITION_AT_END)
- }
- val iconOnlySpecs by
- iconTilesInteractor.iconTilesSpecs.collectAsStateWithLifecycle(
- initialValue = emptySet()
- )
- val isIconOnly: (TileSpec) -> Boolean =
- remember(iconOnlySpecs) { { tileSpec: TileSpec -> tileSpec in iconOnlySpecs } }
+ val iconOnlySpecs by iconTilesInteractor.iconTilesSpecs.collectAsStateWithLifecycle()
val columns by gridSizeInteractor.columns.collectAsStateWithLifecycle()
- TileLazyGrid(modifier = modifier, columns = GridCells.Fixed(columns)) {
- // These Text are just placeholders to see the different sections. Not final UI.
- item(span = { GridItemSpan(maxLineSpan) }) {
- Text("Current tiles", color = Color.White)
- }
-
- editTiles(
- currentTiles,
- ClickAction.REMOVE,
- onRemoveTile,
- isIconOnly,
- indicatePosition = true,
- )
-
- item(span = { GridItemSpan(maxLineSpan) }) { Text("Tiles to add", color = Color.White) }
-
- editTiles(
- otherTilesStock,
- ClickAction.ADD,
- addTileToEnd,
- isIconOnly,
- )
-
- item(span = { GridItemSpan(maxLineSpan) }) {
- Text("Custom tiles to add", color = Color.White)
- }
-
- editTiles(
- otherTilesCustom,
- ClickAction.ADD,
- addTileToEnd,
- isIconOnly,
- )
- }
- }
-
- private fun LazyGridScope.editTiles(
- tiles: List<EditTileViewModel>,
- clickAction: ClickAction,
- onClick: (TileSpec) -> Unit,
- isIconOnly: (TileSpec) -> Boolean,
- indicatePosition: Boolean = false,
- ) {
- items(
- count = tiles.size,
- key = { tiles[it].tileSpec.spec },
- span = { GridItemSpan(if (isIconOnly(tiles[it].tileSpec)) 1 else 2) },
- contentType = { TileType }
- ) {
- val viewModel = tiles[it]
- val canClick =
- when (clickAction) {
- ClickAction.ADD -> AvailableEditActions.ADD in viewModel.availableEditActions
- ClickAction.REMOVE ->
- AvailableEditActions.REMOVE in viewModel.availableEditActions
- }
- val onClickActionName =
- when (clickAction) {
- ClickAction.ADD ->
- stringResource(id = R.string.accessibility_qs_edit_tile_add_action)
- ClickAction.REMOVE ->
- stringResource(id = R.string.accessibility_qs_edit_remove_tile_action)
- }
- val stateDescription =
- if (indicatePosition) {
- stringResource(id = R.string.accessibility_qs_edit_position, it + 1)
- } else {
- ""
- }
-
- Box(
- modifier =
- Modifier.clickable(enabled = canClick) { onClick.invoke(viewModel.tileSpec) }
- .animateItem()
- .semantics {
- onClick(onClickActionName) { false }
- this.stateDescription = stateDescription
- }
- ) {
- EditTile(
- tileViewModel = viewModel,
- isIconOnly(viewModel.tileSpec),
- modifier = Modifier.height(dimensionResource(id = R.dimen.qs_tile_height))
- )
- if (canClick) {
- Badge(clickAction, Modifier.align(Alignment.TopEnd))
- }
- }
- }
- }
-
- @Composable
- private fun Badge(action: ClickAction, modifier: Modifier = Modifier) {
- Box(modifier = modifier.size(16.dp).background(Color.Cyan, shape = CircleShape)) {
- Icon(
- imageVector =
- when (action) {
- ClickAction.ADD -> Icons.Filled.Add
- ClickAction.REMOVE -> Icons.Filled.Remove
- },
- "",
- tint = Color.Black,
- )
- }
- }
-
- @Composable
- private fun EditTile(
- tileViewModel: EditTileViewModel,
- iconOnly: Boolean,
- modifier: Modifier = Modifier,
- ) {
- val label = tileViewModel.label.load() ?: tileViewModel.tileSpec.spec
- val colors = ActiveTileColorAttributes
-
- Row(
- modifier = modifier.tileModifier(colors).semantics { this.contentDescription = label },
- verticalAlignment = Alignment.CenterVertically,
- horizontalArrangement = tileHorizontalArrangement(iconOnly)
- ) {
- TileContent(
- label = label,
- secondaryLabel = tileViewModel.appName?.load(),
- colors = colors,
- icon = tileViewModel.icon,
- iconOnly = iconOnly,
- animateIconToEnd = true,
- )
- }
- }
-
- private enum class ClickAction {
- ADD,
- REMOVE,
- }
-}
-
-@OptIn(ExperimentalAnimationGraphicsApi::class)
-@Composable
-private fun TileIcon(
- icon: Icon,
- color: Color,
- animateToEnd: Boolean = false,
-) {
- val modifier = Modifier.size(dimensionResource(id = R.dimen.qs_icon_size))
- val context = LocalContext.current
- val loadedDrawable =
- remember(icon, context) {
- when (icon) {
- is Icon.Loaded -> icon.drawable
- is Icon.Resource -> AppCompatResources.getDrawable(context, icon.res)
- }
- }
- if (loadedDrawable !is Animatable) {
- Icon(
- icon = icon,
- tint = color,
+ DefaultEditTileGrid(
+ tiles = tiles,
+ iconOnlySpecs = iconOnlySpecs,
+ columns = GridCells.Fixed(columns),
modifier = modifier,
+ onAddTile = onAddTile,
+ onRemoveTile = onRemoveTile,
)
- } else if (icon is Icon.Resource) {
- val image = AnimatedImageVector.animatedVectorResource(id = icon.res)
- val painter =
- if (animateToEnd) {
- rememberAnimatedVectorPainter(animatedImageVector = image, atEnd = true)
- } else {
- var atEnd by remember(icon.res) { mutableStateOf(false) }
- LaunchedEffect(key1 = icon.res) {
- delay(350)
- atEnd = true
- }
- rememberAnimatedVectorPainter(animatedImageVector = image, atEnd = atEnd)
- }
- Image(
- painter = painter,
- contentDescription = null,
- colorFilter = ColorFilter.tint(color = color),
- modifier = modifier
- )
- }
-}
-
-@Composable
-private fun TileLazyGrid(
- modifier: Modifier = Modifier,
- columns: GridCells,
- content: LazyGridScope.() -> Unit,
-) {
- LazyVerticalGrid(
- columns = columns,
- verticalArrangement = spacedBy(dimensionResource(R.dimen.qs_tile_margin_vertical)),
- horizontalArrangement = spacedBy(dimensionResource(R.dimen.qs_tile_margin_horizontal)),
- modifier = modifier,
- content = content,
- )
-}
-
-@Composable
-private fun Modifier.tileModifier(colors: TileColorAttributes): Modifier {
- return fillMaxWidth()
- .clip(RoundedCornerShape(dimensionResource(R.dimen.qs_corner_radius)))
- .background(colorAttr(colors.background))
- .padding(horizontal = dimensionResource(id = R.dimen.qs_label_container_margin))
-}
-
-@Composable
-private fun tileHorizontalArrangement(iconOnly: Boolean): Arrangement.Horizontal {
- val horizontalAlignment =
- if (iconOnly) {
- Alignment.CenterHorizontally
- } else {
- Alignment.Start
- }
- return spacedBy(
- space = dimensionResource(id = R.dimen.qs_label_container_margin),
- alignment = horizontalAlignment
- )
-}
-
-@Composable
-private fun TileContent(
- label: String,
- secondaryLabel: String?,
- icon: Icon,
- colors: TileColorAttributes,
- iconOnly: Boolean,
- animateIconToEnd: Boolean = false,
-) {
- TileIcon(icon, colorAttr(colors.icon), animateIconToEnd)
-
- if (!iconOnly) {
- Column(verticalArrangement = Arrangement.Center, modifier = Modifier.fillMaxHeight()) {
- Text(
- label,
- color = colorAttr(colors.label),
- modifier = Modifier.basicMarquee(),
- )
- if (!TextUtils.isEmpty(secondaryLabel)) {
- Text(
- secondaryLabel ?: "",
- color = colorAttr(colors.secondaryLabel),
- modifier = Modifier.basicMarquee(),
- )
- }
- }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/StretchedGridLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/StretchedGridLayout.kt
new file mode 100644
index 0000000..ddd97c2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/StretchedGridLayout.kt
@@ -0,0 +1,139 @@
+/*
+ * 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.qs.panels.ui.compose
+
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.lazy.grid.GridCells
+import androidx.compose.foundation.lazy.grid.GridItemSpan
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.DisposableEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.dimensionResource
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.qs.panels.domain.interactor.IconTilesInteractor
+import com.android.systemui.qs.panels.domain.interactor.InfiniteGridSizeInteractor
+import com.android.systemui.qs.panels.shared.model.SizedTile
+import com.android.systemui.qs.panels.shared.model.TileRow
+import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel
+import com.android.systemui.qs.panels.ui.viewmodel.TileViewModel
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.res.R
+import javax.inject.Inject
+
+@SysUISingleton
+class StretchedGridLayout
+@Inject
+constructor(
+ private val iconTilesInteractor: IconTilesInteractor,
+ private val gridSizeInteractor: InfiniteGridSizeInteractor,
+) : GridLayout {
+
+ @Composable
+ override fun TileGrid(
+ tiles: List<TileViewModel>,
+ modifier: Modifier,
+ ) {
+ DisposableEffect(tiles) {
+ val token = Any()
+ tiles.forEach { it.startListening(token) }
+ onDispose { tiles.forEach { it.stopListening(token) } }
+ }
+
+ // Tile widths [normal|stretched]
+ // Icon [3 | 4]
+ // Large [6 | 8]
+ val columns = 12
+ val iconTilesSpecs by iconTilesInteractor.iconTilesSpecs.collectAsStateWithLifecycle()
+ val stretchedTiles =
+ remember(tiles) {
+ val sizedTiles =
+ tiles.map {
+ SizedTile(
+ it,
+ if (iconTilesSpecs.contains(it.spec)) {
+ 3
+ } else {
+ 6
+ }
+ )
+ }
+ splitInRows(sizedTiles, columns)
+ }
+
+ TileLazyGrid(columns = GridCells.Fixed(columns), modifier = modifier) {
+ items(stretchedTiles.size, span = { GridItemSpan(stretchedTiles[it].width) }) { index ->
+ Tile(
+ stretchedTiles[index].tile,
+ iconTilesSpecs.contains(stretchedTiles[index].tile.spec),
+ Modifier.height(dimensionResource(id = R.dimen.qs_tile_height))
+ )
+ }
+ }
+ }
+
+ @Composable
+ override fun EditTileGrid(
+ tiles: List<EditTileViewModel>,
+ modifier: Modifier,
+ onAddTile: (TileSpec, Int) -> Unit,
+ onRemoveTile: (TileSpec) -> Unit
+ ) {
+ val iconOnlySpecs by iconTilesInteractor.iconTilesSpecs.collectAsStateWithLifecycle()
+ val columns by gridSizeInteractor.columns.collectAsStateWithLifecycle()
+
+ DefaultEditTileGrid(
+ tiles = tiles,
+ iconOnlySpecs = iconOnlySpecs,
+ columns = GridCells.Fixed(columns),
+ modifier = modifier,
+ onAddTile = onAddTile,
+ onRemoveTile = onRemoveTile,
+ )
+ }
+
+ private fun splitInRows(
+ tiles: List<SizedTile<TileViewModel>>,
+ columns: Int
+ ): List<SizedTile<TileViewModel>> {
+ val row = TileRow<TileViewModel>(columns)
+
+ return buildList {
+ for (tile in tiles) {
+ if (row.maybeAddTile(tile)) {
+ if (row.isFull()) {
+ // Row is full, no need to stretch tiles
+ addAll(row.tiles)
+ row.clear()
+ }
+ } else {
+ if (row.isFull()) {
+ addAll(row.tiles)
+ } else {
+ // Stretching tiles when row isn't full
+ addAll(row.tiles.map { it.copy(width = it.width + (it.width / 3)) })
+ }
+ row.clear()
+ row.maybeAddTile(tile)
+ }
+ }
+ addAll(row.tiles)
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt
new file mode 100644
index 0000000..eb45110
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt
@@ -0,0 +1,403 @@
+/*
+ * 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.qs.panels.ui.compose
+
+import android.graphics.drawable.Animatable
+import android.text.TextUtils
+import androidx.appcompat.content.res.AppCompatResources
+import androidx.compose.animation.graphics.ExperimentalAnimationGraphicsApi
+import androidx.compose.animation.graphics.res.animatedVectorResource
+import androidx.compose.animation.graphics.res.rememberAnimatedVectorPainter
+import androidx.compose.animation.graphics.vector.AnimatedImageVector
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.background
+import androidx.compose.foundation.basicMarquee
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.combinedClickable
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Arrangement.spacedBy
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxHeight
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.lazy.grid.GridCells
+import androidx.compose.foundation.lazy.grid.GridItemSpan
+import androidx.compose.foundation.lazy.grid.LazyGridScope
+import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Add
+import androidx.compose.material.icons.filled.Remove
+import androidx.compose.material3.Icon
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberUpdatedState
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.ColorFilter
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.dimensionResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.semantics.contentDescription
+import androidx.compose.ui.semantics.onClick
+import androidx.compose.ui.semantics.semantics
+import androidx.compose.ui.semantics.stateDescription
+import androidx.compose.ui.unit.dp
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import com.android.compose.animation.Expandable
+import com.android.compose.theme.colorAttr
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.common.ui.compose.Icon
+import com.android.systemui.common.ui.compose.load
+import com.android.systemui.qs.panels.ui.viewmodel.ActiveTileColorAttributes
+import com.android.systemui.qs.panels.ui.viewmodel.AvailableEditActions
+import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel
+import com.android.systemui.qs.panels.ui.viewmodel.TileColorAttributes
+import com.android.systemui.qs.panels.ui.viewmodel.TileUiState
+import com.android.systemui.qs.panels.ui.viewmodel.TileViewModel
+import com.android.systemui.qs.panels.ui.viewmodel.toUiState
+import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractor
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.tileimpl.QSTileImpl
+import com.android.systemui.res.R
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.mapLatest
+
+object TileType
+
+@OptIn(ExperimentalCoroutinesApi::class, ExperimentalFoundationApi::class)
+@Composable
+fun Tile(
+ tile: TileViewModel,
+ iconOnly: Boolean,
+ modifier: Modifier,
+) {
+ val state: TileUiState by
+ tile.state
+ .mapLatest { it.toUiState() }
+ .collectAsStateWithLifecycle(tile.currentState.toUiState())
+ val context = LocalContext.current
+
+ Expandable(
+ color = colorAttr(state.colors.background),
+ shape = RoundedCornerShape(dimensionResource(R.dimen.qs_corner_radius)),
+ ) {
+ Row(
+ modifier =
+ modifier
+ .combinedClickable(
+ onClick = { tile.onClick(it) },
+ onLongClick = { tile.onLongClick(it) }
+ )
+ .tileModifier(state.colors),
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = tileHorizontalArrangement(iconOnly),
+ ) {
+ val icon =
+ remember(state.icon) {
+ state.icon.get().let {
+ if (it is QSTileImpl.ResourceIcon) {
+ Icon.Resource(it.resId, null)
+ } else {
+ Icon.Loaded(it.getDrawable(context), null)
+ }
+ }
+ }
+ TileContent(
+ label = state.label.toString(),
+ secondaryLabel = state.secondaryLabel.toString(),
+ icon = icon,
+ colors = state.colors,
+ iconOnly = iconOnly
+ )
+ }
+ }
+}
+
+@Composable
+fun TileLazyGrid(
+ modifier: Modifier = Modifier,
+ columns: GridCells,
+ content: LazyGridScope.() -> Unit,
+) {
+ LazyVerticalGrid(
+ columns = columns,
+ verticalArrangement = spacedBy(dimensionResource(R.dimen.qs_tile_margin_vertical)),
+ horizontalArrangement = spacedBy(dimensionResource(R.dimen.qs_tile_margin_horizontal)),
+ modifier = modifier,
+ content = content,
+ )
+}
+
+@Composable
+fun DefaultEditTileGrid(
+ tiles: List<EditTileViewModel>,
+ iconOnlySpecs: Set<TileSpec>,
+ columns: GridCells,
+ modifier: Modifier,
+ onAddTile: (TileSpec, Int) -> Unit,
+ onRemoveTile: (TileSpec) -> Unit,
+) {
+ val (currentTiles, otherTiles) = tiles.partition { it.isCurrent }
+ val (otherTilesStock, otherTilesCustom) = otherTiles.partition { it.appName == null }
+ val addTileToEnd: (TileSpec) -> Unit by rememberUpdatedState {
+ onAddTile(it, CurrentTilesInteractor.POSITION_AT_END)
+ }
+ val isIconOnly: (TileSpec) -> Boolean =
+ remember(iconOnlySpecs) { { tileSpec: TileSpec -> tileSpec in iconOnlySpecs } }
+
+ TileLazyGrid(modifier = modifier, columns = columns) {
+ // These Text are just placeholders to see the different sections. Not final UI.
+ item(span = { GridItemSpan(maxLineSpan) }) { Text("Current tiles", color = Color.White) }
+
+ editTiles(
+ currentTiles,
+ ClickAction.REMOVE,
+ onRemoveTile,
+ isIconOnly,
+ indicatePosition = true,
+ )
+
+ item(span = { GridItemSpan(maxLineSpan) }) { Text("Tiles to add", color = Color.White) }
+
+ editTiles(
+ otherTilesStock,
+ ClickAction.ADD,
+ addTileToEnd,
+ isIconOnly,
+ )
+
+ item(span = { GridItemSpan(maxLineSpan) }) {
+ Text("Custom tiles to add", color = Color.White)
+ }
+
+ editTiles(
+ otherTilesCustom,
+ ClickAction.ADD,
+ addTileToEnd,
+ isIconOnly,
+ )
+ }
+}
+
+private fun LazyGridScope.editTiles(
+ tiles: List<EditTileViewModel>,
+ clickAction: ClickAction,
+ onClick: (TileSpec) -> Unit,
+ isIconOnly: (TileSpec) -> Boolean,
+ indicatePosition: Boolean = false,
+) {
+ items(
+ count = tiles.size,
+ key = { tiles[it].tileSpec.spec },
+ span = { GridItemSpan(if (isIconOnly(tiles[it].tileSpec)) 1 else 2) },
+ contentType = { TileType }
+ ) {
+ val viewModel = tiles[it]
+ val canClick =
+ when (clickAction) {
+ ClickAction.ADD -> AvailableEditActions.ADD in viewModel.availableEditActions
+ ClickAction.REMOVE -> AvailableEditActions.REMOVE in viewModel.availableEditActions
+ }
+ val onClickActionName =
+ when (clickAction) {
+ ClickAction.ADD ->
+ stringResource(id = R.string.accessibility_qs_edit_tile_add_action)
+ ClickAction.REMOVE ->
+ stringResource(id = R.string.accessibility_qs_edit_remove_tile_action)
+ }
+ val stateDescription =
+ if (indicatePosition) {
+ stringResource(id = R.string.accessibility_qs_edit_position, it + 1)
+ } else {
+ ""
+ }
+
+ Box(
+ modifier =
+ Modifier.clickable(enabled = canClick) { onClick.invoke(viewModel.tileSpec) }
+ .animateItem()
+ .semantics {
+ onClick(onClickActionName) { false }
+ this.stateDescription = stateDescription
+ }
+ ) {
+ EditTile(
+ tileViewModel = viewModel,
+ isIconOnly(viewModel.tileSpec),
+ modifier = Modifier.height(dimensionResource(id = R.dimen.qs_tile_height))
+ )
+ if (canClick) {
+ Badge(clickAction, Modifier.align(Alignment.TopEnd))
+ }
+ }
+ }
+}
+
+@Composable
+fun Badge(action: ClickAction, modifier: Modifier = Modifier) {
+ Box(modifier = modifier.size(16.dp).background(Color.Cyan, shape = CircleShape)) {
+ Icon(
+ imageVector =
+ when (action) {
+ ClickAction.ADD -> Icons.Filled.Add
+ ClickAction.REMOVE -> Icons.Filled.Remove
+ },
+ "",
+ tint = Color.Black,
+ )
+ }
+}
+
+@Composable
+fun EditTile(
+ tileViewModel: EditTileViewModel,
+ iconOnly: Boolean,
+ modifier: Modifier = Modifier,
+) {
+ val label = tileViewModel.label.load() ?: tileViewModel.tileSpec.spec
+ val colors = ActiveTileColorAttributes
+
+ Row(
+ modifier = modifier.tileModifier(colors).semantics { this.contentDescription = label },
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = tileHorizontalArrangement(iconOnly)
+ ) {
+ TileContent(
+ label = label,
+ secondaryLabel = tileViewModel.appName?.load(),
+ colors = colors,
+ icon = tileViewModel.icon,
+ iconOnly = iconOnly,
+ animateIconToEnd = true,
+ )
+ }
+}
+
+enum class ClickAction {
+ ADD,
+ REMOVE,
+}
+
+@OptIn(ExperimentalAnimationGraphicsApi::class)
+@Composable
+private fun TileIcon(
+ icon: Icon,
+ color: Color,
+ animateToEnd: Boolean = false,
+) {
+ val modifier = Modifier.size(dimensionResource(id = R.dimen.qs_icon_size))
+ val context = LocalContext.current
+ val loadedDrawable =
+ remember(icon, context) {
+ when (icon) {
+ is Icon.Loaded -> icon.drawable
+ is Icon.Resource -> AppCompatResources.getDrawable(context, icon.res)
+ }
+ }
+ if (loadedDrawable !is Animatable) {
+ Icon(
+ icon = icon,
+ tint = color,
+ modifier = modifier,
+ )
+ } else if (icon is Icon.Resource) {
+ val image = AnimatedImageVector.animatedVectorResource(id = icon.res)
+ val painter =
+ if (animateToEnd) {
+ rememberAnimatedVectorPainter(animatedImageVector = image, atEnd = true)
+ } else {
+ var atEnd by remember(icon.res) { mutableStateOf(false) }
+ LaunchedEffect(key1 = icon.res) {
+ delay(350)
+ atEnd = true
+ }
+ rememberAnimatedVectorPainter(animatedImageVector = image, atEnd = atEnd)
+ }
+ Image(
+ painter = painter,
+ contentDescription = null,
+ colorFilter = ColorFilter.tint(color = color),
+ modifier = modifier
+ )
+ }
+}
+
+@Composable
+private fun Modifier.tileModifier(colors: TileColorAttributes): Modifier {
+ return fillMaxWidth()
+ .clip(RoundedCornerShape(dimensionResource(R.dimen.qs_corner_radius)))
+ .background(colorAttr(colors.background))
+ .padding(horizontal = dimensionResource(id = R.dimen.qs_label_container_margin))
+}
+
+@Composable
+private fun tileHorizontalArrangement(iconOnly: Boolean): Arrangement.Horizontal {
+ val horizontalAlignment =
+ if (iconOnly) {
+ Alignment.CenterHorizontally
+ } else {
+ Alignment.Start
+ }
+ return spacedBy(
+ space = dimensionResource(id = R.dimen.qs_label_container_margin),
+ alignment = horizontalAlignment
+ )
+}
+
+@Composable
+private fun TileContent(
+ label: String,
+ secondaryLabel: String?,
+ icon: Icon,
+ colors: TileColorAttributes,
+ iconOnly: Boolean,
+ animateIconToEnd: Boolean = false,
+) {
+ TileIcon(icon, colorAttr(colors.icon), animateIconToEnd)
+
+ if (!iconOnly) {
+ Column(verticalArrangement = Arrangement.Center, modifier = Modifier.fillMaxHeight()) {
+ Text(
+ label,
+ color = colorAttr(colors.label),
+ modifier = Modifier.basicMarquee(),
+ )
+ if (!TextUtils.isEmpty(secondaryLabel)) {
+ Text(
+ secondaryLabel ?: "",
+ color = colorAttr(colors.secondaryLabel),
+ modifier = Modifier.basicMarquee(),
+ )
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java
index 2068799..71b69c9 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java
@@ -16,6 +16,8 @@
package com.android.systemui.qs.tiles;
+import static com.android.settingslib.satellite.SatelliteDialogUtils.TYPE_IS_AIRPLANE_MODE;
+
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -32,9 +34,12 @@
import android.widget.Switch;
import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.internal.telephony.flags.Flags;
+import com.android.settingslib.satellite.SatelliteDialogUtils;
import com.android.systemui.animation.Expandable;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.qualifiers.Background;
@@ -54,6 +59,8 @@
import dagger.Lazy;
+import kotlinx.coroutines.Job;
+
import javax.inject.Inject;
/** Quick settings tile: Airplane mode **/
@@ -66,6 +73,9 @@
private final Lazy<ConnectivityManager> mLazyConnectivityManager;
private boolean mListening;
+ @Nullable
+ @VisibleForTesting
+ Job mClickJob;
@Inject
public AirplaneModeTile(
@@ -111,6 +121,21 @@
new Intent(TelephonyManager.ACTION_SHOW_NOTICE_ECM_BLOCK_OTHERS), 0);
return;
}
+
+ if (Flags.oemEnabledSatelliteFlag()) {
+ if (mClickJob != null && !mClickJob.isCompleted()) {
+ return;
+ }
+ mClickJob = SatelliteDialogUtils.mayStartSatelliteWarningDialog(
+ mContext, this, TYPE_IS_AIRPLANE_MODE, isAllowClick -> {
+ if (isAllowClick) {
+ setEnabled(!airplaneModeEnabled);
+ }
+ return null;
+ });
+ return;
+ }
+
setEnabled(!airplaneModeEnabled);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
index 9af34f6..9f41d98 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
@@ -16,6 +16,7 @@
package com.android.systemui.qs.tiles;
+import static com.android.settingslib.satellite.SatelliteDialogUtils.TYPE_IS_BLUETOOTH;
import static com.android.systemui.util.PluralMessageFormaterKt.icuMessageFormat;
import android.annotation.Nullable;
@@ -33,11 +34,14 @@
import android.util.Log;
import android.widget.Switch;
+import androidx.annotation.VisibleForTesting;
+
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.settingslib.Utils;
import com.android.settingslib.bluetooth.BluetoothUtils;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
+import com.android.settingslib.satellite.SatelliteDialogUtils;
import com.android.systemui.animation.Expandable;
import com.android.systemui.bluetooth.qsdialog.BluetoothTileDialogViewModel;
import com.android.systemui.dagger.qualifiers.Background;
@@ -55,6 +59,8 @@
import com.android.systemui.res.R;
import com.android.systemui.statusbar.policy.BluetoothController;
+import kotlinx.coroutines.Job;
+
import java.util.List;
import java.util.concurrent.Executor;
@@ -78,6 +84,9 @@
private final BluetoothTileDialogViewModel mDialogViewModel;
private final FeatureFlags mFeatureFlags;
+ @Nullable
+ @VisibleForTesting
+ Job mClickJob;
@Inject
public BluetoothTile(
@@ -110,6 +119,24 @@
@Override
protected void handleClick(@Nullable Expandable expandable) {
+ if (com.android.internal.telephony.flags.Flags.oemEnabledSatelliteFlag()) {
+ if (mClickJob != null && !mClickJob.isCompleted()) {
+ return;
+ }
+ mClickJob = SatelliteDialogUtils.mayStartSatelliteWarningDialog(
+ mContext, this, TYPE_IS_BLUETOOTH, isAllowClick -> {
+ if (!isAllowClick) {
+ return null;
+ }
+ handleClickEvent(expandable);
+ return null;
+ });
+ return;
+ }
+ handleClickEvent(expandable);
+ }
+
+ private void handleClickEvent(@Nullable Expandable expandable) {
if (mFeatureFlags.isEnabled(Flags.BLUETOOTH_QS_TILE_DIALOG)) {
mDialogViewModel.showDialog(expandable);
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandler.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandler.kt
index 2d3120a..972b20e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandler.kt
@@ -29,11 +29,15 @@
/**
* Provides a shortcut to start an activity from [QSTileUserActionInteractor]. It supports keyguard
- * dismissing and tile from-view animations.
+ * dismissing and tile from-view animations, as well as the option to show over lockscreen.
*/
interface QSTileIntentUserInputHandler {
- fun handle(expandable: Expandable?, intent: Intent)
+ fun handle(
+ expandable: Expandable?,
+ intent: Intent,
+ dismissShadeShowOverLockScreenWhenLocked: Boolean = false
+ )
/** @param requestLaunchingDefaultActivity used in case !pendingIndent.isActivity */
fun handle(
@@ -52,12 +56,25 @@
private val userHandle: UserHandle,
) : QSTileIntentUserInputHandler {
- override fun handle(expandable: Expandable?, intent: Intent) {
+ override fun handle(
+ expandable: Expandable?,
+ intent: Intent,
+ dismissShadeShowOverLockScreenWhenLocked: Boolean
+ ) {
val animationController: ActivityTransitionAnimator.Controller? =
expandable?.activityTransitionController(
InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE
)
- activityStarter.postStartActivityDismissingKeyguard(intent, 0, animationController)
+ if (dismissShadeShowOverLockScreenWhenLocked) {
+ activityStarter.startActivity(
+ intent,
+ true /* dismissShade */,
+ animationController,
+ true /* showOverLockscreenWhenLocked */
+ )
+ } else {
+ activityStarter.postStartActivityDismissingKeyguard(intent, 0, animationController)
+ }
}
// TODO(b/249804373): make sure to allow showing activities over the lockscreen. See b/292112939
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegate.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegate.java
index c9c4443..c971f54 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegate.java
@@ -15,6 +15,7 @@
*/
package com.android.systemui.qs.tiles.dialog;
+import static com.android.settingslib.satellite.SatelliteDialogUtils.TYPE_IS_WIFI;
import static com.android.systemui.Prefs.Key.QS_HAS_TURNED_OFF_MOBILE_DATA;
import static com.android.systemui.qs.tiles.dialog.InternetDialogController.MAX_WIFI_ENTRY_COUNT;
@@ -57,6 +58,8 @@
import com.android.internal.logging.UiEvent;
import com.android.internal.logging.UiEventLogger;
+import com.android.internal.telephony.flags.Flags;
+import com.android.settingslib.satellite.SatelliteDialogUtils;
import com.android.settingslib.wifi.WifiEnterpriseRestrictionUtils;
import com.android.systemui.Prefs;
import com.android.systemui.accessibility.floatingmenu.AnnotationLinkSpan;
@@ -73,6 +76,7 @@
import dagger.assisted.AssistedInject;
import kotlinx.coroutines.CoroutineScope;
+import kotlinx.coroutines.Job;
import java.util.List;
import java.util.concurrent.Executor;
@@ -161,6 +165,9 @@
// Wi-Fi scanning progress bar
protected boolean mIsProgressBarVisible;
private SystemUIDialog mDialog;
+ private final CoroutineScope mCoroutineScope;
+ @Nullable
+ private Job mClickJob;
@AssistedFactory
public interface Factory {
@@ -203,7 +210,7 @@
mCanConfigWifi = canConfigWifi;
mCanChangeWifiState = WifiEnterpriseRestrictionUtils.isChangeWifiStateAllowed(context);
mKeyguard = keyguardStateController;
-
+ mCoroutineScope = coroutineScope;
mUiEventLogger = uiEventLogger;
mDialogTransitionAnimator = dialogTransitionAnimator;
mAdapter = new InternetAdapter(mInternetDialogController, coroutineScope);
@@ -388,11 +395,9 @@
});
mConnectedWifListLayout.setOnClickListener(this::onClickConnectedWifi);
mSeeAllLayout.setOnClickListener(this::onClickSeeMoreButton);
- mWiFiToggle.setOnCheckedChangeListener(
- (buttonView, isChecked) -> {
- if (mInternetDialogController.isWifiEnabled() == isChecked) return;
- mInternetDialogController.setWifiEnabled(isChecked);
- });
+ mWiFiToggle.setOnClickListener(v -> {
+ handleWifiToggleClicked(mWiFiToggle.isChecked());
+ });
mDoneButton.setOnClickListener(v -> dialog.dismiss());
mShareWifiButton.setOnClickListener(v -> {
if (mInternetDialogController.mayLaunchShareWifiSettings(mConnectedWifiEntry, v)) {
@@ -404,6 +409,32 @@
});
}
+ private void handleWifiToggleClicked(boolean isChecked) {
+ if (Flags.oemEnabledSatelliteFlag()) {
+ if (mClickJob != null && !mClickJob.isCompleted()) {
+ return;
+ }
+ mClickJob = SatelliteDialogUtils.mayStartSatelliteWarningDialog(
+ mDialog.getContext(), mCoroutineScope, TYPE_IS_WIFI, isAllowClick -> {
+ if (isAllowClick) {
+ setWifiEnable(isChecked);
+ } else {
+ mWiFiToggle.setChecked(!isChecked);
+ }
+ return null;
+ });
+ return;
+ }
+ setWifiEnable(isChecked);
+ }
+
+ private void setWifiEnable(boolean isChecked) {
+ if (mInternetDialogController.isWifiEnabled() == isChecked) {
+ return;
+ }
+ mInternetDialogController.setWifiEnabled(isChecked);
+ }
+
@MainThread
private void updateEthernet() {
mEthernetLayout.setVisibility(
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/domain/interactor/QRCodeScannerTileDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/domain/interactor/QRCodeScannerTileDataInteractor.kt
new file mode 100644
index 0000000..1e8ce58
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/domain/interactor/QRCodeScannerTileDataInteractor.kt
@@ -0,0 +1,85 @@
+/*
+ * 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.qs.tiles.impl.qr.domain.interactor
+
+import android.os.UserHandle
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.qrcodescanner.controller.QRCodeScannerController
+import com.android.systemui.qrcodescanner.controller.QRCodeScannerController.DEFAULT_QR_CODE_SCANNER_CHANGE
+import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
+import com.android.systemui.qs.tiles.base.interactor.QSTileDataInteractor
+import com.android.systemui.qs.tiles.impl.qr.domain.model.QRCodeScannerTileModel
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
+import javax.inject.Inject
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.flow.stateIn
+
+/** Observes one qr scanner state changes providing the [QRCodeScannerTileModel]. */
+class QRCodeScannerTileDataInteractor
+@Inject
+constructor(
+ @Background private val bgCoroutineContext: CoroutineContext,
+ @Application private val scope: CoroutineScope,
+ private val qrController: QRCodeScannerController,
+) : QSTileDataInteractor<QRCodeScannerTileModel> {
+ override fun tileData(
+ user: UserHandle,
+ triggers: Flow<DataUpdateTrigger>
+ ): Flow<QRCodeScannerTileModel> =
+ conflatedCallbackFlow {
+ qrController.registerQRCodeScannerChangeObservers(DEFAULT_QR_CODE_SCANNER_CHANGE)
+ val callback =
+ object : QRCodeScannerController.Callback {
+ override fun onQRCodeScannerActivityChanged() {
+ trySend(generateModel())
+ }
+ }
+ qrController.addCallback(callback)
+ awaitClose {
+ qrController.removeCallback(callback)
+ qrController.unregisterQRCodeScannerChangeObservers(
+ DEFAULT_QR_CODE_SCANNER_CHANGE
+ )
+ }
+ }
+ .onStart { emit(generateModel()) }
+ .flowOn(bgCoroutineContext)
+ .stateIn(
+ scope,
+ SharingStarted.WhileSubscribed(),
+ QRCodeScannerTileModel.TemporarilyUnavailable
+ )
+
+ override fun availability(user: UserHandle): Flow<Boolean> =
+ flowOf(qrController.isCameraAvailable)
+
+ private fun generateModel(): QRCodeScannerTileModel {
+ val intent = qrController.intent
+
+ return if (qrController.isAbleToLaunchScannerActivity && intent != null)
+ QRCodeScannerTileModel.Available(intent)
+ else QRCodeScannerTileModel.TemporarilyUnavailable
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/domain/interactor/QRCodeScannerTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/domain/interactor/QRCodeScannerTileUserActionInteractor.kt
new file mode 100644
index 0000000..7c0c41e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/domain/interactor/QRCodeScannerTileUserActionInteractor.kt
@@ -0,0 +1,50 @@
+/*
+ * 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.qs.tiles.impl.qr.domain.interactor
+
+import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandler
+import com.android.systemui.qs.tiles.base.interactor.QSTileInput
+import com.android.systemui.qs.tiles.base.interactor.QSTileUserActionInteractor
+import com.android.systemui.qs.tiles.impl.qr.domain.model.QRCodeScannerTileModel
+import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction
+import javax.inject.Inject
+
+/** Handles qr tile clicks. */
+class QRCodeScannerTileUserActionInteractor
+@Inject
+constructor(
+ private val qsTileIntentUserActionHandler: QSTileIntentUserInputHandler,
+) : QSTileUserActionInteractor<QRCodeScannerTileModel> {
+
+ override suspend fun handleInput(input: QSTileInput<QRCodeScannerTileModel>): Unit =
+ with(input) {
+ when (action) {
+ is QSTileUserAction.Click -> {
+ when (data) {
+ is QRCodeScannerTileModel.Available ->
+ qsTileIntentUserActionHandler.handle(
+ action.expandable,
+ data.intent,
+ true
+ )
+ is QRCodeScannerTileModel.TemporarilyUnavailable -> {} // no-op
+ }
+ }
+ is QSTileUserAction.LongClick -> {} // no-op
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/domain/model/QRCodeScannerTileModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/domain/model/QRCodeScannerTileModel.kt
new file mode 100644
index 0000000..22c9b66
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/domain/model/QRCodeScannerTileModel.kt
@@ -0,0 +1,25 @@
+/*
+ * 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.qs.tiles.impl.qr.domain.model
+
+import android.content.Intent
+
+/** qr scanner tile model. */
+sealed interface QRCodeScannerTileModel {
+ data class Available(val intent: Intent) : QRCodeScannerTileModel
+ data object TemporarilyUnavailable : QRCodeScannerTileModel
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/ui/QRCodeScannerTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/ui/QRCodeScannerTileMapper.kt
new file mode 100644
index 0000000..45a7717
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/ui/QRCodeScannerTileMapper.kt
@@ -0,0 +1,59 @@
+/*
+ * 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.qs.tiles.impl.qr.ui
+
+import android.content.res.Resources
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper
+import com.android.systemui.qs.tiles.impl.qr.domain.model.QRCodeScannerTileModel
+import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
+import com.android.systemui.qs.tiles.viewmodel.QSTileState
+import com.android.systemui.res.R
+import javax.inject.Inject
+
+/** Maps [QRCodeScannerTileModel] to [QSTileState]. */
+class QRCodeScannerTileMapper
+@Inject
+constructor(
+ @Main private val resources: Resources,
+ private val theme: Resources.Theme,
+) : QSTileDataToStateMapper<QRCodeScannerTileModel> {
+
+ override fun map(config: QSTileConfig, data: QRCodeScannerTileModel): QSTileState =
+ QSTileState.build(resources, theme, config.uiConfig) {
+ label = resources.getString(R.string.qr_code_scanner_title)
+ contentDescription = label
+ icon = {
+ Icon.Loaded(resources.getDrawable(R.drawable.ic_qr_code_scanner, theme), null)
+ }
+ sideViewIcon = QSTileState.SideViewIcon.Chevron
+ supportedActions = setOf(QSTileState.UserAction.CLICK)
+
+ when (data) {
+ is QRCodeScannerTileModel.Available -> {
+ activationState = QSTileState.ActivationState.INACTIVE
+ secondaryLabel = null
+ }
+ is QRCodeScannerTileModel.TemporarilyUnavailable -> {
+ activationState = QSTileState.ActivationState.UNAVAILABLE
+ secondaryLabel =
+ resources.getString(R.string.qr_code_scanner_updating_secondary_label)
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/scroll/CropView.java b/packages/SystemUI/src/com/android/systemui/screenshot/scroll/CropView.java
index 5e561cf..ee1944e 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/scroll/CropView.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/scroll/CropView.java
@@ -45,6 +45,7 @@
import androidx.interpolator.view.animation.FastOutSlowInInterpolator;
import com.android.internal.graphics.ColorUtils;
+import com.android.systemui.Flags;
import com.android.systemui.res.R;
import java.util.List;
@@ -378,8 +379,14 @@
upper = 1;
break;
}
- Log.i(TAG, "getAllowedValues: " + boundary + ", "
- + "result=[lower=" + lower + ", upper=" + upper + "]");
+ if (lower >= upper) {
+ Log.wtf(TAG, "getAllowedValues computed an invalid range "
+ + "[" + lower + ", " + upper + "]");
+ if (Flags.screenshotScrollCropViewCrashFix()) {
+ lower = Math.min(lower, upper);
+ upper = lower;
+ }
+ }
return new Range<>(lower, upper);
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java
index 8b56dbd..6df8ac4 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java
@@ -207,11 +207,6 @@
/** Indicates QS is at its max height */
private boolean mFullyExpanded;
- /**
- * Indicates QS is at its maximum height, AND takes up the whole screen (i.e. not in split
- * shade).
- */
- private boolean mFullScreen;
private boolean mExpandedWhenExpandingStarted;
private boolean mAnimatingHiddenFromCollapsed;
private boolean mVisible;
@@ -972,35 +967,27 @@
}
private void setQsFullScreen(boolean qsFullScreen) {
- if (mFullScreen == qsFullScreen) {
- return; // no change
- }
- mFullScreen = qsFullScreen;
-
mShadeRepository.setLegacyQsFullscreen(qsFullScreen);
mNotificationStackScrollLayoutController.setQsFullScreen(qsFullScreen);
+ if (!SceneContainerFlag.isEnabled()) {
+ mNotificationStackScrollLayoutController.setScrollingEnabled(
+ mBarState != KEYGUARD && (!qsFullScreen || mExpansionFromOverscroll));
+ }
}
void updateQsState() {
- boolean qsExpanded = getExpanded();
-
if (!FooterViewRefactor.isEnabled()) {
// Update full screen state; note that this will be true if the QS panel is only
// partially expanded, and that is fixed with the footer view refactor.
- setQsFullScreen(/* qsFullScreen = */ qsExpanded && !mSplitShadeEnabled);
- }
-
- if (!SceneContainerFlag.isEnabled()) {
- mNotificationStackScrollLayoutController.setScrollingEnabled(
- mBarState != KEYGUARD && (!mFullScreen || mExpansionFromOverscroll));
+ setQsFullScreen(/* qsFullScreen = */ getExpanded() && !mSplitShadeEnabled);
}
if (mQsStateUpdateListener != null) {
- mQsStateUpdateListener.onQsStateUpdated(qsExpanded, mStackScrollerOverscrolling);
+ mQsStateUpdateListener.onQsStateUpdated(getExpanded(), mStackScrollerOverscrolling);
}
if (mQs == null) return;
- mQs.setExpanded(qsExpanded);
+ mQs.setExpanded(getExpanded());
}
/** update expanded state of QS */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallBackgroundContainer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/view/ChipBackgroundContainer.kt
similarity index 81%
rename from packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallBackgroundContainer.kt
rename to packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/view/ChipBackgroundContainer.kt
index ce88a5f..cae86a6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallBackgroundContainer.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/view/ChipBackgroundContainer.kt
@@ -14,18 +14,18 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.phone.ongoingcall
+package com.android.systemui.statusbar.chips.ui.view
import android.content.Context
import android.util.AttributeSet
import com.android.systemui.animation.view.LaunchableLinearLayout
/**
- * A container view for the ongoing call chip background. Needed so that we can limit the height of
- * the background when the font size is very large (200%), in which case the background would go
+ * A container view for the ongoing activity chip background. Needed so that we can limit the height
+ * of the background when the font size is very large (200%), in which case the background would go
* past the bounds of the status bar.
*/
-class OngoingCallBackgroundContainer(context: Context, attrs: AttributeSet) :
+class ChipBackgroundContainer(context: Context, attrs: AttributeSet) :
LaunchableLinearLayout(context, attrs) {
/** Sets where this view should fetch its max height from. */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallChronometer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/view/ChipChronometer.kt
similarity index 72%
rename from packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallChronometer.kt
rename to packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/view/ChipChronometer.kt
index bb7ba4c..ff3061e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallChronometer.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/view/ChipChronometer.kt
@@ -14,36 +14,34 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.phone.ongoingcall
+package com.android.systemui.statusbar.chips.ui.view
import android.content.Context
import android.util.AttributeSet
-
import android.widget.Chronometer
import androidx.annotation.UiThread
/**
- * A [Chronometer] specifically for the ongoing call chip in the status bar.
+ * A [Chronometer] specifically for chips in the status bar that show ongoing duration of an
+ * activity.
*
* This class handles:
- * 1) Setting the text width. If we used a basic WRAP_CONTENT for width, the chip width would
- * change slightly each second because the width of each number is slightly different.
+ * 1) Setting the text width. If we used a basic WRAP_CONTENT for width, the chip width would change
+ * slightly each second because the width of each number is slightly different.
*
- * Instead, we save the largest number width seen so far and ensure that the chip is at least
- * that wide. This means the chip may get larger over time (e.g. in the transition from 59:59
- * to 1:00:00), but never smaller.
- *
- * 2) Hiding the text if the time gets too long for the space available. Once the text has been
- * hidden, it remains hidden for the duration of the call.
+ * Instead, we save the largest number width seen so far and ensure that the chip is at least
+ * that wide. This means the chip may get larger over time (e.g. in the transition from 59:59 to
+ * 1:00:00), but never smaller.
+ * 2) Hiding the text if the time gets too long for the space available. Once the text has been
+ * hidden, it remains hidden for the duration of the activity.
*
* Note that if the text was too big in portrait mode, resulting in the text being hidden, then the
* text will also be hidden in landscape (even if there is enough space for it in landscape).
*/
-class OngoingCallChronometer @JvmOverloads constructor(
- context: Context,
- attrs: AttributeSet? = null,
- defStyle: Int = 0
-) : Chronometer(context, attrs, defStyle) {
+class ChipChronometer
+@JvmOverloads
+constructor(context: Context, attrs: AttributeSet? = null, defStyle: Int = 0) :
+ Chronometer(context, attrs, defStyle) {
// Minimum width that the text view can be. Corresponds with the largest number width seen so
// far.
@@ -53,8 +51,8 @@
private var shouldHideText: Boolean = false
override fun setBase(base: Long) {
- // These variables may have changed during the previous call, so re-set them before the new
- // call starts.
+ // These variables may have changed during the previous activity, so re-set them before the
+ // new activity starts.
minimumTextWidth = 0
shouldHideText = false
visibility = VISIBLE
@@ -75,9 +73,7 @@
}
// Evaluate how wide the text *wants* to be if it had unlimited space.
- super.onMeasure(
- MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),
- heightMeasureSpec)
+ super.onMeasure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), heightMeasureSpec)
val desiredTextWidth = measuredWidth
// Evaluate how wide the text *can* be based on the enforced constraints
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java
index 968b591..5a616df 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java
@@ -18,7 +18,7 @@
import static android.graphics.PorterDuff.Mode.SRC_ATOP;
-import static com.android.systemui.Flags.notificationBackgroundTintOptimization;
+import static com.android.systemui.Flags.notificationFooterBackgroundTintOptimization;
import static com.android.systemui.util.ColorUtilKt.hexColorString;
import android.annotation.ColorInt;
@@ -407,7 +407,7 @@
final Drawable clearAllBg = theme.getDrawable(R.drawable.notif_footer_btn_background);
final Drawable manageBg = theme.getDrawable(R.drawable.notif_footer_btn_background);
final @ColorInt int scHigh;
- if (!notificationBackgroundTintOptimization()) {
+ if (!notificationFooterBackgroundTintOptimization()) {
scHigh = Utils.getColorAttrDefaultColor(mContext,
com.android.internal.R.attr.materialColorSurfaceContainerHigh);
if (scHigh != 0) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt
index bfc5932..a901c5f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt
@@ -241,9 +241,6 @@
) {
val TAG = "AvalancheSuppressor"
- override var reason: String = "avalanche"
- protected set
-
enum class State {
ALLOW_CONVERSATION_AFTER_AVALANCHE,
ALLOW_HIGH_PRIORITY_CONVERSATION_ANY_TIME,
@@ -257,19 +254,15 @@
override fun shouldSuppress(entry: NotificationEntry): Boolean {
if (!isCooldownEnabled()) {
- reason = "FALSE avalanche cooldown setting DISABLED"
return false
}
val timeSinceAvalancheMs = systemClock.currentTimeMillis() - avalancheProvider.startTime
val timedOut = timeSinceAvalancheMs >= avalancheProvider.timeoutMs
if (timedOut) {
- reason = "FALSE avalanche event TIMED OUT. " +
- "${timeSinceAvalancheMs/1000} seconds since last avalanche"
return false
}
val state = calculateState(entry)
if (state != State.SUPPRESS) {
- reason = "FALSE avalanche IN ALLOWLIST: $state"
return false
}
return true
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HeadsUpStyleProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HeadsUpStyleProvider.kt
index db3cf5a..9d0fcd3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HeadsUpStyleProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HeadsUpStyleProvider.kt
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.notification.row
import android.app.Flags
+import android.os.SystemProperties
import com.android.systemui.statusbar.data.repository.StatusBarModeRepositoryStore
import javax.inject.Inject
@@ -34,8 +35,13 @@
HeadsUpStyleProvider {
override fun shouldApplyCompactStyle(): Boolean {
- // Use compact HUN for immersive mode.
- return Flags.compactHeadsUpNotification() &&
- statusBarModeRepositoryStore.defaultDisplay.isInFullscreenMode.value
+ return Flags.compactHeadsUpNotification() && (isInImmersiveMode() || alwaysShow())
}
+
+ private fun isInImmersiveMode() =
+ statusBarModeRepositoryStore.defaultDisplay.isInFullscreenMode.value
+
+ /** developer setting to always show Minimal HUN, even if the device is not in full screen */
+ private fun alwaysShow() =
+ SystemProperties.getBoolean("persist.compact_heads_up_notification.always_show", false)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index abbe7d7..17b54c8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -674,7 +674,7 @@
void setOverExpansion(float margin) {
mAmbientState.setOverExpansion(margin);
if (notificationOverExpansionClippingFix() && !SceneContainerFlag.isEnabled()) {
- setRoundingClippingYTranslation((int) margin);
+ setRoundingClippingYTranslation(mShouldUseSplitNotificationShade ? (int) margin : 0);
}
updateStackPosition();
requestChildrenUpdate();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index 5b0b46e..d3d2b1e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -2805,7 +2805,16 @@
mScrimController.setExpansionAffectsAlpha(!unlocking);
if (mAlternateBouncerInteractor.isVisibleState()) {
- if (!DeviceEntryUdfpsRefactor.isEnabled()) {
+ if (DeviceEntryUdfpsRefactor.isEnabled()) {
+ if ((!mKeyguardStateController.isOccluded() || mShadeSurface.isPanelExpanded())
+ && (mState == StatusBarState.SHADE || mState == StatusBarState.SHADE_LOCKED
+ || mTransitionToFullShadeProgress > 0f)) {
+ // Assume scrim state for shade is already correct and do nothing
+ } else {
+ // Safeguard which prevents the scrim from being stuck in the wrong state
+ mScrimController.transitionTo(ScrimState.KEYGUARD);
+ }
+ } else {
if ((!mKeyguardStateController.isOccluded() || mShadeSurface.isPanelExpanded())
&& (mState == StatusBarState.SHADE || mState == StatusBarState.SHADE_LOCKED
|| mTransitionToFullShadeProgress > 0f)) {
@@ -2814,7 +2823,6 @@
mScrimController.transitionTo(ScrimState.AUTH_SCRIMMED);
}
}
-
// This will cancel the keyguardFadingAway animation if it is running. We need to do
// this as otherwise it can remain pending and leave keyguard in a weird state.
mUnlockScrimCallback.onCancelled();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
index 4fc11df..a858fb0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
@@ -119,7 +119,7 @@
private MultiSourceMinAlphaController mEndSideAlphaController;
private LinearLayout mEndSideContent;
private View mClockView;
- private View mOngoingCallChip;
+ private View mOngoingActivityChip;
private View mNotificationIconAreaInner;
// Visibilities come in from external system callers via disable flags, but we also sometimes
// modify the visibilities internally. We need to store both so that we don't accidentally
@@ -345,7 +345,7 @@
mEndSideContent = mStatusBar.findViewById(R.id.status_bar_end_side_content);
mEndSideAlphaController = new MultiSourceMinAlphaController(mEndSideContent);
mClockView = mStatusBar.findViewById(R.id.clock);
- mOngoingCallChip = mStatusBar.findViewById(R.id.ongoing_call_chip);
+ mOngoingActivityChip = mStatusBar.findViewById(R.id.ongoing_activity_chip);
showEndSideContent(false);
showClock(false);
initOperatorName();
@@ -594,9 +594,9 @@
// so if the icons are disabled then the call chip should be, too.)
boolean showOngoingCallChip = hasOngoingCall && !disableNotifications;
if (showOngoingCallChip) {
- showOngoingCallChip(animate);
+ showOngoingActivityChip(animate);
} else {
- hideOngoingCallChip(animate);
+ hideOngoingActivityChip(animate);
}
mOngoingCallController.notifyChipVisibilityChanged(showOngoingCallChip);
}
@@ -688,14 +688,19 @@
animateShow(mClockView, animate);
}
- /** Hides the ongoing call chip. */
- public void hideOngoingCallChip(boolean animate) {
- animateHiddenState(mOngoingCallChip, View.GONE, animate);
+ /** Hides the ongoing activity chip. */
+ private void hideOngoingActivityChip(boolean animate) {
+ animateHiddenState(mOngoingActivityChip, View.GONE, animate);
}
- /** Displays the ongoing call chip. */
- public void showOngoingCallChip(boolean animate) {
- animateShow(mOngoingCallChip, animate);
+ /**
+ * Displays the ongoing activity chip.
+ *
+ * For now, this chip will only ever contain the ongoing call information, but after b/332662551
+ * feature is implemented, it will support different kinds of ongoing activities.
+ */
+ private void showOngoingActivityChip(boolean animate) {
+ animateShow(mOngoingActivityChip, animate);
}
/**
@@ -803,7 +808,7 @@
private void initOngoingCallChip() {
mOngoingCallController.addCallback(mOngoingCallListener);
- mOngoingCallController.setChipView(mOngoingCallChip);
+ mOngoingCallController.setChipView(mOngoingActivityChip);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
index ec88b6c..a7d4ce3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
@@ -36,6 +36,8 @@
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.dump.DumpManager
import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.statusbar.chips.ui.view.ChipChronometer
+import com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer
import com.android.systemui.statusbar.data.repository.StatusBarModeRepositoryStore
import com.android.systemui.statusbar.gesture.SwipeStatusBarAwayGestureHandler
import com.android.systemui.statusbar.notification.collection.NotificationEntry
@@ -145,8 +147,8 @@
fun setChipView(chipView: View) {
tearDownChipView()
this.chipView = chipView
- val backgroundView: OngoingCallBackgroundContainer? =
- chipView.findViewById(R.id.ongoing_call_chip_background)
+ val backgroundView: ChipBackgroundContainer? =
+ chipView.findViewById(R.id.ongoing_activity_chip_background)
backgroundView?.maxHeightFetcher = { statusBarWindowController.statusBarHeight }
if (hasOngoingCall()) {
updateChip()
@@ -226,7 +228,7 @@
if (callNotificationInfo == null) { return }
val currentChipView = chipView
val backgroundView =
- currentChipView?.findViewById<View>(R.id.ongoing_call_chip_background)
+ currentChipView?.findViewById<View>(R.id.ongoing_activity_chip_background)
val intent = callNotificationInfo?.intent
if (currentChipView != null && backgroundView != null && intent != null) {
currentChipView.setOnClickListener {
@@ -271,8 +273,8 @@
@VisibleForTesting
fun tearDownChipView() = chipView?.getTimeView()?.stop()
- private fun View.getTimeView(): OngoingCallChronometer? {
- return this.findViewById(R.id.ongoing_call_chip_time)
+ private fun View.getTimeView(): ChipChronometer? {
+ return this.findViewById(R.id.ongoing_activity_chip_time)
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/data/repository/OngoingCallRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/data/repository/OngoingCallRepository.kt
index 9c78ab4..886481e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/data/repository/OngoingCallRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/data/repository/OngoingCallRepository.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * 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.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt
index fa8a7d8..8b48bd3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt
@@ -22,6 +22,7 @@
import com.android.systemui.dump.DumpManager
import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun
import com.android.systemui.statusbar.policy.BaseHeadsUpManager.HeadsUpEntry
+import com.android.systemui.util.Compile
import java.io.PrintWriter
import javax.inject.Inject
@@ -30,12 +31,14 @@
* succession, by delaying visual listener side effects and removal handling from BaseHeadsUpManager
*/
@SysUISingleton
-class AvalancheController @Inject constructor(
+class AvalancheController
+@Inject
+constructor(
dumpManager: DumpManager,
) : Dumpable {
private val tag = "AvalancheController"
- private val debug = false
+ private val debug = Compile.IS_DEBUG && Log.isLoggable(tag, Log.DEBUG)
// HUN showing right now, in the floating state where full shade is hidden, on launcher or AOD
@VisibleForTesting var headsUpEntryShowing: HeadsUpEntry? = null
@@ -79,7 +82,7 @@
val fn = "[$label] => AvalancheController.update [${getKey(entry)}]"
if (entry == null) {
log { "Entry is NULL, stop update." }
- return;
+ return
}
if (debug) {
debugRunnableLabelMap[runnable] = label
@@ -106,7 +109,10 @@
if (isOnlyNextEntry) {
// HeadsUpEntry.updateEntry recursively calls AvalancheController#update
// and goes to the isShowing case above
- headsUpEntryShowing!!.updateEntry(false, "avalanche duration update")
+ headsUpEntryShowing!!.updateEntry(
+ /* updatePostTime= */ false,
+ /* updateEarliestRemovalTime= */ false,
+ /* reason= */ "avalanche duration update")
}
}
logState("after $fn")
@@ -142,9 +148,12 @@
} else if (isShowing(entry)) {
log { "$fn => [remove showing ${getKey(entry)}]" }
previousHunKey = getKey(headsUpEntryShowing)
-
+ // Show the next HUN before removing this one, so that we don't tell listeners
+ // onHeadsUpPinnedModeChanged, which causes
+ // NotificationPanelViewController.updateTouchableRegion to hide the window while the
+ // HUN is animating out, resulting in a flicker.
+ showNext()
runnable.run()
- showNextAfterRemove()
} else {
log { "$fn => [removing untracked ${getKey(entry)}]" }
}
@@ -247,7 +256,7 @@
}
}
- private fun showNextAfterRemove() {
+ private fun showNext() {
log { "SHOW NEXT" }
headsUpEntryShowing = null
@@ -294,17 +303,21 @@
private fun getStateStr(): String {
return "SHOWING: [${getKey(headsUpEntryShowing)}]" +
- "\nPREVIOUS: [$previousHunKey]" +
- "\nNEXT LIST: $nextListStr" +
- "\nNEXT MAP: $nextMapStr" +
- "\nDROPPED: $dropSetStr"
+ "\nPREVIOUS: [$previousHunKey]" +
+ "\nNEXT LIST: $nextListStr" +
+ "\nNEXT MAP: $nextMapStr" +
+ "\nDROPPED: $dropSetStr"
}
private fun logState(reason: String) {
- log { "\n================================================================================="}
+ log {
+ "\n================================================================================="
+ }
log { "STATE $reason" }
log { getStateStr() }
- log { "=================================================================================\n"}
+ log {
+ "=================================================================================\n"
+ }
}
private val dropSetStr: String
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java
index b8318a7..a7fe49b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java
@@ -39,6 +39,7 @@
import com.android.systemui.res.R;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
+import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun;
import com.android.systemui.statusbar.notification.shared.NotificationsHeadsUpRefactor;
import com.android.systemui.util.ListenerSet;
import com.android.systemui.util.concurrency.DelayableExecutor;
@@ -114,7 +115,8 @@
mUiEventLogger = uiEventLogger;
mAvalancheController = avalancheController;
Resources resources = context.getResources();
- mMinimumDisplayTime = resources.getInteger(R.integer.heads_up_notification_minimum_time);
+ mMinimumDisplayTime = NotificationThrottleHun.isEnabled()
+ ? 500 : resources.getInteger(R.integer.heads_up_notification_minimum_time);
mStickyForSomeTimeAutoDismissTime = resources.getInteger(
R.integer.sticky_heads_up_notification_time);
mAutoDismissTime = resources.getInteger(R.integer.heads_up_notification_decay);
@@ -765,11 +767,23 @@
* @param updatePostTime whether or not to refresh the post time
*/
public void updateEntry(boolean updatePostTime, @Nullable String reason) {
+ updateEntry(updatePostTime, /* updateEarliestRemovalTime= */ true, reason);
+ }
+
+ /**
+ * Updates an entry's removal time.
+ * @param updatePostTime whether or not to refresh the post time
+ * @param updateEarliestRemovalTime whether this update should further delay removal
+ */
+ public void updateEntry(boolean updatePostTime, boolean updateEarliestRemovalTime,
+ @Nullable String reason) {
Runnable runnable = () -> {
mLogger.logUpdateEntry(mEntry, updatePostTime, reason);
final long now = mSystemClock.elapsedRealtime();
- mEarliestRemovalTime = now + mMinimumDisplayTime;
+ if (updateEarliestRemovalTime) {
+ mEarliestRemovalTime = now + mMinimumDisplayTime;
+ }
if (updatePostTime) {
mPostTime = Math.max(mPostTime, now);
@@ -785,7 +799,9 @@
FinishTimeUpdater finishTimeCalculator = () -> {
final long finishTime = calculateFinishTime();
final long now = mSystemClock.elapsedRealtime();
- final long timeLeft = Math.max(finishTime - now, mMinimumDisplayTime);
+ final long timeLeft = NotificationThrottleHun.isEnabled()
+ ? Math.max(finishTime, mEarliestRemovalTime) - now
+ : Math.max(finishTime - now, mMinimumDisplayTime);
return timeLeft;
};
scheduleAutoRemovalCallback(finishTimeCalculator, "updateEntry (not sticky)");
@@ -818,13 +834,6 @@
}
public int compareNonTimeFields(HeadsUpEntry headsUpEntry) {
- boolean isPinned = mEntry.isRowPinned();
- boolean otherPinned = headsUpEntry.mEntry.isRowPinned();
- if (isPinned && !otherPinned) {
- return -1;
- } else if (!isPinned && otherPinned) {
- return 1;
- }
boolean selfFullscreen = hasFullScreenIntent(mEntry);
boolean otherFullscreen = hasFullScreenIntent(headsUpEntry.mEntry);
if (selfFullscreen && !otherFullscreen) {
@@ -851,6 +860,13 @@
}
public int compareTo(@NonNull HeadsUpEntry headsUpEntry) {
+ boolean isPinned = mEntry.isRowPinned();
+ boolean otherPinned = headsUpEntry.mEntry.isRowPinned();
+ if (isPinned && !otherPinned) {
+ return -1;
+ } else if (!isPinned && otherPinned) {
+ return 1;
+ }
int nonTimeCompareResult = compareNonTimeFields(headsUpEntry);
if (nonTimeCompareResult != 0) {
return nonTimeCompareResult;
diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractor.kt b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractor.kt
index 9339651..516cb46 100644
--- a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractor.kt
@@ -533,7 +533,7 @@
targetUserId = targetUserId,
::showDialog,
::dismissDialog,
- ::selectUser,
+ ::switchUser
)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java
index 51ceda3..f9fe5e7 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java
@@ -27,6 +27,7 @@
import static org.mockito.Mockito.when;
import android.os.SystemClock;
+import android.platform.test.annotations.EnableFlags;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper.RunWithLooper;
import android.view.KeyEvent;
@@ -125,8 +126,8 @@
}
@Test
+ @EnableFlags(Flags.FLAG_REVAMPED_BOUNCER_MESSAGES)
public void withFeatureFlagOn_oldMessage_isHidden() {
- mSetFlagsRule.enableFlags(Flags.FLAG_REVAMPED_BOUNCER_MESSAGES);
KeyguardAbsKeyInputViewController underTest = createTestObject();
underTest.init();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
index 97c3c42..fa78f0c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
@@ -37,6 +37,7 @@
import android.hardware.face.FaceSensorPropertiesInternal
import android.hardware.fingerprint.FingerprintSensorProperties
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
+import android.platform.test.annotations.EnableFlags
import android.view.HapticFeedbackConstants
import android.view.MotionEvent
import androidx.test.filters.SmallTest
@@ -1272,8 +1273,8 @@
}
@Test
+ @EnableFlags(FLAG_BP_TALKBACK)
fun hint_for_talkback_guidance() = runGenericTest {
- mSetFlagsRule.enableFlags(FLAG_BP_TALKBACK)
val hint by collectLastValue(viewModel.accessibilityHint)
// Touches should fall outside of sensor area
@@ -1295,10 +1296,9 @@
}
@Test
+ @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT, FLAG_CONSTRAINT_BP)
fun descriptionOverriddenByVerticalListContentView() =
runGenericTest(contentView = promptContentView, description = "test description") {
- mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
- mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP)
val contentView by collectLastValue(viewModel.contentView)
val description by collectLastValue(viewModel.description)
@@ -1307,13 +1307,12 @@
}
@Test
+ @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT, FLAG_CONSTRAINT_BP)
fun descriptionOverriddenByContentViewWithMoreOptionsButton() =
runGenericTest(
contentView = promptContentViewWithMoreOptionsButton,
description = "test description"
) {
- mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
- mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP)
val contentView by collectLastValue(viewModel.contentView)
val description by collectLastValue(viewModel.description)
@@ -1322,10 +1321,9 @@
}
@Test
+ @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT, FLAG_CONSTRAINT_BP)
fun descriptionWithoutContentView() =
runGenericTest(description = "test description") {
- mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
- mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP)
val contentView by collectLastValue(viewModel.contentView)
val description by collectLastValue(viewModel.description)
@@ -1334,19 +1332,17 @@
}
@Test
+ @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT, FLAG_CONSTRAINT_BP)
fun logo_nullIfPkgNameNotFound() =
runGenericTest(packageName = OP_PACKAGE_NAME_CAN_NOT_BE_FOUND) {
- mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
- mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP)
val logo by collectLastValue(viewModel.logo)
assertThat(logo).isNull()
}
@Test
+ @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT, FLAG_CONSTRAINT_BP)
fun logo_defaultWithOverrides() =
runGenericTest(packageName = packageNameForLogoWithOverrides) {
- mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
- mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP)
val logo by collectLastValue(viewModel.logo)
// 1. PM.getApplicationInfo(packageNameForLogoWithOverrides) is set to return
@@ -1357,71 +1353,63 @@
}
@Test
+ @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT, FLAG_CONSTRAINT_BP)
fun logo_defaultIsNull() =
runGenericTest(packageName = OP_PACKAGE_NAME_NO_ICON) {
- mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
- mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP)
val logo by collectLastValue(viewModel.logo)
assertThat(logo).isNull()
}
@Test
+ @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT, FLAG_CONSTRAINT_BP)
fun logo_default() = runGenericTest {
- mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
- mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP)
val logo by collectLastValue(viewModel.logo)
assertThat(logo).isEqualTo(defaultLogoIcon)
}
@Test
+ @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT, FLAG_CONSTRAINT_BP)
fun logo_resSetByApp() =
runGenericTest(logoRes = logoResFromApp) {
- mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
- mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP)
val logo by collectLastValue(viewModel.logo)
assertThat(logo).isEqualTo(logoFromApp)
}
@Test
+ @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT, FLAG_CONSTRAINT_BP)
fun logo_bitmapSetByApp() =
runGenericTest(logoBitmap = logoBitmapFromApp) {
- mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
- mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP)
val logo by collectLastValue(viewModel.logo)
assertThat((logo as BitmapDrawable).bitmap).isEqualTo(logoBitmapFromApp)
}
@Test
+ @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT, FLAG_CONSTRAINT_BP)
fun logoDescription_emptyIfPkgNameNotFound() =
runGenericTest(packageName = OP_PACKAGE_NAME_CAN_NOT_BE_FOUND) {
- mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
- mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP)
val logoDescription by collectLastValue(viewModel.logoDescription)
assertThat(logoDescription).isEqualTo("")
}
@Test
+ @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT, FLAG_CONSTRAINT_BP)
fun logoDescription_defaultIsEmpty() =
runGenericTest(packageName = OP_PACKAGE_NAME_NO_ICON) {
- mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
- mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP)
val logoDescription by collectLastValue(viewModel.logoDescription)
assertThat(logoDescription).isEqualTo("")
}
@Test
+ @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT, FLAG_CONSTRAINT_BP)
fun logoDescription_default() = runGenericTest {
- mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
- mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP)
val logoDescription by collectLastValue(viewModel.logoDescription)
assertThat(logoDescription).isEqualTo(defaultLogoDescription)
}
@Test
+ @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT, FLAG_CONSTRAINT_BP)
fun logoDescription_setByApp() =
runGenericTest(logoDescription = logoDescriptionFromApp) {
- mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
- mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP)
val logoDescription by collectLastValue(viewModel.logoDescription)
assertThat(logoDescription).isEqualTo(logoDescriptionFromApp)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModelTest.kt
index f62a55d..11f74c0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModelTest.kt
@@ -17,6 +17,7 @@
package com.android.systemui.bluetooth.qsdialog
import android.bluetooth.BluetoothAdapter
+import android.platform.test.annotations.EnableFlags
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.view.View
@@ -63,6 +64,7 @@
@SmallTest
@RunWith(AndroidTestingRunner::class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
+@EnableFlags(Flags.FLAG_BLUETOOTH_QS_TILE_DIALOG_AUTO_ON_TOGGLE)
class BluetoothTileDialogViewModelTest : SysuiTestCase() {
@get:Rule val mockitoRule: MockitoRule = MockitoJUnit.rule()
@@ -113,7 +115,6 @@
@Before
fun setUp() {
- mSetFlagsRule.enableFlags(Flags.FLAG_BLUETOOTH_QS_TILE_DIALOG_AUTO_ON_TOGGLE)
scheduler = TestCoroutineScheduler()
dispatcher = UnconfinedTestDispatcher(scheduler)
testScope = TestScope(dispatcher)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/contrast/ContrastDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/contrast/ContrastDialogDelegateTest.kt
deleted file mode 100644
index ab03465..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/contrast/ContrastDialogDelegateTest.kt
+++ /dev/null
@@ -1,112 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.contrast
-
-import android.app.UiModeManager
-import android.app.UiModeManager.ContrastUtils.fromContrastLevel
-import android.os.Looper
-import android.provider.Settings
-import android.testing.AndroidTestingRunner
-import android.testing.TestableLooper.RunWithLooper
-import android.view.LayoutInflater
-import android.view.View
-import android.widget.FrameLayout
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.animation.DialogTransitionAnimator
-import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.model.SysUiState
-import com.android.systemui.settings.UserTracker
-import com.android.systemui.statusbar.phone.SystemUIDialog
-import com.android.systemui.util.concurrency.FakeExecutor
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.whenever
-import com.android.systemui.util.settings.SecureSettings
-import com.android.systemui.util.time.FakeSystemClock
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.ArgumentCaptor
-import org.mockito.ArgumentMatchers.anyInt
-import org.mockito.Mock
-import org.mockito.Mockito.eq
-import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations
-
-/** Test the behaviour of buttons of the [ContrastDialogDelegate]. */
-@SmallTest
-@RunWith(AndroidTestingRunner::class)
-@RunWithLooper
-class ContrastDialogDelegateTest : SysuiTestCase() {
-
- private val mainExecutor = FakeExecutor(FakeSystemClock())
- private lateinit var mContrastDialogDelegate: ContrastDialogDelegate
- @Mock private lateinit var sysuiDialogFactory: SystemUIDialog.Factory
- @Mock private lateinit var sysuiDialog: SystemUIDialog
- @Mock private lateinit var mockUiModeManager: UiModeManager
- @Mock private lateinit var mockUserTracker: UserTracker
- @Mock private lateinit var mockSecureSettings: SecureSettings
- @Mock private lateinit var sysuiState: SysUiState
-
- @Before
- fun setUp() {
- MockitoAnnotations.initMocks(this)
- mDependency.injectTestDependency(FeatureFlags::class.java, FakeFeatureFlags())
- mDependency.injectTestDependency(SysUiState::class.java, sysuiState)
- mDependency.injectMockDependency(DialogTransitionAnimator::class.java)
- whenever(sysuiState.setFlag(any(), any())).thenReturn(sysuiState)
- whenever(sysuiDialogFactory.create(any(SystemUIDialog.Delegate::class.java)))
- .thenReturn(sysuiDialog)
- whenever(sysuiDialog.layoutInflater).thenReturn(LayoutInflater.from(mContext))
-
- whenever(mockUserTracker.userId).thenReturn(context.userId)
- if (Looper.myLooper() == null) Looper.prepare()
-
- mContrastDialogDelegate =
- ContrastDialogDelegate(
- sysuiDialogFactory,
- mainExecutor,
- mockUiModeManager,
- mockUserTracker,
- mockSecureSettings
- )
-
- mContrastDialogDelegate.createDialog()
- val viewCaptor = ArgumentCaptor.forClass(View::class.java)
- verify(sysuiDialog).setView(viewCaptor.capture())
- whenever(sysuiDialog.requireViewById(anyInt()) as View?).then {
- viewCaptor.value.requireViewById(it.getArgument(0))
- }
- }
-
- @Test
- fun testClickButtons_putsContrastInSettings() {
- mContrastDialogDelegate.onCreate(sysuiDialog, null)
-
- mContrastDialogDelegate.contrastButtons.forEach {
- (contrastLevel: Int, clickedButton: FrameLayout) ->
- clickedButton.performClick()
- mainExecutor.runAllReady()
- verify(mockSecureSettings)
- .putFloatForUser(
- eq(Settings.Secure.CONTRAST_LEVEL),
- eq(fromContrastLevel(contrastLevel)),
- eq(context.userId)
- )
- }
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt
index 7afed9d..977116e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt
@@ -8,6 +8,8 @@
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.flags.DisableSceneContainer
+import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.flags.Flags
import com.android.systemui.flags.fakeFeatureFlagsClassic
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
@@ -19,7 +21,10 @@
import com.android.systemui.kosmos.testScope
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
import com.android.systemui.power.domain.interactor.powerInteractor
+import com.android.systemui.scene.data.repository.Idle
+import com.android.systemui.scene.data.repository.setSceneTransition
import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.any
import com.android.systemui.utils.GlobalWindowManager
@@ -70,12 +75,13 @@
resourceTrimmer =
ResourceTrimmer(
keyguardInteractor,
- powerInteractor,
- kosmos.keyguardTransitionInteractor,
- globalWindowManager,
- testScope.backgroundScope,
- kosmos.testDispatcher,
- featureFlags,
+ powerInteractor = powerInteractor,
+ keyguardTransitionInteractor = kosmos.keyguardTransitionInteractor,
+ globalWindowManager = globalWindowManager,
+ applicationScope = testScope.backgroundScope,
+ bgDispatcher = kosmos.testDispatcher,
+ featureFlags = featureFlags,
+ sceneInteractor = kosmos.sceneInteractor,
)
resourceTrimmer.start()
}
@@ -204,6 +210,7 @@
@Test
@EnableFlags(com.android.systemui.Flags.FLAG_TRIM_RESOURCES_WITH_BACKGROUND_TRIM_AT_LOCK)
+ @DisableSceneContainer
fun keyguardTransitionsToGone_trimsFontCache() =
testScope.runTest {
keyguardTransitionRepository.sendTransitionSteps(
@@ -219,6 +226,20 @@
@Test
@EnableFlags(com.android.systemui.Flags.FLAG_TRIM_RESOURCES_WITH_BACKGROUND_TRIM_AT_LOCK)
+ @EnableSceneContainer
+ fun keyguardTransitionsToGone_trimsFontCache_scene_container() =
+ testScope.runTest {
+ kosmos.setSceneTransition(Idle(Scenes.Gone))
+
+ verify(globalWindowManager, times(1))
+ .trimMemory(ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN)
+ verify(globalWindowManager, times(1)).trimCaches(HardwareRenderer.CACHE_TRIM_FONT)
+ verifyNoMoreInteractions(globalWindowManager)
+ }
+
+ @Test
+ @EnableFlags(com.android.systemui.Flags.FLAG_TRIM_RESOURCES_WITH_BACKGROUND_TRIM_AT_LOCK)
+ @DisableSceneContainer
fun keyguardTransitionsToGone_flagDisabled_doesNotTrimFontCache() =
testScope.runTest {
featureFlags.set(Flags.TRIM_FONT_CACHES_AT_UNLOCK, false)
@@ -232,4 +253,18 @@
.trimMemory(ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN)
verify(globalWindowManager, times(0)).trimCaches(any())
}
+
+ @Test
+ @EnableFlags(com.android.systemui.Flags.FLAG_TRIM_RESOURCES_WITH_BACKGROUND_TRIM_AT_LOCK)
+ @EnableSceneContainer
+ fun keyguardTransitionsToGone_flagDisabled_doesNotTrimFontCache_scene_container() =
+ testScope.runTest {
+ featureFlags.set(Flags.TRIM_FONT_CACHES_AT_UNLOCK, false)
+ kosmos.setSceneTransition(Idle(Scenes.Gone))
+
+ // Memory hidden should still be called.
+ verify(globalWindowManager, times(1))
+ .trimMemory(ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN)
+ verify(globalWindowManager, times(0)).trimCaches(any())
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorTest.kt
index 62855d7..974e3bb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorTest.kt
@@ -21,12 +21,18 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.DisableSceneContainer
+import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.DismissAction
import com.android.systemui.keyguard.shared.model.KeyguardDone
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.kosmos.testScope
+import com.android.systemui.scene.data.repository.Idle
+import com.android.systemui.scene.data.repository.setSceneTransition
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -65,10 +71,11 @@
underTest =
KeyguardDismissActionInteractor(
- keyguardRepository,
- kosmos.keyguardTransitionInteractor,
- dismissInteractorWithDependencies.interactor,
- testScope.backgroundScope,
+ repository = keyguardRepository,
+ transitionInteractor = kosmos.keyguardTransitionInteractor,
+ dismissInteractor = dismissInteractorWithDependencies.interactor,
+ applicationScope = testScope.backgroundScope,
+ sceneInteractor = kosmos.sceneInteractor,
)
}
@@ -153,6 +160,7 @@
}
@Test
+ @DisableSceneContainer
fun executeDismissAction_dismissKeyguardRequestWithoutImmediateDismissAction() =
testScope.runTest {
val executeDismissAction by collectLastValue(underTest.executeDismissAction)
@@ -179,6 +187,29 @@
}
@Test
+ @EnableSceneContainer
+ fun executeDismissAction_dismissKeyguardRequestWithoutImmediateDismissAction_scene_container() =
+ testScope.runTest {
+ val executeDismissAction by collectLastValue(underTest.executeDismissAction)
+
+ // WHEN a keyguard action will run after the keyguard is gone
+ val onDismissAction = {}
+ keyguardRepository.setDismissAction(
+ DismissAction.RunAfterKeyguardGone(
+ dismissAction = onDismissAction,
+ onCancelAction = {},
+ message = "message",
+ willAnimateOnLockscreen = true,
+ )
+ )
+ assertThat(executeDismissAction).isNull()
+
+ kosmos.setSceneTransition(Idle(Scenes.Gone))
+
+ assertThat(executeDismissAction).isNotNull()
+ }
+
+ @Test
fun resetDismissAction() =
testScope.runTest {
val resetDismissAction by collectLastValue(underTest.resetDismissAction)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
index ef15d21..00f94a5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
@@ -91,33 +91,40 @@
}
private val testScope = kosmos.testScope
- private val keyguardRepository = kosmos.fakeKeyguardRepository
- private val bouncerRepository = kosmos.fakeKeyguardBouncerRepository
+ private val keyguardRepository by lazy { kosmos.fakeKeyguardRepository }
+ private val bouncerRepository by lazy { kosmos.fakeKeyguardBouncerRepository }
private var commandQueue = kosmos.fakeCommandQueue
private val shadeTestUtil by lazy { kosmos.shadeTestUtil }
- private val transitionRepository = kosmos.fakeKeyguardTransitionRepository
+ private val transitionRepository by lazy { kosmos.fakeKeyguardTransitionRepository }
private lateinit var featureFlags: FakeFeatureFlags
// Used to verify transition requests for test output
@Mock private lateinit var keyguardSecurityModel: KeyguardSecurityModel
- private val fromLockscreenTransitionInteractor = kosmos.fromLockscreenTransitionInteractor
- private val fromDreamingTransitionInteractor = kosmos.fromDreamingTransitionInteractor
- private val fromDozingTransitionInteractor = kosmos.fromDozingTransitionInteractor
- private val fromOccludedTransitionInteractor = kosmos.fromOccludedTransitionInteractor
- private val fromGoneTransitionInteractor = kosmos.fromGoneTransitionInteractor
- private val fromAodTransitionInteractor = kosmos.fromAodTransitionInteractor
- private val fromAlternateBouncerTransitionInteractor =
+ private val fromLockscreenTransitionInteractor by lazy {
+ kosmos.fromLockscreenTransitionInteractor
+ }
+ private val fromDreamingTransitionInteractor by lazy { kosmos.fromDreamingTransitionInteractor }
+ private val fromDozingTransitionInteractor by lazy { kosmos.fromDozingTransitionInteractor }
+ private val fromOccludedTransitionInteractor by lazy { kosmos.fromOccludedTransitionInteractor }
+ private val fromGoneTransitionInteractor by lazy { kosmos.fromGoneTransitionInteractor }
+ private val fromAodTransitionInteractor by lazy { kosmos.fromAodTransitionInteractor }
+ private val fromAlternateBouncerTransitionInteractor by lazy {
kosmos.fromAlternateBouncerTransitionInteractor
- private val fromPrimaryBouncerTransitionInteractor =
+ }
+ private val fromPrimaryBouncerTransitionInteractor by lazy {
kosmos.fromPrimaryBouncerTransitionInteractor
- private val fromDreamingLockscreenHostedTransitionInteractor =
+ }
+ private val fromDreamingLockscreenHostedTransitionInteractor by lazy {
kosmos.fromDreamingLockscreenHostedTransitionInteractor
- private val fromGlanceableHubTransitionInteractor = kosmos.fromGlanceableHubTransitionInteractor
+ }
+ private val fromGlanceableHubTransitionInteractor by lazy {
+ kosmos.fromGlanceableHubTransitionInteractor
+ }
- private val powerInteractor = kosmos.powerInteractor
- private val communalInteractor = kosmos.communalInteractor
- private val dockManager = kosmos.fakeDockManager
+ private val powerInteractor by lazy { kosmos.powerInteractor }
+ private val communalInteractor by lazy { kosmos.communalInteractor }
+ private val dockManager by lazy { kosmos.fakeDockManager }
companion object {
@JvmStatic
@@ -633,7 +640,7 @@
// GIVEN a prior transition has run to DREAMING
keyguardRepository.setDreamingWithOverlay(true)
runTransitionAndSetWakefulness(KeyguardState.LOCKSCREEN, KeyguardState.DREAMING)
- runCurrent()
+ advanceTimeBy(60L)
// WHEN the device wakes up without a keyguard
keyguardRepository.setKeyguardShowing(false)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelTest.kt
index 768d446..40663ce 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelTest.kt
@@ -23,6 +23,7 @@
import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.BrokenWithSceneContainer
import com.android.systemui.flags.andSceneContainer
import com.android.systemui.keyguard.data.repository.fakeKeyguardClockRepository
import com.android.systemui.keyguard.data.repository.keyguardClockRepository
@@ -56,7 +57,7 @@
class KeyguardClockViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
val kosmos = testKosmos()
val testScope = kosmos.testScope
- val underTest = kosmos.keyguardClockViewModel
+ val underTest by lazy { kosmos.keyguardClockViewModel }
val res = context.resources
@Mock lateinit var clockController: ClockController
@@ -96,6 +97,7 @@
}
@Test
+ @BrokenWithSceneContainer(339465026)
fun currentClockLayout_splitShadeOn_clockNotCentered_largeClock_splitShadeLargeClock() =
testScope.runTest {
val currentClockLayout by collectLastValue(underTest.currentClockLayout)
@@ -110,6 +112,7 @@
}
@Test
+ @BrokenWithSceneContainer(339465026)
fun currentClockLayout_splitShadeOn_clockNotCentered_smallClock_splitShadeSmallClock() =
testScope.runTest {
val currentClockLayout by collectLastValue(underTest.currentClockLayout)
@@ -124,6 +127,7 @@
}
@Test
+ @BrokenWithSceneContainer(339465026)
fun currentClockLayout_singleShade_smallClock_smallClock() =
testScope.runTest {
val currentClockLayout by collectLastValue(underTest.currentClockLayout)
@@ -193,6 +197,7 @@
}
@Test
+ @BrokenWithSceneContainer(339465026)
fun testClockSize_dynamicClockSize() =
testScope.runTest {
with(kosmos) {
@@ -216,6 +221,7 @@
}
@Test
+ @BrokenWithSceneContainer(339465026)
fun isLargeClockVisible_whenSmallClockSize_isFalse() =
testScope.runTest {
val value by collectLastValue(underTest.isLargeClockVisible)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerTest.kt
index 05c882e..7856f9b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerTest.kt
@@ -33,6 +33,8 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.dump.DumpManager
+import com.android.systemui.flags.DisableSceneContainer
+import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.flags.Flags
import com.android.systemui.flags.fakeFeatureFlagsClassic
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
@@ -54,7 +56,10 @@
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.qs.PageIndicator
import com.android.systemui.res.R
+import com.android.systemui.scene.data.repository.Idle
+import com.android.systemui.scene.data.repository.setSceneTransition
import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.statusbar.notification.collection.provider.OnReorderingAllowedListener
import com.android.systemui.statusbar.notification.collection.provider.VisualStabilityProvider
import com.android.systemui.statusbar.policy.ConfigurationController
@@ -153,29 +158,30 @@
testDispatcher = UnconfinedTestDispatcher()
mediaCarouselController =
MediaCarouselController(
- context,
- mediaControlPanelFactory,
- visualStabilityProvider,
- mediaHostStatesManager,
- activityStarter,
- clock,
- kosmos.testDispatcher,
- executor,
- bgExecutor,
- testDispatcher,
- mediaDataManager,
- configurationController,
- falsingManager,
- dumpManager,
- logger,
- debugLogger,
- mediaFlags,
- keyguardUpdateMonitor,
- kosmos.keyguardTransitionInteractor,
- globalSettings,
- secureSettings,
- kosmos.mediaCarouselViewModel,
- mediaViewControllerFactory,
+ context = context,
+ mediaControlPanelFactory = mediaControlPanelFactory,
+ visualStabilityProvider = visualStabilityProvider,
+ mediaHostStatesManager = mediaHostStatesManager,
+ activityStarter = activityStarter,
+ systemClock = clock,
+ mainDispatcher = kosmos.testDispatcher,
+ executor = executor,
+ bgExecutor = bgExecutor,
+ backgroundDispatcher = testDispatcher,
+ mediaManager = mediaDataManager,
+ configurationController = configurationController,
+ falsingManager = falsingManager,
+ dumpManager = dumpManager,
+ logger = logger,
+ debugLogger = debugLogger,
+ mediaFlags = mediaFlags,
+ keyguardUpdateMonitor = keyguardUpdateMonitor,
+ keyguardTransitionInteractor = kosmos.keyguardTransitionInteractor,
+ globalSettings = globalSettings,
+ secureSettings = secureSettings,
+ mediaCarouselViewModel = kosmos.mediaCarouselViewModel,
+ mediaViewControllerFactory = mediaViewControllerFactory,
+ sceneInteractor = kosmos.sceneInteractor,
)
verify(configurationController).addCallback(capture(configListener))
verify(mediaDataManager).addListener(capture(listener))
@@ -835,6 +841,7 @@
verify(mediaCarousel).visibility = View.VISIBLE
}
+ @DisableSceneContainer
@ExperimentalCoroutinesApi
@Test
fun testKeyguardGone_showMediaCarousel() =
@@ -858,6 +865,25 @@
job.cancel()
}
+ @EnableSceneContainer
+ @ExperimentalCoroutinesApi
+ @Test
+ fun testKeyguardGone_showMediaCarousel_scene_container() =
+ kosmos.testScope.runTest {
+ kosmos.fakeFeatureFlagsClassic.set(Flags.MEDIA_RETAIN_RECOMMENDATIONS, false)
+ var updatedVisibility = false
+ mediaCarouselController.updateHostVisibility = { updatedVisibility = true }
+ mediaCarouselController.mediaCarousel = mediaCarousel
+
+ val job = mediaCarouselController.listenForAnyStateToGoneKeyguardTransition(this)
+ kosmos.setSceneTransition(Idle(Scenes.Gone))
+
+ verify(mediaCarousel).visibility = View.VISIBLE
+ assertEquals(true, updatedVisibility)
+
+ job.cancel()
+ }
+
@ExperimentalCoroutinesApi
@Test
fun keyguardShowing_notAllowedOnLockscreen_updateVisibility() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogReceiverTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogReceiverTest.java
index 3b6a88a..5dbfe47 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogReceiverTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogReceiverTest.java
@@ -25,6 +25,8 @@
import static org.mockito.Mockito.verify;
import android.content.Intent;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
import android.testing.AndroidTestingRunner;
import androidx.test.filters.SmallTest;
@@ -91,8 +93,8 @@
}
@Test
+ @DisableFlags(Flags.FLAG_LEGACY_LE_AUDIO_SHARING)
public void launchMediaOutputBroadcastDialog_flagOff_broadcastDialogFactoryNotCalled() {
- mSetFlagsRule.disableFlags(Flags.FLAG_LEGACY_LE_AUDIO_SHARING);
Intent intent = new Intent(
MediaOutputConstants.ACTION_LAUNCH_MEDIA_OUTPUT_BROADCAST_DIALOG);
intent.putExtra(MediaOutputConstants.EXTRA_PACKAGE_NAME, getContext().getPackageName());
@@ -105,8 +107,8 @@
}
@Test
+ @EnableFlags(Flags.FLAG_LEGACY_LE_AUDIO_SHARING)
public void launchMediaOutputBroadcastDialog_ExtraPackageName_BroadcastDialogFactoryCalled() {
- mSetFlagsRule.enableFlags(Flags.FLAG_LEGACY_LE_AUDIO_SHARING);
Intent intent = new Intent(
MediaOutputConstants.ACTION_LAUNCH_MEDIA_OUTPUT_BROADCAST_DIALOG);
intent.putExtra(MediaOutputConstants.EXTRA_PACKAGE_NAME, getContext().getPackageName());
@@ -119,8 +121,8 @@
}
@Test
+ @EnableFlags(Flags.FLAG_LEGACY_LE_AUDIO_SHARING)
public void launchMediaOutputBroadcastDialog_WrongExtraKey_DialogBroadcastFactoryNotCalled() {
- mSetFlagsRule.enableFlags(Flags.FLAG_LEGACY_LE_AUDIO_SHARING);
Intent intent = new Intent(
MediaOutputConstants.ACTION_LAUNCH_MEDIA_OUTPUT_BROADCAST_DIALOG);
intent.putExtra("Wrong Package Name Key", getContext().getPackageName());
@@ -133,8 +135,8 @@
}
@Test
+ @EnableFlags(Flags.FLAG_LEGACY_LE_AUDIO_SHARING)
public void launchMediaOutputBroadcastDialog_NoExtra_BroadcastDialogFactoryNotCalled() {
- mSetFlagsRule.enableFlags(Flags.FLAG_LEGACY_LE_AUDIO_SHARING);
Intent intent = new Intent(
MediaOutputConstants.ACTION_LAUNCH_MEDIA_OUTPUT_BROADCAST_DIALOG);
mMediaOutputDialogReceiver.onReceive(getContext(), intent);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleSpaceWidgetManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleSpaceWidgetManagerTest.java
index 890e1e0..0998c0c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleSpaceWidgetManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleSpaceWidgetManagerTest.java
@@ -94,6 +94,8 @@
import android.os.Bundle;
import android.os.UserHandle;
import android.os.UserManager;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
import android.service.notification.ConversationChannelWrapper;
import android.service.notification.StatusBarNotification;
import android.service.notification.ZenModeConfig;
@@ -1576,17 +1578,19 @@
}
@Test
+ @DisableFlags({
+ android.appwidget.flags.Flags.FLAG_GENERATED_PREVIEWS,
+ android.appwidget.flags.Flags.FLAG_DRAW_DATA_PARCEL
+ })
public void testUpdateGeneratedPreview_flagDisabled() {
- mSetFlagsRule.disableFlags(android.appwidget.flags.Flags.FLAG_GENERATED_PREVIEWS);
- mSetFlagsRule.disableFlags(android.appwidget.flags.Flags.FLAG_DRAW_DATA_PARCEL);
mManager.updateGeneratedPreviewForUser(mUserTracker.getUserHandle());
verify(mAppWidgetManager, times(0)).setWidgetPreview(any(), anyInt(), any());
}
@Test
+ @EnableFlags(android.appwidget.flags.Flags.FLAG_GENERATED_PREVIEWS)
+ @DisableFlags(android.appwidget.flags.Flags.FLAG_DRAW_DATA_PARCEL)
public void testUpdateGeneratedPreview_userLocked() {
- mSetFlagsRule.enableFlags(android.appwidget.flags.Flags.FLAG_GENERATED_PREVIEWS);
- mSetFlagsRule.disableFlags(android.appwidget.flags.Flags.FLAG_DRAW_DATA_PARCEL);
when(mUserManager.isUserUnlocked(mUserTracker.getUserHandle())).thenReturn(false);
mManager.updateGeneratedPreviewForUser(mUserTracker.getUserHandle());
@@ -1594,9 +1598,9 @@
}
@Test
+ @EnableFlags(android.appwidget.flags.Flags.FLAG_GENERATED_PREVIEWS)
+ @DisableFlags(android.appwidget.flags.Flags.FLAG_DRAW_DATA_PARCEL)
public void testUpdateGeneratedPreview_userUnlocked() {
- mSetFlagsRule.enableFlags(android.appwidget.flags.Flags.FLAG_GENERATED_PREVIEWS);
- mSetFlagsRule.disableFlags(android.appwidget.flags.Flags.FLAG_DRAW_DATA_PARCEL);
when(mUserManager.isUserUnlocked(mUserTracker.getUserHandle())).thenReturn(true);
when(mAppWidgetManager.setWidgetPreview(any(), anyInt(), any())).thenReturn(true);
@@ -1605,9 +1609,9 @@
}
@Test
+ @EnableFlags(android.appwidget.flags.Flags.FLAG_GENERATED_PREVIEWS)
+ @DisableFlags(android.appwidget.flags.Flags.FLAG_DRAW_DATA_PARCEL)
public void testUpdateGeneratedPreview_doesNotSetTwice() {
- mSetFlagsRule.enableFlags(android.appwidget.flags.Flags.FLAG_GENERATED_PREVIEWS);
- mSetFlagsRule.disableFlags(android.appwidget.flags.Flags.FLAG_DRAW_DATA_PARCEL);
when(mUserManager.isUserUnlocked(mUserTracker.getUserHandle())).thenReturn(true);
when(mAppWidgetManager.setWidgetPreview(any(), anyInt(), any())).thenReturn(true);
@@ -1617,9 +1621,11 @@
}
@Test
+ @EnableFlags({
+ android.appwidget.flags.Flags.FLAG_GENERATED_PREVIEWS,
+ android.appwidget.flags.Flags.FLAG_DRAW_DATA_PARCEL
+ })
public void testUpdateGeneratedPreviewWithDataParcel_userLocked() throws InterruptedException {
- mSetFlagsRule.enableFlags(android.appwidget.flags.Flags.FLAG_GENERATED_PREVIEWS);
- mSetFlagsRule.enableFlags(android.appwidget.flags.Flags.FLAG_DRAW_DATA_PARCEL);
when(mUserManager.isUserUnlocked(mUserTracker.getUserHandle())).thenReturn(false);
mManager.updateGeneratedPreviewForUser(mUserTracker.getUserHandle());
@@ -1628,10 +1634,12 @@
}
@Test
+ @EnableFlags({
+ android.appwidget.flags.Flags.FLAG_GENERATED_PREVIEWS,
+ android.appwidget.flags.Flags.FLAG_DRAW_DATA_PARCEL
+ })
public void testUpdateGeneratedPreviewWithDataParcel_userUnlocked()
throws InterruptedException {
- mSetFlagsRule.enableFlags(android.appwidget.flags.Flags.FLAG_GENERATED_PREVIEWS);
- mSetFlagsRule.enableFlags(android.appwidget.flags.Flags.FLAG_DRAW_DATA_PARCEL);
when(mUserManager.isUserUnlocked(mUserTracker.getUserHandle())).thenReturn(true);
when(mAppWidgetManager.setWidgetPreview(any(), anyInt(), any())).thenReturn(true);
@@ -1641,10 +1649,12 @@
}
@Test
+ @EnableFlags({
+ android.appwidget.flags.Flags.FLAG_GENERATED_PREVIEWS,
+ android.appwidget.flags.Flags.FLAG_DRAW_DATA_PARCEL
+ })
public void testUpdateGeneratedPreviewWithDataParcel_doesNotSetTwice()
throws InterruptedException {
- mSetFlagsRule.enableFlags(android.appwidget.flags.Flags.FLAG_GENERATED_PREVIEWS);
- mSetFlagsRule.enableFlags(android.appwidget.flags.Flags.FLAG_DRAW_DATA_PARCEL);
when(mUserManager.isUserUnlocked(mUserTracker.getUserHandle())).thenReturn(true);
when(mAppWidgetManager.setWidgetPreview(any(), anyInt(), any())).thenReturn(true);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/TileStateToProtoTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/TileStateToProtoTest.kt
index 629c663..bc947fb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/TileStateToProtoTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/TileStateToProtoTest.kt
@@ -3,6 +3,7 @@
import android.content.ComponentName
import android.service.quicksettings.Tile
import android.testing.AndroidTestingRunner
+import android.widget.Switch
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.plugins.qs.QSTile
@@ -66,15 +67,19 @@
assertThat(proto?.hasBooleanState()).isFalse()
}
+ /**
+ * The [QSTile.AdapterState.expandedAccessibilityClassName] setting to [Switch] results in the
+ * proto having a booleanState. The value of that boolean is true iff the tile is active.
+ */
@Test
- fun booleanState_ACTIVE() {
+ fun adapterState_ACTIVE() {
val state =
- QSTile.BooleanState().apply {
+ QSTile.AdapterState().apply {
spec = TEST_SPEC
label = TEST_LABEL
secondaryLabel = TEST_SUBTITLE
state = Tile.STATE_ACTIVE
- value = true
+ expandedAccessibilityClassName = Switch::class.java.name
}
val proto = state.toProto()
@@ -89,6 +94,33 @@
assertThat(proto?.booleanState).isTrue()
}
+ /**
+ * Similar to [adapterState_ACTIVE], the use of
+ * [QSTile.AdapterState.expandedAccessibilityClassName] signals that the tile is toggleable.
+ */
+ @Test
+ fun adapterState_INACTIVE() {
+ val state =
+ QSTile.AdapterState().apply {
+ spec = TEST_SPEC
+ label = TEST_LABEL
+ secondaryLabel = TEST_SUBTITLE
+ state = Tile.STATE_INACTIVE
+ expandedAccessibilityClassName = Switch::class.java.name
+ }
+ val proto = state.toProto()
+
+ assertThat(proto).isNotNull()
+ assertThat(proto?.hasSpec()).isTrue()
+ assertThat(proto?.spec).isEqualTo(TEST_SPEC)
+ assertThat(proto?.hasComponentName()).isFalse()
+ assertThat(proto?.label).isEqualTo(TEST_LABEL)
+ assertThat(proto?.secondaryLabel).isEqualTo(TEST_SUBTITLE)
+ assertThat(proto?.state).isEqualTo(Tile.STATE_INACTIVE)
+ assertThat(proto?.hasBooleanState()).isTrue()
+ assertThat(proto?.booleanState).isFalse()
+ }
+
@Test
fun noSpec_returnsNull() {
val state =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/panels/domain/interactor/GridConsistencyInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/domain/interactor/GridConsistencyInteractorTest.kt
index db752dd..d15cfbf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/panels/domain/interactor/GridConsistencyInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/domain/interactor/GridConsistencyInteractorTest.kt
@@ -20,7 +20,6 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.kosmos.testScope
-import com.android.systemui.qs.panels.data.repository.GridLayoutTypeRepository
import com.android.systemui.qs.panels.data.repository.IconTilesRepository
import com.android.systemui.qs.panels.data.repository.gridLayoutTypeRepository
import com.android.systemui.qs.panels.data.repository.iconTilesRepository
@@ -48,9 +47,6 @@
data object TestGridLayoutType : GridLayoutType
- private val gridLayout: MutableStateFlow<GridLayoutType> =
- MutableStateFlow(InfiniteGridLayoutType)
-
private val iconOnlyTiles =
MutableStateFlow(
setOf(
@@ -74,17 +70,13 @@
Pair(InfiniteGridLayoutType, infiniteGridConsistencyInteractor),
Pair(TestGridLayoutType, noopGridConsistencyInteractor)
)
- gridLayoutTypeRepository =
- object : GridLayoutTypeRepository {
- override val layout: StateFlow<GridLayoutType> = gridLayout.asStateFlow()
- }
}
private val underTest = with(kosmos) { gridConsistencyInteractor }
@Before
fun setUp() {
- gridLayout.value = InfiniteGridLayoutType
+ with(kosmos) { gridLayoutTypeRepository.setLayout(InfiniteGridLayoutType) }
underTest.start()
}
@@ -94,7 +86,7 @@
with(kosmos) {
testScope.runTest {
// Using the no-op grid consistency interactor
- gridLayout.value = TestGridLayoutType
+ gridLayoutTypeRepository.setLayout(TestGridLayoutType)
// Setting an invalid layout with holes
// [ Large A ] [ sa ]
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/AirplaneModeTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/AirplaneModeTileTest.kt
index e2a3fac6..ad87315 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/AirplaneModeTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/AirplaneModeTileTest.kt
@@ -22,7 +22,7 @@
import android.testing.TestableLooper
import androidx.test.filters.SmallTest
import com.android.internal.logging.MetricsLogger
-import com.android.systemui.res.R
+import com.android.internal.telephony.flags.Flags
import com.android.systemui.SysuiTestCase
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.classifier.FalsingManagerFake
@@ -33,10 +33,12 @@
import com.android.systemui.qs.QsEventLogger
import com.android.systemui.qs.logging.QSLogger
import com.android.systemui.qs.tileimpl.QSTileImpl
+import com.android.systemui.res.R
import com.android.systemui.settings.UserTracker
import com.android.systemui.util.settings.GlobalSettings
import com.google.common.truth.Truth.assertThat
import dagger.Lazy
+import kotlinx.coroutines.Job
import org.junit.After
import org.junit.Before
import org.junit.Test
@@ -44,11 +46,15 @@
import org.mockito.Mock
import org.mockito.Mockito
import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.any
+import org.mockito.kotlin.times
+import org.mockito.kotlin.verify
@RunWith(AndroidTestingRunner::class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
@SmallTest
class AirplaneModeTileTest : SysuiTestCase() {
+
@Mock
private lateinit var mHost: QSHost
@Mock
@@ -62,7 +68,9 @@
@Mock
private lateinit var mBroadcastDispatcher: BroadcastDispatcher
@Mock
- private lateinit var mConnectivityManager: Lazy<ConnectivityManager>
+ private lateinit var mLazyConnectivityManager: Lazy<ConnectivityManager>
+ @Mock
+ private lateinit var mConnectivityManager: ConnectivityManager
@Mock
private lateinit var mGlobalSettings: GlobalSettings
@Mock
@@ -72,13 +80,15 @@
private lateinit var mTestableLooper: TestableLooper
private lateinit var mTile: AirplaneModeTile
+ @Mock
+ private lateinit var mClickJob: Job
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
mTestableLooper = TestableLooper.get(this)
Mockito.`when`(mHost.context).thenReturn(mContext)
Mockito.`when`(mHost.userContext).thenReturn(mContext)
-
+ Mockito.`when`(mLazyConnectivityManager.get()).thenReturn(mConnectivityManager)
mTile = AirplaneModeTile(
mHost,
mUiEventLogger,
@@ -90,7 +100,7 @@
mActivityStarter,
mQsLogger,
mBroadcastDispatcher,
- mConnectivityManager,
+ mLazyConnectivityManager,
mGlobalSettings,
mUserTracker)
}
@@ -120,4 +130,24 @@
assertThat(state.icon)
.isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_airplane_icon_on))
}
+
+ @Test
+ fun handleClick_noSatelliteFeature_directSetAirplaneMode() {
+ mSetFlagsRule.disableFlags(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+
+ mTile.handleClick(null)
+
+ verify(mConnectivityManager).setAirplaneMode(any())
+ }
+
+ @Test
+ fun handleClick_hasSatelliteFeatureButClickIsProcessing_doNothing() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+ Mockito.`when`(mClickJob.isCompleted).thenReturn(false)
+ mTile.mClickJob = mClickJob
+
+ mTile.handleClick(null)
+
+ verify(mConnectivityManager, times(0)).setAirplaneMode(any())
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt
index 830f08a..1ffbb7b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt
@@ -9,10 +9,11 @@
import android.testing.TestableLooper.RunWithLooper
import androidx.test.filters.SmallTest
import com.android.internal.logging.MetricsLogger
+import com.android.internal.telephony.flags.Flags
import com.android.settingslib.Utils
import com.android.settingslib.bluetooth.CachedBluetoothDevice
-import com.android.systemui.res.R
import com.android.systemui.SysuiTestCase
+import com.android.systemui.bluetooth.qsdialog.BluetoothTileDialogViewModel
import com.android.systemui.classifier.FalsingManagerFake
import com.android.systemui.flags.FeatureFlagsClassic
import com.android.systemui.plugins.ActivityStarter
@@ -23,13 +24,14 @@
import com.android.systemui.qs.QsEventLogger
import com.android.systemui.qs.logging.QSLogger
import com.android.systemui.qs.tileimpl.QSTileImpl
-import com.android.systemui.bluetooth.qsdialog.BluetoothTileDialogViewModel
+import com.android.systemui.res.R
import com.android.systemui.statusbar.policy.BluetoothController
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.Job
import org.junit.After
import org.junit.Before
import org.junit.Test
@@ -37,6 +39,7 @@
import org.mockito.Mock
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
@RunWith(AndroidTestingRunner::class)
@@ -54,7 +57,7 @@
@Mock private lateinit var uiEventLogger: QsEventLogger
@Mock private lateinit var featureFlags: FeatureFlagsClassic
@Mock private lateinit var bluetoothTileDialogViewModel: BluetoothTileDialogViewModel
-
+ @Mock private lateinit var clickJob: Job
private lateinit var testableLooper: TestableLooper
private lateinit var tile: FakeBluetoothTile
@@ -191,6 +194,41 @@
}
@Test
+ fun handleClick_hasSatelliteFeatureButNoQsTileDialogAndClickIsProcessing_doNothing() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+ `when`(featureFlags.isEnabled(com.android.systemui.flags.Flags.BLUETOOTH_QS_TILE_DIALOG))
+ .thenReturn(false)
+ `when`(clickJob.isCompleted).thenReturn(false)
+ tile.mClickJob = clickJob
+
+ tile.handleClick(null)
+
+ verify(bluetoothController, times(0)).setBluetoothEnabled(any())
+ }
+
+ @Test
+ fun handleClick_noSatelliteFeatureAndNoQsTileDialog_directSetBtEnable() {
+ mSetFlagsRule.disableFlags(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+ `when`(featureFlags.isEnabled(com.android.systemui.flags.Flags.BLUETOOTH_QS_TILE_DIALOG))
+ .thenReturn(false)
+
+ tile.handleClick(null)
+
+ verify(bluetoothController).setBluetoothEnabled(any())
+ }
+
+ @Test
+ fun handleClick_noSatelliteFeatureButHasQsTileDialog_showDialog() {
+ mSetFlagsRule.disableFlags(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+ `when`(featureFlags.isEnabled(com.android.systemui.flags.Flags.BLUETOOTH_QS_TILE_DIALOG))
+ .thenReturn(true)
+
+ tile.handleClick(null)
+
+ verify(bluetoothTileDialogViewModel).showDialog(null)
+ }
+
+ @Test
fun testMetadataListener_whenDisconnected_isUnregistered() {
val state = QSTile.BooleanState()
val cachedDevice = mock<CachedBluetoothDevice>()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt
index 81d0e06..2c2fcbe 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt
@@ -16,6 +16,8 @@
package com.android.systemui.shade
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.view.View
@@ -26,8 +28,8 @@
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet
import androidx.test.filters.SmallTest
-import com.android.systemui.Flags as AConfigFlags
import com.android.systemui.Flags.FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX
+import com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT
import com.android.systemui.SysuiTestCase
import com.android.systemui.fragments.FragmentHostManager
import com.android.systemui.fragments.FragmentService
@@ -164,10 +166,10 @@
}
@Test
+ @DisableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX)
fun testLargeScreen_updateResources_refactorFlagOff_splitShadeHeightIsSetBasedOnResource() {
val headerResourceHeight = 20
val headerHelperHeight = 30
- mSetFlagsRule.disableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX)
whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight())
.thenReturn(headerHelperHeight)
overrideResource(R.bool.config_use_large_screen_shade_header, true)
@@ -187,10 +189,10 @@
}
@Test
+ @EnableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX)
fun testLargeScreen_updateResources_refactorFlagOn_splitShadeHeightIsSetBasedOnHelper() {
val headerResourceHeight = 20
val headerHelperHeight = 30
- mSetFlagsRule.enableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX)
whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight())
.thenReturn(headerHelperHeight)
overrideResource(R.bool.config_use_large_screen_shade_header, true)
@@ -400,8 +402,8 @@
}
@Test
+ @DisableFlags(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
fun testSplitShadeLayout_isAlignedToGuideline() {
- mSetFlagsRule.disableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
enableSplitShade()
underTest.updateResources()
assertThat(getConstraintSetLayout(R.id.qs_frame).endToEnd).isEqualTo(R.id.qs_edge_guideline)
@@ -410,8 +412,8 @@
}
@Test
+ @DisableFlags(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
fun testSinglePaneLayout_childrenHaveEqualMargins() {
- mSetFlagsRule.disableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
disableSplitShade()
underTest.updateResources()
val qsStartMargin = getConstraintSetLayout(R.id.qs_frame).startMargin
@@ -427,8 +429,8 @@
}
@Test
+ @DisableFlags(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
fun testSplitShadeLayout_childrenHaveInsideMarginsOfZero() {
- mSetFlagsRule.disableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
enableSplitShade()
underTest.updateResources()
assertThat(getConstraintSetLayout(R.id.qs_frame).endMargin).isEqualTo(0)
@@ -445,9 +447,8 @@
}
@Test
+ @DisableFlags(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT, FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX)
fun testLargeScreenLayout_refactorFlagOff_qsAndNotifsTopMarginIsOfHeaderHeightResource() {
- mSetFlagsRule.disableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
- mSetFlagsRule.disableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX)
setLargeScreen()
val largeScreenHeaderResourceHeight = 100
val largeScreenHeaderHelperHeight = 200
@@ -468,9 +469,9 @@
}
@Test
+ @DisableFlags(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
+ @EnableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX)
fun testLargeScreenLayout_refactorFlagOn_qsAndNotifsTopMarginIsOfHeaderHeightHelper() {
- mSetFlagsRule.disableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
- mSetFlagsRule.enableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX)
setLargeScreen()
val largeScreenHeaderResourceHeight = 100
val largeScreenHeaderHelperHeight = 200
@@ -491,8 +492,8 @@
}
@Test
+ @DisableFlags(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
fun testSmallScreenLayout_qsAndNotifsTopMarginIsZero() {
- mSetFlagsRule.disableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
setSmallScreen()
underTest.updateResources()
assertThat(getConstraintSetLayout(R.id.qs_frame).topMargin).isEqualTo(0)
@@ -512,8 +513,8 @@
}
@Test
+ @DisableFlags(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
fun testSinglePaneShadeLayout_isAlignedToParent() {
- mSetFlagsRule.disableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
disableSplitShade()
underTest.updateResources()
assertThat(getConstraintSetLayout(R.id.qs_frame).endToEnd)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt
index 4ae751b..f21def3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt
@@ -16,6 +16,8 @@
package com.android.systemui.shade
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.view.View
@@ -27,6 +29,7 @@
import androidx.constraintlayout.widget.ConstraintSet
import androidx.test.filters.SmallTest
import com.android.systemui.Flags.FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX
+import com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT
import com.android.systemui.SysuiTestCase
import com.android.systemui.fragments.FragmentHostManager
import com.android.systemui.fragments.FragmentService
@@ -67,6 +70,7 @@
@RunWith(AndroidTestingRunner::class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
@SmallTest
+@EnableFlags(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
class NotificationsQSContainerControllerTest : SysuiTestCase() {
private val view = mock<NotificationsQuickSettingsContainer>()
@@ -99,7 +103,6 @@
MockitoAnnotations.initMocks(this)
fakeSystemClock = FakeSystemClock()
delayableExecutor = FakeExecutor(fakeSystemClock)
- mSetFlagsRule.enableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
mContext.ensureTestableResources()
whenever(view.context).thenReturn(mContext)
whenever(view.resources).thenReturn(mContext.resources)
@@ -161,8 +164,8 @@
}
@Test
+ @DisableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX)
fun testLargeScreen_updateResources_refactorFlagOff_splitShadeHeightIsSet_basedOnResource() {
- mSetFlagsRule.disableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX)
val helperHeight = 30
val resourceHeight = 20
whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight()).thenReturn(helperHeight)
@@ -182,8 +185,8 @@
}
@Test
+ @EnableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX)
fun testLargeScreen_updateResources_refactorFlagOn_splitShadeHeightIsSet_basedOnHelper() {
- mSetFlagsRule.enableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX)
val helperHeight = 30
val resourceHeight = 20
whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight()).thenReturn(helperHeight)
@@ -424,8 +427,8 @@
}
@Test
+ @DisableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX)
fun testLargeScreenLayout_refactorFlagOff_qsAndNotifsTopMarginIsOfHeaderResourceHeight() {
- mSetFlagsRule.disableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX)
setLargeScreen()
val largeScreenHeaderHelperHeight = 200
val largeScreenHeaderResourceHeight = 100
@@ -444,8 +447,8 @@
}
@Test
+ @EnableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX)
fun testLargeScreenLayout_refactorFlagOn_qsAndNotifsTopMarginIsOfHeaderHelperHeight() {
- mSetFlagsRule.enableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX)
setLargeScreen()
val largeScreenHeaderHelperHeight = 200
val largeScreenHeaderResourceHeight = 100
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallBackgroundContainerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/view/ChipBackgroundContainerTest.kt
similarity index 86%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallBackgroundContainerTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/view/ChipBackgroundContainerTest.kt
index f0a457e..7d2b463 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallBackgroundContainerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/view/ChipBackgroundContainerTest.kt
@@ -14,15 +14,15 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.phone.ongoingcall
+package com.android.systemui.statusbar.chips.ui.view
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.view.LayoutInflater
import android.view.View
import androidx.test.filters.SmallTest
-import com.android.systemui.res.R
import com.android.systemui.SysuiTestCase
+import com.android.systemui.res.R
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
@@ -31,16 +31,17 @@
@SmallTest
@RunWith(AndroidTestingRunner::class)
@TestableLooper.RunWithLooper
-class OngoingCallBackgroundContainerTest : SysuiTestCase() {
+class ChipBackgroundContainerTest : SysuiTestCase() {
- private lateinit var underTest: OngoingCallBackgroundContainer
+ private lateinit var underTest: ChipBackgroundContainer
@Before
fun setUp() {
allowTestableLooperAsMainThread()
TestableLooper.get(this).runWithLooper {
- val chipView = LayoutInflater.from(context).inflate(R.layout.ongoing_call_chip, null)
- underTest = chipView.requireViewById(R.id.ongoing_call_chip_background)
+ val chipView =
+ LayoutInflater.from(context).inflate(R.layout.ongoing_activity_chip, null)
+ underTest = chipView.requireViewById(R.id.ongoing_activity_chip_background)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallChronometerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/view/ChipChronometerTest.kt
similarity index 89%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallChronometerTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/view/ChipChronometerTest.kt
index 7e25aa3..b8d4e47 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallChronometerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/view/ChipChronometerTest.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,15 +14,15 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.phone.ongoingcall
+package com.android.systemui.statusbar.chips.ui.view
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.view.LayoutInflater
import android.view.View
import androidx.test.filters.SmallTest
-import com.android.systemui.res.R
import com.android.systemui.SysuiTestCase
+import com.android.systemui.res.R
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
@@ -38,17 +38,18 @@
@SmallTest
@RunWith(AndroidTestingRunner::class)
@TestableLooper.RunWithLooper
-class OngoingCallChronometerTest : SysuiTestCase() {
+class ChipChronometerTest : SysuiTestCase() {
- private lateinit var textView: OngoingCallChronometer
+ private lateinit var textView: ChipChronometer
private lateinit var doesNotFitText: String
@Before
fun setUp() {
allowTestableLooperAsMainThread()
TestableLooper.get(this).runWithLooper {
- val chipView = LayoutInflater.from(mContext).inflate(R.layout.ongoing_call_chip, null)
- textView = chipView.findViewById(R.id.ongoing_call_chip_time)!!
+ val chipView =
+ LayoutInflater.from(mContext).inflate(R.layout.ongoing_activity_chip, null)
+ textView = chipView.findViewById(R.id.ongoing_activity_chip_time)!!
measureTextView()
calculateDoesNotFixText()
}
@@ -159,8 +160,8 @@
private fun measureTextView() {
textView.measure(
- View.MeasureSpec.makeMeasureSpec(TEXT_VIEW_MAX_WIDTH, View.MeasureSpec.AT_MOST),
- View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
+ View.MeasureSpec.makeMeasureSpec(TEXT_VIEW_MAX_WIDTH, View.MeasureSpec.AT_MOST),
+ View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt
index 745d20d..1eed420 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt
@@ -1,5 +1,6 @@
package com.android.systemui.statusbar.notification.stack
+import android.platform.test.annotations.DisableFlags
import android.service.notification.StatusBarNotification
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
@@ -69,8 +70,8 @@
}
@Test
+ @DisableFlags(NotificationIconContainerRefactor.FLAG_NAME)
fun testShadeWidth_BasedOnFractionToShade() {
- mSetFlagsRule.disableFlags(NotificationIconContainerRefactor.FLAG_NAME)
setFractionToShade(0f)
setOnLockscreen(true)
@@ -85,8 +86,8 @@
}
@Test
+ @DisableFlags(NotificationIconContainerRefactor.FLAG_NAME)
fun testShelfIsLong_WhenNotOnLockscreen() {
- mSetFlagsRule.disableFlags(NotificationIconContainerRefactor.FLAG_NAME)
setFractionToShade(0f)
setOnLockscreen(false)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
index b9312d3..4488799 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
@@ -21,6 +21,7 @@
import static android.provider.Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED;
import static android.provider.Settings.Global.HEADS_UP_ON;
+import static com.android.systemui.Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR;
import static com.android.systemui.Flags.FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE;
import static com.android.systemui.Flags.FLAG_LIGHT_REVEAL_MIGRATION;
import static com.android.systemui.flags.Flags.SHORTCUT_LIST_SEARCH_LAYOUT;
@@ -70,6 +71,8 @@
import android.os.Looper;
import android.os.PowerManager;
import android.os.UserHandle;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
import android.service.dreams.IDreamManager;
import android.support.test.metricshelper.MetricsAsserts;
import android.testing.AndroidTestingRunner;
@@ -105,8 +108,6 @@
import com.android.systemui.classifier.FalsingCollectorFake;
import com.android.systemui.classifier.FalsingManagerFake;
import com.android.systemui.colorextraction.SysuiColorExtractor;
-import com.android.systemui.communal.data.repository.CommunalRepository;
-import com.android.systemui.communal.domain.interactor.CommunalInteractor;
import com.android.systemui.communal.shared.model.CommunalScenes;
import com.android.systemui.demomode.DemoModeController;
import com.android.systemui.dump.DumpManager;
@@ -224,6 +225,7 @@
@SmallTest
@RunWith(AndroidTestingRunner.class)
@RunWithLooper(setAsMainLooper = true)
+@EnableFlags(FLAG_LIGHT_REVEAL_MIGRATION)
public class CentralSurfacesImplTest extends SysuiTestCase {
private static final int FOLD_STATE_FOLDED = 0;
@@ -238,8 +240,6 @@
private final TestScope mTestScope = mKosmos.getTestScope();
- private final CommunalInteractor mCommunalInteractor = mKosmos.getCommunalInteractor();
- private final CommunalRepository mCommunalRepository = mKosmos.getCommunalRepository();
@Mock private NotificationsController mNotificationsController;
@Mock private LightBarController mLightBarController;
@Mock private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
@@ -362,13 +362,9 @@
// Set default value to avoid IllegalStateException.
mFeatureFlags.set(SHORTCUT_LIST_SEARCH_LAYOUT, false);
- mSetFlagsRule.enableFlags(FLAG_LIGHT_REVEAL_MIGRATION);
// Turn AOD on and toggle feature flag for jank fixes
mFeatureFlags.set(Flags.ZJ_285570694_LOCKSCREEN_TRANSITION_FROM_AOD, true);
when(mDozeParameters.getAlwaysOn()).thenReturn(true);
- if (!SceneContainerFlag.isEnabled()) {
- mSetFlagsRule.disableFlags(com.android.systemui.Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR);
- }
IThermalService thermalService = mock(IThermalService.class);
mPowerManager = new PowerManager(mContext, mPowerManagerService, thermalService,
@@ -528,7 +524,7 @@
mScreenLifecycle,
mWakefulnessLifecycle,
mPowerInteractor,
- mCommunalInteractor,
+ mKosmos.getCommunalInteractor(),
mStatusBarStateController,
Optional.of(mBubbles),
() -> mNoteTaskController,
@@ -837,6 +833,7 @@
}
@Test
+ @DisableFlags(FLAG_DEVICE_ENTRY_UDFPS_REFACTOR)
public void testSetDozingNotUnlocking_transitionToAuthScrimmed_cancelKeyguardFadingAway() {
when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true);
when(mKeyguardStateController.isKeyguardFadingAway()).thenReturn(true);
@@ -848,7 +845,8 @@
}
@Test
- public void testOccludingQSNotExpanded_transitionToAuthScrimmed() {
+ @DisableFlags(FLAG_DEVICE_ENTRY_UDFPS_REFACTOR)
+ public void testOccludingQSNotExpanded_flagOff_transitionToAuthScrimmed() {
when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true);
// GIVEN device occluded and panel is NOT expanded
@@ -862,6 +860,39 @@
}
@Test
+ @EnableFlags(FLAG_DEVICE_ENTRY_UDFPS_REFACTOR)
+ public void testNotOccluding_QSNotExpanded_flagOn_doesNotTransitionScrimState() {
+ when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true);
+
+ // GIVEN device occluded and panel is NOT expanded
+ mCentralSurfaces.setBarStateForTest(SHADE);
+ when(mKeyguardStateController.isOccluded()).thenReturn(false);
+ when(mNotificationPanelViewController.isPanelExpanded()).thenReturn(false);
+
+ mCentralSurfaces.updateScrimController();
+
+ // Tests the safeguard to reset the scrimstate
+ verify(mScrimController, never()).transitionTo(any());
+ }
+
+ @Test
+ @EnableFlags(FLAG_DEVICE_ENTRY_UDFPS_REFACTOR)
+ public void testNotOccluding_QSExpanded_flagOn_doesTransitionScrimStateToKeyguard() {
+ when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true);
+
+ // GIVEN device occluded and panel is NOT expanded
+ mCentralSurfaces.setBarStateForTest(SHADE);
+ when(mKeyguardStateController.isOccluded()).thenReturn(false);
+ when(mNotificationPanelViewController.isPanelExpanded()).thenReturn(true);
+
+ mCentralSurfaces.updateScrimController();
+
+ // Tests the safeguard to reset the scrimstate
+ verify(mScrimController, never()).transitionTo(eq(ScrimState.KEYGUARD));
+ }
+
+ @Test
+ @DisableFlags(FLAG_DEVICE_ENTRY_UDFPS_REFACTOR)
public void testOccludingQSExpanded_transitionToAuthScrimmedShade() {
when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true);
@@ -878,16 +909,18 @@
@Test
public void testEnteringGlanceableHub_updatesScrim() {
// Transition to the glanceable hub.
- mCommunalRepository.setTransitionState(flowOf(new ObservableTransitionState.Idle(
- CommunalScenes.Communal)));
+ mKosmos.getCommunalRepository()
+ .setTransitionState(
+ flowOf(new ObservableTransitionState.Idle(CommunalScenes.Communal)));
mTestScope.getTestScheduler().runCurrent();
// ScrimState also transitions.
verify(mScrimController).transitionTo(ScrimState.GLANCEABLE_HUB);
// Transition away from the glanceable hub.
- mCommunalRepository.setTransitionState(flowOf(new ObservableTransitionState.Idle(
- CommunalScenes.Blank)));
+ mKosmos.getCommunalRepository()
+ .setTransitionState(
+ flowOf(new ObservableTransitionState.Idle(CommunalScenes.Blank)));
mTestScope.getTestScheduler().runCurrent();
// ScrimState goes back to UNLOCKED.
@@ -901,16 +934,18 @@
when(mKeyguardUpdateMonitor.isDreaming()).thenReturn(true);
// Transition to the glanceable hub.
- mCommunalRepository.setTransitionState(flowOf(new ObservableTransitionState.Idle(
- CommunalScenes.Communal)));
+ mKosmos.getCommunalRepository()
+ .setTransitionState(
+ flowOf(new ObservableTransitionState.Idle(CommunalScenes.Communal)));
mTestScope.getTestScheduler().runCurrent();
// ScrimState also transitions.
verify(mScrimController).transitionTo(ScrimState.GLANCEABLE_HUB_OVER_DREAM);
// Transition away from the glanceable hub.
- mCommunalRepository.setTransitionState(flowOf(new ObservableTransitionState.Idle(
- CommunalScenes.Blank)));
+ mKosmos.getCommunalRepository()
+ .setTransitionState(
+ flowOf(new ObservableTransitionState.Idle(CommunalScenes.Blank)));
mTestScope.getTestScheduler().runCurrent();
// ScrimState goes back to UNLOCKED.
@@ -1105,18 +1140,16 @@
}
@Test
+ @EnableFlags(com.android.systemui.Flags.FLAG_TRUNCATED_STATUS_BAR_ICONS_FIX)
public void updateResources_flagEnabled_doesNotUpdateStatusBarWindowHeight() {
- mSetFlagsRule.enableFlags(com.android.systemui.Flags.FLAG_TRUNCATED_STATUS_BAR_ICONS_FIX);
-
mCentralSurfaces.updateResources();
verify(mStatusBarWindowController, never()).refreshStatusBarHeight();
}
@Test
+ @DisableFlags(com.android.systemui.Flags.FLAG_TRUNCATED_STATUS_BAR_ICONS_FIX)
public void updateResources_flagDisabled_updatesStatusBarWindowHeight() {
- mSetFlagsRule.disableFlags(com.android.systemui.Flags.FLAG_TRUNCATED_STATUS_BAR_ICONS_FIX);
-
mCentralSurfaces.updateResources();
verify(mStatusBarWindowController).refreshStatusBarHeight();
@@ -1151,10 +1184,10 @@
}
@Test
+ @EnableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE)
public void dismissKeyboardShortcuts_largeScreen_bothFlagsEnabled_doesNotDismissAny() {
switchToLargeScreen();
mFeatureFlags.set(SHORTCUT_LIST_SEARCH_LAYOUT, true);
- mSetFlagsRule.enableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE);
createCentralSurfaces();
dismissKeyboardShortcuts();
@@ -1163,10 +1196,10 @@
}
@Test
+ @DisableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE)
public void dismissKeyboardShortcuts_largeScreen_newFlagsDisabled_dismissesTabletVersion() {
switchToLargeScreen();
mFeatureFlags.set(SHORTCUT_LIST_SEARCH_LAYOUT, true);
- mSetFlagsRule.disableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE);
createCentralSurfaces();
dismissKeyboardShortcuts();
@@ -1175,10 +1208,10 @@
}
@Test
+ @DisableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE)
public void dismissKeyboardShortcuts_largeScreen_bothFlagsDisabled_dismissesPhoneVersion() {
switchToLargeScreen();
mFeatureFlags.set(SHORTCUT_LIST_SEARCH_LAYOUT, false);
- mSetFlagsRule.disableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE);
createCentralSurfaces();
dismissKeyboardShortcuts();
@@ -1188,10 +1221,10 @@
}
@Test
+ @EnableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE)
public void dismissKeyboardShortcuts_smallScreen_bothFlagsEnabled_doesNotDismissAny() {
switchToSmallScreen();
mFeatureFlags.set(SHORTCUT_LIST_SEARCH_LAYOUT, true);
- mSetFlagsRule.enableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE);
createCentralSurfaces();
dismissKeyboardShortcuts();
@@ -1200,10 +1233,10 @@
}
@Test
+ @DisableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE)
public void dismissKeyboardShortcuts_smallScreen_newFlagsDisabled_dismissesPhoneVersion() {
switchToSmallScreen();
mFeatureFlags.set(SHORTCUT_LIST_SEARCH_LAYOUT, true);
- mSetFlagsRule.disableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE);
createCentralSurfaces();
dismissKeyboardShortcuts();
@@ -1213,10 +1246,10 @@
}
@Test
+ @DisableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE)
public void dismissKeyboardShortcuts_smallScreen_bothFlagsDisabled_dismissesPhoneVersion() {
switchToSmallScreen();
mFeatureFlags.set(SHORTCUT_LIST_SEARCH_LAYOUT, false);
- mSetFlagsRule.disableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE);
createCentralSurfaces();
dismissKeyboardShortcuts();
@@ -1226,10 +1259,10 @@
}
@Test
+ @EnableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE)
public void toggleKeyboardShortcuts_largeScreen_bothFlagsEnabled_doesNotTogglesAny() {
switchToLargeScreen();
mFeatureFlags.set(SHORTCUT_LIST_SEARCH_LAYOUT, true);
- mSetFlagsRule.enableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE);
createCentralSurfaces();
int deviceId = 321;
@@ -1239,10 +1272,10 @@
}
@Test
+ @DisableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE)
public void toggleKeyboardShortcuts_largeScreen_newFlagsDisabled_togglesTabletVersion() {
switchToLargeScreen();
mFeatureFlags.set(SHORTCUT_LIST_SEARCH_LAYOUT, true);
- mSetFlagsRule.disableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE);
createCentralSurfaces();
int deviceId = 654;
@@ -1253,10 +1286,10 @@
}
@Test
+ @DisableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE)
public void toggleKeyboardShortcuts_largeScreen_bothFlagsDisabled_togglesPhoneVersion() {
switchToLargeScreen();
mFeatureFlags.set(SHORTCUT_LIST_SEARCH_LAYOUT, false);
- mSetFlagsRule.disableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE);
createCentralSurfaces();
int deviceId = 987;
@@ -1267,10 +1300,10 @@
}
@Test
+ @EnableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE)
public void toggleKeyboardShortcuts_smallScreen_bothFlagsEnabled_doesNotToggleAny() {
switchToSmallScreen();
mFeatureFlags.set(SHORTCUT_LIST_SEARCH_LAYOUT, true);
- mSetFlagsRule.enableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE);
createCentralSurfaces();
int deviceId = 789;
@@ -1280,10 +1313,10 @@
}
@Test
+ @DisableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE)
public void toggleKeyboardShortcuts_smallScreen_newFlagsDisabled_togglesPhoneVersion() {
switchToSmallScreen();
mFeatureFlags.set(SHORTCUT_LIST_SEARCH_LAYOUT, true);
- mSetFlagsRule.disableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE);
createCentralSurfaces();
int deviceId = 456;
@@ -1294,10 +1327,10 @@
}
@Test
+ @DisableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE)
public void toggleKeyboardShortcuts_smallScreen_bothFlagsDisabled_togglesPhoneVersion() {
switchToSmallScreen();
mFeatureFlags.set(SHORTCUT_LIST_SEARCH_LAYOUT, false);
- mSetFlagsRule.disableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE);
createCentralSurfaces();
int deviceId = 123;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java
index fd295b5..e834693 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java
@@ -26,6 +26,8 @@
import static org.mockito.Mockito.when;
import android.content.res.Resources;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
import android.testing.AndroidTestingRunner;
import androidx.test.filters.SmallTest;
@@ -297,8 +299,8 @@
}
@Test
+ @DisableFlags(Flags.FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX)
public void notifPaddingMakesUpToFullMarginInSplitShade_refactorFlagOff_usesResource() {
- mSetFlagsRule.disableFlags(Flags.FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX);
int keyguardSplitShadeTopMargin = 100;
int largeScreenHeaderHeightResource = 70;
when(mResources.getDimensionPixelSize(R.dimen.keyguard_split_shade_top_margin))
@@ -316,8 +318,8 @@
}
@Test
+ @EnableFlags(Flags.FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX)
public void notifPaddingMakesUpToFullMarginInSplitShade_refactorFlagOn_usesHelper() {
- mSetFlagsRule.enableFlags(Flags.FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX);
int keyguardSplitShadeTopMargin = 100;
int largeScreenHeaderHeightHelper = 50;
int largeScreenHeaderHeightResource = 70;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
index 66211c9..fdf77ae 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
@@ -426,7 +426,7 @@
fragment.disable(DEFAULT_DISPLAY, 0, 0, false);
assertEquals(View.GONE,
- mFragment.getView().findViewById(R.id.ongoing_call_chip).getVisibility());
+ mFragment.getView().findViewById(R.id.ongoing_activity_chip).getVisibility());
}
@Test
@@ -438,7 +438,7 @@
fragment.disable(DEFAULT_DISPLAY, 0, 0, false);
assertEquals(View.VISIBLE,
- mFragment.getView().findViewById(R.id.ongoing_call_chip).getVisibility());
+ mFragment.getView().findViewById(R.id.ongoing_activity_chip).getVisibility());
assertEquals(View.INVISIBLE, getNotificationAreaView().getVisibility());
}
@@ -452,7 +452,7 @@
StatusBarManager.DISABLE_NOTIFICATION_ICONS, 0, false);
assertEquals(View.GONE,
- mFragment.getView().findViewById(R.id.ongoing_call_chip).getVisibility());
+ mFragment.getView().findViewById(R.id.ongoing_activity_chip).getVisibility());
}
@Test
@@ -465,7 +465,7 @@
fragment.disable(DEFAULT_DISPLAY, 0, 0, false);
assertEquals(View.GONE,
- mFragment.getView().findViewById(R.id.ongoing_call_chip).getVisibility());
+ mFragment.getView().findViewById(R.id.ongoing_activity_chip).getVisibility());
}
@Test
@@ -477,21 +477,21 @@
fragment.disable(DEFAULT_DISPLAY, 0, 0, false);
assertEquals(View.VISIBLE,
- mFragment.getView().findViewById(R.id.ongoing_call_chip).getVisibility());
+ mFragment.getView().findViewById(R.id.ongoing_activity_chip).getVisibility());
// Ongoing call ended
when(mOngoingCallController.hasOngoingCall()).thenReturn(false);
fragment.disable(DEFAULT_DISPLAY, 0, 0, false);
assertEquals(View.GONE,
- mFragment.getView().findViewById(R.id.ongoing_call_chip).getVisibility());
+ mFragment.getView().findViewById(R.id.ongoing_activity_chip).getVisibility());
// Ongoing call started
when(mOngoingCallController.hasOngoingCall()).thenReturn(true);
fragment.disable(DEFAULT_DISPLAY, 0, 0, false);
assertEquals(View.VISIBLE,
- mFragment.getView().findViewById(R.id.ongoing_call_chip).getVisibility());
+ mFragment.getView().findViewById(R.id.ongoing_activity_chip).getVisibility());
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt
index 05464f3..4d6798b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt
@@ -106,7 +106,7 @@
fun setUp() {
allowTestableLooperAsMainThread()
TestableLooper.get(this).runWithLooper {
- chipView = LayoutInflater.from(mContext).inflate(R.layout.ongoing_call_chip, null)
+ chipView = LayoutInflater.from(mContext).inflate(R.layout.ongoing_activity_chip, null)
}
MockitoAnnotations.initMocks(this)
@@ -206,7 +206,7 @@
View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
)
- assertThat(chipView.findViewById<View>(R.id.ongoing_call_chip_time)?.measuredWidth)
+ assertThat(chipView.findViewById<View>(R.id.ongoing_activity_chip_time)?.measuredWidth)
.isEqualTo(0)
}
@@ -222,7 +222,7 @@
View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
)
- assertThat(chipView.findViewById<View>(R.id.ongoing_call_chip_time)?.measuredWidth)
+ assertThat(chipView.findViewById<View>(R.id.ongoing_activity_chip_time)?.measuredWidth)
.isGreaterThan(0)
}
@@ -237,7 +237,7 @@
View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
)
- assertThat(chipView.findViewById<View>(R.id.ongoing_call_chip_time)?.measuredWidth)
+ assertThat(chipView.findViewById<View>(R.id.ongoing_activity_chip_time)?.measuredWidth)
.isGreaterThan(0)
}
@@ -472,7 +472,10 @@
lateinit var newChipView: View
TestableLooper.get(this).runWithLooper {
- newChipView = LayoutInflater.from(mContext).inflate(R.layout.ongoing_call_chip, null)
+ newChipView = LayoutInflater.from(mContext).inflate(
+ R.layout.ongoing_activity_chip,
+ null
+ )
}
// Change the chip view associated with the controller.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorTest.kt
index 3dee093..96c6eb8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorTest.kt
@@ -887,6 +887,46 @@
}
@Test
+ fun removeGuestUser_shouldNotShowExitGuestDialog() {
+ createUserInteractor()
+ testScope.runTest {
+ val (userInfo, guestUserInfo) = createUserInfos(count = 2, includeGuest = true)
+ userRepository.setUserInfos(listOf(userInfo, guestUserInfo))
+ userRepository.setSelectedUserInfo(guestUserInfo)
+
+ whenever(manager.markGuestForDeletion(guestUserInfo.id)).thenReturn(true)
+ underTest.removeGuestUser(guestUserInfo.id, userInfo.id)
+ runCurrent()
+
+ verify(manager).markGuestForDeletion(guestUserInfo.id)
+ verify(activityManager).switchUser(userInfo.id)
+ assertThat(collectLastValue(underTest.dialogShowRequests)()).isNull()
+ }
+ }
+
+ @Test
+ fun resetGuestUser_shouldNotShowExitGuestDialog() {
+ createUserInteractor()
+ testScope.runTest {
+ val (userInfo, guestUserInfo) = createUserInfos(count = 2, includeGuest = true)
+ val otherGuestUserInfo = createUserInfos(count = 1, includeGuest = true)[0]
+ userRepository.setUserInfos(listOf(userInfo, guestUserInfo))
+ userRepository.setSelectedUserInfo(guestUserInfo)
+
+ whenever(manager.markGuestForDeletion(guestUserInfo.id)).thenReturn(true)
+ whenever(manager.createGuest(any())).thenReturn(otherGuestUserInfo)
+ underTest.removeGuestUser(guestUserInfo.id, UserHandle.USER_NULL)
+ runCurrent()
+
+ verify(manager).markGuestForDeletion(guestUserInfo.id)
+ verify(manager).createGuest(any())
+ verify(activityManager).switchUser(otherGuestUserInfo.id)
+ assertThat(collectLastValue(underTest.dialogShowRequests)())
+ .isEqualTo(ShowDialogRequestModel.ShowUserCreationDialog(isGuest = true))
+ }
+ }
+
+ @Test
fun showUserSwitcher_fullScreenDisabled_showsDialogSwitcher() {
createUserInteractor()
testScope.runTest {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
index aac3640..df78110 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -27,6 +27,7 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.server.notification.Flags.FLAG_SCREENSHARE_NOTIFICATION_HIDING;
+import static com.android.wm.shell.Flags.FLAG_ENABLE_BUBBLE_BAR;
import static com.google.common.truth.Truth.assertThat;
@@ -2141,6 +2142,112 @@
assertThat(mBubbleController.getLayerView().isExpanded()).isFalse();
}
+ @EnableFlags(FLAG_ENABLE_BUBBLE_BAR)
+ @Test
+ public void dragBubbleBarBubble_selectedBubble_expandedViewCollapsesDuringDrag() {
+ mBubbleProperties.mIsBubbleBarEnabled = true;
+ mPositioner.setIsLargeScreen(true);
+ FakeBubbleStateListener bubbleStateListener = new FakeBubbleStateListener();
+ mBubbleController.registerBubbleStateListener(bubbleStateListener);
+
+ // Add 2 bubbles
+ mEntryListener.onEntryAdded(mRow);
+ mEntryListener.onEntryAdded(mRow2);
+ mBubbleController.updateBubble(mBubbleEntry);
+ mBubbleController.updateBubble(mBubbleEntry2);
+
+ // Select first bubble
+ mBubbleController.expandStackAndSelectBubbleFromLauncher(mBubbleEntry.getKey(), new Rect());
+ assertThat(mBubbleData.getSelectedBubbleKey()).isEqualTo(mBubbleEntry.getKey());
+ assertThat(mBubbleController.getLayerView().isExpanded()).isTrue();
+
+ // Drag first bubble, bubble should collapse
+ mBubbleController.startBubbleDrag(mBubbleEntry.getKey());
+ assertThat(mBubbleController.getLayerView().isExpanded()).isFalse();
+
+ // Stop dragging, first bubble should be expanded
+ mBubbleController.stopBubbleDrag(BubbleBarLocation.LEFT);
+ assertThat(mBubbleData.getSelectedBubbleKey()).isEqualTo(mBubbleEntry.getKey());
+ assertThat(mBubbleController.getLayerView().isExpanded()).isTrue();
+ }
+
+ @EnableFlags(FLAG_ENABLE_BUBBLE_BAR)
+ @Test
+ public void dragBubbleBarBubble_unselectedBubble_expandedViewCollapsesDuringDrag() {
+ mBubbleProperties.mIsBubbleBarEnabled = true;
+ mPositioner.setIsLargeScreen(true);
+ FakeBubbleStateListener bubbleStateListener = new FakeBubbleStateListener();
+ mBubbleController.registerBubbleStateListener(bubbleStateListener);
+
+ // Add 2 bubbles
+ mEntryListener.onEntryAdded(mRow);
+ mEntryListener.onEntryAdded(mRow2);
+ mBubbleController.updateBubble(mBubbleEntry);
+ mBubbleController.updateBubble(mBubbleEntry2);
+
+ // Select first bubble
+ mBubbleController.expandStackAndSelectBubbleFromLauncher(mBubbleEntry.getKey(), new Rect());
+ assertThat(mBubbleData.getSelectedBubbleKey()).isEqualTo(mBubbleEntry.getKey());
+ assertThat(mBubbleController.getLayerView().isExpanded()).isTrue();
+
+ // Drag second bubble, bubble should collapse
+ mBubbleController.startBubbleDrag(mBubbleEntry2.getKey());
+ assertThat(mBubbleController.getLayerView().isExpanded()).isFalse();
+
+ // Stop dragging, first bubble should be expanded
+ mBubbleController.stopBubbleDrag(BubbleBarLocation.LEFT);
+ assertThat(mBubbleData.getSelectedBubbleKey()).isEqualTo(mBubbleEntry.getKey());
+ assertThat(mBubbleController.getLayerView().isExpanded()).isTrue();
+ }
+
+ @EnableFlags(FLAG_ENABLE_BUBBLE_BAR)
+ @Test
+ public void dismissBubbleBarBubble_selected_selectsAndExpandsNext() {
+ mBubbleProperties.mIsBubbleBarEnabled = true;
+ mPositioner.setIsLargeScreen(true);
+ FakeBubbleStateListener bubbleStateListener = new FakeBubbleStateListener();
+ mBubbleController.registerBubbleStateListener(bubbleStateListener);
+
+ // Add 2 bubbles
+ mEntryListener.onEntryAdded(mRow);
+ mEntryListener.onEntryAdded(mRow2);
+ mBubbleController.updateBubble(mBubbleEntry);
+ mBubbleController.updateBubble(mBubbleEntry2);
+
+ // Select first bubble
+ mBubbleController.expandStackAndSelectBubbleFromLauncher(mBubbleEntry.getKey(), new Rect());
+ // Drag first bubble to dismiss
+ mBubbleController.startBubbleDrag(mBubbleEntry.getKey());
+ mBubbleController.dragBubbleToDismiss(mBubbleEntry.getKey());
+ // Second bubble is selected and expanded
+ assertThat(mBubbleData.getSelectedBubbleKey()).isEqualTo(mBubbleEntry2.getKey());
+ assertThat(mBubbleController.getLayerView().isExpanded()).isTrue();
+ }
+
+ @EnableFlags(FLAG_ENABLE_BUBBLE_BAR)
+ @Test
+ public void dismissBubbleBarBubble_unselected_selectionDoesNotChange() {
+ mBubbleProperties.mIsBubbleBarEnabled = true;
+ mPositioner.setIsLargeScreen(true);
+ FakeBubbleStateListener bubbleStateListener = new FakeBubbleStateListener();
+ mBubbleController.registerBubbleStateListener(bubbleStateListener);
+
+ // Add 2 bubbles
+ mEntryListener.onEntryAdded(mRow);
+ mEntryListener.onEntryAdded(mRow2);
+ mBubbleController.updateBubble(mBubbleEntry);
+ mBubbleController.updateBubble(mBubbleEntry2);
+
+ // Select first bubble
+ mBubbleController.expandStackAndSelectBubbleFromLauncher(mBubbleEntry.getKey(), new Rect());
+ // Drag second bubble to dismiss
+ mBubbleController.startBubbleDrag(mBubbleEntry2.getKey());
+ mBubbleController.dragBubbleToDismiss(mBubbleEntry2.getKey());
+ // First bubble remains selected and expanded
+ assertThat(mBubbleData.getSelectedBubbleKey()).isEqualTo(mBubbleEntry.getKey());
+ assertThat(mBubbleController.getLayerView().isExpanded()).isTrue();
+ }
+
@DisableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
@Test
public void doesNotRegisterSensitiveStateListener() {
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
index 9dcd946..8eef930 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
@@ -15,8 +15,6 @@
*/
package com.android.systemui;
-import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT;
-
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
@@ -99,8 +97,22 @@
.setProvideMainThread(true)
.build();
- @Rule
- public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT);
+ @ClassRule
+ public static final SetFlagsRule.ClassRule mSetFlagsClassRule =
+ new SetFlagsRule.ClassRule(
+ android.app.Flags.class,
+ android.hardware.biometrics.Flags.class,
+ android.multiuser.Flags.class,
+ android.net.platform.flags.Flags.class,
+ android.os.Flags.class,
+ android.service.controls.flags.Flags.class,
+ com.android.internal.telephony.flags.Flags.class,
+ com.android.server.notification.Flags.class,
+ com.android.systemui.Flags.class);
+
+ // TODO(b/339471826): Fix Robolectric to execute the @ClassRule correctly
+ @Rule public final SetFlagsRule mSetFlagsRule =
+ isRobolectricTest() ? new SetFlagsRule() : mSetFlagsClassRule.createSetFlagsRule();
@Rule(order = 10)
public final SceneContainerRule mSceneContainerRule = new SceneContainerRule();
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/brightness/data/repository/FakeBrightnessPolicyRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/brightness/data/repository/FakeBrightnessPolicyRepository.kt
index d3ceb15..f5d02f3 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/brightness/data/repository/FakeBrightnessPolicyRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/brightness/data/repository/FakeBrightnessPolicyRepository.kt
@@ -38,4 +38,8 @@
)
)
}
+
+ fun setBaseUserRestriction() {
+ _restrictionPolicy.value = PolicyRestriction.Restricted(RestrictedLockUtils.EnforcedAdmin())
+ }
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorKosmos.kt
index 2c6d44f..03e5a90 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorKosmos.kt
@@ -19,6 +19,7 @@
import com.android.systemui.keyguard.data.repository.keyguardRepository
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.testScope
+import com.android.systemui.scene.domain.interactor.sceneInteractor
import kotlinx.coroutines.ExperimentalCoroutinesApi
@ExperimentalCoroutinesApi
@@ -29,5 +30,6 @@
transitionInteractor = keyguardTransitionInteractor,
dismissInteractor = keyguardDismissInteractor,
applicationScope = testScope.backgroundScope,
+ sceneInteractor = sceneInteractor,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qrcodescanner/QRCodeScannerControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qrcodescanner/QRCodeScannerControllerKosmos.kt
new file mode 100644
index 0000000..8ad6087
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qrcodescanner/QRCodeScannerControllerKosmos.kt
@@ -0,0 +1,23 @@
+/*
+ * 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.qrcodescanner
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qrcodescanner.controller.QRCodeScannerController
+import com.android.systemui.util.mockito.mock
+
+val Kosmos.qrCodeScannerController by Kosmos.Fixture { mock<QRCodeScannerController>() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/actions/FakeQSTileIntentUserInputHandler.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/actions/FakeQSTileIntentUserInputHandler.kt
index c4bf8ff..f50443e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/actions/FakeQSTileIntentUserInputHandler.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/actions/FakeQSTileIntentUserInputHandler.kt
@@ -31,7 +31,11 @@
private val mutableInputs = mutableListOf<Input>()
- override fun handle(expandable: Expandable?, intent: Intent) {
+ override fun handle(
+ expandable: Expandable?,
+ intent: Intent,
+ handleDismissShadeShowOverLockScreenWhenLocked: Boolean
+ ) {
mutableInputs.add(Input.Intent(expandable, intent))
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandlerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandlerKosmos.kt
new file mode 100644
index 0000000..ccfb609
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandlerKosmos.kt
@@ -0,0 +1,21 @@
+/*
+ * 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.qs.tiles.base.actions
+
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.qsTileIntentUserInputHandler by Kosmos.Fixture { FakeQSTileIntentUserInputHandler() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/analytics/QSTileAnalyticsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/analytics/QSTileAnalyticsKosmos.kt
new file mode 100644
index 0000000..146c1ad
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/analytics/QSTileAnalyticsKosmos.kt
@@ -0,0 +1,22 @@
+/*
+ * 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.qs.tiles.base.analytics
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.mockito.mock
+
+val Kosmos.qsTileAnalytics by Kosmos.Fixture { mock<QSTileAnalytics>() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/DisabledByPolicyInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/DisabledByPolicyInteractorKosmos.kt
new file mode 100644
index 0000000..9ad49f0
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/DisabledByPolicyInteractorKosmos.kt
@@ -0,0 +1,21 @@
+/*
+ * 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.qs.tiles.base.interactor
+
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.fakeDisabledByPolicyInteractor by Kosmos.Fixture { FakeDisabledByPolicyInteractor() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/qr/QRCodeScannerTileKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/qr/QRCodeScannerTileKosmos.kt
new file mode 100644
index 0000000..dcfcce7
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/qr/QRCodeScannerTileKosmos.kt
@@ -0,0 +1,74 @@
+/*
+ * 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.qs.tiles.impl.qr
+
+import android.content.res.mainResources
+import com.android.systemui.classifier.fakeFalsingManager
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.backgroundCoroutineContext
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.qrcodescanner.dagger.QRCodeScannerModule
+import com.android.systemui.qrcodescanner.qrCodeScannerController
+import com.android.systemui.qs.qsEventLogger
+import com.android.systemui.qs.tiles.base.actions.qsTileIntentUserInputHandler
+import com.android.systemui.qs.tiles.base.analytics.qsTileAnalytics
+import com.android.systemui.qs.tiles.base.interactor.fakeDisabledByPolicyInteractor
+import com.android.systemui.qs.tiles.base.viewmodel.QSTileViewModelImpl
+import com.android.systemui.qs.tiles.impl.custom.qsTileLogger
+import com.android.systemui.qs.tiles.impl.qr.domain.interactor.QRCodeScannerTileDataInteractor
+import com.android.systemui.qs.tiles.impl.qr.domain.interactor.QRCodeScannerTileUserActionInteractor
+import com.android.systemui.qs.tiles.impl.qr.ui.QRCodeScannerTileMapper
+import com.android.systemui.user.data.repository.fakeUserRepository
+import com.android.systemui.util.time.systemClock
+
+val Kosmos.qsQRCodeScannerTileConfig by
+ Kosmos.Fixture { QRCodeScannerModule.provideQRCodeScannerTileConfig(qsEventLogger) }
+
+val Kosmos.qrCodeScannerTileDataInteractor by
+ Kosmos.Fixture {
+ QRCodeScannerTileDataInteractor(
+ backgroundCoroutineContext,
+ applicationCoroutineScope,
+ qrCodeScannerController
+ )
+ }
+
+val Kosmos.qrCodeScannerTileUserActionInteractor by
+ Kosmos.Fixture { QRCodeScannerTileUserActionInteractor(qsTileIntentUserInputHandler) }
+
+val Kosmos.qrCodeScannerTileMapper by
+ Kosmos.Fixture { QRCodeScannerTileMapper(mainResources, mainResources.newTheme()) }
+
+val Kosmos.qsQRCodeScannerViewModel by
+ Kosmos.Fixture {
+ QSTileViewModelImpl(
+ qsQRCodeScannerTileConfig,
+ { qrCodeScannerTileUserActionInteractor },
+ { qrCodeScannerTileDataInteractor },
+ { qrCodeScannerTileMapper },
+ fakeDisabledByPolicyInteractor,
+ fakeUserRepository,
+ fakeFalsingManager,
+ qsTileAnalytics,
+ qsTileLogger,
+ systemClock,
+ testDispatcher,
+ testScope.backgroundScope,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryUtil.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryUtil.kt
new file mode 100644
index 0000000..641a757
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryUtil.kt
@@ -0,0 +1,63 @@
+/*
+ * 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.scene.data.repository
+
+import com.android.compose.animation.scene.ObservableTransitionState
+import com.android.compose.animation.scene.SceneKey
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.scene.shared.model.Scenes
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+
+private val mutableTransitionState =
+ MutableStateFlow<ObservableTransitionState>(ObservableTransitionState.Idle(Scenes.Lockscreen))
+
+fun Kosmos.setSceneTransition(
+ transition: ObservableTransitionState,
+ scope: TestScope = testScope,
+ repository: SceneContainerRepository = sceneContainerRepository
+) {
+ repository.setTransitionState(mutableTransitionState)
+ mutableTransitionState.value = transition
+ scope.runCurrent()
+}
+
+fun Transition(
+ from: SceneKey,
+ to: SceneKey,
+ currentScene: Flow<SceneKey> = flowOf(to),
+ progress: Flow<Float> = flowOf(0f),
+ isInitiatedByUserInput: Boolean = false,
+ isUserInputOngoing: Flow<Boolean> = flowOf(false),
+): ObservableTransitionState.Transition {
+ return ObservableTransitionState.Transition(
+ fromScene = from,
+ toScene = to,
+ currentScene = currentScene,
+ progress = progress,
+ isInitiatedByUserInput = isInitiatedByUserInput,
+ isUserInputOngoing = isUserInputOngoing
+ )
+}
+
+fun Idle(currentScene: SceneKey): ObservableTransitionState.Idle {
+ return ObservableTransitionState.Idle(currentScene)
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeAudioRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeAudioRepository.kt
index 617fc52..6b27079 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeAudioRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeAudioRepository.kt
@@ -17,6 +17,7 @@
package com.android.systemui.volume.data.repository
import android.media.AudioDeviceInfo
+import android.media.AudioManager
import com.android.settingslib.volume.data.repository.AudioRepository
import com.android.settingslib.volume.shared.model.AudioStream
import com.android.settingslib.volume.shared.model.AudioStreamModel
@@ -29,10 +30,10 @@
class FakeAudioRepository : AudioRepository {
- private val mutableMode = MutableStateFlow(0)
+ private val mutableMode = MutableStateFlow(AudioManager.MODE_NORMAL)
override val mode: StateFlow<Int> = mutableMode.asStateFlow()
- private val mutableRingerMode = MutableStateFlow(RingerMode(0))
+ private val mutableRingerMode = MutableStateFlow(RingerMode(AudioManager.RINGER_MODE_NORMAL))
override val ringerMode: StateFlow<RingerMode> = mutableRingerMode.asStateFlow()
private val mutableCommunicationDevice = MutableStateFlow<AudioDeviceInfo?>(null)
@@ -53,7 +54,7 @@
audioStream = audioStream,
volume = 0,
minVolume = 0,
- maxVolume = 0,
+ maxVolume = 10,
isAffectedByRingerMode = false,
isMuted = false,
)
@@ -67,8 +68,14 @@
getAudioStreamModelState(audioStream).update { it.copy(volume = volume) }
}
- override suspend fun setMuted(audioStream: AudioStream, isMuted: Boolean) {
- getAudioStreamModelState(audioStream).update { it.copy(isMuted = isMuted) }
+ override suspend fun setMuted(audioStream: AudioStream, isMuted: Boolean): Boolean {
+ val modelState = getAudioStreamModelState(audioStream)
+ return if (modelState.value.isMuted == isMuted) {
+ false
+ } else {
+ modelState.update { it.copy(isMuted = isMuted) }
+ true
+ }
}
override suspend fun getLastAudibleVolume(audioStream: AudioStream): Int =
diff --git a/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/runtimehelper/ClassLoadHook.java b/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/runtimehelper/ClassLoadHook.java
index 96b7057..69ff262 100644
--- a/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/runtimehelper/ClassLoadHook.java
+++ b/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/runtimehelper/ClassLoadHook.java
@@ -162,20 +162,23 @@
android.graphics.Interpolator.class,
android.graphics.Matrix.class,
android.graphics.Path.class,
+ android.graphics.Color.class,
+ android.graphics.ColorSpace.class,
};
/**
- * @return if a given class has any native method or not.
+ * @return if a given class and its nested classes, if any, have any native method or not.
*/
private static boolean hasNativeMethod(Class<?> clazz) {
- for (var method : clazz.getDeclaredMethods()) {
- if (Modifier.isNative(method.getModifiers())) {
- return true;
+ for (var nestedClass : clazz.getNestMembers()) {
+ for (var method : nestedClass.getDeclaredMethods()) {
+ if (Modifier.isNative(method.getModifiers())) {
+ return true;
+ }
}
}
return false;
}
-
/**
* Create a list of classes as comma-separated that require JNI methods to be set up from
* a given class list, ignoring classes with no native methods.
diff --git a/ravenwood/texts/ravenwood-annotation-allowed-classes.txt b/ravenwood/texts/ravenwood-annotation-allowed-classes.txt
index d856f6d..f3172ae 100644
--- a/ravenwood/texts/ravenwood-annotation-allowed-classes.txt
+++ b/ravenwood/texts/ravenwood-annotation-allowed-classes.txt
@@ -239,6 +239,8 @@
android.accounts.Account
android.graphics.Bitmap$Config
+android.graphics.Color
+android.graphics.ColorSpace
android.graphics.Insets
android.graphics.Interpolator
android.graphics.Matrix
diff --git a/services/accessibility/accessibility.aconfig b/services/accessibility/accessibility.aconfig
index 82579d8..3f30b0a 100644
--- a/services/accessibility/accessibility.aconfig
+++ b/services/accessibility/accessibility.aconfig
@@ -152,6 +152,16 @@
}
flag {
+ name: "remove_on_window_infos_changed_handler"
+ namespace: "accessibility"
+ description: "Updates onWindowInfosChanged() to run without posting to a handler."
+ bug: "333834990"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "reset_hover_event_timer_on_action_up"
namespace: "accessibility"
description: "Reset the timer for sending hover events on receiving ACTION_UP to guarantee the correct amount of time is available between taps."
@@ -183,3 +193,10 @@
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "enable_color_correction_saturation"
+ namespace: "accessibility"
+ description: "Feature allows users to change color correction saturation for daltonizer."
+ bug: "322829049"
+}
\ No newline at end of file
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 07b16c5..a8b1235 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -1168,6 +1168,7 @@
return null;
}
+ @GuardedBy("mLock")
@Nullable
private AutofillValue findValueFromThisSessionOnlyLocked(@NonNull AutofillId autofillId) {
final ViewState state = mViewStates.get(autofillId);
@@ -1176,9 +1177,25 @@
return null;
}
AutofillValue value = state.getCurrentValue();
+
+ // Some app clears the form before navigating to another activities. In this case, use the
+ // cached value instead.
+ if (value == null || value.isEmpty()) {
+ AutofillValue candidateSaveValue = state.getCandidateSaveValue();
+ if (candidateSaveValue != null && !candidateSaveValue.isEmpty()) {
+ if (sDebug) {
+ Slog.d(TAG, "findValueLocked(): current value for " + autofillId
+ + " is empty, using candidateSaveValue instead.");
+ }
+ return candidateSaveValue;
+ }
+ }
if (value == null) {
- if (sDebug) Slog.d(TAG, "findValueLocked(): no current value for " + autofillId);
- value = getValueFromContextsLocked(autofillId);
+ if (sDebug) {
+ Slog.d(TAG, "findValueLocked(): no current value for " + autofillId
+ + ", checking value from previous fill contexts");
+ value = getValueFromContextsLocked(autofillId);
+ }
}
return value;
}
@@ -3717,19 +3734,34 @@
AutofillValue value = viewState.getCurrentValue();
if (value == null || value.isEmpty()) {
- final AutofillValue initialValue = getValueFromContextsLocked(id);
- if (initialValue != null) {
- if (sDebug) {
- Slog.d(TAG, "Value of required field " + id + " didn't change; "
- + "using initial value (" + initialValue + ") instead");
+ // Some apps clear the form before navigating to other activities.
+ // If current value is empty, consider fall back to last cached
+ // non-empty result first.
+ final AutofillValue candidateSaveValue =
+ viewState.getCandidateSaveValue();
+ if (candidateSaveValue != null && !candidateSaveValue.isEmpty()) {
+ if (sVerbose) {
+ Slog.v(TAG, "current value is empty, using cached last non-empty "
+ + "value instead");
}
- value = initialValue;
+ value = candidateSaveValue;
} else {
- if (sDebug) {
- Slog.d(TAG, "empty value for required " + id );
+ // If candidate save value is also empty, consider falling back to initial
+ // value in context.
+ final AutofillValue initialValue = getValueFromContextsLocked(id);
+ if (initialValue != null) {
+ if (sDebug) {
+ Slog.d(TAG, "Value of required field " + id + " didn't change; "
+ + "using initial value (" + initialValue + ") instead");
+ }
+ value = initialValue;
+ } else {
+ if (sDebug) {
+ Slog.d(TAG, "empty value for required " + id);
+ }
+ allRequiredAreNotEmpty = false;
+ break;
}
- allRequiredAreNotEmpty = false;
- break;
}
}
@@ -3801,7 +3833,21 @@
continue;
}
if ((viewState.getState() & ViewState.STATE_CHANGED) != 0) {
- final AutofillValue currentValue = viewState.getCurrentValue();
+ AutofillValue currentValue = viewState.getCurrentValue();
+ if (currentValue == null || currentValue.isEmpty()) {
+ // Some apps clear the form before navigating to other activities.
+ // If current value is empty, consider fall back to last cached
+ // non-empty result instead.
+ final AutofillValue candidateSaveValue =
+ viewState.getCandidateSaveValue();
+ if (candidateSaveValue != null && !candidateSaveValue.isEmpty()) {
+ if (sVerbose) {
+ Slog.v(TAG, "current value is empty, using cached last "
+ + "non-empty value instead");
+ }
+ currentValue = candidateSaveValue;
+ }
+ }
final AutofillValue value = getSanitizedValue(sanitizers, id, currentValue);
if (value == null) {
if (sDebug) {
@@ -4714,14 +4760,18 @@
@GuardedBy("mLock")
private void updateViewStateAndUiOnValueChangedLocked(AutofillId id, AutofillValue value,
ViewState viewState, int flags) {
+ // Cache the last non-empty value for save purpose. Some apps clear the form before
+ // navigating to other activities.
if (mIgnoreViewStateResetToEmpty && (value == null || value.isEmpty())
&& viewState.getCurrentValue() != null && viewState.getCurrentValue().isText()
&& viewState.getCurrentValue().getTextValue() != null
&& viewState.getCurrentValue().getTextValue().length() > 1) {
if (sVerbose) {
- Slog.v(TAG, "Ignoring view state reset to empty on id " + id);
+ Slog.v(TAG, "value is resetting to empty, caching the last non-empty value");
}
- return;
+ viewState.setCandidateSaveValue(viewState.getCurrentValue());
+ } else {
+ viewState.setCandidateSaveValue(null);
}
final String textValue;
if (value == null || !value.isText()) {
diff --git a/services/autofill/java/com/android/server/autofill/ViewState.java b/services/autofill/java/com/android/server/autofill/ViewState.java
index fec5aa5..6ad0eb6 100644
--- a/services/autofill/java/com/android/server/autofill/ViewState.java
+++ b/services/autofill/java/com/android/server/autofill/ViewState.java
@@ -106,6 +106,15 @@
*/
private FillResponse mSecondaryFillResponse;
private AutofillValue mCurrentValue;
+
+ /**
+ * Some apps clear the form before navigating to another activity. The mCandidateSaveValue
+ * caches the value when a field with string longer than 2 characters are cleared.
+ *
+ * When showing save UI, if mCurrentValue of view state is empty, session would use
+ * mCandidateSaveValue to prompt save instead.
+ */
+ private AutofillValue mCandidateSaveValue;
private AutofillValue mAutofilledValue;
private AutofillValue mSanitizedValue;
private Rect mVirtualBounds;
@@ -139,6 +148,18 @@
mCurrentValue = value;
}
+ /**
+ * Gets the candidate save value of the view.
+ */
+ @Nullable
+ AutofillValue getCandidateSaveValue() {
+ return mCandidateSaveValue;
+ }
+
+ void setCandidateSaveValue(AutofillValue value) {
+ mCandidateSaveValue = value;
+ }
+
@Nullable
AutofillValue getAutofilledValue() {
return mAutofilledValue;
@@ -268,6 +289,9 @@
if (mCurrentValue != null) {
builder.append(", currentValue:" ).append(mCurrentValue);
}
+ if (mCandidateSaveValue != null) {
+ builder.append(", candidateSaveValue:").append(mCandidateSaveValue);
+ }
if (mAutofilledValue != null) {
builder.append(", autofilledValue:" ).append(mAutofilledValue);
}
@@ -302,6 +326,9 @@
if (mAutofilledValue != null) {
pw.print(prefix); pw.print("autofilledValue:" ); pw.println(mAutofilledValue);
}
+ if (mCandidateSaveValue != null) {
+ pw.print(prefix); pw.print("candidateSaveValue:"); pw.println(mCandidateSaveValue);
+ }
if (mSanitizedValue != null) {
pw.print(prefix); pw.print("sanitizedValue:" ); pw.println(mSanitizedValue);
}
diff --git a/services/core/Android.bp b/services/core/Android.bp
index dc1155a..0fdf6d0 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -260,7 +260,6 @@
"connectivity_flags_lib",
"dreams_flags_lib",
"aconfig_new_storage_flags_lib",
- "aconfigd_java_proto_lib",
],
javac_shard_size: 50,
javacflags: [
diff --git a/services/core/java/com/android/server/TEST_MAPPING b/services/core/java/com/android/server/TEST_MAPPING
index 5933639..04e85c7 100644
--- a/services/core/java/com/android/server/TEST_MAPPING
+++ b/services/core/java/com/android/server/TEST_MAPPING
@@ -187,6 +187,20 @@
},
{
"name": "SelinuxFrameworksTests"
+ },
+ {
+ "name": "CtsWindowManagerBackgroundActivityTestCases",
+ "file_patterns": [
+ "Background.*\\.java",
+ "Activity.*\\.java"
+ ]
+ },
+ {
+ "name": "WmTests",
+ "file_patterns": [
+ "Background.*\\.java",
+ "Activity.*\\.java"
+ ]
}
]
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 008d7b2..00d8efa 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -12334,8 +12334,8 @@
ProcessList.SYSTEM_ADJ, ProcessList.PERSISTENT_PROC_ADJ,
ProcessList.PERSISTENT_SERVICE_ADJ, ProcessList.FOREGROUND_APP_ADJ,
ProcessList.VISIBLE_APP_ADJ,
- ProcessList.PERCEPTIBLE_APP_ADJ, ProcessList.PERCEPTIBLE_LOW_APP_ADJ,
- ProcessList.PERCEPTIBLE_MEDIUM_APP_ADJ,
+ ProcessList.PERCEPTIBLE_APP_ADJ,
+ ProcessList.PERCEPTIBLE_MEDIUM_APP_ADJ, ProcessList.PERCEPTIBLE_LOW_APP_ADJ,
ProcessList.BACKUP_APP_ADJ, ProcessList.HEAVY_WEIGHT_APP_ADJ,
ProcessList.SERVICE_ADJ, ProcessList.HOME_APP_ADJ,
ProcessList.PREVIOUS_APP_ADJ, ProcessList.SERVICE_B_ADJ, ProcessList.CACHED_APP_MIN_ADJ
@@ -12343,7 +12343,7 @@
static final String[] DUMP_MEM_OOM_LABEL = new String[] {
"Native",
"System", "Persistent", "Persistent Service", "Foreground",
- "Visible", "Perceptible", "Perceptible Low", "Perceptible Medium",
+ "Visible", "Perceptible", "Perceptible Medium", "Perceptible Low",
"Backup", "Heavy Weight",
"A Services", "Home",
"Previous", "B Services", "Cached"
@@ -12351,7 +12351,7 @@
static final String[] DUMP_MEM_OOM_COMPACT_LABEL = new String[] {
"native",
"sys", "pers", "persvc", "fore",
- "vis", "percept", "perceptl", "perceptm",
+ "vis", "percept", "perceptm", "perceptl",
"backup", "heavy",
"servicea", "home",
"prev", "serviceb", "cached"
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index 4f84149..58732fd 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -159,6 +159,8 @@
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
/**
* All information we are collecting about things that can happen that impact
@@ -409,26 +411,14 @@
com.android.internal.R.bool.config_batteryStatsResetOnUnplugHighBatteryLevel);
final boolean resetOnUnplugAfterSignificantCharge = context.getResources().getBoolean(
com.android.internal.R.bool.config_batteryStatsResetOnUnplugAfterSignificantCharge);
- final long powerStatsThrottlePeriodCpu = context.getResources().getInteger(
- com.android.internal.R.integer.config_defaultPowerStatsThrottlePeriodCpu);
- final long powerStatsThrottlePeriodMobileRadio = context.getResources().getInteger(
- com.android.internal.R.integer.config_defaultPowerStatsThrottlePeriodMobileRadio);
- final long powerStatsThrottlePeriodWifi = context.getResources().getInteger(
- com.android.internal.R.integer.config_defaultPowerStatsThrottlePeriodWifi);
- mBatteryStatsConfig =
+ BatteryStatsImpl.BatteryStatsConfig.Builder batteryStatsConfigBuilder =
new BatteryStatsImpl.BatteryStatsConfig.Builder()
.setResetOnUnplugHighBatteryLevel(resetOnUnplugHighBatteryLevel)
- .setResetOnUnplugAfterSignificantCharge(resetOnUnplugAfterSignificantCharge)
- .setPowerStatsThrottlePeriodMillis(
- BatteryConsumer.POWER_COMPONENT_CPU,
- powerStatsThrottlePeriodCpu)
- .setPowerStatsThrottlePeriodMillis(
- BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO,
- powerStatsThrottlePeriodMobileRadio)
- .setPowerStatsThrottlePeriodMillis(
- BatteryConsumer.POWER_COMPONENT_WIFI,
- powerStatsThrottlePeriodWifi)
- .build();
+ .setResetOnUnplugAfterSignificantCharge(
+ resetOnUnplugAfterSignificantCharge);
+ setPowerStatsThrottlePeriods(batteryStatsConfigBuilder, context.getResources().getString(
+ com.android.internal.R.string.config_powerStatsThrottlePeriods));
+ mBatteryStatsConfig = batteryStatsConfigBuilder.build();
mPowerStatsUidResolver = new PowerStatsUidResolver();
mStats = new BatteryStatsImpl(mBatteryStatsConfig, Clock.SYSTEM_CLOCK, mMonotonicClock,
systemDir, mHandler, this, this, mUserManagerUserInfoProvider, mPowerProfile,
@@ -515,6 +505,26 @@
return config;
}
+ private void setPowerStatsThrottlePeriods(BatteryStatsImpl.BatteryStatsConfig.Builder builder,
+ String configString) {
+ Matcher matcher = Pattern.compile("([^:]+):(\\d+)\\s*").matcher(configString);
+ while (matcher.find()) {
+ String powerComponentName = matcher.group(1);
+ long throttlePeriod;
+ try {
+ throttlePeriod = Long.parseLong(matcher.group(2));
+ } catch (NumberFormatException nfe) {
+ throw new IllegalArgumentException(
+ "Invalid config_powerStatsThrottlePeriods format: " + configString);
+ }
+ if (powerComponentName.equals("*")) {
+ builder.setDefaultPowerStatsThrottlePeriodMillis(throttlePeriod);
+ } else {
+ builder.setPowerStatsThrottlePeriodMillis(powerComponentName, throttlePeriod);
+ }
+ }
+ }
+
/**
* Creates an instance of BatteryStatsService and restores data from stored state.
*/
diff --git a/services/core/java/com/android/server/am/PendingIntentRecord.java b/services/core/java/com/android/server/am/PendingIntentRecord.java
index da45a77..8d7a1c9 100644
--- a/services/core/java/com/android/server/am/PendingIntentRecord.java
+++ b/services/core/java/com/android/server/am/PendingIntentRecord.java
@@ -18,6 +18,10 @@
import static android.app.ActivityManager.PROCESS_STATE_TOP;
import static android.app.ActivityManager.START_SUCCESS;
+import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED;
+import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_COMPAT;
+import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED;
+import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
@@ -389,13 +393,20 @@
private static BackgroundStartPrivileges getBackgroundStartPrivilegesAllowedByCaller(
@Nullable Bundle options, int callingUid, @Nullable String callingPackage) {
- if (options == null || !options.containsKey(
- ActivityOptions.KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED)) {
+ if (options == null) {
return getDefaultBackgroundStartPrivileges(callingUid, callingPackage);
}
- return options.getBoolean(ActivityOptions.KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED)
- ? BackgroundStartPrivileges.ALLOW_BAL
- : BackgroundStartPrivileges.NONE;
+ switch (options.getInt(ActivityOptions.KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED,
+ MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED)) {
+ case MODE_BACKGROUND_ACTIVITY_START_DENIED:
+ return BackgroundStartPrivileges.NONE;
+ case MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED:
+ return getDefaultBackgroundStartPrivileges(callingUid, callingPackage);
+ case MODE_BACKGROUND_ACTIVITY_START_ALLOWED:
+ case MODE_BACKGROUND_ACTIVITY_START_COMPAT:
+ default:
+ return BackgroundStartPrivileges.ALLOW_BAL;
+ }
}
/**
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index 827db57..5793758 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -29,6 +29,8 @@
import android.provider.Settings;
import android.text.TextUtils;
import android.util.Slog;
+import android.util.proto.ProtoInputStream;
+import android.util.proto.ProtoOutputStream;
import com.android.internal.annotations.VisibleForTesting;
@@ -262,11 +264,11 @@
Uri settingUri = Settings.Global.getUriFor(globalSetting);
String propName = makePropertyName(GLOBAL_SETTINGS_CATEGORY, globalSetting);
if (settingUri == null) {
- log("setting uri is null for globalSetting " + globalSetting);
+ logErr("setting uri is null for globalSetting " + globalSetting);
continue;
}
if (propName == null) {
- log("invalid prop name for globalSetting " + globalSetting);
+ logErr("invalid prop name for globalSetting " + globalSetting);
continue;
}
@@ -294,7 +296,7 @@
for (String key : properties.getKeyset()) {
String propertyName = makePropertyName(scope, key);
if (propertyName == null) {
- log("unable to construct system property for " + scope + "/"
+ logErr("unable to construct system property for " + scope + "/"
+ key);
return;
}
@@ -306,7 +308,7 @@
// sys prop slot can be removed.
String aconfigPropertyName = makeAconfigFlagPropertyName(scope, key);
if (aconfigPropertyName == null) {
- log("unable to construct system property for " + scope + "/"
+ logErr("unable to construct system property for " + scope + "/"
+ key);
return;
}
@@ -324,7 +326,7 @@
for (String key : properties.getKeyset()) {
String aconfigPropertyName = makeAconfigFlagPropertyName(scope, key);
if (aconfigPropertyName == null) {
- log("unable to construct system property for " + scope + "/"
+ logErr("unable to construct system property for " + scope + "/"
+ key);
return;
}
@@ -354,7 +356,7 @@
if (!propertyName.matches(SYSTEM_PROPERTY_VALID_CHARACTERS_REGEX)
|| propertyName.contains(SYSTEM_PROPERTY_INVALID_SUBSTRING)) {
- log("unable to construct system property for " + actualNamespace
+ logErr("unable to construct system property for " + actualNamespace
+ "/" + flagName);
continue;
}
@@ -383,18 +385,18 @@
/**
* apply flag local override in aconfig new storage
- * @param props
- * @return aconfigd socket return
+ * @param requests: request proto output stream
+ * @return aconfigd socket return as proto input stream
*/
- public static StorageReturnMessages sendAconfigdRequests(StorageRequestMessages requests) {
+ static ProtoInputStream sendAconfigdRequests(ProtoOutputStream requests) {
// connect to aconfigd socket
LocalSocket client = new LocalSocket();
try{
client.connect(new LocalSocketAddress(
"aconfigd", LocalSocketAddress.Namespace.RESERVED));
- log("connected to aconfigd socket");
+ Slog.d(TAG, "connected to aconfigd socket");
} catch (IOException ioe) {
- log("failed to connect to aconfigd socket", ioe);
+ logErr("failed to connect to aconfigd socket", ioe);
return null;
}
@@ -404,43 +406,93 @@
inputStream = new DataInputStream(client.getInputStream());
outputStream = new DataOutputStream(client.getOutputStream());
} catch (IOException ioe) {
- log("failed to get local socket iostreams", ioe);
+ logErr("failed to get local socket iostreams", ioe);
return null;
}
// send requests
try {
- byte[] requests_bytes = requests.toByteArray();
+ byte[] requests_bytes = requests.getBytes();
outputStream.writeInt(requests_bytes.length);
outputStream.write(requests_bytes, 0, requests_bytes.length);
- log(requests.getMsgsCount() + " flag override requests sent to aconfigd");
+ Slog.d(TAG, "flag override requests sent to aconfigd");
} catch (IOException ioe) {
- log("failed to send requests to aconfigd", ioe);
+ logErr("failed to send requests to aconfigd", ioe);
return null;
}
// read return
- StorageReturnMessages return_msgs = null;
try {
int num_bytes = inputStream.readInt();
- byte[] buffer = new byte[num_bytes];
- inputStream.read(buffer, 0, num_bytes);
- return_msgs = StorageReturnMessages.parseFrom(buffer);
- log(return_msgs.getMsgsCount() + " acknowledgement received from aconfigd");
+ ProtoInputStream returns = new ProtoInputStream(inputStream);
+ Slog.d(TAG, "received " + num_bytes + " bytes back from aconfigd");
+ return returns;
} catch (IOException ioe) {
- log("failed to read requests return from aconfigd", ioe);
+ logErr("failed to read requests return from aconfigd", ioe);
return null;
}
+ }
- return return_msgs;
+ /**
+ * serialize a flag override request
+ * @param proto
+ */
+ static void writeFlagOverrideRequest(
+ ProtoOutputStream proto, String packageName, String flagName, String flagValue,
+ boolean isLocal) {
+ long msgsToken = proto.start(StorageRequestMessages.MSGS);
+ long msgToken = proto.start(StorageRequestMessage.FLAG_OVERRIDE_MESSAGE);
+ proto.write(StorageRequestMessage.FlagOverrideMessage.PACKAGE_NAME, packageName);
+ proto.write(StorageRequestMessage.FlagOverrideMessage.FLAG_NAME, flagName);
+ proto.write(StorageRequestMessage.FlagOverrideMessage.FLAG_VALUE, flagValue);
+ proto.write(StorageRequestMessage.FlagOverrideMessage.IS_LOCAL, isLocal);
+ proto.end(msgToken);
+ proto.end(msgsToken);
+ }
+
+ /**
+ * deserialize a flag input proto stream and log
+ * @param proto
+ */
+ static void parseAndLogAconfigdReturn(ProtoInputStream proto) throws IOException {
+ while (true) {
+ switch (proto.nextField()) {
+ case (int) StorageReturnMessages.MSGS:
+ long msgsToken = proto.start(StorageReturnMessages.MSGS);
+ switch (proto.nextField()) {
+ case (int) StorageReturnMessage.FLAG_OVERRIDE_MESSAGE:
+ Slog.d(TAG, "successfully handled override requests");
+ long msgToken = proto.start(StorageReturnMessage.FLAG_OVERRIDE_MESSAGE);
+ proto.end(msgToken);
+ break;
+ case (int) StorageReturnMessage.ERROR_MESSAGE:
+ String errmsg = proto.readString(StorageReturnMessage.ERROR_MESSAGE);
+ Slog.d(TAG, "override request failed: " + errmsg);
+ break;
+ case ProtoInputStream.NO_MORE_FIELDS:
+ break;
+ default:
+ logErr("invalid message type, expecting only flag override return or error message");
+ break;
+ }
+ proto.end(msgsToken);
+ break;
+ case ProtoInputStream.NO_MORE_FIELDS:
+ return;
+ default:
+ logErr("invalid message type, expect storage return message");
+ break;
+ }
+ }
}
/**
* apply flag local override in aconfig new storage
* @param props
*/
- public static void setLocalOverridesInNewStorage(DeviceConfig.Properties props) {
- StorageRequestMessages.Builder requests_builder = StorageRequestMessages.newBuilder();
+ static void setLocalOverridesInNewStorage(DeviceConfig.Properties props) {
+ int num_requests = 0;
+ ProtoOutputStream requests = new ProtoOutputStream();
for (String flagName : props.getKeyset()) {
String flagValue = props.getString(flagName, null);
if (flagName == null || flagValue == null) {
@@ -449,32 +501,35 @@
int idx = flagName.indexOf(":");
if (idx == -1 || idx == flagName.length() - 1 || idx == 0) {
- log("invalid local flag override: " + flagName);
+ logErr("invalid local flag override: " + flagName);
continue;
}
String actualNamespace = flagName.substring(0, idx);
String fullFlagName = flagName.substring(idx+1);
idx = fullFlagName.lastIndexOf(".");
if (idx == -1) {
- log("invalid flag name: " + fullFlagName);
+ logErr("invalid flag name: " + fullFlagName);
continue;
}
String packageName = fullFlagName.substring(0, idx);
String realFlagName = fullFlagName.substring(idx+1);
-
- StorageRequestMessage.FlagOverrideMessage.Builder override_msg_builder =
- StorageRequestMessage.FlagOverrideMessage.newBuilder();
- override_msg_builder.setPackageName(packageName);
- override_msg_builder.setFlagName(realFlagName);
- override_msg_builder.setFlagValue(flagValue);
- override_msg_builder.setIsLocal(true);
-
- StorageRequestMessage.Builder request_builder = StorageRequestMessage.newBuilder();
- request_builder.setFlagOverrideMessage(override_msg_builder.build());
- requests_builder.addMsgs(request_builder.build());
+ writeFlagOverrideRequest(requests, packageName, realFlagName, flagValue, true);
+ ++num_requests;
}
- StorageRequestMessages requests = requests_builder.build();
- StorageReturnMessages acks = sendAconfigdRequests(requests);
+
+ if (num_requests == 0) {
+ return;
+ }
+
+ // send requests to aconfigd and obtain the return byte buffer
+ ProtoInputStream returns = sendAconfigdRequests(requests);
+
+ // deserialize back using proto input stream
+ try {
+ parseAndLogAconfigdReturn(returns);
+ } catch (IOException ioe) {
+ logErr("failed to parse aconfigd return", ioe);
+ }
}
public static SettingsToPropertiesMapper start(ContentResolver contentResolver) {
@@ -517,7 +572,7 @@
for (String property_name : property_names) {
String[] segments = property_name.split("\\.");
if (segments.length < 3) {
- log("failed to extract category name from property " + property_name);
+ logErr("failed to extract category name from property " + property_name);
continue;
}
categories.add(segments[2]);
@@ -545,14 +600,16 @@
return propertyName;
}
+
/**
* stage flags in aconfig new storage
* @param propsToStage
*/
@VisibleForTesting
static void stageFlagsInNewStorage(HashMap<String, HashMap<String, String>> propsToStage) {
- // create storage request proto
- StorageRequestMessages.Builder requests_builder = StorageRequestMessages.newBuilder();
+ // write aconfigd requests proto to proto output stream
+ int num_requests = 0;
+ ProtoOutputStream requests = new ProtoOutputStream();
for (HashMap.Entry<String, HashMap<String, String>> entry : propsToStage.entrySet()) {
String actualNamespace = entry.getKey();
HashMap<String, String> flagValuesToStage = entry.getValue();
@@ -560,26 +617,29 @@
String stagedValue = flagValuesToStage.get(fullFlagName);
int idx = fullFlagName.lastIndexOf(".");
if (idx == -1) {
- log("invalid flag name: " + fullFlagName);
+ logErr("invalid flag name: " + fullFlagName);
continue;
}
String packageName = fullFlagName.substring(0, idx);
String flagName = fullFlagName.substring(idx+1);
-
- StorageRequestMessage.FlagOverrideMessage.Builder override_msg_builder =
- StorageRequestMessage.FlagOverrideMessage.newBuilder();
- override_msg_builder.setPackageName(packageName);
- override_msg_builder.setFlagName(flagName);
- override_msg_builder.setFlagValue(stagedValue);
- override_msg_builder.setIsLocal(false);
-
- StorageRequestMessage.Builder request_builder = StorageRequestMessage.newBuilder();
- request_builder.setFlagOverrideMessage(override_msg_builder.build());
- requests_builder.addMsgs(request_builder.build());
+ writeFlagOverrideRequest(requests, packageName, flagName, stagedValue, false);
+ ++num_requests;
}
}
- StorageRequestMessages requests = requests_builder.build();
- StorageReturnMessages acks = sendAconfigdRequests(requests);
+
+ if (num_requests == 0) {
+ return;
+ }
+
+ // send requests to aconfigd and obtain the return
+ ProtoInputStream returns = sendAconfigdRequests(requests);
+
+ // deserialize back using proto input stream
+ try {
+ parseAndLogAconfigdReturn(returns);
+ } catch (IOException ioe) {
+ logErr("failed to parse aconfigd return", ioe);
+ }
}
/**
@@ -620,7 +680,7 @@
for (String flagName : properties.getKeyset()) {
int idx = flagName.indexOf(NAMESPACE_REBOOT_STAGING_DELIMITER);
if (idx == -1 || idx == flagName.length() - 1 || idx == 0) {
- log("invalid staged flag: " + flagName);
+ logErr("invalid staged flag: " + flagName);
continue;
}
String actualNamespace = flagName.substring(0, idx);
@@ -671,7 +731,7 @@
}
value = "";
} else if (value.length() > SYSTEM_PROPERTY_MAX_LENGTH) {
- log("key=" + key + " value=" + value + " exceeds system property max length.");
+ logErr("key=" + key + " value=" + value + " exceeds system property max length.");
return;
}
@@ -681,11 +741,11 @@
// Failure to set a property can be caused by SELinux denial. This usually indicates
// that the property wasn't allowlisted in sepolicy.
// No need to report it on all user devices, only on debug builds.
- log("Unable to set property " + key + " value '" + value + "'", e);
+ logErr("Unable to set property " + key + " value '" + value + "'", e);
}
}
- private static void log(String msg, Exception e) {
+ private static void logErr(String msg, Exception e) {
if (Build.IS_DEBUGGABLE) {
Slog.wtf(TAG, msg, e);
} else {
@@ -693,7 +753,7 @@
}
}
- private static void log(String msg) {
+ private static void logErr(String msg) {
if (Build.IS_DEBUGGABLE) {
Slog.wtf(TAG, msg);
} else {
@@ -711,7 +771,7 @@
br.close();
} catch (IOException ioe) {
- log("failed to read file " + RESET_RECORD_FILE_PATH, ioe);
+ logErr("failed to read file " + RESET_RECORD_FILE_PATH, ioe);
}
return content;
}
diff --git a/services/core/java/com/android/server/display/color/ColorDisplayService.java b/services/core/java/com/android/server/display/color/ColorDisplayService.java
index 0bb93a9..3883604 100644
--- a/services/core/java/com/android/server/display/color/ColorDisplayService.java
+++ b/services/core/java/com/android/server/display/color/ColorDisplayService.java
@@ -78,6 +78,7 @@
import com.android.server.DisplayThread;
import com.android.server.LocalServices;
import com.android.server.SystemService;
+import com.android.server.accessibility.Flags;
import com.android.server.display.feature.DisplayManagerFlags;
import com.android.server.twilight.TwilightListener;
import com.android.server.twilight.TwilightManager;
@@ -356,6 +357,11 @@
case Secure.ACCESSIBILITY_DISPLAY_DALTONIZER:
onAccessibilityDaltonizerChanged();
break;
+ case Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_SATURATION_LEVEL:
+ if (Flags.enableColorCorrectionSaturation()) {
+ onAccessibilityDaltonizerChanged();
+ }
+ break;
case Secure.DISPLAY_WHITE_BALANCE_ENABLED:
updateDisplayWhiteBalanceStatus();
break;
@@ -398,6 +404,11 @@
false /* notifyForDescendants */, mContentObserver, mCurrentUser);
cr.registerContentObserver(Secure.getUriFor(Secure.REDUCE_BRIGHT_COLORS_LEVEL),
false /* notifyForDescendants */, mContentObserver, mCurrentUser);
+ if (Flags.enableColorCorrectionSaturation()) {
+ cr.registerContentObserver(
+ Secure.getUriFor(Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_SATURATION_LEVEL),
+ false /* notifyForDescendants */, mContentObserver, mCurrentUser);
+ }
// Apply the accessibility settings first, since they override most other settings.
onAccessibilityInversionChanged();
@@ -597,21 +608,31 @@
if (mCurrentUser == UserHandle.USER_NULL) {
return;
}
+ var contentResolver = getContext().getContentResolver();
final int daltonizerMode = isAccessiblityDaltonizerEnabled()
- ? Secure.getIntForUser(getContext().getContentResolver(),
- Secure.ACCESSIBILITY_DISPLAY_DALTONIZER,
- AccessibilityManager.DALTONIZER_CORRECT_DEUTERANOMALY, mCurrentUser)
+ ? Secure.getIntForUser(contentResolver,
+ Secure.ACCESSIBILITY_DISPLAY_DALTONIZER,
+ AccessibilityManager.DALTONIZER_CORRECT_DEUTERANOMALY, mCurrentUser)
: AccessibilityManager.DALTONIZER_DISABLED;
+ int saturation = NOT_SET;
+ if (Flags.enableColorCorrectionSaturation()) {
+ saturation = Secure.getIntForUser(
+ contentResolver,
+ Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_SATURATION_LEVEL,
+ NOT_SET,
+ mCurrentUser);
+ }
+
final DisplayTransformManager dtm = getLocalService(DisplayTransformManager.class);
if (daltonizerMode == AccessibilityManager.DALTONIZER_SIMULATE_MONOCHROMACY) {
// Monochromacy isn't supported by the native Daltonizer implementation; use grayscale.
dtm.setColorMatrix(DisplayTransformManager.LEVEL_COLOR_MATRIX_GRAYSCALE,
MATRIX_GRAYSCALE);
- dtm.setDaltonizerMode(AccessibilityManager.DALTONIZER_DISABLED);
+ dtm.setDaltonizerMode(AccessibilityManager.DALTONIZER_DISABLED, saturation);
} else {
dtm.setColorMatrix(DisplayTransformManager.LEVEL_COLOR_MATRIX_GRAYSCALE, null);
- dtm.setDaltonizerMode(daltonizerMode);
+ dtm.setDaltonizerMode(daltonizerMode, saturation);
}
}
diff --git a/services/core/java/com/android/server/display/color/DisplayTransformManager.java b/services/core/java/com/android/server/display/color/DisplayTransformManager.java
index 0dba9e1..a76c427 100644
--- a/services/core/java/com/android/server/display/color/DisplayTransformManager.java
+++ b/services/core/java/com/android/server/display/color/DisplayTransformManager.java
@@ -16,6 +16,7 @@
package com.android.server.display.color;
+import android.annotation.IntRange;
import android.app.ActivityTaskManager;
import android.hardware.display.ColorDisplayManager;
import android.opengl.Matrix;
@@ -111,9 +112,15 @@
/**
* Lock used for synchronize access to {@link #mDaltonizerMode}.
*/
- private final Object mDaltonizerModeLock = new Object();
+ @VisibleForTesting
+ final Object mDaltonizerModeLock = new Object();
+ @VisibleForTesting
@GuardedBy("mDaltonizerModeLock")
- private int mDaltonizerMode = -1;
+ int mDaltonizerMode = -1;
+
+ @VisibleForTesting
+ @GuardedBy("mDaltonizerModeLock")
+ int mDaltonizerLevel = -1;
private static final IBinder sFlinger = ServiceManager.getService(SURFACE_FLINGER);
@@ -168,12 +175,15 @@
* various types of color blindness.
*
* @param mode the new Daltonization mode, or -1 to disable
+ * @param level the level of saturation for color correction [-1,10] inclusive. -1 for when
+ * it is not set.
*/
- public void setDaltonizerMode(int mode) {
+ public void setDaltonizerMode(int mode, @IntRange(from = -1, to = 10) int level) {
synchronized (mDaltonizerModeLock) {
- if (mDaltonizerMode != mode) {
+ if (mDaltonizerMode != mode || mDaltonizerLevel != level) {
mDaltonizerMode = mode;
- applyDaltonizerMode(mode);
+ mDaltonizerLevel = level;
+ applyDaltonizerMode(mode, level);
}
}
}
@@ -223,10 +233,11 @@
/**
* Propagates the provided Daltonization mode to the SurfaceFlinger.
*/
- private static void applyDaltonizerMode(int mode) {
+ private static void applyDaltonizerMode(int mode, int level) {
final Parcel data = Parcel.obtain();
data.writeInterfaceToken("android.ui.ISurfaceComposer");
data.writeInt(mode);
+ data.writeInt(level);
try {
sFlinger.transact(SURFACE_FLINGER_TRANSACTION_DALTONIZER, data, null, 0);
} catch (RemoteException ex) {
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
index 46061a5..275c930 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
@@ -206,6 +206,10 @@
launchDeviceDiscovery();
startQueuedActions();
if (!mDelayedMessageBuffer.isBuffered(Constants.MESSAGE_ACTIVE_SOURCE)) {
+ if (hasAction(RequestActiveSourceAction.class)) {
+ Slog.i(TAG, "RequestActiveSourceAction is in progress. Restarting.");
+ removeAction(RequestActiveSourceAction.class);
+ }
addAndStartAction(new RequestActiveSourceAction(this, new IHdmiControlCallback.Stub() {
@Override
public void onComplete(int result) {
@@ -1308,6 +1312,8 @@
mService.sendCecCommand(
HdmiCecMessageBuilder.buildActiveSource(
getDeviceInfo().getLogicalAddress(), activePath));
+ updateActiveSource(getDeviceInfo().getLogicalAddress(), activePath,
+ "HdmiCecLocalDeviceTv#launchRoutingControl()");
}
}
}
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index 936e8b6..05c4aa6 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -1646,6 +1646,13 @@
case Constants.MESSAGE_ROUTING_CHANGE:
case Constants.MESSAGE_SET_STREAM_PATH:
case Constants.MESSAGE_TEXT_VIEW_ON:
+ // RequestActiveSourceAction is started after the TV finished logical address
+ // allocation. This action is used by the TV to get the active source from the CEC
+ // network. If the TV sent a source changing CEC message, this action does not have
+ // to continue anymore.
+ if (isTvDevice()) {
+ tv().removeAction(RequestActiveSourceAction.class);
+ }
sendCecCommandWithRetries(command, callback);
break;
default:
diff --git a/services/core/java/com/android/server/hdmi/RequestActiveSourceAction.java b/services/core/java/com/android/server/hdmi/RequestActiveSourceAction.java
index d250416..539a00d 100644
--- a/services/core/java/com/android/server/hdmi/RequestActiveSourceAction.java
+++ b/services/core/java/com/android/server/hdmi/RequestActiveSourceAction.java
@@ -21,13 +21,20 @@
import android.util.Slog;
/**
- * Feature action that sends <Request Active Source> message and waits for <Active Source>.
+ * Feature action that sends <Request Active Source> message and waits for <Active Source> on TV
+ * panels.
+ * This action has a delay before sending <Request Active Source>. This is because it should wait
+ * for a possible request from LauncherX and can be cancelled if an <Active Source> message was
+ * received or the TV switched to another input.
*/
public class RequestActiveSourceAction extends HdmiCecFeatureAction {
private static final String TAG = "RequestActiveSourceAction";
+ // State to wait for the LauncherX to call the CEC API.
+ private static final int STATE_WAIT_FOR_LAUNCHERX_API_CALL = 1;
+
// State to wait for the <Active Source> message.
- private static final int STATE_WAIT_FOR_ACTIVE_SOURCE = 1;
+ private static final int STATE_WAIT_FOR_ACTIVE_SOURCE = 2;
// Number of retries <Request Active Source> is sent if no device answers this message.
private static final int MAX_SEND_RETRY_COUNT = 1;
@@ -43,10 +50,12 @@
boolean start() {
Slog.v(TAG, "RequestActiveSourceAction started.");
- sendCommand(HdmiCecMessageBuilder.buildRequestActiveSource(getSourceAddress()));
+ mState = STATE_WAIT_FOR_LAUNCHERX_API_CALL;
- mState = STATE_WAIT_FOR_ACTIVE_SOURCE;
- addTimer(mState, HdmiConfig.TIMEOUT_MS);
+ // We wait for default timeout to allow the message triggered by the LauncherX API call to
+ // be sent by the TV and another default timeout in case the message has to be answered
+ // (e.g. TV sent a <Set Stream Path> or <Routing Change>).
+ addTimer(mState, HdmiConfig.TIMEOUT_MS * 2);
return true;
}
@@ -65,13 +74,23 @@
if (mState != state) {
return;
}
- if (mState == STATE_WAIT_FOR_ACTIVE_SOURCE) {
- if (mSendRetryCount++ < MAX_SEND_RETRY_COUNT) {
+
+ switch (mState) {
+ case STATE_WAIT_FOR_LAUNCHERX_API_CALL:
+ mState = STATE_WAIT_FOR_ACTIVE_SOURCE;
sendCommand(HdmiCecMessageBuilder.buildRequestActiveSource(getSourceAddress()));
addTimer(mState, HdmiConfig.TIMEOUT_MS);
- } else {
- finishWithCallback(HdmiControlManager.RESULT_TIMEOUT);
- }
+ return;
+ case STATE_WAIT_FOR_ACTIVE_SOURCE:
+ if (mSendRetryCount++ < MAX_SEND_RETRY_COUNT) {
+ sendCommand(HdmiCecMessageBuilder.buildRequestActiveSource(getSourceAddress()));
+ addTimer(mState, HdmiConfig.TIMEOUT_MS);
+ } else {
+ finishWithCallback(HdmiControlManager.RESULT_TIMEOUT);
+ }
+ return;
+ default:
+ return;
}
}
}
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index 8685d2c..8e85b81 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -2671,24 +2671,6 @@
return null;
}
- private static class PointerDisplayIdChangedArgs {
- final int mPointerDisplayId;
- final float mXPosition;
- final float mYPosition;
- PointerDisplayIdChangedArgs(int pointerDisplayId, float xPosition, float yPosition) {
- mPointerDisplayId = pointerDisplayId;
- mXPosition = xPosition;
- mYPosition = yPosition;
- }
- }
-
- // Native callback.
- @SuppressWarnings("unused")
- @VisibleForTesting
- void onPointerDisplayIdChanged(int pointerDisplayId, float xPosition, float yPosition) {
- // TODO(b/311416205): Remove.
- }
-
@Override
@EnforcePermission(Manifest.permission.MONITOR_STICKY_MODIFIER_STATE)
public void registerStickyModifierStateListener(
diff --git a/services/core/java/com/android/server/inputmethod/ImeTrackerService.java b/services/core/java/com/android/server/inputmethod/ImeTrackerService.java
index 1c14fc1..fff0e6e 100644
--- a/services/core/java/com/android/server/inputmethod/ImeTrackerService.java
+++ b/services/core/java/com/android/server/inputmethod/ImeTrackerService.java
@@ -133,6 +133,13 @@
}
}
+ @Override
+ public void onDispatched(@NonNull ImeTracker.Token statsToken) {
+ synchronized (mLock) {
+ mHistory.setFinished(statsToken, ImeTracker.STATUS_SUCCESS, ImeTracker.PHASE_NOT_SET);
+ }
+ }
+
/**
* Updates the IME request tracking token with new information available in IMMS.
*
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
index b709174..e862c7e 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
@@ -443,7 +443,16 @@
mCurId = info.getId();
mLastBindTime = SystemClock.uptimeMillis();
- addFreshWindowToken();
+ final int displayIdToShowIme = mService.getDisplayIdToShowImeLocked();
+ mCurToken = new Binder();
+ mService.setCurTokenDisplayIdLocked(displayIdToShowIme);
+ if (DEBUG) {
+ Slog.v(TAG, "Adding window token: " + mCurToken + " for display: "
+ + displayIdToShowIme);
+ }
+ mWindowManagerInternal.addWindowToken(mCurToken,
+ WindowManager.LayoutParams.TYPE_INPUT_METHOD,
+ displayIdToShowIme, null /* options */);
return new InputBindResult(
InputBindResult.ResultCode.SUCCESS_WAITING_IME_BINDING,
null, null, null, mCurId, mCurSeq, false);
@@ -471,22 +480,6 @@
}
@GuardedBy("ImfLock.class")
- private void addFreshWindowToken() {
- int displayIdToShowIme = mService.getDisplayIdToShowImeLocked();
- mCurToken = new Binder();
-
- mService.setCurTokenDisplayIdLocked(displayIdToShowIme);
-
- if (DEBUG) {
- Slog.v(TAG, "Adding window token: " + mCurToken + " for display: "
- + displayIdToShowIme);
- }
- mWindowManagerInternal.addWindowToken(mCurToken,
- WindowManager.LayoutParams.TYPE_INPUT_METHOD,
- displayIdToShowIme, null /* options */);
- }
-
- @GuardedBy("ImfLock.class")
private void unbindMainConnection() {
mContext.unbindService(mMainConnection);
mHasMainConnection = false;
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodInfoUtils.java b/services/core/java/com/android/server/inputmethod/InputMethodInfoUtils.java
index 6339686..458c359 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodInfoUtils.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodInfoUtils.java
@@ -22,6 +22,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
+import android.os.Parcel;
import android.text.TextUtils;
import android.util.Slog;
import android.view.inputmethod.InputMethodInfo;
@@ -323,4 +324,24 @@
return SubtypeUtils.containsSubtypeOf(imi, requiredLocale, checkCountry,
requiredSubtypeMode);
}
+
+ /**
+ * Marshals the given {@link InputMethodInfo} into a byte array.
+ *
+ * @param imi {@link InputMethodInfo} to be marshalled
+ * @return a byte array where the given {@link InputMethodInfo} is marshalled
+ */
+ @NonNull
+ static byte[] marshal(@NonNull InputMethodInfo imi) {
+ Parcel parcel = null;
+ try {
+ parcel = Parcel.obtain();
+ parcel.writeTypedObject(imi, 0);
+ return parcel.marshall();
+ } finally {
+ if (parcel != null) {
+ parcel.recycle();
+ }
+ }
+ }
}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 954f9bc..46c5772 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -262,6 +262,7 @@
private static final int NOT_A_SUBTYPE_ID = InputMethodUtils.NOT_A_SUBTYPE_ID;
private static final String TAG_TRY_SUPPRESSING_IME_SWITCHER = "TrySuppressingImeSwitcher";
private static final String HANDLER_THREAD_NAME = "android.imms";
+ private static final String PACKAGE_MONITOR_THREAD_NAME = "android.imms2";
/**
* When set, {@link #startInputUncheckedLocked} will return
@@ -285,6 +286,9 @@
final Resources mRes;
private final Handler mHandler;
+ @NonNull
+ private final Handler mPackageMonitorHandler;
+
@MultiUserUnawareField
@UserIdInt
@GuardedBy("ImfLock.class")
@@ -1288,13 +1292,14 @@
}
public InputMethodManagerService(Context context) {
- this(context, null, null);
+ this(context, null, null, null);
}
@VisibleForTesting
InputMethodManagerService(
Context context,
@Nullable ServiceThread serviceThreadForTesting,
+ @Nullable ServiceThread packageMonitorThreadForTesting,
@Nullable IntFunction<InputMethodBindingController> bindingControllerForTesting) {
synchronized (ImfLock.class) {
mContext = context;
@@ -1312,6 +1317,17 @@
true /* allowIo */);
thread.start();
mHandler = Handler.createAsync(thread.getLooper(), this);
+ {
+ final ServiceThread packageMonitorThread =
+ packageMonitorThreadForTesting != null
+ ? packageMonitorThreadForTesting
+ : new ServiceThread(
+ PACKAGE_MONITOR_THREAD_NAME,
+ Process.THREAD_PRIORITY_FOREGROUND,
+ true /* allowIo */);
+ packageMonitorThread.start();
+ mPackageMonitorHandler = Handler.createAsync(packageMonitorThread.getLooper());
+ }
SystemLocaleWrapper.onStart(context, this::onActionLocaleChanged, mHandler);
mImeTrackerService = new ImeTrackerService(serviceThreadForTesting != null
? serviceThreadForTesting.getLooper() : Looper.getMainLooper());
@@ -1487,7 +1503,8 @@
// Note that in b/197848765 we want to see if we can keep the binding alive for better
// profile switching.
final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
- userData.mBindingController.unbindCurrentMethod();
+ final var bindingController = userData.mBindingController;
+ bindingController.unbindCurrentMethod();
unbindCurrentClientLocked(UnbindReason.SWITCH_USER);
@@ -1585,7 +1602,7 @@
}
}, "Lazily initialize IMMS#mImeDrawsImeNavBarRes");
- mMyPackageMonitor.register(mContext, UserHandle.ALL, mHandler);
+ mMyPackageMonitor.register(mContext, UserHandle.ALL, mPackageMonitorHandler);
mSettingsObserver.registerContentObserverLocked(currentUserId);
final IntentFilter broadcastFilterForAllUsers = new IntentFilter();
@@ -1706,9 +1723,10 @@
// Check if selected IME of current user supports handwriting.
if (userId == mCurrentUserId) {
final var userData = mUserDataRepository.getOrCreate(userId);
- return userData.mBindingController.supportsStylusHandwriting()
+ final var bindingController = userData.mBindingController;
+ return bindingController.supportsStylusHandwriting()
&& (!connectionless
- || userData.mBindingController.supportsConnectionlessStylusHandwriting());
+ || bindingController.supportsConnectionlessStylusHandwriting());
}
final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
final InputMethodInfo imi = settings.getMethodMap().get(
@@ -1904,7 +1922,8 @@
// following dependencies also need to be user independent: mCurClient, mBoundToMethod,
// getCurMethodLocked(), and mMenuController.
final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
- mCurClient.mClient.onUnbindMethod(userData.mBindingController.getSequenceNumber(),
+ final var bindingController = userData.mBindingController;
+ mCurClient.mClient.onUnbindMethod(bindingController.getSequenceNumber(),
unbindClientReason);
mCurClient.mSessionRequested = false;
mCurClient.mSessionRequestedForAccessibility = false;
@@ -1984,13 +2003,14 @@
final boolean restarting = !initial;
final Binder startInputToken = new Binder();
final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
+ final var bindingController = userData.mBindingController;
final StartInputInfo info = new StartInputInfo(mCurrentUserId,
getCurTokenLocked(),
- mCurTokenDisplayId, userData.mBindingController.getCurId(), startInputReason,
+ getCurTokenDisplayIdLocked(), bindingController.getCurId(), startInputReason,
restarting, UserHandle.getUserId(mCurClient.mUid),
mCurClient.mSelfReportedDisplayId, mImeBindingState.mFocusedWindow, mCurEditorInfo,
mImeBindingState.mFocusedWindowSoftInputMode,
- userData.mBindingController.getSequenceNumber());
+ bindingController.getSequenceNumber());
mImeTargetWindowMap.put(startInputToken, mImeBindingState.mFocusedWindow);
mStartInputHistory.addEntry(info);
@@ -2002,7 +2022,7 @@
if (mCurrentUserId == UserHandle.getUserId(
mCurClient.mUid)) {
mPackageManagerInternal.grantImplicitAccess(mCurrentUserId, null /* intent */,
- UserHandle.getAppId(userData.mBindingController.getCurMethodUid()),
+ UserHandle.getAppId(bindingController.getCurMethodUid()),
mCurClient.mUid, true /* direct */);
}
@@ -2023,20 +2043,20 @@
null /* resultReceiver */, SoftInputShowHideReason.ATTACH_NEW_INPUT);
}
- final var curId = userData.mBindingController.getCurId();
+ final var curId = bindingController.getCurId();
final InputMethodInfo curInputMethodInfo = InputMethodSettingsRepository.get(mCurrentUserId)
.getMethodMap().get(curId);
final boolean suppressesSpellChecker =
curInputMethodInfo != null && curInputMethodInfo.suppressesSpellChecker();
final SparseArray<IAccessibilityInputMethodSession> accessibilityInputMethodSessions =
createAccessibilityInputMethodSessions(mCurClient.mAccessibilitySessions);
- if (userData.mBindingController.supportsStylusHandwriting() && hasSupportedStylusLocked()) {
+ if (bindingController.supportsStylusHandwriting() && hasSupportedStylusLocked()) {
mHwController.setInkWindowInitializer(new InkWindowInitializer());
}
return new InputBindResult(InputBindResult.ResultCode.SUCCESS_WITH_IME_SESSION,
session.mSession, accessibilityInputMethodSessions,
(session.mChannel != null ? session.mChannel.dup() : null),
- curId, userData.mBindingController.getSequenceNumber(), suppressesSpellChecker);
+ curId, bindingController.getSequenceNumber(), suppressesSpellChecker);
}
@GuardedBy("ImfLock.class")
@@ -2130,7 +2150,8 @@
final boolean connectionWasActive = mCurInputConnection != null;
// Bump up the sequence for this client and attach it.
- userData.mBindingController.advanceSequenceNumber();
+ final var bindingController = userData.mBindingController;
+ bindingController.advanceSequenceNumber();
mCurClient = cs;
mCurInputConnection = inputConnection;
@@ -2153,7 +2174,6 @@
if (connectionIsActive != connectionWasActive) {
mInputManagerInternal.notifyInputMethodConnectionActive(connectionIsActive);
}
- final var bindingController = userData.mBindingController;
// If configured, we want to avoid starting up the IME if it is not supposed to be showing
if (shouldPreventImeStartupLocked(selectedMethodId, startInputFlags,
@@ -2171,7 +2191,7 @@
// display ID.
final String curId = bindingController.getCurId();
if (curId != null && curId.equals(bindingController.getSelectedMethodId())
- && mDisplayIdToShowIme == mCurTokenDisplayId) {
+ && mDisplayIdToShowIme == getCurTokenDisplayIdLocked()) {
if (cs.mCurSession != null) {
// Fast case: if we are already connected to the input method,
// then just return it.
@@ -2304,7 +2324,8 @@
@Nullable
private InputBindResult tryReuseConnectionLocked(@NonNull UserDataRepository.UserData userData,
@NonNull ClientState cs) {
- if (userData.mBindingController.hasMainConnection()) {
+ final var bindingController = userData.mBindingController;
+ if (bindingController.hasMainConnection()) {
if (getCurMethodLocked() != null) {
// Return to client, and we will get back with it when
// we have had a session made for it.
@@ -2313,10 +2334,10 @@
return new InputBindResult(
InputBindResult.ResultCode.SUCCESS_WAITING_IME_SESSION,
null, null, null,
- userData.mBindingController.getCurId(),
- userData.mBindingController.getSequenceNumber(), false);
+ bindingController.getCurId(),
+ bindingController.getSequenceNumber(), false);
} else {
- final long lastBindTime = userData.mBindingController.getLastBindTime();
+ final long lastBindTime = bindingController.getLastBindTime();
long bindingDuration = SystemClock.uptimeMillis() - lastBindTime;
if (bindingDuration < TIME_TO_RECONNECT) {
// In this case we have connected to the service, but
@@ -2329,8 +2350,8 @@
return new InputBindResult(
InputBindResult.ResultCode.SUCCESS_WAITING_IME_BINDING,
null, null, null,
- userData.mBindingController.getCurId(),
- userData.mBindingController.getSequenceNumber(), false);
+ bindingController.getCurId(),
+ bindingController.getSequenceNumber(), false);
} else {
EventLog.writeEvent(EventLogTags.IMF_FORCE_RECONNECT_IME,
getSelectedMethodIdLocked(), bindingDuration, 0);
@@ -2377,7 +2398,7 @@
void initializeImeLocked(@NonNull IInputMethodInvoker inputMethod, @NonNull IBinder token) {
if (DEBUG) {
Slog.v(TAG, "Sending attach of token: " + token + " for display: "
- + mCurTokenDisplayId);
+ + getCurTokenDisplayIdLocked());
}
inputMethod.initializeInternal(token, new InputMethodPrivilegedOperationsImpl(this, token),
getInputMethodNavButtonFlagsLocked());
@@ -2457,13 +2478,14 @@
@GuardedBy("ImfLock.class")
void resetCurrentMethodAndClientLocked(@UnbindReason int unbindClientReason) {
- final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
- userData.mBindingController.setSelectedMethodId(null);
+ final var bindingController =
+ mUserDataRepository.getOrCreate(mCurrentUserId).mBindingController;
+ bindingController.setSelectedMethodId(null);
// Callback before clean-up binding states.
// TODO(b/338461930): Check if this is still necessary or not.
onUnbindCurrentMethodByReset();
- userData.mBindingController.unbindCurrentMethod();
+ bindingController.unbindCurrentMethod();
unbindCurrentClientLocked(unbindClientReason);
}
@@ -2666,9 +2688,10 @@
}
// Whether the current display has a navigation bar. When this is false (e.g. emulator),
// the IME should not draw the IME navigation bar.
+ final int tokenDisplayId = getCurTokenDisplayIdLocked();
final boolean hasNavigationBar = mWindowManagerInternal
- .hasNavigationBar(mCurTokenDisplayId != INVALID_DISPLAY
- ? mCurTokenDisplayId : DEFAULT_DISPLAY);
+ .hasNavigationBar(tokenDisplayId != INVALID_DISPLAY
+ ? tokenDisplayId : DEFAULT_DISPLAY);
final boolean canImeDrawsImeNavBar =
mImeDrawsImeNavBarRes != null && mImeDrawsImeNavBarRes.get() && hasNavigationBar;
final boolean shouldShowImeSwitcherWhenImeIsShown = shouldShowImeSwitcherLocked(
@@ -2764,8 +2787,8 @@
// Note that we still need to update IME status when focusing external display
// that does not support system decoration and fallback to show IME on default
// display since it is intentional behavior.
- if (mCurTokenDisplayId != topFocusedDisplayId
- && mCurTokenDisplayId != FALLBACK_DISPLAY_ID) {
+ final int tokenDisplayId = getCurTokenDisplayIdLocked();
+ if (tokenDisplayId != topFocusedDisplayId && tokenDisplayId != FALLBACK_DISPLAY_ID) {
return;
}
mImeWindowVis = vis;
@@ -2828,7 +2851,7 @@
Slog.d(TAG, "IME window vis: " + vis
+ " active: " + (vis & InputMethodService.IME_ACTIVE)
+ " inv: " + (vis & InputMethodService.IME_INVISIBLE)
- + " displayId: " + mCurTokenDisplayId);
+ + " displayId: " + getCurTokenDisplayIdLocked());
}
final IBinder focusedWindowToken = mImeBindingState != null
? mImeBindingState.mFocusedWindow : null;
@@ -2857,7 +2880,7 @@
}
final boolean needsToShowImeSwitcher = shouldShowImeSwitcherLocked(vis);
if (mStatusBarManagerInternal != null) {
- mStatusBarManagerInternal.setImeWindowStatus(mCurTokenDisplayId,
+ mStatusBarManagerInternal.setImeWindowStatus(getCurTokenDisplayIdLocked(),
getCurTokenLocked(), vis, backDisposition, needsToShowImeSwitcher);
}
} finally {
@@ -3546,13 +3569,14 @@
final InputBindResult result;
synchronized (ImfLock.class) {
final var userData = mUserDataRepository.getOrCreate(userId);
+ final var bindingController = userData.mBindingController;
// If the system is not yet ready, we shouldn't be running third party code.
if (!mSystemReady) {
return new InputBindResult(
InputBindResult.ResultCode.ERROR_SYSTEM_NOT_READY,
null /* method */, null /* accessibilitySessions */, null /* channel */,
getSelectedMethodIdLocked(),
- userData.mBindingController.getSequenceNumber(),
+ bindingController.getSequenceNumber(),
false /* isInputMethodSuppressingSpellChecker */);
}
final ClientState cs = mClientController.getClient(client.asBinder());
@@ -3761,7 +3785,7 @@
// window token removed.
// Note that we can trust client's display ID as long as it matches
// to the display ID obtained from the window.
- if (cs.mSelfReportedDisplayId != mCurTokenDisplayId) {
+ if (cs.mSelfReportedDisplayId != getCurTokenDisplayIdLocked()) {
userData.mBindingController.unbindCurrentMethod();
}
}
@@ -4143,7 +4167,7 @@
}
// This should probably use the caller's display id, but because this is unsupported
// and maintained only for compatibility, there's no point in fixing it.
- curTokenDisplayId = mCurTokenDisplayId;
+ curTokenDisplayId = getCurTokenDisplayIdLocked();
}
return mWindowManagerInternal.getInputMethodWindowVisibleHeight(curTokenDisplayId);
});
@@ -4224,8 +4248,9 @@
// a new Stylus is detected. If IME supports handwriting, and we don't have
// handwriting initialized, lets do it now.
final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
+ final var bindingController = userData.mBindingController;
if (!mHwController.getCurrentRequestId().isPresent()
- && userData.mBindingController.supportsStylusHandwriting()) {
+ && bindingController.supportsStylusHandwriting()) {
scheduleResetStylusHandwriting();
}
}
@@ -4407,9 +4432,10 @@
private void dumpDebug(ProtoOutputStream proto, long fieldId) {
synchronized (ImfLock.class) {
final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
+ final var bindingController = userData.mBindingController;
final long token = proto.start(fieldId);
proto.write(CUR_METHOD_ID, getSelectedMethodIdLocked());
- proto.write(CUR_SEQ, userData.mBindingController.getSequenceNumber());
+ proto.write(CUR_SEQ, bindingController.getSequenceNumber());
proto.write(CUR_CLIENT, Objects.toString(mCurClient));
mImeBindingState.dumpDebug(proto, mWindowManagerInternal);
proto.write(LAST_IME_TARGET_WINDOW_NAME,
@@ -4419,13 +4445,13 @@
if (mCurEditorInfo != null) {
mCurEditorInfo.dumpDebug(proto, CUR_ATTRIBUTE);
}
- proto.write(CUR_ID, userData.mBindingController.getCurId());
+ proto.write(CUR_ID, bindingController.getCurId());
mVisibilityStateComputer.dumpDebug(proto, fieldId);
proto.write(IN_FULLSCREEN_MODE, mInFullscreenMode);
proto.write(CUR_TOKEN, Objects.toString(getCurTokenLocked()));
- proto.write(CUR_TOKEN_DISPLAY_ID, mCurTokenDisplayId);
+ proto.write(CUR_TOKEN_DISPLAY_ID, getCurTokenDisplayIdLocked());
proto.write(SYSTEM_READY, mSystemReady);
- proto.write(HAVE_CONNECTION, userData.mBindingController.hasMainConnection());
+ proto.write(HAVE_CONNECTION, bindingController.hasMainConnection());
proto.write(BOUND_TO_METHOD, mBoundToMethod);
proto.write(IS_INTERACTIVE, mIsInteractive);
proto.write(BACK_DISPOSITION, mBackDisposition);
@@ -4537,7 +4563,8 @@
final IBinder requestToken = mVisibilityStateComputer.getWindowTokenFrom(requestImeToken);
final WindowManagerInternal.ImeTargetInfo info =
mWindowManagerInternal.onToggleImeRequested(
- show, mImeBindingState.mFocusedWindow, requestToken, mCurTokenDisplayId);
+ show, mImeBindingState.mFocusedWindow, requestToken,
+ getCurTokenDisplayIdLocked());
mSoftInputShowHideHistory.addEntry(new SoftInputShowHideHistory.Entry(
mImeBindingState.mFocusedWindowClient, mImeBindingState.mFocusedWindowEditorInfo,
info.focusedWindowName, mImeBindingState.mFocusedWindowSoftInputMode, reason,
@@ -4796,10 +4823,11 @@
case MSG_RESET_HANDWRITING: {
synchronized (ImfLock.class) {
final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
- if (userData.mBindingController.supportsStylusHandwriting()
+ final var bindingController = userData.mBindingController;
+ if (bindingController.supportsStylusHandwriting()
&& getCurMethodLocked() != null && hasSupportedStylusLocked()) {
Slog.d(TAG, "Initializing Handwriting Spy");
- mHwController.initializeHandwritingSpy(mCurTokenDisplayId);
+ mHwController.initializeHandwritingSpy(getCurTokenDisplayIdLocked());
} else {
mHwController.reset();
}
@@ -4822,11 +4850,12 @@
return true;
}
final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
+ final var bindingController = userData.mBindingController;
final HandwritingModeController.HandwritingSession session =
mHwController.startHandwritingSession(
msg.arg1 /*requestId*/,
msg.arg2 /*pid*/,
- userData.mBindingController.getCurMethodUid(),
+ bindingController.getCurMethodUid(),
mImeBindingState.mFocusedWindow);
if (session == null) {
Slog.e(TAG,
@@ -4878,8 +4907,9 @@
}
// TODO(b/325515685): user data must be retrieved by a userId parameter
final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
+ final var bindingController = userData.mBindingController;
if (mImePlatformCompatUtils.shouldUseSetInteractiveProtocol(
- userData.mBindingController.getCurMethodUid())) {
+ bindingController.getCurMethodUid())) {
// Handle IME visibility when interactive changed before finishing the input to
// ensure we preserve the last state as possible.
final ImeVisibilityResult imeVisRes = mVisibilityStateComputer.onInteractiveChanged(
@@ -5124,7 +5154,8 @@
@GuardedBy("ImfLock.class")
void sendOnNavButtonFlagsChangedLocked() {
final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
- final IInputMethodInvoker curMethod = userData.mBindingController.getCurMethod();
+ final var bindingController = userData.mBindingController;
+ final IInputMethodInvoker curMethod = bindingController.getCurMethod();
if (curMethod == null) {
// No need to send the data if the IME is not yet bound.
return;
@@ -5558,7 +5589,7 @@
//TODO(b/150843766): Check if Input Token is valid.
final IBinder curHostInputToken;
synchronized (ImfLock.class) {
- if (displayId != mCurTokenDisplayId) {
+ if (displayId != getCurTokenDisplayIdLocked()) {
return false;
}
curHostInputToken = mAutofillController.getCurHostInputToken();
@@ -5610,6 +5641,7 @@
IAccessibilityInputMethodSession session, @UserIdInt int userId) {
synchronized (ImfLock.class) {
final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
+ final var bindingController = userData.mBindingController;
// TODO(b/305829876): Implement user ID verification
if (mCurClient != null) {
clearClientSessionForAccessibilityLocked(mCurClient, accessibilityConnectionId);
@@ -5632,8 +5664,8 @@
final InputBindResult res = new InputBindResult(
InputBindResult.ResultCode.SUCCESS_WITH_ACCESSIBILITY_SESSION,
imeSession, accessibilityInputMethodSessions, /* channel= */ null,
- userData.mBindingController.getCurId(),
- userData.mBindingController.getSequenceNumber(),
+ bindingController.getCurId(),
+ bindingController.getSequenceNumber(),
/* isInputMethodSuppressingSpellChecker= */ false);
mCurClient.mClient.onBindAccessibilityService(res, accessibilityConnectionId);
}
@@ -5645,6 +5677,7 @@
@UserIdInt int userId) {
synchronized (ImfLock.class) {
final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
+ final var bindingController = userData.mBindingController;
// TODO(b/305829876): Implement user ID verification
if (mCurClient != null) {
if (DEBUG) {
@@ -5654,7 +5687,7 @@
// A11yManagerService unbinds the disabled accessibility service. We don't need
// to do it here.
mCurClient.mClient.onUnbindAccessibilityService(
- userData.mBindingController.getSequenceNumber(),
+ bindingController.getSequenceNumber(),
accessibilityConnectionId);
}
// We only have sessions when we bound to an input method. Remove this session
@@ -5877,18 +5910,19 @@
};
mClientController.forAllClients(clientControllerDump);
final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
+ final var bindingController = userData.mBindingController;
p.println(" mCurrentUserId=" + mCurrentUserId);
p.println(" mCurMethodId=" + getSelectedMethodIdLocked());
client = mCurClient;
p.println(" mCurClient=" + client + " mCurSeq="
- + userData.mBindingController.getSequenceNumber());
+ + bindingController.getSequenceNumber());
p.println(" mFocusedWindowPerceptible=" + mFocusedWindowPerceptible);
mImeBindingState.dump(/* prefix= */ " ", p);
- p.println(" mCurId=" + userData.mBindingController.getCurId()
- + " mHaveConnection=" + userData.mBindingController.hasMainConnection()
+ p.println(" mCurId=" + bindingController.getCurId()
+ + " mHaveConnection=" + bindingController.hasMainConnection()
+ " mBoundToMethod=" + mBoundToMethod + " mVisibleBound="
- + userData.mBindingController.isVisibleBound());
+ + bindingController.isVisibleBound());
p.println(" mUserDataRepository=");
// TODO(b/324907325): Remove the suppress warnings once b/324907325 is fixed.
@@ -5902,9 +5936,9 @@
mUserDataRepository.forAllUserData(userDataDump);
p.println(" mCurToken=" + getCurTokenLocked());
- p.println(" mCurTokenDisplayId=" + mCurTokenDisplayId);
+ p.println(" mCurTokenDisplayId=" + getCurTokenDisplayIdLocked());
p.println(" mCurHostInputToken=" + mAutofillController.getCurHostInputToken());
- p.println(" mCurIntent=" + userData.mBindingController.getCurIntent());
+ p.println(" mCurIntent=" + bindingController.getCurIntent());
method = getCurMethodLocked();
p.println(" mCurMethod=" + getCurMethodLocked());
p.println(" mEnabledSession=" + mEnabledSession);
@@ -6395,7 +6429,8 @@
hideCurrentInputLocked(mImeBindingState.mFocusedWindow, 0 /* flags */,
SoftInputShowHideReason.HIDE_RESET_SHELL_COMMAND);
final var userData = mUserDataRepository.getOrCreate(userId);
- userData.mBindingController.unbindCurrentMethod();
+ final var bindingController = userData.mBindingController;
+ bindingController.unbindCurrentMethod();
// Enable default IMEs, disable others
var toDisable = settings.getEnabledInputMethodList();
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodMap.java b/services/core/java/com/android/server/inputmethod/InputMethodMap.java
index 221309e..f06643df 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodMap.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodMap.java
@@ -23,6 +23,7 @@
import android.util.ArrayMap;
import android.view.inputmethod.InputMethodInfo;
+import java.util.Arrays;
import java.util.List;
/**
@@ -99,4 +100,37 @@
}
return updated ? InputMethodMap.of(newMethodMap) : this;
}
+
+ /**
+ * Compares the given two {@link InputMethodMap} instances to see if they contain the same data
+ * or not.
+ *
+ * @param map1 {@link InputMethodMap} to be compared with
+ * @param map2 {@link InputMethodMap} to be compared with
+ * @return {@code true} if both {@link InputMethodMap} instances contain exactly the same data
+ */
+ @AnyThread
+ static boolean equals(@NonNull InputMethodMap map1, @NonNull InputMethodMap map2) {
+ if (map1 == map2) {
+ return true;
+ }
+ final int size = map1.size();
+ if (size != map2.size()) {
+ return false;
+ }
+ for (int i = 0; i < size; ++i) {
+ final var imi1 = map1.valueAt(i);
+ final var imeId = imi1.getId();
+ final var imi2 = map2.get(imeId);
+ if (imi2 == null) {
+ return false;
+ }
+ final var marshaled1 = InputMethodInfoUtils.marshal(imi1);
+ final var marshaled2 = InputMethodInfoUtils.marshal(imi2);
+ if (!Arrays.equals(marshaled1, marshaled2)) {
+ return false;
+ }
+ }
+ return true;
+ }
}
diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
index 73647db..e1f8939 100644
--- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
@@ -2800,6 +2800,10 @@
final String providerId = route.getProviderId();
final MediaRoute2Provider provider = findProvider(providerId);
if (provider == null) {
+ Slog.w(
+ TAG,
+ "Ignoring transferToRoute due to lack of matching provider for target: "
+ + route);
return;
}
provider.transferToRoute(
diff --git a/services/core/java/com/android/server/notification/NotificationAttentionHelper.java b/services/core/java/com/android/server/notification/NotificationAttentionHelper.java
index a78b3a2..13429db 100644
--- a/services/core/java/com/android/server/notification/NotificationAttentionHelper.java
+++ b/services/core/java/com/android/server/notification/NotificationAttentionHelper.java
@@ -22,12 +22,14 @@
import static android.app.NotificationManager.IMPORTANCE_MIN;
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_LIGHTS;
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_STATUS_BAR;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.media.AudioAttributes.USAGE_NOTIFICATION_RINGTONE;
import static android.media.audio.Flags.focusExclusiveWithRecording;
import static android.service.notification.NotificationListenerService.HINT_HOST_DISABLE_CALL_EFFECTS;
import static android.service.notification.NotificationListenerService.HINT_HOST_DISABLE_EFFECTS;
import static android.service.notification.NotificationListenerService.HINT_HOST_DISABLE_NOTIFICATION_EFFECTS;
+import android.Manifest.permission;
import android.annotation.IntDef;
import android.app.ActivityManager;
import android.app.KeyguardManager;
@@ -223,7 +225,10 @@
mFlagResolver.getIntValue(NotificationFlags.NOTIF_COOLDOWN_T2),
mFlagResolver.getIntValue(NotificationFlags.NOTIF_VOLUME1),
mFlagResolver.getIntValue(NotificationFlags.NOTIF_VOLUME2),
- mFlagResolver.getIntValue(NotificationFlags.NOTIF_COOLDOWN_COUNTER_RESET));
+ mFlagResolver.getIntValue(NotificationFlags.NOTIF_COOLDOWN_COUNTER_RESET),
+ record -> mPackageManager.checkPermission(
+ permission.RECEIVE_EMERGENCY_BROADCAST,
+ record.getSbn().getPackageName()) == PERMISSION_GRANTED);
return new StrategyAvalanche(
mFlagResolver.getIntValue(NotificationFlags.NOTIF_COOLDOWN_T1),
@@ -231,14 +236,17 @@
mFlagResolver.getIntValue(NotificationFlags.NOTIF_VOLUME1),
mFlagResolver.getIntValue(NotificationFlags.NOTIF_VOLUME2),
mFlagResolver.getIntValue(NotificationFlags.NOTIF_AVALANCHE_TIMEOUT),
- appStrategy);
+ appStrategy, appStrategy.mExemptionProvider);
} else {
return new StrategyPerApp(
mFlagResolver.getIntValue(NotificationFlags.NOTIF_COOLDOWN_T1),
mFlagResolver.getIntValue(NotificationFlags.NOTIF_COOLDOWN_T2),
mFlagResolver.getIntValue(NotificationFlags.NOTIF_VOLUME1),
mFlagResolver.getIntValue(NotificationFlags.NOTIF_VOLUME2),
- mFlagResolver.getIntValue(NotificationFlags.NOTIF_COOLDOWN_COUNTER_RESET));
+ mFlagResolver.getIntValue(NotificationFlags.NOTIF_COOLDOWN_COUNTER_RESET),
+ record -> mPackageManager.checkPermission(
+ permission.RECEIVE_EMERGENCY_BROADCAST,
+ record.getSbn().getPackageName()) == PERMISSION_GRANTED);
}
}
@@ -1098,6 +1106,11 @@
}
}
+ // Returns true if a notification should be exempted from attenuation
+ private interface ExemptionProvider {
+ boolean isExempted(NotificationRecord record);
+ }
+
@VisibleForTesting
abstract static class PolitenessStrategy {
static final int POLITE_STATE_DEFAULT = 0;
@@ -1128,8 +1141,10 @@
protected boolean mIsActive = true;
+ protected final ExemptionProvider mExemptionProvider;
+
public PolitenessStrategy(int timeoutPolite, int timeoutMuted, int volumePolite,
- int volumeMuted) {
+ int volumeMuted, ExemptionProvider exemptionProvider) {
mVolumeStates = new HashMap<>();
mLastUpdatedTimestampByPackage = new HashMap<>();
@@ -1137,6 +1152,7 @@
this.mTimeoutMuted = timeoutMuted;
this.mVolumePolite = volumePolite / 100.0f;
this.mVolumeMuted = volumeMuted / 100.0f;
+ this.mExemptionProvider = exemptionProvider;
}
abstract void onNotificationPosted(NotificationRecord record);
@@ -1294,8 +1310,8 @@
private final int mMaxPostedForReset;
public StrategyPerApp(int timeoutPolite, int timeoutMuted, int volumePolite,
- int volumeMuted, int maxPosted) {
- super(timeoutPolite, timeoutMuted, volumePolite, volumeMuted);
+ int volumeMuted, int maxPosted, ExemptionProvider exemptionProvider) {
+ super(timeoutPolite, timeoutMuted, volumePolite, volumeMuted, exemptionProvider);
mNumPosted = new HashMap<>();
mMaxPostedForReset = maxPosted;
@@ -1316,7 +1332,12 @@
final String key = getChannelKey(record);
@PolitenessState final int currState = getPolitenessState(record);
- @PolitenessState int nextState = getNextState(currState, timeSinceLastNotif);
+ @PolitenessState int nextState;
+ if (Flags.politeNotificationsAttnUpdate()) {
+ nextState = getNextState(currState, timeSinceLastNotif, record);
+ } else {
+ nextState = getNextState(currState, timeSinceLastNotif);
+ }
// Reset to default state if number of posted notifications exceed this value when muted
int numPosted = mNumPosted.getOrDefault(key, 0) + 1;
@@ -1334,6 +1355,14 @@
mVolumeStates.put(key, nextState);
}
+ @PolitenessState int getNextState(@PolitenessState final int currState,
+ final long timeSinceLastNotif, final NotificationRecord record) {
+ if (mExemptionProvider.isExempted(record)) {
+ return POLITE_STATE_DEFAULT;
+ }
+ return getNextState(currState, timeSinceLastNotif);
+ }
+
@Override
public void onUserInteraction(final NotificationRecord record) {
super.onUserInteraction(record);
@@ -1354,8 +1383,9 @@
private long mLastAvalancheTriggerTimestamp = 0;
StrategyAvalanche(int timeoutPolite, int timeoutMuted, int volumePolite,
- int volumeMuted, int timeoutAvalanche, PolitenessStrategy appStrategy) {
- super(timeoutPolite, timeoutMuted, volumePolite, volumeMuted);
+ int volumeMuted, int timeoutAvalanche, PolitenessStrategy appStrategy,
+ ExemptionProvider exemptionProvider) {
+ super(timeoutPolite, timeoutMuted, volumePolite, volumeMuted, exemptionProvider);
mTimeoutAvalanche = timeoutAvalanche;
mAppStrategy = appStrategy;
@@ -1528,7 +1558,7 @@
return true;
}
- return false;
+ return mExemptionProvider.isExempted(record);
}
private boolean isAvalancheExempted(final NotificationRecord record) {
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 4e9c91c..44e7694 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -5832,7 +5832,16 @@
@Override
public ComponentName getEffectsSuppressor() {
- return !mEffectsSuppressors.isEmpty() ? mEffectsSuppressors.get(0) : null;
+ ComponentName suppressor = !mEffectsSuppressors.isEmpty()
+ ? mEffectsSuppressors.get(0)
+ : null;
+ if (isCallerSystemOrSystemUiOrShell() || suppressor == null
+ || mPackageManagerInternal.isSameApp(suppressor.getPackageName(),
+ Binder.getCallingUid(), UserHandle.getUserId(Binder.getCallingUid()))) {
+ return suppressor;
+ }
+
+ return null;
}
@Override
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 908b47d..472f228 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -37,7 +37,7 @@
import static android.content.pm.PackageManager.INSTALL_FAILED_UPDATE_INCOMPATIBLE;
import static android.content.pm.PackageManager.INSTALL_STAGED;
import static android.content.pm.PackageManager.INSTALL_SUCCEEDED;
-import static android.content.pm.PackageManager.PROPERTY_ANDROID_SAFETY_LABEL_PATH;
+import static android.content.pm.PackageManager.PROPERTY_ANDROID_SAFETY_LABEL;
import static android.content.pm.PackageManager.UNINSTALL_REASON_UNKNOWN;
import static android.content.pm.SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V4;
import static android.content.pm.parsing.ApkLiteParseUtils.isApkFile;
@@ -505,6 +505,7 @@
// metadata file path for the new package.
if (oldPkgSetting != null) {
pkgSetting.setAppMetadataFilePath(null);
+ pkgSetting.setAppMetadataSource(APP_METADATA_SOURCE_UNKNOWN);
}
// If the app metadata file path is not null then this is a system app with a preloaded app
// metadata file on the system image. Do not reset the path and source if this is the
@@ -523,7 +524,7 @@
}
} else if (Flags.aslInApkAppMetadataSource()) {
Map<String, PackageManager.Property> properties = pkg.getProperties();
- if (properties.containsKey(PROPERTY_ANDROID_SAFETY_LABEL_PATH)) {
+ if (properties.containsKey(PROPERTY_ANDROID_SAFETY_LABEL)) {
// ASL file extraction is done in post-install
pkgSetting.setAppMetadataFilePath(appMetadataFile.getAbsolutePath());
pkgSetting.setAppMetadataSource(APP_METADATA_SOURCE_APK);
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 121cf3f..fda8535 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -4003,7 +4003,7 @@
final PackageMetrics.ComponentStateMetrics componentStateMetrics =
new PackageMetrics.ComponentStateMetrics(setting,
UserHandle.getUid(userId, packageSetting.getAppId()),
- packageSetting.getEnabled(userId));
+ packageSetting.getEnabled(userId), callingUid);
if (!setEnabledSettingInternalLocked(computer, packageSetting, setting, userId,
callingPackage)) {
continue;
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
index b369f03..23ae983 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
@@ -19,7 +19,7 @@
import static android.content.pm.PackageManager.INSTALL_FAILED_SHARED_USER_INCOMPATIBLE;
import static android.content.pm.PackageManager.INSTALL_FAILED_UPDATE_INCOMPATIBLE;
import static android.content.pm.PackageManager.INSTALL_FAILED_VERSION_DOWNGRADE;
-import static android.content.pm.PackageManager.PROPERTY_ANDROID_SAFETY_LABEL_PATH;
+import static android.content.pm.PackageManager.PROPERTY_ANDROID_SAFETY_LABEL;
import static android.content.pm.SigningDetails.CertCapabilities.SHARED_USER_ID;
import static android.system.OsConstants.O_CREAT;
import static android.system.OsConstants.O_RDWR;
@@ -71,8 +71,12 @@
import android.content.pm.parsing.PackageLite;
import android.content.pm.parsing.result.ParseResult;
import android.content.pm.parsing.result.ParseTypeImpl;
+import android.content.res.ApkAssets;
+import android.content.res.AssetManager;
+import android.content.res.Resources;
import android.os.Binder;
import android.os.Build;
+import android.os.CancellationSignal;
import android.os.Debug;
import android.os.Environment;
import android.os.FileUtils;
@@ -93,6 +97,7 @@
import android.util.ArraySet;
import android.util.AtomicFile;
import android.util.Base64;
+import android.util.DisplayMetrics;
import android.util.Log;
import android.util.LogPrinter;
import android.util.Printer;
@@ -147,11 +152,10 @@
import java.util.Map;
import java.util.Objects;
import java.util.Set;
+import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.zip.GZIPInputStream;
-import java.util.zip.ZipEntry;
-import java.util.zip.ZipFile;
/**
* Class containing helper methods for the PackageManagerService.
@@ -1668,11 +1672,11 @@
return true;
}
Map<String, Property> properties = pkg.getProperties();
- if (!properties.containsKey(PROPERTY_ANDROID_SAFETY_LABEL_PATH)) {
+ if (!properties.containsKey(PROPERTY_ANDROID_SAFETY_LABEL)) {
return false;
}
- Property fileInAPkPathProperty = properties.get(PROPERTY_ANDROID_SAFETY_LABEL_PATH);
- if (!fileInAPkPathProperty.isString()) {
+ Property fileInApkProperty = properties.get(PROPERTY_ANDROID_SAFETY_LABEL);
+ if (!fileInApkProperty.isResourceId()) {
return false;
}
if (isSystem && !appMetadataFile.getParentFile().exists()) {
@@ -1684,28 +1688,46 @@
return false;
}
}
- String fileInApkPath = fileInAPkPathProperty.getString();
List<AndroidPackageSplit> splits = pkg.getSplits();
+ AssetManager.Builder builder = new AssetManager.Builder();
for (int i = 0; i < splits.size(); i++) {
- try (ZipFile zipFile = new ZipFile(splits.get(i).getPath())) {
- ZipEntry zipEntry = zipFile.getEntry(fileInApkPath);
- if (zipEntry != null
- && (isSystem || zipEntry.getSize() <= getAppMetadataSizeLimit())) {
- try (InputStream in = zipFile.getInputStream(zipEntry)) {
- try (FileOutputStream out = new FileOutputStream(appMetadataFile)) {
- FileUtils.copy(in, out);
- Os.chmod(appMetadataFile.getAbsolutePath(),
- APP_METADATA_FILE_ACCESS_MODE);
- return true;
+ try {
+ builder.addApkAssets(ApkAssets.loadFromPath(splits.get(i).getPath()));
+ } catch (IOException e) {
+ Slog.e(TAG, "Failed to load resources from APK " + splits.get(i).getPath());
+ }
+ }
+ AssetManager assetManager = builder.build();
+ DisplayMetrics displayMetrics = new DisplayMetrics();
+ displayMetrics.setToDefaults();
+ Resources res = new Resources(assetManager, displayMetrics, null);
+ AtomicBoolean copyFailed = new AtomicBoolean(false);
+ try (InputStream in = res.openRawResource(fileInApkProperty.getResourceId())) {
+ try (FileOutputStream out = new FileOutputStream(appMetadataFile)) {
+ if (isSystem) {
+ FileUtils.copy(in, out);
+ } else {
+ long sizeLimit = getAppMetadataSizeLimit();
+ CancellationSignal signal = new CancellationSignal();
+ FileUtils.copy(in, out, signal, Runnable::run, (long progress) -> {
+ if (progress > sizeLimit) {
+ copyFailed.set(true);
+ signal.cancel();
}
- }
+ });
}
- } catch (Exception e) {
- Slog.e(TAG, e.getMessage());
+ Os.chmod(appMetadataFile.getAbsolutePath(),
+ APP_METADATA_FILE_ACCESS_MODE);
+ }
+ } catch (Exception e) {
+ Slog.e(TAG, e.getMessage());
+ copyFailed.set(true);
+ } finally {
+ if (copyFailed.get()) {
appMetadataFile.delete();
}
}
- return false;
+ return !copyFailed.get();
}
public static void linkFilesToOldDirs(@NonNull Installer installer,
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index 7a36f6d..0a8b2b2 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -328,6 +328,8 @@
return runGetPrivappDenyPermissions();
case "get-oem-permissions":
return runGetOemPermissions();
+ case "get-signature-permission-allowlist":
+ return runGetSignaturePermissionAllowlist();
case "trim-caches":
return runTrimCaches();
case "create-user":
@@ -2920,6 +2922,54 @@
return 0;
}
+ private int runGetSignaturePermissionAllowlist() {
+ final var partition = getNextArg();
+ if (partition == null) {
+ getErrPrintWriter().println("Error: no partition specified.");
+ return 1;
+ }
+ final var permissionAllowlist =
+ SystemConfig.getInstance().getPermissionAllowlist();
+ final ArrayMap<String, ArrayMap<String, Boolean>> allowlist;
+ switch (partition) {
+ case "system":
+ allowlist = permissionAllowlist.getSignatureAppAllowlist();
+ break;
+ case "vendor":
+ allowlist = permissionAllowlist.getVendorSignatureAppAllowlist();
+ break;
+ case "product":
+ allowlist = permissionAllowlist.getProductSignatureAppAllowlist();
+ break;
+ case "system-ext":
+ allowlist = permissionAllowlist.getSystemExtSignatureAppAllowlist();
+ break;
+ default:
+ getErrPrintWriter().println("Error: unknown partition: " + partition);
+ return 1;
+ }
+ final var ipw = new IndentingPrintWriter(getOutPrintWriter(), " ");
+ final var allowlistSize = allowlist.size();
+ for (var allowlistIndex = 0; allowlistIndex < allowlistSize; allowlistIndex++) {
+ final var packageName = allowlist.keyAt(allowlistIndex);
+ final var permissions = allowlist.valueAt(allowlistIndex);
+ ipw.print("Package: ");
+ ipw.println(packageName);
+ ipw.increaseIndent();
+ final var permissionsSize = permissions.size();
+ for (var permissionsIndex = 0; permissionsIndex < permissionsSize; permissionsIndex++) {
+ final var permissionName = permissions.keyAt(permissionsIndex);
+ final var granted = permissions.valueAt(permissionsIndex);
+ if (granted) {
+ ipw.print("Permission: ");
+ ipw.println(permissionName);
+ }
+ }
+ ipw.decreaseIndent();
+ }
+ return 0;
+ }
+
private int runTrimCaches() throws RemoteException {
String size = getNextArg();
if (size == null) {
@@ -4852,6 +4902,10 @@
pw.println(" get-oem-permissions TARGET-PACKAGE");
pw.println(" Prints all OEM permissions for a package.");
pw.println("");
+ pw.println(" get-signature-permission-allowlist PARTITION");
+ pw.println(" Prints the signature permission allowlist for a partition.");
+ pw.println(" PARTITION is one of system, vendor, product and system-ext");
+ pw.println("");
pw.println(" trim-caches DESIRED_FREE_SPACE [internal|UUID]");
pw.println(" Trim cache files to reach the given free space.");
pw.println("");
diff --git a/services/core/java/com/android/server/pm/PackageMetrics.java b/services/core/java/com/android/server/pm/PackageMetrics.java
index 20598f9..2081f73 100644
--- a/services/core/java/com/android/server/pm/PackageMetrics.java
+++ b/services/core/java/com/android/server/pm/PackageMetrics.java
@@ -358,6 +358,7 @@
public static class ComponentStateMetrics {
public int mUid;
+ public int mCallingUid;
public int mComponentOldState;
public int mComponentNewState;
public boolean mIsForWholeApp;
@@ -365,13 +366,14 @@
@Nullable private String mClassName;
ComponentStateMetrics(@NonNull PackageManager.ComponentEnabledSetting setting, int uid,
- int componentOldState) {
+ int componentOldState, int callingUid) {
mUid = uid;
mComponentOldState = componentOldState;
mComponentNewState = setting.getEnabledState();
mIsForWholeApp = !setting.isComponent();
mPackageName = setting.getPackageName();
mClassName = setting.getClassName();
+ mCallingUid = callingUid;
}
public boolean isSameComponent(ActivityInfo activityInfo) {
@@ -412,14 +414,15 @@
componentStateMetrics.mComponentOldState,
componentStateMetrics.mComponentNewState,
isLauncher,
- componentStateMetrics.mIsForWholeApp);
+ componentStateMetrics.mIsForWholeApp,
+ componentStateMetrics.mCallingUid);
}
}
private static void reportComponentStateChanged(int uid, int componentOldState,
- int componentNewState, boolean isLauncher, boolean isForWholeApp) {
+ int componentNewState, boolean isLauncher, boolean isForWholeApp, int callingUid) {
FrameworkStatsLog.write(FrameworkStatsLog.COMPONENT_STATE_CHANGED_REPORTED,
- uid, componentOldState, componentNewState, isLauncher, isForWholeApp);
+ uid, componentOldState, componentNewState, isLauncher, isForWholeApp, callingUid);
}
private static List<ResolveInfo> getHomeActivitiesResolveInfoAsUser(@NonNull Computer computer,
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 41d6288..8d6d774 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -2142,7 +2142,8 @@
ComponentName unflattenOriginalComponentName = ComponentName.unflattenFromString(
originalComponentName);
if (unflattenOriginalComponentName == null) {
- Slog.d(TAG, "Incorrect component name from the attributes");
+ Slog.wtf(TAG, "Incorrect component name: " + originalComponentName
+ + " from the attributes");
continue;
}
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 4ff345f..ebdca5b 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -1924,16 +1924,20 @@
private void showConfirmCredentialToDisableQuietMode(
@UserIdInt int userId, @Nullable IntentSender target, @Nullable String callingPackage) {
if (android.app.admin.flags.Flags.quietModeCredentialBugFix()) {
- // TODO (b/308121702) It may be brittle to rely on user states to check profile state
- int state;
- synchronized (mUserStates) {
- state = mUserStates.get(userId, UserState.STATE_NONE);
- }
- if (state != UserState.STATE_NONE) {
- Slog.i(LOG_TAG,
- "showConfirmCredentialToDisableQuietMode() called too early, user " + userId
- + " is still alive.");
- return;
+ if (!android.multiuser.Flags.restrictQuietModeCredentialBugFixToManagedProfiles()
+ || getUserInfo(userId).isManagedProfile()) {
+ // TODO (b/308121702) It may be brittle to rely on user states to check managed
+ // profile state
+ int state;
+ synchronized (mUserStates) {
+ state = mUserStates.get(userId, UserState.STATE_NONE);
+ }
+ if (state != UserState.STATE_NONE) {
+ Slog.i(LOG_TAG,
+ "showConfirmCredentialToDisableQuietMode() called too early, managed "
+ + "user " + userId + " is still alive.");
+ return;
+ }
}
}
// otherwise, we show a profile challenge to trigger decryption of the user
diff --git a/services/core/java/com/android/server/power/stats/AggregatedPowerStatsConfig.java b/services/core/java/com/android/server/power/stats/AggregatedPowerStatsConfig.java
index 5aad570..884c26c 100644
--- a/services/core/java/com/android/server/power/stats/AggregatedPowerStatsConfig.java
+++ b/services/core/java/com/android/server/power/stats/AggregatedPowerStatsConfig.java
@@ -19,12 +19,9 @@
import android.annotation.NonNull;
import android.os.BatteryConsumer;
-import com.android.internal.os.PowerStats;
-
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.List;
/**
@@ -206,25 +203,9 @@
return mPowerComponents;
}
- private static final PowerStatsProcessor NO_OP_PROCESSOR =
- new PowerStatsProcessor() {
- @Override
- void finish(PowerComponentAggregatedPowerStats stats) {
- }
-
- @Override
- String deviceStatsToString(PowerStats.Descriptor descriptor, long[] stats) {
- return Arrays.toString(stats);
- }
-
- @Override
- String stateStatsToString(PowerStats.Descriptor descriptor, int key, long[] stats) {
- return descriptor.getStateLabel(key) + " " + Arrays.toString(stats);
- }
-
- @Override
- String uidStatsToString(PowerStats.Descriptor descriptor, long[] stats) {
- return Arrays.toString(stats);
- }
- };
+ private static final PowerStatsProcessor NO_OP_PROCESSOR = new PowerStatsProcessor() {
+ @Override
+ void finish(PowerComponentAggregatedPowerStats stats) {
+ }
+ };
}
diff --git a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
index 49c4000..9a41551 100644
--- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
+++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
@@ -463,11 +463,17 @@
public static class BatteryStatsConfig {
static final int RESET_ON_UNPLUG_HIGH_BATTERY_LEVEL_FLAG = 1 << 0;
static final int RESET_ON_UNPLUG_AFTER_SIGNIFICANT_CHARGE_FLAG = 1 << 1;
- static final long DEFAULT_POWER_STATS_COLLECTION_THROTTLE_PERIOD =
- TimeUnit.HOURS.toMillis(1);
private final int mFlags;
- private SparseLongArray mPowerStatsThrottlePeriods;
+ private final Long mDefaultPowerStatsThrottlePeriod;
+ private final Map<String, Long> mPowerStatsThrottlePeriods;
+
+ @VisibleForTesting
+ public BatteryStatsConfig() {
+ mFlags = 0;
+ mDefaultPowerStatsThrottlePeriod = 0L;
+ mPowerStatsThrottlePeriods = Map.of();
+ }
private BatteryStatsConfig(Builder builder) {
int flags = 0;
@@ -478,6 +484,7 @@
flags |= RESET_ON_UNPLUG_AFTER_SIGNIFICANT_CHARGE_FLAG;
}
mFlags = flags;
+ mDefaultPowerStatsThrottlePeriod = builder.mDefaultPowerStatsThrottlePeriod;
mPowerStatsThrottlePeriods = builder.mPowerStatsThrottlePeriods;
}
@@ -485,7 +492,7 @@
* Returns whether a BatteryStats reset should occur on unplug when the battery level is
* high.
*/
- boolean shouldResetOnUnplugHighBatteryLevel() {
+ public boolean shouldResetOnUnplugHighBatteryLevel() {
return (mFlags & RESET_ON_UNPLUG_HIGH_BATTERY_LEVEL_FLAG)
== RESET_ON_UNPLUG_HIGH_BATTERY_LEVEL_FLAG;
}
@@ -494,14 +501,18 @@
* Returns whether a BatteryStats reset should occur on unplug if the battery charge a
* significant amount since it has been plugged in.
*/
- boolean shouldResetOnUnplugAfterSignificantCharge() {
+ public boolean shouldResetOnUnplugAfterSignificantCharge() {
return (mFlags & RESET_ON_UNPLUG_AFTER_SIGNIFICANT_CHARGE_FLAG)
== RESET_ON_UNPLUG_AFTER_SIGNIFICANT_CHARGE_FLAG;
}
- long getPowerStatsThrottlePeriod(@BatteryConsumer.PowerComponent int powerComponent) {
- return mPowerStatsThrottlePeriods.get(powerComponent,
- DEFAULT_POWER_STATS_COLLECTION_THROTTLE_PERIOD);
+ /**
+ * Returns the minimum amount of time (in millis) to wait between passes
+ * of power stats collection for the specified power component.
+ */
+ public long getPowerStatsThrottlePeriod(String powerComponentName) {
+ return mPowerStatsThrottlePeriods.getOrDefault(powerComponentName,
+ mDefaultPowerStatsThrottlePeriod);
}
/**
@@ -510,18 +521,19 @@
public static class Builder {
private boolean mResetOnUnplugHighBatteryLevel;
private boolean mResetOnUnplugAfterSignificantCharge;
- private SparseLongArray mPowerStatsThrottlePeriods;
+ public static final long DEFAULT_POWER_STATS_THROTTLE_PERIOD =
+ TimeUnit.HOURS.toMillis(1);
+ public static final long DEFAULT_POWER_STATS_THROTTLE_PERIOD_CPU =
+ TimeUnit.MINUTES.toMillis(1);
+ private long mDefaultPowerStatsThrottlePeriod = DEFAULT_POWER_STATS_THROTTLE_PERIOD;
+ private final Map<String, Long> mPowerStatsThrottlePeriods = new HashMap<>();
public Builder() {
mResetOnUnplugHighBatteryLevel = true;
mResetOnUnplugAfterSignificantCharge = true;
- mPowerStatsThrottlePeriods = new SparseLongArray();
- setPowerStatsThrottlePeriodMillis(BatteryConsumer.POWER_COMPONENT_CPU,
- TimeUnit.MINUTES.toMillis(1));
- setPowerStatsThrottlePeriodMillis(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO,
- TimeUnit.HOURS.toMillis(1));
- setPowerStatsThrottlePeriodMillis(BatteryConsumer.POWER_COMPONENT_WIFI,
- TimeUnit.HOURS.toMillis(1));
+ setPowerStatsThrottlePeriodMillis(BatteryConsumer.powerComponentIdToString(
+ BatteryConsumer.POWER_COMPONENT_CPU),
+ DEFAULT_POWER_STATS_THROTTLE_PERIOD_CPU);
}
/**
@@ -553,9 +565,18 @@
* Sets the minimum amount of time (in millis) to wait between passes
* of power stats collection for the specified power component.
*/
- public Builder setPowerStatsThrottlePeriodMillis(
- @BatteryConsumer.PowerComponent int powerComponent, long periodMs) {
- mPowerStatsThrottlePeriods.put(powerComponent, periodMs);
+ public Builder setPowerStatsThrottlePeriodMillis(String powerComponentName,
+ long periodMs) {
+ mPowerStatsThrottlePeriods.put(powerComponentName, periodMs);
+ return this;
+ }
+
+ /**
+ * Sets the minimum amount of time (in millis) to wait between passes
+ * of power stats collection for any components not configured explicitly.
+ */
+ public Builder setDefaultPowerStatsThrottlePeriodMillis(long periodMs) {
+ mDefaultPowerStatsThrottlePeriod = periodMs;
return this;
}
}
@@ -1586,8 +1607,7 @@
protected final Constants mConstants;
@VisibleForTesting
- @GuardedBy("this")
- protected BatteryStatsConfig mBatteryStatsConfig;
+ protected final BatteryStatsConfig mBatteryStatsConfig;
@GuardedBy("this")
private AlarmManager mAlarmManager = null;
@@ -1933,6 +1953,11 @@
}
@Override
+ public long getPowerStatsCollectionThrottlePeriod(String powerComponentName) {
+ return mBatteryStatsConfig.getPowerStatsThrottlePeriod(powerComponentName);
+ }
+
+ @Override
public PowerStatsUidResolver getUidResolver() {
return mPowerStatsUidResolver;
}
@@ -11167,19 +11192,14 @@
mConstants.MAX_HISTORY_FILES, mConstants.MAX_HISTORY_BUFFER, mStepDetailsCalculator,
mClock, mMonotonicClock, traceDelegate, eventLogger);
- mCpuPowerStatsCollector = new CpuPowerStatsCollector(mPowerStatsCollectorInjector,
- mBatteryStatsConfig.getPowerStatsThrottlePeriod(
- BatteryConsumer.POWER_COMPONENT_CPU));
+ mCpuPowerStatsCollector = new CpuPowerStatsCollector(mPowerStatsCollectorInjector);
mCpuPowerStatsCollector.addConsumer(this::recordPowerStats);
mMobileRadioPowerStatsCollector = new MobileRadioPowerStatsCollector(
- mPowerStatsCollectorInjector, mBatteryStatsConfig.getPowerStatsThrottlePeriod(
- BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO));
+ mPowerStatsCollectorInjector);
mMobileRadioPowerStatsCollector.addConsumer(this::recordPowerStats);
- mWifiPowerStatsCollector = new WifiPowerStatsCollector(
- mPowerStatsCollectorInjector, mBatteryStatsConfig.getPowerStatsThrottlePeriod(
- BatteryConsumer.POWER_COMPONENT_WIFI));
+ mWifiPowerStatsCollector = new WifiPowerStatsCollector(mPowerStatsCollectorInjector);
mWifiPowerStatsCollector.addConsumer(this::recordPowerStats);
mStartCount++;
diff --git a/services/core/java/com/android/server/power/stats/CpuPowerStatsCollector.java b/services/core/java/com/android/server/power/stats/CpuPowerStatsCollector.java
index f53a1b0..b5ef67b 100644
--- a/services/core/java/com/android/server/power/stats/CpuPowerStatsCollector.java
+++ b/services/core/java/com/android/server/power/stats/CpuPowerStatsCollector.java
@@ -59,6 +59,7 @@
KernelCpuStatsReader getKernelCpuStatsReader();
ConsumedEnergyRetriever getConsumedEnergyRetriever();
IntSupplier getVoltageSupplier();
+ long getPowerStatsCollectionThrottlePeriod(String powerComponentName);
default int getDefaultCpuPowerBrackets() {
return DEFAULT_CPU_POWER_BRACKETS;
@@ -94,9 +95,11 @@
private int mLastVoltageMv;
private long[] mLastConsumedEnergyUws;
- public CpuPowerStatsCollector(Injector injector, long throttlePeriodMs) {
- super(injector.getHandler(), throttlePeriodMs, injector.getUidResolver(),
- injector.getClock());
+ CpuPowerStatsCollector(Injector injector) {
+ super(injector.getHandler(), injector.getPowerStatsCollectionThrottlePeriod(
+ BatteryConsumer.powerComponentIdToString(
+ BatteryConsumer.POWER_COMPONENT_CPU)),
+ injector.getUidResolver(), injector.getClock());
mInjector = injector;
}
diff --git a/services/core/java/com/android/server/power/stats/CpuPowerStatsLayout.java b/services/core/java/com/android/server/power/stats/CpuPowerStatsLayout.java
index 1bcb2c4..2a02bd0 100644
--- a/services/core/java/com/android/server/power/stats/CpuPowerStatsLayout.java
+++ b/services/core/java/com/android/server/power/stats/CpuPowerStatsLayout.java
@@ -44,7 +44,7 @@
* Declare that the stats array has a section capturing CPU time per scaling step
*/
public void addDeviceSectionCpuTimeByScalingStep(int scalingStepCount) {
- mDeviceCpuTimeByScalingStepPosition = addDeviceSection(scalingStepCount);
+ mDeviceCpuTimeByScalingStepPosition = addDeviceSection(scalingStepCount, "steps");
mDeviceCpuTimeByScalingStepCount = scalingStepCount;
}
@@ -72,7 +72,7 @@
* Declare that the stats array has a section capturing CPU time in each cluster
*/
public void addDeviceSectionCpuTimeByCluster(int clusterCount) {
- mDeviceCpuTimeByClusterPosition = addDeviceSection(clusterCount);
+ mDeviceCpuTimeByClusterPosition = addDeviceSection(clusterCount, "clusters");
mDeviceCpuTimeByClusterCount = clusterCount;
}
@@ -102,7 +102,7 @@
public void addUidSectionCpuTimeByPowerBracket(int[] scalingStepToPowerBracketMap) {
mScalingStepToPowerBracketMap = scalingStepToPowerBracketMap;
updatePowerBracketCount();
- mUidPowerBracketsPosition = addUidSection(mUidPowerBracketCount);
+ mUidPowerBracketsPosition = addUidSection(mUidPowerBracketCount, "time");
}
private void updatePowerBracketCount() {
diff --git a/services/core/java/com/android/server/power/stats/CpuPowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/CpuPowerStatsProcessor.java
index c34b8a8..57b7259 100644
--- a/services/core/java/com/android/server/power/stats/CpuPowerStatsProcessor.java
+++ b/services/core/java/com/android/server/power/stats/CpuPowerStatsProcessor.java
@@ -16,7 +16,6 @@
package com.android.server.power.stats;
-import android.os.BatteryStats;
import android.util.ArraySet;
import android.util.Log;
@@ -487,64 +486,4 @@
stats.setUidStats(uid, proportionalEstimate.stateValues, mTmpUidStatsArray);
}
}
-
- @Override
- public String deviceStatsToString(PowerStats.Descriptor descriptor, long[] stats) {
- unpackPowerStatsDescriptor(descriptor);
- StringBuilder sb = new StringBuilder();
- int cpuScalingStepCount = mStatsLayout.getCpuScalingStepCount();
- sb.append("steps: [");
- for (int step = 0; step < cpuScalingStepCount; step++) {
- if (step != 0) {
- sb.append(", ");
- }
- sb.append(mStatsLayout.getTimeByScalingStep(stats, step));
- }
- int clusterCount = mStatsLayout.getCpuClusterCount();
- sb.append("] clusters: [");
- for (int cluster = 0; cluster < clusterCount; cluster++) {
- if (cluster != 0) {
- sb.append(", ");
- }
- sb.append(mStatsLayout.getTimeByCluster(stats, cluster));
- }
- sb.append("] uptime: ").append(mStatsLayout.getUsageDuration(stats));
- int energyConsumerCount = mStatsLayout.getEnergyConsumerCount();
- if (energyConsumerCount > 0) {
- sb.append(" energy: [");
- for (int i = 0; i < energyConsumerCount; i++) {
- if (i != 0) {
- sb.append(", ");
- }
- sb.append(mStatsLayout.getConsumedEnergy(stats, i));
- }
- sb.append("]");
- }
- sb.append(" power: ").append(
- BatteryStats.formatCharge(mStatsLayout.getDevicePowerEstimate(stats)));
- return sb.toString();
- }
-
- @Override
- String stateStatsToString(PowerStats.Descriptor descriptor, int key, long[] stats) {
- // Unsupported for this power component
- return null;
- }
-
- @Override
- public String uidStatsToString(PowerStats.Descriptor descriptor, long[] stats) {
- unpackPowerStatsDescriptor(descriptor);
- StringBuilder sb = new StringBuilder();
- sb.append("[");
- int powerBracketCount = mStatsLayout.getCpuPowerBracketCount();
- for (int bracket = 0; bracket < powerBracketCount; bracket++) {
- if (bracket != 0) {
- sb.append(", ");
- }
- sb.append(mStatsLayout.getUidTimeByPowerBracket(stats, bracket));
- }
- sb.append("] power: ").append(
- BatteryStats.formatCharge(mStatsLayout.getUidPowerEstimate(stats)));
- return sb.toString();
- }
}
diff --git a/services/core/java/com/android/server/power/stats/MobileRadioPowerStatsCollector.java b/services/core/java/com/android/server/power/stats/MobileRadioPowerStatsCollector.java
index 7bc6817..a96e01b 100644
--- a/services/core/java/com/android/server/power/stats/MobileRadioPowerStatsCollector.java
+++ b/services/core/java/com/android/server/power/stats/MobileRadioPowerStatsCollector.java
@@ -73,6 +73,7 @@
Handler getHandler();
Clock getClock();
PowerStatsUidResolver getUidResolver();
+ long getPowerStatsCollectionThrottlePeriod(String powerComponentName);
PackageManager getPackageManager();
ConsumedEnergyRetriever getConsumedEnergyRetriever();
IntSupplier getVoltageSupplier();
@@ -104,8 +105,11 @@
private long mLastCallDuration;
private long mLastScanDuration;
- public MobileRadioPowerStatsCollector(Injector injector, long throttlePeriodMs) {
- super(injector.getHandler(), throttlePeriodMs, injector.getUidResolver(),
+ MobileRadioPowerStatsCollector(Injector injector) {
+ super(injector.getHandler(), injector.getPowerStatsCollectionThrottlePeriod(
+ BatteryConsumer.powerComponentIdToString(
+ BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO)),
+ injector.getUidResolver(),
injector.getClock());
mInjector = injector;
}
diff --git a/services/core/java/com/android/server/power/stats/MobileRadioPowerStatsLayout.java b/services/core/java/com/android/server/power/stats/MobileRadioPowerStatsLayout.java
index 81d7c2f..07d78f8 100644
--- a/services/core/java/com/android/server/power/stats/MobileRadioPowerStatsLayout.java
+++ b/services/core/java/com/android/server/power/stats/MobileRadioPowerStatsLayout.java
@@ -64,29 +64,30 @@
}
void addDeviceMobileActivity() {
- mDeviceSleepTimePosition = addDeviceSection(1);
- mDeviceIdleTimePosition = addDeviceSection(1);
- mDeviceScanTimePosition = addDeviceSection(1);
- mDeviceCallTimePosition = addDeviceSection(1);
+ mDeviceSleepTimePosition = addDeviceSection(1, "sleep");
+ mDeviceIdleTimePosition = addDeviceSection(1, "idle");
+ mDeviceScanTimePosition = addDeviceSection(1, "scan");
+ mDeviceCallTimePosition = addDeviceSection(1, "call", FLAG_OPTIONAL);
}
void addStateStats() {
- mStateRxTimePosition = addStateSection(1);
+ mStateRxTimePosition = addStateSection(1, "rx");
mStateTxTimesCount = ModemActivityInfo.getNumTxPowerLevels();
- mStateTxTimesPosition = addStateSection(mStateTxTimesCount);
+ mStateTxTimesPosition = addStateSection(mStateTxTimesCount, "tx");
}
void addUidNetworkStats() {
- mUidRxBytesPosition = addUidSection(1);
- mUidTxBytesPosition = addUidSection(1);
- mUidRxPacketsPosition = addUidSection(1);
- mUidTxPacketsPosition = addUidSection(1);
+ mUidRxPacketsPosition = addUidSection(1, "rx-pkts");
+ mUidRxBytesPosition = addUidSection(1, "rx-B");
+ mUidTxPacketsPosition = addUidSection(1, "tx-pkts");
+ mUidTxBytesPosition = addUidSection(1, "tx-B");
}
@Override
public void addDeviceSectionPowerEstimate() {
super.addDeviceSectionPowerEstimate();
- mDeviceCallPowerPosition = addDeviceSection(1);
+ // Printed as part of the PhoneCallPowerStatsProcessor
+ mDeviceCallPowerPosition = addDeviceSection(1, "call-power", FLAG_HIDDEN);
}
public void setDeviceSleepTime(long[] stats, long durationMillis) {
diff --git a/services/core/java/com/android/server/power/stats/MobileRadioPowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/MobileRadioPowerStatsProcessor.java
index c97c64b..eebed2f 100644
--- a/services/core/java/com/android/server/power/stats/MobileRadioPowerStatsProcessor.java
+++ b/services/core/java/com/android/server/power/stats/MobileRadioPowerStatsProcessor.java
@@ -398,37 +398,4 @@
}
}
}
-
- @Override
- String deviceStatsToString(PowerStats.Descriptor descriptor, long[] stats) {
- unpackPowerStatsDescriptor(descriptor);
- return "idle: " + mStatsLayout.getDeviceIdleTime(stats)
- + " sleep: " + mStatsLayout.getDeviceSleepTime(stats)
- + " scan: " + mStatsLayout.getDeviceScanTime(stats)
- + " power: " + mStatsLayout.getDevicePowerEstimate(stats);
- }
-
- @Override
- String stateStatsToString(PowerStats.Descriptor descriptor, int key, long[] stats) {
- unpackPowerStatsDescriptor(descriptor);
- StringBuilder sb = new StringBuilder();
- sb.append(descriptor.getStateLabel(key));
- sb.append(" rx: ").append(mStatsLayout.getStateRxTime(stats));
- sb.append(" tx: ");
- for (int txLevel = 0; txLevel < ModemActivityInfo.getNumTxPowerLevels(); txLevel++) {
- if (txLevel != 0) {
- sb.append(", ");
- }
- sb.append(mStatsLayout.getStateTxTime(stats, txLevel));
- }
- return sb.toString();
- }
-
- @Override
- String uidStatsToString(PowerStats.Descriptor descriptor, long[] stats) {
- unpackPowerStatsDescriptor(descriptor);
- return "rx: " + mStatsLayout.getUidRxPackets(stats)
- + " tx: " + mStatsLayout.getUidTxPackets(stats)
- + " power: " + mStatsLayout.getUidPowerEstimate(stats);
- }
}
diff --git a/services/core/java/com/android/server/power/stats/MultiStateStats.java b/services/core/java/com/android/server/power/stats/MultiStateStats.java
index 6c4a2b6..a822281 100644
--- a/services/core/java/com/android/server/power/stats/MultiStateStats.java
+++ b/services/core/java/com/android/server/power/stats/MultiStateStats.java
@@ -28,10 +28,8 @@
import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
-import java.io.PrintWriter;
import java.util.Arrays;
import java.util.function.Consumer;
-import java.util.function.Function;
/**
* Maintains multidimensional multi-state stats. States could be something like on-battery (0,1),
@@ -287,6 +285,14 @@
mCounter = new LongArrayMultiStateCounter(factory.mSerialStateCount, dimensionCount);
}
+ public int getDimensionCount() {
+ return mFactory.mDimensionCount;
+ }
+
+ public States[] getStates() {
+ return mFactory.mStates;
+ }
+
/**
* Copies time-in-state and timestamps from the supplied prototype. Does not
* copy accumulated counts.
@@ -343,11 +349,6 @@
mTracking = false;
}
- @Override
- public String toString() {
- return mCounter.toString();
- }
-
/**
* Stores contents in an XML doc.
*/
@@ -451,10 +452,9 @@
return true;
}
- /**
- * Prints the accumulated stats, one line of every combination of states that has data.
- */
- public void dump(PrintWriter pw, Function<long[], String> statsFormatter) {
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
long[] values = new long[mCounter.getArrayLength()];
States.forEachTrackedStateCombination(mFactory.mStates, states -> {
mCounter.getCounts(values, mFactory.getSerialState(states));
@@ -469,18 +469,24 @@
return;
}
- StringBuilder sb = new StringBuilder();
+ if (!sb.isEmpty()) {
+ sb.append("\n");
+ }
+
+ sb.append("(");
+ boolean first = true;
for (int i = 0; i < states.length; i++) {
if (mFactory.mStates[i].mTracked) {
- if (sb.length() != 0) {
+ if (!first) {
sb.append(" ");
}
+ first = false;
sb.append(mFactory.mStates[i].mLabels[states[i]]);
}
}
- sb.append(" ");
- sb.append(statsFormatter.apply(values));
- pw.println(sb);
+ sb.append(") ");
+ sb.append(Arrays.toString(values));
});
+ return sb.toString();
}
}
diff --git a/services/core/java/com/android/server/power/stats/PhoneCallPowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/PhoneCallPowerStatsProcessor.java
index 62b653f..5c545fd 100644
--- a/services/core/java/com/android/server/power/stats/PhoneCallPowerStatsProcessor.java
+++ b/services/core/java/com/android/server/power/stats/PhoneCallPowerStatsProcessor.java
@@ -76,21 +76,4 @@
stats.setDeviceStats(states, mTmpDeviceStats);
});
}
-
- @Override
- String deviceStatsToString(PowerStats.Descriptor descriptor, long[] stats) {
- return "power: " + mStatsLayout.getDevicePowerEstimate(stats);
- }
-
- @Override
- String stateStatsToString(PowerStats.Descriptor descriptor, int key, long[] stats) {
- // Unsupported for this power component
- return null;
- }
-
- @Override
- String uidStatsToString(PowerStats.Descriptor descriptor, long[] stats) {
- // Unsupported for this power component
- return null;
- }
}
diff --git a/services/core/java/com/android/server/power/stats/PowerComponentAggregatedPowerStats.java b/services/core/java/com/android/server/power/stats/PowerComponentAggregatedPowerStats.java
index 6d58307..0528733 100644
--- a/services/core/java/com/android/server/power/stats/PowerComponentAggregatedPowerStats.java
+++ b/services/core/java/com/android/server/power/stats/PowerComponentAggregatedPowerStats.java
@@ -436,36 +436,76 @@
void dumpDevice(IndentingPrintWriter ipw) {
if (mDeviceStats != null) {
- ipw.println(mPowerStatsDescriptor.name);
- ipw.increaseIndent();
- mDeviceStats.dump(ipw, stats ->
- mConfig.getProcessor().deviceStatsToString(mPowerStatsDescriptor, stats));
- ipw.decreaseIndent();
+ dumpMultiStateStats(ipw, mDeviceStats, mPowerStatsDescriptor.name, null,
+ mPowerStatsDescriptor.getDeviceStatsFormatter());
}
if (mStateStats.size() != 0) {
ipw.increaseIndent();
- ipw.println(mPowerStatsDescriptor.name + " states");
- ipw.increaseIndent();
+ String header = mPowerStatsDescriptor.name + " states";
+ PowerStats.PowerStatsFormatter formatter =
+ mPowerStatsDescriptor.getStateStatsFormatter();
for (int i = 0; i < mStateStats.size(); i++) {
int key = mStateStats.keyAt(i);
+ String stateLabel = mPowerStatsDescriptor.getStateLabel(key);
MultiStateStats stateStats = mStateStats.valueAt(i);
- stateStats.dump(ipw, stats ->
- mConfig.getProcessor().stateStatsToString(mPowerStatsDescriptor, key,
- stats));
+ dumpMultiStateStats(ipw, stateStats, header, stateLabel, formatter);
}
ipw.decreaseIndent();
- ipw.decreaseIndent();
}
}
void dumpUid(IndentingPrintWriter ipw, int uid) {
UidStats uidStats = mUidStats.get(uid);
if (uidStats != null && uidStats.stats != null) {
- ipw.println(mPowerStatsDescriptor.name);
- ipw.increaseIndent();
- uidStats.stats.dump(ipw, stats ->
- mConfig.getProcessor().uidStatsToString(mPowerStatsDescriptor, stats));
+ dumpMultiStateStats(ipw, uidStats.stats, mPowerStatsDescriptor.name, null,
+ mPowerStatsDescriptor.getUidStatsFormatter());
+ }
+ }
+
+ private void dumpMultiStateStats(IndentingPrintWriter ipw, MultiStateStats stats,
+ String header, String additionalLabel,
+ PowerStats.PowerStatsFormatter statsFormatter) {
+ boolean[] firstLine = new boolean[]{true};
+ long[] values = new long[stats.getDimensionCount()];
+ MultiStateStats.States[] stateInfo = stats.getStates();
+ MultiStateStats.States.forEachTrackedStateCombination(stateInfo, states -> {
+ stats.getStats(values, states);
+ boolean nonZero = false;
+ for (long value : values) {
+ if (value != 0) {
+ nonZero = true;
+ break;
+ }
+ }
+ if (!nonZero) {
+ return;
+ }
+
+ if (firstLine[0]) {
+ ipw.println(header);
+ ipw.increaseIndent();
+ }
+ firstLine[0] = false;
+ StringBuilder sb = new StringBuilder();
+ sb.append("(");
+ boolean first = true;
+ for (int i = 0; i < states.length; i++) {
+ if (stateInfo[i].isTracked()) {
+ if (!first) {
+ sb.append(" ");
+ }
+ first = false;
+ sb.append(stateInfo[i].getLabels()[states[i]]);
+ }
+ }
+ if (additionalLabel != null) {
+ sb.append(" ").append(additionalLabel);
+ }
+ sb.append(") ").append(statsFormatter.format(values));
+ ipw.println(sb);
+ });
+ if (!firstLine[0]) {
ipw.decreaseIndent();
}
}
diff --git a/services/core/java/com/android/server/power/stats/PowerStatsLayout.java b/services/core/java/com/android/server/power/stats/PowerStatsLayout.java
index aa96409..58efd94 100644
--- a/services/core/java/com/android/server/power/stats/PowerStatsLayout.java
+++ b/services/core/java/com/android/server/power/stats/PowerStatsLayout.java
@@ -33,13 +33,20 @@
private static final String EXTRA_DEVICE_ENERGY_CONSUMERS_COUNT = "dec";
private static final String EXTRA_UID_POWER_POSITION = "up";
- protected static final double MILLI_TO_NANO_MULTIPLIER = 1000000.0;
protected static final int UNSUPPORTED = -1;
+ protected static final double MILLI_TO_NANO_MULTIPLIER = 1000000.0;
+ protected static final int FLAG_OPTIONAL = 1;
+ protected static final int FLAG_HIDDEN = 2;
+ protected static final int FLAG_FORMAT_AS_POWER = 4;
private int mDeviceStatsArrayLength;
private int mStateStatsArrayLength;
private int mUidStatsArrayLength;
+ private StringBuilder mDeviceFormat = new StringBuilder();
+ private StringBuilder mStateFormat = new StringBuilder();
+ private StringBuilder mUidFormat = new StringBuilder();
+
protected int mDeviceDurationPosition = UNSUPPORTED;
private int mDeviceEnergyConsumerPosition;
private int mDeviceEnergyConsumerCount;
@@ -65,29 +72,71 @@
return mUidStatsArrayLength;
}
- protected int addDeviceSection(int length) {
+ /**
+ * @param label should not contain either spaces or colons
+ */
+ private void appendFormat(StringBuilder sb, int position, int length, String label,
+ int flags) {
+ if ((flags & FLAG_HIDDEN) != 0) {
+ return;
+ }
+
+ if (!sb.isEmpty()) {
+ sb.append(' ');
+ }
+
+ sb.append(label).append(':');
+ sb.append(position);
+ if (length != 1) {
+ sb.append('[').append(length).append(']');
+ }
+ if ((flags & FLAG_FORMAT_AS_POWER) != 0) {
+ sb.append('p');
+ }
+ if ((flags & FLAG_OPTIONAL) != 0) {
+ sb.append('?');
+ }
+ }
+
+ protected int addDeviceSection(int length, String label, int flags) {
int position = mDeviceStatsArrayLength;
mDeviceStatsArrayLength += length;
+ appendFormat(mDeviceFormat, position, length, label, flags);
return position;
}
- protected int addStateSection(int length) {
+ protected int addDeviceSection(int length, String label) {
+ return addDeviceSection(length, label, 0);
+ }
+
+ protected int addStateSection(int length, String label, int flags) {
int position = mStateStatsArrayLength;
mStateStatsArrayLength += length;
+ appendFormat(mStateFormat, position, length, label, flags);
return position;
}
- protected int addUidSection(int length) {
+ protected int addStateSection(int length, String label) {
+ return addStateSection(length, label, 0);
+ }
+
+
+ protected int addUidSection(int length, String label, int flags) {
int position = mUidStatsArrayLength;
mUidStatsArrayLength += length;
+ appendFormat(mUidFormat, position, length, label, flags);
return position;
}
+ protected int addUidSection(int length, String label) {
+ return addUidSection(length, label, 0);
+ }
+
/**
* Declare that the stats array has a section capturing usage duration
*/
public void addDeviceSectionUsageDuration() {
- mDeviceDurationPosition = addDeviceSection(1);
+ mDeviceDurationPosition = addDeviceSection(1, "usage", FLAG_OPTIONAL);
}
/**
@@ -109,7 +158,7 @@
* PowerStatsService.
*/
public void addDeviceSectionEnergyConsumers(int energyConsumerCount) {
- mDeviceEnergyConsumerPosition = addDeviceSection(energyConsumerCount);
+ mDeviceEnergyConsumerPosition = addDeviceSection(energyConsumerCount, "energy");
mDeviceEnergyConsumerCount = energyConsumerCount;
}
@@ -137,7 +186,8 @@
* Declare that the stats array has a section capturing a power estimate
*/
public void addDeviceSectionPowerEstimate() {
- mDevicePowerEstimatePosition = addDeviceSection(1);
+ mDevicePowerEstimatePosition = addDeviceSection(1, "power",
+ FLAG_FORMAT_AS_POWER | FLAG_OPTIONAL);
}
/**
@@ -159,7 +209,7 @@
* Declare that the UID stats array has a section capturing a power estimate
*/
public void addUidSectionPowerEstimate() {
- mUidPowerEstimatePosition = addUidSection(1);
+ mUidPowerEstimatePosition = addUidSection(1, "power", FLAG_FORMAT_AS_POWER | FLAG_OPTIONAL);
}
/**
@@ -195,6 +245,9 @@
mDeviceEnergyConsumerCount);
extras.putInt(EXTRA_DEVICE_POWER_POSITION, mDevicePowerEstimatePosition);
extras.putInt(EXTRA_UID_POWER_POSITION, mUidPowerEstimatePosition);
+ extras.putString(PowerStats.Descriptor.EXTRA_DEVICE_STATS_FORMAT, mDeviceFormat.toString());
+ extras.putString(PowerStats.Descriptor.EXTRA_STATE_STATS_FORMAT, mStateFormat.toString());
+ extras.putString(PowerStats.Descriptor.EXTRA_UID_STATS_FORMAT, mUidFormat.toString());
}
/**
diff --git a/services/core/java/com/android/server/power/stats/PowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/PowerStatsProcessor.java
index 0d5c542..2fd0b9a 100644
--- a/services/core/java/com/android/server/power/stats/PowerStatsProcessor.java
+++ b/services/core/java/com/android/server/power/stats/PowerStatsProcessor.java
@@ -19,8 +19,6 @@
import android.annotation.Nullable;
import android.util.Log;
-import com.android.internal.os.PowerStats;
-
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@@ -47,12 +45,6 @@
abstract void finish(PowerComponentAggregatedPowerStats stats);
- abstract String deviceStatsToString(PowerStats.Descriptor descriptor, long[] stats);
-
- abstract String stateStatsToString(PowerStats.Descriptor descriptor, int key, long[] stats);
-
- abstract String uidStatsToString(PowerStats.Descriptor descriptor, long[] stats);
-
protected static class PowerEstimationPlan {
private final AggregatedPowerStatsConfig.PowerComponent mConfig;
public List<DeviceStateEstimation> deviceStateEstimations = new ArrayList<>();
diff --git a/services/core/java/com/android/server/power/stats/WifiPowerStatsCollector.java b/services/core/java/com/android/server/power/stats/WifiPowerStatsCollector.java
index 6321053..bd04199 100644
--- a/services/core/java/com/android/server/power/stats/WifiPowerStatsCollector.java
+++ b/services/core/java/com/android/server/power/stats/WifiPowerStatsCollector.java
@@ -56,6 +56,7 @@
Handler getHandler();
Clock getClock();
PowerStatsUidResolver getUidResolver();
+ long getPowerStatsCollectionThrottlePeriod(String powerComponentName);
PackageManager getPackageManager();
ConsumedEnergyRetriever getConsumedEnergyRetriever();
IntSupplier getVoltageSupplier();
@@ -92,9 +93,11 @@
private final SparseArray<WifiScanTimes> mLastScanTimes = new SparseArray<>();
private long mLastWifiActiveDuration;
- public WifiPowerStatsCollector(Injector injector, long throttlePeriodMs) {
- super(injector.getHandler(), throttlePeriodMs, injector.getUidResolver(),
- injector.getClock());
+ WifiPowerStatsCollector(Injector injector) {
+ super(injector.getHandler(), injector.getPowerStatsCollectionThrottlePeriod(
+ BatteryConsumer.powerComponentIdToString(
+ BatteryConsumer.POWER_COMPONENT_WIFI)),
+ injector.getUidResolver(), injector.getClock());
mInjector = injector;
}
diff --git a/services/core/java/com/android/server/power/stats/WifiPowerStatsLayout.java b/services/core/java/com/android/server/power/stats/WifiPowerStatsLayout.java
index 0fa6ec6..e2e8226 100644
--- a/services/core/java/com/android/server/power/stats/WifiPowerStatsLayout.java
+++ b/services/core/java/com/android/server/power/stats/WifiPowerStatsLayout.java
@@ -65,28 +65,28 @@
mPowerReportingSupported = powerReportingSupported;
if (mPowerReportingSupported) {
mDeviceActiveTimePosition = UNSPECIFIED;
- mDeviceRxTimePosition = addDeviceSection(1);
- mDeviceTxTimePosition = addDeviceSection(1);
- mDeviceIdleTimePosition = addDeviceSection(1);
- mDeviceScanTimePosition = addDeviceSection(1);
+ mDeviceRxTimePosition = addDeviceSection(1, "rx");
+ mDeviceTxTimePosition = addDeviceSection(1, "tx");
+ mDeviceIdleTimePosition = addDeviceSection(1, "idle");
+ mDeviceScanTimePosition = addDeviceSection(1, "scan");
} else {
- mDeviceActiveTimePosition = addDeviceSection(1);
+ mDeviceActiveTimePosition = addDeviceSection(1, "rx-tx");
mDeviceRxTimePosition = UNSPECIFIED;
mDeviceTxTimePosition = UNSPECIFIED;
mDeviceIdleTimePosition = UNSPECIFIED;
mDeviceScanTimePosition = UNSPECIFIED;
}
- mDeviceBasicScanTimePosition = addDeviceSection(1);
- mDeviceBatchedScanTimePosition = addDeviceSection(1);
+ mDeviceBasicScanTimePosition = addDeviceSection(1, "basic-scan", FLAG_OPTIONAL);
+ mDeviceBatchedScanTimePosition = addDeviceSection(1, "batched-scan", FLAG_OPTIONAL);
}
void addUidNetworkStats() {
- mUidRxBytesPosition = addUidSection(1);
- mUidTxBytesPosition = addUidSection(1);
- mUidRxPacketsPosition = addUidSection(1);
- mUidTxPacketsPosition = addUidSection(1);
- mUidScanTimePosition = addUidSection(1);
- mUidBatchScanTimePosition = addUidSection(1);
+ mUidRxPacketsPosition = addUidSection(1, "rx-pkts");
+ mUidRxBytesPosition = addUidSection(1, "rx-B");
+ mUidTxPacketsPosition = addUidSection(1, "tx-pkts");
+ mUidTxBytesPosition = addUidSection(1, "tx-B");
+ mUidScanTimePosition = addUidSection(1, "scan", FLAG_OPTIONAL);
+ mUidBatchScanTimePosition = addUidSection(1, "batched-scan", FLAG_OPTIONAL);
}
public boolean isPowerReportingSupported() {
diff --git a/services/core/java/com/android/server/power/stats/WifiPowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/WifiPowerStatsProcessor.java
index 5e9cc40..a4a2e18 100644
--- a/services/core/java/com/android/server/power/stats/WifiPowerStatsProcessor.java
+++ b/services/core/java/com/android/server/power/stats/WifiPowerStatsProcessor.java
@@ -389,37 +389,4 @@
}
}
}
-
- @Override
- String deviceStatsToString(PowerStats.Descriptor descriptor, long[] stats) {
- unpackPowerStatsDescriptor(descriptor);
- if (mHasWifiPowerController) {
- return "rx: " + mStatsLayout.getDeviceRxTime(stats)
- + " tx: " + mStatsLayout.getDeviceTxTime(stats)
- + " scan: " + mStatsLayout.getDeviceScanTime(stats)
- + " idle: " + mStatsLayout.getDeviceIdleTime(stats)
- + " power: " + mStatsLayout.getDevicePowerEstimate(stats);
- } else {
- return "active: " + mStatsLayout.getDeviceActiveTime(stats)
- + " scan: " + mStatsLayout.getDeviceBasicScanTime(stats)
- + " batched-scan: " + mStatsLayout.getDeviceBatchedScanTime(stats)
- + " power: " + mStatsLayout.getDevicePowerEstimate(stats);
- }
- }
-
- @Override
- String stateStatsToString(PowerStats.Descriptor descriptor, int key, long[] stats) {
- // Unsupported for this power component
- return null;
- }
-
- @Override
- String uidStatsToString(PowerStats.Descriptor descriptor, long[] stats) {
- unpackPowerStatsDescriptor(descriptor);
- return "rx: " + mStatsLayout.getUidRxPackets(stats)
- + " tx: " + mStatsLayout.getUidTxPackets(stats)
- + " scan: " + mStatsLayout.getUidScanTime(stats)
- + " batched-scan: " + mStatsLayout.getUidBatchedScanTime(stats)
- + " power: " + mStatsLayout.getUidPowerEstimate(stats);
- }
}
diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
index 3b9ad19..c1b825b 100644
--- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
+++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
@@ -836,9 +836,7 @@
registerEventListeners();
});
} else if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) {
- if (true) {
- initNetworkStatsManager();
- }
+ initNetworkStatsManager();
BackgroundThread.getHandler().post(() -> {
// Network stats related pullers can only be initialized after service is ready.
initAndRegisterNetworkStatsPullers();
@@ -859,9 +857,6 @@
mContext.getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE);
mStatsSubscriptionsListener = new StatsSubscriptionsListener(mSubscriptionManager);
mStorageManager = (StorageManager) mContext.getSystemService(StorageManager.class);
- if (false) {
- initNetworkStatsManager();
- }
// Initialize DiskIO
mStoragedUidIoStatsReader = new StoragedUidIoStatsReader();
@@ -1043,10 +1038,8 @@
*/
@NonNull
private NetworkStatsManager getNetworkStatsManager() {
- if (true) {
- if (mNetworkStatsManager == null) {
- throw new IllegalStateException("NetworkStatsManager is not ready");
- }
+ if (mNetworkStatsManager == null) {
+ throw new IllegalStateException("NetworkStatsManager is not ready");
}
return mNetworkStatsManager;
}
diff --git a/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java b/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java
index f6afc52..3393d3e 100644
--- a/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java
+++ b/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java
@@ -150,7 +150,11 @@
@Override
public void onWindowInfosChanged(InputWindowHandle[] windowHandles,
DisplayInfo[] displayInfos) {
- mHandler.post(() -> onWindowInfosChangedInternal(windowHandles, displayInfos));
+ if (com.android.server.accessibility.Flags.removeOnWindowInfosChangedHandler()) {
+ onWindowInfosChangedInternal(windowHandles, displayInfos);
+ } else {
+ mHandler.post(() -> onWindowInfosChangedInternal(windowHandles, displayInfos));
+ }
}
private void onWindowInfosChangedInternal(InputWindowHandle[] windowHandles,
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 2b32a30..76e7f53 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -339,7 +339,6 @@
import android.service.dreams.DreamActivity;
import android.service.voice.IVoiceInteractionSession;
import android.util.ArraySet;
-import android.util.DisplayMetrics;
import android.util.EventLog;
import android.util.Log;
import android.util.MergedConfiguration;
@@ -2123,14 +2122,14 @@
if (mWmService.mFlags.mInsetsDecoupledConfiguration) {
// When the stable configuration is the default behavior, override for the legacy apps
// without forward override flag.
- mResolveConfigHint.mUseOverrideInsetsForStableBounds =
+ mResolveConfigHint.mUseOverrideInsetsForConfig =
!info.isChangeEnabled(INSETS_DECOUPLED_CONFIGURATION_ENFORCED)
&& !info.isChangeEnabled(
OVERRIDE_ENABLE_INSETS_DECOUPLED_CONFIGURATION);
} else {
// When the stable configuration is not the default behavior, forward overriding the
// listed apps.
- mResolveConfigHint.mUseOverrideInsetsForStableBounds =
+ mResolveConfigHint.mUseOverrideInsetsForConfig =
info.isChangeEnabled(OVERRIDE_ENABLE_INSETS_DECOUPLED_CONFIGURATION);
}
@@ -5682,6 +5681,8 @@
} else if (mTransitionController.inFinishingTransition(this)) {
mTransitionChangeFlags |= FLAGS_IS_OCCLUDED_NO_ANIMATION;
}
+ } else {
+ mTransitionChangeFlags &= ~FLAG_IS_OCCLUDED;
}
return;
}
@@ -6529,8 +6530,8 @@
// and the token could be null.
return;
}
- if (r.mDisplayContent.mDisplayRotationCompatPolicy != null) {
- r.mDisplayContent.mDisplayRotationCompatPolicy.onActivityRefreshed(r);
+ if (r.mDisplayContent.mActivityRefresher != null) {
+ r.mDisplayContent.mActivityRefresher.onActivityRefreshed(r);
}
}
@@ -8490,7 +8491,7 @@
mCompatDisplayInsets =
new CompatDisplayInsets(
mDisplayContent, this, letterboxedContainerBounds,
- mResolveConfigHint.mUseOverrideInsetsForStableBounds);
+ mResolveConfigHint.mUseOverrideInsetsForConfig);
}
private void clearSizeCompatModeAttributes() {
@@ -8570,8 +8571,6 @@
final int parentWindowingMode =
newParentConfiguration.windowConfiguration.getWindowingMode();
- applySizeOverrideIfNeeded(newParentConfiguration, parentWindowingMode, resolvedConfig);
-
// Bubble activities should always fill their parent and should not be letterboxed.
final boolean isFixedOrientationLetterboxAllowed = !getLaunchedFromBubble()
&& (parentWindowingMode == WINDOWING_MODE_MULTI_WINDOW
@@ -8671,6 +8670,8 @@
resolvedConfig.windowConfiguration.setMaxBounds(mTmpBounds);
}
+ applySizeOverrideIfNeeded(newParentConfiguration, parentWindowingMode, resolvedConfig);
+
logAppCompatState();
}
@@ -8689,14 +8690,13 @@
if (mDisplayContent == null) {
return;
}
- final Rect parentBounds = newParentConfiguration.windowConfiguration.getBounds();
int rotation = newParentConfiguration.windowConfiguration.getRotation();
if (rotation == ROTATION_UNDEFINED && !isFixedRotationTransforming()) {
rotation = mDisplayContent.getRotation();
}
- if (!mResolveConfigHint.mUseOverrideInsetsForStableBounds
- || getCompatDisplayInsets() != null || isFloating(parentWindowingMode)
- || rotation == ROTATION_UNDEFINED) {
+ if (!mResolveConfigHint.mUseOverrideInsetsForConfig
+ || getCompatDisplayInsets() != null || shouldCreateCompatDisplayInsets()
+ || isFloating(parentWindowingMode) || rotation == ROTATION_UNDEFINED) {
// If the insets configuration decoupled logic is not enabled for the app, or the app
// already has a compat override, or the context doesn't contain enough info to
// calculate the override, skip the override.
@@ -8713,53 +8713,7 @@
}
// Override starts here.
- final boolean rotated = (rotation == ROTATION_90 || rotation == ROTATION_270);
- final int dw = rotated ? mDisplayContent.mBaseDisplayHeight
- : mDisplayContent.mBaseDisplayWidth;
- final int dh = rotated ? mDisplayContent.mBaseDisplayWidth
- : mDisplayContent.mBaseDisplayHeight;
- final Rect nonDecorInsets = mDisplayContent.getDisplayPolicy()
- .getDecorInsetsInfo(rotation, dw, dh).mOverrideNonDecorInsets;
- // This should be the only place override the configuration for ActivityRecord. Override
- // the value if not calculated yet.
- Rect outAppBounds = inOutConfig.windowConfiguration.getAppBounds();
- if (outAppBounds == null || outAppBounds.isEmpty()) {
- inOutConfig.windowConfiguration.setAppBounds(parentBounds);
- outAppBounds = inOutConfig.windowConfiguration.getAppBounds();
- outAppBounds.inset(nonDecorInsets);
- }
- float density = inOutConfig.densityDpi;
- if (density == Configuration.DENSITY_DPI_UNDEFINED) {
- density = newParentConfiguration.densityDpi;
- }
- density *= DisplayMetrics.DENSITY_DEFAULT_SCALE;
- if (inOutConfig.screenWidthDp == Configuration.SCREEN_WIDTH_DP_UNDEFINED) {
- final int overrideScreenWidthDp = (int) (outAppBounds.width() / density + 0.5f);
- inOutConfig.screenWidthDp = overrideScreenWidthDp;
- }
- if (inOutConfig.screenHeightDp == Configuration.SCREEN_HEIGHT_DP_UNDEFINED) {
- final int overrideScreenHeightDp = (int) (outAppBounds.height() / density + 0.5f);
- inOutConfig.screenHeightDp = overrideScreenHeightDp;
- }
- if (inOutConfig.smallestScreenWidthDp
- == Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED
- && parentWindowingMode == WINDOWING_MODE_FULLSCREEN) {
- // For the case of PIP transition and multi-window environment, the
- // smallestScreenWidthDp is handled already. Override only if the app is in
- // fullscreen.
- final DisplayInfo info = new DisplayInfo(mDisplayContent.getDisplayInfo());
- mDisplayContent.computeSizeRanges(info, rotated, dw, dh,
- mDisplayContent.getDisplayMetrics().density,
- inOutConfig, true /* overrideConfig */);
- }
-
- // It's possible that screen size will be considered in different orientation with or
- // without considering the system bar insets. Override orientation as well.
- if (inOutConfig.orientation == ORIENTATION_UNDEFINED) {
- inOutConfig.orientation =
- (inOutConfig.screenWidthDp <= inOutConfig.screenHeightDp)
- ? ORIENTATION_PORTRAIT : ORIENTATION_LANDSCAPE;
- }
+ computeConfigByResolveHint(inOutConfig, newParentConfiguration);
}
private void computeConfigByResolveHint(@NonNull Configuration resolvedConfig,
@@ -9015,7 +8969,7 @@
if (mDisplayContent == null) {
return true;
}
- if (!mResolveConfigHint.mUseOverrideInsetsForStableBounds) {
+ if (!mResolveConfigHint.mUseOverrideInsetsForConfig) {
// No insets should be considered any more.
return true;
}
@@ -9034,7 +8988,7 @@
final Task task = getTask();
task.calculateInsetFrames(outNonDecorBounds /* outNonDecorBounds */,
outStableBounds /* outStableBounds */, parentBounds /* bounds */, di,
- mResolveConfigHint.mUseOverrideInsetsForStableBounds);
+ mResolveConfigHint.mUseOverrideInsetsForConfig);
final int orientationWithInsets = outStableBounds.height() >= outStableBounds.width()
? ORIENTATION_PORTRAIT : ORIENTATION_LANDSCAPE;
// If orientation does not match the orientation with insets applied, then a
@@ -9091,7 +9045,7 @@
getResolvedOverrideConfiguration().windowConfiguration.getBounds();
final int stableBoundsOrientation = stableBounds.width() > stableBounds.height()
? ORIENTATION_LANDSCAPE : ORIENTATION_PORTRAIT;
- final int parentOrientation = mResolveConfigHint.mUseOverrideInsetsForStableBounds
+ final int parentOrientation = mResolveConfigHint.mUseOverrideInsetsForConfig
? stableBoundsOrientation : newParentConfig.orientation;
// If the activity requires a different orientation (either by override or activityInfo),
@@ -9116,7 +9070,7 @@
return;
}
- final Rect parentAppBounds = mResolveConfigHint.mUseOverrideInsetsForStableBounds
+ final Rect parentAppBounds = mResolveConfigHint.mUseOverrideInsetsForConfig
? outNonDecorBounds : newParentConfig.windowConfiguration.getAppBounds();
// TODO(b/182268157): Explore using only one type of parentBoundsWithInsets, either app
// bounds or stable bounds to unify aspect ratio logic.
@@ -10042,7 +9996,7 @@
} else {
scheduleConfigurationChanged(newMergedOverrideConfig, newActivityWindowInfo);
}
- notifyDisplayCompatPolicyAboutConfigurationChange(
+ notifyActivityRefresherAboutConfigurationChange(
mLastReportedConfiguration.getMergedConfiguration(), mTmpConfig);
return true;
}
@@ -10109,18 +10063,18 @@
} else {
scheduleConfigurationChanged(newMergedOverrideConfig, newActivityWindowInfo);
}
- notifyDisplayCompatPolicyAboutConfigurationChange(
+ notifyActivityRefresherAboutConfigurationChange(
mLastReportedConfiguration.getMergedConfiguration(), mTmpConfig);
return true;
}
- private void notifyDisplayCompatPolicyAboutConfigurationChange(
+ private void notifyActivityRefresherAboutConfigurationChange(
Configuration newConfig, Configuration lastReportedConfig) {
- if (mDisplayContent.mDisplayRotationCompatPolicy == null
+ if (mDisplayContent.mActivityRefresher == null
|| !shouldBeResumed(/* activeActivity */ null)) {
return;
}
- mDisplayContent.mDisplayRotationCompatPolicy.onActivityConfigurationChanging(
+ mDisplayContent.mActivityRefresher.onActivityConfigurationChanging(
this, newConfig, lastReportedConfig);
}
diff --git a/services/core/java/com/android/server/wm/ActivityRefresher.java b/services/core/java/com/android/server/wm/ActivityRefresher.java
new file mode 100644
index 0000000..23a9708
--- /dev/null
+++ b/services/core/java/com/android/server/wm/ActivityRefresher.java
@@ -0,0 +1,131 @@
+/*
+ * 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.server.wm;
+
+import static android.app.servertransaction.ActivityLifecycleItem.ON_PAUSE;
+import static android.app.servertransaction.ActivityLifecycleItem.ON_STOP;
+
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_STATES;
+
+import android.annotation.NonNull;
+import android.app.servertransaction.RefreshCallbackItem;
+import android.app.servertransaction.ResumeActivityItem;
+import android.content.res.Configuration;
+import android.os.Handler;
+import android.os.RemoteException;
+
+import com.android.internal.protolog.common.ProtoLog;
+import com.android.internal.util.ArrayUtils;
+
+import java.util.ArrayList;
+
+/**
+ * Class that refreshes the activity (through stop/pause -> resume) based on configuration change.
+ *
+ * <p>This class queries all of its {@link Evaluator}s and restarts the activity if any of them
+ * return {@code true} in {@link Evaluator#shouldRefreshActivity}. {@link ActivityRefresher} cycles
+ * through either stop or pause and then resume, based on the global config and per-app override.
+ */
+class ActivityRefresher {
+ // Delay for ensuring that onActivityRefreshed is always called after an activity refresh. The
+ // client process may not always report the event back to the server, such as process is
+ // crashed or got killed.
+ private static final long REFRESH_CALLBACK_TIMEOUT_MS = 2000L;
+
+ @NonNull private final WindowManagerService mWmService;
+ @NonNull private final Handler mHandler;
+ @NonNull private final ArrayList<Evaluator> mEvaluators = new ArrayList<>();
+
+ ActivityRefresher(@NonNull WindowManagerService wmService, @NonNull Handler handler) {
+ mWmService = wmService;
+ mHandler = handler;
+ }
+
+ void addEvaluator(@NonNull Evaluator evaluator) {
+ mEvaluators.add(evaluator);
+ }
+
+ void removeEvaluator(@NonNull Evaluator evaluator) {
+ mEvaluators.remove(evaluator);
+ }
+
+ /**
+ * "Refreshes" activity by going through "stopped -> resumed" or "paused -> resumed" cycle.
+ * This allows to clear cached values in apps (e.g. display or camera rotation) that influence
+ * camera preview and can lead to sideways or stretching issues persisting even after force
+ * rotation.
+ */
+ void onActivityConfigurationChanging(@NonNull ActivityRecord activity,
+ @NonNull Configuration newConfig, @NonNull Configuration lastReportedConfig) {
+ if (!shouldRefreshActivity(activity, newConfig, lastReportedConfig)) {
+ return;
+ }
+
+ final boolean cycleThroughStop =
+ mWmService.mLetterboxConfiguration
+ .isCameraCompatRefreshCycleThroughStopEnabled()
+ && !activity.mLetterboxUiController
+ .shouldRefreshActivityViaPauseForCameraCompat();
+
+ activity.mLetterboxUiController.setIsRefreshRequested(true);
+ ProtoLog.v(WM_DEBUG_STATES,
+ "Refreshing activity for freeform camera compatibility treatment, "
+ + "activityRecord=%s", activity);
+ final RefreshCallbackItem refreshCallbackItem = RefreshCallbackItem.obtain(
+ activity.token, cycleThroughStop ? ON_STOP : ON_PAUSE);
+ final ResumeActivityItem resumeActivityItem = ResumeActivityItem.obtain(
+ activity.token, /* isForward */ false, /* shouldSendCompatFakeFocus */ false);
+ try {
+ activity.mAtmService.getLifecycleManager().scheduleTransactionAndLifecycleItems(
+ activity.app.getThread(), refreshCallbackItem, resumeActivityItem);
+ mHandler.postDelayed(() -> {
+ synchronized (mWmService.mGlobalLock) {
+ onActivityRefreshed(activity);
+ }
+ }, REFRESH_CALLBACK_TIMEOUT_MS);
+ } catch (RemoteException e) {
+ activity.mLetterboxUiController.setIsRefreshRequested(false);
+ }
+ }
+
+ boolean isActivityRefreshing(@NonNull ActivityRecord activity) {
+ return activity.mLetterboxUiController.isRefreshRequested();
+ }
+
+ void onActivityRefreshed(@NonNull ActivityRecord activity) {
+ // TODO(b/333060789): can we tell that refresh did not happen by observing the activity
+ // state?
+ activity.mLetterboxUiController.setIsRefreshRequested(false);
+ }
+
+ private boolean shouldRefreshActivity(@NonNull ActivityRecord activity,
+ @NonNull Configuration newConfig, @NonNull Configuration lastReportedConfig) {
+ return mWmService.mLetterboxConfiguration.isCameraCompatRefreshEnabled()
+ && activity.mLetterboxUiController.shouldRefreshActivityForCameraCompat()
+ && ArrayUtils.find(mEvaluators.toArray(), evaluator ->
+ ((Evaluator) evaluator)
+ .shouldRefreshActivity(activity, newConfig, lastReportedConfig)) != null;
+ }
+
+ /**
+ * Interface for classes that would like to refresh the recently updated activity, based on the
+ * configuration change.
+ */
+ interface Evaluator {
+ boolean shouldRefreshActivity(@NonNull ActivityRecord activity,
+ @NonNull Configuration newConfig, @NonNull Configuration lastReportedConfig);
+ }
+}
diff --git a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
index eb1f3b4..f7910b0 100644
--- a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
+++ b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
@@ -20,6 +20,7 @@
import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT;
import static android.app.ActivityOptions.BackgroundActivityStartMode;
import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED;
+import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_COMPAT;
import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED;
import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
@@ -105,6 +106,7 @@
static final String AUTO_OPT_IN_NOT_PENDING_INTENT = "notPendingIntent";
static final String AUTO_OPT_IN_CALL_FOR_RESULT = "callForResult";
static final String AUTO_OPT_IN_SAME_UID = "sameUid";
+ static final String AUTO_OPT_IN_COMPAT = "compatibility";
/** If enabled the creator will not allow BAL on its behalf by default. */
@ChangeId
@@ -303,6 +305,10 @@
} else if (callingUid == realCallingUid && !balRequireOptInSameUid()) {
mAutoOptInReason = AUTO_OPT_IN_SAME_UID;
mAutoOptInCaller = false;
+ } else if (realCallerBackgroundActivityStartMode
+ == MODE_BACKGROUND_ACTIVITY_START_COMPAT) {
+ mAutoOptInReason = AUTO_OPT_IN_COMPAT;
+ mAutoOptInCaller = false;
} else {
mAutoOptInReason = null;
mAutoOptInCaller = false;
@@ -1695,6 +1701,7 @@
return false;
}
if (state.mBalAllowedByPiSender.allowsBackgroundActivityStarts()
+ && state.mResultForRealCaller != null
&& state.mResultForRealCaller.getRawCode() == BAL_ALLOW_VISIBLE_WINDOW) {
return false;
}
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 5079ec1..e49cb38 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -478,6 +478,8 @@
final DisplayRotationCompatPolicy mDisplayRotationCompatPolicy;
@Nullable
final CameraStateMonitor mCameraStateMonitor;
+ @Nullable
+ final ActivityRefresher mActivityRefresher;
DisplayFrames mDisplayFrames;
final DisplayUpdater mDisplayUpdater;
@@ -1233,13 +1235,15 @@
mWmService.mLetterboxConfiguration.isCameraCompatTreatmentEnabledAtBuildTime();
if (shouldCreateDisplayRotationCompatPolicy) {
mCameraStateMonitor = new CameraStateMonitor(this, mWmService.mH);
+ mActivityRefresher = new ActivityRefresher(mWmService, mWmService.mH);
mDisplayRotationCompatPolicy = new DisplayRotationCompatPolicy(
- this, mWmService.mH, mCameraStateMonitor);
+ this, mCameraStateMonitor, mActivityRefresher);
mCameraStateMonitor.startListeningToCameraState();
} else {
// These are to satisfy the `final` check.
mCameraStateMonitor = null;
+ mActivityRefresher = null;
mDisplayRotationCompatPolicy = null;
}
diff --git a/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java b/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java
index eacf9a3..e0cc064 100644
--- a/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java
@@ -18,8 +18,6 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
-import static android.app.servertransaction.ActivityLifecycleItem.ON_PAUSE;
-import static android.app.servertransaction.ActivityLifecycleItem.ON_STOP;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LOCKED;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_NOSENSOR;
@@ -32,19 +30,14 @@
import static android.view.Display.TYPE_INTERNAL;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION;
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_STATES;
import static com.android.server.wm.DisplayRotationReversionController.REVERSION_TYPE_CAMERA_COMPAT;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.StringRes;
-import android.app.servertransaction.RefreshCallbackItem;
-import android.app.servertransaction.ResumeActivityItem;
import android.content.pm.ActivityInfo.ScreenOrientation;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
-import android.os.Handler;
-import android.os.RemoteException;
import android.widget.Toast;
import com.android.internal.R;
@@ -64,48 +57,38 @@
* R.bool.config_isWindowManagerCameraCompatTreatmentEnabled} is {@code true}.
*/
// TODO(b/261444714): Consider moving Camera-specific logic outside of the WM Core path
-class DisplayRotationCompatPolicy implements CameraStateMonitor.CameraCompatStateListener {
+final class DisplayRotationCompatPolicy implements CameraStateMonitor.CameraCompatStateListener,
+ ActivityRefresher.Evaluator {
- // Delay for updating display rotation after Camera connection is closed. Needed to avoid
- // rotation flickering when an app is flipping between front and rear cameras or when size
- // compat mode is restarted.
- // TODO(b/263114289): Consider associating this delay with a specific activity so that if
- // the new non-camera activity started on top of the camer one we can rotate faster.
- private static final int CAMERA_CLOSED_ROTATION_UPDATE_DELAY_MS = 2000;
- // Delay for updating display rotation after Camera connection is opened. This delay is
- // selected to be long enough to avoid conflicts with transitions on the app's side.
- // Using a delay < CAMERA_CLOSED_ROTATION_UPDATE_DELAY_MS to avoid flickering when an app
- // is flipping between front and rear cameras (in case requested orientation changes at
- // runtime at the same time) or when size compat mode is restarted.
- private static final int CAMERA_OPENED_ROTATION_UPDATE_DELAY_MS =
- CAMERA_CLOSED_ROTATION_UPDATE_DELAY_MS / 2;
- // Delay for ensuring that onActivityRefreshed is always called after an activity refresh. The
- // client process may not always report the event back to the server, such as process is
- // crashed or got killed.
- private static final int REFRESH_CALLBACK_TIMEOUT_MS = 2000;
-
+ @NonNull
private final DisplayContent mDisplayContent;
+ @NonNull
private final WindowManagerService mWmService;
+ @NonNull
private final CameraStateMonitor mCameraStateMonitor;
- private final Handler mHandler;
+ @NonNull
+ private final ActivityRefresher mActivityRefresher;
@ScreenOrientation
private int mLastReportedOrientation = SCREEN_ORIENTATION_UNSET;
- DisplayRotationCompatPolicy(@NonNull DisplayContent displayContent, Handler handler,
- @NonNull CameraStateMonitor cameraStateMonitor) {
+ DisplayRotationCompatPolicy(@NonNull DisplayContent displayContent,
+ @NonNull CameraStateMonitor cameraStateMonitor,
+ @NonNull ActivityRefresher activityRefresher) {
// This constructor is called from DisplayContent constructor. Don't use any fields in
// DisplayContent here since they aren't guaranteed to be set.
- mHandler = handler;
mDisplayContent = displayContent;
mWmService = displayContent.mWmService;
mCameraStateMonitor = cameraStateMonitor;
mCameraStateMonitor.addCameraStateListener(this);
+ mActivityRefresher = activityRefresher;
+ mActivityRefresher.addEvaluator(this);
}
/** Releases camera state listener. */
void dispose() {
mCameraStateMonitor.removeCameraStateListener(this);
+ mActivityRefresher.removeEvaluator(this);
}
/**
@@ -169,47 +152,6 @@
}
/**
- * "Refreshes" activity by going through "stopped -> resumed" or "paused -> resumed" cycle.
- * This allows to clear cached values in apps (e.g. display or camera rotation) that influence
- * camera preview and can lead to sideways or stretching issues persisting even after force
- * rotation.
- */
- void onActivityConfigurationChanging(ActivityRecord activity, Configuration newConfig,
- Configuration lastReportedConfig) {
- if (!isTreatmentEnabledForDisplay()
- || !mWmService.mLetterboxConfiguration.isCameraCompatRefreshEnabled()
- || !shouldRefreshActivity(activity, newConfig, lastReportedConfig)) {
- return;
- }
- boolean cycleThroughStop =
- mWmService.mLetterboxConfiguration
- .isCameraCompatRefreshCycleThroughStopEnabled()
- && !activity.mLetterboxUiController
- .shouldRefreshActivityViaPauseForCameraCompat();
- try {
- activity.mLetterboxUiController.setIsRefreshAfterRotationRequested(true);
- ProtoLog.v(WM_DEBUG_STATES,
- "Refreshing activity for camera compatibility treatment, "
- + "activityRecord=%s", activity);
- final RefreshCallbackItem refreshCallbackItem = RefreshCallbackItem.obtain(
- activity.token, cycleThroughStop ? ON_STOP : ON_PAUSE);
- final ResumeActivityItem resumeActivityItem = ResumeActivityItem.obtain(
- activity.token, /* isForward */ false, /* shouldSendCompatFakeFocus */ false);
- activity.mAtmService.getLifecycleManager().scheduleTransactionAndLifecycleItems(
- activity.app.getThread(), refreshCallbackItem, resumeActivityItem);
- mHandler.postDelayed(
- () -> onActivityRefreshed(activity),
- REFRESH_CALLBACK_TIMEOUT_MS);
- } catch (RemoteException e) {
- activity.mLetterboxUiController.setIsRefreshAfterRotationRequested(false);
- }
- }
-
- void onActivityRefreshed(@NonNull ActivityRecord activity) {
- activity.mLetterboxUiController.setIsRefreshAfterRotationRequested(false);
- }
-
- /**
* Notifies that animation in {@link ScreenRotationAnimation} has finished.
*
* <p>This class uses this signal as a trigger for notifying the user about forced rotation
@@ -276,14 +218,16 @@
// Refreshing only when configuration changes after rotation or camera split screen aspect ratio
// treatment is enabled
- private boolean shouldRefreshActivity(ActivityRecord activity, Configuration newConfig,
- Configuration lastReportedConfig) {
+ @Override
+ public boolean shouldRefreshActivity(@NonNull ActivityRecord activity,
+ @NonNull Configuration newConfig, @NonNull Configuration lastReportedConfig) {
final boolean displayRotationChanged = (newConfig.windowConfiguration.getDisplayRotation()
!= lastReportedConfig.windowConfiguration.getDisplayRotation());
- return (displayRotationChanged
- || activity.mLetterboxUiController.isCameraCompatSplitScreenAspectRatioAllowed())
+ return isTreatmentEnabledForDisplay()
&& isTreatmentEnabledForActivity(activity)
- && activity.mLetterboxUiController.shouldRefreshActivityForCameraCompat();
+ && activity.mLetterboxUiController.shouldRefreshActivityForCameraCompat()
+ && (displayRotationChanged
+ || activity.mLetterboxUiController.isCameraCompatSplitScreenAspectRatioAllowed());
}
/**
@@ -310,7 +254,6 @@
&& activity.mLetterboxUiController.shouldForceRotateForCameraCompat();
}
-
/**
* Whether camera compat treatment is applicable for the given activity.
*
@@ -429,6 +372,6 @@
|| !mCameraStateMonitor.isCameraWithIdRunningForActivity(topActivity, cameraId)) {
return false;
}
- return topActivity.mLetterboxUiController.isRefreshAfterRotationRequested();
+ return mActivityRefresher.isActivityRefreshing(topActivity);
}
}
diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
index 4400ed2..2288fe9 100644
--- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
@@ -198,7 +198,7 @@
if (mControllable) {
mWindowContainer.setControllableInsetProvider(this);
if (mPendingControlTarget != mControlTarget) {
- updateControlForTarget(mPendingControlTarget, true /* force */);
+ mStateController.notifyControlTargetChanged(mPendingControlTarget, this);
}
}
}
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index 57827c5..16d7b4f 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -260,7 +260,7 @@
// Whether activity "refresh" was requested but not finished in
// ActivityRecord#activityResumedLocked following the camera compat force rotation in
// DisplayRotationCompatPolicy.
- private boolean mIsRefreshAfterRotationRequested;
+ private boolean mIsRefreshRequested;
@NonNull
private final OptProp mIgnoreRequestedOrientationOptProp;
@@ -571,15 +571,14 @@
}
/**
- * Whether activity "refresh" was requested but not finished in {@link #activityResumedLocked}
- * following the camera compat force rotation in {@link DisplayRotationCompatPolicy}.
+ * Whether activity "refresh" was requested but not finished in {@link #activityResumedLocked}.
*/
- boolean isRefreshAfterRotationRequested() {
- return mIsRefreshAfterRotationRequested;
+ boolean isRefreshRequested() {
+ return mIsRefreshRequested;
}
- void setIsRefreshAfterRotationRequested(boolean isRequested) {
- mIsRefreshAfterRotationRequested = isRequested;
+ void setIsRefreshRequested(boolean isRequested) {
+ mIsRefreshRequested = isRequested;
}
boolean isOverrideRespectRequestedOrientationEnabled() {
@@ -1068,7 +1067,7 @@
* thin letteboxing
*/
boolean allowVerticalReachabilityForThinLetterbox() {
- if (!Flags.disableThinLetterboxingReachability()) {
+ if (!Flags.disableThinLetterboxingPolicy()) {
return true;
}
// When the flag is enabled we allow vertical reachability only if the
@@ -1081,7 +1080,7 @@
* thin letteboxing
*/
boolean allowHorizontalReachabilityForThinLetterbox() {
- if (!Flags.disableThinLetterboxingReachability()) {
+ if (!Flags.disableThinLetterboxingPolicy()) {
return true;
}
// When the flag is enabled we allow horizontal reachability only if the
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index a444c96..26e4eaa 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -40,6 +40,8 @@
import static android.os.Process.SYSTEM_UID;
import static android.os.UserHandle.USER_NULL;
import static android.view.Display.INVALID_DISPLAY;
+import static android.view.Surface.ROTATION_270;
+import static android.view.Surface.ROTATION_90;
import static android.view.WindowManager.LayoutParams.FLAG_DIM_BEHIND;
import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_FLAG_OPEN_BEHIND;
@@ -2222,7 +2224,7 @@
static class ConfigOverrideHint {
@Nullable DisplayInfo mTmpOverrideDisplayInfo;
@Nullable ActivityRecord.CompatDisplayInsets mTmpCompatInsets;
- boolean mUseOverrideInsetsForStableBounds;
+ boolean mUseOverrideInsetsForConfig;
}
void computeConfigResourceOverrides(@NonNull Configuration inOutConfig,
@@ -2255,11 +2257,11 @@
@NonNull Configuration parentConfig, @Nullable ConfigOverrideHint overrideHint) {
DisplayInfo overrideDisplayInfo = null;
ActivityRecord.CompatDisplayInsets compatInsets = null;
- boolean useOverrideInsetsForStableBounds = false;
+ boolean useOverrideInsetsForConfig = false;
if (overrideHint != null) {
overrideDisplayInfo = overrideHint.mTmpOverrideDisplayInfo;
compatInsets = overrideHint.mTmpCompatInsets;
- useOverrideInsetsForStableBounds = overrideHint.mUseOverrideInsetsForStableBounds;
+ useOverrideInsetsForConfig = overrideHint.mUseOverrideInsetsForConfig;
if (overrideDisplayInfo != null) {
// Make sure the screen related configs can be computed by the provided
// display info.
@@ -2323,6 +2325,7 @@
}
}
+ boolean insetsOverrideApplied = false;
if (inOutConfig.screenWidthDp == Configuration.SCREEN_WIDTH_DP_UNDEFINED
|| inOutConfig.screenHeightDp == Configuration.SCREEN_HEIGHT_DP_UNDEFINED) {
if (!customContainerPolicy && WindowConfiguration.isFloating(windowingMode)) {
@@ -2339,7 +2342,7 @@
// The non decor inset are areas that could never be removed in Honeycomb. See
// {@link WindowManagerPolicy#getNonDecorInsetsLw}.
calculateInsetFrames(mTmpNonDecorBounds, mTmpStableBounds, mTmpFullBounds, di,
- useOverrideInsetsForStableBounds);
+ useOverrideInsetsForConfig);
} else {
// Apply the given non-decor and stable insets to calculate the corresponding bounds
// for screen size of configuration.
@@ -2356,8 +2359,21 @@
intersectWithInsetsIfFits(mTmpStableBounds, mTmpBounds,
compatInsets.mStableInsets[rotation]);
outAppBounds.set(mTmpNonDecorBounds);
+ } else if (useOverrideInsetsForConfig) {
+ final boolean rotated = (rotation == ROTATION_90 || rotation == ROTATION_270);
+ final int dw = rotated ? mDisplayContent.mBaseDisplayHeight
+ : mDisplayContent.mBaseDisplayWidth;
+ final int dh = rotated ? mDisplayContent.mBaseDisplayWidth
+ : mDisplayContent.mBaseDisplayHeight;
+ final DisplayPolicy.DecorInsets.Info decorInsets = mDisplayContent
+ .getDisplayPolicy().getDecorInsetsInfo(rotation, dw, dh);
+ mTmpStableBounds.set(outAppBounds);
+ mTmpStableBounds.inset(decorInsets.mOverrideConfigInsets);
+ outAppBounds.inset(decorInsets.mOverrideNonDecorInsets);
+ mTmpNonDecorBounds.set(outAppBounds);
+ // Record the override apply to avoid duplicated check.
+ insetsOverrideApplied = true;
} else {
- // Set to app bounds because it excludes decor insets.
mTmpNonDecorBounds.set(outAppBounds);
mTmpStableBounds.set(outAppBounds);
}
@@ -2399,6 +2415,11 @@
// from the parent task would result in applications loaded wrong resource.
inOutConfig.smallestScreenWidthDp =
Math.min(inOutConfig.screenWidthDp, inOutConfig.screenHeightDp);
+ } else if (insetsOverrideApplied) {
+ // The smallest width should also consider insets. If the insets are overridden,
+ // use the overridden value.
+ inOutConfig.smallestScreenWidthDp =
+ Math.min(inOutConfig.screenWidthDp, inOutConfig.screenHeightDp);
}
// otherwise, it will just inherit
}
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index 74ca9ad..97f1e19 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -122,7 +122,6 @@
jmethodID interceptMotionBeforeQueueingNonInteractive;
jmethodID interceptKeyBeforeDispatching;
jmethodID dispatchUnhandledKey;
- jmethodID onPointerDisplayIdChanged;
jmethodID onPointerDownOutsideFocus;
jmethodID getVirtualKeyQuietTimeMillis;
jmethodID getExcludedDeviceNames;
@@ -786,12 +785,6 @@
} // release lock
mInputManager->getReader().requestRefreshConfiguration(
InputReaderConfiguration::Change::DISPLAY_INFO);
-
- // Notify the system.
- JNIEnv* env = jniEnv();
- env->CallVoidMethod(mServiceObj, gServiceClassInfo.onPointerDisplayIdChanged, pointerDisplayId,
- position.x, position.y);
- checkAndClearExceptionFromCallback(env, "onPointerDisplayIdChanged");
}
void NativeInputManager::notifyStickyModifierStateChanged(uint32_t modifierState,
@@ -2933,9 +2926,6 @@
"dispatchUnhandledKey",
"(Landroid/os/IBinder;Landroid/view/KeyEvent;I)Landroid/view/KeyEvent;");
- GET_METHOD_ID(gServiceClassInfo.onPointerDisplayIdChanged, clazz, "onPointerDisplayIdChanged",
- "(IFF)V");
-
GET_METHOD_ID(gServiceClassInfo.notifyStickyModifierStateChanged, clazz,
"notifyStickyModifierStateChanged", "(II)V");
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
index d114337..d733762 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
@@ -217,7 +217,7 @@
<V> void setLocalPolicy(
@NonNull PolicyDefinition<V> policyDefinition,
@NonNull EnforcingAdmin enforcingAdmin,
- @Nullable PolicyValue<V> value,
+ @NonNull PolicyValue<V> value,
int userId,
boolean skipEnforcePolicy) {
Objects.requireNonNull(policyDefinition);
@@ -313,6 +313,7 @@
}
updateDeviceAdminServiceOnPolicyAddLocked(enforcingAdmin);
write();
+ applyToInheritableProfiles(policyDefinition, enforcingAdmin, value, userId);
}
// TODO: add more documentation on broadcasts/callbacks to use to get current enforced values
@@ -400,7 +401,7 @@
* else remove the policy from child.
*/
private <V> void applyToInheritableProfiles(PolicyDefinition<V> policyDefinition,
- EnforcingAdmin enforcingAdmin, PolicyValue<V> value, int userId) {
+ EnforcingAdmin enforcingAdmin, @Nullable PolicyValue<V> value, int userId) {
if (policyDefinition.isInheritable()) {
Binder.withCleanCallingIdentity(() -> {
List<UserInfo> userInfos = mUserManager.getProfiles(userId);
@@ -1742,14 +1743,17 @@
}
}
- <V> void reapplyAllPoliciesLocked() {
+ <V> void reapplyAllPoliciesOnBootLocked() {
for (PolicyKey policy : mGlobalPolicies.keySet()) {
PolicyState<?> policyState = mGlobalPolicies.get(policy);
// Policy definition and value will always be of the same type
PolicyDefinition<V> policyDefinition =
(PolicyDefinition<V>) policyState.getPolicyDefinition();
- PolicyValue<V> policyValue = (PolicyValue<V>) policyState.getCurrentResolvedPolicy();
- enforcePolicy(policyDefinition, policyValue, UserHandle.USER_ALL);
+ if (!policyDefinition.shouldSkipEnforcementIfNotChanged()) {
+ PolicyValue<V> policyValue =
+ (PolicyValue<V>) policyState.getCurrentResolvedPolicy();
+ enforcePolicy(policyDefinition, policyValue, UserHandle.USER_ALL);
+ }
}
for (int i = 0; i < mLocalPolicies.size(); i++) {
int userId = mLocalPolicies.keyAt(i);
@@ -1758,10 +1762,11 @@
// Policy definition and value will always be of the same type
PolicyDefinition<V> policyDefinition =
(PolicyDefinition<V>) policyState.getPolicyDefinition();
- PolicyValue<V> policyValue =
- (PolicyValue<V>) policyState.getCurrentResolvedPolicy();
- enforcePolicy(policyDefinition, policyValue, userId);
-
+ if (!policyDefinition.shouldSkipEnforcementIfNotChanged()) {
+ PolicyValue<V> policyValue =
+ (PolicyValue<V>) policyState.getCurrentResolvedPolicy();
+ enforcePolicy(policyDefinition, policyValue, userId);
+ }
}
}
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 2b93d21..85d2a0d 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -3351,7 +3351,7 @@
break;
case SystemService.PHASE_SYSTEM_SERVICES_READY:
synchronized (getLockObject()) {
- mDevicePolicyEngine.reapplyAllPoliciesLocked();
+ mDevicePolicyEngine.reapplyAllPoliciesOnBootLocked();
}
break;
case SystemService.PHASE_ACTIVITY_MANAGER_READY:
@@ -11443,7 +11443,7 @@
}
setBackwardsCompatibleAppRestrictions(
caller, packageName, restrictions, caller.getUserHandle());
- } else if (Flags.dmrhCanSetAppRestriction()) {
+ } else if (Flags.dmrhSetAppRestrictions()) {
final boolean isRoleHolder;
if (who != null) {
// DO or PO
@@ -11484,10 +11484,6 @@
new BundlePolicyValue(restrictions),
affectedUserId);
}
- Intent changeIntent = new Intent(Intent.ACTION_APPLICATION_RESTRICTIONS_CHANGED);
- changeIntent.setPackage(packageName);
- changeIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
- mContext.sendBroadcastAsUser(changeIntent, UserHandle.of(affectedUserId));
} else {
mInjector.binderWithCleanCallingIdentity(() -> {
mUserManager.setApplicationRestrictions(packageName, restrictions,
@@ -12845,7 +12841,7 @@
return Bundle.EMPTY;
}
return policies.get(enforcingAdmin).getValue();
- } else if (Flags.dmrhCanSetAppRestriction()) {
+ } else if (Flags.dmrhSetAppRestrictions()) {
final boolean isRoleHolder;
if (who != null) {
// Caller is DO or PO. They cannot call this on parent
@@ -15770,8 +15766,13 @@
PolicyDefinition.APPLICATION_RESTRICTIONS(packageName),
userId);
List<Bundle> restrictions = new ArrayList<>();
- for (EnforcingAdmin admin : policies.keySet()) {
- restrictions.add(policies.get(admin).getValue());
+ for (PolicyValue<Bundle> policyValue: policies.values()) {
+ Bundle value = policyValue.getValue();
+ // Probably not necessary since setApplicationRestrictions only sets non-empty
+ // Bundle, but just in case.
+ if (value != null && !value.isEmpty()) {
+ restrictions.add(value);
+ }
}
return mInjector.binderWithCleanCallingIdentity(() -> {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/EnterpriseSpecificIdCalculator.java b/services/devicepolicy/java/com/android/server/devicepolicy/EnterpriseSpecificIdCalculator.java
index 1000bfa..cbd2847 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/EnterpriseSpecificIdCalculator.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/EnterpriseSpecificIdCalculator.java
@@ -52,7 +52,18 @@
EnterpriseSpecificIdCalculator(Context context) {
TelephonyManager telephonyService = context.getSystemService(TelephonyManager.class);
Preconditions.checkState(telephonyService != null, "Unable to access telephony service");
- mImei = telephonyService.getImei(0);
+
+ String imei;
+ try {
+ imei = telephonyService.getImei(0);
+ } catch (UnsupportedOperationException doesNotSupportGms) {
+ // Instead of catching the exception, we could check for FEATURE_TELEPHONY_GSM.
+ // However that runs the risk of changing a device's existing ESID if on these devices
+ // telephonyService.getImei() actually returns non-null even when the device does not
+ // declare FEATURE_TELEPHONY_GSM.
+ imei = null;
+ }
+ mImei = imei;
String meid;
try {
meid = telephonyService.getMeid(0);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
index 8d980b5..8bec384 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
@@ -51,6 +51,7 @@
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.Set;
final class PolicyDefinition<V> {
@@ -82,6 +83,10 @@
// them.
private static final int POLICY_FLAG_USER_RESTRICTION_POLICY = 1 << 4;
+ // Only invoke the policy enforcer callback when the policy value changes, and do not invoke the
+ // callback in other cases such as device reboots.
+ private static final int POLICY_FLAG_SKIP_ENFORCEMENT_IF_UNCHANGED = 1 << 5;
+
private static final MostRestrictive<Boolean> FALSE_MORE_RESTRICTIVE = new MostRestrictive<>(
List.of(new BooleanPolicyValue(false), new BooleanPolicyValue(true)));
@@ -231,11 +236,11 @@
// Don't need to take in a resolution mechanism since its never used, but might
// need some refactoring to not always assume a non-null mechanism.
new MostRecent<>(),
- POLICY_FLAG_LOCAL_ONLY_POLICY | POLICY_FLAG_NON_COEXISTABLE_POLICY,
- // Application restrictions are now stored and retrieved from DPMS, so no
- // enforcing is required, however DPMS calls into UM to set restrictions for
- // backwards compatibility.
- (Bundle value, Context context, Integer userId, PolicyKey policyKey) -> true,
+ // Only invoke the enforcement callback during policy change and not other state
+ POLICY_FLAG_LOCAL_ONLY_POLICY | POLICY_FLAG_INHERITABLE
+ | POLICY_FLAG_NON_COEXISTABLE_POLICY
+ | POLICY_FLAG_SKIP_ENFORCEMENT_IF_UNCHANGED,
+ PolicyEnforcerCallbacks::setApplicationRestrictions,
new BundlePolicySerializer());
/**
@@ -581,6 +586,10 @@
return (mPolicyFlags & POLICY_FLAG_USER_RESTRICTION_POLICY) != 0;
}
+ boolean shouldSkipEnforcementIfNotChanged() {
+ return (mPolicyFlags & POLICY_FLAG_SKIP_ENFORCEMENT_IF_UNCHANGED) != 0;
+ }
+
@Nullable
PolicyValue<V> resolvePolicy(LinkedHashMap<EnforcingAdmin, PolicyValue<V>> adminsPolicy) {
return mResolutionMechanism.resolve(adminsPolicy);
@@ -610,7 +619,7 @@
* {@link Object#equals} implementation.
*/
private PolicyDefinition(
- PolicyKey key,
+ @NonNull PolicyKey key,
ResolutionMechanism<V> resolutionMechanism,
QuadFunction<V, Context, Integer, PolicyKey, Boolean> policyEnforcerCallback,
PolicySerializer<V> policySerializer) {
@@ -622,11 +631,12 @@
* {@link Object#equals} and {@link Object#hashCode()} implementation.
*/
private PolicyDefinition(
- PolicyKey policyKey,
+ @NonNull PolicyKey policyKey,
ResolutionMechanism<V> resolutionMechanism,
int policyFlags,
QuadFunction<V, Context, Integer, PolicyKey, Boolean> policyEnforcerCallback,
PolicySerializer<V> policySerializer) {
+ Objects.requireNonNull(policyKey);
mPolicyKey = policyKey;
mResolutionMechanism = resolutionMechanism;
mPolicyFlags = policyFlags;
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java
index 09eef45..04d277e 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java
@@ -37,11 +37,13 @@
import android.app.usage.UsageStatsManagerInternal;
import android.content.ComponentName;
import android.content.Context;
+import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.os.Binder;
+import android.os.Bundle;
import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
@@ -172,6 +174,29 @@
return true;
}
+
+ /**
+ * Application restrictions are stored and retrieved from DPMS, so no enforcing (aka pushing
+ * it to UMS) is required. Only need to send broadcast to the target user here as we rely on
+ * the inheritable policy propagation logic in PolicyEngine to apply this policy to multiple
+ * profiles. The broadcast should only be sent when an application restriction is set, so we
+ * rely on the POLICY_FLAG_SKIP_ENFORCEMENT_IF_UNCHANGED flag so DPE only invokes this callback
+ * when the policy is set, and not during system boot or other situations.
+ */
+ static boolean setApplicationRestrictions(Bundle bundle, Context context, Integer userId,
+ PolicyKey policyKey) {
+ Binder.withCleanCallingIdentity(() -> {
+ PackagePolicyKey key = (PackagePolicyKey) policyKey;
+ String packageName = key.getPackageName();
+ Objects.requireNonNull(packageName);
+ Intent changeIntent = new Intent(Intent.ACTION_APPLICATION_RESTRICTIONS_CHANGED);
+ changeIntent.setPackage(packageName);
+ changeIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+ context.sendBroadcastAsUser(changeIntent, UserHandle.of(userId));
+ });
+ return true;
+ }
+
private static class BlockingCallback {
private final CountDownLatch mLatch = new CountDownLatch(1);
private final AtomicReference<Boolean> mValue = new AtomicReference<>();
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodInfoUtilsTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodInfoUtilsTest.java
new file mode 100644
index 0000000..50804da
--- /dev/null
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodInfoUtilsTest.java
@@ -0,0 +1,84 @@
+/*
+ * 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.server.inputmethod;
+
+import static com.android.server.inputmethod.TestUtils.TEST_IME_ID1;
+import static com.android.server.inputmethod.TestUtils.TEST_IME_ID2;
+import static com.android.server.inputmethod.TestUtils.createFakeInputMethodInfo;
+import static com.android.server.inputmethod.TestUtils.createFakeSubtypes;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.view.inputmethod.InputMethodInfo;
+
+import androidx.annotation.NonNull;
+
+import org.junit.Test;
+
+import java.util.Arrays;
+import java.util.Objects;
+
+public final class InputMethodInfoUtilsTest {
+
+ @Test
+ public void testMarshalSameObject() {
+ final var imi = createFakeInputMethodInfo(TEST_IME_ID1, createFakeSubtypes(3));
+ final byte[] buf = InputMethodInfoUtils.marshal(imi);
+
+ assertArrayEquals("The same value must be returned when called multiple times",
+ buf, InputMethodInfoUtils.marshal(imi));
+ assertArrayEquals("The same value must be returned when called multiple times",
+ buf, InputMethodInfoUtils.marshal(imi));
+ }
+
+ @Test
+ public void testMarshalDifferentObjects() {
+ final var imi1 = createFakeInputMethodInfo(TEST_IME_ID1, createFakeSubtypes(3));
+ final var imi2 = createFakeInputMethodInfo(TEST_IME_ID2, createFakeSubtypes(0));
+
+ assertFalse("Different inputs must yield different byte patterns", Arrays.equals(
+ InputMethodInfoUtils.marshal(imi1), InputMethodInfoUtils.marshal(imi2)));
+ }
+
+ @NonNull
+ private static <T> T readTypedObject(byte[] data, @NonNull Parcelable.Creator<T> creator) {
+ Parcel parcel = null;
+ try {
+ parcel = Parcel.obtain();
+ parcel.unmarshall(data, 0, data.length);
+ parcel.setDataPosition(0);
+ return Objects.requireNonNull(parcel.readTypedObject(creator));
+ } finally {
+ if (parcel != null) {
+ parcel.recycle();
+ }
+ }
+ }
+
+ @Test
+ public void testUnmarshalSameObject() {
+ final var imi = createFakeInputMethodInfo(TEST_IME_ID1, createFakeSubtypes(3));
+ final var cloned = readTypedObject(InputMethodInfoUtils.marshal(imi),
+ InputMethodInfo.CREATOR);
+ assertEquals(imi.getPackageName(), cloned.getPackageName());
+ assertEquals(imi.getSubtypeCount(), cloned.getSubtypeCount());
+ }
+}
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java
index 28a99f2f..2bbd3c0 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java
@@ -127,6 +127,7 @@
protected IInputMethodInvoker mMockInputMethodInvoker;
protected InputMethodManagerService mInputMethodManagerService;
protected ServiceThread mServiceThread;
+ protected ServiceThread mPackageMonitorThread;
protected boolean mIsLargeScreen;
private InputManagerGlobal.TestSession mInputManagerGlobalSession;
@@ -222,11 +223,16 @@
mServiceThread =
new ServiceThread(
- "TestServiceThread",
- Process.THREAD_PRIORITY_FOREGROUND, /* allowIo */
- false);
+ "immstest1",
+ Process.THREAD_PRIORITY_FOREGROUND,
+ true /* allowIo */);
+ mPackageMonitorThread =
+ new ServiceThread(
+ "immstest2",
+ Process.THREAD_PRIORITY_FOREGROUND,
+ true /* allowIo */);
mInputMethodManagerService = new InputMethodManagerService(mContext, mServiceThread,
- unusedUserId -> mMockInputMethodBindingController);
+ mPackageMonitorThread, unusedUserId -> mMockInputMethodBindingController);
spyOn(mInputMethodManagerService);
// Start a InputMethodManagerService.Lifecycle to publish and manage the lifecycle of
@@ -259,6 +265,10 @@
mInputMethodManagerService.mInputMethodDeviceConfigs.destroy();
}
+ if (mPackageMonitorThread != null) {
+ mPackageMonitorThread.quitSafely();
+ }
+
if (mServiceThread != null) {
mServiceThread.quitSafely();
}
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodMapTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodMapTest.java
new file mode 100644
index 0000000..5e3bc56
--- /dev/null
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodMapTest.java
@@ -0,0 +1,93 @@
+/*
+ * 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.server.inputmethod;
+
+import static com.android.server.inputmethod.TestUtils.TEST_IME_ID1;
+import static com.android.server.inputmethod.TestUtils.TEST_IME_ID2;
+import static com.android.server.inputmethod.TestUtils.TEST_IME_ID3;
+import static com.android.server.inputmethod.TestUtils.createFakeInputMethodInfo;
+import static com.android.server.inputmethod.TestUtils.createFakeSubtypes;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.util.ArrayMap;
+import android.view.inputmethod.InputMethodInfo;
+
+import androidx.annotation.NonNull;
+
+import org.junit.Test;
+
+public final class InputMethodMapTest {
+
+ @NonNull
+ private static InputMethodMap toMap(InputMethodInfo... list) {
+ final ArrayMap<String, InputMethodInfo> map = new ArrayMap<>();
+ for (var imi : list) {
+ map.put(imi.getId(), imi);
+ }
+ return InputMethodMap.of(map);
+ }
+
+ @Test
+ public void testEqualsSameObject() {
+ final var imi1 = createFakeInputMethodInfo(TEST_IME_ID1, createFakeSubtypes(0));
+ final var imi2 = createFakeInputMethodInfo(TEST_IME_ID2, createFakeSubtypes(3));
+ final var map = toMap(imi1, imi2);
+ assertTrue("Must return true for the same instance",
+ InputMethodMap.equals(map, map));
+ }
+
+ @Test
+ public void testEqualsEquivalentObject() {
+ final var imi1 = createFakeInputMethodInfo(TEST_IME_ID1, createFakeSubtypes(0));
+ final var imi2 = createFakeInputMethodInfo(TEST_IME_ID2, createFakeSubtypes(3));
+ assertTrue("Must return true for the equivalent instances",
+ InputMethodMap.equals(toMap(imi1, imi2), toMap(imi1, imi2)));
+
+ assertTrue("Must return true for the equivalent instances",
+ InputMethodMap.equals(toMap(imi1, imi2), toMap(imi2, imi1)));
+ }
+
+ @Test
+ public void testEqualsDifferentKeys() {
+ final var imi1 = createFakeInputMethodInfo(TEST_IME_ID1, createFakeSubtypes(0));
+ final var imi2 = createFakeInputMethodInfo(TEST_IME_ID2, createFakeSubtypes(3));
+ final var imi3 = createFakeInputMethodInfo(TEST_IME_ID3, createFakeSubtypes(3));
+ assertFalse("Must return false if keys are different",
+ InputMethodMap.equals(toMap(imi1), toMap(imi1, imi2)));
+ assertFalse("Must return false if keys are different",
+ InputMethodMap.equals(toMap(imi1, imi2), toMap(imi1)));
+ assertFalse("Must return false if keys are different",
+ InputMethodMap.equals(toMap(imi1, imi2), toMap(imi1, imi3)));
+ }
+
+ @Test
+ public void testEqualsDifferentValues() {
+ final var imi1_without_subtypes =
+ createFakeInputMethodInfo(TEST_IME_ID1, createFakeSubtypes(0));
+ final var imi1_with_subtypes =
+ createFakeInputMethodInfo(TEST_IME_ID1, createFakeSubtypes(3));
+ final var imi2 = createFakeInputMethodInfo(TEST_IME_ID2, createFakeSubtypes(3));
+ assertFalse("Must return false if values are different",
+ InputMethodMap.equals(toMap(imi1_without_subtypes), toMap(imi1_with_subtypes)));
+ assertFalse("Must return false if values are different",
+ InputMethodMap.equals(
+ toMap(imi1_without_subtypes, imi2),
+ toMap(imi1_with_subtypes, imi2)));
+ }
+}
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/TestUtils.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/TestUtils.java
new file mode 100644
index 0000000..c51ff87f
--- /dev/null
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/TestUtils.java
@@ -0,0 +1,101 @@
+/*
+ * 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.server.inputmethod;
+
+import android.content.ComponentName;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.InputMethodSubtype;
+
+import androidx.annotation.NonNull;
+
+import java.util.ArrayList;
+import java.util.Objects;
+
+public final class TestUtils {
+ /**
+ * {@link ComponentName} for fake {@link InputMethodInfo}.
+ */
+ @NonNull
+ public static final ComponentName TEST_IME_ID1 = Objects.requireNonNull(
+ ComponentName.unflattenFromString("com.android.test.testime1/.InputMethod"));
+
+ /**
+ * {@link ComponentName} for fake {@link InputMethodInfo}.
+ */
+ @NonNull
+ public static final ComponentName TEST_IME_ID2 = Objects.requireNonNull(
+ ComponentName.unflattenFromString("com.android.test.testime2/.InputMethod"));
+
+ /**
+ * {@link ComponentName} for fake {@link InputMethodInfo}.
+ */
+ @NonNull
+ public static final ComponentName TEST_IME_ID3 = Objects.requireNonNull(
+ ComponentName.unflattenFromString("com.android.test.testime3/.InputMethod"));
+
+ /**
+ * Creates a list of fake {@link InputMethodSubtype} for unit testing for the given number.
+ *
+ * @param count The number of fake {@link InputMethodSubtype} objects
+ * @return The list of fake {@link InputMethodSubtype} objects
+ */
+ @NonNull
+ public static ArrayList<InputMethodSubtype> createFakeSubtypes(int count) {
+ final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>(count);
+ for (int i = 0; i < count; ++i) {
+ subtypes.add(
+ new InputMethodSubtype.InputMethodSubtypeBuilder()
+ .setSubtypeId(i + 0x100)
+ .setLanguageTag("en-US")
+ .setSubtypeNameOverride("TestSubtype" + i)
+ .build());
+ }
+ return subtypes;
+ }
+
+ /**
+ * Creates a fake {@link InputMethodInfo} for unit testing.
+ *
+ * @param componentName {@link ComponentName} of the fake {@link InputMethodInfo}
+ * @param subtypes A list of (fake) {@link InputMethodSubtype}
+ * @return a fake {@link InputMethodInfo} object
+ */
+ @NonNull
+ public static InputMethodInfo createFakeInputMethodInfo(
+ @NonNull ComponentName componentName, @NonNull ArrayList<InputMethodSubtype> subtypes) {
+ final ApplicationInfo ai = new ApplicationInfo();
+ ai.packageName = componentName.getPackageName();
+ ai.enabled = true;
+
+ final ServiceInfo si = new ServiceInfo();
+ si.applicationInfo = ai;
+ si.enabled = true;
+ si.packageName = componentName.getPackageName();
+ si.name = componentName.getClassName();
+ si.exported = true;
+ si.nonLocalizedLabel = "Fake Label";
+
+ final ResolveInfo ri = new ResolveInfo();
+ ri.serviceInfo = si;
+
+ return new InputMethodInfo(ri, false /* isAuxIme */, null /* settingsActivity */,
+ subtypes, 0 /* isDefaultResId */, false /* forceDefault */);
+ }
+}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/color/DisplayTransformManagerTest.java b/services/tests/displayservicetests/src/com/android/server/display/color/DisplayTransformManagerTest.java
index a785300..27f87aa 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/color/DisplayTransformManagerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/color/DisplayTransformManagerTest.java
@@ -161,4 +161,20 @@
.isEqualTo(Integer.toString(testPropertyValue));
}
+ @Test
+ public void daltonizer_defaultValues() {
+ synchronized (mDtm.mDaltonizerModeLock) {
+ assertThat(mDtm.mDaltonizerMode).isEqualTo(-1);
+ assertThat(mDtm.mDaltonizerLevel).isEqualTo(-1);
+ }
+ }
+
+ @Test
+ public void setDaltonizerMode_newValues_valuesUpdated() {
+ mDtm.setDaltonizerMode(0, 0);
+ synchronized (mDtm.mDaltonizerModeLock) {
+ assertThat(mDtm.mDaltonizerMode).isEqualTo(0);
+ assertThat(mDtm.mDaltonizerLevel).isEqualTo(0);
+ }
+ }
}
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsResetTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsResetTest.java
index d29bf1a..3635e9a 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsResetTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsResetTest.java
@@ -19,6 +19,7 @@
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
import android.content.Context;
import android.os.BatteryManager;
@@ -49,9 +50,9 @@
private static final int BATTERY_CHARGE_RATE_SECONDS_PER_LEVEL = 100;
private MockClock mMockClock;
+ private BatteryStatsImpl.BatteryStatsConfig mConfig;
private MockBatteryStatsImpl mBatteryStatsImpl;
-
/**
* Battery status. Must be one of the following:
* {@link BatteryManager#BATTERY_STATUS_UNKNOWN}
@@ -91,8 +92,9 @@
@Before
public void setUp() throws IOException {
+ mConfig = mock(BatteryStatsImpl.BatteryStatsConfig.class);
mMockClock = new MockClock();
- mBatteryStatsImpl = new MockBatteryStatsImpl(mMockClock,
+ mBatteryStatsImpl = new MockBatteryStatsImpl(mConfig, mMockClock,
Files.createTempDirectory("BatteryStatsResetTest").toFile());
mBatteryStatsImpl.onSystemReady(mock(Context.class));
@@ -110,10 +112,7 @@
@Test
public void testResetOnUnplug_highBatteryLevel() {
- mBatteryStatsImpl.setBatteryStatsConfig(
- new BatteryStatsImpl.BatteryStatsConfig.Builder()
- .setResetOnUnplugHighBatteryLevel(true)
- .build());
+ when(mConfig.shouldResetOnUnplugHighBatteryLevel()).thenReturn(true);
long expectedResetTimeUs = 0;
@@ -137,10 +136,7 @@
assertThat(mBatteryStatsImpl.getStatsStartRealtime()).isEqualTo(expectedResetTimeUs);
// disable high battery level reset on unplug.
- mBatteryStatsImpl.setBatteryStatsConfig(
- new BatteryStatsImpl.BatteryStatsConfig.Builder()
- .setResetOnUnplugHighBatteryLevel(false)
- .build());
+ when(mConfig.shouldResetOnUnplugHighBatteryLevel()).thenReturn(false);
dischargeToLevel(60);
@@ -153,10 +149,7 @@
@Test
public void testResetOnUnplug_significantCharge() {
- mBatteryStatsImpl.setBatteryStatsConfig(
- new BatteryStatsImpl.BatteryStatsConfig.Builder()
- .setResetOnUnplugAfterSignificantCharge(true)
- .build());
+ when(mConfig.shouldResetOnUnplugAfterSignificantCharge()).thenReturn(true);
long expectedResetTimeUs = 0;
unplugBattery();
@@ -186,10 +179,7 @@
assertThat(mBatteryStatsImpl.getStatsStartRealtime()).isEqualTo(expectedResetTimeUs);
// disable reset on unplug after significant charge.
- mBatteryStatsImpl.setBatteryStatsConfig(
- new BatteryStatsImpl.BatteryStatsConfig.Builder()
- .setResetOnUnplugAfterSignificantCharge(false)
- .build());
+ when(mConfig.shouldResetOnUnplugAfterSignificantCharge()).thenReturn(false);
// Battery level dropped below 20%.
dischargeToLevel(15);
@@ -256,11 +246,9 @@
@Test
public void testResetWhilePluggedIn_longPlugIn() {
// disable high battery level reset on unplug.
- mBatteryStatsImpl.setBatteryStatsConfig(
- new BatteryStatsImpl.BatteryStatsConfig.Builder()
- .setResetOnUnplugHighBatteryLevel(false)
- .setResetOnUnplugAfterSignificantCharge(false)
- .build());
+ when(mConfig.shouldResetOnUnplugHighBatteryLevel()).thenReturn(false);
+ when(mConfig.shouldResetOnUnplugAfterSignificantCharge()).thenReturn(false);
+
long expectedResetTimeUs = 0;
plugBattery(BatteryManager.BATTERY_PLUGGED_USB);
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java
index 2d7cb22..6edfede 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java
@@ -98,10 +98,12 @@
mFreqsByPolicy.put(0, new int[]{300000, 1000000, 2000000});
mFreqsByPolicy.put(4, new int[]{300000, 1000000, 2500000, 3000000});
mBatteryStatsConfigBuilder = new BatteryStatsImpl.BatteryStatsConfig.Builder()
- .setPowerStatsThrottlePeriodMillis(BatteryConsumer.POWER_COMPONENT_CPU,
- 10000)
- .setPowerStatsThrottlePeriodMillis(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO,
- 10000);
+ .setPowerStatsThrottlePeriodMillis(
+ BatteryConsumer.powerComponentIdToString(
+ BatteryConsumer.POWER_COMPONENT_CPU), 10000)
+ .setPowerStatsThrottlePeriodMillis(
+ BatteryConsumer.powerComponentIdToString(
+ BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO), 10000);
}
private void initBatteryStats() {
@@ -290,7 +292,8 @@
public BatteryUsageStatsRule setPowerStatsThrottlePeriodMillis(int powerComponent,
long throttleMs) {
- mBatteryStatsConfigBuilder.setPowerStatsThrottlePeriodMillis(powerComponent, throttleMs);
+ mBatteryStatsConfigBuilder.setPowerStatsThrottlePeriodMillis(
+ BatteryConsumer.powerComponentIdToString(powerComponent), throttleMs);
return this;
}
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerStatsCollectorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerStatsCollectorTest.java
index 4e3e80f..d1105a4 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerStatsCollectorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerStatsCollectorTest.java
@@ -32,6 +32,7 @@
import android.os.Handler;
import android.os.HandlerThread;
import android.platform.test.ravenwood.RavenwoodRule;
+import android.util.IndentingPrintWriter;
import android.util.SparseArray;
import androidx.test.filters.SmallTest;
@@ -51,6 +52,7 @@
import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
+import java.io.StringWriter;
import java.util.function.IntSupplier;
@RunWith(AndroidJUnit4.class)
@@ -127,6 +129,11 @@
}
@Override
+ public long getPowerStatsCollectionThrottlePeriod(String powerComponentName) {
+ return 0;
+ }
+
+ @Override
public int getDefaultCpuPowerBrackets() {
return mDefaultCpuPowerBrackets;
}
@@ -363,6 +370,36 @@
.isEqualTo(528);
}
+ @Test
+ public void dump() {
+ mockCpuScalingPolicies(1);
+ mockPowerProfile();
+ mockEnergyConsumers();
+
+ CpuPowerStatsCollector collector = createCollector(8, 0);
+ collector.collectStats(); // Establish baseline
+
+ mockKernelCpuStats(new long[]{1111, 2222, 3333},
+ new SparseArray<>() {{
+ put(UID_1, new long[]{100, 200});
+ put(UID_2, new long[]{100, 150});
+ put(ISOLATED_UID, new long[]{200, 450});
+ }}, 0, 1234);
+
+ PowerStats powerStats = collector.collectStats();
+
+ StringWriter sw = new StringWriter();
+ IndentingPrintWriter pw = new IndentingPrintWriter(sw);
+ powerStats.dump(pw);
+ pw.flush();
+ String dump = sw.toString();
+
+ assertThat(dump).contains("duration=1234");
+ assertThat(dump).contains("steps: [1111, 2222, 3333]");
+ assertThat(dump).contains("UID 42: time: [100, 200]");
+ assertThat(dump).contains("UID 99: time: [300, 600]");
+ }
+
private void mockCpuScalingPolicies(int clusterCount) {
SparseArray<int[]> cpus = new SparseArray<>();
SparseArray<int[]> freqs = new SparseArray<>();
@@ -386,8 +423,8 @@
private CpuPowerStatsCollector createCollector(int defaultCpuPowerBrackets,
int defaultCpuPowerBracketsPerEnergyConsumer) {
CpuPowerStatsCollector collector = new CpuPowerStatsCollector(
- new TestInjector(defaultCpuPowerBrackets, defaultCpuPowerBracketsPerEnergyConsumer),
- 0);
+ new TestInjector(defaultCpuPowerBrackets, defaultCpuPowerBracketsPerEnergyConsumer)
+ );
collector.addConsumer(stats -> mCollectedStats = stats);
collector.setEnabled(true);
return collector;
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerStatsCollectorValidationTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerStatsCollectorValidationTest.java
index 70c40f5..644ae47 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerStatsCollectorValidationTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerStatsCollectorValidationTest.java
@@ -25,6 +25,7 @@
import android.content.Intent;
import android.os.Bundle;
import android.os.IBinder;
+import android.os.UserHandle;
import android.platform.test.annotations.RequiresFlagsEnabled;
import android.platform.test.flag.junit.CheckFlagsRule;
import android.platform.test.flag.junit.DeviceFlagsValueProvider;
@@ -46,7 +47,9 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.ArrayList;
import java.util.Arrays;
+import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
@@ -92,9 +95,10 @@
long duration = 0;
long[] stats = null;
- String[] cpuStatsDump = dumpCpuStats();
+ List<String> cpuStatsDump = dumpCpuStats();
Pattern durationPattern = Pattern.compile("duration=([0-9]*)");
- Pattern uidPattern = Pattern.compile("UID " + mTestPkgUid + ": \\[([0-9,\\s]*)]");
+ Pattern uidPattern = Pattern.compile(
+ "UID " + UserHandle.formatUid(mTestPkgUid) + ": time: [\\[]?([0-9,\\s]*)[]]?");
for (String line : cpuStatsDump) {
Matcher durationMatcher = durationPattern.matcher(line);
if (durationMatcher.find()) {
@@ -119,15 +123,23 @@
assertThat(total).isAtLeast((long) (WORK_DURATION_MS * 0.8));
}
- private String[] dumpCpuStats() throws Exception {
+ private List<String> dumpCpuStats() throws Exception {
+ ArrayList<String> cpuStats = new ArrayList<>();
String dump = executeCmdSilent("dumpsys batterystats --sample");
String[] lines = dump.split("\n");
+ boolean inCpuSection = false;
for (int i = 0; i < lines.length; i++) {
- if (lines[i].startsWith("CpuPowerStatsCollector")) {
- return Arrays.copyOfRange(lines, i + 1, lines.length);
+ if (!inCpuSection) {
+ if (lines[i].startsWith("CpuPowerStatsCollector")) {
+ inCpuSection = true;
+ }
+ } else if (lines[i].startsWith(" ")) {
+ cpuStats.add(lines[i]);
+ } else {
+ break;
}
}
- return new String[0];
+ return cpuStats;
}
private void doSomeWork() throws Exception {
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/MobileRadioPowerStatsCollectorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/MobileRadioPowerStatsCollectorTest.java
index f93c4da..0275319 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/MobileRadioPowerStatsCollectorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/MobileRadioPowerStatsCollectorTest.java
@@ -123,6 +123,11 @@
}
@Override
+ public long getPowerStatsCollectionThrottlePeriod(String powerComponentName) {
+ return 0;
+ }
+
+ @Override
public PackageManager getPackageManager() {
return mPackageManager;
}
@@ -337,16 +342,18 @@
pw.flush();
String dump = sw.toString();
assertThat(dump).contains("duration=100");
+ assertThat(dump).contains("sleep: 200 idle: 300 scan: 60000 call: 40000 energy: "
+ + ((64321 - 10000) * 1000 / 3500));
+ assertThat(dump).contains("(LTE) rx: 7000 tx: [8000, 9000, 1000, 2000, 3000]");
+ assertThat(dump).contains("(NR MMWAVE) rx: 6000 tx: [1000, 2000, 3000, 4000, 5000]");
assertThat(dump).contains(
- "stats=[200, 300, 60000, 40000, " + ((64321 - 10000) * 1000 / 3500) + ", 0, 0, 0]");
- assertThat(dump).contains("state LTE: [7000, 8000, 9000, 1000, 2000, 3000]");
- assertThat(dump).contains("state NR MMWAVE: [6000, 1000, 2000, 3000, 4000, 5000]");
- assertThat(dump).contains("UID 24: [6000, 3000, 60, 30, 0]");
- assertThat(dump).contains("UID 42: [1000, 2000, 100, 200, 0]");
+ "UID 24: rx-pkts: 60 rx-B: 6000 tx-pkts: 30 tx-B: 3000");
+ assertThat(dump).contains(
+ "UID 42: rx-pkts: 100 rx-B: 1000 tx-pkts: 200 tx-B: 2000");
}
private PowerStats collectPowerStats(boolean perNetworkTypeData) throws Throwable {
- MobileRadioPowerStatsCollector collector = new MobileRadioPowerStatsCollector(mInjector, 0);
+ MobileRadioPowerStatsCollector collector = new MobileRadioPowerStatsCollector(mInjector);
collector.setEnabled(true);
when(mConsumedEnergyRetriever.getEnergyConsumerIds(
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/MobileRadioPowerStatsProcessorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/MobileRadioPowerStatsProcessorTest.java
index 4ac7ad8..29ef3b6 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/MobileRadioPowerStatsProcessorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/MobileRadioPowerStatsProcessorTest.java
@@ -114,6 +114,11 @@
}
@Override
+ public long getPowerStatsCollectionThrottlePeriod(String powerComponentName) {
+ return 0;
+ }
+
+ @Override
public PackageManager getPackageManager() {
return mPackageManager;
}
@@ -186,7 +191,7 @@
aggregatedStats.setUidState(APP_UID, STATE_PROCESS_STATE, PROCESS_STATE_FOREGROUND, 0);
aggregatedStats.setUidState(APP_UID2, STATE_PROCESS_STATE, PROCESS_STATE_CACHED, 0);
- MobileRadioPowerStatsCollector collector = new MobileRadioPowerStatsCollector(mInjector, 0);
+ MobileRadioPowerStatsCollector collector = new MobileRadioPowerStatsCollector(mInjector);
collector.setEnabled(true);
// Initial empty ModemActivityInfo.
@@ -305,79 +310,9 @@
}
@Test
- public void measuredEnergyModel() {
- // PowerStats hardware is available
- when(mConsumedEnergyRetriever.getEnergyConsumerIds(EnergyConsumerType.MOBILE_RADIO))
- .thenReturn(new int[] {MOBILE_RADIO_ENERGY_CONSUMER_ID});
-
- mStatsRule.setTestPowerProfile("power_profile_test_legacy_modem")
- .initMeasuredEnergyStatsLocked();
-
- MobileRadioPowerStatsProcessor processor =
- new MobileRadioPowerStatsProcessor(mStatsRule.getPowerProfile());
-
- AggregatedPowerStatsConfig.PowerComponent config =
- new AggregatedPowerStatsConfig.PowerComponent(
- BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO)
- .trackDeviceStates(STATE_POWER, STATE_SCREEN)
- .trackUidStates(STATE_POWER, STATE_SCREEN, STATE_PROCESS_STATE)
- .setProcessor(processor);
-
+ public void energyConsumerModel() {
PowerComponentAggregatedPowerStats aggregatedStats =
- new PowerComponentAggregatedPowerStats(
- new AggregatedPowerStats(mock(AggregatedPowerStatsConfig.class)), config);
-
- aggregatedStats.setState(STATE_POWER, POWER_STATE_OTHER, 0);
- aggregatedStats.setState(STATE_SCREEN, SCREEN_STATE_ON, 0);
- aggregatedStats.setUidState(APP_UID, STATE_PROCESS_STATE, PROCESS_STATE_FOREGROUND, 0);
- aggregatedStats.setUidState(APP_UID2, STATE_PROCESS_STATE, PROCESS_STATE_CACHED, 0);
-
- MobileRadioPowerStatsCollector collector = new MobileRadioPowerStatsCollector(mInjector, 0);
- collector.setEnabled(true);
-
- // Initial empty ModemActivityInfo.
- mockModemActivityInfo(new ModemActivityInfo(0L, 0L, 0L, new int[5], 0L));
-
- when(mConsumedEnergyRetriever.getConsumedEnergyUws(
- new int[]{MOBILE_RADIO_ENERGY_CONSUMER_ID}))
- .thenReturn(new long[]{0});
-
- // Establish a baseline
- aggregatedStats.addPowerStats(collector.collectStats(), 0);
-
- // Turn the screen off after 2.5 seconds
- aggregatedStats.setState(STATE_SCREEN, SCREEN_STATE_OTHER, 2500);
- aggregatedStats.setUidState(APP_UID, STATE_PROCESS_STATE, PROCESS_STATE_BACKGROUND, 2500);
- aggregatedStats.setUidState(APP_UID, STATE_PROCESS_STATE, PROCESS_STATE_FOREGROUND_SERVICE,
- 5000);
-
- // Note application network activity
- NetworkStats networkStats = mockNetworkStats(10000, 1,
- mockNetworkStatsEntry("cellular", APP_UID, 0, 0,
- METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 10000, 1500, 20000, 300, 100),
- mockNetworkStatsEntry("cellular", APP_UID2, 0, 0,
- METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 5000, 500, 3000, 100, 111));
-
- when(mNetworkStatsSupplier.get()).thenReturn(networkStats);
-
- ModemActivityInfo mai = new ModemActivityInfo(10000, 2000, 3000,
- new int[]{100, 200, 300, 400, 500}, 600);
- mockModemActivityInfo(mai);
-
- mStatsRule.setTime(10_000, 10_000);
-
- long energyUws = 10_000_000L * VOLTAGE_MV / 1000L;
- when(mConsumedEnergyRetriever.getConsumedEnergyUws(
- new int[]{MOBILE_RADIO_ENERGY_CONSUMER_ID})).thenReturn(new long[]{energyUws});
-
- when(mCallDurationSupplier.getAsLong()).thenReturn(200L);
- when(mScanDurationSupplier.getAsLong()).thenReturn(5555L);
-
- PowerStats powerStats = collector.collectStats();
-
- aggregatedStats.addPowerStats(powerStats, 10_000);
-
- processor.finish(aggregatedStats);
+ prepareAggregatedStats_energyConsumerModel();
MobileRadioPowerStatsLayout statsLayout =
new MobileRadioPowerStatsLayout(
@@ -448,6 +383,102 @@
.isWithin(PRECISION).of(0.75);
}
+ @Test
+ public void test_toString() {
+ PowerComponentAggregatedPowerStats stats = prepareAggregatedStats_energyConsumerModel();
+ String string = stats.toString();
+ assertThat(string).contains("(pwr-other scr-on)"
+ + " sleep: 500 idle: 750 scan: 1388 call: 50 energy: 2500000 power: 0.672");
+ assertThat(string).contains("(pwr-other scr-other)"
+ + " sleep: 1500 idle: 2250 scan: 4166 call: 150 energy: 7500000 power: 2.02");
+ assertThat(string).contains("(pwr-other scr-on other)"
+ + " rx: 150 tx: [25, 50, 75, 100, 125]");
+ assertThat(string).contains("(pwr-other scr-other other)"
+ + " rx: 450 tx: [75, 150, 225, 300, 375]");
+ assertThat(string).contains("(pwr-other scr-on fg)"
+ + " rx-pkts: 375 rx-B: 2500 tx-pkts: 75 tx-B: 5000 power: 0.198");
+ assertThat(string).contains("(pwr-other scr-other bg)"
+ + " rx-pkts: 375 rx-B: 2500 tx-pkts: 75 tx-B: 5000 power: 0.198");
+ assertThat(string).contains("(pwr-other scr-other fgs)"
+ + " rx-pkts: 750 rx-B: 5000 tx-pkts: 150 tx-B: 10000 power: 0.396");
+ }
+
+ private PowerComponentAggregatedPowerStats prepareAggregatedStats_energyConsumerModel() {
+ // PowerStats hardware is available
+ when(mConsumedEnergyRetriever.getEnergyConsumerIds(EnergyConsumerType.MOBILE_RADIO))
+ .thenReturn(new int[] {MOBILE_RADIO_ENERGY_CONSUMER_ID});
+
+ mStatsRule.setTestPowerProfile("power_profile_test_legacy_modem")
+ .initMeasuredEnergyStatsLocked();
+
+ MobileRadioPowerStatsProcessor processor =
+ new MobileRadioPowerStatsProcessor(mStatsRule.getPowerProfile());
+
+ AggregatedPowerStatsConfig.PowerComponent config =
+ new AggregatedPowerStatsConfig.PowerComponent(
+ BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO)
+ .trackDeviceStates(STATE_POWER, STATE_SCREEN)
+ .trackUidStates(STATE_POWER, STATE_SCREEN, STATE_PROCESS_STATE)
+ .setProcessor(processor);
+
+ PowerComponentAggregatedPowerStats aggregatedStats =
+ new PowerComponentAggregatedPowerStats(
+ new AggregatedPowerStats(mock(AggregatedPowerStatsConfig.class)), config);
+
+ aggregatedStats.setState(STATE_POWER, POWER_STATE_OTHER, 0);
+ aggregatedStats.setState(STATE_SCREEN, SCREEN_STATE_ON, 0);
+ aggregatedStats.setUidState(APP_UID, STATE_PROCESS_STATE, PROCESS_STATE_FOREGROUND, 0);
+ aggregatedStats.setUidState(APP_UID2, STATE_PROCESS_STATE, PROCESS_STATE_CACHED, 0);
+
+ MobileRadioPowerStatsCollector collector = new MobileRadioPowerStatsCollector(mInjector);
+ collector.setEnabled(true);
+
+ // Initial empty ModemActivityInfo.
+ mockModemActivityInfo(new ModemActivityInfo(0L, 0L, 0L, new int[5], 0L));
+
+ when(mConsumedEnergyRetriever.getConsumedEnergyUws(
+ new int[]{MOBILE_RADIO_ENERGY_CONSUMER_ID}))
+ .thenReturn(new long[]{0});
+
+ // Establish a baseline
+ aggregatedStats.addPowerStats(collector.collectStats(), 0);
+
+ // Turn the screen off after 2.5 seconds
+ aggregatedStats.setState(STATE_SCREEN, SCREEN_STATE_OTHER, 2500);
+ aggregatedStats.setUidState(APP_UID, STATE_PROCESS_STATE, PROCESS_STATE_BACKGROUND, 2500);
+ aggregatedStats.setUidState(APP_UID, STATE_PROCESS_STATE, PROCESS_STATE_FOREGROUND_SERVICE,
+ 5000);
+
+ // Note application network activity
+ NetworkStats networkStats = mockNetworkStats(10000, 1,
+ mockNetworkStatsEntry("cellular", APP_UID, 0, 0,
+ METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 10000, 1500, 20000, 300, 100),
+ mockNetworkStatsEntry("cellular", APP_UID2, 0, 0,
+ METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 5000, 500, 3000, 100, 111));
+
+ when(mNetworkStatsSupplier.get()).thenReturn(networkStats);
+
+ ModemActivityInfo mai = new ModemActivityInfo(10000, 2000, 3000,
+ new int[]{100, 200, 300, 400, 500}, 600);
+ mockModemActivityInfo(mai);
+
+ mStatsRule.setTime(10_000, 10_000);
+
+ long energyUws = 10_000_000L * VOLTAGE_MV / 1000L;
+ when(mConsumedEnergyRetriever.getConsumedEnergyUws(
+ new int[]{MOBILE_RADIO_ENERGY_CONSUMER_ID})).thenReturn(new long[]{energyUws});
+
+ when(mCallDurationSupplier.getAsLong()).thenReturn(200L);
+ when(mScanDurationSupplier.getAsLong()).thenReturn(5555L);
+
+ PowerStats powerStats = collector.collectStats();
+
+ aggregatedStats.addPowerStats(powerStats, 10_000);
+
+ processor.finish(aggregatedStats);
+ return aggregatedStats;
+ }
+
private int[] states(int... states) {
return states;
}
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/MockBatteryStatsImpl.java b/services/tests/powerstatstests/src/com/android/server/power/stats/MockBatteryStatsImpl.java
index 1d48975..2c03f9d 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/MockBatteryStatsImpl.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/MockBatteryStatsImpl.java
@@ -72,6 +72,11 @@
this(DEFAULT_CONFIG, clock, historyDirectory, handler, new PowerStatsUidResolver());
}
+ MockBatteryStatsImpl(BatteryStatsConfig config, Clock clock, File historyDirectory) {
+ this(config, clock, historyDirectory, new Handler(Looper.getMainLooper()),
+ new PowerStatsUidResolver());
+ }
+
MockBatteryStatsImpl(BatteryStatsConfig config, Clock clock, File historyDirectory,
Handler handler, PowerStatsUidResolver powerStatsUidResolver) {
super(config, clock, new MonotonicClock(0, clock), historyDirectory, handler,
@@ -137,13 +142,6 @@
return MOBILE_RADIO_POWER_STATE_UPDATE_FREQ_MS;
}
- public MockBatteryStatsImpl setBatteryStatsConfig(BatteryStatsConfig config) {
- synchronized (this) {
- mBatteryStatsConfig = config;
- }
- return this;
- }
-
public MockBatteryStatsImpl setNetworkStats(NetworkStats networkStats) {
mNetworkStats = networkStats;
return this;
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/MultiStateStatsTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/MultiStateStatsTest.java
index 1b045c5..ae258cd3 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/MultiStateStatsTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/MultiStateStatsTest.java
@@ -29,8 +29,6 @@
import org.junit.Test;
import org.junit.runner.RunWith;
-import java.io.PrintWriter;
-import java.io.StringWriter;
import java.util.Arrays;
@RunWith(AndroidJUnit4.class)
@@ -165,7 +163,7 @@
}
@Test
- public void dump() {
+ public void test_toString() {
MultiStateStats.Factory factory = makeFactory(true, true, false);
MultiStateStats multiStateStats = factory.create();
multiStateStats.setState(0 /* batteryState */, 0 /* off */, 1000);
@@ -175,13 +173,10 @@
multiStateStats.setState(1 /* procState */, BatteryConsumer.PROCESS_STATE_BACKGROUND, 3000);
multiStateStats.increment(new long[]{100, 200}, 5000);
- StringWriter sw = new StringWriter();
- PrintWriter pw = new PrintWriter(sw, true);
- multiStateStats.dump(pw, Arrays::toString);
- assertThat(sw.toString()).isEqualTo(
- "plugged-in fg [25, 50]\n"
- + "on-battery fg [25, 50]\n"
- + "on-battery bg [50, 100]\n"
+ assertThat(multiStateStats.toString()).isEqualTo(
+ "(plugged-in fg) [25, 50]\n"
+ + "(on-battery fg) [25, 50]\n"
+ + "(on-battery bg) [50, 100]"
);
}
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/PhoneCallPowerStatsProcessorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/PhoneCallPowerStatsProcessorTest.java
index dadcf3f..69d655b 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/PhoneCallPowerStatsProcessorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/PhoneCallPowerStatsProcessorTest.java
@@ -98,6 +98,11 @@
}
@Override
+ public long getPowerStatsCollectionThrottlePeriod(String powerComponentName) {
+ return 0;
+ }
+
+ @Override
public PackageManager getPackageManager() {
return mPackageManager;
}
@@ -175,7 +180,7 @@
aggregatedPowerStats.setDeviceState(STATE_POWER, POWER_STATE_OTHER, 0);
aggregatedPowerStats.setDeviceState(STATE_SCREEN, SCREEN_STATE_ON, 0);
- MobileRadioPowerStatsCollector collector = new MobileRadioPowerStatsCollector(mInjector, 0);
+ MobileRadioPowerStatsCollector collector = new MobileRadioPowerStatsCollector(mInjector);
collector.setEnabled(true);
// Initial empty ModemActivityInfo.
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/WifiPowerStatsCollectorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/WifiPowerStatsCollectorTest.java
index 8b1d423..a280cfe 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/WifiPowerStatsCollectorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/WifiPowerStatsCollectorTest.java
@@ -138,6 +138,11 @@
}
@Override
+ public long getPowerStatsCollectionThrottlePeriod(String powerComponentName) {
+ return 0;
+ }
+
+ @Override
public PackageManager getPackageManager() {
return mPackageManager;
}
@@ -304,16 +309,20 @@
String dump = sw.toString();
assertThat(dump).contains("duration=7500");
assertThat(dump).contains(
- "stats=[6000, 1000, 300, 200, 634, 945, " + ((64321 - 10000) * 1000 / 3500)
- + ", 0, 0]");
- assertThat(dump).contains("UID 24: [6000, 3000, 60, 30, 400, 600, 0]");
- assertThat(dump).contains("UID 42: [1000, 2000, 100, 200, 234, 345, 0]");
+ "rx: 6000 tx: 1000 idle: 300 scan: 200 basic-scan: 634 batched-scan: 945"
+ + " energy: " + ((64321 - 10000) * 1000 / 3500));
+ assertThat(dump).contains(
+ "UID 24: rx-pkts: 60 rx-B: 6000 tx-pkts: 30 tx-B: 3000"
+ + " scan: 400 batched-scan: 600");
+ assertThat(dump).contains(
+ "UID 42: rx-pkts: 100 rx-B: 1000 tx-pkts: 200 tx-B: 2000"
+ + " scan: 234 batched-scan: 345");
}
private PowerStats collectPowerStats(boolean hasPowerReporting) {
when(mWifiManager.isEnhancedPowerReportingSupported()).thenReturn(hasPowerReporting);
- WifiPowerStatsCollector collector = new WifiPowerStatsCollector(mInjector, 0);
+ WifiPowerStatsCollector collector = new WifiPowerStatsCollector(mInjector);
collector.setEnabled(true);
when(mConsumedEnergyRetriever.getEnergyConsumerIds(EnergyConsumerType.WIFI))
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/WifiPowerStatsProcessorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/WifiPowerStatsProcessorTest.java
index 257a1a6..3ceaf35 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/WifiPowerStatsProcessorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/WifiPowerStatsProcessorTest.java
@@ -142,6 +142,11 @@
}
@Override
+ public long getPowerStatsCollectionThrottlePeriod(String powerComponentName) {
+ return 0;
+ }
+
+ @Override
public PackageManager getPackageManager() {
return mPackageManager;
}
@@ -195,7 +200,7 @@
PowerComponentAggregatedPowerStats aggregatedStats = createAggregatedPowerStats(processor);
- WifiPowerStatsCollector collector = new WifiPowerStatsCollector(mInjector, 0);
+ WifiPowerStatsCollector collector = new WifiPowerStatsCollector(mInjector);
collector.setEnabled(true);
// Initial empty WifiActivityEnergyInfo.
@@ -307,7 +312,7 @@
PowerComponentAggregatedPowerStats aggregatedStats = createAggregatedPowerStats(processor);
- WifiPowerStatsCollector collector = new WifiPowerStatsCollector(mInjector, 0);
+ WifiPowerStatsCollector collector = new WifiPowerStatsCollector(mInjector);
collector.setEnabled(true);
// Initial empty WifiActivityEnergyInfo.
@@ -420,7 +425,7 @@
PowerComponentAggregatedPowerStats aggregatedStats = createAggregatedPowerStats(processor);
- WifiPowerStatsCollector collector = new WifiPowerStatsCollector(mInjector, 0);
+ WifiPowerStatsCollector collector = new WifiPowerStatsCollector(mInjector);
collector.setEnabled(true);
// Establish a baseline
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
index b5f0a52..b50684b 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
@@ -26,6 +26,8 @@
import static com.android.server.hdmi.HdmiCecLocalDevice.ActiveSource;
import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC;
import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_WAKE_UP_MESSAGE;
+import static com.android.server.hdmi.HdmiControlService.STANDBY_SCREEN_OFF;
+import static com.android.server.hdmi.HdmiControlService.WAKE_UP_SCREEN_ON;
import static com.google.common.truth.Truth.assertThat;
@@ -1780,9 +1782,17 @@
HdmiCecMessage activeSourceFromTv =
HdmiCecMessageBuilder.buildActiveSource(ADDR_TV, 0x0000);
- mHdmiControlService.getHdmiCecNetwork().clearLocalDevices();
+ // Go to standby to invalidate the active source on the local device s.t. the
+ // RequestActiveSourceAction will start.
+ mHdmiControlService.onStandby(STANDBY_SCREEN_OFF);
+ mTestLooper.dispatchAll();
+
mNativeWrapper.setPollAddressResponse(ADDR_PLAYBACK_1, SendMessageResult.SUCCESS);
- mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
+ mHdmiControlService.onWakeUp(WAKE_UP_SCREEN_ON);
+ mTestLooper.dispatchAll();
+
+ // Skip the LauncherX API timeout.
+ mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS * 2);
mTestLooper.dispatchAll();
assertThat(mNativeWrapper.getResultMessages()).contains(requestActiveSource);
@@ -1791,6 +1801,10 @@
mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS);
mTestLooper.dispatchAll();
+ // Assume there was a retry and the action did not finish earlier.
+ mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS);
+ mTestLooper.dispatchAll();
+
assertThat(mNativeWrapper.getResultMessages()).doesNotContain(activeSourceFromTv);
}
@@ -1800,9 +1814,18 @@
HdmiCecMessageBuilder.buildRequestActiveSource(ADDR_TV);
HdmiCecMessage activeSourceFromTv =
HdmiCecMessageBuilder.buildActiveSource(ADDR_TV, 0x0000);
- mHdmiControlService.getHdmiCecNetwork().clearLocalDevices();
+
+ // Go to standby to invalidate the active source on the local device s.t. the
+ // RequestActiveSourceAction will start.
+ mHdmiControlService.onStandby(STANDBY_SCREEN_OFF);
+ mTestLooper.dispatchAll();
+
mNativeWrapper.setPollAddressResponse(ADDR_PLAYBACK_1, SendMessageResult.SUCCESS);
- mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
+ mHdmiControlService.onWakeUp(WAKE_UP_SCREEN_ON);
+ mTestLooper.dispatchAll();
+
+ // Skip the LauncherX API timeout.
+ mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS * 2);
mTestLooper.dispatchAll();
assertThat(mNativeWrapper.getResultMessages()).contains(requestActiveSource);
@@ -1827,8 +1850,18 @@
HdmiCecMessageBuilder.buildRequestActiveSource(ADDR_TV);
HdmiCecMessage activeSourceFromTv =
HdmiCecMessageBuilder.buildActiveSource(ADDR_TV, 0x0000);
- mHdmiControlService.getHdmiCecNetwork().clearLocalDevices();
- mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
+
+ // Go to standby to invalidate the active source on the local device s.t. the
+ // RequestActiveSourceAction will start.
+ mHdmiControlService.onStandby(STANDBY_SCREEN_OFF);
+ mTestLooper.dispatchAll();
+
+ mNativeWrapper.setPollAddressResponse(ADDR_PLAYBACK_1, SendMessageResult.SUCCESS);
+ mHdmiControlService.onWakeUp(WAKE_UP_SCREEN_ON);
+ mTestLooper.dispatchAll();
+
+ // Skip the LauncherX API timeout.
+ mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS * 2);
mTestLooper.dispatchAll();
assertThat(mNativeWrapper.getResultMessages()).contains(requestActiveSource);
@@ -1847,8 +1880,16 @@
HdmiCecMessageBuilder.buildRequestActiveSource(ADDR_TV);
HdmiCecMessage activeSourceFromTv =
HdmiCecMessageBuilder.buildActiveSource(ADDR_TV, 0x0000);
- mHdmiControlService.getHdmiCecNetwork().clearLocalDevices();
- mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
+
+ // Go to standby to invalidate the active source on the local device s.t. the
+ // RequestActiveSourceAction will start.
+ mHdmiControlService.onStandby(STANDBY_SCREEN_OFF);
+ mTestLooper.dispatchAll();
+
+ mNativeWrapper.setPollAddressResponse(ADDR_PLAYBACK_1, SendMessageResult.SUCCESS);
+ mHdmiControlService.onWakeUp(WAKE_UP_SCREEN_ON);
+ mTestLooper.dispatchAll();
+
HdmiDeviceInfo playbackDevice = HdmiDeviceInfo.cecDeviceBuilder()
.setLogicalAddress(ADDR_PLAYBACK_1)
.setPhysicalAddress(0x1000)
@@ -1862,6 +1903,10 @@
mHdmiControlService.getHdmiCecNetwork().addCecDevice(playbackDevice);
mTestLooper.dispatchAll();
+ // Skip the LauncherX API timeout.
+ mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS * 2);
+ mTestLooper.dispatchAll();
+
assertThat(mNativeWrapper.getResultMessages()).contains(requestActiveSource);
mNativeWrapper.clearResultMessages();
mHdmiCecLocalDeviceTv.deviceSelect(playbackDevice.getId(), null);
@@ -1874,6 +1919,41 @@
assertThat(mNativeWrapper.getResultMessages()).doesNotContain(activeSourceFromTv);
}
+ @Test
+ public void onAddressAllocated_sendSourceChangingMessage_noRequestActiveSourceMessage() {
+ HdmiCecMessage requestActiveSource =
+ HdmiCecMessageBuilder.buildRequestActiveSource(ADDR_TV);
+ HdmiCecMessage activeSourceFromTv =
+ HdmiCecMessageBuilder.buildActiveSource(ADDR_TV, 0x0000);
+ HdmiCecMessage setStreamPathFromTv =
+ HdmiCecMessageBuilder.buildSetStreamPath(ADDR_TV, 0x2000);
+
+ // Go to standby to invalidate the active source on the local device s.t. the
+ // RequestActiveSourceAction will start.
+ mHdmiControlService.onStandby(STANDBY_SCREEN_OFF);
+ mTestLooper.dispatchAll();
+
+ mHdmiControlService.onWakeUp(WAKE_UP_SCREEN_ON);
+ mTestLooper.dispatchAll();
+
+ // Even if the device at the end of this path doesn't answer to this message, TV should not
+ // continue the RequestActiveSourceAction.
+ mHdmiControlService.sendCecCommand(setStreamPathFromTv);
+
+ // Skip the LauncherX API timeout.
+ mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS * 2);
+ mTestLooper.dispatchAll();
+
+ assertThat(mNativeWrapper.getResultMessages()).doesNotContain(requestActiveSource);
+ mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS);
+ mTestLooper.dispatchAll();
+
+ // Assume there was a retry and the action did not finish earlier.
+ mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS);
+ mTestLooper.dispatchAll();
+
+ assertThat(mNativeWrapper.getResultMessages()).doesNotContain(activeSourceFromTv);
+ }
@Test
public void newDeviceConnectedIfOnlyOneGiveOsdNameSent() {
@@ -1900,7 +1980,12 @@
HdmiCecMessage activeSourceFromTv =
HdmiCecMessageBuilder.buildActiveSource(ADDR_TV, 0x0000);
- mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_WAKE_UP_MESSAGE);
+ // Go to standby to invalidate the active source on the local device s.t. the
+ // TV will send <Active Source> when it selects its internal source.
+ mHdmiControlService.onStandby(STANDBY_SCREEN_OFF);
+ mTestLooper.dispatchAll();
+
+ mHdmiControlService.onWakeUp(WAKE_UP_SCREEN_ON);
mTestLooper.dispatchAll();
mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS);
@@ -1930,6 +2015,8 @@
public void handleStandby_fromActiveSource_standby() {
mPowerManager.setInteractive(true);
mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
+ mTestLooper.dispatchAll();
+
mHdmiControlService.setActiveSource(ADDR_PLAYBACK_1, 0x1000,
"HdmiCecLocalDeviceTvTest");
mTestLooper.dispatchAll();
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java
index a3d57c3..70a0038 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java
@@ -24,6 +24,8 @@
import static android.app.NotificationManager.IMPORTANCE_LOW;
import static android.app.NotificationManager.IMPORTANCE_MIN;
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_LIGHTS;
+import static android.content.pm.PackageManager.PERMISSION_DENIED;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.media.AudioAttributes.USAGE_NOTIFICATION;
import static android.media.AudioAttributes.USAGE_NOTIFICATION_RINGTONE;
@@ -52,6 +54,7 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.Manifest.permission;
import android.annotation.SuppressLint;
import android.app.ActivityManager;
import android.app.KeyguardManager;
@@ -190,6 +193,8 @@
getContext().addMockSystemService(Vibrator.class, mVibrator);
getContext().addMockSystemService(PackageManager.class, mPackageManager);
when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)).thenReturn(false);
+ when(mPackageManager.checkPermission(eq(permission.RECEIVE_EMERGENCY_BROADCAST),
+ anyString())).thenReturn(PERMISSION_DENIED);
when(mAudioManager.isAudioFocusExclusive()).thenReturn(false);
when(mAudioManager.getRingtonePlayer()).thenReturn(mRingtonePlayer);
@@ -2363,6 +2368,72 @@
}
@Test
+ public void testBeepVolume_politeNotif_Avalanche_exemptEmergency() throws Exception {
+ mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS);
+ mSetFlagsRule.enableFlags(Flags.FLAG_CROSS_APP_POLITE_NOTIFICATIONS);
+ mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS_ATTN_UPDATE);
+ TestableFlagResolver flagResolver = new TestableFlagResolver();
+ flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME1, 50);
+ flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME2, 0);
+ initAttentionHelper(flagResolver);
+
+ // Trigger avalanche trigger intent
+ final Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+ intent.putExtra("state", false);
+ mAvalancheBroadcastReceiver.onReceive(getContext(), intent);
+
+ NotificationRecord r = getBeepyNotification();
+
+ // Grant RECEIVE_EMERGENCY_BROADCAST to notification's package
+ when(mPackageManager.checkPermission(eq(permission.RECEIVE_EMERGENCY_BROADCAST),
+ eq(r.getSbn().getPackageName()))).thenReturn(PERMISSION_GRANTED);
+
+ // Should beep at 100% volume
+ mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS);
+ verifyBeepVolume(1.0f);
+ assertNotEquals(-1, r.getLastAudiblyAlertedMs());
+ verify(mAccessibilityService, times(1)).sendAccessibilityEvent(any(), anyInt());
+ }
+
+ @Test
+ public void testBeepVolume_politeNotif_exemptEmergency() throws Exception {
+ mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS);
+ mSetFlagsRule.disableFlags(Flags.FLAG_CROSS_APP_POLITE_NOTIFICATIONS);
+ mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS_ATTN_UPDATE);
+ TestableFlagResolver flagResolver = new TestableFlagResolver();
+ flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME1, 50);
+ flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME2, 0);
+ // NOTIFICATION_COOLDOWN_ALL setting is enabled
+ Settings.System.putInt(getContext().getContentResolver(),
+ Settings.System.NOTIFICATION_COOLDOWN_ALL, 1);
+ initAttentionHelper(flagResolver);
+
+ NotificationRecord r = getBeepyNotification();
+
+ // Grant RECEIVE_EMERGENCY_BROADCAST to notification's package
+ when(mPackageManager.checkPermission(eq(permission.RECEIVE_EMERGENCY_BROADCAST),
+ eq(r.getSbn().getPackageName()))).thenReturn(PERMISSION_GRANTED);
+
+ // set up internal state
+ mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS);
+ Mockito.reset(mRingtonePlayer);
+
+ // update should beep at 100% volume
+ NotificationRecord r2 = getBeepyNotification();
+ mAttentionHelper.buzzBeepBlinkLocked(r2, DEFAULT_SIGNALS);
+ assertNotEquals(-1, r2.getLastAudiblyAlertedMs());
+ verifyBeepVolume(1.0f);
+
+ // 2nd update should beep at 100% volume
+ Mockito.reset(mRingtonePlayer);
+ mAttentionHelper.buzzBeepBlinkLocked(r2, DEFAULT_SIGNALS);
+ assertNotEquals(-1, r2.getLastAudiblyAlertedMs());
+ verifyBeepVolume(1.0f);
+
+ verify(mAccessibilityService, times(3)).sendAccessibilityEvent(any(), anyInt());
+ }
+
+ @Test
public void testBeepVolume_politeNotif_applyPerApp() throws Exception {
mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS);
mSetFlagsRule.disableFlags(Flags.FLAG_CROSS_APP_POLITE_NOTIFICATIONS);
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 926a994..e564ba6 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -99,6 +99,7 @@
import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_ALERTING;
import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_CONVERSATIONS;
import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_ONGOING;
+import static android.service.notification.NotificationListenerService.HINT_HOST_DISABLE_EFFECTS;
import static android.service.notification.NotificationListenerService.REASON_CANCEL;
import static android.service.notification.NotificationListenerService.REASON_LOCKDOWN;
import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEGATIVE;
@@ -15673,4 +15674,47 @@
// yay
}
}
+
+ @Test
+ public void testGetEffectsSuppressor_noSuppressor() throws Exception {
+ when(mUmInternal.getProfileIds(anyInt(), anyBoolean())).thenReturn(new int[]{mUserId});
+ when(mListeners.checkServiceTokenLocked(any())).thenReturn(mListener);
+ when(mPackageManagerInternal.isSameApp(anyString(), anyInt(), anyInt())).thenReturn(true);
+ assertThat(mBinderService.getEffectsSuppressor()).isNull();
+ }
+
+ @Test
+ public void testGetEffectsSuppressor_suppressorSameApp() throws Exception {
+ when(mUmInternal.getProfileIds(anyInt(), anyBoolean())).thenReturn(new int[]{mUserId});
+ when(mListeners.checkServiceTokenLocked(any())).thenReturn(mListener);
+ mService.isSystemUid = false;
+ mService.isSystemAppId = false;
+ mBinderService.requestHintsFromListener(mock(INotificationListener.class),
+ HINT_HOST_DISABLE_EFFECTS);
+ when(mPackageManagerInternal.isSameApp(anyString(), anyInt(), anyInt())).thenReturn(true);
+ assertThat(mBinderService.getEffectsSuppressor()).isEqualTo(mListener.component);
+ }
+
+ @Test
+ public void testGetEffectsSuppressor_suppressorDiffApp() throws Exception {
+ when(mUmInternal.getProfileIds(anyInt(), anyBoolean())).thenReturn(new int[]{mUserId});
+ when(mListeners.checkServiceTokenLocked(any())).thenReturn(mListener);
+ mService.isSystemUid = false;
+ mService.isSystemAppId = false;
+ mBinderService.requestHintsFromListener(mock(INotificationListener.class),
+ HINT_HOST_DISABLE_EFFECTS);
+ when(mPackageManagerInternal.isSameApp(anyString(), anyInt(), anyInt())).thenReturn(false);
+ assertThat(mBinderService.getEffectsSuppressor()).isEqualTo(null);
+ }
+
+ @Test
+ public void testGetEffectsSuppressor_suppressorDiffAppSystemCaller() throws Exception {
+ when(mUmInternal.getProfileIds(anyInt(), anyBoolean())).thenReturn(new int[]{mUserId});
+ when(mListeners.checkServiceTokenLocked(any())).thenReturn(mListener);
+ mService.isSystemUid = true;
+ mBinderService.requestHintsFromListener(mock(INotificationListener.class),
+ HINT_HOST_DISABLE_EFFECTS);
+ when(mPackageManagerInternal.isSameApp(anyString(), anyInt(), anyInt())).thenReturn(false);
+ assertThat(mBinderService.getEffectsSuppressor()).isEqualTo(mListener.component);
+ }
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityOptionsTest.java b/services/tests/wmtests/src/com/android/server/wm/ActivityOptionsTest.java
index 37e0818..5787780 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityOptionsTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityOptionsTest.java
@@ -24,6 +24,8 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
@@ -250,6 +252,7 @@
case ActivityOptions.KEY_LAUNCH_ROOT_TASK_TOKEN:
case ActivityOptions.KEY_LAUNCH_TASK_FRAGMENT_TOKEN:
case ActivityOptions.KEY_TRANSIENT_LAUNCH:
+ case ActivityOptions.KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED:
case "android:activity.animationFinishedListener":
// KEY_ANIMATION_FINISHED_LISTENER
case "android:activity.animSpecs": // KEY_ANIM_SPECS
@@ -319,7 +322,7 @@
Log.e("ActivityOptionsTests", "Unknown key " + key + " is found. "
+ "Please review if the given bundle should be protected with permissions.");
}
- assertTrue(unknownKeys.isEmpty());
+ assertThat(unknownKeys).isEmpty();
}
public static class TrampolineActivity extends Activity {
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRefresherTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRefresherTests.java
new file mode 100644
index 0000000..12ab3e1
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRefresherTests.java
@@ -0,0 +1,219 @@
+/*
+ * 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.server.wm;
+
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.app.servertransaction.ActivityLifecycleItem.ON_PAUSE;
+import static android.app.servertransaction.ActivityLifecycleItem.ON_STOP;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.servertransaction.RefreshCallbackItem;
+import android.app.servertransaction.ResumeActivityItem;
+import android.content.ComponentName;
+import android.content.res.Configuration;
+import android.os.Handler;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests for {@link ActivityRefresher}.
+ *
+ * <p>Build/Install/Run:
+ * atest WmTests:ActivityRefresherTests
+ */
+@SmallTest
+@Presubmit
+@RunWith(WindowTestRunner.class)
+public class ActivityRefresherTests extends WindowTestsBase {
+ private Handler mMockHandler;
+ private LetterboxConfiguration mLetterboxConfiguration;
+
+ private ActivityRecord mActivity;
+ private ActivityRefresher mActivityRefresher;
+
+ private ActivityRefresher.Evaluator mEvaluatorFalse =
+ (activity, newConfig, lastReportedConfig) -> false;
+
+ private ActivityRefresher.Evaluator mEvaluatorTrue =
+ (activity, newConfig, lastReportedConfig) -> true;
+
+ private final Configuration mNewConfig = new Configuration();
+ private final Configuration mOldConfig = new Configuration();
+
+ @Before
+ public void setUp() throws Exception {
+ mLetterboxConfiguration = mDisplayContent.mWmService.mLetterboxConfiguration;
+ spyOn(mLetterboxConfiguration);
+ when(mLetterboxConfiguration.isCameraCompatTreatmentEnabled())
+ .thenReturn(true);
+ when(mLetterboxConfiguration.isCameraCompatRefreshEnabled())
+ .thenReturn(true);
+ when(mLetterboxConfiguration.isCameraCompatRefreshCycleThroughStopEnabled())
+ .thenReturn(true);
+
+ mMockHandler = mock(Handler.class);
+ when(mMockHandler.postDelayed(any(Runnable.class), anyLong())).thenAnswer(
+ invocation -> {
+ ((Runnable) invocation.getArgument(0)).run();
+ return null;
+ });
+
+ mActivityRefresher = new ActivityRefresher(mDisplayContent.mWmService, mMockHandler);
+ }
+
+ @Test
+ public void testShouldRefreshActivity_refreshDisabled() throws Exception {
+ when(mLetterboxConfiguration.isCameraCompatRefreshEnabled())
+ .thenReturn(false);
+ configureActivityAndDisplay();
+ mActivityRefresher.addEvaluator(mEvaluatorTrue);
+
+ mActivityRefresher.onActivityConfigurationChanging(mActivity, mNewConfig, mOldConfig);
+
+ assertActivityRefreshRequested(/* refreshRequested= */ false);
+ }
+
+ @Test
+ public void testShouldRefreshActivity_refreshDisabledForActivity() throws Exception {
+ configureActivityAndDisplay();
+ when(mActivity.mLetterboxUiController.shouldRefreshActivityForCameraCompat())
+ .thenReturn(false);
+ mActivityRefresher.addEvaluator(mEvaluatorTrue);
+
+ mActivityRefresher.onActivityConfigurationChanging(mActivity, mNewConfig, mOldConfig);
+
+ assertActivityRefreshRequested(/* refreshRequested= */ false);
+ }
+
+ @Test
+ public void testShouldRefreshActivity_noRefreshTriggerers() throws Exception {
+ configureActivityAndDisplay();
+
+ mActivityRefresher.onActivityConfigurationChanging(mActivity, mNewConfig, mOldConfig);
+
+ assertActivityRefreshRequested(/* refreshRequested= */ false);
+ }
+
+ @Test
+ public void testShouldRefreshActivity_refreshTriggerersReturnFalse() throws Exception {
+ configureActivityAndDisplay();
+ mActivityRefresher.addEvaluator(mEvaluatorFalse);
+
+ mActivityRefresher.onActivityConfigurationChanging(mActivity, mNewConfig, mOldConfig);
+
+ assertActivityRefreshRequested(/* refreshRequested= */ false);
+ }
+
+ @Test
+ public void testShouldRefreshActivity_anyRefreshTriggerersReturnTrue() throws Exception {
+ configureActivityAndDisplay();
+ mActivityRefresher.addEvaluator(mEvaluatorFalse);
+ mActivityRefresher.addEvaluator(mEvaluatorTrue);
+
+ mActivityRefresher.onActivityConfigurationChanging(mActivity, mNewConfig, mOldConfig);
+
+ assertActivityRefreshRequested(/* refreshRequested= */ true);
+ }
+
+ @Test
+ public void testOnActivityConfigurationChanging_cycleThroughStopDisabled()
+ throws Exception {
+ mActivityRefresher.addEvaluator(mEvaluatorTrue);
+ when(mLetterboxConfiguration.isCameraCompatRefreshCycleThroughStopEnabled())
+ .thenReturn(false);
+ configureActivityAndDisplay();
+
+ mActivityRefresher.onActivityConfigurationChanging(mActivity, mNewConfig, mOldConfig);
+
+ assertActivityRefreshRequested(/* refreshRequested */ true, /* cycleThroughStop */ false);
+ }
+
+ @Test
+ public void testOnActivityConfigurationChanging_cycleThroughPauseEnabledForApp()
+ throws Exception {
+ configureActivityAndDisplay();
+ mActivityRefresher.addEvaluator(mEvaluatorTrue);
+ doReturn(true).when(mActivity.mLetterboxUiController)
+ .shouldRefreshActivityViaPauseForCameraCompat();
+
+ mActivityRefresher.onActivityConfigurationChanging(mActivity, mNewConfig, mOldConfig);
+
+ assertActivityRefreshRequested(/* refreshRequested */ true, /* cycleThroughStop */ false);
+ }
+
+ @Test
+ public void testOnActivityRefreshed_setIsRefreshRequestedToFalse() throws Exception {
+ configureActivityAndDisplay();
+ mActivityRefresher.addEvaluator(mEvaluatorTrue);
+ doReturn(true).when(mActivity.mLetterboxUiController)
+ .shouldRefreshActivityViaPauseForCameraCompat();
+
+ mActivityRefresher.onActivityRefreshed(mActivity);
+
+ assertActivityRefreshRequested(false);
+ }
+
+ private void assertActivityRefreshRequested(boolean refreshRequested) throws Exception {
+ assertActivityRefreshRequested(refreshRequested, /* cycleThroughStop*/ true);
+ }
+
+ private void assertActivityRefreshRequested(boolean refreshRequested,
+ boolean cycleThroughStop) throws Exception {
+ verify(mActivity.mLetterboxUiController, times(refreshRequested ? 1 : 0))
+ .setIsRefreshRequested(true);
+
+ final RefreshCallbackItem refreshCallbackItem = RefreshCallbackItem.obtain(mActivity.token,
+ cycleThroughStop ? ON_STOP : ON_PAUSE);
+ final ResumeActivityItem resumeActivityItem = ResumeActivityItem.obtain(mActivity.token,
+ /* isForward */ false, /* shouldSendCompatFakeFocus */ false);
+
+ verify(mActivity.mAtmService.getLifecycleManager(), times(refreshRequested ? 1 : 0))
+ .scheduleTransactionAndLifecycleItems(mActivity.app.getThread(),
+ refreshCallbackItem, resumeActivityItem);
+ }
+
+ private void configureActivityAndDisplay() {
+ mActivity = new TaskBuilder(mSupervisor)
+ .setCreateActivity(true)
+ .setDisplay(mDisplayContent)
+ // Set the component to be that of the test class in order to enable compat changes
+ .setComponent(ComponentName.createRelative(mContext,
+ ActivityRefresherTests.class.getName()))
+ .setWindowingMode(WINDOWING_MODE_FREEFORM)
+ .build()
+ .getTopMostActivity();
+
+ spyOn(mActivity.mLetterboxUiController);
+ doReturn(true).when(
+ mActivity.mLetterboxUiController).shouldRefreshActivityForCameraCompat();
+
+ doReturn(true).when(mActivity).inFreeformWindowingMode();
+ }
+}
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 262ba8b..c76acd7 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java
@@ -91,6 +91,7 @@
private CameraManager mMockCameraManager;
private Handler mMockHandler;
private LetterboxConfiguration mLetterboxConfiguration;
+ private ActivityRefresher mActivityRefresher;
private DisplayRotationCompatPolicy mDisplayRotationCompatPolicy;
private CameraManager.AvailabilityCallback mCameraAvailabilityCallback;
@@ -132,8 +133,9 @@
});
CameraStateMonitor cameraStateMonitor =
new CameraStateMonitor(mDisplayContent, mMockHandler);
- mDisplayRotationCompatPolicy =
- new DisplayRotationCompatPolicy(mDisplayContent, mMockHandler, cameraStateMonitor);
+ mActivityRefresher = new ActivityRefresher(mDisplayContent.mWmService, mMockHandler);
+ mDisplayRotationCompatPolicy = new DisplayRotationCompatPolicy(mDisplayContent,
+ cameraStateMonitor, mActivityRefresher);
// Do not show the real toast.
spyOn(mDisplayRotationCompatPolicy);
@@ -606,7 +608,7 @@
private void assertActivityRefreshRequested(boolean refreshRequested,
boolean cycleThroughStop) throws Exception {
verify(mActivity.mLetterboxUiController, times(refreshRequested ? 1 : 0))
- .setIsRefreshAfterRotationRequested(true);
+ .setIsRefreshRequested(true);
final RefreshCallbackItem refreshCallbackItem = RefreshCallbackItem.obtain(mActivity.token,
cycleThroughStop ? ON_STOP : ON_PAUSE);
@@ -628,7 +630,7 @@
private void callOnActivityConfigurationChanging(
ActivityRecord activity, boolean isDisplayRotationChanging) {
- mDisplayRotationCompatPolicy.onActivityConfigurationChanging(activity,
+ mActivityRefresher.onActivityConfigurationChanging(activity,
/* newConfig */ createConfigurationWithDisplayRotation(ROTATION_0),
/* newConfig */ createConfigurationWithDisplayRotation(
isDisplayRotationChanging ? ROTATION_90 : ROTATION_0));
diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
index 0e1a1af..c69faed 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
@@ -353,6 +353,17 @@
}
@Test
+ public void testControlTargetChangedWhileProviderHasNoWindow() {
+ final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
+ final InsetsSourceProvider provider = getController().getOrCreateSourceProvider(
+ ID_STATUS_BAR, statusBars());
+ getController().onBarControlTargetChanged(app, null, null, null);
+ assertNull(getController().getControlsForDispatch(app));
+ provider.setWindowContainer(createWindow(null, TYPE_APPLICATION, "statusBar"), null, null);
+ assertNotNull(getController().getControlsForDispatch(app));
+ }
+
+ @Test
public void testTransientVisibilityOfFixedRotationState() {
final WindowState statusBar = createWindow(null, TYPE_APPLICATION, "statusBar");
final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
index 1195c93..6b17de4 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
@@ -1594,7 +1594,7 @@
}
@Test
- @EnableFlags(Flags.FLAG_DISABLE_THIN_LETTERBOXING_REACHABILITY)
+ @EnableFlags(Flags.FLAG_DISABLE_THIN_LETTERBOXING_POLICY)
public void testAllowReachabilityForThinLetterboxWithFlagEnabled() {
spyOn(mController);
doReturn(true).when(mController).isVerticalThinLetterboxed();
@@ -1609,7 +1609,7 @@
}
@Test
- @DisableFlags(Flags.FLAG_DISABLE_THIN_LETTERBOXING_REACHABILITY)
+ @DisableFlags(Flags.FLAG_DISABLE_THIN_LETTERBOXING_POLICY)
public void testAllowReachabilityForThinLetterboxWithFlagDisabled() {
spyOn(mController);
doReturn(true).when(mController).isVerticalThinLetterboxed();
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index 8fe45cb..76b4e005 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -99,18 +99,7 @@
import java.util.stream.Collectors;
/**
- * Subscription manager provides the mobile subscription information that are associated with the
- * calling user profile {@link UserHandle} for Android SDK 35(V) and above, while Android SDK 34(U)
- * and below can see all subscriptions as it does today.
- *
- * <p>For example, if we have
- * <ul>
- * <li> Subscription 1 associated with personal profile.
- * <li> Subscription 2 associated with work profile.
- * </ul>
- * Then for SDK 35+, if the caller identity is personal profile, then
- * {@link #getActiveSubscriptionInfoList} will return subscription 1 only and vice versa.
- *
+ * Subscription manager provides the mobile subscription information.
*/
@SystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE)
@RequiresFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION)
@@ -1980,17 +1969,7 @@
}
/**
- * Get the SubscriptionInfo(s) of the currently active SIM(s) associated with the current caller
- * user profile {@link UserHandle} for Android SDK 35(V) and above, while Android SDK 34(U)
- * and below can see all subscriptions as it does today.
- *
- * <p>For example, if we have
- * <ul>
- * <li> Subscription 1 associated with personal profile.
- * <li> Subscription 2 associated with work profile.
- * </ul>
- * Then for SDK 35+, if the caller identity is personal profile, then this will return
- * subscription 1 only and vice versa.
+ * Get the SubscriptionInfo(s) of the currently active SIM(s).
*
* <p> Returned records will be sorted by {@link SubscriptionInfo#getSimSlotIndex} then by
* {@link SubscriptionInfo#getSubscriptionId}. Beginning with Android SDK 35, this method will
@@ -2259,9 +2238,7 @@
}
/**
- * Get the active subscription count associated with the current caller user profile for
- * Android SDK 35(V) and above, while Android SDK 34(U) and below can see all subscriptions as
- * it does today.
+ * Get the active subscription count.
*
* @return The current number of active subscriptions.
*
diff --git a/tests/FlickerTests/ActivityEmbedding/AndroidManifest.xml b/tests/FlickerTests/ActivityEmbedding/AndroidManifest.xml
index 6f8f008..955b43a 100644
--- a/tests/FlickerTests/ActivityEmbedding/AndroidManifest.xml
+++ b/tests/FlickerTests/ActivityEmbedding/AndroidManifest.xml
@@ -19,7 +19,7 @@
xmlns:tools="http://schemas.android.com/tools"
package="com.android.server.wm.flicker">
- <uses-sdk android:minSdkVersion="29" android:targetSdkVersion="29"/>
+ <uses-sdk android:minSdkVersion="29" android:targetSdkVersion="35"/>
<!-- Read and write traces from external storage -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
@@ -46,6 +46,8 @@
<uses-permission android:name="android.permission.START_TASKS_FROM_RECENTS" />
<!-- Allow the test to connect to perfetto trace processor -->
<uses-permission android:name="android.permission.INTERNET"/>
+ <!-- Allow to query for the Launcher TestInfo on SDK 30+ -->
+ <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
<!-- Allow the test to write directly to /sdcard/ and connect to trace processor -->
<application android:requestLegacyExternalStorage="true"
android:networkSecurityConfig="@xml/network_security_config"
diff --git a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/open/OpenTrampolineActivityTest.kt b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/open/OpenTrampolineActivityTest.kt
index cf4edd5..67825d2 100644
--- a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/open/OpenTrampolineActivityTest.kt
+++ b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/open/OpenTrampolineActivityTest.kt
@@ -43,6 +43,7 @@
*
* To run this test: `atest FlickerTestsOther:OpenTrampolineActivityTest`
*/
+@FlakyTest(bugId = 341209752)
@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@@ -168,7 +169,6 @@
}
}
- @FlakyTest(bugId = 290736037)
/** Main activity should go from fullscreen to being a split with secondary activity. */
@Test
fun mainActivityLayerGoesFromFullscreenToSplit() {
@@ -203,7 +203,6 @@
}
}
- @FlakyTest(bugId = 288591571)
@Test
override fun visibleLayersShownMoreThanOneConsecutiveEntry() {
super.visibleLayersShownMoreThanOneConsecutiveEntry()
diff --git a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/pip/SecondaryActivityEnterPipTest.kt b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/pip/SecondaryActivityEnterPipTest.kt
index bc3696b..eed9225 100644
--- a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/pip/SecondaryActivityEnterPipTest.kt
+++ b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/pip/SecondaryActivityEnterPipTest.kt
@@ -205,7 +205,8 @@
it.visibleRegion(ComponentNameMatcher.PIP_CONTENT_OVERLAY)
val secondaryVisibleRegion =
it.visibleRegion(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
- overlayVisibleRegion.coversExactly(secondaryVisibleRegion.region)
+ // TODO(b/340992001): replace coverAtLeast with coverExactly
+ overlayVisibleRegion.coversAtLeast(secondaryVisibleRegion.region)
}
.then()
.isInvisible(ComponentNameMatcher.PIP_CONTENT_OVERLAY)
diff --git a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/splitscreen/EnterSystemSplitTest.kt b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/splitscreen/EnterSystemSplitTest.kt
index fb92583..379b45c 100644
--- a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/splitscreen/EnterSystemSplitTest.kt
+++ b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/splitscreen/EnterSystemSplitTest.kt
@@ -60,14 +60,16 @@
testApp.launchViaIntent(wmHelper)
testApp.launchSecondaryActivity(wmHelper)
secondaryApp.launchViaIntent(wmHelper)
- tapl.goHome()
- wmHelper
- .StateSyncBuilder()
- .withAppTransitionIdle()
- .withHomeActivityVisible()
- .waitForAndVerify()
startDisplayBounds =
wmHelper.currentState.layerState.physicalDisplayBounds ?: error("Display not found")
+
+ // Record the displayBounds before `goHome()` in case the launcher is fixed-portrait.
+ tapl.goHome()
+ wmHelper
+ .StateSyncBuilder()
+ .withAppTransitionIdle()
+ .withHomeActivityVisible()
+ .waitForAndVerify()
}
transitions {
SplitScreenUtils.enterSplit(
@@ -138,10 +140,6 @@
check { "ActivityEmbeddingSplitHeight" }
.that(leftAELayerRegion.region.bounds.height())
.isEqual(rightAELayerRegion.region.bounds.height())
- check { "SystemSplitHeight" }
- .that(rightAELayerRegion.region.bounds.height())
- .isEqual(secondaryAppLayerRegion.region.bounds.height())
- // TODO(b/292283182): Remove this special case handling.
check { "ActivityEmbeddingSplitWidth" }
.that(
abs(
@@ -150,14 +148,6 @@
)
)
.isLower(2)
- check { "SystemSplitWidth" }
- .that(
- abs(
- secondaryAppLayerRegion.region.bounds.width() -
- 2 * rightAELayerRegion.region.bounds.width()
- )
- )
- .isLower(2)
}
}
@@ -170,15 +160,9 @@
visibleRegion(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
val rightAEWindowRegion =
visibleRegion(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
- // There's no window for the divider bar.
- val secondaryAppLayerRegion =
- visibleRegion(ActivityOptions.SplitScreen.Primary.COMPONENT.toFlickerComponent())
check { "ActivityEmbeddingSplitHeight" }
.that(leftAEWindowRegion.region.bounds.height())
.isEqual(rightAEWindowRegion.region.bounds.height())
- check { "SystemSplitHeight" }
- .that(rightAEWindowRegion.region.bounds.height())
- .isEqual(secondaryAppLayerRegion.region.bounds.height())
check { "ActivityEmbeddingSplitWidth" }
.that(
abs(
@@ -187,14 +171,6 @@
)
)
.isLower(2)
- check { "SystemSplitWidth" }
- .that(
- abs(
- secondaryAppLayerRegion.region.bounds.width() -
- 2 * rightAEWindowRegion.region.bounds.width()
- )
- )
- .isLower(2)
}
}
diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_embedding_main_layout.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_embedding_main_layout.xml
index 86c21906..917aec1 100644
--- a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_embedding_main_layout.xml
+++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_embedding_main_layout.xml
@@ -14,66 +14,71 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<LinearLayout
+<ScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:orientation="vertical"
android:background="@android:color/holo_orange_light">
- <Button
- android:id="@+id/launch_secondary_activity_button"
- android:layout_width="wrap_content"
- android:layout_height="48dp"
- android:onClick="launchSecondaryActivity"
- android:tag="LEFT_TO_RIGHT"
- android:text="Launch Secondary Activity" />
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
- <Button
- android:id="@+id/launch_secondary_activity_rtl_button"
- android:layout_width="wrap_content"
- android:layout_height="48dp"
- android:onClick="launchSecondaryActivity"
- android:tag="RIGHT_TO_LEFT"
- android:text="Launch Secondary Activity in RTL" />
+ <Button
+ android:id="@+id/launch_secondary_activity_button"
+ android:layout_width="wrap_content"
+ android:layout_height="48dp"
+ android:onClick="launchSecondaryActivity"
+ android:tag="LEFT_TO_RIGHT"
+ android:text="Launch Secondary Activity" />
- <Button
- android:id="@+id/launch_secondary_activity_horizontally_button"
- android:layout_width="wrap_content"
- android:layout_height="48dp"
- android:onClick="launchSecondaryActivity"
- android:tag="BOTTOM_TO_TOP"
- android:text="Launch Secondary Activity Horizontally" />
+ <Button
+ android:id="@+id/launch_secondary_activity_rtl_button"
+ android:layout_width="wrap_content"
+ android:layout_height="48dp"
+ android:onClick="launchSecondaryActivity"
+ android:tag="RIGHT_TO_LEFT"
+ android:text="Launch Secondary Activity in RTL" />
- <Button
- android:id="@+id/launch_placeholder_split_button"
- android:layout_width="wrap_content"
- android:layout_height="48dp"
- android:onClick="launchPlaceholderSplit"
- android:tag="LEFT_TO_RIGHT"
- android:text="Launch Placeholder Split" />
+ <Button
+ android:id="@+id/launch_secondary_activity_horizontally_button"
+ android:layout_width="wrap_content"
+ android:layout_height="48dp"
+ android:onClick="launchSecondaryActivity"
+ android:tag="BOTTOM_TO_TOP"
+ android:text="Launch Secondary Activity Horizontally" />
- <Button
- android:id="@+id/launch_always_expand_activity_button"
- android:layout_width="wrap_content"
- android:layout_height="48dp"
- android:onClick="launchAlwaysExpandActivity"
- android:text="Launch Always Expand Activity" />
+ <Button
+ android:id="@+id/launch_placeholder_split_button"
+ android:layout_width="wrap_content"
+ android:layout_height="48dp"
+ android:onClick="launchPlaceholderSplit"
+ android:tag="LEFT_TO_RIGHT"
+ android:text="Launch Placeholder Split" />
- <Button
- android:id="@+id/launch_placeholder_split_rtl_button"
- android:layout_width="wrap_content"
- android:layout_height="48dp"
- android:onClick="launchPlaceholderSplit"
- android:tag="RIGHT_TO_LEFT"
- android:text="Launch Placeholder Split in RTL" />
+ <Button
+ android:id="@+id/launch_always_expand_activity_button"
+ android:layout_width="wrap_content"
+ android:layout_height="48dp"
+ android:onClick="launchAlwaysExpandActivity"
+ android:text="Launch Always Expand Activity" />
- <Button
- android:id="@+id/launch_trampoline_button"
- android:layout_width="wrap_content"
- android:layout_height="48dp"
- android:onClick="launchTrampolineActivity"
- android:tag="LEFT_TO_RIGHT"
- android:text="Launch Trampoline Activity" />
+ <Button
+ android:id="@+id/launch_placeholder_split_rtl_button"
+ android:layout_width="wrap_content"
+ android:layout_height="48dp"
+ android:onClick="launchPlaceholderSplit"
+ android:tag="RIGHT_TO_LEFT"
+ android:text="Launch Placeholder Split in RTL" />
-</LinearLayout>
+ <Button
+ android:id="@+id/launch_trampoline_button"
+ android:layout_width="wrap_content"
+ android:layout_height="48dp"
+ android:onClick="launchTrampolineActivity"
+ android:tag="LEFT_TO_RIGHT"
+ android:text="Launch Trampoline Activity" />
+
+ </LinearLayout>
+</ScrollView>
diff --git a/tests/Input/src/android/hardware/input/KeyboardLayoutPreviewTests.kt b/tests/Input/src/android/hardware/input/KeyboardLayoutPreviewTests.kt
index 3a2a3be..ae32bda 100644
--- a/tests/Input/src/android/hardware/input/KeyboardLayoutPreviewTests.kt
+++ b/tests/Input/src/android/hardware/input/KeyboardLayoutPreviewTests.kt
@@ -16,6 +16,8 @@
package android.hardware.input
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
import android.content.ContextWrapper
import android.graphics.drawable.Drawable
import android.platform.test.annotations.Presubmit
@@ -54,16 +56,16 @@
}
@Test
+ @EnableFlags(Flags.FLAG_KEYBOARD_LAYOUT_PREVIEW_FLAG)
fun testKeyboardLayoutDrawable_hasCorrectDimensions() {
- setFlagsRule.enableFlags(Flags.FLAG_KEYBOARD_LAYOUT_PREVIEW_FLAG)
val drawable = createDrawable()!!
assertEquals(WIDTH, drawable.intrinsicWidth)
assertEquals(HEIGHT, drawable.intrinsicHeight)
}
@Test
+ @DisableFlags(Flags.FLAG_KEYBOARD_LAYOUT_PREVIEW_FLAG)
fun testKeyboardLayoutDrawable_isNull_ifFlagOff() {
- setFlagsRule.disableFlags(Flags.FLAG_KEYBOARD_LAYOUT_PREVIEW_FLAG)
assertNull(createDrawable())
}
}
\ No newline at end of file
diff --git a/tests/Input/src/android/hardware/input/StickyModifierStateListenerTest.kt b/tests/Input/src/android/hardware/input/StickyModifierStateListenerTest.kt
index e2b0c36..bcd56ad 100644
--- a/tests/Input/src/android/hardware/input/StickyModifierStateListenerTest.kt
+++ b/tests/Input/src/android/hardware/input/StickyModifierStateListenerTest.kt
@@ -21,6 +21,7 @@
import android.os.Handler
import android.os.HandlerExecutor
import android.os.test.TestLooper
+import android.platform.test.annotations.EnableFlags
import android.platform.test.annotations.Presubmit
import android.platform.test.flag.junit.SetFlagsRule
import android.view.KeyEvent
@@ -50,6 +51,10 @@
*/
@Presubmit
@RunWith(MockitoJUnitRunner::class)
+@EnableFlags(
+ com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_STICKY_KEYS_FLAG,
+ com.android.input.flags.Flags.FLAG_ENABLE_INPUT_FILTER_RUST_IMPL,
+)
class StickyModifierStateListenerTest {
@get:Rule
@@ -67,10 +72,6 @@
@Before
fun setUp() {
- // Enable Sticky keys feature
- rule.enableFlags(com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_STICKY_KEYS_FLAG)
- rule.enableFlags(com.android.input.flags.Flags.FLAG_ENABLE_INPUT_FILTER_RUST_IMPL)
-
context = Mockito.spy(ContextWrapper(ApplicationProvider.getApplicationContext()))
inputManagerGlobalSession = InputManagerGlobal.createTestSession(iInputManagerMock)
inputManager = InputManager(context)
diff --git a/tests/Internal/src/com/android/internal/protolog/PerfettoDataSourceTest.java b/tests/Internal/src/com/android/internal/protolog/ProtologDataSourceTest.java
similarity index 99%
rename from tests/Internal/src/com/android/internal/protolog/PerfettoDataSourceTest.java
rename to tests/Internal/src/com/android/internal/protolog/ProtologDataSourceTest.java
index 001a09a..be9fb1b 100644
--- a/tests/Internal/src/com/android/internal/protolog/PerfettoDataSourceTest.java
+++ b/tests/Internal/src/com/android/internal/protolog/ProtologDataSourceTest.java
@@ -34,7 +34,7 @@
import perfetto.protos.ProtologCommon;
import perfetto.protos.ProtologConfig;
-public class PerfettoDataSourceTest {
+public class ProtologDataSourceTest {
@Before
public void before() {
assumeTrue(android.tracing.Flags.perfettoProtologTracing());
diff --git a/tests/UsbManagerTests/src/android/hardware/usb/DeviceFilterTest.java b/tests/UsbManagerTests/src/android/hardware/usb/DeviceFilterTest.java
new file mode 100644
index 0000000..d6f3148
--- /dev/null
+++ b/tests/UsbManagerTests/src/android/hardware/usb/DeviceFilterTest.java
@@ -0,0 +1,248 @@
+/*
+ * 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 android.hardware.usb;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.hardware.usb.flags.Flags;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.dx.mockito.inline.extended.ExtendedMockito;
+import com.android.internal.util.XmlUtils;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mockito;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserFactory;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.StringReader;
+
+/**
+ * Unit tests for {@link android.hardware.usb.DeviceFilter}.
+ */
+@RunWith(AndroidJUnit4.class)
+public class DeviceFilterTest {
+
+ private static final int VID = 10;
+ private static final int PID = 11;
+ private static final int CLASS = 12;
+ private static final int SUBCLASS = 13;
+ private static final int PROTOCOL = 14;
+ private static final String MANUFACTURER = "Google";
+ private static final String PRODUCT = "Test";
+ private static final String SERIAL_NO = "4AL23";
+ private static final String INTERFACE_NAME = "MTP";
+
+ private MockitoSession mStaticMockSession;
+
+ @Before
+ public void setUp() throws Exception {
+ mStaticMockSession = ExtendedMockito.mockitoSession()
+ .mockStatic(Flags.class)
+ .strictness(Strictness.WARN)
+ .startMocking();
+
+ when(Flags.enableInterfaceNameDeviceFilter()).thenReturn(true);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ mStaticMockSession.finishMocking();
+ }
+
+ @Test
+ public void testConstructorFromValues_interfaceNameIsInitialized() {
+ DeviceFilter deviceFilter = new DeviceFilter(
+ VID, PID, CLASS, SUBCLASS, PROTOCOL, MANUFACTURER,
+ PRODUCT, SERIAL_NO, INTERFACE_NAME
+ );
+
+ verifyDeviceFilterConfigurationExceptInterfaceName(deviceFilter);
+ assertThat(deviceFilter.mInterfaceName).isEqualTo(INTERFACE_NAME);
+ }
+
+ @Test
+ public void testConstructorFromUsbDevice_interfaceNameIsNull() {
+ UsbDevice usbDevice = Mockito.mock(UsbDevice.class);
+ when(usbDevice.getVendorId()).thenReturn(VID);
+ when(usbDevice.getProductId()).thenReturn(PID);
+ when(usbDevice.getDeviceClass()).thenReturn(CLASS);
+ when(usbDevice.getDeviceSubclass()).thenReturn(SUBCLASS);
+ when(usbDevice.getDeviceProtocol()).thenReturn(PROTOCOL);
+ when(usbDevice.getManufacturerName()).thenReturn(MANUFACTURER);
+ when(usbDevice.getProductName()).thenReturn(PRODUCT);
+ when(usbDevice.getSerialNumber()).thenReturn(SERIAL_NO);
+
+ DeviceFilter deviceFilter = new DeviceFilter(usbDevice);
+
+ verifyDeviceFilterConfigurationExceptInterfaceName(deviceFilter);
+ assertThat(deviceFilter.mInterfaceName).isEqualTo(null);
+ }
+
+ @Test
+ public void testConstructorFromDeviceFilter_interfaceNameIsInitialized() {
+ DeviceFilter originalDeviceFilter = new DeviceFilter(
+ VID, PID, CLASS, SUBCLASS, PROTOCOL, MANUFACTURER,
+ PRODUCT, SERIAL_NO, INTERFACE_NAME
+ );
+
+ DeviceFilter deviceFilter = new DeviceFilter(originalDeviceFilter);
+
+ verifyDeviceFilterConfigurationExceptInterfaceName(deviceFilter);
+ assertThat(deviceFilter.mInterfaceName).isEqualTo(INTERFACE_NAME);
+ }
+
+
+ @Test
+ public void testReadFromXml_interfaceNamePresent_propertyIsInitialized() throws Exception {
+ DeviceFilter deviceFilter = getDeviceFilterFromXml("<usb-device interface-name=\"MTP\"/>");
+
+ assertThat(deviceFilter.mInterfaceName).isEqualTo("MTP");
+ }
+
+ @Test
+ public void testReadFromXml_interfaceNameAbsent_propertyIsNull() throws Exception {
+ DeviceFilter deviceFilter = getDeviceFilterFromXml("<usb-device vendor-id=\"1\" />");
+
+ assertThat(deviceFilter.mInterfaceName).isEqualTo(null);
+ }
+
+ @Test
+ public void testWrite_withInterfaceName() throws Exception {
+ DeviceFilter deviceFilter = getDeviceFilterFromXml("<usb-device interface-name=\"MTP\"/>");
+ XmlSerializer serializer = Mockito.mock(XmlSerializer.class);
+
+ deviceFilter.write(serializer);
+
+ verify(serializer).attribute(null, "interface-name", "MTP");
+ }
+
+ @Test
+ public void testWrite_withoutInterfaceName() throws Exception {
+ DeviceFilter deviceFilter = getDeviceFilterFromXml("<usb-device vendor-id=\"1\" />");
+ XmlSerializer serializer = Mockito.mock(XmlSerializer.class);
+
+ deviceFilter.write(serializer);
+
+ verify(serializer, times(0)).attribute(eq(null), eq("interface-name"), any());
+ }
+
+ @Test
+ public void testToString() {
+ DeviceFilter deviceFilter = new DeviceFilter(
+ VID, PID, CLASS, SUBCLASS, PROTOCOL, MANUFACTURER,
+ PRODUCT, SERIAL_NO, INTERFACE_NAME
+ );
+
+ assertThat(deviceFilter.toString()).isEqualTo(
+ "DeviceFilter[mVendorId=10,mProductId=11,mClass=12,mSubclass=13,mProtocol=14,"
+ + "mManufacturerName=Google,mProductName=Test,mSerialNumber=4AL23,"
+ + "mInterfaceName=MTP]");
+ }
+
+ @Test
+ public void testMatch_interfaceNameMatches_returnTrue() throws Exception {
+ DeviceFilter deviceFilter = getDeviceFilterFromXml(
+ "<usb-device class=\"255\" subclass=\"255\" protocol=\"0\" "
+ + "interface-name=\"MTP\"/>");
+ UsbDevice usbDevice = Mockito.mock(UsbDevice.class);
+ when(usbDevice.getInterfaceCount()).thenReturn(1);
+ when(usbDevice.getInterface(0)).thenReturn(new UsbInterface(
+ /* id= */ 0,
+ /* alternateSetting= */ 0,
+ /* name= */ "MTP",
+ /* class= */ 255,
+ /* subClass= */ 255,
+ /* protocol= */ 0));
+
+ assertTrue(deviceFilter.matches(usbDevice));
+ }
+
+ @Test
+ public void testMatch_interfaceNameMismatch_returnFalse() throws Exception {
+ DeviceFilter deviceFilter = getDeviceFilterFromXml(
+ "<usb-device class=\"255\" subclass=\"255\" protocol=\"0\" "
+ + "interface-name=\"MTP\"/>");
+ UsbDevice usbDevice = Mockito.mock(UsbDevice.class);
+ when(usbDevice.getInterfaceCount()).thenReturn(1);
+ when(usbDevice.getInterface(0)).thenReturn(new UsbInterface(
+ /* id= */ 0,
+ /* alternateSetting= */ 0,
+ /* name= */ "UVC",
+ /* class= */ 255,
+ /* subClass= */ 255,
+ /* protocol= */ 0));
+
+ assertFalse(deviceFilter.matches(usbDevice));
+ }
+
+ @Test
+ public void testMatch_interfaceNameMismatchFlagDisabled_returnTrue() throws Exception {
+ when(Flags.enableInterfaceNameDeviceFilter()).thenReturn(false);
+ DeviceFilter deviceFilter = getDeviceFilterFromXml(
+ "<usb-device class=\"255\" subclass=\"255\" protocol=\"0\" "
+ + "interface-name=\"MTP\"/>");
+ UsbDevice usbDevice = Mockito.mock(UsbDevice.class);
+ when(usbDevice.getInterfaceCount()).thenReturn(1);
+ when(usbDevice.getInterface(0)).thenReturn(new UsbInterface(
+ /* id= */ 0,
+ /* alternateSetting= */ 0,
+ /* name= */ "UVC",
+ /* class= */ 255,
+ /* subClass= */ 255,
+ /* protocol= */ 0));
+
+ assertTrue(deviceFilter.matches(usbDevice));
+ }
+
+ private void verifyDeviceFilterConfigurationExceptInterfaceName(DeviceFilter deviceFilter) {
+ assertThat(deviceFilter.mVendorId).isEqualTo(VID);
+ assertThat(deviceFilter.mProductId).isEqualTo(PID);
+ assertThat(deviceFilter.mClass).isEqualTo(CLASS);
+ assertThat(deviceFilter.mSubclass).isEqualTo(SUBCLASS);
+ assertThat(deviceFilter.mProtocol).isEqualTo(PROTOCOL);
+ assertThat(deviceFilter.mManufacturerName).isEqualTo(MANUFACTURER);
+ assertThat(deviceFilter.mProductName).isEqualTo(PRODUCT);
+ assertThat(deviceFilter.mSerialNumber).isEqualTo(SERIAL_NO);
+ }
+
+ private DeviceFilter getDeviceFilterFromXml(String xml) throws Exception {
+ XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
+ XmlPullParser parser = factory.newPullParser();
+ parser.setInput(new StringReader(xml));
+ XmlUtils.nextElement(parser);
+
+ return DeviceFilter.read(parser);
+ }
+
+}
diff --git a/tools/aapt/Symbol.h b/tools/aapt/Symbol.h
index de1d60c..24c3208 100644
--- a/tools/aapt/Symbol.h
+++ b/tools/aapt/Symbol.h
@@ -40,7 +40,7 @@
};
/**
- * A specific defintion of a symbol, defined with a configuration and a definition site.
+ * A specific definition of a symbol, defined with a configuration and a definition site.
*/
struct SymbolDefinition {
inline SymbolDefinition();
@@ -92,4 +92,3 @@
}
#endif // AAPT_SYMBOL_H
-