Merge "Fix setSurfaceBehindVisibility(false) being ignored" into main
diff --git a/android-sdk-flags/OWNERS b/android-sdk-flags/OWNERS
new file mode 100644
index 0000000..01f45dd
--- /dev/null
+++ b/android-sdk-flags/OWNERS
@@ -0,0 +1 @@
+include /SDK_OWNERS
diff --git a/android-sdk-flags/flags.aconfig b/android-sdk-flags/flags.aconfig
index cfe298e..19c7bf6 100644
--- a/android-sdk-flags/flags.aconfig
+++ b/android-sdk-flags/flags.aconfig
@@ -6,6 +6,7 @@
namespace: "android_sdk"
description: "Use the new SDK major.minor versioning scheme (e.g. Android 40.1) which replaces the old single-integer scheme (e.g. Android 15)."
bug: "350458259"
+ is_exported: true
# Use is_fixed_read_only because DeviceConfig may not be available when Build.VERSION_CODES is first accessed
is_fixed_read_only: true
diff --git a/core/api/current.txt b/core/api/current.txt
index 0153213..d9ab273 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -52997,7 +52997,7 @@
method public void addOnUnhandledKeyEventListener(android.view.View.OnUnhandledKeyEventListener);
method public void addTouchables(java.util.ArrayList<android.view.View>);
method public android.view.ViewPropertyAnimator animate();
- method public void announceForAccessibility(CharSequence);
+ method @Deprecated @FlaggedApi("android.view.accessibility.deprecate_accessibility_announcement_apis") public void announceForAccessibility(CharSequence);
method public void autofill(android.view.autofill.AutofillValue);
method public void autofill(@NonNull android.util.SparseArray<android.view.autofill.AutofillValue>);
method protected boolean awakenScrollBars();
@@ -55247,7 +55247,7 @@
field public static final int SPEECH_STATE_SPEAKING_END = 2; // 0x2
field public static final int SPEECH_STATE_SPEAKING_START = 1; // 0x1
field public static final int TYPES_ALL_MASK = -1; // 0xffffffff
- field public static final int TYPE_ANNOUNCEMENT = 16384; // 0x4000
+ field @Deprecated @FlaggedApi("android.view.accessibility.deprecate_accessibility_announcement_apis") public static final int TYPE_ANNOUNCEMENT = 16384; // 0x4000
field public static final int TYPE_ASSIST_READING_CONTEXT = 16777216; // 0x1000000
field public static final int TYPE_GESTURE_DETECTION_END = 524288; // 0x80000
field public static final int TYPE_GESTURE_DETECTION_START = 262144; // 0x40000
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 02cd00d..860089f 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -3579,6 +3579,7 @@
field @FlaggedApi("android.companion.virtualdevice.flags.activity_control_api") public static final int POLICY_TYPE_BLOCKED_ACTIVITY = 6; // 0x6
field @FlaggedApi("android.companion.virtual.flags.virtual_camera") public static final int POLICY_TYPE_CAMERA = 5; // 0x5
field @FlaggedApi("android.companion.virtual.flags.cross_device_clipboard") public static final int POLICY_TYPE_CLIPBOARD = 4; // 0x4
+ field @FlaggedApi("android.companion.virtualdevice.flags.default_device_camera_access_policy") public static final int POLICY_TYPE_DEFAULT_DEVICE_CAMERA_ACCESS = 7; // 0x7
field public static final int POLICY_TYPE_RECENTS = 2; // 0x2
field public static final int POLICY_TYPE_SENSORS = 0; // 0x0
}
diff --git a/core/java/android/app/StatusBarManager.java b/core/java/android/app/StatusBarManager.java
index 63e03914..b7285c3 100644
--- a/core/java/android/app/StatusBarManager.java
+++ b/core/java/android/app/StatusBarManager.java
@@ -602,6 +602,15 @@
@LoggingOnly
private static final long MEDIA_CONTROL_BLANK_TITLE = 274775190L;
+ /**
+ * Media controls based on {@link android.app.Notification.MediaStyle} notifications will have
+ * actions from the associated {@link androidx.media3.MediaController}, if available.
+ */
+ @ChangeId
+ @EnabledSince(targetSdkVersion = Build.VERSION_CODES.CUR_DEVELOPMENT)
+ // TODO(b/360196209): Set target SDK to Baklava once available
+ private static final long MEDIA_CONTROL_MEDIA3_ACTIONS = 360196209L;
+
@UnsupportedAppUsage
private Context mContext;
private IStatusBarService mService;
@@ -1270,6 +1279,21 @@
}
/**
+ * Checks whether the media controls for a given package should use a Media3 controller
+ *
+ * @param packageName App posting media controls
+ * @param user Current user handle
+ * @return true if Media3 should be used
+ *
+ * @hide
+ */
+ @RequiresPermission(allOf = {android.Manifest.permission.READ_COMPAT_CHANGE_CONFIG,
+ android.Manifest.permission.LOG_COMPAT_CHANGE})
+ public static boolean useMedia3ControllerForApp(String packageName, UserHandle user) {
+ return CompatChanges.isChangeEnabled(MEDIA_CONTROL_MEDIA3_ACTIONS, packageName, user);
+ }
+
+ /**
* Checks whether the supplied activity can {@link Activity#startActivityForResult(Intent, int)}
* a system activity that captures content on the screen to take a screenshot.
*
diff --git a/core/java/android/companion/virtual/VirtualDeviceParams.java b/core/java/android/companion/virtual/VirtualDeviceParams.java
index 65f9cbe..6dad015 100644
--- a/core/java/android/companion/virtual/VirtualDeviceParams.java
+++ b/core/java/android/companion/virtual/VirtualDeviceParams.java
@@ -160,7 +160,7 @@
*/
@IntDef(prefix = "POLICY_TYPE_", value = {POLICY_TYPE_SENSORS, POLICY_TYPE_AUDIO,
POLICY_TYPE_RECENTS, POLICY_TYPE_ACTIVITY, POLICY_TYPE_CLIPBOARD, POLICY_TYPE_CAMERA,
- POLICY_TYPE_BLOCKED_ACTIVITY})
+ POLICY_TYPE_BLOCKED_ACTIVITY, POLICY_TYPE_DEFAULT_DEVICE_CAMERA_ACCESS})
@Retention(RetentionPolicy.SOURCE)
@Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
public @interface PolicyType {}
@@ -301,6 +301,21 @@
@FlaggedApi(android.companion.virtualdevice.flags.Flags.FLAG_ACTIVITY_CONTROL_API)
public static final int POLICY_TYPE_BLOCKED_ACTIVITY = 6;
+ /**
+ * Tells the virtual device framework how to handle camera access of the default device by apps
+ * running on the virtual device.
+ *
+ * <ul>
+ * <li>{@link #DEVICE_POLICY_DEFAULT}: Default device camera access will be allowed.
+ * <li>{@link #DEVICE_POLICY_CUSTOM}: Default device camera access will be blocked.
+ * </ul>
+ *
+ * @see Context#DEVICE_ID_DEFAULT
+ */
+ @FlaggedApi(android.companion.virtualdevice.flags.Flags
+ .FLAG_DEFAULT_DEVICE_CAMERA_ACCESS_POLICY)
+ public static final int POLICY_TYPE_DEFAULT_DEVICE_CAMERA_ACCESS = 7;
+
private final int mLockState;
@NonNull private final ArraySet<UserHandle> mUsersWithMatchingAccounts;
@NavigationPolicy
@@ -696,7 +711,7 @@
@NonNull
public static final Parcelable.Creator<VirtualDeviceParams> CREATOR =
- new Parcelable.Creator<VirtualDeviceParams>() {
+ new Parcelable.Creator<>() {
public VirtualDeviceParams createFromParcel(Parcel in) {
return new VirtualDeviceParams(in);
}
@@ -1213,6 +1228,10 @@
mDevicePolicies.delete(POLICY_TYPE_CAMERA);
}
+ if (!android.companion.virtualdevice.flags.Flags.defaultDeviceCameraAccessPolicy()) {
+ mDevicePolicies.delete(POLICY_TYPE_DEFAULT_DEVICE_CAMERA_ACCESS);
+ }
+
if (!android.companion.virtualdevice.flags.Flags.activityControlApi()) {
mDevicePolicies.delete(POLICY_TYPE_BLOCKED_ACTIVITY);
}
diff --git a/core/java/android/companion/virtual/flags.aconfig b/core/java/android/companion/virtual/flags.aconfig
index fc9c94d..3e6919b 100644
--- a/core/java/android/companion/virtual/flags.aconfig
+++ b/core/java/android/companion/virtual/flags.aconfig
@@ -67,13 +67,6 @@
}
flag {
- name: "stream_camera"
- namespace: "virtual_devices"
- description: "Enable streaming camera to Virtual Devices"
- bug: "291740640"
-}
-
-flag {
name: "persistent_device_id_api"
is_exported: true
namespace: "virtual_devices"
diff --git a/core/java/android/content/res/TEST_MAPPING b/core/java/android/content/res/TEST_MAPPING
index c2febae..e8893e4 100644
--- a/core/java/android/content/res/TEST_MAPPING
+++ b/core/java/android/content/res/TEST_MAPPING
@@ -5,6 +5,9 @@
},
{
"path": "frameworks/base/core/tests/coretests/src/com/android/internal/content/res"
+ },
+ {
+ "path": "platform_testing/libraries/screenshot"
}
],
"presubmit": [
diff --git a/core/java/android/hardware/usb/OWNERS b/core/java/android/hardware/usb/OWNERS
index a753f96..37604bc 100644
--- a/core/java/android/hardware/usb/OWNERS
+++ b/core/java/android/hardware/usb/OWNERS
@@ -1,7 +1,7 @@
# Bug component: 175220
-aprasath@google.com
-kumarashishg@google.com
-sarup@google.com
anothermark@google.com
+febinthattil@google.com
+aprasath@google.com
badhri@google.com
+kumarashishg@google.com
\ No newline at end of file
diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java
index 13d7e3c..b3aebad 100644
--- a/core/java/android/os/Build.java
+++ b/core/java/android/os/Build.java
@@ -40,8 +40,6 @@
import android.util.Slog;
import android.view.View;
-import com.android.internal.ravenwood.RavenwoodEnvironment;
-
import dalvik.system.VMRuntime;
import java.lang.annotation.Retention;
@@ -57,10 +55,6 @@
*/
@RavenwoodKeepWholeClass
public class Build {
- static {
- // Set up the default system properties.
- RavenwoodEnvironment.ensureRavenwoodInitialized();
- }
private static final String TAG = "Build";
/** Value used for when a build property is unknown. */
diff --git a/core/java/android/os/ParcelFileDescriptor.java b/core/java/android/os/ParcelFileDescriptor.java
index e80efd2..60eeb2b 100644
--- a/core/java/android/os/ParcelFileDescriptor.java
+++ b/core/java/android/os/ParcelFileDescriptor.java
@@ -41,7 +41,6 @@
import android.net.Uri;
import android.os.MessageQueue.OnFileDescriptorEventListener;
import android.ravenwood.annotation.RavenwoodKeepWholeClass;
-import android.ravenwood.annotation.RavenwoodReplace;
import android.ravenwood.annotation.RavenwoodThrow;
import android.system.ErrnoException;
import android.system.Os;
@@ -51,8 +50,6 @@
import android.util.Log;
import android.util.Slog;
-import com.android.internal.ravenwood.RavenwoodEnvironment;
-
import dalvik.system.VMRuntime;
import libcore.io.IoUtils;
@@ -1254,15 +1251,10 @@
}
}
- @RavenwoodReplace
private static boolean isAtLeastQ() {
return (VMRuntime.getRuntime().getTargetSdkVersion() >= Build.VERSION_CODES.Q);
}
- private static boolean isAtLeastQ$ravenwood() {
- return RavenwoodEnvironment.workaround().isTargetSdkAtLeastQ();
- }
-
private static int ifAtLeastQ(int value) {
return isAtLeastQ() ? value : 0;
}
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java
index 71d29af..e728243 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -29,6 +29,11 @@
import android.annotation.UptimeMillisLong;
import android.compat.annotation.UnsupportedAppUsage;
import android.os.Build.VERSION_CODES;
+import android.ravenwood.annotation.RavenwoodKeep;
+import android.ravenwood.annotation.RavenwoodKeepPartialClass;
+import android.ravenwood.annotation.RavenwoodRedirect;
+import android.ravenwood.annotation.RavenwoodRedirectionClass;
+import android.ravenwood.annotation.RavenwoodReplace;
import android.sysprop.MemoryProperties;
import android.system.ErrnoException;
import android.system.Os;
@@ -37,8 +42,6 @@
import android.util.Pair;
import android.webkit.WebViewZygote;
-import com.android.internal.os.SomeArgs;
-import com.android.internal.util.Preconditions;
import com.android.sdksandbox.flags.Flags;
import dalvik.system.VMDebug;
@@ -55,6 +58,8 @@
/**
* Tools for managing OS processes.
*/
+@RavenwoodKeepPartialClass
+@RavenwoodRedirectionClass("Process_ravenwood")
public class Process {
private static final String LOG_TAG = "Process";
@@ -671,7 +676,6 @@
*/
public static final ZygoteProcess ZYGOTE_PROCESS = new ZygoteProcess();
-
/**
* The process name set via {@link #setArgV0(String)}.
*/
@@ -845,47 +849,20 @@
/**
* Returns true if the current process is a 64-bit runtime.
*/
- @android.ravenwood.annotation.RavenwoodKeep
+ @RavenwoodKeep
public static final boolean is64Bit() {
return VMRuntime.getRuntime().is64Bit();
}
- private static volatile ThreadLocal<SomeArgs> sIdentity$ravenwood;
-
- /** @hide */
- @android.ravenwood.annotation.RavenwoodKeep
- public static void init$ravenwood(final int uid, final int pid) {
- sIdentity$ravenwood = ThreadLocal.withInitial(() -> {
- final SomeArgs args = SomeArgs.obtain();
- args.argi1 = uid;
- args.argi2 = pid;
- args.argi3 = Long.hashCode(Thread.currentThread().getId());
- args.argi4 = THREAD_PRIORITY_DEFAULT;
- args.arg1 = Boolean.TRUE; // backgroundOk
- return args;
- });
- }
-
- /** @hide */
- @android.ravenwood.annotation.RavenwoodKeep
- public static void reset$ravenwood() {
- sIdentity$ravenwood = null;
- }
-
/**
* Returns the identifier of this process, which can be used with
* {@link #killProcess} and {@link #sendSignal}.
*/
- @android.ravenwood.annotation.RavenwoodReplace
+ @RavenwoodKeep
public static final int myPid() {
return Os.getpid();
}
- /** @hide */
- public static final int myPid$ravenwood() {
- return Preconditions.requireNonNullViaRavenwoodRule(sIdentity$ravenwood).get().argi2;
- }
-
/**
* Returns the identifier of this process' parent.
* @hide
@@ -899,39 +876,29 @@
* Returns the identifier of the calling thread, which be used with
* {@link #setThreadPriority(int, int)}.
*/
- @android.ravenwood.annotation.RavenwoodReplace
+ @RavenwoodKeep
public static final int myTid() {
return Os.gettid();
}
- /** @hide */
- public static final int myTid$ravenwood() {
- return Preconditions.requireNonNullViaRavenwoodRule(sIdentity$ravenwood).get().argi3;
- }
-
/**
* Returns the identifier of this process's uid. This is the kernel uid
* that the process is running under, which is the identity of its
* app-specific sandbox. It is different from {@link #myUserHandle} in that
* a uid identifies a specific app sandbox in a specific user.
*/
- @android.ravenwood.annotation.RavenwoodReplace
+ @RavenwoodKeep
public static final int myUid() {
return Os.getuid();
}
- /** @hide */
- public static final int myUid$ravenwood() {
- return Preconditions.requireNonNullViaRavenwoodRule(sIdentity$ravenwood).get().argi1;
- }
-
/**
* Returns this process's user handle. This is the
* user the process is running under. It is distinct from
* {@link #myUid()} in that a particular user will have multiple
* distinct apps running under it each with their own uid.
*/
- @android.ravenwood.annotation.RavenwoodKeep
+ @RavenwoodKeep
public static UserHandle myUserHandle() {
return UserHandle.of(UserHandle.getUserId(myUid()));
}
@@ -940,7 +907,7 @@
* Returns whether the given uid belongs to a system core component or not.
* @hide
*/
- @android.ravenwood.annotation.RavenwoodKeep
+ @RavenwoodKeep
public static boolean isCoreUid(int uid) {
return UserHandle.isCore(uid);
}
@@ -951,7 +918,7 @@
* @return Whether the uid corresponds to an application sandbox running in
* a specific user.
*/
- @android.ravenwood.annotation.RavenwoodKeep
+ @RavenwoodKeep
public static boolean isApplicationUid(int uid) {
return UserHandle.isApp(uid);
}
@@ -959,7 +926,7 @@
/**
* Returns whether the current process is in an isolated sandbox.
*/
- @android.ravenwood.annotation.RavenwoodKeep
+ @RavenwoodKeep
public static final boolean isIsolated() {
return isIsolated(myUid());
}
@@ -971,7 +938,7 @@
@Deprecated
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.TIRAMISU,
publicAlternatives = "Use {@link #isIsolatedUid(int)} instead.")
- @android.ravenwood.annotation.RavenwoodKeep
+ @RavenwoodKeep
public static final boolean isIsolated(int uid) {
return isIsolatedUid(uid);
}
@@ -979,7 +946,7 @@
/**
* Returns whether the process with the given {@code uid} is an isolated sandbox.
*/
- @android.ravenwood.annotation.RavenwoodKeep
+ @RavenwoodKeep
public static final boolean isIsolatedUid(int uid) {
uid = UserHandle.getAppId(uid);
return (uid >= FIRST_ISOLATED_UID && uid <= LAST_ISOLATED_UID)
@@ -991,7 +958,7 @@
* @see android.app.sdksandbox.SdkSandboxManager
*/
@SuppressLint("UnflaggedApi") // promoting from @SystemApi.
- @android.ravenwood.annotation.RavenwoodKeep
+ @RavenwoodKeep
public static final boolean isSdkSandboxUid(int uid) {
uid = UserHandle.getAppId(uid);
return (uid >= FIRST_SDK_SANDBOX_UID && uid <= LAST_SDK_SANDBOX_UID);
@@ -1007,7 +974,7 @@
* @throws IllegalArgumentException if input is not an sdk sandbox uid
*/
@SuppressLint("UnflaggedApi") // promoting from @SystemApi.
- @android.ravenwood.annotation.RavenwoodKeep
+ @RavenwoodKeep
public static final int getAppUidForSdkSandboxUid(int uid) {
if (!isSdkSandboxUid(uid)) {
throw new IllegalArgumentException("Input UID is not an SDK sandbox UID");
@@ -1023,7 +990,7 @@
*/
@SystemApi(client = MODULE_LIBRARIES)
@TestApi
- @android.ravenwood.annotation.RavenwoodKeep
+ @RavenwoodKeep
// TODO(b/318651609): Deprecate once Process#getSdkSandboxUidForAppUid is rolled out to 100%
public static final int toSdkSandboxUid(int uid) {
return uid + (FIRST_SDK_SANDBOX_UID - FIRST_APPLICATION_UID);
@@ -1039,7 +1006,7 @@
* @throws IllegalArgumentException if input is not an app uid
*/
@FlaggedApi(Flags.FLAG_SDK_SANDBOX_UID_TO_APP_UID_API)
- @android.ravenwood.annotation.RavenwoodKeep
+ @RavenwoodKeep
public static final int getSdkSandboxUidForAppUid(int uid) {
if (!isApplicationUid(uid)) {
throw new IllegalArgumentException("Input UID is not an app UID");
@@ -1050,7 +1017,7 @@
/**
* Returns whether the current process is a sdk sandbox process.
*/
- @android.ravenwood.annotation.RavenwoodKeep
+ @RavenwoodKeep
public static final boolean isSdkSandbox() {
return isSdkSandboxUid(myUid());
}
@@ -1127,28 +1094,11 @@
* not have permission to modify the given thread, or to use the given
* priority.
*/
- @android.ravenwood.annotation.RavenwoodReplace
+ @RavenwoodRedirect
public static final native void setThreadPriority(int tid,
@IntRange(from = -20, to = THREAD_PRIORITY_LOWEST) int priority)
throws IllegalArgumentException, SecurityException;
- /** @hide */
- public static final void setThreadPriority$ravenwood(int tid, int priority) {
- final SomeArgs args =
- Preconditions.requireNonNullViaRavenwoodRule(sIdentity$ravenwood).get();
- if (args.argi3 == tid) {
- boolean backgroundOk = (args.arg1 == Boolean.TRUE);
- if (priority >= THREAD_PRIORITY_BACKGROUND && !backgroundOk) {
- throw new IllegalArgumentException(
- "Priority " + priority + " blocked by setCanSelfBackground()");
- }
- args.argi4 = priority;
- } else {
- throw new UnsupportedOperationException(
- "Cross-thread priority management not yet available in Ravenwood");
- }
- }
-
/**
* Call with 'false' to cause future calls to {@link #setThreadPriority(int)} to
* throw an exception if passed a background-level thread priority. This is only
@@ -1156,16 +1106,9 @@
*
* @hide
*/
- @android.ravenwood.annotation.RavenwoodReplace
+ @RavenwoodRedirect
public static final native void setCanSelfBackground(boolean backgroundOk);
- /** @hide */
- public static final void setCanSelfBackground$ravenwood(boolean backgroundOk) {
- final SomeArgs args =
- Preconditions.requireNonNullViaRavenwoodRule(sIdentity$ravenwood).get();
- args.arg1 = Boolean.valueOf(backgroundOk);
- }
-
/**
* Sets the scheduling group for a thread.
* @hide
@@ -1294,13 +1237,12 @@
*
* @see #setThreadPriority(int, int)
*/
- @android.ravenwood.annotation.RavenwoodReplace
+ @RavenwoodReplace
public static final native void setThreadPriority(
@IntRange(from = -20, to = THREAD_PRIORITY_LOWEST) int priority)
throws IllegalArgumentException, SecurityException;
- /** @hide */
- public static final void setThreadPriority$ravenwood(int priority) {
+ private static void setThreadPriority$ravenwood(int priority) {
setThreadPriority(myTid(), priority);
}
@@ -1317,23 +1259,11 @@
* @throws IllegalArgumentException Throws IllegalArgumentException if
* <var>tid</var> does not exist.
*/
- @android.ravenwood.annotation.RavenwoodReplace
+ @RavenwoodRedirect
@IntRange(from = -20, to = THREAD_PRIORITY_LOWEST)
public static final native int getThreadPriority(int tid)
throws IllegalArgumentException;
- /** @hide */
- public static final int getThreadPriority$ravenwood(int tid) {
- final SomeArgs args =
- Preconditions.requireNonNullViaRavenwoodRule(sIdentity$ravenwood).get();
- if (args.argi3 == tid) {
- return args.argi4;
- } else {
- throw new UnsupportedOperationException(
- "Cross-thread priority management not yet available in Ravenwood");
- }
- }
-
/**
* Return the current scheduling policy of a thread, based on Linux.
*
diff --git a/core/java/android/view/HapticFeedbackConstants.java b/core/java/android/view/HapticFeedbackConstants.java
index 1fe06d4..66d64d7 100644
--- a/core/java/android/view/HapticFeedbackConstants.java
+++ b/core/java/android/view/HapticFeedbackConstants.java
@@ -176,7 +176,7 @@
/**
* The user is executing a swipe/drag-style gesture, such as pull-to-refresh, where the
- * gesture action is “eligible” at a certain threshold of movement, and can be cancelled by
+ * gesture action is "eligible" at a certain threshold of movement, and can be cancelled by
* moving back past the threshold. This constant indicates that the user's motion has just
* passed the threshold for the action to be activated on release.
*
@@ -186,7 +186,7 @@
/**
* The user is executing a swipe/drag-style gesture, such as pull-to-refresh, where the
- * gesture action is “eligible” at a certain threshold of movement, and can be cancelled by
+ * gesture action is "eligible" at a certain threshold of movement, and can be cancelled by
* moving back past the threshold. This constant indicates that the user's motion has just
* re-crossed back "under" the threshold for the action to be activated, meaning the gesture is
* currently in a cancelled state.
diff --git a/core/java/android/view/InsetsSourceControl.java b/core/java/android/view/InsetsSourceControl.java
index 7877352..acbd95bf 100644
--- a/core/java/android/view/InsetsSourceControl.java
+++ b/core/java/android/view/InsetsSourceControl.java
@@ -212,8 +212,7 @@
&& mInitiallyVisible == that.mInitiallyVisible
&& mSurfacePosition.equals(that.mSurfacePosition)
&& mInsetsHint.equals(that.mInsetsHint)
- && mSkipAnimationOnce == that.mSkipAnimationOnce
- && Objects.equals(mImeStatsToken, that.mImeStatsToken);
+ && mSkipAnimationOnce == that.mSkipAnimationOnce;
}
@Override
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index e49eec6..f2951d3 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -30,6 +30,7 @@
import static android.view.Surface.FRAME_RATE_COMPATIBILITY_GTE;
import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
import static android.view.accessibility.AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED;
+import static android.view.accessibility.Flags.FLAG_DEPRECATE_ACCESSIBILITY_ANNOUNCEMENT_APIS;
import static android.view.accessibility.Flags.FLAG_SUPPLEMENTAL_DESCRIPTION;
import static android.view.accessibility.Flags.removeChildHoverCheckForTouchExploration;
import static android.view.accessibility.Flags.supplementalDescription;
@@ -8941,44 +8942,45 @@
}
/**
- * Convenience method for sending a {@link AccessibilityEvent#TYPE_ANNOUNCEMENT}
- * {@link AccessibilityEvent} to suggest that an accessibility service announce the
- * specified text to its users.
- * <p>
- * Note: The event generated with this API carries no semantic meaning, and is appropriate only
- * in exceptional situations. Apps can generally achieve correct behavior for accessibility by
- * accurately supplying the semantics of their UI.
- * They should not need to specify what exactly is announced to users.
+ * Convenience method for sending a {@link AccessibilityEvent#TYPE_ANNOUNCEMENT} {@link
+ * AccessibilityEvent} to suggest that an accessibility service announce the specified text to
+ * its users.
*
- * <p>
- * In general, only announce transitions and don't generate a confirmation message for simple
- * actions like a button press. Label your controls concisely and precisely instead, and for
- * significant UI changes like window changes, use
- * {@link android.app.Activity#setTitle(CharSequence)} and
- * {@link #setAccessibilityPaneTitle(CharSequence)}.
+ * <p>Note: The event generated with this API carries no semantic meaning, and accessibility
+ * services may choose to ignore it. Apps that accurately supply accessibility with the
+ * semantics of their UI should not need to specify what exactly is announced.
*
- * <p>
- * Use {@link #setAccessibilityLiveRegion(int)} to inform the user of changes to critical
+ * <p>In general, do not attempt to generate announcements as confirmation message for simple
+ * actions like a button press. Label your controls concisely and precisely instead.
+ *
+ * <p>To convey significant UI changes like window changes, use {@link
+ * android.app.Activity#setTitle(CharSequence)} and {@link
+ * #setAccessibilityPaneTitle(CharSequence)}.
+ *
+ * <p>Use {@link #setAccessibilityLiveRegion(int)} to inform the user of changes to critical
* views within the user interface. These should still be used sparingly as they may generate
* announcements every time a View is updated.
*
- * <p>
- * For notifying users about errors, such as in a login screen with text that displays an
- * "incorrect password" notification, that view should send an AccessibilityEvent of type
- * {@link AccessibilityEvent#CONTENT_CHANGE_TYPE_ERROR} and set
- * {@link AccessibilityNodeInfo#setError(CharSequence)} instead. Custom widgets should expose
- * error-setting methods that support accessibility automatically. For example, instead of
- * explicitly sending this event when using a TextView, use
- * {@link android.widget.TextView#setError(CharSequence)}.
- *
- * <p>
- * Use {@link #setStateDescription(CharSequence)} to convey state changes to views within the
+ * <p>Use {@link #setStateDescription(CharSequence)} to convey state changes to views within the
* user interface. While a live region may send different types of events generated by the view,
* state description will send {@link AccessibilityEvent#TYPE_WINDOW_CONTENT_CHANGED} events of
* type {@link AccessibilityEvent#CONTENT_CHANGE_TYPE_STATE_DESCRIPTION}.
*
+ * <p>For notifying users about errors, such as in a login screen with text that displays an
+ * "incorrect password" notification, set {@link AccessibilityNodeInfo#setError(CharSequence)}
+ * and dispatch an {@link AccessibilityEvent#TYPE_WINDOW_CONTENT_CHANGED} event with a change
+ * type of {@link AccessibilityEvent#CONTENT_CHANGE_TYPE_ERROR}, instead. Some widgets may
+ * expose methods that convey error states to accessibility automatically, such as {@link
+ * android.widget.TextView#setError(CharSequence)}, which manages these accessibility semantics
+ * and event dispatch for callers.
+ *
+ * @deprecated Use one of the methods described in the documentation above to semantically
+ * describe UI instead of using an announcement, as accessibility services may choose to
+ * ignore events dispatched with this method.
* @param text The announcement text.
*/
+ @FlaggedApi(FLAG_DEPRECATE_ACCESSIBILITY_ANNOUNCEMENT_APIS)
+ @Deprecated
public void announceForAccessibility(CharSequence text) {
if (AccessibilityManager.getInstance(mContext).isEnabled() && mParent != null) {
AccessibilityEvent event = AccessibilityEvent.obtain(
diff --git a/core/java/android/view/accessibility/AccessibilityEvent.java b/core/java/android/view/accessibility/AccessibilityEvent.java
index c690787..0dfaf41 100644
--- a/core/java/android/view/accessibility/AccessibilityEvent.java
+++ b/core/java/android/view/accessibility/AccessibilityEvent.java
@@ -563,10 +563,13 @@
/**
* Represents the event of an application making an announcement.
- * <p>
- * In general, follow the practices described in
- * {@link View#announceForAccessibility(CharSequence)}.
+ *
+ * @deprecated Use one of the semantic alternative methods described in the documentation of
+ * {@link View#announceForAccessibility(CharSequence)} instead of using this event, as
+ * accessibility services may choose to ignore dispatch of this event type.
*/
+ @FlaggedApi(Flags.FLAG_DEPRECATE_ACCESSIBILITY_ANNOUNCEMENT_APIS)
+ @Deprecated
public static final int TYPE_ANNOUNCEMENT = 1 << 14;
/**
diff --git a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
index c07da41..7177ef3 100644
--- a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
+++ b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
@@ -85,6 +85,13 @@
flag {
namespace: "accessibility"
+ name: "deprecate_accessibility_announcement_apis"
+ description: "Controls the deprecation of platform APIs related to disruptive accessibility announcements"
+ bug: "376727542"
+}
+
+flag {
+ namespace: "accessibility"
name: "fix_merged_content_change_event_v2"
description: "Fixes event type and source of content change event merged in ViewRootImpl"
bug: "277305460"
diff --git a/core/java/android/view/inputmethod/ImeTracker.java b/core/java/android/view/inputmethod/ImeTracker.java
index 2ca62a0..dd32d57 100644
--- a/core/java/android/view/inputmethod/ImeTracker.java
+++ b/core/java/android/view/inputmethod/ImeTracker.java
@@ -221,6 +221,7 @@
PHASE_WM_INVOKING_IME_REQUESTED_LISTENER,
PHASE_CLIENT_ALREADY_HIDDEN,
PHASE_CLIENT_VIEW_HANDLER_AVAILABLE,
+ PHASE_SERVER_UPDATE_CLIENT_VISIBILITY,
})
@Retention(RetentionPolicy.SOURCE)
@interface Phase {}
@@ -430,6 +431,11 @@
* continue without.
*/
int PHASE_CLIENT_VIEW_HANDLER_AVAILABLE = ImeProtoEnums.PHASE_CLIENT_VIEW_HANDLER_AVAILABLE;
+ /**
+ * ImeInsetsSourceProvider sets the reported visibility of the caller/client window (either the
+ * app or the RemoteInsetsControlTarget).
+ */
+ int PHASE_SERVER_UPDATE_CLIENT_VISIBILITY = ImeProtoEnums.PHASE_SERVER_UPDATE_CLIENT_VISIBILITY;
/**
* Called when an IME request is started.
diff --git a/core/java/android/window/flags/responsible_apis.aconfig b/core/java/android/window/flags/responsible_apis.aconfig
index fd5de91..b2f125d 100644
--- a/core/java/android/window/flags/responsible_apis.aconfig
+++ b/core/java/android/window/flags/responsible_apis.aconfig
@@ -16,13 +16,6 @@
}
flag {
- name: "bal_show_toasts"
- namespace: "responsible_apis"
- description: "Enable toasts to indicate (potential) BAL blocking."
- bug: "308059069"
-}
-
-flag {
name: "bal_show_toasts_blocked"
namespace: "responsible_apis"
description: "Enable toasts to indicate actual BAL blocking."
@@ -64,14 +57,6 @@
bug: "339720406"
}
-# replaced by bal_strict_mode_ro
-flag {
- name: "bal_strict_mode"
- namespace: "responsible_apis"
- description: "Strict mode flag"
- bug: "324089586"
-}
-
flag {
name: "bal_strict_mode_ro"
namespace: "responsible_apis"
diff --git a/core/java/com/android/internal/ravenwood/RavenwoodEnvironment.java b/core/java/com/android/internal/ravenwood/RavenwoodEnvironment.java
index 30b160a..a69d2e4 100644
--- a/core/java/com/android/internal/ravenwood/RavenwoodEnvironment.java
+++ b/core/java/com/android/internal/ravenwood/RavenwoodEnvironment.java
@@ -28,19 +28,9 @@
public final class RavenwoodEnvironment {
public static final String TAG = "RavenwoodEnvironment";
- private static final RavenwoodEnvironment sInstance;
- private static final Workaround sWorkaround;
+ private static RavenwoodEnvironment sInstance = new RavenwoodEnvironment();
- private RavenwoodEnvironment() {
- }
-
- static {
- sInstance = new RavenwoodEnvironment();
- sWorkaround = new Workaround();
- ensureRavenwoodInitialized();
- }
-
- public static RuntimeException notSupportedOnDevice() {
+ private static RuntimeException notSupportedOnDevice() {
return new UnsupportedOperationException("This method can only be used on Ravenwood");
}
@@ -52,15 +42,6 @@
}
/**
- * Initialize the ravenwood environment if it hasn't happened already, if running on Ravenwood.
- *
- * No-op if called on the device side.
- */
- @RavenwoodRedirect
- public static void ensureRavenwoodInitialized() {
- }
-
- /**
* USE IT SPARINGLY! Returns true if it's running on Ravenwood, hostside test environment.
*
* <p>Using this allows code to behave differently on a real device and on Ravenwood, but
@@ -91,38 +72,10 @@
}
/**
- * See {@link Workaround}. It's only usable on Ravenwood.
- */
- @RavenwoodReplace
- public static Workaround workaround() {
- throw notSupportedOnDevice();
- }
-
- private static Workaround workaround$ravenwood() {
- return sWorkaround;
- }
-
- /**
* @return the "ravenwood-runtime" directory.
*/
@RavenwoodRedirect
public String getRavenwoodRuntimePath() {
throw notSupportedOnDevice();
}
-
- /**
- * A set of APIs used to work around missing features on Ravenwood. Ideally, this class should
- * be empty, and all its APIs should be able to be implemented properly.
- */
- public static class Workaround {
- Workaround() {
- }
-
- /**
- * @return whether the app's target SDK level is at least Q.
- */
- public boolean isTargetSdkAtLeastQ() {
- return true;
- }
- }
}
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 5f0eed9..14f8cc7 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
@@ -1632,6 +1632,7 @@
if (!isShowingAsBubbleBar()) {
callback = b -> {
if (mStackView != null) {
+ b.setSuppressFlyout(true);
mStackView.addBubble(b);
mStackView.setSelectedBubble(b);
} else {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index 75f8839..83ad14b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -554,19 +554,6 @@
}
}
- /** Move a desktop app to split screen. */
- fun moveToSplit(task: RunningTaskInfo) {
- logV( "moveToSplit taskId=%s", task.taskId)
- desktopTilingDecorViewModel.removeTaskIfTiled(task.displayId, task.taskId)
- val wct = WindowContainerTransaction()
- wct.setBounds(task.token, Rect())
- // Rather than set windowing mode to multi-window at task level, set it to
- // undefined and inherit from split stage.
- wct.setWindowingMode(task.token, WINDOWING_MODE_UNDEFINED)
-
- transitions.startTransition(TRANSIT_CHANGE, wct, null /* handler */)
- }
-
private fun exitSplitIfApplicable(wct: WindowContainerTransaction, taskInfo: RunningTaskInfo) {
if (splitScreenController.isTaskInSplitScreen(taskInfo.taskId)) {
splitScreenController.prepareExitSplitScreen(
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipEnterAnimator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipEnterAnimator.java
index e513758..eb33ff4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipEnterAnimator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipEnterAnimator.java
@@ -20,6 +20,7 @@
import static android.view.Surface.ROTATION_90;
import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
import android.animation.RectEvaluator;
import android.animation.ValueAnimator;
import android.content.Context;
@@ -34,6 +35,7 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.launcher3.icons.IconProvider;
import com.android.wm.shell.R;
import com.android.wm.shell.common.pip.PipUtils;
@@ -45,8 +47,7 @@
/**
* Animator that handles bounds animations for entering PIP.
*/
-public class PipEnterAnimator extends ValueAnimator
- implements ValueAnimator.AnimatorUpdateListener, ValueAnimator.AnimatorListener {
+public class PipEnterAnimator extends ValueAnimator {
@NonNull private final SurfaceControl mLeash;
private final SurfaceControl.Transaction mStartTransaction;
private final SurfaceControl.Transaction mFinishTransaction;
@@ -56,49 +57,82 @@
private final RectEvaluator mRectEvaluator;
private final Rect mEndBounds = new Rect();
- @Nullable private final Rect mSourceRectHint;
private final @Surface.Rotation int mRotation;
@Nullable private Runnable mAnimationStartCallback;
@Nullable private Runnable mAnimationEndCallback;
- private final PipSurfaceTransactionHelper.SurfaceControlTransactionFactory
+ private PipSurfaceTransactionHelper.SurfaceControlTransactionFactory
mSurfaceControlTransactionFactory;
Matrix mTransformTensor = new Matrix();
final float[] mMatrixTmp = new float[9];
@Nullable private PipContentOverlay mContentOverlay;
+ private PipAppIconOverlaySupplier mPipAppIconOverlaySupplier;
// Internal state representing initial transform - cached to avoid recalculation.
private final PointF mInitScale = new PointF();
private final PointF mInitPos = new PointF();
private final Rect mInitCrop = new Rect();
- private final PointF mInitActivityScale = new PointF();
- private final PointF mInitActivityPos = new PointF();
+
+ private final Animator.AnimatorListener mAnimatorListener = new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ super.onAnimationStart(animation);
+ if (mAnimationStartCallback != null) {
+ mAnimationStartCallback.run();
+ }
+ if (mStartTransaction != null) {
+ onEnterAnimationUpdate(0f /* fraction */, mStartTransaction);
+ mStartTransaction.apply();
+ }
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ super.onAnimationEnd(animation);
+ if (mFinishTransaction != null) {
+ onEnterAnimationUpdate(1f /* fraction */, mFinishTransaction);
+ }
+ if (mAnimationEndCallback != null) {
+ mAnimationEndCallback.run();
+ }
+ }
+ };
+
+ private final AnimatorUpdateListener mAnimatorUpdateListener = new AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(@NonNull ValueAnimator animation) {
+ final SurfaceControl.Transaction tx =
+ mSurfaceControlTransactionFactory.getTransaction();
+ final float fraction = getAnimatedFraction();
+ onEnterAnimationUpdate(fraction, tx);
+ tx.apply();
+ }
+ };
public PipEnterAnimator(Context context,
@NonNull SurfaceControl leash,
SurfaceControl.Transaction startTransaction,
SurfaceControl.Transaction finishTransaction,
@NonNull Rect endBounds,
- @Nullable Rect sourceRectHint,
@Surface.Rotation int rotation) {
mLeash = leash;
mStartTransaction = startTransaction;
mFinishTransaction = finishTransaction;
mRectEvaluator = new RectEvaluator(mAnimatedRect);
mEndBounds.set(endBounds);
- mSourceRectHint = sourceRectHint != null ? new Rect(sourceRectHint) : null;
mRotation = rotation;
mSurfaceControlTransactionFactory =
new PipSurfaceTransactionHelper.VsyncSurfaceControlTransactionFactory();
+ mPipAppIconOverlaySupplier = this::getAppIconOverlay;
final int enterAnimationDuration = context.getResources()
.getInteger(R.integer.config_pipEnterAnimationDuration);
setDuration(enterAnimationDuration);
setFloatValues(0f, 1f);
setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
- addListener(this);
- addUpdateListener(this);
+ addListener(mAnimatorListener);
+ addUpdateListener(mAnimatorUpdateListener);
}
public void setAnimationStartCallback(@NonNull Runnable runnable) {
@@ -109,35 +143,6 @@
mAnimationEndCallback = runnable;
}
- @Override
- public void onAnimationStart(@NonNull Animator animation) {
- if (mAnimationStartCallback != null) {
- mAnimationStartCallback.run();
- }
- if (mStartTransaction != null) {
- onEnterAnimationUpdate(0f /* fraction */, mStartTransaction);
- mStartTransaction.apply();
- }
- }
-
- @Override
- public void onAnimationEnd(@NonNull Animator animation) {
- if (mFinishTransaction != null) {
- onEnterAnimationUpdate(1f /* fraction */, mFinishTransaction);
- }
- if (mAnimationEndCallback != null) {
- mAnimationEndCallback.run();
- }
- }
-
- @Override
- public void onAnimationUpdate(@NonNull ValueAnimator animation) {
- final SurfaceControl.Transaction tx = mSurfaceControlTransactionFactory.getTransaction();
- final float fraction = getAnimatedFraction();
- onEnterAnimationUpdate(fraction, tx);
- tx.apply();
- }
-
/**
* Updates the transaction to reflect the state of PiP leash at a certain fraction during enter.
*
@@ -177,14 +182,6 @@
}
}
- // no-ops
-
- @Override
- public void onAnimationCancel(@NonNull Animator animation) {}
-
- @Override
- public void onAnimationRepeat(@NonNull Animator animation) {}
-
/**
* Caches the initial transform relevant values for the bounds enter animation.
*
@@ -201,18 +198,13 @@
*/
public void setAppIconContentOverlay(Context context, Rect appBounds, Rect destinationBounds,
ActivityInfo activityInfo, int appIconSizePx) {
- reattachAppIconOverlay(
- new PipAppIconOverlay(context, appBounds, destinationBounds,
- new IconProvider(context).getIcon(activityInfo), appIconSizePx));
- }
-
- private void reattachAppIconOverlay(PipAppIconOverlay overlay) {
final SurfaceControl.Transaction tx =
mSurfaceControlTransactionFactory.getTransaction();
if (mContentOverlay != null) {
mContentOverlay.detach(tx);
}
- mContentOverlay = overlay;
+ mContentOverlay = mPipAppIconOverlaySupplier.get(context, appBounds, destinationBounds,
+ activityInfo, appIconSizePx);
mContentOverlay.attach(tx, mLeash);
}
@@ -229,6 +221,13 @@
mContentOverlay = null;
}
+ private PipAppIconOverlay getAppIconOverlay(
+ Context context, Rect appBounds, Rect destinationBounds,
+ ActivityInfo activityInfo, int iconSize) {
+ return new PipAppIconOverlay(context, appBounds, destinationBounds,
+ new IconProvider(context).getIcon(activityInfo), iconSize);
+ }
+
/**
* @return the app icon overlay leash; null if no overlay is attached.
*/
@@ -239,4 +238,21 @@
}
return mContentOverlay.getLeash();
}
+
+ @VisibleForTesting
+ void setSurfaceControlTransactionFactory(
+ @NonNull PipSurfaceTransactionHelper.SurfaceControlTransactionFactory factory) {
+ mSurfaceControlTransactionFactory = factory;
+ }
+
+ @VisibleForTesting
+ interface PipAppIconOverlaySupplier {
+ PipAppIconOverlay get(Context context, Rect appBounds, Rect destinationBounds,
+ ActivityInfo activityInfo, int iconSize);
+ }
+
+ @VisibleForTesting
+ void setPipAppIconOverlaySupplier(@NonNull PipAppIconOverlaySupplier supplier) {
+ mPipAppIconOverlaySupplier = supplier;
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
index 64d8887..6bf92f6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
@@ -352,17 +352,11 @@
handleBoundsTypeFixedRotation(pipChange, pipActivityChange, endRotation);
}
- Rect sourceRectHint = null;
- if (pipChange.getTaskInfo() != null
- && pipChange.getTaskInfo().pictureInPictureParams != null) {
- sourceRectHint = pipChange.getTaskInfo().pictureInPictureParams.getSourceRectHint();
- }
-
prepareConfigAtEndActivity(startTransaction, finishTransaction, pipChange,
pipActivityChange);
startTransaction.merge(finishTransaction);
PipEnterAnimator animator = new PipEnterAnimator(mContext, pipLeash,
- startTransaction, finishTransaction, destinationBounds, sourceRectHint, delta);
+ startTransaction, finishTransaction, destinationBounds, delta);
animator.setEnterStartState(pipChange);
animator.onEnterAnimationUpdate(1.0f /* fraction */, startTransaction);
startTransaction.apply();
@@ -433,7 +427,7 @@
}
PipEnterAnimator animator = new PipEnterAnimator(mContext, pipLeash,
- startTransaction, finishTransaction, endBounds, adjustedSourceRectHint, delta);
+ startTransaction, finishTransaction, endBounds, delta);
if (sourceRectHint == null) {
// update the src-rect-hint in params in place, to set up initial animator transform.
params.getSourceRectHint().set(adjustedSourceRectHint);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index e1683f3..4d4bd53 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -41,7 +41,6 @@
import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
-import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED;
import android.annotation.NonNull;
import android.app.ActivityManager;
@@ -122,8 +121,6 @@
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource;
import com.android.wm.shell.shared.split.SplitScreenConstants.SplitPosition;
-import com.android.wm.shell.splitscreen.SplitScreen;
-import com.android.wm.shell.splitscreen.SplitScreen.StageType;
import com.android.wm.shell.splitscreen.SplitScreenController;
import com.android.wm.shell.sysui.KeyguardChangeListener;
import com.android.wm.shell.sysui.ShellCommandHandler;
@@ -446,17 +443,6 @@
@Override
public void setSplitScreenController(SplitScreenController splitScreenController) {
mSplitScreenController = splitScreenController;
- mSplitScreenController.registerSplitScreenListener(new SplitScreen.SplitScreenListener() {
- @Override
- public void onTaskStageChanged(int taskId, @StageType int stage, boolean visible) {
- if (visible && stage != STAGE_TYPE_UNDEFINED) {
- DesktopModeWindowDecoration decor = mWindowDecorByTaskId.get(taskId);
- if (decor != null && DesktopModeStatus.canEnterDesktopMode(mContext)) {
- mDesktopTasksController.moveToSplit(decor.mTaskInfo);
- }
- }
- }
- });
}
@Override
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipEnterAnimatorTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipEnterAnimatorTest.java
new file mode 100644
index 0000000..a4008c1
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipEnterAnimatorTest.java
@@ -0,0 +1,201 @@
+/*
+ * 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.wm.shell.pip2.animation;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyFloat;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.pm.ActivityInfo;
+import android.content.res.Resources;
+import android.graphics.Matrix;
+import android.graphics.Rect;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.view.Surface;
+import android.view.SurfaceControl;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.wm.shell.pip2.PipSurfaceTransactionHelper;
+import com.android.wm.shell.pip2.phone.PipAppIconOverlay;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Unit test again {@link PipEnterAnimator}.
+ */
+@SmallTest
+@TestableLooper.RunWithLooper
+@RunWith(AndroidTestingRunner.class)
+public class PipEnterAnimatorTest {
+
+ @Mock private Context mMockContext;
+
+ @Mock private Resources mMockResources;
+
+ @Mock private PipSurfaceTransactionHelper.SurfaceControlTransactionFactory mMockFactory;
+
+ @Mock private SurfaceControl.Transaction mMockAnimateTransaction;
+
+ @Mock private SurfaceControl.Transaction mMockStartTransaction;
+
+ @Mock private SurfaceControl.Transaction mMockFinishTransaction;
+
+ @Mock private Runnable mMockStartCallback;
+
+ @Mock private Runnable mMockEndCallback;
+
+ @Mock private PipAppIconOverlay mMockPipAppIconOverlay;
+
+ @Mock private SurfaceControl mMockAppIconOverlayLeash;
+
+ @Mock private ActivityInfo mMockActivityInfo;
+
+ @Surface.Rotation private int mRotation;
+ private SurfaceControl mTestLeash;
+ private Rect mEndBounds;
+ private PipEnterAnimator mPipEnterAnimator;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ when(mMockContext.getResources()).thenReturn(mMockResources);
+ when(mMockResources.getInteger(anyInt())).thenReturn(0);
+ when(mMockFactory.getTransaction()).thenReturn(mMockAnimateTransaction);
+ when(mMockAnimateTransaction.setMatrix(any(SurfaceControl.class), any(Matrix.class), any()))
+ .thenReturn(mMockAnimateTransaction);
+ when(mMockStartTransaction.setMatrix(any(SurfaceControl.class), any(Matrix.class), any()))
+ .thenReturn(mMockStartTransaction);
+ when(mMockFinishTransaction.setMatrix(any(SurfaceControl.class), any(Matrix.class), any()))
+ .thenReturn(mMockFinishTransaction);
+ when(mMockPipAppIconOverlay.getLeash()).thenReturn(mMockAppIconOverlayLeash);
+
+ mTestLeash = new SurfaceControl.Builder()
+ .setContainerLayer()
+ .setName("PipExpandAnimatorTest")
+ .setCallsite("PipExpandAnimatorTest")
+ .build();
+ }
+
+ @Test
+ public void setAnimationStartCallback_enter_callbackStartCallback() {
+ mRotation = Surface.ROTATION_0;
+ mEndBounds = new Rect(100, 100, 500, 500);
+ mPipEnterAnimator = new PipEnterAnimator(mMockContext, mTestLeash,
+ mMockStartTransaction, mMockFinishTransaction,
+ mEndBounds, mRotation);
+ mPipEnterAnimator.setSurfaceControlTransactionFactory(mMockFactory);
+
+ mPipEnterAnimator.setAnimationStartCallback(mMockStartCallback);
+ mPipEnterAnimator.setAnimationEndCallback(mMockEndCallback);
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+ mPipEnterAnimator.start();
+ mPipEnterAnimator.pause();
+ });
+
+ verify(mMockStartCallback).run();
+ verifyZeroInteractions(mMockEndCallback);
+ }
+
+ @Test
+ public void setAnimationEndCallback_enter_callbackStartAndEndCallback() {
+ mRotation = Surface.ROTATION_0;
+ mEndBounds = new Rect(100, 100, 500, 500);
+ mPipEnterAnimator = new PipEnterAnimator(mMockContext, mTestLeash,
+ mMockStartTransaction, mMockFinishTransaction,
+ mEndBounds, mRotation);
+ mPipEnterAnimator.setSurfaceControlTransactionFactory(mMockFactory);
+
+ mPipEnterAnimator.setAnimationStartCallback(mMockStartCallback);
+ mPipEnterAnimator.setAnimationEndCallback(mMockEndCallback);
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+ mPipEnterAnimator.start();
+ mPipEnterAnimator.end();
+ });
+
+ verify(mMockStartCallback).run();
+ verify(mMockEndCallback).run();
+ }
+
+ @Test
+ public void setAppIconContentOverlay_thenGetContentOverlayLeash_returnOverlayLeash() {
+ mRotation = Surface.ROTATION_0;
+ mEndBounds = new Rect(100, 100, 500, 500);
+ mPipEnterAnimator = new PipEnterAnimator(mMockContext, mTestLeash,
+ mMockStartTransaction, mMockFinishTransaction,
+ mEndBounds, mRotation);
+ mPipEnterAnimator.setSurfaceControlTransactionFactory(mMockFactory);
+ mPipEnterAnimator.setPipAppIconOverlaySupplier(
+ (context, appBounds, endBounds, icon, iconSize) -> mMockPipAppIconOverlay);
+
+ mPipEnterAnimator.setAppIconContentOverlay(mMockContext, mEndBounds, mEndBounds,
+ mMockActivityInfo, 64 /* iconSize */);
+
+ assertEquals(mPipEnterAnimator.getContentOverlayLeash(), mMockAppIconOverlayLeash);
+ }
+
+ @Test
+ public void setAppIconContentOverlay_thenClearAppIconOverlay_returnNullLeash() {
+ mRotation = Surface.ROTATION_0;
+ mEndBounds = new Rect(100, 100, 500, 500);
+ mPipEnterAnimator = new PipEnterAnimator(mMockContext, mTestLeash,
+ mMockStartTransaction, mMockFinishTransaction,
+ mEndBounds, mRotation);
+ mPipEnterAnimator.setSurfaceControlTransactionFactory(mMockFactory);
+ mPipEnterAnimator.setPipAppIconOverlaySupplier(
+ (context, appBounds, endBounds, icon, iconSize) -> mMockPipAppIconOverlay);
+
+ mPipEnterAnimator.setAppIconContentOverlay(mMockContext, mEndBounds, mEndBounds,
+ mMockActivityInfo, 64 /* iconSize */);
+ mPipEnterAnimator.clearAppIconOverlay();
+
+ assertNull(mPipEnterAnimator.getContentOverlayLeash());
+ }
+
+ @Test
+ public void onEnterAnimationUpdate_withContentOverlay_animateOverlay() {
+ mRotation = Surface.ROTATION_0;
+ mEndBounds = new Rect(100, 100, 500, 500);
+ mPipEnterAnimator = new PipEnterAnimator(mMockContext, mTestLeash,
+ mMockStartTransaction, mMockFinishTransaction,
+ mEndBounds, mRotation);
+ mPipEnterAnimator.setSurfaceControlTransactionFactory(mMockFactory);
+ mPipEnterAnimator.setPipAppIconOverlaySupplier(
+ (context, appBounds, endBounds, icon, iconSize) -> mMockPipAppIconOverlay);
+
+ float fraction = 0.5f;
+ mPipEnterAnimator.setAppIconContentOverlay(mMockContext, mEndBounds, mEndBounds,
+ mMockActivityInfo, 64 /* iconSize */);
+ mPipEnterAnimator.onEnterAnimationUpdate(fraction, mMockAnimateTransaction);
+
+ verify(mMockPipAppIconOverlay).onAnimationUpdate(
+ eq(mMockAnimateTransaction), anyFloat(), eq(fraction), eq(mEndBounds));
+ }
+}
diff --git a/media/java/android/mtp/OWNERS b/media/java/android/mtp/OWNERS
index 6b5336e..77ed08b 100644
--- a/media/java/android/mtp/OWNERS
+++ b/media/java/android/mtp/OWNERS
@@ -1,10 +1,9 @@
set noparent
-aprasath@google.com
anothermark@google.com
-kumarashishg@google.com
-sarup@google.com
+febinthattil@google.com
+aprasath@google.com
jsharkey@android.com
jameswei@google.com
rmojumder@google.com
-
+kumarashishg@google.com
diff --git a/media/tests/MtpTests/OWNERS b/media/tests/MtpTests/OWNERS
index 6b5336e..bdb6cdb 100644
--- a/media/tests/MtpTests/OWNERS
+++ b/media/tests/MtpTests/OWNERS
@@ -1,10 +1,9 @@
set noparent
-aprasath@google.com
anothermark@google.com
-kumarashishg@google.com
-sarup@google.com
+febinthattil@google.com
+aprasath@google.com
jsharkey@android.com
jameswei@google.com
rmojumder@google.com
-
+kumarashishg@google.com
\ No newline at end of file
diff --git a/packages/SettingsLib/Graph/graph.proto b/packages/SettingsLib/Graph/graph.proto
index cbe602e..6b93cd7 100644
--- a/packages/SettingsLib/Graph/graph.proto
+++ b/packages/SettingsLib/Graph/graph.proto
@@ -77,6 +77,8 @@
// Intent to show and locate the preference (might have highlight animation on
// the preference).
optional IntentProto launch_intent = 14;
+ // Descriptor of the preference value.
+ optional PreferenceValueDescriptorProto value_descriptor = 15;
// Target of an Intent
message ActionTarget {
@@ -103,9 +105,28 @@
message PreferenceValueProto {
oneof value {
bool boolean_value = 1;
+ int32 int_value = 2;
}
}
+// Proto of preference value descriptor.
+message PreferenceValueDescriptorProto {
+ oneof type {
+ bool boolean_type = 1;
+ RangeValueProto range_value = 2;
+ }
+}
+
+// Proto of preference value that is between a range.
+message RangeValueProto {
+ // The lower bound (inclusive) of the range.
+ optional int32 min = 1;
+ // The upper bound (inclusive) of the range.
+ optional int32 max = 2;
+ // The increment step within the range. 0 means unset, which implies step size is 1.
+ optional int32 step = 3;
+}
+
// Proto of android.content.Intent
message IntentProto {
// The action of the Intent.
diff --git a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/GetPreferenceGraphApiHandler.kt b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/GetPreferenceGraphApiHandler.kt
index fdffe5d..5ceee6d 100644
--- a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/GetPreferenceGraphApiHandler.kt
+++ b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/GetPreferenceGraphApiHandler.kt
@@ -65,6 +65,7 @@
val visitedScreens: Set<String> = setOf(),
val locale: Locale? = null,
val includeValue: Boolean = true,
+ val includeValueDescriptor: Boolean = true,
)
object GetPreferenceGraphRequestCodec : MessageCodec<GetPreferenceGraphRequest> {
diff --git a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGraphBuilder.kt b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGraphBuilder.kt
index cf6bf70..2256bb3 100644
--- a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGraphBuilder.kt
+++ b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGraphBuilder.kt
@@ -49,6 +49,7 @@
import com.android.settingslib.metadata.PreferenceScreenRegistry
import com.android.settingslib.metadata.PreferenceSummaryProvider
import com.android.settingslib.metadata.PreferenceTitleProvider
+import com.android.settingslib.metadata.RangeValue
import com.android.settingslib.preference.PreferenceScreenFactory
import com.android.settingslib.preference.PreferenceScreenProvider
import java.util.Locale
@@ -66,6 +67,7 @@
private val builder by lazy { PreferenceGraphProto.newBuilder() }
private val visitedScreens = mutableSetOf<String>().apply { addAll(request.visitedScreens) }
private val includeValue = request.includeValue
+ private val includeValueDescriptor = request.includeValueDescriptor
private suspend fun init() {
for (key in request.screenKeys) {
@@ -284,14 +286,37 @@
restricted = metadata.isRestricted(context)
}
persistent = metadata.isPersistent(context)
- if (
- includeValue &&
- persistent &&
- metadata is BooleanValue &&
- metadata is PersistentPreference<*>
- ) {
- metadata.storage(context).getValue(metadata.key, Boolean::class.javaObjectType)?.let {
- value = preferenceValueProto { booleanValue = it }
+ if (persistent) {
+ if (includeValue && metadata is PersistentPreference<*>) {
+ value = preferenceValueProto {
+ when (metadata) {
+ is BooleanValue ->
+ metadata
+ .storage(context)
+ .getValue(metadata.key, Boolean::class.javaObjectType)
+ ?.let { booleanValue = it }
+ is RangeValue -> {
+ metadata
+ .storage(context)
+ .getValue(metadata.key, Int::class.javaObjectType)
+ ?.let { intValue = it }
+ }
+ else -> {}
+ }
+ }
+ }
+ if (includeValueDescriptor) {
+ valueDescriptor = preferenceValueDescriptorProto {
+ when (metadata) {
+ is BooleanValue -> booleanType = true
+ is RangeValue -> rangeValue = rangeValueProto {
+ min = metadata.getMinValue(context)
+ max = metadata.getMaxValue(context)
+ step = metadata.getIncrementStep(context)
+ }
+ else -> {}
+ }
+ }
}
}
if (metadata is PreferenceScreenMetadata) {
diff --git a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceSetterApi.kt b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceSetterApi.kt
index d8db1bb..6e4db1d 100644
--- a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceSetterApi.kt
+++ b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceSetterApi.kt
@@ -27,8 +27,10 @@
import com.android.settingslib.metadata.BooleanValue
import com.android.settingslib.metadata.PersistentPreference
import com.android.settingslib.metadata.PreferenceAvailabilityProvider
+import com.android.settingslib.metadata.PreferenceMetadata
import com.android.settingslib.metadata.PreferenceRestrictionProvider
import com.android.settingslib.metadata.PreferenceScreenRegistry
+import com.android.settingslib.metadata.RangeValue
import com.android.settingslib.metadata.ReadWritePermit
/** Request to set preference value. */
@@ -114,27 +116,39 @@
if (metadata is PreferenceAvailabilityProvider && !metadata.isAvailable(application)) {
return PreferenceSetterResult.UNAVAILABLE
}
+
+ fun <T> PreferenceMetadata.checkWritePermit(value: T): Int {
+ @Suppress("UNCHECKED_CAST") val preference = (this as PersistentPreference<T>)
+ return when (preference.getWritePermit(application, value, myUid, callingUid)) {
+ ReadWritePermit.ALLOW -> PreferenceSetterResult.OK
+ ReadWritePermit.DISALLOW -> PreferenceSetterResult.DISALLOW
+ ReadWritePermit.REQUIRE_APP_PERMISSION ->
+ PreferenceSetterResult.REQUIRE_APP_PERMISSION
+ ReadWritePermit.REQUIRE_USER_AGREEMENT ->
+ PreferenceSetterResult.REQUIRE_USER_AGREEMENT
+ else -> PreferenceSetterResult.INTERNAL_ERROR
+ }
+ }
+
val storage = metadata.storage(application)
val value = request.value
try {
if (value.hasBooleanValue()) {
if (metadata !is BooleanValue) return PreferenceSetterResult.INVALID_REQUEST
val booleanValue = value.booleanValue
- @Suppress("UNCHECKED_CAST")
- val booleanPreference = metadata as PersistentPreference<Boolean>
- when (
- booleanPreference.getWritePermit(application, booleanValue, myUid, callingUid)
- ) {
- ReadWritePermit.ALLOW -> {}
- ReadWritePermit.DISALLOW -> return PreferenceSetterResult.DISALLOW
- ReadWritePermit.REQUIRE_APP_PERMISSION ->
- return PreferenceSetterResult.REQUIRE_APP_PERMISSION
- ReadWritePermit.REQUIRE_USER_AGREEMENT ->
- return PreferenceSetterResult.REQUIRE_USER_AGREEMENT
- else -> return PreferenceSetterResult.INTERNAL_ERROR
- }
+ val resultCode = metadata.checkWritePermit(booleanValue)
+ if (resultCode != PreferenceSetterResult.OK) return resultCode
storage.setValue(key, Boolean::class.javaObjectType, booleanValue)
return PreferenceSetterResult.OK
+ } else if (value.hasIntValue()) {
+ val intValue = value.intValue
+ val resultCode = metadata.checkWritePermit(intValue)
+ if (resultCode != PreferenceSetterResult.OK) return resultCode
+ if (metadata is RangeValue && !metadata.isValidValue(application, intValue)) {
+ return PreferenceSetterResult.INVALID_REQUEST
+ }
+ storage.setValue(key, Int::class.javaObjectType, intValue)
+ return PreferenceSetterResult.OK
}
} catch (e: Exception) {
return PreferenceSetterResult.INTERNAL_ERROR
diff --git a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/ProtoDsl.kt b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/ProtoDsl.kt
index d7dae77..dee32d9 100644
--- a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/ProtoDsl.kt
+++ b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/ProtoDsl.kt
@@ -24,7 +24,9 @@
import com.android.settingslib.graph.proto.PreferenceProto
import com.android.settingslib.graph.proto.PreferenceProto.ActionTarget
import com.android.settingslib.graph.proto.PreferenceScreenProto
+import com.android.settingslib.graph.proto.PreferenceValueDescriptorProto
import com.android.settingslib.graph.proto.PreferenceValueProto
+import com.android.settingslib.graph.proto.RangeValueProto
import com.android.settingslib.graph.proto.TextProto
/** Returns root or null. */
@@ -89,6 +91,16 @@
inline fun preferenceValueProto(init: PreferenceValueProto.Builder.() -> Unit) =
PreferenceValueProto.newBuilder().also(init).build()
+/** Kotlin DSL-style builder for [PreferenceValueDescriptorProto]. */
+@JvmSynthetic
+inline fun preferenceValueDescriptorProto(init: PreferenceValueDescriptorProto.Builder.() -> Unit) =
+ PreferenceValueDescriptorProto.newBuilder().also(init).build()
+
+/** Kotlin DSL-style builder for [RangeValueProto]. */
+@JvmSynthetic
+inline fun rangeValueProto(init: RangeValueProto.Builder.() -> Unit) =
+ RangeValueProto.newBuilder().also(init).build()
+
/** Kotlin DSL-style builder for [TextProto]. */
@JvmSynthetic
inline fun textProto(init: TextProto.Builder.() -> Unit) = TextProto.newBuilder().also(init).build()
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index a18b6c1..bffda8b 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -536,6 +536,8 @@
"androidx.room_room-runtime",
"androidx.room_room-ktx",
"androidx.datastore_datastore-preferences",
+ "androidx.media3.media3-common",
+ "androidx.media3.media3-session",
"com.google.android.material_material",
"device_state_flags_lib",
"kotlinx_coroutines_android",
@@ -703,6 +705,8 @@
"androidx.room_room-testing",
"androidx.room_room-ktx",
"androidx.datastore_datastore-preferences",
+ "androidx.media3.media3-common",
+ "androidx.media3.media3-session",
"device_state_flags_lib",
"kotlinx-coroutines-android",
"kotlinx-coroutines-core",
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 5c5edb1..b0bd5b7 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -1708,3 +1708,9 @@
bug: "365064144"
}
+flag {
+ name: "touchpad_three_finger_tap_customization"
+ namespace: "systemui"
+ description: "Customize touchpad three finger tap"
+ bug: "365063048"
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt
index 6fc51e4..e78862e 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt
@@ -53,6 +53,7 @@
import com.android.systemui.notifications.ui.composable.SnoozeableHeadsUpNotificationSpace
import com.android.systemui.res.R
import com.android.systemui.shade.LargeScreenHeaderHelper
+import com.android.systemui.shade.ShadeDisplayAware
import com.android.systemui.statusbar.notification.icon.ui.viewbinder.AlwaysOnDisplayNotificationIconViewStore
import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerViewBinder
import com.android.systemui.statusbar.notification.icon.ui.viewbinder.StatusBarIconViewBindingFailureTracker
@@ -84,7 +85,7 @@
stackScrollLayout: NotificationStackScrollLayout,
sharedNotificationContainerBinder: SharedNotificationContainerBinder,
private val keyguardRootViewModel: KeyguardRootViewModel,
- private val configurationState: ConfigurationState,
+ @ShadeDisplayAware private val configurationState: ConfigurationState,
private val iconBindingFailureTracker: StatusBarIconViewBindingFailureTracker,
private val nicAodViewModel: NotificationIconContainerAlwaysOnDisplayViewModel,
private val nicAodIconViewStore: AlwaysOnDisplayNotificationIconViewStore,
@@ -127,7 +128,7 @@
}
val burnIn = rememberBurnIn(clockInteractor)
AnimatedVisibility(
- visibleState = transitionState,
+ visibleState = transitionState,
enter = fadeIn(),
exit = fadeOut(),
modifier =
@@ -150,7 +151,7 @@
)
}
}
- },
+ }
)
}
}
@@ -172,7 +173,7 @@
areNotificationsVisible: Boolean,
isShadeLayoutWide: Boolean,
burnInParams: BurnInParameters?,
- modifier: Modifier = Modifier
+ modifier: Modifier = Modifier,
) {
if (!areNotificationsVisible) {
return
@@ -192,10 +193,7 @@
if (burnInParams == null) {
it
} else {
- it.burnInAware(
- viewModel = aodBurnInViewModel,
- params = burnInParams,
- )
+ it.burnInAware(viewModel = aodBurnInViewModel, params = burnInParams)
}
},
)
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeOverlay.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeOverlay.kt
index 2a91bd8..26c827a 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeOverlay.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeOverlay.kt
@@ -43,6 +43,7 @@
import com.android.systemui.compose.modifiers.sysuiResTag
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.lifecycle.rememberViewModel
+import com.android.systemui.notifications.ui.composable.SnoozeableHeadsUpNotificationSpace
import com.android.systemui.qs.composefragment.ui.GridAnchor
import com.android.systemui.qs.panels.ui.compose.EditMode
import com.android.systemui.qs.panels.ui.compose.TileGrid
@@ -53,8 +54,11 @@
import com.android.systemui.scene.ui.composable.Overlay
import com.android.systemui.shade.ui.composable.ExpandedShadeHeader
import com.android.systemui.shade.ui.composable.OverlayShade
+import com.android.systemui.statusbar.notification.stack.ui.view.NotificationScrollView
+import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
import com.android.systemui.statusbar.phone.ui.StatusBarIconController
import com.android.systemui.statusbar.phone.ui.TintedIconManager
+import dagger.Lazy
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
@@ -67,6 +71,8 @@
private val tintedIconManagerFactory: TintedIconManager.Factory,
private val batteryMeterViewControllerFactory: BatteryMeterViewController.Factory,
private val statusBarIconController: StatusBarIconController,
+ private val notificationStackScrollView: Lazy<NotificationScrollView>,
+ private val notificationsPlaceholderViewModelFactory: NotificationsPlaceholderViewModel.Factory,
) : Overlay {
override val key = Overlays.QuickSettingsShade
@@ -98,6 +104,14 @@
ShadeBody(viewModel = viewModel.quickSettingsContainerViewModel)
}
+
+ SnoozeableHeadsUpNotificationSpace(
+ stackScrollView = notificationStackScrollView.get(),
+ viewModel =
+ rememberViewModel("QuickSettingsShadeOverlay") {
+ notificationsPlaceholderViewModelFactory.create()
+ },
+ )
}
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/OnTeardownRuleTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/OnTeardownRuleTest.kt
new file mode 100644
index 0000000..8635bb0
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/OnTeardownRuleTest.kt
@@ -0,0 +1,124 @@
+/*
+ * 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
+
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Assert.fail
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.JUnitCore
+
+@Suppress("JUnitMalformedDeclaration")
+@SmallTest
+class OnTeardownRuleTest : SysuiTestCase() {
+ // None of these inner classes should be run except as part of this utilities-testing test
+ class HasTeardown {
+ @get:Rule val teardownRule = OnTeardownRule()
+
+ @Before
+ fun setUp() {
+ teardownWasRun = false
+ teardownRule.onTeardown { teardownWasRun = true }
+ }
+
+ @Test fun doTest() {}
+
+ companion object {
+ var teardownWasRun = false
+ }
+ }
+
+ @Test
+ fun teardownRuns() {
+ val result = JUnitCore().run(HasTeardown::class.java)
+ assertThat(result.failures).isEmpty()
+ assertThat(HasTeardown.teardownWasRun).isTrue()
+ }
+
+ class FirstTeardownFails {
+ @get:Rule val teardownRule = OnTeardownRule()
+
+ @Before
+ fun setUp() {
+ teardownWasRun = false
+ teardownRule.onTeardown { fail("One fails") }
+ teardownRule.onTeardown { teardownWasRun = true }
+ }
+
+ @Test fun doTest() {}
+
+ companion object {
+ var teardownWasRun = false
+ }
+ }
+
+ @Test
+ fun allTeardownsRun() {
+ val result = JUnitCore().run(FirstTeardownFails::class.java)
+ assertThat(result.failures.map { it.message }).isEqualTo(listOf("One fails"))
+ assertThat(FirstTeardownFails.teardownWasRun).isTrue()
+ }
+
+ class ThreeTeardowns {
+ @get:Rule val teardownRule = OnTeardownRule()
+
+ @Before
+ fun setUp() {
+ messages.clear()
+ }
+
+ @Test
+ fun doTest() {
+ teardownRule.onTeardown { messages.add("A") }
+ teardownRule.onTeardown { messages.add("B") }
+ teardownRule.onTeardown { messages.add("C") }
+ }
+
+ companion object {
+ val messages = mutableListOf<String>()
+ }
+ }
+
+ @Test
+ fun reverseOrder() {
+ val result = JUnitCore().run(ThreeTeardowns::class.java)
+ assertThat(result.failures).isEmpty()
+ assertThat(ThreeTeardowns.messages).isEqualTo(listOf("C", "B", "A"))
+ }
+
+ class TryToDoABadThing {
+ @get:Rule val teardownRule = OnTeardownRule()
+
+ @Test
+ fun doTest() {
+ teardownRule.onTeardown {
+ teardownRule.onTeardown {
+ // do nothing
+ }
+ }
+ }
+ }
+
+ @Test
+ fun prohibitTeardownDuringTeardown() {
+ val result = JUnitCore().run(TryToDoABadThing::class.java)
+ assertThat(result.failures.map { it.message })
+ .isEqualTo(listOf("Cannot add new teardown routines after test complete."))
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dump/DumpManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dump/DumpManagerTest.kt
index f331060..5827c7b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dump/DumpManagerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dump/DumpManagerTest.kt
@@ -23,6 +23,7 @@
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.table.TableLogBuffer
import com.google.common.truth.Truth.assertThat
+import java.io.PrintWriter
import org.junit.Assert.assertThrows
import org.junit.Before
import org.junit.Test
@@ -134,6 +135,21 @@
}
@Test
+ fun registerDumpable_supportsAnonymousDumpables() {
+ val anonDumpable =
+ object : Dumpable {
+ override fun dump(pw: PrintWriter, args: Array<out String>) {
+ pw.println("AnonDumpable")
+ }
+ }
+
+ // THEN registration with implicit names should succeed
+ dumpManager.registerCriticalDumpable(anonDumpable)
+
+ // No exception thrown
+ }
+
+ @Test
fun getDumpables_returnsSafeCollection() {
// GIVEN a variety of registered dumpables
dumpManager.registerCriticalDumpable("dumpable1", dumpable1)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/pipeline/Media3ActionFactoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/pipeline/Media3ActionFactoryTest.kt
new file mode 100644
index 0000000..9e3fdf3
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/pipeline/Media3ActionFactoryTest.kt
@@ -0,0 +1,335 @@
+/*
+ * 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.media.controls.domain.pipeline
+
+import android.media.session.MediaSession
+import android.os.Bundle
+import android.os.Handler
+import android.os.looper
+import android.testing.TestableLooper
+import android.testing.TestableLooper.RunWithLooper
+import androidx.media.utils.MediaConstants
+import androidx.media3.common.Player
+import androidx.media3.session.CommandButton
+import androidx.media3.session.MediaController as Media3Controller
+import androidx.media3.session.SessionCommand
+import androidx.media3.session.SessionToken
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.graphics.imageLoader
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.media.controls.shared.mediaLogger
+import com.android.systemui.media.controls.shared.model.MediaButton
+import com.android.systemui.media.controls.util.fakeMediaControllerFactory
+import com.android.systemui.media.controls.util.fakeSessionTokenFactory
+import com.android.systemui.res.R
+import com.android.systemui.testKosmos
+import com.google.common.collect.ImmutableList
+import com.google.common.truth.Truth.assertThat
+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.kotlin.any
+import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.clearInvocations
+import org.mockito.kotlin.doAnswer
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
+
+private const val PACKAGE_NAME = "package_name"
+private const val CUSTOM_ACTION_NAME = "Custom Action"
+private const val CUSTOM_ACTION_COMMAND = "custom-action"
+
+@SmallTest
+@RunWithLooper
+@RunWith(AndroidJUnit4::class)
+class Media3ActionFactoryTest : SysuiTestCase() {
+
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val controllerFactory = kosmos.fakeMediaControllerFactory
+ private val tokenFactory = kosmos.fakeSessionTokenFactory
+ private lateinit var testableLooper: TestableLooper
+
+ private var commandCaptor = argumentCaptor<SessionCommand>()
+ private var runnableCaptor = argumentCaptor<Runnable>()
+
+ private val legacyToken = MediaSession.Token(1, null)
+ private val token = mock<SessionToken>()
+ private val handler =
+ mock<Handler> {
+ on { post(runnableCaptor.capture()) } doAnswer
+ {
+ runnableCaptor.lastValue.run()
+ true
+ }
+ }
+ private val customLayout = ImmutableList.of<CommandButton>()
+ private val media3Controller =
+ mock<Media3Controller> {
+ on { customLayout } doReturn customLayout
+ on { sessionExtras } doReturn Bundle()
+ on { isCommandAvailable(any()) } doReturn true
+ on { isSessionCommandAvailable(any<SessionCommand>()) } doReturn true
+ }
+
+ private lateinit var underTest: Media3ActionFactory
+
+ @Before
+ fun setup() {
+ testableLooper = TestableLooper.get(this)
+
+ underTest =
+ Media3ActionFactory(
+ context,
+ kosmos.imageLoader,
+ controllerFactory,
+ tokenFactory,
+ kosmos.mediaLogger,
+ kosmos.looper,
+ handler,
+ kosmos.testScope,
+ )
+
+ controllerFactory.setMedia3Controller(media3Controller)
+ tokenFactory.setMedia3SessionToken(token)
+ }
+
+ @Test
+ fun media3Actions_playingState_withCustomActions() =
+ testScope.runTest {
+ // Media is playing, all commands available, with custom actions
+ val customLayout = ImmutableList.copyOf((0..1).map { createCustomCommandButton(it) })
+ whenever(media3Controller.customLayout).thenReturn(customLayout)
+ whenever(media3Controller.isPlaying).thenReturn(true)
+ val result = getActions()
+
+ assertThat(result).isNotNull()
+
+ val actions = result!!
+ assertThat(actions.playOrPause!!.contentDescription)
+ .isEqualTo(context.getString(R.string.controls_media_button_pause))
+ actions.playOrPause!!.action!!.run()
+ runCurrent()
+ verify(media3Controller).pause()
+ verify(media3Controller).release()
+ clearInvocations(media3Controller)
+
+ assertThat(actions.prevOrCustom!!.contentDescription)
+ .isEqualTo(context.getString(R.string.controls_media_button_prev))
+ actions.prevOrCustom!!.action!!.run()
+ runCurrent()
+ verify(media3Controller).seekToPrevious()
+ verify(media3Controller).release()
+ clearInvocations(media3Controller)
+
+ assertThat(actions.nextOrCustom!!.contentDescription)
+ .isEqualTo(context.getString(R.string.controls_media_button_next))
+ actions.nextOrCustom!!.action!!.run()
+ runCurrent()
+ verify(media3Controller).seekToNext()
+ verify(media3Controller).release()
+ clearInvocations(media3Controller)
+
+ assertThat(actions.custom0!!.contentDescription).isEqualTo("$CUSTOM_ACTION_NAME 0")
+ actions.custom0!!.action!!.run()
+ runCurrent()
+ verify(media3Controller).sendCustomCommand(commandCaptor.capture(), any<Bundle>())
+ assertThat(commandCaptor.lastValue.customAction).isEqualTo("$CUSTOM_ACTION_COMMAND 0")
+ verify(media3Controller).release()
+ clearInvocations(media3Controller)
+
+ assertThat(actions.custom1!!.contentDescription).isEqualTo("$CUSTOM_ACTION_NAME 1")
+ actions.custom1!!.action!!.run()
+ runCurrent()
+ verify(media3Controller).sendCustomCommand(commandCaptor.capture(), any<Bundle>())
+ assertThat(commandCaptor.lastValue.customAction).isEqualTo("$CUSTOM_ACTION_COMMAND 1")
+ verify(media3Controller).release()
+ }
+
+ @Test
+ fun media3Actions_pausedState_hasPauseAction() =
+ testScope.runTest {
+ whenever(media3Controller.isPlaying).thenReturn(false)
+ val result = getActions()
+
+ assertThat(result).isNotNull()
+ val actions = result!!
+ assertThat(actions.playOrPause!!.contentDescription)
+ .isEqualTo(context.getString(R.string.controls_media_button_play))
+ clearInvocations(media3Controller)
+
+ actions.playOrPause!!.action!!.run()
+ runCurrent()
+ verify(media3Controller).play()
+ verify(media3Controller).release()
+ clearInvocations(media3Controller)
+ }
+
+ @Test
+ fun media3Actions_bufferingState_hasLoadingSpinner() =
+ testScope.runTest {
+ whenever(media3Controller.isPlaying).thenReturn(false)
+ whenever(media3Controller.playbackState).thenReturn(Player.STATE_BUFFERING)
+ val result = getActions()
+
+ assertThat(result).isNotNull()
+ val actions = result!!
+ assertThat(actions.playOrPause!!.contentDescription)
+ .isEqualTo(context.getString(R.string.controls_media_button_connecting))
+ assertThat(actions.playOrPause!!.action).isNull()
+ assertThat(actions.playOrPause!!.rebindId)
+ .isEqualTo(com.android.internal.R.drawable.progress_small_material)
+ }
+
+ @Test
+ fun media3Actions_noPrevNext_usesCustom() =
+ testScope.runTest {
+ val customLayout = ImmutableList.copyOf((0..4).map { createCustomCommandButton(it) })
+ whenever(media3Controller.customLayout).thenReturn(customLayout)
+ whenever(media3Controller.isPlaying).thenReturn(true)
+ whenever(media3Controller.isCommandAvailable(eq(Player.COMMAND_SEEK_TO_PREVIOUS)))
+ .thenReturn(false)
+ whenever(
+ media3Controller.isCommandAvailable(
+ eq(Player.COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM)
+ )
+ )
+ .thenReturn(false)
+ whenever(media3Controller.isCommandAvailable(eq(Player.COMMAND_SEEK_TO_NEXT)))
+ .thenReturn(false)
+ whenever(
+ media3Controller.isCommandAvailable(eq(Player.COMMAND_SEEK_TO_NEXT_MEDIA_ITEM))
+ )
+ .thenReturn(false)
+ val result = getActions()
+
+ assertThat(result).isNotNull()
+ val actions = result!!
+
+ assertThat(actions.prevOrCustom!!.contentDescription).isEqualTo("$CUSTOM_ACTION_NAME 0")
+ actions.prevOrCustom!!.action!!.run()
+ runCurrent()
+ verify(media3Controller).sendCustomCommand(commandCaptor.capture(), any<Bundle>())
+ assertThat(commandCaptor.lastValue.customAction).isEqualTo("$CUSTOM_ACTION_COMMAND 0")
+ verify(media3Controller).release()
+ clearInvocations(media3Controller)
+
+ assertThat(actions.nextOrCustom!!.contentDescription).isEqualTo("$CUSTOM_ACTION_NAME 1")
+ actions.nextOrCustom!!.action!!.run()
+ runCurrent()
+ verify(media3Controller).sendCustomCommand(commandCaptor.capture(), any<Bundle>())
+ assertThat(commandCaptor.lastValue.customAction).isEqualTo("$CUSTOM_ACTION_COMMAND 1")
+ verify(media3Controller).release()
+ clearInvocations(media3Controller)
+
+ assertThat(actions.custom0!!.contentDescription).isEqualTo("$CUSTOM_ACTION_NAME 2")
+ actions.custom0!!.action!!.run()
+ runCurrent()
+ testableLooper.processAllMessages()
+ verify(media3Controller).sendCustomCommand(commandCaptor.capture(), any<Bundle>())
+ assertThat(commandCaptor.lastValue.customAction).isEqualTo("$CUSTOM_ACTION_COMMAND 2")
+ verify(media3Controller).release()
+ clearInvocations(media3Controller)
+
+ assertThat(actions.custom1!!.contentDescription).isEqualTo("$CUSTOM_ACTION_NAME 3")
+ actions.custom1!!.action!!.run()
+ runCurrent()
+ verify(media3Controller).sendCustomCommand(commandCaptor.capture(), any<Bundle>())
+ assertThat(commandCaptor.lastValue.customAction).isEqualTo("$CUSTOM_ACTION_COMMAND 3")
+ verify(media3Controller).release()
+ }
+
+ @Test
+ fun media3Actions_noPrevNext_reservedSpace() =
+ testScope.runTest {
+ val customLayout = ImmutableList.copyOf((0..4).map { createCustomCommandButton(it) })
+ whenever(media3Controller.customLayout).thenReturn(customLayout)
+ whenever(media3Controller.isPlaying).thenReturn(true)
+ whenever(media3Controller.isCommandAvailable(eq(Player.COMMAND_SEEK_TO_PREVIOUS)))
+ .thenReturn(false)
+ whenever(
+ media3Controller.isCommandAvailable(
+ eq(Player.COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM)
+ )
+ )
+ .thenReturn(false)
+ whenever(media3Controller.isCommandAvailable(eq(Player.COMMAND_SEEK_TO_NEXT)))
+ .thenReturn(false)
+ whenever(
+ media3Controller.isCommandAvailable(eq(Player.COMMAND_SEEK_TO_NEXT_MEDIA_ITEM))
+ )
+ .thenReturn(false)
+ val extras =
+ Bundle().apply {
+ putBoolean(
+ MediaConstants.SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_PREV,
+ true,
+ )
+ putBoolean(
+ MediaConstants.SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_NEXT,
+ true,
+ )
+ }
+ whenever(media3Controller.sessionExtras).thenReturn(extras)
+ val result = getActions()
+
+ assertThat(result).isNotNull()
+ val actions = result!!
+
+ assertThat(actions.prevOrCustom).isNull()
+ assertThat(actions.nextOrCustom).isNull()
+
+ assertThat(actions.custom0!!.contentDescription).isEqualTo("$CUSTOM_ACTION_NAME 0")
+ actions.custom0!!.action!!.run()
+ runCurrent()
+ verify(media3Controller).sendCustomCommand(commandCaptor.capture(), any<Bundle>())
+ assertThat(commandCaptor.lastValue.customAction).isEqualTo("$CUSTOM_ACTION_COMMAND 0")
+ verify(media3Controller).release()
+ clearInvocations(media3Controller)
+
+ assertThat(actions.custom1!!.contentDescription).isEqualTo("$CUSTOM_ACTION_NAME 1")
+ actions.custom1!!.action!!.run()
+ runCurrent()
+ verify(media3Controller).sendCustomCommand(commandCaptor.capture(), any<Bundle>())
+ assertThat(commandCaptor.lastValue.customAction).isEqualTo("$CUSTOM_ACTION_COMMAND 1")
+ verify(media3Controller).release()
+ }
+
+ private suspend fun getActions(): MediaButton? {
+ val result = underTest.createActionsFromSession(PACKAGE_NAME, legacyToken)
+ testScope.runCurrent()
+ verify(media3Controller).release()
+
+ // Clear so tests can verify the correct number of release() calls in later operations
+ clearInvocations(media3Controller)
+ return result
+ }
+
+ private fun createCustomCommandButton(id: Int): CommandButton {
+ return CommandButton.Builder()
+ .setDisplayName("$CUSTOM_ACTION_NAME $id")
+ .setSessionCommand(SessionCommand("$CUSTOM_ACTION_COMMAND $id", Bundle()))
+ .build()
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoaderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoaderTest.kt
index fc9e595..1a7265b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoaderTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoaderTest.kt
@@ -29,6 +29,7 @@
import android.media.session.PlaybackState
import android.os.Bundle
import android.service.notification.StatusBarNotification
+import android.testing.TestableLooper.RunWithLooper
import androidx.media.utils.MediaConstants
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
@@ -69,6 +70,7 @@
private const val SESSION_EMPTY_TITLE = ""
@SmallTest
+@RunWithLooper
@RunWith(AndroidJUnit4::class)
class MediaDataLoaderTest : SysuiTestCase() {
@@ -80,6 +82,7 @@
private val fakeFeatureFlags = kosmos.fakeFeatureFlagsClassic
private val mediaFlags = kosmos.mediaFlags
private val mediaControllerFactory = kosmos.fakeMediaControllerFactory
+ private val media3ActionFactory = kosmos.media3ActionFactory
private val session = MediaSession(context, "MediaDataLoaderTestSession")
private val metadataBuilder =
MediaMetadata.Builder().apply {
@@ -87,21 +90,25 @@
putString(MediaMetadata.METADATA_KEY_TITLE, SESSION_TITLE)
}
- private val underTest: MediaDataLoader =
- MediaDataLoader(
- context,
- testDispatcher,
- testScope,
- mediaControllerFactory,
- mediaFlags,
- kosmos.imageLoader,
- statusBarManager,
- )
+ private lateinit var underTest: MediaDataLoader
@Before
fun setUp() {
mediaControllerFactory.setControllerForToken(session.sessionToken, mediaController)
+ whenever(mediaController.sessionToken).thenReturn(session.sessionToken)
whenever(mediaController.metadata).then { metadataBuilder.build() }
+
+ underTest =
+ MediaDataLoader(
+ context,
+ testDispatcher,
+ testScope,
+ mediaControllerFactory,
+ mediaFlags,
+ kosmos.imageLoader,
+ statusBarManager,
+ kosmos.media3ActionFactory,
+ )
}
@Test
@@ -394,6 +401,7 @@
mediaFlags,
mockImageLoader,
statusBarManager,
+ media3ActionFactory,
)
metadataBuilder.putString(
MediaMetadata.METADATA_KEY_ALBUM_ART_URI,
@@ -422,6 +430,7 @@
mediaFlags,
mockImageLoader,
statusBarManager,
+ media3ActionFactory,
)
metadataBuilder.putString(
MediaMetadata.METADATA_KEY_ALBUM_ART_URI,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/NavigationBarTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/NavigationBarTest.java
index 2905a73..646722b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/NavigationBarTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/NavigationBarTest.java
@@ -116,6 +116,7 @@
import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.NotificationShadeDepthController;
import com.android.systemui.statusbar.NotificationShadeWindowController;
+import com.android.systemui.statusbar.data.repository.LightBarControllerStore;
import com.android.systemui.statusbar.phone.AutoHideController;
import com.android.systemui.statusbar.phone.CentralSurfaces;
import com.android.systemui.statusbar.phone.LightBarController;
@@ -208,7 +209,7 @@
@Mock
private LightBarController mLightBarController;
@Mock
- private LightBarController.Factory mLightBarcontrollerFactory;
+ private LightBarControllerStore mLightBarControllerStore;
@Mock
private AutoHideController mAutoHideController;
@Mock
@@ -257,7 +258,7 @@
public void setup() throws Exception {
MockitoAnnotations.initMocks(this);
- when(mLightBarcontrollerFactory.create(any(Context.class))).thenReturn(mLightBarController);
+ when(mLightBarControllerStore.forDisplay(anyInt())).thenReturn(mLightBarController);
when(mAutoHideControllerFactory.create(any(Context.class))).thenReturn(mAutoHideController);
when(mNavigationBarView.getHomeButton()).thenReturn(mHomeButton);
when(mNavigationBarView.getRecentsButton()).thenReturn(mRecentsButton);
@@ -649,8 +650,7 @@
mFakeExecutor,
mUiEventLogger,
mNavBarHelper,
- mLightBarController,
- mLightBarcontrollerFactory,
+ mLightBarControllerStore,
mAutoHideController,
mAutoHideControllerFactory,
Optional.of(mTelecomManager),
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/CommunalSmartspaceControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/CommunalSmartspaceControllerTest.kt
index ce9b3be..7842d75 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/CommunalSmartspaceControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/CommunalSmartspaceControllerTest.kt
@@ -31,6 +31,7 @@
import com.android.systemui.plugins.BcSmartspaceDataPlugin
import com.android.systemui.plugins.BcSmartspaceDataPlugin.SmartspaceView
import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.settings.UserTracker
import com.android.systemui.util.concurrency.Execution
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.withArgCaptor
@@ -52,6 +53,10 @@
@RunWith(AndroidJUnit4::class)
@TestableLooper.RunWithLooper
class CommunalSmartspaceControllerTest : SysuiTestCase() {
+ @Mock private lateinit var userTracker: UserTracker
+
+ @Mock private lateinit var userContextPrimary: Context
+
@Mock private lateinit var smartspaceManager: SmartspaceManager
@Mock private lateinit var execution: Execution
@@ -113,15 +118,18 @@
MockitoAnnotations.initMocks(this)
`when`(smartspaceManager.createSmartspaceSession(any())).thenReturn(session)
+ `when`(userTracker.userContext).thenReturn(userContextPrimary)
+ `when`(userContextPrimary.getSystemService(SmartspaceManager::class.java))
+ .thenReturn(smartspaceManager)
+
controller =
CommunalSmartspaceController(
- context,
- smartspaceManager,
+ userTracker,
execution,
uiExecutor,
precondition,
Optional.of(targetFilter),
- Optional.of(plugin)
+ Optional.of(plugin),
)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/DreamSmartspaceControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/DreamSmartspaceControllerTest.kt
index e774aed..c83c82d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/DreamSmartspaceControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/DreamSmartspaceControllerTest.kt
@@ -33,6 +33,7 @@
import com.android.systemui.plugins.BcSmartspaceDataPlugin
import com.android.systemui.plugins.BcSmartspaceDataPlugin.SmartspaceView
import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.settings.UserTracker
import com.android.systemui.smartspace.dagger.SmartspaceViewComponent
import com.android.systemui.util.concurrency.Execution
import com.android.systemui.util.mockito.any
@@ -46,66 +47,51 @@
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Mockito
+import org.mockito.Mockito.anyInt
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when`
-import org.mockito.Mockito.anyInt
import org.mockito.MockitoAnnotations
-import org.mockito.Spy
@SmallTest
@RunWith(AndroidJUnit4::class)
@TestableLooper.RunWithLooper
class DreamSmartspaceControllerTest : SysuiTestCase() {
- @Mock
- private lateinit var smartspaceManager: SmartspaceManager
+ @Mock private lateinit var userTracker: UserTracker
- @Mock
- private lateinit var execution: Execution
+ @Mock private lateinit var userContextPrimary: Context
- @Mock
- private lateinit var uiExecutor: Executor
+ @Mock private lateinit var smartspaceManager: SmartspaceManager
- @Mock
- private lateinit var viewComponentFactory: SmartspaceViewComponent.Factory
+ @Mock private lateinit var execution: Execution
- @Mock
- private lateinit var viewComponent: SmartspaceViewComponent
+ @Mock private lateinit var uiExecutor: Executor
- @Mock
- private lateinit var weatherViewComponent: SmartspaceViewComponent
+ @Mock private lateinit var viewComponentFactory: SmartspaceViewComponent.Factory
- private val weatherSmartspaceView: SmartspaceView by lazy {
- Mockito.spy(TestView(context))
- }
+ @Mock private lateinit var viewComponent: SmartspaceViewComponent
- @Mock
- private lateinit var targetFilter: SmartspaceTargetFilter
+ @Mock private lateinit var weatherViewComponent: SmartspaceViewComponent
- @Mock
- private lateinit var plugin: BcSmartspaceDataPlugin
+ private val weatherSmartspaceView: SmartspaceView by lazy { Mockito.spy(TestView(context)) }
- @Mock
- private lateinit var weatherPlugin: BcSmartspaceDataPlugin
+ @Mock private lateinit var targetFilter: SmartspaceTargetFilter
- @Mock
- private lateinit var precondition: SmartspacePrecondition
+ @Mock private lateinit var plugin: BcSmartspaceDataPlugin
- private val smartspaceView: SmartspaceView by lazy {
- Mockito.spy(TestView(context))
- }
+ @Mock private lateinit var weatherPlugin: BcSmartspaceDataPlugin
- @Mock
- private lateinit var listener: BcSmartspaceDataPlugin.SmartspaceTargetListener
+ @Mock private lateinit var precondition: SmartspacePrecondition
- @Mock
- private lateinit var session: SmartspaceSession
+ private val smartspaceView: SmartspaceView by lazy { Mockito.spy(TestView(context)) }
+
+ @Mock private lateinit var listener: BcSmartspaceDataPlugin.SmartspaceTargetListener
+
+ @Mock private lateinit var session: SmartspaceSession
private lateinit var controller: DreamSmartspaceController
// TODO(b/272811280): Remove usage of real view
- private val fakeParent by lazy {
- FrameLayout(context)
- }
+ private val fakeParent by lazy { FrameLayout(context) }
/**
* A class which implements SmartspaceView and extends View. This is mocked to provide the right
@@ -134,30 +120,44 @@
override fun setMediaTarget(target: SmartspaceTarget?) {}
- override fun getSelectedPage(): Int { return 0; }
+ override fun getSelectedPage(): Int {
+ return 0
+ }
- override fun getCurrentCardTopPadding(): Int { return 0; }
+ override fun getCurrentCardTopPadding(): Int {
+ return 0
+ }
}
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
`when`(viewComponentFactory.create(any(), eq(plugin), any(), eq(null)))
- .thenReturn(viewComponent)
+ .thenReturn(viewComponent)
`when`(viewComponent.getView()).thenReturn(smartspaceView)
`when`(viewComponentFactory.create(any(), eq(weatherPlugin), any(), any()))
.thenReturn(weatherViewComponent)
`when`(weatherViewComponent.getView()).thenReturn(weatherSmartspaceView)
`when`(smartspaceManager.createSmartspaceSession(any())).thenReturn(session)
- controller = DreamSmartspaceController(context, smartspaceManager, execution, uiExecutor,
- viewComponentFactory, precondition, Optional.of(targetFilter), Optional.of(plugin),
- Optional.of(weatherPlugin))
+ `when`(userTracker.userContext).thenReturn(userContextPrimary)
+ `when`(userContextPrimary.getSystemService(SmartspaceManager::class.java))
+ .thenReturn(smartspaceManager)
+
+ controller =
+ DreamSmartspaceController(
+ userTracker,
+ execution,
+ uiExecutor,
+ viewComponentFactory,
+ precondition,
+ Optional.of(targetFilter),
+ Optional.of(plugin),
+ Optional.of(weatherPlugin),
+ )
}
- /**
- * Ensures smartspace session begins on a listener only flow.
- */
+ /** Ensures smartspace session begins on a listener only flow. */
@Test
fun testConnectOnListen() {
`when`(precondition.conditionsMet()).thenReturn(true)
@@ -165,18 +165,18 @@
verify(smartspaceManager).createSmartspaceSession(any())
- var targetListener = withArgCaptor<SmartspaceSession.OnTargetsAvailableListener> {
- verify(session).addOnTargetsAvailableListener(any(), capture())
- }
+ var targetListener =
+ withArgCaptor<SmartspaceSession.OnTargetsAvailableListener> {
+ verify(session).addOnTargetsAvailableListener(any(), capture())
+ }
`when`(targetFilter.filterSmartspaceTarget(any())).thenReturn(true)
var target = Mockito.mock(SmartspaceTarget::class.java)
targetListener.onTargetsAvailable(listOf(target))
- var targets = withArgCaptor<List<SmartspaceTarget>> {
- verify(plugin).onTargetsAvailable(capture())
- }
+ var targets =
+ withArgCaptor<List<SmartspaceTarget>> { verify(plugin).onTargetsAvailable(capture()) }
assertThat(targets.contains(target)).isTrue()
@@ -185,17 +185,16 @@
verify(session).close()
}
- /**
- * Ensures session begins when a view is attached.
- */
+ /** Ensures session begins when a view is attached. */
@Test
fun testConnectOnViewCreate() {
`when`(precondition.conditionsMet()).thenReturn(true)
controller.buildAndConnectView(Mockito.mock(ViewGroup::class.java))
- val stateChangeListener = withArgCaptor<View.OnAttachStateChangeListener> {
- verify(viewComponentFactory).create(any(), eq(plugin), capture(), eq(null))
- }
+ val stateChangeListener =
+ withArgCaptor<View.OnAttachStateChangeListener> {
+ verify(viewComponentFactory).create(any(), eq(plugin), capture(), eq(null))
+ }
val mockView = Mockito.mock(TestView::class.java)
`when`(precondition.conditionsMet()).thenReturn(true)
@@ -209,9 +208,7 @@
verify(session).close()
}
- /**
- * Ensures session is created when weather smartspace view is created and attached.
- */
+ /** Ensures session is created when weather smartspace view is created and attached. */
@Test
fun testConnectOnWeatherViewCreate() {
`when`(precondition.conditionsMet()).thenReturn(true)
@@ -223,8 +220,8 @@
// Then weather view is created with custom view and the default weatherPlugin.getView
// should not be called
- verify(viewComponentFactory).create(eq(fakeParent), eq(weatherPlugin), any(),
- eq(customView))
+ verify(viewComponentFactory)
+ .create(eq(fakeParent), eq(weatherPlugin), any(), eq(customView))
verify(weatherPlugin, Mockito.never()).getView(fakeParent)
// And then session is created
@@ -234,9 +231,7 @@
verify(weatherSmartspaceView).setDozeAmount(0f)
}
- /**
- * Ensures weather plugin registers target listener when it is added from the controller.
- */
+ /** Ensures weather plugin registers target listener when it is added from the controller. */
@Test
fun testAddListenerInController_registersListenerForWeatherPlugin() {
val customView = Mockito.mock(TestView::class.java)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/StatusBarInitializerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/StatusBarInitializerTest.kt
index 20a19a9..938da88 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/StatusBarInitializerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/StatusBarInitializerTest.kt
@@ -26,11 +26,14 @@
import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
import com.android.systemui.fragments.FragmentHostManager
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
+import com.android.systemui.statusbar.data.repository.fakeStatusBarModePerDisplayRepository
import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment
import com.android.systemui.statusbar.phone.fragment.dagger.HomeStatusBarComponent
import com.android.systemui.statusbar.pipeline.shared.ui.composable.StatusBarRootFactory
import com.android.systemui.statusbar.window.StatusBarWindowController
import com.android.systemui.statusbar.window.StatusBarWindowControllerStore
+import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlin.test.Test
import org.junit.Assert.assertThrows
@@ -45,12 +48,14 @@
@SmallTest
@RunWith(AndroidJUnit4::class)
class StatusBarInitializerTest : SysuiTestCase() {
+ private val kosmos = testKosmos().useUnconfinedTestDispatcher()
private val windowController = mock(StatusBarWindowController::class.java)
private val windowControllerStore = mock(StatusBarWindowControllerStore::class.java)
private val transaction = mock(FragmentTransaction::class.java)
private val fragmentManager = mock(FragmentManager::class.java)
private val fragmentHostManager = mock(FragmentHostManager::class.java)
private val backgroundView = mock(ViewGroup::class.java)
+ private val statusBarModePerDisplayRepository = kosmos.fakeStatusBarModePerDisplayRepository
@Before
fun setup() {
@@ -72,6 +77,7 @@
statusBarRootFactory = mock(StatusBarRootFactory::class.java),
componentFactory = mock(HomeStatusBarComponent.Factory::class.java),
creationListeners = setOf(),
+ statusBarModePerDisplayRepository = statusBarModePerDisplayRepository,
)
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/LightBarControllerStoreImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/LightBarControllerStoreImplTest.kt
new file mode 100644
index 0000000..18eef33
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/LightBarControllerStoreImplTest.kt
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.data.repository
+
+import android.view.Display.DEFAULT_DISPLAY
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.display.data.repository.displayRepository
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
+import com.android.systemui.testKosmos
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.never
+import org.mockito.kotlin.verify
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class LightBarControllerStoreImplTest : SysuiTestCase() {
+ private val kosmos = testKosmos().useUnconfinedTestDispatcher()
+ private val testScope = kosmos.testScope
+ private val fakeDisplayRepository = kosmos.displayRepository
+
+ private val underTest = kosmos.lightBarControllerStoreImpl
+
+ @Before
+ fun start() {
+ underTest.start()
+ }
+
+ @Before fun addDisplays() = runBlocking { fakeDisplayRepository.addDisplay(DEFAULT_DISPLAY) }
+
+ @Test
+ fun forDisplay_startsInstance() =
+ testScope.runTest {
+ val instance = underTest.forDisplay(DEFAULT_DISPLAY)
+
+ verify(instance).start()
+ }
+
+ @Test
+ fun beforeDisplayRemoved_doesNotStopInstances() =
+ testScope.runTest {
+ val instance = underTest.forDisplay(DEFAULT_DISPLAY)
+
+ verify(instance, never()).stop()
+ }
+
+ @Test
+ fun displayRemoved_stopsInstance() =
+ testScope.runTest {
+ val instance = underTest.forDisplay(DEFAULT_DISPLAY)
+
+ fakeDisplayRepository.removeDisplay(DEFAULT_DISPLAY)
+
+ verify(instance).stop()
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/MultiDisplayStatusBarModeRepositoryStoreTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/MultiDisplayStatusBarModeRepositoryStoreTest.kt
new file mode 100644
index 0000000..a9920ec5
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/MultiDisplayStatusBarModeRepositoryStoreTest.kt
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.data.repository
+
+import android.platform.test.annotations.EnableFlags
+import android.view.Display.DEFAULT_DISPLAY
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.display.data.repository.displayRepository
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
+import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
+import com.android.systemui.testKosmos
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.verify
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@EnableFlags(StatusBarConnectedDisplays.FLAG_NAME)
+class MultiDisplayStatusBarModeRepositoryStoreTest : SysuiTestCase() {
+
+ private val kosmos = testKosmos().useUnconfinedTestDispatcher()
+ private val testScope = kosmos.testScope
+ private val fakeDisplayRepository = kosmos.displayRepository
+ private val underTest by lazy { kosmos.multiDisplayStatusBarModeRepositoryStore }
+
+ @Before
+ fun start() {
+ underTest.start()
+ }
+
+ @Before fun addDisplays() = runBlocking { fakeDisplayRepository.addDisplay(DEFAULT_DISPLAY) }
+
+ @Test
+ fun forDisplay_startsInstance() =
+ testScope.runTest {
+ val instance = underTest.forDisplay(DEFAULT_DISPLAY)
+
+ verify(instance).start()
+ }
+
+ @Test
+ fun displayRemoved_stopsInstance() =
+ testScope.runTest {
+ val instance = underTest.forDisplay(DEFAULT_DISPLAY)
+
+ fakeDisplayRepository.removeDisplay(DEFAULT_DISPLAY)
+
+ verify(instance).stop()
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java
index 88ec18d..9099334 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java
@@ -48,12 +48,10 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.navigationbar.NavigationModeController;
-import com.android.systemui.settings.FakeDisplayTracker;
import com.android.systemui.statusbar.data.model.StatusBarAppearance;
import com.android.systemui.statusbar.data.model.StatusBarMode;
-import com.android.systemui.statusbar.data.repository.FakeStatusBarModeRepository;
+import com.android.systemui.statusbar.data.repository.FakeStatusBarModePerDisplayRepository;
import com.android.systemui.statusbar.policy.BatteryController;
-import com.android.systemui.util.kotlin.JavaAdapter;
import kotlinx.coroutines.test.TestScope;
@@ -81,8 +79,8 @@
private SysuiDarkIconDispatcher mStatusBarIconController;
private LightBarController mLightBarController;
private final TestScope mTestScope = TestScopeProvider.getTestScope();
- private final FakeStatusBarModeRepository mStatusBarModeRepository =
- new FakeStatusBarModeRepository();
+ private final FakeStatusBarModePerDisplayRepository mStatusBarModeRepository =
+ new FakeStatusBarModePerDisplayRepository();
@Before
public void setup() {
@@ -92,15 +90,16 @@
mLightBarTransitionsController = mock(LightBarTransitionsController.class);
when(mStatusBarIconController.getTransitionsController()).thenReturn(
mLightBarTransitionsController);
- mLightBarController = new LightBarController(
- mContext,
- new JavaAdapter(mTestScope),
+ mLightBarController = new LightBarControllerImpl(
+ mContext.getDisplayId(),
+ mTestScope,
mStatusBarIconController,
mock(BatteryController.class),
mock(NavigationModeController.class),
mStatusBarModeRepository,
mock(DumpManager.class),
- new FakeDisplayTracker(mContext));
+ mTestScope.getCoroutineContext(),
+ mock(BiometricUnlockController.class));
mLightBarController.start();
}
@@ -121,7 +120,7 @@
new AppearanceRegion(APPEARANCE_LIGHT_STATUS_BARS, secondBounds)
);
- mStatusBarModeRepository.getDefaultDisplay().getStatusBarAppearance().setValue(
+ mStatusBarModeRepository.getStatusBarAppearance().setValue(
new StatusBarAppearance(
StatusBarMode.TRANSPARENT,
STATUS_BAR_BOUNDS,
@@ -142,7 +141,7 @@
new AppearanceRegion(0 /* appearance */, secondBounds)
);
- mStatusBarModeRepository.getDefaultDisplay().getStatusBarAppearance().setValue(
+ mStatusBarModeRepository.getStatusBarAppearance().setValue(
new StatusBarAppearance(
StatusBarMode.TRANSPARENT,
STATUS_BAR_BOUNDS,
@@ -165,7 +164,7 @@
new AppearanceRegion(APPEARANCE_LIGHT_STATUS_BARS, secondBounds)
);
- mStatusBarModeRepository.getDefaultDisplay().getStatusBarAppearance().setValue(
+ mStatusBarModeRepository.getStatusBarAppearance().setValue(
new StatusBarAppearance(
StatusBarMode.TRANSPARENT,
STATUS_BAR_BOUNDS,
@@ -190,7 +189,7 @@
new AppearanceRegion(APPEARANCE_LIGHT_STATUS_BARS, thirdBounds)
);
- mStatusBarModeRepository.getDefaultDisplay().getStatusBarAppearance().setValue(
+ mStatusBarModeRepository.getStatusBarAppearance().setValue(
new StatusBarAppearance(
StatusBarMode.TRANSPARENT,
STATUS_BAR_BOUNDS,
@@ -214,7 +213,7 @@
new AppearanceRegion(0 /* appearance */, secondBounds)
);
- mStatusBarModeRepository.getDefaultDisplay().getStatusBarAppearance().setValue(
+ mStatusBarModeRepository.getStatusBarAppearance().setValue(
new StatusBarAppearance(
StatusBarMode.TRANSPARENT,
STATUS_BAR_BOUNDS,
@@ -231,7 +230,7 @@
new AppearanceRegion(APPEARANCE_LIGHT_STATUS_BARS, new Rect(0, 0, 1, 1))
);
- mStatusBarModeRepository.getDefaultDisplay().getStatusBarAppearance().setValue(
+ mStatusBarModeRepository.getStatusBarAppearance().setValue(
new StatusBarAppearance(
StatusBarMode.TRANSPARENT,
STATUS_BAR_BOUNDS,
@@ -249,7 +248,7 @@
new AppearanceRegion(0, new Rect(0, 0, 1, 1))
);
- mStatusBarModeRepository.getDefaultDisplay().getStatusBarAppearance().setValue(
+ mStatusBarModeRepository.getStatusBarAppearance().setValue(
new StatusBarAppearance(
StatusBarMode.TRANSPARENT,
STATUS_BAR_BOUNDS,
@@ -266,7 +265,7 @@
new AppearanceRegion(APPEARANCE_LIGHT_STATUS_BARS, new Rect(0, 0, 1, 1))
);
- mStatusBarModeRepository.getDefaultDisplay().getStatusBarAppearance().setValue(
+ mStatusBarModeRepository.getStatusBarAppearance().setValue(
new StatusBarAppearance(
StatusBarMode.TRANSPARENT,
STATUS_BAR_BOUNDS,
@@ -276,7 +275,7 @@
reset(mStatusBarIconController);
// WHEN the same appearance regions but different status bar mode is sent
- mStatusBarModeRepository.getDefaultDisplay().getStatusBarAppearance().setValue(
+ mStatusBarModeRepository.getStatusBarAppearance().setValue(
new StatusBarAppearance(
StatusBarMode.LIGHTS_OUT_TRANSPARENT,
STATUS_BAR_BOUNDS,
@@ -298,7 +297,7 @@
/* start= */ new Rect(0, 0, 10, 10),
/* end= */ new Rect(0, 0, 20, 20));
- mStatusBarModeRepository.getDefaultDisplay().getStatusBarAppearance().setValue(
+ mStatusBarModeRepository.getStatusBarAppearance().setValue(
new StatusBarAppearance(
StatusBarMode.TRANSPARENT,
startingBounds,
@@ -311,7 +310,7 @@
BoundsPair newBounds = new BoundsPair(
/* start= */ new Rect(0, 0, 30, 30),
/* end= */ new Rect(0, 0, 40, 40));
- mStatusBarModeRepository.getDefaultDisplay().getStatusBarAppearance().setValue(
+ mStatusBarModeRepository.getStatusBarAppearance().setValue(
new StatusBarAppearance(
StatusBarMode.TRANSPARENT,
newBounds,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerViaListenerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerViaListenerTest.kt
index e0d9fac..110dec6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerViaListenerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerViaListenerTest.kt
@@ -36,7 +36,6 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.testScope
import com.android.systemui.log.logcatLogBuffer
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.res.R
@@ -58,7 +57,6 @@
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
-import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -144,6 +142,7 @@
controller.start()
controller.addCallback(mockOngoingCallListener)
controller.setChipView(chipView)
+ onTeardown { controller.tearDownChipView() }
val collectionListenerCaptor = ArgumentCaptor.forClass(NotifCollectionListener::class.java)
verify(notificationCollection).addCollectionListener(collectionListenerCaptor.capture())
@@ -153,11 +152,6 @@
.thenReturn(PROC_STATE_INVISIBLE)
}
- @After
- fun tearDown() {
- controller.tearDownChipView()
- }
-
@Test
fun onEntryUpdated_isOngoingCallNotif_listenerAndRepoNotified() {
val notification = NotificationEntryBuilder(createOngoingCallNotifEntry())
@@ -224,7 +218,7 @@
notifCollectionListener.onEntryUpdated(notification.build())
chipView.measure(
View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
- View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
+ View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
)
assertThat(chipView.findViewById<View>(R.id.ongoing_activity_chip_time)?.measuredWidth)
@@ -241,7 +235,7 @@
notifCollectionListener.onEntryUpdated(notification.build())
chipView.measure(
View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
- View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
+ View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
)
assertThat(chipView.findViewById<View>(R.id.ongoing_activity_chip_time)?.measuredWidth)
@@ -257,7 +251,7 @@
notifCollectionListener.onEntryUpdated(notification.build())
chipView.measure(
View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
- View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
+ View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
)
assertThat(chipView.findViewById<View>(R.id.ongoing_activity_chip_time)?.measuredWidth)
@@ -668,7 +662,7 @@
private fun createCallNotifEntry(
callStyle: Notification.CallStyle,
- nullContentIntent: Boolean = false
+ nullContentIntent: Boolean = false,
): NotificationEntry {
val notificationEntryBuilder = NotificationEntryBuilder()
notificationEntryBuilder.modifyNotification(context).style = callStyle
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/satellite/data/DeviceBasedSatelliteRepositorySwitcherTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/satellite/data/DeviceBasedSatelliteRepositorySwitcherTest.kt
index fc1ea22..19d5a16 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/satellite/data/DeviceBasedSatelliteRepositorySwitcherTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/satellite/data/DeviceBasedSatelliteRepositorySwitcherTest.kt
@@ -66,6 +66,7 @@
logBuffer = FakeLogBuffer.Factory.create(),
verboseLogBuffer = FakeLogBuffer.Factory.create(),
systemClock,
+ context.resources,
)
private val demoDataSource =
mock<DemoDeviceBasedSatelliteDataSource>().also {
@@ -80,7 +81,11 @@
)
}
private val demoImpl =
- DemoDeviceBasedSatelliteRepository(demoDataSource, testScope.backgroundScope)
+ DemoDeviceBasedSatelliteRepository(
+ demoDataSource,
+ testScope.backgroundScope,
+ context.resources,
+ )
private val underTest =
DeviceBasedSatelliteRepositorySwitcher(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/satellite/data/demo/DemoDeviceBasedSatelliteRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/satellite/data/demo/DemoDeviceBasedSatelliteRepositoryTest.kt
index 8769389..a70881a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/satellite/data/demo/DemoDeviceBasedSatelliteRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/satellite/data/demo/DemoDeviceBasedSatelliteRepositoryTest.kt
@@ -58,7 +58,12 @@
whenever(it.satelliteEvents).thenReturn(fakeSatelliteEvents)
}
- underTest = DemoDeviceBasedSatelliteRepository(dataSource, testScope.backgroundScope)
+ underTest =
+ DemoDeviceBasedSatelliteRepository(
+ dataSource,
+ testScope.backgroundScope,
+ context.resources,
+ )
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/FakeDeviceBasedSatelliteRepository.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/FakeDeviceBasedSatelliteRepository.kt
index 55460bd..41fa9e7 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/FakeDeviceBasedSatelliteRepository.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/FakeDeviceBasedSatelliteRepository.kt
@@ -28,4 +28,6 @@
override val signalStrength = MutableStateFlow(0)
override val isSatelliteAllowedForCurrentLocation = MutableStateFlow(false)
+
+ override var isOpportunisticSatelliteIconEnabled: Boolean = true
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt
index e7e4969..509aa7a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt
@@ -172,6 +172,26 @@
}
@Test
+ fun icon_null_allOosAndConfigIsFalse() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.icon)
+
+ // GIVEN config for opportunistic icon is false
+ repo.isOpportunisticSatelliteIconEnabled = false
+
+ // GIVEN all icons are OOS
+ val i1 = mobileIconsInteractor.getMobileConnectionInteractorForSubId(1)
+ i1.isInService.value = false
+ i1.isEmergencyOnly.value = false
+
+ // GIVEN apm is disabled
+ airplaneModeRepository.setIsAirplaneMode(false)
+
+ // THEN icon is null because it is not allowed
+ assertThat(latest).isNull()
+ }
+
+ @Test
fun icon_null_isEmergencyOnly() =
testScope.runTest {
val latest by collectLastValue(underTest.icon)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
index 53e033e..e7ca1dd 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
@@ -51,6 +51,8 @@
import android.os.Handler;
import android.os.UserHandle;
import android.os.UserManager;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
import android.provider.Settings;
import androidx.annotation.VisibleForTesting;
@@ -561,6 +563,113 @@
}
@Test
+ @EnableFlags(com.android.systemui.shared.Flags.FLAG_NEW_CUSTOMIZATION_PICKER_UI)
+ public void onWallpaperColorsChanged_homeWallpaper_shouldUpdateTheme() {
+ // Should ask for a new theme when the colors of the last applied wallpaper change
+ WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.RED),
+ Color.valueOf(Color.BLUE), null);
+
+ String jsonString =
+ "{\"android.theme.customization.color_source\":\"home_wallpaper\","
+ + "\"android.theme.customization.system_palette\":\"A16B00\","
+ + "\"android.theme.customization.accent_color\":\"A16B00\","
+ + "\"android.theme.customization.color_index\":\"2\"}";
+
+ when(mSecureSettings.getStringForUser(
+ eq(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES), anyInt()))
+ .thenReturn(jsonString);
+ when(mWallpaperManager.getWallpaperIdForUser(WallpaperManager.FLAG_SYSTEM, USER_SYSTEM))
+ .thenReturn(1);
+ // Set LOCK wallpaper as the last applied one to verify that theme is no longer based on
+ // latest wallpaper
+ when(mWallpaperManager.getWallpaperIdForUser(WallpaperManager.FLAG_LOCK, USER_SYSTEM))
+ .thenReturn(2);
+
+ mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_SYSTEM,
+ USER_SYSTEM);
+
+ ArgumentCaptor<String> updatedSetting = ArgumentCaptor.forClass(String.class);
+ verify(mSecureSettings).putStringForUser(
+ eq(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES), updatedSetting.capture(),
+ anyInt());
+
+ verify(mThemeOverlayApplier)
+ .applyCurrentUserOverlays(any(), any(), anyInt(), any(), any());
+ }
+
+
+
+ @Test
+ @EnableFlags(com.android.systemui.shared.Flags.FLAG_NEW_CUSTOMIZATION_PICKER_UI)
+ public void onWallpaperColorsChanged_homeWallpaperWithSameColor_shouldKeepThemeAndReapply() {
+ // Shouldn't ask for a new theme when the colors of the last applied wallpaper change
+ // with the same specified system palette one.
+ WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.RED),
+ Color.valueOf(0xffa16b00), null);
+
+ String jsonString =
+ "{\"android.theme.customization.color_source\":\"home_wallpaper\","
+ + "\"android.theme.customization.system_palette\":\"A16B00\","
+ + "\"android.theme.customization.accent_color\":\"A16B00\","
+ + "\"android.theme.customization.color_index\":\"2\"}";
+
+ when(mSecureSettings.getStringForUser(
+ eq(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES), anyInt()))
+ .thenReturn(jsonString);
+ when(mWallpaperManager.getWallpaperIdForUser(WallpaperManager.FLAG_SYSTEM, USER_SYSTEM))
+ .thenReturn(1);
+ // Set LOCK wallpaper as the last applied one to verify that theme is no longer based on
+ // latest wallpaper
+ when(mWallpaperManager.getWallpaperIdForUser(WallpaperManager.FLAG_LOCK, USER_SYSTEM))
+ .thenReturn(2);
+
+ mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_SYSTEM,
+ USER_SYSTEM);
+
+ ArgumentCaptor<String> updatedSetting = ArgumentCaptor.forClass(String.class);
+ verify(mSecureSettings, never()).putString(
+ eq(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES), updatedSetting.capture());
+
+ // Apply overlay by existing theme from secure setting
+ verify(mThemeOverlayApplier).applyCurrentUserOverlays(any(), any(), anyInt(), any(), any());
+ }
+
+ @Test
+ @EnableFlags(com.android.systemui.shared.Flags.FLAG_NEW_CUSTOMIZATION_PICKER_UI)
+ public void onWallpaperColorsChanged_lockWallpaper_shouldKeepTheme() {
+ // Should ask for a new theme when the colors of the last applied wallpaper change
+ WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.RED),
+ Color.valueOf(Color.BLUE), null);
+
+ String jsonString =
+ "{\"android.theme.customization.color_source\":\"home_wallpaper\","
+ + "\"android.theme.customization.system_palette\":\"A16B00\","
+ + "\"android.theme.customization.accent_color\":\"A16B00\","
+ + "\"android.theme.customization.color_index\":\"2\"}";
+
+ when(mSecureSettings.getStringForUser(
+ eq(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES), anyInt()))
+ .thenReturn(jsonString);
+ when(mWallpaperManager.getWallpaperIdForUser(WallpaperManager.FLAG_SYSTEM, USER_SYSTEM))
+ .thenReturn(1);
+ // Set LOCK wallpaper as the last applied one to verify that theme is no longer based on
+ // latest wallpaper
+ when(mWallpaperManager.getWallpaperIdForUser(WallpaperManager.FLAG_LOCK, USER_SYSTEM))
+ .thenReturn(2);
+
+ mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_LOCK,
+ USER_SYSTEM);
+
+ ArgumentCaptor<String> updatedSetting = ArgumentCaptor.forClass(String.class);
+ verify(mSecureSettings, never()).putString(
+ eq(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES), updatedSetting.capture());
+
+ verify(mThemeOverlayApplier, never())
+ .applyCurrentUserOverlays(any(), any(), anyInt(), any(), any());
+ }
+
+ @Test
+ @DisableFlags(com.android.systemui.shared.Flags.FLAG_NEW_CUSTOMIZATION_PICKER_UI)
public void onWallpaperColorsChanged_resetThemeWhenFromLatestWallpaper() {
// Should ask for a new theme when the colors of the last applied wallpaper change
WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.RED),
@@ -594,6 +703,7 @@
}
@Test
+ @DisableFlags(com.android.systemui.shared.Flags.FLAG_NEW_CUSTOMIZATION_PICKER_UI)
public void onWallpaperColorsChanged_keepThemeWhenFromLatestWallpaperAndSpecifiedColor() {
// Shouldn't ask for a new theme when the colors of the last applied wallpaper change
// with the same specified system palette one.
@@ -627,6 +737,7 @@
}
@Test
+ @DisableFlags(com.android.systemui.shared.Flags.FLAG_NEW_CUSTOMIZATION_PICKER_UI)
public void onWallpaperColorsChanged_keepThemeIfNotLatestWallpaper() {
// Shouldn't ask for a new theme when the colors of the wallpaper that is not the last
// applied one change
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index d4a52c3..c8ef093 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -383,6 +383,10 @@
<!-- Whether to show activity indicators in the status bar -->
<bool name="config_showActivity">false</bool>
+ <!-- Whether to show the opportunistic satellite icon. When true, an icon will show to indicate
+ satellite capabilities when all other connections are out of service. -->
+ <bool name="config_showOpportunisticSatelliteIcon">true</bool>
+
<!-- Whether or not to show the notification shelf that houses the icons of notifications that
have been scrolled off-screen. -->
<bool name="config_showNotificationShelf">true</bool>
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
index 12b5fc0..b491c94 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
@@ -27,6 +27,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.AlertDialog;
+import android.app.KeyguardManager;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.PixelFormat;
@@ -315,6 +316,16 @@
mBiometricCallback = new BiometricCallback();
mMSDLPlayer = msdlPlayer;
+ // Listener for when device locks from adaptive auth, dismiss prompt
+ getContext().getSystemService(KeyguardManager.class).addKeyguardLockedStateListener(
+ getContext().getMainExecutor(),
+ isKeyguardLocked -> {
+ if (isKeyguardLocked) {
+ onStartedGoingToSleep();
+ }
+ }
+ );
+
final BiometricModalities biometricModalities = new BiometricModalities(
Utils.findFirstSensorProperties(fpProps, mConfig.mSensorIds),
Utils.findFirstSensorProperties(faceProps, mConfig.mSensorIds));
diff --git a/packages/SystemUI/src/com/android/systemui/common/data/CommonDataLayerModule.kt b/packages/SystemUI/src/com/android/systemui/common/data/CommonDataLayerModule.kt
index d1c728c..1923880 100644
--- a/packages/SystemUI/src/com/android/systemui/common/data/CommonDataLayerModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/data/CommonDataLayerModule.kt
@@ -18,19 +18,12 @@
import com.android.systemui.common.data.repository.PackageChangeRepository
import com.android.systemui.common.data.repository.PackageChangeRepositoryImpl
-import com.android.systemui.common.ui.data.repository.ConfigurationRepository
-import com.android.systemui.common.ui.data.repository.ConfigurationRepositoryImpl
import dagger.Binds
import dagger.Module
@Module
abstract class CommonDataLayerModule {
@Binds
- abstract fun bindConfigurationRepository(
- impl: ConfigurationRepositoryImpl
- ): ConfigurationRepository
-
- @Binds
abstract fun bindPackageChangeRepository(
impl: PackageChangeRepositoryImpl
): PackageChangeRepository
diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/ConfigurationStateModule.kt b/packages/SystemUI/src/com/android/systemui/common/ui/ConfigurationStateModule.kt
index b36da3b..a3735f9 100644
--- a/packages/SystemUI/src/com/android/systemui/common/ui/ConfigurationStateModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/ui/ConfigurationStateModule.kt
@@ -28,7 +28,7 @@
/**
* Annotates elements that provide information from the global configuration.
*
- * The global configuration is the one associted with the main display. Secondary displays will
+ * The global configuration is the one associated with the main display. Secondary displays will
* apply override to the global configuration. Elements annotated with this shouldn't be used for
* secondary displays.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/data/repository/ConfigurationRepository.kt b/packages/SystemUI/src/com/android/systemui/common/ui/data/repository/ConfigurationRepository.kt
index 2052c70..df89152 100644
--- a/packages/SystemUI/src/com/android/systemui/common/ui/data/repository/ConfigurationRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/ui/data/repository/ConfigurationRepository.kt
@@ -23,13 +23,17 @@
import androidx.annotation.DimenRes
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.common.ui.GlobalConfig
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.util.wrapper.DisplayUtilsWrapper
import dagger.Binds
import dagger.Module
-import javax.inject.Inject
+import dagger.Provides
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.channels.awaitClose
@@ -57,66 +61,62 @@
fun getDimensionPixelSize(id: Int): Int
}
-@SysUISingleton
class ConfigurationRepositoryImpl
-@Inject
+@AssistedInject
constructor(
- private val configurationController: ConfigurationController,
- private val context: Context,
+ @Assisted private val configurationController: ConfigurationController,
+ @Assisted private val context: Context,
@Application private val scope: CoroutineScope,
private val displayUtils: DisplayUtilsWrapper,
) : ConfigurationRepository {
private val displayInfo = MutableStateFlow(DisplayInfo())
- override val onAnyConfigurationChange: Flow<Unit> =
- conflatedCallbackFlow {
- val callback =
- object : ConfigurationController.ConfigurationListener {
- override fun onUiModeChanged() {
- sendUpdate("ConfigurationRepository#onUiModeChanged")
- }
-
- override fun onThemeChanged() {
- sendUpdate("ConfigurationRepository#onThemeChanged")
- }
-
- override fun onConfigChanged(newConfig: Configuration) {
- sendUpdate("ConfigurationRepository#onConfigChanged")
- }
-
- fun sendUpdate(reason: String) {
- trySendWithFailureLogging(Unit, reason)
- }
+ override val onAnyConfigurationChange: Flow<Unit> = conflatedCallbackFlow {
+ val callback =
+ object : ConfigurationController.ConfigurationListener {
+ override fun onUiModeChanged() {
+ sendUpdate("ConfigurationRepository#onUiModeChanged")
}
- configurationController.addCallback(callback)
- awaitClose { configurationController.removeCallback(callback) }
- }
- override val onConfigurationChange: Flow<Unit> =
- conflatedCallbackFlow {
- val callback =
- object : ConfigurationController.ConfigurationListener {
- override fun onConfigChanged(newConfig: Configuration) {
- trySendWithFailureLogging(Unit, "ConfigurationRepository#onConfigChanged")
- }
+ override fun onThemeChanged() {
+ sendUpdate("ConfigurationRepository#onThemeChanged")
}
- configurationController.addCallback(callback)
- awaitClose { configurationController.removeCallback(callback) }
- }
- override val configurationValues: Flow<Configuration> =
- conflatedCallbackFlow {
- val callback =
- object : ConfigurationController.ConfigurationListener {
- override fun onConfigChanged(newConfig: Configuration) {
- trySend(newConfig)
- }
- }
+ override fun onConfigChanged(newConfig: Configuration) {
+ sendUpdate("ConfigurationRepository#onConfigChanged")
+ }
- trySend(context.resources.configuration)
- configurationController.addCallback(callback)
- awaitClose { configurationController.removeCallback(callback) }
+ fun sendUpdate(reason: String) {
+ trySendWithFailureLogging(Unit, reason)
+ }
}
+ configurationController.addCallback(callback)
+ awaitClose { configurationController.removeCallback(callback) }
+ }
+
+ override val onConfigurationChange: Flow<Unit> = conflatedCallbackFlow {
+ val callback =
+ object : ConfigurationController.ConfigurationListener {
+ override fun onConfigChanged(newConfig: Configuration) {
+ trySendWithFailureLogging(Unit, "ConfigurationRepository#onConfigChanged")
+ }
+ }
+ configurationController.addCallback(callback)
+ awaitClose { configurationController.removeCallback(callback) }
+ }
+
+ override val configurationValues: Flow<Configuration> = conflatedCallbackFlow {
+ val callback =
+ object : ConfigurationController.ConfigurationListener {
+ override fun onConfigChanged(newConfig: Configuration) {
+ trySend(newConfig)
+ }
+ }
+
+ trySend(context.resources.configuration)
+ configurationController.addCallback(callback)
+ awaitClose { configurationController.removeCallback(callback) }
+ }
override val scaleForResolution: StateFlow<Float> =
onConfigurationChange
@@ -134,7 +134,7 @@
maxDisplayMode.physicalWidth,
maxDisplayMode.physicalHeight,
displayInfo.value.naturalWidth,
- displayInfo.value.naturalHeight
+ displayInfo.value.naturalHeight,
)
return if (scaleFactor == Float.POSITIVE_INFINITY) 1f else scaleFactor
}
@@ -144,9 +144,40 @@
override fun getDimensionPixelSize(@DimenRes id: Int): Int {
return context.resources.getDimensionPixelSize(id)
}
+
+ @AssistedFactory
+ interface Factory {
+ fun create(
+ context: Context,
+ configurationController: ConfigurationController,
+ ): ConfigurationRepositoryImpl
+ }
}
@Module
-interface ConfigurationRepositoryModule {
- @Binds fun bindImpl(impl: ConfigurationRepositoryImpl): ConfigurationRepository
+abstract class ConfigurationRepositoryModule {
+
+ /**
+ * For compatibility reasons. Ideally, only the an annotated [ConfigurationRepository] should be
+ * injected.
+ */
+ @Binds
+ @Deprecated("Use the ConfigurationRepository annotated with @GlobalConfig instead.")
+ @SysUISingleton
+ abstract fun provideDefaultConfigRepository(
+ @GlobalConfig configurationRepository: ConfigurationRepository
+ ): ConfigurationRepository
+
+ companion object {
+ @Provides
+ @GlobalConfig
+ @SysUISingleton
+ fun provideGlobalConfigRepository(
+ context: Context,
+ @GlobalConfig configurationController: ConfigurationController,
+ factory: ConfigurationRepositoryImpl.Factory,
+ ): ConfigurationRepository {
+ return factory.create(context, configurationController)
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractor.kt b/packages/SystemUI/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractor.kt
index adb1ee2..eb423d6 100644
--- a/packages/SystemUI/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractor.kt
@@ -32,6 +32,7 @@
import kotlinx.coroutines.flow.onStart
/** Business logic related to configuration changes. */
+// TODO: b/374267505 - Create a @ShadeDisplayWindow annotated version of this.
@SysUISingleton
class ConfigurationInteractor @Inject constructor(private val repository: ConfigurationRepository) {
/**
diff --git a/packages/SystemUI/src/com/android/systemui/communal/smartspace/CommunalSmartspaceController.kt b/packages/SystemUI/src/com/android/systemui/communal/smartspace/CommunalSmartspaceController.kt
index 012c844..b80e77c 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/smartspace/CommunalSmartspaceController.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/smartspace/CommunalSmartspaceController.kt
@@ -19,13 +19,13 @@
import android.app.smartspace.SmartspaceConfig
import android.app.smartspace.SmartspaceManager
import android.app.smartspace.SmartspaceSession
-import android.content.Context
import android.util.Log
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.plugins.BcSmartspaceDataPlugin
import com.android.systemui.plugins.BcSmartspaceDataPlugin.SmartspaceTargetListener
import com.android.systemui.plugins.BcSmartspaceDataPlugin.UI_SURFACE_GLANCEABLE_HUB
+import com.android.systemui.settings.UserTracker
import com.android.systemui.smartspace.SmartspacePrecondition
import com.android.systemui.smartspace.SmartspaceTargetFilter
import com.android.systemui.smartspace.dagger.SmartspaceModule.Companion.GLANCEABLE_HUB_SMARTSPACE_DATA_PLUGIN
@@ -42,8 +42,7 @@
class CommunalSmartspaceController
@Inject
constructor(
- private val context: Context,
- private val smartspaceManager: SmartspaceManager?,
+ private val userTracker: UserTracker,
private val execution: Execution,
@Main private val uiExecutor: Executor,
@Named(LOCKSCREEN_SMARTSPACE_PRECONDITION) private val precondition: SmartspacePrecondition,
@@ -55,6 +54,7 @@
private const val TAG = "CommunalSmartspaceCtrlr"
}
+ private var userSmartspaceManager: SmartspaceManager? = null
private var session: SmartspaceSession? = null
private val plugin: BcSmartspaceDataPlugin? = optionalPlugin.orElse(null)
private var targetFilter: SmartspaceTargetFilter? = optionalTargetFilter.orElse(null)
@@ -104,7 +104,11 @@
}
private fun connectSession() {
- if (smartspaceManager == null) {
+ if (userSmartspaceManager == null) {
+ userSmartspaceManager =
+ userTracker.userContext.getSystemService(SmartspaceManager::class.java)
+ }
+ if (userSmartspaceManager == null) {
return
}
if (plugin == null) {
@@ -119,11 +123,11 @@
}
val newSession =
- smartspaceManager.createSmartspaceSession(
- SmartspaceConfig.Builder(context, UI_SURFACE_GLANCEABLE_HUB).build()
+ userSmartspaceManager?.createSmartspaceSession(
+ SmartspaceConfig.Builder(userTracker.userContext, UI_SURFACE_GLANCEABLE_HUB).build()
)
Log.d(TAG, "Starting smartspace session for communal")
- newSession.addOnTargetsAvailableListener(uiExecutor, sessionListener)
+ newSession?.addOnTargetsAvailableListener(uiExecutor, sessionListener)
this.session = newSession
plugin?.registerSmartspaceEventNotifier { e -> session?.notifySmartspaceEvent(e) }
@@ -163,7 +167,7 @@
private fun addAndRegisterListener(
listener: SmartspaceTargetListener,
- smartspaceDataPlugin: BcSmartspaceDataPlugin?
+ smartspaceDataPlugin: BcSmartspaceDataPlugin?,
) {
execution.assertIsMainThread()
smartspaceDataPlugin?.registerListener(listener)
@@ -174,7 +178,7 @@
private fun removeAndUnregisterListener(
listener: SmartspaceTargetListener,
- smartspaceDataPlugin: BcSmartspaceDataPlugin?
+ smartspaceDataPlugin: BcSmartspaceDataPlugin?,
) {
execution.assertIsMainThread()
smartspaceDataPlugin?.unregisterListener(listener)
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/GlanceableHubWidgetManagerService.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/GlanceableHubWidgetManagerService.kt
index 4d042fc..be4be19 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/GlanceableHubWidgetManagerService.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/GlanceableHubWidgetManagerService.kt
@@ -22,6 +22,7 @@
import android.content.Intent
import android.os.IBinder
import android.os.RemoteCallbackList
+import android.os.RemoteException
import android.os.UserHandle
import android.widget.RemoteViews
import androidx.lifecycle.LifecycleService
@@ -98,7 +99,15 @@
val job =
widgetRepository.communalWidgets
- .onEach { widgets -> listener.onWidgetsUpdated(widgets) }
+ .onEach { widgets ->
+ try {
+ listener.onWidgetsUpdated(widgets)
+ } catch (e: RemoteException) {
+ logger.e({ "Error pushing widget update: $str1" }) {
+ str1 = e.localizedMessage
+ }
+ }
+ }
.launchIn(lifecycleScope)
widgetListenersRegistry.register(listener, job)
}
@@ -171,19 +180,41 @@
private fun createListener(listener: IAppWidgetHostListener): AppWidgetHostListener {
return object : AppWidgetHostListener {
override fun onUpdateProviderInfo(appWidget: AppWidgetProviderInfo?) {
- listener.onUpdateProviderInfo(appWidget)
+ try {
+ listener.onUpdateProviderInfo(appWidget)
+ } catch (e: RemoteException) {
+ logger.e({ "Error pushing on update provider info: $str1" }) {
+ str1 = e.localizedMessage
+ }
+ }
}
override fun updateAppWidget(views: RemoteViews?) {
- listener.updateAppWidget(views)
+ try {
+ listener.updateAppWidget(views)
+ } catch (e: RemoteException) {
+ logger.e({ "Error updating app widget: $str1" }) { str1 = e.localizedMessage }
+ }
}
override fun updateAppWidgetDeferred(packageName: String?, appWidgetId: Int) {
- listener.updateAppWidgetDeferred(packageName, appWidgetId)
+ try {
+ listener.updateAppWidgetDeferred(packageName, appWidgetId)
+ } catch (e: RemoteException) {
+ logger.e({ "Error updating app widget deferred: $str1" }) {
+ str1 = e.localizedMessage
+ }
+ }
}
override fun onViewDataChanged(viewId: Int) {
- listener.onViewDataChanged(viewId)
+ try {
+ listener.onViewDataChanged(viewId)
+ } catch (e: RemoteException) {
+ logger.e({ "Error pushing on view data changed: $str1" }) {
+ str1 = e.localizedMessage
+ }
+ }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index cb649f2..9138243 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -49,6 +49,7 @@
import com.android.systemui.clipboardoverlay.dagger.ClipboardOverlayModule;
import com.android.systemui.common.data.CommonDataLayerModule;
import com.android.systemui.common.ui.ConfigurationStateModule;
+import com.android.systemui.common.ui.data.repository.ConfigurationRepositoryModule;
import com.android.systemui.common.usagestats.data.CommonUsageStatsDataLayerModule;
import com.android.systemui.communal.dagger.CommunalModule;
import com.android.systemui.complication.dagger.ComplicationComponent;
@@ -212,6 +213,7 @@
CommunalModule.class,
CommonDataLayerModule.class,
ConfigurationStateModule.class,
+ ConfigurationRepositoryModule.class,
CommonUsageStatsDataLayerModule.class,
ConfigurationControllerModule.class,
ConnectivityModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/smartspace/DreamSmartspaceController.kt b/packages/SystemUI/src/com/android/systemui/dreams/smartspace/DreamSmartspaceController.kt
index 4a8c040..fd91389 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/smartspace/DreamSmartspaceController.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/smartspace/DreamSmartspaceController.kt
@@ -20,7 +20,6 @@
import android.app.smartspace.SmartspaceManager
import android.app.smartspace.SmartspaceSession
import android.app.smartspace.SmartspaceTarget
-import android.content.Context
import android.graphics.Color
import android.util.Log
import android.view.View
@@ -31,6 +30,7 @@
import com.android.systemui.plugins.BcSmartspaceDataPlugin.SmartspaceTargetListener
import com.android.systemui.plugins.BcSmartspaceDataPlugin.SmartspaceView
import com.android.systemui.plugins.BcSmartspaceDataPlugin.UI_SURFACE_DREAM
+import com.android.systemui.settings.UserTracker
import com.android.systemui.smartspace.SmartspacePrecondition
import com.android.systemui.smartspace.SmartspaceTargetFilter
import com.android.systemui.smartspace.dagger.SmartspaceModule.Companion.DREAM_SMARTSPACE_DATA_PLUGIN
@@ -44,13 +44,12 @@
import javax.inject.Inject
import javax.inject.Named
-/**
- * Controller for managing the smartspace view on the dream
- */
+/** Controller for managing the smartspace view on the dream */
@SysUISingleton
-class DreamSmartspaceController @Inject constructor(
- private val context: Context,
- private val smartspaceManager: SmartspaceManager?,
+class DreamSmartspaceController
+@Inject
+constructor(
+ private val userTracker: UserTracker,
private val execution: Execution,
@Main private val uiExecutor: Executor,
private val smartspaceViewComponentFactory: SmartspaceViewComponent.Factory,
@@ -65,6 +64,7 @@
private const val TAG = "DreamSmartspaceCtrlr"
}
+ private var userSmartspaceManager: SmartspaceManager? = null
private var session: SmartspaceSession? = null
private val weatherPlugin: BcSmartspaceDataPlugin? = optionalWeatherPlugin.orElse(null)
private val plugin: BcSmartspaceDataPlugin? = optionalPlugin.orElse(null)
@@ -78,66 +78,68 @@
// Smartspace can be used on multiple displays, such as when the user casts their screen
private var smartspaceViews = mutableSetOf<SmartspaceView>()
- var preconditionListener = object : SmartspacePrecondition.Listener {
- override fun onCriteriaChanged() {
- reloadSmartspace()
+ var preconditionListener =
+ object : SmartspacePrecondition.Listener {
+ override fun onCriteriaChanged() {
+ reloadSmartspace()
+ }
}
- }
init {
precondition.addListener(preconditionListener)
}
- var filterListener = object : SmartspaceTargetFilter.Listener {
- override fun onCriteriaChanged() {
- reloadSmartspace()
+ var filterListener =
+ object : SmartspaceTargetFilter.Listener {
+ override fun onCriteriaChanged() {
+ reloadSmartspace()
+ }
}
- }
init {
targetFilter?.addListener(filterListener)
}
- var stateChangeListener = object : View.OnAttachStateChangeListener {
- override fun onViewAttachedToWindow(v: View) {
- val view = v as SmartspaceView
- // Until there is dream color matching
- view.setPrimaryTextColor(Color.WHITE)
- smartspaceViews.add(view)
- connectSession()
- view.setDozeAmount(0f)
- }
+ var stateChangeListener =
+ object : View.OnAttachStateChangeListener {
+ override fun onViewAttachedToWindow(v: View) {
+ val view = v as SmartspaceView
+ // Until there is dream color matching
+ view.setPrimaryTextColor(Color.WHITE)
+ smartspaceViews.add(view)
+ connectSession()
+ view.setDozeAmount(0f)
+ }
- override fun onViewDetachedFromWindow(v: View) {
- smartspaceViews.remove(v as SmartspaceView)
+ override fun onViewDetachedFromWindow(v: View) {
+ smartspaceViews.remove(v as SmartspaceView)
- if (smartspaceViews.isEmpty()) {
- disconnect()
+ if (smartspaceViews.isEmpty()) {
+ disconnect()
+ }
}
}
- }
- private val sessionListener = SmartspaceSession.OnTargetsAvailableListener { targets ->
- execution.assertIsMainThread()
+ private val sessionListener =
+ SmartspaceSession.OnTargetsAvailableListener { targets ->
+ execution.assertIsMainThread()
- // The weather data plugin takes unfiltered targets and performs the filtering internally.
- weatherPlugin?.onTargetsAvailable(targets)
+ // The weather data plugin takes unfiltered targets and performs the filtering
+ // internally.
+ weatherPlugin?.onTargetsAvailable(targets)
- onTargetsAvailableUnfiltered(targets)
- val filteredTargets = targets.filter { targetFilter?.filterSmartspaceTarget(it) ?: true }
- plugin?.onTargetsAvailable(filteredTargets)
- }
+ onTargetsAvailableUnfiltered(targets)
+ val filteredTargets =
+ targets.filter { targetFilter?.filterSmartspaceTarget(it) ?: true }
+ plugin?.onTargetsAvailable(filteredTargets)
+ }
- /**
- * Constructs the weather view with custom layout and connects it to the weather plugin.
- */
+ /** Constructs the weather view with custom layout and connects it to the weather plugin. */
fun buildAndConnectWeatherView(parent: ViewGroup, customView: View?): View? {
return buildAndConnectViewWithPlugin(parent, weatherPlugin, customView)
}
- /**
- * Constructs the smartspace view and connects it to the smartspace service.
- */
+ /** Constructs the smartspace view and connects it to the smartspace service. */
fun buildAndConnectView(parent: ViewGroup): View? {
return buildAndConnectViewWithPlugin(parent, plugin, null)
}
@@ -145,7 +147,7 @@
private fun buildAndConnectViewWithPlugin(
parent: ViewGroup,
smartspaceDataPlugin: BcSmartspaceDataPlugin?,
- customView: View?
+ customView: View?,
): View? {
execution.assertIsMainThread()
@@ -163,12 +165,13 @@
private fun buildView(
parent: ViewGroup,
smartspaceDataPlugin: BcSmartspaceDataPlugin?,
- customView: View?
+ customView: View?,
): View? {
return if (smartspaceDataPlugin != null) {
- val view = smartspaceViewComponentFactory.create(parent, smartspaceDataPlugin,
- stateChangeListener, customView)
- .getView()
+ val view =
+ smartspaceViewComponentFactory
+ .create(parent, smartspaceDataPlugin, stateChangeListener, customView)
+ .getView()
if (view !is View) {
return null
}
@@ -179,12 +182,17 @@
}
private fun hasActiveSessionListeners(): Boolean {
- return smartspaceViews.isNotEmpty() || listeners.isNotEmpty() ||
+ return smartspaceViews.isNotEmpty() ||
+ listeners.isNotEmpty() ||
unfilteredListeners.isNotEmpty()
}
private fun connectSession() {
- if (smartspaceManager == null) {
+ if (userSmartspaceManager == null) {
+ userSmartspaceManager =
+ userTracker.userContext.getSystemService(SmartspaceManager::class.java)
+ }
+ if (userSmartspaceManager == null) {
return
}
if (plugin == null && weatherPlugin == null) {
@@ -198,25 +206,21 @@
return
}
- val newSession = smartspaceManager.createSmartspaceSession(
- SmartspaceConfig.Builder(context, UI_SURFACE_DREAM).build()
- )
+ val newSession =
+ userSmartspaceManager?.createSmartspaceSession(
+ SmartspaceConfig.Builder(userTracker.userContext, UI_SURFACE_DREAM).build()
+ )
Log.d(TAG, "Starting smartspace session for dream")
- newSession.addOnTargetsAvailableListener(uiExecutor, sessionListener)
+ newSession?.addOnTargetsAvailableListener(uiExecutor, sessionListener)
this.session = newSession
weatherPlugin?.registerSmartspaceEventNotifier { e -> session?.notifySmartspaceEvent(e) }
- plugin?.registerSmartspaceEventNotifier {
- e ->
- session?.notifySmartspaceEvent(e)
- }
+ plugin?.registerSmartspaceEventNotifier { e -> session?.notifySmartspaceEvent(e) }
reloadSmartspace()
}
- /**
- * Disconnects the smartspace view from the smartspace service and cleans up any resources.
- */
+ /** Disconnects the smartspace view from the smartspace service and cleans up any resources. */
private fun disconnect() {
if (hasActiveSessionListeners()) return
@@ -259,7 +263,7 @@
private fun addAndRegisterListener(
listener: SmartspaceTargetListener,
- smartspaceDataPlugin: BcSmartspaceDataPlugin?
+ smartspaceDataPlugin: BcSmartspaceDataPlugin?,
) {
execution.assertIsMainThread()
smartspaceDataPlugin?.registerListener(listener)
@@ -270,7 +274,7 @@
private fun removeAndUnregisterListener(
listener: SmartspaceTargetListener,
- smartspaceDataPlugin: BcSmartspaceDataPlugin?
+ smartspaceDataPlugin: BcSmartspaceDataPlugin?,
) {
execution.assertIsMainThread()
smartspaceDataPlugin?.unregisterListener(listener)
diff --git a/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt b/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt
index 3492365..b2fcc43 100644
--- a/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt
@@ -45,7 +45,7 @@
/** See [registerCriticalDumpable]. */
fun registerCriticalDumpable(module: Dumpable) {
- registerCriticalDumpable(module::class.java.canonicalName, module)
+ registerCriticalDumpable(module::class.java.name, module)
}
/**
@@ -62,7 +62,7 @@
/** See [registerNormalDumpable]. */
fun registerNormalDumpable(module: Dumpable) {
- registerNormalDumpable(module::class.java.canonicalName, module)
+ registerNormalDumpable(module::class.java.name, module)
}
/**
@@ -104,13 +104,10 @@
dumpables[name] = DumpableEntry(module, name, priority)
}
- /**
- * Same as the above override, but automatically uses the canonical class name as the dumpable
- * name.
- */
+ /** Same as the above override, but automatically uses the class name as the dumpable name. */
@Synchronized
fun registerDumpable(module: Dumpable) {
- registerDumpable(module::class.java.canonicalName, module)
+ registerDumpable(module::class.java.name, module)
}
/** Unregisters a previously-registered dumpable. */
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/Media3ActionFactory.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/Media3ActionFactory.kt
new file mode 100644
index 0000000..a33685b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/Media3ActionFactory.kt
@@ -0,0 +1,341 @@
+/*
+ * 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.media.controls.domain.pipeline
+
+import android.content.Context
+import android.graphics.drawable.Animatable
+import android.graphics.drawable.Drawable
+import android.media.session.MediaController
+import android.media.session.MediaSession
+import android.os.Handler
+import android.os.Looper
+import android.util.Log
+import androidx.annotation.WorkerThread
+import androidx.media.utils.MediaConstants
+import androidx.media3.common.Player
+import androidx.media3.session.CommandButton
+import androidx.media3.session.SessionCommand
+import androidx.media3.session.SessionToken
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.graphics.ImageLoader
+import com.android.systemui.media.controls.shared.MediaControlDrawables
+import com.android.systemui.media.controls.shared.MediaLogger
+import com.android.systemui.media.controls.shared.model.MediaAction
+import com.android.systemui.media.controls.shared.model.MediaButton
+import com.android.systemui.media.controls.util.MediaControllerFactory
+import com.android.systemui.media.controls.util.SessionTokenFactory
+import com.android.systemui.res.R
+import com.android.systemui.util.Assert
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.suspendCancellableCoroutine
+
+private const val TAG = "Media3ActionFactory"
+
+@SysUISingleton
+class Media3ActionFactory
+@Inject
+constructor(
+ @Application val context: Context,
+ private val imageLoader: ImageLoader,
+ private val controllerFactory: MediaControllerFactory,
+ private val tokenFactory: SessionTokenFactory,
+ private val logger: MediaLogger,
+ @Background private val looper: Looper,
+ @Background private val handler: Handler,
+ @Background private val bgScope: CoroutineScope,
+) {
+
+ /**
+ * Generates action button info for this media session based on the Media3 session info
+ *
+ * @param packageName Package name for the media app
+ * @param controller The framework [MediaController] for the session
+ * @return The media action buttons, or null if the session token is null
+ */
+ suspend fun createActionsFromSession(
+ packageName: String,
+ sessionToken: MediaSession.Token,
+ ): MediaButton? {
+ // Get the Media3 controller using the legacy token
+ val token = tokenFactory.createTokenFromLegacy(sessionToken)
+ val m3controller = controllerFactory.create(token, looper)
+
+ // Build button info
+ val buttons = suspendCancellableCoroutine { continuation ->
+ // Media3Controller methods must always be called from a specific looper
+ handler.post {
+ val result = getMedia3Actions(packageName, m3controller, token)
+ m3controller.release()
+ continuation.resumeWith(Result.success(result))
+ }
+ }
+ return buttons
+ }
+
+ /** This method must be called on the Media3 looper! */
+ @WorkerThread
+ private fun getMedia3Actions(
+ packageName: String,
+ m3controller: androidx.media3.session.MediaController,
+ token: SessionToken,
+ ): MediaButton? {
+ Assert.isNotMainThread()
+
+ // First, get standard actions
+ val playOrPause =
+ if (m3controller.playbackState == Player.STATE_BUFFERING) {
+ // Spinner needs to be animating to render anything. Start it here.
+ val drawable =
+ context.getDrawable(com.android.internal.R.drawable.progress_small_material)
+ (drawable as Animatable).start()
+ MediaAction(
+ drawable,
+ null, // no action to perform when clicked
+ context.getString(R.string.controls_media_button_connecting),
+ context.getDrawable(R.drawable.ic_media_connecting_container),
+ // Specify a rebind id to prevent the spinner from restarting on later binds.
+ com.android.internal.R.drawable.progress_small_material,
+ )
+ } else {
+ getStandardAction(m3controller, token, Player.COMMAND_PLAY_PAUSE)
+ }
+
+ val prevButton =
+ getStandardAction(
+ m3controller,
+ token,
+ Player.COMMAND_SEEK_TO_PREVIOUS,
+ Player.COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM,
+ )
+ val nextButton =
+ getStandardAction(
+ m3controller,
+ token,
+ Player.COMMAND_SEEK_TO_NEXT,
+ Player.COMMAND_SEEK_TO_NEXT_MEDIA_ITEM,
+ )
+
+ // Then, get custom actions
+ var customActions =
+ m3controller.customLayout
+ .asSequence()
+ .filter {
+ it.isEnabled &&
+ it.sessionCommand?.commandCode == SessionCommand.COMMAND_CODE_CUSTOM &&
+ m3controller.isSessionCommandAvailable(it.sessionCommand!!)
+ }
+ .map { getCustomAction(packageName, token, it) }
+ .iterator()
+ fun nextCustomAction() = if (customActions.hasNext()) customActions.next() else null
+
+ // Finally, assign the remaining button slots: play/pause A B C D
+ // A = previous, else custom action (if not reserved)
+ // B = next, else custom action (if not reserved)
+ // C and D are always custom actions
+ val reservePrev =
+ m3controller.sessionExtras.getBoolean(
+ MediaConstants.SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_PREV,
+ false,
+ )
+ val reserveNext =
+ m3controller.sessionExtras.getBoolean(
+ MediaConstants.SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_NEXT,
+ false,
+ )
+
+ val prevOrCustom =
+ prevButton
+ ?: if (reservePrev) {
+ null
+ } else {
+ nextCustomAction()
+ }
+
+ val nextOrCustom =
+ nextButton
+ ?: if (reserveNext) {
+ null
+ } else {
+ nextCustomAction()
+ }
+
+ return MediaButton(
+ playOrPause = playOrPause,
+ nextOrCustom = nextOrCustom,
+ prevOrCustom = prevOrCustom,
+ custom0 = nextCustomAction(),
+ custom1 = nextCustomAction(),
+ reserveNext = reserveNext,
+ reservePrev = reservePrev,
+ )
+ }
+
+ /**
+ * Create a [MediaAction] for a given command, if supported
+ *
+ * @param controller Media3 controller for the session
+ * @param commands Commands to check, in priority order
+ * @return A [MediaAction] representing the first supported command, or null if not supported
+ */
+ private fun getStandardAction(
+ controller: androidx.media3.session.MediaController,
+ token: SessionToken,
+ vararg commands: @Player.Command Int,
+ ): MediaAction? {
+ for (command in commands) {
+ if (!controller.isCommandAvailable(command)) {
+ continue
+ }
+
+ return when (command) {
+ Player.COMMAND_PLAY_PAUSE -> {
+ if (!controller.isPlaying) {
+ MediaAction(
+ context.getDrawable(R.drawable.ic_media_play),
+ { executeAction(token, Player.COMMAND_PLAY_PAUSE) },
+ context.getString(R.string.controls_media_button_play),
+ context.getDrawable(R.drawable.ic_media_play_container),
+ )
+ } else {
+ MediaAction(
+ context.getDrawable(R.drawable.ic_media_pause),
+ { executeAction(token, Player.COMMAND_PLAY_PAUSE) },
+ context.getString(R.string.controls_media_button_pause),
+ context.getDrawable(R.drawable.ic_media_pause_container),
+ )
+ }
+ }
+ else -> {
+ MediaAction(
+ icon = getIconForAction(command),
+ action = { executeAction(token, command) },
+ contentDescription = getDescriptionForAction(command),
+ background = null,
+ )
+ }
+ }
+ }
+ return null
+ }
+
+ /** Get a [MediaAction] representing a [CommandButton] */
+ private fun getCustomAction(
+ packageName: String,
+ token: SessionToken,
+ customAction: CommandButton,
+ ): MediaAction {
+ return MediaAction(
+ getIconForAction(customAction, packageName),
+ { executeAction(token, Player.COMMAND_INVALID, customAction) },
+ customAction.displayName,
+ null,
+ )
+ }
+
+ private fun getIconForAction(command: @Player.Command Int): Drawable? {
+ return when (command) {
+ Player.COMMAND_SEEK_TO_PREVIOUS -> MediaControlDrawables.getPrevIcon(context)
+ Player.COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM -> MediaControlDrawables.getPrevIcon(context)
+ Player.COMMAND_SEEK_TO_NEXT -> MediaControlDrawables.getNextIcon(context)
+ Player.COMMAND_SEEK_TO_NEXT_MEDIA_ITEM -> MediaControlDrawables.getNextIcon(context)
+ else -> {
+ Log.e(TAG, "Unknown icon for $command")
+ null
+ }
+ }
+ }
+
+ private fun getIconForAction(customAction: CommandButton, packageName: String): Drawable? {
+ val size = context.resources.getDimensionPixelSize(R.dimen.min_clickable_item_size)
+ // TODO(b/360196209): check customAction.icon field to use platform icons
+ if (customAction.iconResId != 0) {
+ val packageContext = context.createPackageContext(packageName, 0)
+ val source = ImageLoader.Res(customAction.iconResId, packageContext)
+ return runBlocking { imageLoader.loadDrawable(source, size, size) }
+ }
+
+ if (customAction.iconUri != null) {
+ val source = ImageLoader.Uri(customAction.iconUri!!)
+ return runBlocking { imageLoader.loadDrawable(source, size, size) }
+ }
+ return null
+ }
+
+ private fun getDescriptionForAction(command: @Player.Command Int): String? {
+ return when (command) {
+ Player.COMMAND_SEEK_TO_PREVIOUS ->
+ context.getString(R.string.controls_media_button_prev)
+ Player.COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM ->
+ context.getString(R.string.controls_media_button_prev)
+ Player.COMMAND_SEEK_TO_NEXT -> context.getString(R.string.controls_media_button_next)
+ Player.COMMAND_SEEK_TO_NEXT_MEDIA_ITEM ->
+ context.getString(R.string.controls_media_button_next)
+ else -> {
+ Log.e(TAG, "Unknown content description for $command")
+ null
+ }
+ }
+ }
+
+ private fun executeAction(
+ token: SessionToken,
+ command: Int,
+ customAction: CommandButton? = null,
+ ) {
+ bgScope.launch {
+ val controller = controllerFactory.create(token, looper)
+ handler.post {
+ when (command) {
+ Player.COMMAND_PLAY_PAUSE -> {
+ if (controller.isPlaying) controller.pause() else controller.play()
+ }
+
+ Player.COMMAND_SEEK_TO_PREVIOUS -> controller.seekToPrevious()
+ Player.COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM ->
+ controller.seekToPreviousMediaItem()
+
+ Player.COMMAND_SEEK_TO_NEXT -> controller.seekToNext()
+ Player.COMMAND_SEEK_TO_NEXT_MEDIA_ITEM -> controller.seekToNextMediaItem()
+ Player.COMMAND_INVALID -> {
+ if (
+ customAction != null &&
+ customAction!!.sessionCommand != null &&
+ controller.isSessionCommandAvailable(
+ customAction!!.sessionCommand!!
+ )
+ ) {
+ controller.sendCustomCommand(
+ customAction!!.sessionCommand!!,
+ customAction!!.extras,
+ )
+ } else {
+ logger.logMedia3UnsupportedCommand("$command, action $customAction")
+ }
+ }
+
+ else -> logger.logMedia3UnsupportedCommand(command.toString())
+ }
+ controller.release()
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoader.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoader.kt
index 591a9cc..a176e0c 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoader.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoader.kt
@@ -84,6 +84,7 @@
private val mediaFlags: MediaFlags,
private val imageLoader: ImageLoader,
private val statusBarManager: StatusBarManager,
+ private val media3ActionFactory: Media3ActionFactory,
) {
private val mediaProcessingJobs = ConcurrentHashMap<String, Job>()
@@ -364,7 +365,7 @@
)
}
- private fun createActionsFromState(
+ private suspend fun createActionsFromState(
packageName: String,
controller: MediaController,
user: UserHandle,
@@ -373,6 +374,12 @@
return null
}
+ if (mediaFlags.areMedia3ActionsEnabled(packageName, user)) {
+ return media3ActionFactory.createActionsFromSession(
+ packageName,
+ controller.sessionToken,
+ )
+ }
return createActionsFromState(context, packageName, controller)
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaProcessingHelper.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaProcessingHelper.kt
index 2bdee67..beb4d41 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaProcessingHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaProcessingHelper.kt
@@ -141,6 +141,7 @@
new: MediaData,
old: MediaData,
): Boolean {
+ // TODO(b/360196209): account for actions generated from media3
val oldState = MediaController(context, old.token!!).playbackState
return if (
new.semanticActions == null &&
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/shared/MediaLogger.kt b/packages/SystemUI/src/com/android/systemui/media/controls/shared/MediaLogger.kt
index 88c47ba..0b598c1 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/shared/MediaLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/shared/MediaLogger.kt
@@ -140,6 +140,10 @@
)
}
+ fun logMedia3UnsupportedCommand(command: String) {
+ buffer.log(TAG, LogLevel.DEBUG, { str1 = command }, { "Unsupported media3 command $str1" })
+ }
+
companion object {
private const val TAG = "MediaLog"
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaControllerFactory.java b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaControllerFactory.java
deleted file mode 100644
index 6caf5c2..0000000
--- a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaControllerFactory.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.media.controls.util;
-
-import android.annotation.NonNull;
-import android.content.Context;
-import android.media.session.MediaController;
-import android.media.session.MediaSession;
-
-import javax.inject.Inject;
-
-/**
- * Testable wrapper around {@link MediaController} constructor.
- */
-public class MediaControllerFactory {
-
- private final Context mContext;
-
- @Inject
- public MediaControllerFactory(Context context) {
- mContext = context;
- }
-
- /**
- * Creates a new MediaController from a session's token.
- *
- * @param token The token for the session. This value must never be null.
- */
- public MediaController create(@NonNull MediaSession.Token token) {
- return new MediaController(mContext, token);
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaControllerFactory.kt b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaControllerFactory.kt
new file mode 100644
index 0000000..741f529
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaControllerFactory.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.media.controls.util
+
+import android.content.Context
+import android.media.session.MediaController
+import android.media.session.MediaSession
+import android.os.Looper
+import androidx.concurrent.futures.await
+import androidx.media3.session.MediaController as Media3Controller
+import androidx.media3.session.SessionToken
+import javax.inject.Inject
+
+/** Testable wrapper for media controller construction */
+open class MediaControllerFactory @Inject constructor(private val context: Context) {
+ /**
+ * Creates a new [MediaController] from the framework session token.
+ *
+ * @param token The token for the session. This value must never be null.
+ */
+ open fun create(token: MediaSession.Token): MediaController {
+ return MediaController(context, token)
+ }
+
+ /** Creates a new [Media3Controller] from a [SessionToken] */
+ open suspend fun create(token: SessionToken, looper: Looper): Media3Controller {
+ return Media3Controller.Builder(context, token)
+ .setApplicationLooper(looper)
+ .buildAsync()
+ .await()
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt
index d4af1b5..ac60c47 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt
@@ -18,9 +18,10 @@
import android.app.StatusBarManager
import android.os.UserHandle
+import com.android.systemui.Flags
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.flags.FeatureFlagsClassic
-import com.android.systemui.flags.Flags
+import com.android.systemui.flags.Flags as FlagsClassic
import javax.inject.Inject
@SysUISingleton
@@ -29,22 +30,29 @@
* Check whether media control actions should be based on PlaybackState instead of notification
*/
fun areMediaSessionActionsEnabled(packageName: String, user: UserHandle): Boolean {
- // Allow global override with flag
return StatusBarManager.useMediaSessionActionsForApp(packageName, user)
}
+ /** Check whether media control actions should be derived from Media3 controller */
+ fun areMedia3ActionsEnabled(packageName: String, user: UserHandle): Boolean {
+ val compatFlag = StatusBarManager.useMedia3ControllerForApp(packageName, user)
+ val featureFlag = Flags.mediaControlsButtonMedia3()
+ return featureFlag && compatFlag
+ }
+
/**
* If true, keep active media controls for the lifetime of the MediaSession, regardless of
* whether the underlying notification was dismissed
*/
- fun isRetainingPlayersEnabled() = featureFlags.isEnabled(Flags.MEDIA_RETAIN_SESSIONS)
+ fun isRetainingPlayersEnabled() = featureFlags.isEnabled(FlagsClassic.MEDIA_RETAIN_SESSIONS)
/** Check whether to get progress information for resume players */
- fun isResumeProgressEnabled() = featureFlags.isEnabled(Flags.MEDIA_RESUME_PROGRESS)
+ fun isResumeProgressEnabled() = featureFlags.isEnabled(FlagsClassic.MEDIA_RESUME_PROGRESS)
/** If true, do not automatically dismiss the recommendation card */
- fun isPersistentSsCardEnabled() = featureFlags.isEnabled(Flags.MEDIA_RETAIN_RECOMMENDATIONS)
+ fun isPersistentSsCardEnabled() =
+ featureFlags.isEnabled(FlagsClassic.MEDIA_RETAIN_RECOMMENDATIONS)
/** Check whether we allow remote media to generate resume controls */
- fun isRemoteResumeAllowed() = featureFlags.isEnabled(Flags.MEDIA_REMOTE_RESUME)
+ fun isRemoteResumeAllowed() = featureFlags.isEnabled(FlagsClassic.MEDIA_REMOTE_RESUME)
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/util/SessionTokenFactory.kt b/packages/SystemUI/src/com/android/systemui/media/controls/util/SessionTokenFactory.kt
new file mode 100644
index 0000000..b289fd4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/util/SessionTokenFactory.kt
@@ -0,0 +1,31 @@
+/*
+ * 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.media.controls.util
+
+import android.content.Context
+import android.media.session.MediaSession
+import androidx.concurrent.futures.await
+import androidx.media3.session.SessionToken
+import javax.inject.Inject
+
+/** Testable wrapper for [SessionToken] creation */
+open class SessionTokenFactory @Inject constructor(private val context: Context) {
+ /** Create a new [SessionToken] from the framework [MediaSession.Token] */
+ open suspend fun createTokenFromLegacy(token: MediaSession.Token): SessionToken {
+ return SessionToken.createSessionToken(context, token).await()
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBar.java
index 96c0cac..40613c0 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBar.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBar.java
@@ -149,6 +149,7 @@
import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.NotificationShadeDepthController;
import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.data.repository.LightBarControllerStore;
import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
import com.android.systemui.statusbar.phone.AutoHideController;
import com.android.systemui.statusbar.phone.CentralSurfaces;
@@ -258,8 +259,7 @@
private boolean mTransientShownFromGestureOnSystemBar;
private int mNavBarMode = NAV_BAR_MODE_3BUTTON;
private LightBarController mLightBarController;
- private final LightBarController mMainLightBarController;
- private final LightBarController.Factory mLightBarControllerFactory;
+ private final LightBarControllerStore mLightBarControllerStore;
private AutoHideController mAutoHideController;
private final AutoHideController mMainAutoHideController;
private final AutoHideController.Factory mAutoHideControllerFactory;
@@ -580,8 +580,7 @@
@Background Executor bgExecutor,
UiEventLogger uiEventLogger,
NavBarHelper navBarHelper,
- LightBarController mainLightBarController,
- LightBarController.Factory lightBarControllerFactory,
+ LightBarControllerStore lightBarControllerStore,
AutoHideController mainAutoHideController,
AutoHideController.Factory autoHideControllerFactory,
Optional<TelecomManager> telecomManagerOptional,
@@ -628,8 +627,7 @@
mUiEventLogger = uiEventLogger;
mNavBarHelper = navBarHelper;
mNotificationShadeDepthController = notificationShadeDepthController;
- mMainLightBarController = mainLightBarController;
- mLightBarControllerFactory = lightBarControllerFactory;
+ mLightBarControllerStore = lightBarControllerStore;
mMainAutoHideController = mainAutoHideController;
mAutoHideControllerFactory = autoHideControllerFactory;
mTelecomManagerOptional = telecomManagerOptional;
@@ -842,8 +840,7 @@
// Unfortunately, we still need it because status bar needs LightBarController
// before notifications creation. We cannot directly use getLightBarController()
// from NavigationBarFragment directly.
- LightBarController lightBarController = mIsOnDefaultDisplay
- ? mMainLightBarController : mLightBarControllerFactory.create(mContext);
+ LightBarController lightBarController = mLightBarControllerStore.forDisplay(mDisplayId);
setLightBarController(lightBarController);
// TODO(b/118592525): to support multi-display, we start to add something which is
diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/adapter/QSSceneAdapter.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/adapter/QSSceneAdapter.kt
index 6d5bf32..e922e09 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/ui/adapter/QSSceneAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/ui/adapter/QSSceneAdapter.kt
@@ -22,6 +22,7 @@
import android.view.View
import androidx.annotation.VisibleForTesting
import androidx.asynclayoutinflater.view.AsyncLayoutInflater
+import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.settingslib.applications.InterestingConfigChanges
import com.android.systemui.Dumpable
import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor
@@ -57,7 +58,6 @@
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.update
-import com.android.app.tracing.coroutines.launchTraced as launch
import kotlinx.coroutines.withContext
// TODO(307945185) Split View concerns into a ViewBinder
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelUnfoldAnimationController.kt b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelUnfoldAnimationController.kt
index 6e63446..1776a2c 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelUnfoldAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelUnfoldAnimationController.kt
@@ -18,14 +18,14 @@
import android.content.Context
import android.view.ViewGroup
-import com.android.systemui.res.R
import com.android.systemui.plugins.statusbar.StatusBarStateController
-import com.android.systemui.statusbar.StatusBarState.SHADE
-import com.android.systemui.statusbar.StatusBarState.SHADE_LOCKED
+import com.android.systemui.res.R
import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator
import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator.Direction.END
import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator.Direction.START
import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator.ViewIdToTranslate
+import com.android.systemui.statusbar.StatusBarState.SHADE
+import com.android.systemui.statusbar.StatusBarState.SHADE_LOCKED
import com.android.systemui.unfold.SysUIUnfoldScope
import com.android.systemui.unfold.util.NaturalRotationUnfoldProgressProvider
import javax.inject.Inject
@@ -39,8 +39,10 @@
progressProvider: NaturalRotationUnfoldProgressProvider,
) {
- private val filterShade: () -> Boolean = { statusBarStateController.getState() == SHADE ||
- statusBarStateController.getState() == SHADE_LOCKED }
+ private val filterShade: () -> Boolean = {
+ statusBarStateController.getState() == SHADE ||
+ statusBarStateController.getState() == SHADE_LOCKED
+ }
private val translateAnimator by lazy {
UnfoldConstantTranslateAnimator(
@@ -48,21 +50,23 @@
setOf(
ViewIdToTranslate(R.id.quick_settings_panel, START, filterShade),
ViewIdToTranslate(R.id.qs_footer_actions, START, filterShade),
- ViewIdToTranslate(R.id.notification_stack_scroller, END, filterShade)),
- progressProvider = progressProvider)
+ ViewIdToTranslate(R.id.notification_stack_scroller, END, filterShade),
+ ),
+ progressProvider = progressProvider,
+ )
}
private val translateAnimatorStatusBar by lazy {
UnfoldConstantTranslateAnimator(
viewsIdToTranslate =
- setOf(
- ViewIdToTranslate(R.id.shade_header_system_icons, END, filterShade),
- ViewIdToTranslate(R.id.privacy_container, END, filterShade),
- ViewIdToTranslate(R.id.carrier_group, END, filterShade),
- ViewIdToTranslate(R.id.clock, START, filterShade),
- ViewIdToTranslate(R.id.date, START, filterShade)
- ),
- progressProvider = progressProvider
+ setOf(
+ ViewIdToTranslate(R.id.shade_header_system_icons, END, filterShade),
+ ViewIdToTranslate(R.id.privacy_container, END, filterShade),
+ ViewIdToTranslate(R.id.carrier_group, END, filterShade),
+ ViewIdToTranslate(R.id.clock, START, filterShade),
+ ViewIdToTranslate(R.id.date, START, filterShade),
+ ),
+ progressProvider = progressProvider,
)
}
@@ -73,10 +77,7 @@
val splitShadeStatusBarViewGroup: ViewGroup? =
root.findViewById(R.id.split_shade_status_bar)
if (splitShadeStatusBarViewGroup != null) {
- translateAnimatorStatusBar.init(
- splitShadeStatusBarViewGroup,
- translationMax
- )
+ translateAnimatorStatusBar.init(splitShadeStatusBarViewGroup, translationMax)
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 0e82bf8..c15c8f9 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -2053,6 +2053,9 @@
}
if (mQsController.getExpanded()) {
mQsController.flingQs(0, FLING_COLLAPSE);
+ } else if (mBarState == KEYGUARD) {
+ mLockscreenShadeTransitionController.goToLockedShade(
+ /* expandedView= */null, /* needsQSAnimation= */false);
} else {
expand(true /* animate */);
}
@@ -3109,7 +3112,7 @@
if (isTracking()) {
onTrackingStopped(true);
}
- if (isExpanded() && !mQsController.getExpanded()) {
+ if (isExpanded() && mBarState != KEYGUARD && !mQsController.getExpanded()) {
mShadeLog.d("Status Bar was long pressed. Expanding to QS.");
expandToQs();
} else {
@@ -5091,13 +5094,6 @@
}
boolean handled = mHeadsUpTouchHelper.onTouchEvent(event);
- if (!mHeadsUpTouchHelper.isTrackingHeadsUp() && mQsController.handleTouch(
- event, isFullyCollapsed(), isShadeOrQsHeightAnimationRunning())) {
- if (event.getActionMasked() != MotionEvent.ACTION_MOVE) {
- mShadeLog.logMotionEvent(event, "onTouch: handleQsTouch handled event");
- }
- return true;
- }
// This touch session has already resulted in shade expansion. Ignore everything else.
if (ShadeExpandsOnStatusBarLongPress.isEnabled()
&& event.getActionMasked() != MotionEvent.ACTION_DOWN
@@ -5105,6 +5101,13 @@
mShadeLog.d("Touch has same down time as Status Bar long press. Ignoring.");
return false;
}
+ if (!mHeadsUpTouchHelper.isTrackingHeadsUp() && mQsController.handleTouch(
+ event, isFullyCollapsed(), isShadeOrQsHeightAnimationRunning())) {
+ if (event.getActionMasked() != MotionEvent.ACTION_MOVE) {
+ mShadeLog.logMotionEvent(event, "onTouch: handleQsTouch handled event");
+ }
+ return true;
+ }
if (event.getActionMasked() == MotionEvent.ACTION_DOWN && isFullyCollapsed()) {
mMetricsLogger.count(COUNTER_PANEL_OPEN, 1);
handled = true;
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt
index a8026cd..6fb9b1f 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt
@@ -20,7 +20,11 @@
import android.content.res.Resources
import android.view.LayoutInflater
import android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
+import com.android.systemui.common.ui.ConfigurationState
+import com.android.systemui.common.ui.ConfigurationStateImpl
import com.android.systemui.common.ui.GlobalConfig
+import com.android.systemui.common.ui.data.repository.ConfigurationRepository
+import com.android.systemui.common.ui.data.repository.ConfigurationRepositoryImpl
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.res.R
import com.android.systemui.shade.shared.flag.ShadeWindowGoesAround
@@ -92,9 +96,41 @@
@ShadeDisplayAware
@SysUISingleton
fun provideShadeWindowConfigurationForwarder(
- @ShadeDisplayAware shadeConfigurationController: ConfigurationController,
+ @ShadeDisplayAware shadeConfigurationController: ConfigurationController
): ConfigurationForwarder {
ShadeWindowGoesAround.isUnexpectedlyInLegacyMode()
return shadeConfigurationController
}
+
+ @SysUISingleton
+ @Provides
+ @ShadeDisplayAware
+ fun provideShadeDisplayAwareConfigurationState(
+ factory: ConfigurationStateImpl.Factory,
+ @ShadeDisplayAware configurationController: ConfigurationController,
+ @ShadeDisplayAware context: Context,
+ @GlobalConfig configurationState: ConfigurationState,
+ ): ConfigurationState {
+ return if (ShadeWindowGoesAround.isEnabled) {
+ factory.create(context, configurationController)
+ } else {
+ configurationState
+ }
+ }
+
+ @SysUISingleton
+ @Provides
+ @ShadeDisplayAware
+ fun provideShadeDisplayAwareConfigurationRepository(
+ factory: ConfigurationRepositoryImpl.Factory,
+ @ShadeDisplayAware configurationController: ConfigurationController,
+ @ShadeDisplayAware context: Context,
+ @GlobalConfig globalConfigurationRepository: ConfigurationRepository,
+ ): ConfigurationRepository {
+ return if (ShadeWindowGoesAround.isEnabled) {
+ factory.create(context, configurationController)
+ } else {
+ globalConfigurationRepository
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt
index 72a4650..2348a11 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt
@@ -49,10 +49,7 @@
import javax.inject.Provider
/** Module for classes related to the notification shade. */
-@Module(
- includes =
- [StartShadeModule::class, ShadeViewProviderModule::class]
-)
+@Module(includes = [StartShadeModule::class, ShadeViewProviderModule::class])
abstract class ShadeModule {
companion object {
@Provides
diff --git a/packages/SystemUI/src/com/android/systemui/shade/LongPressGestureDetector.kt b/packages/SystemUI/src/com/android/systemui/shade/StatusBarLongPressGestureDetector.kt
similarity index 97%
rename from packages/SystemUI/src/com/android/systemui/shade/LongPressGestureDetector.kt
rename to packages/SystemUI/src/com/android/systemui/shade/StatusBarLongPressGestureDetector.kt
index 6fb3ca5..ae36e81 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/LongPressGestureDetector.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/StatusBarLongPressGestureDetector.kt
@@ -25,7 +25,7 @@
/** Accepts touch events, detects long press, and calls ShadeViewController#onStatusBarLongPress. */
@SysUISingleton
-class LongPressGestureDetector
+class StatusBarLongPressGestureDetector
@Inject
constructor(context: Context, val shadeViewController: ShadeViewController) {
val gestureDetector =
diff --git a/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt b/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt
index 5629938..ef62d2d 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt
@@ -15,9 +15,7 @@
*/
package com.android.systemui.shade.data.repository
-import android.content.Context
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
import javax.inject.Inject
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
@@ -182,8 +180,7 @@
/** Business logic for shade interactions */
@SysUISingleton
-class ShadeRepositoryImpl @Inject constructor() :
- ShadeRepository {
+class ShadeRepositoryImpl @Inject constructor() : ShadeRepository {
private val _qsExpansion = MutableStateFlow(0f)
@Deprecated("Use ShadeInteractor.qsExpansion instead")
override val qsExpansion: StateFlow<Float> = _qsExpansion.asStateFlow()
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/startable/ShadeStartable.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/startable/ShadeStartable.kt
index e5d08a0..44f2911 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/startable/ShadeStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/startable/ShadeStartable.kt
@@ -17,6 +17,7 @@
package com.android.systemui.shade.domain.startable
import android.content.Context
+import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.systemui.CoreStartable
import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor
import com.android.systemui.common.ui.data.repository.ConfigurationRepository
@@ -42,7 +43,6 @@
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onStart
-import com.android.app.tracing.coroutines.launchTraced as launch
@SysUISingleton
class ShadeStartable
@@ -51,7 +51,7 @@
@Application private val applicationScope: CoroutineScope,
@ShadeDisplayAware private val context: Context,
@ShadeTouchLog private val touchLog: LogBuffer,
- private val configurationRepository: ConfigurationRepository,
+ @ShadeDisplayAware private val configurationRepository: ConfigurationRepository,
private val shadeRepository: ShadeRepository,
private val splitShadeStateController: SplitShadeStateController,
private val scrimShadeTransitionController: ScrimShadeTransitionController,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializer.kt
index 3abbc6e..f441fd6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializer.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializer.kt
@@ -23,6 +23,7 @@
import com.android.systemui.res.R
import com.android.systemui.statusbar.core.StatusBarInitializer.OnStatusBarViewInitializedListener
import com.android.systemui.statusbar.core.StatusBarInitializer.OnStatusBarViewUpdatedListener
+import com.android.systemui.statusbar.data.repository.StatusBarModePerDisplayRepository
import com.android.systemui.statusbar.phone.PhoneStatusBarTransitions
import com.android.systemui.statusbar.phone.PhoneStatusBarView
import com.android.systemui.statusbar.phone.PhoneStatusBarViewController
@@ -71,7 +72,10 @@
}
interface Factory {
- fun create(statusBarWindowController: StatusBarWindowController): StatusBarInitializer
+ fun create(
+ statusBarWindowController: StatusBarWindowController,
+ statusBarModePerDisplayRepository: StatusBarModePerDisplayRepository,
+ ): StatusBarInitializer
}
}
@@ -79,6 +83,7 @@
@AssistedInject
constructor(
@Assisted private val statusBarWindowController: StatusBarWindowController,
+ @Assisted private val statusBarModePerDisplayRepository: StatusBarModePerDisplayRepository,
private val collapsedStatusBarFragmentProvider: Provider<CollapsedStatusBarFragment>,
private val statusBarRootFactory: StatusBarRootFactory,
private val componentFactory: HomeStatusBarComponent.Factory,
@@ -127,7 +132,7 @@
val phoneStatusBarView = cv.findViewById<PhoneStatusBarView>(R.id.status_bar)
component =
componentFactory.create(phoneStatusBarView).also { component ->
- // CollapsedStatusBarFragment used to be responsible initializting
+ // CollapsedStatusBarFragment used to be responsible initializing
component.init()
statusBarViewUpdatedListener?.onStatusBarViewUpdated(
@@ -135,8 +140,12 @@
component.phoneStatusBarTransitions,
)
- creationListeners.forEach { listener ->
- listener.onStatusBarViewInitialized(component)
+ if (StatusBarConnectedDisplays.isEnabled) {
+ statusBarModePerDisplayRepository.onStatusBarViewInitialized(component)
+ } else {
+ creationListeners.forEach { listener ->
+ listener.onStatusBarViewInitialized(component)
+ }
}
}
}
@@ -184,7 +193,8 @@
@AssistedFactory
interface Factory : StatusBarInitializer.Factory {
override fun create(
- statusBarWindowController: StatusBarWindowController
+ statusBarWindowController: StatusBarWindowController,
+ statusBarModePerDisplayRepository: StatusBarModePerDisplayRepository,
): StatusBarInitializerImpl
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializerStore.kt b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializerStore.kt
index 041f0b0..4f815c1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializerStore.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializerStore.kt
@@ -22,6 +22,7 @@
import com.android.systemui.display.data.repository.PerDisplayStore
import com.android.systemui.display.data.repository.PerDisplayStoreImpl
import com.android.systemui.display.data.repository.SingleDisplayStore
+import com.android.systemui.statusbar.data.repository.StatusBarModeRepositoryStore
import com.android.systemui.statusbar.window.StatusBarWindowControllerStore
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
@@ -37,6 +38,7 @@
displayRepository: DisplayRepository,
private val factory: StatusBarInitializer.Factory,
private val statusBarWindowControllerStore: StatusBarWindowControllerStore,
+ private val statusBarModeRepositoryStore: StatusBarModeRepositoryStore,
) :
StatusBarInitializerStore,
PerDisplayStoreImpl<StatusBarInitializer>(backgroundApplicationScope, displayRepository) {
@@ -47,7 +49,8 @@
override fun createInstanceForDisplay(displayId: Int): StatusBarInitializer {
return factory.create(
- statusBarWindowController = statusBarWindowControllerStore.forDisplay(displayId)
+ statusBarWindowController = statusBarWindowControllerStore.forDisplay(displayId),
+ statusBarModePerDisplayRepository = statusBarModeRepositoryStore.forDisplay(displayId),
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt
index f65ae67..4341200 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt
@@ -26,6 +26,7 @@
import com.android.systemui.log.LogBufferFactory
import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
import com.android.systemui.statusbar.data.StatusBarDataLayerModule
+import com.android.systemui.statusbar.data.repository.LightBarControllerStore
import com.android.systemui.statusbar.phone.LightBarController
import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider
import com.android.systemui.statusbar.phone.StatusBarContentInsetsProviderImpl
@@ -55,26 +56,26 @@
* [com.android.systemui.statusbar.policy.dagger.StatusBarPolicyModule], etc.).
*/
@Module(includes = [StatusBarDataLayerModule::class, SystemBarUtilsProxyImpl.Module::class])
-abstract class StatusBarModule {
+interface StatusBarModule {
@Binds
@IntoMap
@ClassKey(OngoingCallController::class)
- abstract fun bindOngoingCallController(impl: OngoingCallController): CoreStartable
+ fun bindOngoingCallController(impl: OngoingCallController): CoreStartable
@Binds
@IntoMap
@ClassKey(LightBarController::class)
- abstract fun bindLightBarController(impl: LightBarController): CoreStartable
+ fun lightBarControllerAsCoreStartable(controller: LightBarController): CoreStartable
@Binds
@IntoMap
@ClassKey(StatusBarSignalPolicy::class)
- abstract fun bindStatusBarSignalPolicy(impl: StatusBarSignalPolicy): CoreStartable
+ fun bindStatusBarSignalPolicy(impl: StatusBarSignalPolicy): CoreStartable
@Binds
@SysUISingleton
- abstract fun statusBarWindowControllerFactory(
+ fun statusBarWindowControllerFactory(
implFactory: StatusBarWindowControllerImpl.Factory
): StatusBarWindowController.Factory
@@ -82,6 +83,12 @@
@Provides
@SysUISingleton
+ fun lightBarController(store: LightBarControllerStore): LightBarController {
+ return store.defaultDisplay
+ }
+
+ @Provides
+ @SysUISingleton
fun windowControllerStore(
multiDisplayImplLazy: Lazy<MultiDisplayStatusBarWindowControllerStore>,
singleDisplayImplLazy: Lazy<SingleDisplayStatusBarWindowControllerStore>,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/StatusBarDataLayerModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/StatusBarDataLayerModule.kt
index f2d926f..39de28e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/data/StatusBarDataLayerModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/StatusBarDataLayerModule.kt
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.data
import com.android.systemui.statusbar.data.repository.KeyguardStatusBarRepositoryModule
+import com.android.systemui.statusbar.data.repository.LightBarControllerStoreModule
import com.android.systemui.statusbar.data.repository.RemoteInputRepositoryModule
import com.android.systemui.statusbar.data.repository.StatusBarConfigurationControllerModule
import com.android.systemui.statusbar.data.repository.StatusBarContentInsetsProviderStoreModule
@@ -28,6 +29,7 @@
includes =
[
KeyguardStatusBarRepositoryModule::class,
+ LightBarControllerStoreModule::class,
RemoteInputRepositoryModule::class,
StatusBarConfigurationControllerModule::class,
StatusBarContentInsetsProviderStoreModule::class,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/LightBarControllerStore.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/LightBarControllerStore.kt
new file mode 100644
index 0000000..ff50e31
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/LightBarControllerStore.kt
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.data.repository
+
+import com.android.systemui.CoreStartable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.display.data.repository.DisplayRepository
+import com.android.systemui.display.data.repository.DisplayScopeRepository
+import com.android.systemui.display.data.repository.PerDisplayStore
+import com.android.systemui.display.data.repository.PerDisplayStoreImpl
+import com.android.systemui.statusbar.phone.LightBarController
+import com.android.systemui.statusbar.phone.LightBarControllerImpl
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+
+/** Provides per display instances of [LightBarController]. */
+interface LightBarControllerStore : PerDisplayStore<LightBarController>
+
+@SysUISingleton
+class LightBarControllerStoreImpl
+@Inject
+constructor(
+ @Background backgroundApplicationScope: CoroutineScope,
+ displayRepository: DisplayRepository,
+ private val factory: LightBarControllerImpl.Factory,
+ private val displayScopeRepository: DisplayScopeRepository,
+ private val statusBarModeRepositoryStore: StatusBarModeRepositoryStore,
+) :
+ LightBarControllerStore,
+ PerDisplayStoreImpl<LightBarController>(backgroundApplicationScope, displayRepository) {
+
+ override fun createInstanceForDisplay(displayId: Int): LightBarController {
+ return factory
+ .create(
+ displayId,
+ displayScopeRepository.scopeForDisplay(displayId),
+ statusBarModeRepositoryStore.forDisplay(displayId),
+ )
+ .also { it.start() }
+ }
+
+ override suspend fun onDisplayRemovalAction(instance: LightBarController) {
+ instance.stop()
+ }
+
+ override val instanceClass = LightBarController::class.java
+}
+
+@Module
+interface LightBarControllerStoreModule {
+
+ @Binds fun store(impl: LightBarControllerStoreImpl): LightBarControllerStore
+
+ @Binds
+ @IntoMap
+ @ClassKey(LightBarControllerStore::class)
+ fun storeAsCoreStartable(impl: LightBarControllerStoreImpl): CoreStartable
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModePerDisplayRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModePerDisplayRepository.kt
index 44bee1d..cc91e2d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModePerDisplayRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModePerDisplayRepository.kt
@@ -27,7 +27,7 @@
import android.view.WindowInsetsController.Appearance
import com.android.internal.statusbar.LetterboxDetails
import com.android.internal.view.AppearanceRegion
-import com.android.systemui.Dumpable
+import com.android.systemui.CoreStartable
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.statusbar.core.StatusBarInitializer.OnStatusBarViewInitializedListener
@@ -59,7 +59,7 @@
* Note: These status bar modes are status bar *window* states that are sent to us from
* WindowManager, not determined internally.
*/
-interface StatusBarModePerDisplayRepository {
+interface StatusBarModePerDisplayRepository : OnStatusBarViewInitializedListener, CoreStartable {
/**
* True if the status bar window is showing transiently and will disappear soon, and false
* otherwise. ("Otherwise" in this case means the status bar is persistently hidden OR
@@ -104,6 +104,12 @@
* determined internally instead.
*/
fun clearTransient()
+
+ /**
+ * Called when the [StatusBarModePerDisplayRepository] should stop doing any work and clean up
+ * if needed.
+ */
+ fun stop()
}
class StatusBarModePerDisplayRepositoryImpl
@@ -114,7 +120,7 @@
private val commandQueue: CommandQueue,
private val letterboxAppearanceCalculator: LetterboxAppearanceCalculator,
ongoingCallRepository: OngoingCallRepository,
-) : StatusBarModePerDisplayRepository, OnStatusBarViewInitializedListener, Dumpable {
+) : StatusBarModePerDisplayRepository {
private val commandQueueCallback =
object : CommandQueue.Callbacks {
@@ -163,10 +169,14 @@
}
}
- fun start() {
+ override fun start() {
commandQueue.addCallback(commandQueueCallback)
}
+ override fun stop() {
+ commandQueue.removeCallback(commandQueueCallback)
+ }
+
private val _isTransientShown = MutableStateFlow(false)
override val isTransientShown: StateFlow<Boolean> = _isTransientShown.asStateFlow()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryStore.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryStore.kt
index 2c9fa25..143e998 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryStore.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryStore.kt
@@ -18,21 +18,54 @@
import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.DisplayId
+import com.android.systemui.display.data.repository.DisplayRepository
+import com.android.systemui.display.data.repository.PerDisplayStore
+import com.android.systemui.display.data.repository.PerDisplayStoreImpl
+import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
import com.android.systemui.statusbar.core.StatusBarInitializer
import com.android.systemui.statusbar.phone.fragment.dagger.HomeStatusBarComponent
import dagger.Binds
+import dagger.Lazy
import dagger.Module
+import dagger.Provides
import dagger.multibindings.ClassKey
import dagger.multibindings.IntoMap
import dagger.multibindings.IntoSet
import java.io.PrintWriter
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
-interface StatusBarModeRepositoryStore {
- val defaultDisplay: StatusBarModePerDisplayRepository
+interface StatusBarModeRepositoryStore : PerDisplayStore<StatusBarModePerDisplayRepository>
- fun forDisplay(displayId: Int): StatusBarModePerDisplayRepository
+@SysUISingleton
+class MultiDisplayStatusBarModeRepositoryStore
+@Inject
+constructor(
+ @Background backgroundApplicationScope: CoroutineScope,
+ private val factory: StatusBarModePerDisplayRepositoryFactory,
+ displayRepository: DisplayRepository,
+) :
+ StatusBarModeRepositoryStore,
+ PerDisplayStoreImpl<StatusBarModePerDisplayRepository>(
+ backgroundApplicationScope,
+ displayRepository,
+ ) {
+
+ init {
+ StatusBarConnectedDisplays.assertInNewMode()
+ }
+
+ override fun createInstanceForDisplay(displayId: Int): StatusBarModePerDisplayRepository {
+ return factory.create(displayId).also { it.start() }
+ }
+
+ override suspend fun onDisplayRemovalAction(instance: StatusBarModePerDisplayRepository) {
+ instance.stop()
+ }
+
+ override val instanceClass = StatusBarModePerDisplayRepository::class.java
}
@SysUISingleton
@@ -47,10 +80,7 @@
StatusBarInitializer.OnStatusBarViewInitializedListener {
override val defaultDisplay = factory.create(displayId)
- override fun forDisplay(displayId: Int) =
- // TODO(b/369337087): implement per display status bar modes.
- // For now just use default display instance.
- defaultDisplay
+ override fun forDisplay(displayId: Int) = defaultDisplay
override fun start() {
defaultDisplay.start()
@@ -66,17 +96,40 @@
}
@Module
-interface StatusBarModeRepositoryModule {
- @Binds fun bindImpl(impl: StatusBarModeRepositoryImpl): StatusBarModeRepositoryStore
-
- @Binds
- @IntoMap
- @ClassKey(StatusBarModeRepositoryStore::class)
- fun bindCoreStartable(impl: StatusBarModeRepositoryImpl): CoreStartable
-
+abstract class StatusBarModeRepositoryModule {
@Binds
@IntoSet
- fun bindViewInitListener(
+ abstract fun bindViewInitListener(
impl: StatusBarModeRepositoryImpl
): StatusBarInitializer.OnStatusBarViewInitializedListener
+
+ companion object {
+ @Provides
+ @SysUISingleton
+ @IntoMap
+ @ClassKey(StatusBarModeRepositoryStore::class)
+ fun storeAsCoreStartable(
+ singleDisplayLazy: Lazy<StatusBarModeRepositoryImpl>,
+ multiDisplayLazy: Lazy<MultiDisplayStatusBarModeRepositoryStore>,
+ ): CoreStartable {
+ return if (StatusBarConnectedDisplays.isEnabled) {
+ multiDisplayLazy.get()
+ } else {
+ singleDisplayLazy.get()
+ }
+ }
+
+ @Provides
+ @SysUISingleton
+ fun store(
+ singleDisplayLazy: Lazy<StatusBarModeRepositoryImpl>,
+ multiDisplayLazy: Lazy<MultiDisplayStatusBarModeRepositoryStore>,
+ ): StatusBarModeRepositoryStore {
+ return if (StatusBarConnectedDisplays.isEnabled) {
+ multiDisplayLazy.get()
+ } else {
+ singleDisplayLazy.get()
+ }
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt
index 77ec65b..9a779300 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt
@@ -49,12 +49,12 @@
@Inject
constructor(
private val launcherApps: LauncherApps,
- private val conversationNotificationManager: ConversationNotificationManager
+ private val conversationNotificationManager: ConversationNotificationManager,
) {
fun processNotification(
entry: NotificationEntry,
recoveredBuilder: Notification.Builder,
- logger: NotificationRowContentBinderLogger
+ logger: NotificationRowContentBinderLogger,
): Notification.MessagingStyle? {
val messagingStyle = recoveredBuilder.style as? Notification.MessagingStyle ?: return null
messagingStyle.conversationType =
@@ -83,7 +83,7 @@
private val notifCollection: CommonNotifCollection,
private val bindEventManager: BindEventManager,
private val headsUpManager: HeadsUpManager,
- private val statusBarStateController: StatusBarStateController
+ private val statusBarStateController: StatusBarStateController,
) {
private var isStatusBarExpanded = false
@@ -118,7 +118,8 @@
.flatMap { layout -> layout.allViews.asSequence() }
.flatMap { view ->
(view as? ConversationLayout)?.messagingGroups?.asSequence()
- ?: (view as? MessagingLayout)?.messagingGroups?.asSequence() ?: emptySequence()
+ ?: (view as? MessagingLayout)?.messagingGroups?.asSequence()
+ ?: emptySequence()
}
.flatMap { messagingGroup -> messagingGroup.messageContainer.children }
.mapNotNull { view ->
@@ -144,7 +145,7 @@
bindEventManager: BindEventManager,
@ShadeDisplayAware private val context: Context,
private val notifCollection: CommonNotifCollection,
- @Main private val mainHandler: Handler
+ @Main private val mainHandler: Handler,
) {
// Need this state to be thread safe, since it's accessed from the ui thread
// (NotificationEntryListener) and a bg thread (NotificationRowContentBinder)
@@ -175,7 +176,7 @@
// the notif has been moved in the shade
mainHandler.postDelayed(
{ layout.setIsImportantConversation(important, true) },
- IMPORTANCE_ANIMATION_DELAY.toLong()
+ IMPORTANCE_ANIMATION_DELAY.toLong(),
)
} else {
layout.setIsImportantConversation(important, false)
@@ -233,8 +234,7 @@
state?.run {
if (shouldIncrementUnread(recoveredBuilder)) unreadCount + 1
else unreadCount
- }
- ?: 1
+ } ?: 1
ConversationState(newCount, entry.sbn.notification)
}!!
.unreadCount
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAppearanceCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAppearanceCoordinator.kt
index de868d4..df8e56e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAppearanceCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAppearanceCoordinator.kt
@@ -33,29 +33,30 @@
* they are fully attached.
*/
@CoordinatorScope
-class RowAppearanceCoordinator @Inject internal constructor(
+class RowAppearanceCoordinator
+@Inject
+internal constructor(
@ShadeDisplayAware context: Context,
private var mAssistantFeedbackController: AssistantFeedbackController,
- private var mSectionStyleProvider: SectionStyleProvider
+ private var mSectionStyleProvider: SectionStyleProvider,
) : Coordinator {
private var entryToExpand: NotificationEntry? = null
/**
- * `true` if notifications not part of a group should by default be rendered in their
- * expanded state. If `false`, then only the first notification will be expanded if
- * possible.
+ * `true` if notifications not part of a group should by default be rendered in their expanded
+ * state. If `false`, then only the first notification will be expanded if possible.
*/
private val mAlwaysExpandNonGroupedNotification =
context.resources.getBoolean(R.bool.config_alwaysExpandNonGroupedNotifications)
/**
- * `true` if the first non-group expandable notification should be expanded automatically
- * when possible. If `false`, then the first non-group expandable notification should not
- * be expanded.
+ * `true` if the first non-group expandable notification should be expanded automatically when
+ * possible. If `false`, then the first non-group expandable notification should not be
+ * expanded.
*/
private val mAutoExpandFirstNotification =
- context.resources.getBoolean(R.bool.config_autoExpandFirstNotification)
+ context.resources.getBoolean(R.bool.config_autoExpandFirstNotification)
override fun attach(pipeline: NotifPipeline) {
pipeline.addOnBeforeRenderListListener(::onBeforeRenderList)
@@ -63,17 +64,20 @@
}
private fun onBeforeRenderList(list: List<ListEntry>) {
- entryToExpand = list.firstOrNull()?.representativeEntry?.takeIf { entry ->
- !mSectionStyleProvider.isMinimizedSection(entry.section!!)
- }
+ entryToExpand =
+ list.firstOrNull()?.representativeEntry?.takeIf { entry ->
+ !mSectionStyleProvider.isMinimizedSection(entry.section!!)
+ }
}
private fun onAfterRenderEntry(entry: NotificationEntry, controller: NotifRowController) {
// If mAlwaysExpandNonGroupedNotification is false, then only expand the
// very first notification if it's not a child of grouped notifications and when
// mAutoExpandFirstNotification is true.
- controller.setSystemExpanded(mAlwaysExpandNonGroupedNotification ||
- (mAutoExpandFirstNotification && entry == entryToExpand))
+ controller.setSystemExpanded(
+ mAlwaysExpandNonGroupedNotification ||
+ (mAutoExpandFirstNotification && entry == entryToExpand)
+ )
// Show/hide the feedback icon
controller.setFeedbackIcon(mAssistantFeedbackController.getFeedbackIcon(entry))
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinator.kt
index db778b80..2d1eccd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinator.kt
@@ -17,10 +17,12 @@
package com.android.systemui.statusbar.notification.collection.coordinator
import android.util.Log
+import com.android.app.tracing.traceSection
import com.android.internal.widget.MessagingGroup
import com.android.internal.widget.MessagingMessage
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.keyguard.KeyguardUpdateMonitorCallback
+import com.android.systemui.shade.ShadeDisplayAware
import com.android.systemui.statusbar.NotificationLockscreenUserManager
import com.android.systemui.statusbar.NotificationLockscreenUserManager.UserChangedListener
import com.android.systemui.statusbar.notification.ColorUpdateLogger
@@ -29,17 +31,17 @@
import com.android.systemui.statusbar.notification.row.NotificationGutsManager
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.util.Compile
-import com.android.app.tracing.traceSection
-import com.android.systemui.shade.ShadeDisplayAware
import javax.inject.Inject
/**
- * A coordinator which ensures that notifications within the new pipeline are correctly inflated
- * for the current uiMode and screen properties; additionally deferring those changes when a user
- * change is in progress until that process has completed.
+ * A coordinator which ensures that notifications within the new pipeline are correctly inflated for
+ * the current uiMode and screen properties; additionally deferring those changes when a user change
+ * is in progress until that process has completed.
*/
@CoordinatorScope
-class ViewConfigCoordinator @Inject internal constructor(
+class ViewConfigCoordinator
+@Inject
+internal constructor(
@ShadeDisplayAware private val mConfigurationController: ConfigurationController,
private val mLockscreenUserManager: NotificationLockscreenUserManager,
private val mGutsManager: NotificationGutsManager,
@@ -52,28 +54,32 @@
private var mDispatchUiModeChangeOnUserSwitched = false
private var mPipeline: NotifPipeline? = null
- private val mKeyguardUpdateCallback = object : KeyguardUpdateMonitorCallback() {
- override fun onUserSwitching(userId: Int) {
- colorUpdateLogger.logTriggerEvent("VCC.mKeyguardUpdateCallback.onUserSwitching()")
- log { "ViewConfigCoordinator.onUserSwitching(userId=$userId)" }
- mIsSwitchingUser = true
+ private val mKeyguardUpdateCallback =
+ object : KeyguardUpdateMonitorCallback() {
+ override fun onUserSwitching(userId: Int) {
+ colorUpdateLogger.logTriggerEvent("VCC.mKeyguardUpdateCallback.onUserSwitching()")
+ log { "ViewConfigCoordinator.onUserSwitching(userId=$userId)" }
+ mIsSwitchingUser = true
+ }
+
+ override fun onUserSwitchComplete(userId: Int) {
+ colorUpdateLogger.logTriggerEvent(
+ "VCC.mKeyguardUpdateCallback.onUserSwitchComplete()"
+ )
+ log { "ViewConfigCoordinator.onUserSwitchComplete(userId=$userId)" }
+ mIsSwitchingUser = false
+ applyChangesOnUserSwitched()
+ }
}
- override fun onUserSwitchComplete(userId: Int) {
- colorUpdateLogger.logTriggerEvent("VCC.mKeyguardUpdateCallback.onUserSwitchComplete()")
- log { "ViewConfigCoordinator.onUserSwitchComplete(userId=$userId)" }
- mIsSwitchingUser = false
- applyChangesOnUserSwitched()
+ private val mUserChangedListener =
+ object : UserChangedListener {
+ override fun onUserChanged(userId: Int) {
+ colorUpdateLogger.logTriggerEvent("VCC.mUserChangedListener.onUserChanged()")
+ log { "ViewConfigCoordinator.onUserChanged(userId=$userId)" }
+ applyChangesOnUserSwitched()
+ }
}
- }
-
- private val mUserChangedListener = object : UserChangedListener {
- override fun onUserChanged(userId: Int) {
- colorUpdateLogger.logTriggerEvent("VCC.mUserChangedListener.onUserChanged()")
- log { "ViewConfigCoordinator.onUserChanged(userId=$userId)" }
- applyChangesOnUserSwitched()
- }
- }
override fun attach(pipeline: NotifPipeline) {
mPipeline = pipeline
@@ -87,8 +93,8 @@
log {
val keyguardIsSwitchingUser = mKeyguardUpdateMonitor.isSwitchingUser
"ViewConfigCoordinator.onDensityOrFontScaleChanged()" +
- " isSwitchingUser=$mIsSwitchingUser" +
- " keyguardUpdateMonitor.isSwitchingUser=$keyguardIsSwitchingUser"
+ " isSwitchingUser=$mIsSwitchingUser" +
+ " keyguardUpdateMonitor.isSwitchingUser=$keyguardIsSwitchingUser"
}
MessagingMessage.dropCache()
MessagingGroup.dropCache()
@@ -104,8 +110,8 @@
log {
val keyguardIsSwitchingUser = mKeyguardUpdateMonitor.isSwitchingUser
"ViewConfigCoordinator.onUiModeChanged()" +
- " isSwitchingUser=$mIsSwitchingUser" +
- " keyguardUpdateMonitor.isSwitchingUser=$keyguardIsSwitchingUser"
+ " isSwitchingUser=$mIsSwitchingUser" +
+ " keyguardUpdateMonitor.isSwitchingUser=$keyguardIsSwitchingUser"
}
if (!mIsSwitchingUser) {
updateNotificationsOnUiModeChanged()
@@ -132,13 +138,13 @@
}
private fun updateNotificationsOnUiModeChanged() {
- colorUpdateLogger.logEvent("VCC.updateNotificationsOnUiModeChanged()",
- "mode=" + mConfigurationController.nightModeName)
+ colorUpdateLogger.logEvent(
+ "VCC.updateNotificationsOnUiModeChanged()",
+ "mode=" + mConfigurationController.nightModeName,
+ )
log { "ViewConfigCoordinator.updateNotificationsOnUiModeChanged()" }
traceSection("updateNotifOnUiModeChanged") {
- mPipeline?.allNotifs?.forEach { entry ->
- entry.row?.onUiModeChanged()
- }
+ mPipeline?.allNotifs?.forEach { entry -> entry.row?.onUiModeChanged() }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewManager.kt
index cab4c1c..3c838e5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewManager.kt
@@ -18,6 +18,8 @@
import android.content.Context
import android.view.View
+import com.android.app.tracing.traceSection
+import com.android.systemui.shade.ShadeDisplayAware
import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager
import com.android.systemui.statusbar.notification.collection.GroupEntry
import com.android.systemui.statusbar.notification.collection.ListEntry
@@ -26,8 +28,6 @@
import com.android.systemui.statusbar.notification.collection.PipelineDumper
import com.android.systemui.statusbar.notification.collection.provider.SectionHeaderVisibilityProvider
import com.android.systemui.statusbar.notification.stack.NotificationListContainer
-import com.android.app.tracing.traceSection
-import com.android.systemui.shade.ShadeDisplayAware
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
@@ -36,7 +36,9 @@
* Responsible for building and applying the "shade node spec": the list (tree) of things that
* currently populate the notification shade.
*/
-class ShadeViewManager @AssistedInject constructor(
+class ShadeViewManager
+@AssistedInject
+constructor(
@ShadeDisplayAware context: Context,
@Assisted listContainer: NotificationListContainer,
@Assisted private val stackController: NotifStackController,
@@ -45,13 +47,19 @@
sectionHeaderVisibilityProvider: SectionHeaderVisibilityProvider,
nodeSpecBuilderLogger: NodeSpecBuilderLogger,
shadeViewDifferLogger: ShadeViewDifferLogger,
- private val viewBarn: NotifViewBarn
+ private val viewBarn: NotifViewBarn,
) : PipelineDumpable {
// We pass a shim view here because the listContainer may not actually have a view associated
// with it and the differ never actually cares about the root node's view.
private val rootController = RootNodeController(listContainer, View(context))
- private val specBuilder = NodeSpecBuilder(mediaContainerController, featureManager,
- sectionHeaderVisibilityProvider, viewBarn, nodeSpecBuilderLogger)
+ private val specBuilder =
+ NodeSpecBuilder(
+ mediaContainerController,
+ featureManager,
+ sectionHeaderVisibilityProvider,
+ viewBarn,
+ nodeSpecBuilderLogger,
+ )
private val viewDiffer = ShadeViewDiffer(rootController, shadeViewDifferLogger)
/** Method for attaching this manager to the pipeline. */
@@ -59,34 +67,36 @@
renderStageManager.setViewRenderer(viewRenderer)
}
- override fun dumpPipeline(d: PipelineDumper) = with(d) {
- dump("rootController", rootController)
- dump("specBuilder", specBuilder)
- dump("viewDiffer", viewDiffer)
- }
-
- private val viewRenderer = object : NotifViewRenderer {
-
- override fun onRenderList(notifList: List<ListEntry>) {
- traceSection("ShadeViewManager.onRenderList") {
- viewDiffer.applySpec(specBuilder.buildNodeSpec(rootController, notifList))
- }
+ override fun dumpPipeline(d: PipelineDumper) =
+ with(d) {
+ dump("rootController", rootController)
+ dump("specBuilder", specBuilder)
+ dump("viewDiffer", viewDiffer)
}
- override fun getStackController(): NotifStackController = stackController
+ private val viewRenderer =
+ object : NotifViewRenderer {
- override fun getGroupController(group: GroupEntry): NotifGroupController =
- viewBarn.requireGroupController(group.requireSummary)
+ override fun onRenderList(notifList: List<ListEntry>) {
+ traceSection("ShadeViewManager.onRenderList") {
+ viewDiffer.applySpec(specBuilder.buildNodeSpec(rootController, notifList))
+ }
+ }
- override fun getRowController(entry: NotificationEntry): NotifRowController =
- viewBarn.requireRowController(entry)
- }
+ override fun getStackController(): NotifStackController = stackController
+
+ override fun getGroupController(group: GroupEntry): NotifGroupController =
+ viewBarn.requireGroupController(group.requireSummary)
+
+ override fun getRowController(entry: NotificationEntry): NotifRowController =
+ viewBarn.requireRowController(entry)
+ }
}
@AssistedFactory
interface ShadeViewManagerFactory {
fun create(
listContainer: NotificationListContainer,
- stackController: NotifStackController
+ stackController: NotifStackController,
): ShadeViewManager
}
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 96f47e5..a0515ca 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
@@ -459,8 +459,12 @@
Resources.Theme theme = mContext.getTheme();
final @ColorInt int onSurface = Utils.getColorAttrDefaultColor(mContext,
com.android.internal.R.attr.materialColorOnSurface);
+ // Same resource, separate drawables to prevent touch effects from showing on the wrong
+ // button.
final Drawable clearAllBg = theme.getDrawable(R.drawable.notif_footer_btn_background);
- final Drawable manageBg = theme.getDrawable(R.drawable.notif_footer_btn_background);
+ final Drawable settingsBg = theme.getDrawable(R.drawable.notif_footer_btn_background);
+ final Drawable historyBg = NotifRedesignFooter.isEnabled()
+ ? theme.getDrawable(R.drawable.notif_footer_btn_background) : null;
final @ColorInt int scHigh;
if (!notificationFooterBackgroundTintOptimization()) {
scHigh = Utils.getColorAttrDefaultColor(mContext,
@@ -468,7 +472,10 @@
if (scHigh != 0) {
final ColorFilter bgColorFilter = new PorterDuffColorFilter(scHigh, SRC_ATOP);
clearAllBg.setColorFilter(bgColorFilter);
- manageBg.setColorFilter(bgColorFilter);
+ settingsBg.setColorFilter(bgColorFilter);
+ if (NotifRedesignFooter.isEnabled()) {
+ historyBg.setColorFilter(bgColorFilter);
+ }
}
} else {
scHigh = 0;
@@ -476,13 +483,13 @@
mClearAllButton.setBackground(clearAllBg);
mClearAllButton.setTextColor(onSurface);
if (NotifRedesignFooter.isEnabled()) {
- mSettingsButton.setBackground(manageBg);
+ mSettingsButton.setBackground(settingsBg);
mSettingsButton.setCompoundDrawableTintList(ColorStateList.valueOf(onSurface));
- mHistoryButton.setBackground(manageBg);
+ mHistoryButton.setBackground(historyBg);
mHistoryButton.setCompoundDrawableTintList(ColorStateList.valueOf(onSurface));
} else {
- mManageOrHistoryButton.setBackground(manageBg);
+ mManageOrHistoryButton.setBackground(settingsBg);
mManageOrHistoryButton.setTextColor(onSurface);
}
mSeenNotifsFooterTextView.setTextColor(onSurface);
@@ -492,7 +499,7 @@
colorUpdateLogger.logEvent("Footer.updateColors()",
"textColor(onSurface)=" + hexColorString(onSurface)
+ " backgroundTint(surfaceContainerHigh)=" + hexColorString(scHigh)
- + " background=" + DrawableDumpKt.dumpToString(manageBg));
+ + " background=" + DrawableDumpKt.dumpToString(settingsBg));
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt
index 71cddc9..52336be 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt
@@ -75,7 +75,7 @@
private val bubbles: Optional<Bubbles>,
@ShadeDisplayAware private val context: Context,
private val notificationManager: NotificationManager,
- private val settingsInteractor: NotificationSettingsInteractor
+ private val settingsInteractor: NotificationSettingsInteractor,
) : VisualInterruptionDecisionProvider {
init {
@@ -89,7 +89,7 @@
private class DecisionImpl(
override val shouldInterrupt: Boolean,
- override val logReason: String
+ override val logReason: String,
) : Decision
private data class LoggableDecision
@@ -107,7 +107,7 @@
LoggableDecision(
DecisionImpl(
shouldInterrupt = false,
- logReason = "${legacySuppressor.name}.$methodName"
+ logReason = "${legacySuppressor.name}.$methodName",
)
)
@@ -123,7 +123,7 @@
private class FullScreenIntentDecisionImpl(
val entry: NotificationEntry,
- private val fsiDecision: FullScreenIntentDecisionProvider.Decision
+ private val fsiDecision: FullScreenIntentDecisionProvider.Decision,
) : FullScreenIntentDecision, Loggable {
var hasBeenLogged = false
@@ -154,7 +154,7 @@
deviceProvisionedController,
keyguardStateController,
powerManager,
- statusBarStateController
+ statusBarStateController,
)
private val legacySuppressors = mutableSetOf<NotificationInterruptSuppressor>()
@@ -197,7 +197,7 @@
context,
notificationManager,
logger,
- systemSettings
+ systemSettings,
)
)
avalancheProvider.register()
@@ -290,7 +290,7 @@
private fun logDecision(
type: VisualInterruptionType,
entry: NotificationEntry,
- loggableDecision: LoggableDecision
+ loggableDecision: LoggableDecision,
) {
if (!loggableDecision.isSpammy || logger.spew) {
logger.logDecision(type.name, entry, loggableDecision.decision)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/BigPictureIconManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/BigPictureIconManager.kt
index e233def..9164145 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/BigPictureIconManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/BigPictureIconManager.kt
@@ -25,6 +25,7 @@
import android.util.Log
import android.util.Size
import androidx.annotation.MainThread
+import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.internal.R
import com.android.internal.widget.NotificationDrawableConsumer
import com.android.internal.widget.NotificationIconManager
@@ -45,7 +46,6 @@
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
-import com.android.app.tracing.coroutines.launchTraced as launch
import kotlinx.coroutines.withContext
private const val TAG = "BigPicImageLoader"
@@ -67,7 +67,7 @@
private val statsManager: BigPictureStatsManager,
@Application private val scope: CoroutineScope,
@Main private val mainDispatcher: CoroutineDispatcher,
- @Background private val bgDispatcher: CoroutineDispatcher
+ @Background private val bgDispatcher: CoroutineDispatcher,
) : NotificationIconManager, Dumpable {
private var lastLoadingJob: Job? = null
@@ -153,7 +153,7 @@
private fun checkPlaceHolderSizeForDrawable(
displayedState: DrawableState,
- newDrawable: Drawable
+ newDrawable: Drawable,
) {
if (displayedState is PlaceHolder) {
val (oldWidth, oldHeight) = displayedState.drawableSize
@@ -163,7 +163,7 @@
Log.e(
TAG,
"Mismatch in dimensions, when replacing PlaceHolder " +
- "$oldWidth X $oldHeight with Drawable $newWidth X $newHeight."
+ "$oldWidth X $oldHeight with Drawable $newWidth X $newHeight.",
)
}
}
@@ -184,9 +184,8 @@
displayedState = drawableAndState?.second ?: Empty
}
- private fun startLoadingJob(icon: Icon): Job = scope.launch {
- statsManager.measure { loadImage(icon) }
- }
+ private fun startLoadingJob(icon: Icon): Job =
+ scope.launch { statsManager.measure { loadImage(icon) } }
private suspend fun loadImage(icon: Icon) {
val drawableAndState = withContext(bgDispatcher) { loadImageSync(icon) }
@@ -254,9 +253,12 @@
private sealed class DrawableState(open val icon: Icon?) {
data object Initial : DrawableState(null)
+
data object Empty : DrawableState(null)
+
data class PlaceHolder(override val icon: Icon, val drawableSize: Size) :
DrawableState(icon)
+
data class FullImage(override val icon: Icon, val drawableSize: Size) : DrawableState(icon)
}
}
@@ -298,7 +300,7 @@
}
private val Drawable.intrinsicSize
- get() = Size(/*width=*/ intrinsicWidth, /*height=*/ intrinsicHeight)
+ get() = Size(/* width= */ intrinsicWidth, /* height= */ intrinsicHeight)
private operator fun Size.component1() = width
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt
index c9a0010..31e4d2c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt
@@ -26,7 +26,14 @@
import com.android.systemui.statusbar.notification.collection.NotificationClassificationFlag
import com.android.systemui.statusbar.notification.collection.render.MediaContainerController
import com.android.systemui.statusbar.notification.collection.render.SectionHeaderController
-import com.android.systemui.statusbar.notification.dagger.*
+import com.android.systemui.statusbar.notification.dagger.AlertingHeader
+import com.android.systemui.statusbar.notification.dagger.IncomingHeader
+import com.android.systemui.statusbar.notification.dagger.NewsHeader
+import com.android.systemui.statusbar.notification.dagger.PeopleHeader
+import com.android.systemui.statusbar.notification.dagger.PromoHeader
+import com.android.systemui.statusbar.notification.dagger.RecsHeader
+import com.android.systemui.statusbar.notification.dagger.SilentHeader
+import com.android.systemui.statusbar.notification.dagger.SocialHeader
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.android.systemui.statusbar.notification.row.ExpandableView
import com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm.SectionProvider
@@ -54,7 +61,7 @@
@NewsHeader private val newsHeaderController: SectionHeaderController,
@SocialHeader private val socialHeaderController: SectionHeaderController,
@RecsHeader private val recsHeaderController: SectionHeaderController,
- @PromoHeader private val promoHeaderController: SectionHeaderController
+ @PromoHeader private val promoHeaderController: SectionHeaderController,
) : SectionProvider {
private val configurationListener =
@@ -136,14 +143,16 @@
override fun beginsSection(view: View, previous: View?): Boolean =
view === silentHeaderView ||
- view === mediaControlsView ||
- view === peopleHeaderView ||
- view === alertingHeaderView ||
- view === incomingHeaderView ||
- (NotificationClassificationFlag.isEnabled && (view === newsHeaderView
- || view === socialHeaderView || view === recsHeaderView
- || view === promoHeaderView)) ||
- getBucket(view) != getBucket(previous)
+ view === mediaControlsView ||
+ view === peopleHeaderView ||
+ view === alertingHeaderView ||
+ view === incomingHeaderView ||
+ (NotificationClassificationFlag.isEnabled &&
+ (view === newsHeaderView ||
+ view === socialHeaderView ||
+ view === recsHeaderView ||
+ view === promoHeaderView)) ||
+ getBucket(view) != getBucket(previous)
private fun getBucket(view: View?): Int? =
when {
@@ -165,6 +174,7 @@
data class Many(val first: ExpandableView, val last: ExpandableView) : SectionBounds()
data class One(val lone: ExpandableView) : SectionBounds()
+
object None : SectionBounds()
fun addNotif(notif: ExpandableView): SectionBounds =
@@ -183,7 +193,7 @@
private fun NotificationSection.setFirstAndLastVisibleChildren(
first: ExpandableView?,
- last: ExpandableView?
+ last: ExpandableView?,
): Boolean {
val firstChanged = setFirstVisibleChild(first)
val lastChanged = setLastVisibleChild(last)
@@ -198,7 +208,7 @@
*/
fun updateFirstAndLastViewsForAllSections(
sections: Array<NotificationSection>,
- children: List<ExpandableView>
+ children: List<ExpandableView>,
): Boolean {
// Create mapping of bucket to section
val sectionBounds =
@@ -213,7 +223,7 @@
.foldToSparseArray(
SectionBounds.None,
size = sections.size,
- operation = SectionBounds::addNotif
+ operation = SectionBounds::addNotif,
)
// Build a set of the old first/last Views of the sections
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 99efba4..0b1b9922 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -171,12 +171,14 @@
import com.android.systemui.shade.NotificationShadeWindowViewController;
import com.android.systemui.shade.QuickSettingsController;
import com.android.systemui.shade.ShadeController;
+import com.android.systemui.shade.ShadeExpandsOnStatusBarLongPress;
import com.android.systemui.shade.ShadeExpansionChangeEvent;
import com.android.systemui.shade.ShadeExpansionListener;
import com.android.systemui.shade.ShadeExpansionStateManager;
import com.android.systemui.shade.ShadeLogger;
import com.android.systemui.shade.ShadeSurface;
import com.android.systemui.shade.ShadeViewController;
+import com.android.systemui.shade.StatusBarLongPressGestureDetector;
import com.android.systemui.shared.recents.utilities.Utilities;
import com.android.systemui.shared.statusbar.phone.BarTransitions;
import com.android.systemui.statusbar.AutoHideUiElement;
@@ -366,6 +368,7 @@
private PhoneStatusBarViewController mPhoneStatusBarViewController;
private PhoneStatusBarTransitions mStatusBarTransitions;
+ private final Provider<StatusBarLongPressGestureDetector> mStatusBarLongPressGestureDetector;
private final AuthRippleController mAuthRippleController;
@WindowVisibleState private int mStatusBarWindowState = WINDOW_STATE_SHOWING;
private final NotificationShadeWindowController mNotificationShadeWindowController;
@@ -671,6 +674,7 @@
ShadeController shadeController,
WindowRootViewVisibilityInteractor windowRootViewVisibilityInteractor,
StatusBarKeyguardViewManager statusBarKeyguardViewManager,
+ Provider<StatusBarLongPressGestureDetector> statusBarLongPressGestureDetector,
ViewMediatorCallback viewMediatorCallback,
InitController initController,
@Named(TIME_TICK_HANDLER_NAME) Handler timeTickHandler,
@@ -778,6 +782,7 @@
mShadeController = shadeController;
mWindowRootViewVisibilityInteractor = windowRootViewVisibilityInteractor;
mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
+ mStatusBarLongPressGestureDetector = statusBarLongPressGestureDetector;
mKeyguardViewMediatorCallback = viewMediatorCallback;
mInitController = initController;
mPluginDependencyProvider = pluginDependencyProvider;
@@ -1527,6 +1532,11 @@
// to touch outside the customizer to close it, such as on the status or nav bar.
mShadeController.onStatusBarTouch(event);
}
+ if (ShadeExpandsOnStatusBarLongPress.isEnabled()
+ && mStatusBarStateController.getState() == StatusBarState.KEYGUARD) {
+ mStatusBarLongPressGestureDetector.get().handleTouch(event);
+ }
+
return getNotificationShadeWindowView().onTouchEvent(event);
};
}
@@ -1589,8 +1599,6 @@
.setStatusBarKeyguardViewManager(mStatusBarKeyguardViewManager);
mBiometricUnlockController.setKeyguardViewController(mStatusBarKeyguardViewManager);
mRemoteInputManager.addControllerCallback(mStatusBarKeyguardViewManager);
-
- mLightBarController.setBiometricUnlockController(mBiometricUnlockController);
Trace.endSection();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.kt
new file mode 100644
index 0000000..a6374a6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.kt
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.phone
+
+import android.view.WindowInsetsController
+import com.android.internal.colorextraction.ColorExtractor
+import com.android.internal.view.AppearanceRegion
+import com.android.systemui.CoreStartable
+
+/** Controls how light status bar flag applies to the icons. */
+interface LightBarController : CoreStartable {
+
+ fun stop()
+
+ fun setNavigationBar(navigationBar: LightBarTransitionsController)
+
+ fun onNavigationBarAppearanceChanged(
+ @WindowInsetsController.Appearance appearance: Int,
+ nbModeChanged: Boolean,
+ navigationBarMode: Int,
+ navbarColorManagedByIme: Boolean,
+ )
+
+ fun onNavigationBarModeChanged(newBarMode: Int)
+
+ fun setQsCustomizing(customizing: Boolean)
+
+ /** Set if Quick Settings is fully expanded, which affects notification scrim visibility. */
+ fun setQsExpanded(expanded: Boolean)
+
+ /** Set if Global Actions dialog is visible, which requires dark mode (light buttons). */
+ fun setGlobalActionsVisible(visible: Boolean)
+
+ /**
+ * Controls the light status bar temporarily for back navigation.
+ *
+ * @param appearance the customized appearance.
+ */
+ fun customizeStatusBarAppearance(appearance: AppearanceRegion)
+
+ /**
+ * Sets whether the direct-reply is in use or not.
+ *
+ * @param directReplying `true` when the direct-reply is in-use.
+ */
+ fun setDirectReplying(directReplying: Boolean)
+
+ fun setScrimState(
+ scrimState: ScrimState,
+ scrimBehindAlpha: Float,
+ scrimInFrontColor: ColorExtractor.GradientColors,
+ )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarControllerImpl.java
similarity index 81%
rename from packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java
rename to packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarControllerImpl.java
index a33996b..edc1f88 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarControllerImpl.java
@@ -11,7 +11,7 @@
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
- * limitations under the License
+ * limitations under the License.
*/
package com.android.systemui.statusbar.phone;
@@ -22,9 +22,9 @@
import static com.android.systemui.shared.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT_TRANSPARENT;
import static com.android.systemui.shared.statusbar.phone.BarTransitions.MODE_TRANSPARENT;
-import android.content.Context;
import android.graphics.Rect;
import android.util.Log;
+import android.view.Display;
import android.view.InsetsFlags;
import android.view.ViewDebug;
import android.view.WindowInsetsController.Appearance;
@@ -34,30 +34,32 @@
import com.android.internal.colorextraction.ColorExtractor.GradientColors;
import com.android.internal.view.AppearanceRegion;
-import com.android.systemui.CoreStartable;
-import com.android.systemui.Dumpable;
-import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.navigationbar.NavigationModeController;
import com.android.systemui.plugins.DarkIconDispatcher;
-import com.android.systemui.settings.DisplayTracker;
import com.android.systemui.statusbar.data.model.StatusBarAppearance;
-import com.android.systemui.statusbar.data.repository.StatusBarModeRepositoryStore;
+import com.android.systemui.statusbar.data.repository.StatusBarModePerDisplayRepository;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.util.Compile;
-import com.android.systemui.util.kotlin.JavaAdapter;
+import com.android.systemui.util.kotlin.JavaAdapterKt;
+
+import dagger.assisted.Assisted;
+import dagger.assisted.AssistedFactory;
+import dagger.assisted.AssistedInject;
+
+import kotlin.coroutines.CoroutineContext;
+
+import kotlinx.coroutines.CoroutineScope;
import java.io.PrintWriter;
import java.util.ArrayList;
-import javax.inject.Inject;
-
/**
* Controls how light status bar flag applies to the icons.
*/
-@SysUISingleton
-public class LightBarController implements
- BatteryController.BatteryStateChangeCallback, Dumpable, CoreStartable {
+public class LightBarControllerImpl implements
+ BatteryController.BatteryStateChangeCallback, LightBarController {
private static final String TAG = "LightBarController";
private static final boolean DEBUG_NAVBAR = Compile.IS_DEBUG;
@@ -65,11 +67,14 @@
private static final float NAV_BAR_INVERSION_SCRIM_ALPHA_THRESHOLD = 0.1f;
- private final JavaAdapter mJavaAdapter;
+ private final CoroutineScope mCoroutineScope;
private final SysuiDarkIconDispatcher mStatusBarIconController;
private final BatteryController mBatteryController;
- private final StatusBarModeRepositoryStore mStatusBarModeRepository;
- private BiometricUnlockController mBiometricUnlockController;
+ private final NavigationModeController mNavModeController;
+ private final DumpManager mDumpManager;
+ private final StatusBarModePerDisplayRepository mStatusBarModeRepository;
+ private final CoroutineContext mMainContext;
+ private final BiometricUnlockController mBiometricUnlockController;
private LightBarTransitionsController mNavigationBarController;
private @Appearance int mAppearance;
@@ -119,47 +124,60 @@
private String mLastNavigationBarAppearanceChangedLog;
private StringBuilder mLogStringBuilder = null;
- @Inject
- public LightBarController(
- Context ctx,
- JavaAdapter javaAdapter,
+ private final String mDumpableName;
+
+ private final NavigationModeController.ModeChangedListener mNavigationModeListener =
+ (mode) -> mNavigationMode = mode;
+
+ @AssistedInject
+ public LightBarControllerImpl(
+ @Assisted int displayId,
+ @Assisted CoroutineScope coroutineScope,
DarkIconDispatcher darkIconDispatcher,
BatteryController batteryController,
NavigationModeController navModeController,
- StatusBarModeRepositoryStore statusBarModeRepository,
+ @Assisted StatusBarModePerDisplayRepository statusBarModeRepository,
DumpManager dumpManager,
- DisplayTracker displayTracker) {
- mJavaAdapter = javaAdapter;
+ @Main CoroutineContext mainContext,
+ BiometricUnlockController biometricUnlockController) {
+ mCoroutineScope = coroutineScope;
mStatusBarIconController = (SysuiDarkIconDispatcher) darkIconDispatcher;
mBatteryController = batteryController;
- mBatteryController.addCallback(this);
+ mNavModeController = navModeController;
+ mDumpManager = dumpManager;
mStatusBarModeRepository = statusBarModeRepository;
- mNavigationMode = navModeController.addListener((mode) -> {
- mNavigationMode = mode;
- });
-
- if (ctx.getDisplayId() == displayTracker.getDefaultDisplayId()) {
- dumpManager.registerDumpable(getClass().getSimpleName(), this);
- }
+ mMainContext = mainContext;
+ mBiometricUnlockController = biometricUnlockController;
+ String dumpableNameSuffix =
+ displayId == Display.DEFAULT_DISPLAY ? "" : String.valueOf(displayId);
+ mDumpableName = getClass().getSimpleName() + dumpableNameSuffix;
}
@Override
public void start() {
- mJavaAdapter.alwaysCollectFlow(
- mStatusBarModeRepository.getDefaultDisplay().getStatusBarAppearance(),
+ mDumpManager.registerCriticalDumpable(mDumpableName, this);
+ mBatteryController.addCallback(this);
+ mNavigationMode = mNavModeController.addListener(mNavigationModeListener);
+ JavaAdapterKt.collectFlow(
+ mCoroutineScope,
+ mMainContext,
+ mStatusBarModeRepository.getStatusBarAppearance(),
this::onStatusBarAppearanceChanged);
}
+ @Override
+ public void stop() {
+ mDumpManager.unregisterDumpable(mDumpableName);
+ mBatteryController.removeCallback(this);
+ mNavModeController.removeListener(mNavigationModeListener);
+ }
+
+ @Override
public void setNavigationBar(LightBarTransitionsController navigationBar) {
mNavigationBarController = navigationBar;
updateNavigation();
}
- public void setBiometricUnlockController(
- BiometricUnlockController biometricUnlockController) {
- mBiometricUnlockController = biometricUnlockController;
- }
-
private void onStatusBarAppearanceChanged(@Nullable StatusBarAppearance params) {
if (params == null) {
return;
@@ -202,6 +220,7 @@
mNavbarColorManagedByIme = navbarColorManagedByIme;
}
+ @Override
public void onNavigationBarAppearanceChanged(@Appearance int appearance, boolean nbModeChanged,
int navigationBarMode, boolean navbarColorManagedByIme) {
int diff = appearance ^ mAppearance;
@@ -244,6 +263,7 @@
mNavbarColorManagedByIme = navbarColorManagedByIme;
}
+ @Override
public void onNavigationBarModeChanged(int newBarMode) {
mHasLightNavigationBar = isLight(mAppearance, newBarMode, APPEARANCE_LIGHT_NAVIGATION_BARS);
}
@@ -258,30 +278,28 @@
mNavigationBarMode, mNavbarColorManagedByIme);
}
+ @Override
public void setQsCustomizing(boolean customizing) {
if (mQsCustomizing == customizing) return;
mQsCustomizing = customizing;
reevaluate();
}
- /** Set if Quick Settings is fully expanded, which affects notification scrim visibility */
+ @Override
public void setQsExpanded(boolean expanded) {
if (mQsExpanded == expanded) return;
mQsExpanded = expanded;
reevaluate();
}
- /** Set if Global Actions dialog is visible, which requires dark mode (light buttons) */
+ @Override
public void setGlobalActionsVisible(boolean visible) {
if (mGlobalActionsVisible == visible) return;
mGlobalActionsVisible = visible;
reevaluate();
}
- /**
- * Controls the light status bar temporarily for back navigation.
- * @param appearance the custmoized appearance.
- */
+ @Override
public void customizeStatusBarAppearance(AppearanceRegion appearance) {
if (appearance != null) {
final ArrayList<AppearanceRegion> appearancesList = new ArrayList<>();
@@ -303,16 +321,14 @@
}
}
- /**
- * Sets whether the direct-reply is in use or not.
- * @param directReplying {@code true} when the direct-reply is in-use.
- */
+ @Override
public void setDirectReplying(boolean directReplying) {
if (mDirectReplying == directReplying) return;
mDirectReplying = directReplying;
reevaluate();
}
+ @Override
public void setScrimState(ScrimState scrimState, float scrimBehindAlpha,
GradientColors scrimInFrontColor) {
boolean bouncerVisibleLast = mBouncerVisible;
@@ -368,9 +384,6 @@
}
private boolean animateChange() {
- if (mBiometricUnlockController == null) {
- return false;
- }
int unlockMode = mBiometricUnlockController.getMode();
return unlockMode != BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING
&& unlockMode != BiometricUnlockController.MODE_WAKE_AND_UNLOCK;
@@ -387,20 +400,17 @@
}
}
- // If no one is light, all icons become white.
if (lightBarBounds.isEmpty()) {
- mStatusBarIconController.getTransitionsController().setIconsDark(
- false, animateChange());
- }
-
- // If all stacks are light, all icons get dark.
- else if (lightBarBounds.size() == numStacks) {
+ // If no one is light, all icons become white.
+ mStatusBarIconController
+ .getTransitionsController()
+ .setIconsDark(false, animateChange());
+ } else if (lightBarBounds.size() == numStacks) {
+ // If all stacks are light, all icons get dark.
mStatusBarIconController.setIconsDarkArea(null);
mStatusBarIconController.getTransitionsController().setIconsDark(true, animateChange());
- }
-
- // Not the same for every stack, magic!
- else {
+ } else {
+ // Not the same for every stack, magic!
mStatusBarIconController.setIconsDarkArea(lightBarBounds);
mStatusBarIconController.getTransitionsController().setIconsDark(true, animateChange());
}
@@ -468,47 +478,15 @@
}
}
- /**
- * Injectable factory for creating a {@link LightBarController}.
- */
- public static class Factory {
- private final JavaAdapter mJavaAdapter;
- private final DarkIconDispatcher mDarkIconDispatcher;
- private final BatteryController mBatteryController;
- private final NavigationModeController mNavModeController;
- private final StatusBarModeRepositoryStore mStatusBarModeRepository;
- private final DumpManager mDumpManager;
- private final DisplayTracker mDisplayTracker;
+ /** Injectable factory for creating a {@link LightBarControllerImpl}. */
+ @AssistedFactory
+ @FunctionalInterface
+ public interface Factory {
- @Inject
- public Factory(
- JavaAdapter javaAdapter,
- DarkIconDispatcher darkIconDispatcher,
- BatteryController batteryController,
- NavigationModeController navModeController,
- StatusBarModeRepositoryStore statusBarModeRepository,
- DumpManager dumpManager,
- DisplayTracker displayTracker) {
- mJavaAdapter = javaAdapter;
- mDarkIconDispatcher = darkIconDispatcher;
- mBatteryController = batteryController;
- mNavModeController = navModeController;
- mStatusBarModeRepository = statusBarModeRepository;
- mDumpManager = dumpManager;
- mDisplayTracker = displayTracker;
- }
-
- /** Create an {@link LightBarController} */
- public LightBarController create(Context context) {
- return new LightBarController(
- context,
- mJavaAdapter,
- mDarkIconDispatcher,
- mBatteryController,
- mNavModeController,
- mStatusBarModeRepository,
- mDumpManager,
- mDisplayTracker);
- }
+ /** Creates a {@link LightBarControllerImpl}. */
+ LightBarControllerImpl create(
+ int displayId,
+ CoroutineScope coroutineScope,
+ StatusBarModePerDisplayRepository statusBarModePerDisplayRepository);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
index 91c43dd..176dd8d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
@@ -39,8 +39,8 @@
import com.android.systemui.Flags;
import com.android.systemui.Gefingerpoken;
import com.android.systemui.res.R;
-import com.android.systemui.shade.LongPressGestureDetector;
import com.android.systemui.shade.ShadeExpandsOnStatusBarLongPress;
+import com.android.systemui.shade.StatusBarLongPressGestureDetector;
import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherContainer;
import com.android.systemui.statusbar.window.StatusBarWindowControllerStore;
import com.android.systemui.user.ui.binder.StatusBarUserChipViewBinder;
@@ -69,7 +69,7 @@
private InsetsFetcher mInsetsFetcher;
private int mDensity;
private float mFontScale;
- private LongPressGestureDetector mLongPressGestureDetector;
+ private StatusBarLongPressGestureDetector mStatusBarLongPressGestureDetector;
/**
* Draw this many pixels into the left/right side of the cutout to optimally use the space
@@ -81,9 +81,10 @@
mStatusBarWindowControllerStore = Dependency.get(StatusBarWindowControllerStore.class);
}
- void setLongPressGestureDetector(LongPressGestureDetector longPressGestureDetector) {
+ void setLongPressGestureDetector(
+ StatusBarLongPressGestureDetector statusBarLongPressGestureDetector) {
if (ShadeExpandsOnStatusBarLongPress.isEnabled()) {
- mLongPressGestureDetector = longPressGestureDetector;
+ mStatusBarLongPressGestureDetector = statusBarLongPressGestureDetector;
}
}
@@ -207,8 +208,9 @@
@Override
public boolean onTouchEvent(MotionEvent event) {
- if (ShadeExpandsOnStatusBarLongPress.isEnabled() && mLongPressGestureDetector != null) {
- mLongPressGestureDetector.handleTouch(event);
+ if (ShadeExpandsOnStatusBarLongPress.isEnabled()
+ && mStatusBarLongPressGestureDetector != null) {
+ mStatusBarLongPressGestureDetector.handleTouch(event);
}
if (mTouchEventHandler == null) {
Log.w(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
index c24f432..4245494 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
@@ -33,11 +33,11 @@
import com.android.systemui.res.R
import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.scene.ui.view.WindowRootView
-import com.android.systemui.shade.LongPressGestureDetector
import com.android.systemui.shade.ShadeController
import com.android.systemui.shade.ShadeExpandsOnStatusBarLongPress
import com.android.systemui.shade.ShadeLogger
import com.android.systemui.shade.ShadeViewController
+import com.android.systemui.shade.StatusBarLongPressGestureDetector
import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor
import com.android.systemui.shared.animation.UnfoldMoveFromCenterAnimator
import com.android.systemui.statusbar.data.repository.StatusBarContentInsetsProviderStore
@@ -68,7 +68,7 @@
private val shadeController: ShadeController,
private val shadeViewController: ShadeViewController,
private val panelExpansionInteractor: PanelExpansionInteractor,
- private val longPressGestureDetector: Provider<LongPressGestureDetector>,
+ private val statusBarLongPressGestureDetector: Provider<StatusBarLongPressGestureDetector>,
private val windowRootView: Provider<WindowRootView>,
private val shadeLogger: ShadeLogger,
private val moveFromCenterAnimationController: StatusBarMoveFromCenterAnimationController?,
@@ -118,7 +118,7 @@
addCursorSupportToIconContainers()
if (ShadeExpandsOnStatusBarLongPress.isEnabled) {
- mView.setLongPressGestureDetector(longPressGestureDetector.get())
+ mView.setLongPressGestureDetector(statusBarLongPressGestureDetector.get())
}
progressProvider?.setReadyToHandleTransition(true)
@@ -335,7 +335,7 @@
private val shadeController: ShadeController,
private val shadeViewController: ShadeViewController,
private val panelExpansionInteractor: PanelExpansionInteractor,
- private val longPressGestureDetector: Provider<LongPressGestureDetector>,
+ private val statusBarLongPressGestureDetector: Provider<StatusBarLongPressGestureDetector>,
private val windowRootView: Provider<WindowRootView>,
private val shadeLogger: ShadeLogger,
private val viewUtil: ViewUtil,
@@ -360,7 +360,7 @@
shadeController,
shadeViewController,
panelExpansionInteractor,
- longPressGestureDetector,
+ statusBarLongPressGestureDetector,
windowRootView,
shadeLogger,
statusBarMoveFromCenterAnimationController,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.kt
index 94de3510..ba878ed 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.kt
@@ -103,8 +103,12 @@
fun statusBarInitializerImpl(
implFactory: StatusBarInitializerImpl.Factory,
statusBarWindowControllerStore: StatusBarWindowControllerStore,
+ statusBarModeRepositoryStore: StatusBarModeRepositoryStore,
): StatusBarInitializerImpl {
- return implFactory.create(statusBarWindowControllerStore.defaultDisplay)
+ return implFactory.create(
+ statusBarWindowControllerStore.defaultDisplay,
+ statusBarModeRepositoryStore.defaultDisplay,
+ )
}
@Provides
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/DeviceBasedSatelliteRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/DeviceBasedSatelliteRepository.kt
index 1d08f2b..98eed84 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/DeviceBasedSatelliteRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/DeviceBasedSatelliteRepository.kt
@@ -37,6 +37,9 @@
/** Clients must observe this property, as device-based satellite is location-dependent */
val isSatelliteAllowedForCurrentLocation: StateFlow<Boolean>
+
+ /** When enabled, a satellite icon will display when all other connections are OOS */
+ val isOpportunisticSatelliteIconEnabled: Boolean
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/DeviceBasedSatelliteRepositorySwitcher.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/DeviceBasedSatelliteRepositorySwitcher.kt
index 58c30e0..de42b92 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/DeviceBasedSatelliteRepositorySwitcher.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/DeviceBasedSatelliteRepositorySwitcher.kt
@@ -97,6 +97,9 @@
}
.stateIn(scope, SharingStarted.WhileSubscribed(), realImpl)
+ override val isOpportunisticSatelliteIconEnabled: Boolean
+ get() = activeRepo.value.isOpportunisticSatelliteIconEnabled
+
override val isSatelliteProvisioned: StateFlow<Boolean> =
activeRepo
.flatMapLatest { it.isSatelliteProvisioned }
@@ -118,6 +121,6 @@
.stateIn(
scope,
SharingStarted.WhileSubscribed(),
- realImpl.isSatelliteAllowedForCurrentLocation.value
+ realImpl.isSatelliteAllowedForCurrentLocation.value,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/demo/DemoDeviceBasedSatelliteRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/demo/DemoDeviceBasedSatelliteRepository.kt
index d557bbf..755899f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/demo/DemoDeviceBasedSatelliteRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/demo/DemoDeviceBasedSatelliteRepository.kt
@@ -16,15 +16,18 @@
package com.android.systemui.statusbar.pipeline.satellite.data.demo
+import android.content.res.Resources
+import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.res.R
import com.android.systemui.statusbar.pipeline.satellite.data.DeviceBasedSatelliteRepository
import com.android.systemui.statusbar.pipeline.satellite.shared.model.SatelliteConnectionState
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.MutableStateFlow
-import com.android.app.tracing.coroutines.launchTraced as launch
/** A satellite repository that represents the latest satellite values sent via demo mode. */
@SysUISingleton
@@ -33,9 +36,13 @@
constructor(
private val dataSource: DemoDeviceBasedSatelliteDataSource,
@Application private val scope: CoroutineScope,
+ @Main resources: Resources,
) : DeviceBasedSatelliteRepository {
private var demoCommandJob: Job? = null
+ override val isOpportunisticSatelliteIconEnabled =
+ resources.getBoolean(R.bool.config_showOpportunisticSatelliteIcon)
+
override val isSatelliteProvisioned = MutableStateFlow(true)
override val connectionState = MutableStateFlow(SatelliteConnectionState.Unknown)
override val signalStrength = MutableStateFlow(0)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImpl.kt
index 7686338..a36ef56 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImpl.kt
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.pipeline.satellite.data.prod
+import android.content.res.Resources
import android.os.OutcomeReceiver
import android.telephony.TelephonyCallback
import android.telephony.TelephonyManager
@@ -27,14 +28,17 @@
import android.telephony.satellite.SatelliteProvisionStateCallback
import android.telephony.satellite.SatelliteSupportedStateCallback
import androidx.annotation.VisibleForTesting
+import com.android.app.tracing.coroutines.launchTraced as launch
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.dagger.qualifiers.Main
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.core.LogLevel
import com.android.systemui.log.core.MessageInitializer
import com.android.systemui.log.core.MessagePrinter
+import com.android.systemui.res.R
import com.android.systemui.statusbar.pipeline.dagger.DeviceBasedSatelliteInputLog
import com.android.systemui.statusbar.pipeline.dagger.VerboseDeviceBasedSatelliteInputLog
import com.android.systemui.statusbar.pipeline.satellite.data.RealDeviceBasedSatelliteRepository
@@ -66,7 +70,6 @@
import kotlinx.coroutines.flow.mapNotNull
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.stateIn
-import com.android.app.tracing.coroutines.launchTraced as launch
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.withContext
@@ -146,10 +149,14 @@
@DeviceBasedSatelliteInputLog private val logBuffer: LogBuffer,
@VerboseDeviceBasedSatelliteInputLog private val verboseLogBuffer: LogBuffer,
private val systemClock: SystemClock,
+ @Main resources: Resources,
) : RealDeviceBasedSatelliteRepository {
private val satelliteManager: SatelliteManager?
+ override val isOpportunisticSatelliteIconEnabled: Boolean =
+ resources.getBoolean(R.bool.config_showOpportunisticSatelliteIcon)
+
// Some calls into satellite manager will throw exceptions if it is not supported.
// This is never expected to change after boot, but may need to be retried in some cases
@get:VisibleForTesting
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractor.kt
index f1a444f..08a98c3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractor.kt
@@ -53,6 +53,9 @@
@DeviceBasedSatelliteInputLog private val logBuffer: LogBuffer,
@DeviceBasedSatelliteTableLog private val tableLog: TableLogBuffer,
) {
+ /** Whether or not we should show the satellite icon when all connections are OOS */
+ val isOpportunisticSatelliteIconEnabled = repo.isOpportunisticSatelliteIconEnabled
+
/** Must be observed by any UI showing Satellite iconography */
val isSatelliteAllowed =
if (Flags.oemEnabledSatelliteFlag()) {
@@ -93,12 +96,7 @@
flowOf(0)
}
.distinctUntilChanged()
- .logDiffsForTable(
- tableLog,
- columnPrefix = "",
- columnName = COL_LEVEL,
- initialValue = 0,
- )
+ .logDiffsForTable(tableLog, columnPrefix = "", columnName = COL_LEVEL, initialValue = 0)
.stateIn(scope, SharingStarted.WhileSubscribed(), 0)
val isSatelliteProvisioned = repo.isSatelliteProvisioned
@@ -132,10 +130,9 @@
/** When all connections are considered OOS, satellite connectivity is potentially valid */
val areAllConnectionsOutOfService =
if (Flags.oemEnabledSatelliteFlag()) {
- combine(
- allConnectionsOos,
- iconsInteractor.isDeviceInEmergencyCallsOnlyMode,
- ) { connectionsOos, deviceEmergencyOnly ->
+ combine(allConnectionsOos, iconsInteractor.isDeviceInEmergencyCallsOnlyMode) {
+ connectionsOos,
+ deviceEmergencyOnly ->
logBuffer.log(
TAG,
LogLevel.INFO,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModel.kt
index 37f2f19..13ac321 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModel.kt
@@ -77,35 +77,38 @@
// This adds a 10 seconds delay before showing the icon
private val shouldShowIconForOosAfterHysteresis: StateFlow<Boolean> =
- interactor.areAllConnectionsOutOfService
- .flatMapLatest { shouldShow ->
- if (shouldShow) {
- logBuffer.log(
- TAG,
- LogLevel.INFO,
- { long1 = DELAY_DURATION.inWholeSeconds },
- { "Waiting $long1 seconds before showing the satellite icon" }
+ if (interactor.isOpportunisticSatelliteIconEnabled) {
+ interactor.areAllConnectionsOutOfService
+ .flatMapLatest { shouldShow ->
+ if (shouldShow) {
+ logBuffer.log(
+ TAG,
+ LogLevel.INFO,
+ { long1 = DELAY_DURATION.inWholeSeconds },
+ { "Waiting $long1 seconds before showing the satellite icon" },
+ )
+ delay(DELAY_DURATION)
+ flowOf(true)
+ } else {
+ flowOf(false)
+ }
+ }
+ .distinctUntilChanged()
+ .logDiffsForTable(
+ tableLog,
+ columnPrefix = "vm",
+ columnName = COL_VISIBLE_FOR_OOS,
+ initialValue = false,
)
- delay(DELAY_DURATION)
- flowOf(true)
- } else {
- flowOf(false)
- }
+ } else {
+ flowOf(false)
}
- .distinctUntilChanged()
- .logDiffsForTable(
- tableLog,
- columnPrefix = "vm",
- columnName = COL_VISIBLE_FOR_OOS,
- initialValue = false,
- )
.stateIn(scope, SharingStarted.WhileSubscribed(), false)
private val canShowIcon =
- combine(
- interactor.isSatelliteAllowed,
- interactor.isSatelliteProvisioned,
- ) { allowed, provisioned ->
+ combine(interactor.isSatelliteAllowed, interactor.isSatelliteProvisioned) {
+ allowed,
+ provisioned ->
allowed && provisioned
}
@@ -141,11 +144,10 @@
.stateIn(scope, SharingStarted.WhileSubscribed(), false)
override val icon: StateFlow<Icon?> =
- combine(
- showIcon,
- interactor.connectionState,
- interactor.signalStrength,
- ) { shouldShow, state, signalStrength ->
+ combine(showIcon, interactor.connectionState, interactor.signalStrength) {
+ shouldShow,
+ state,
+ signalStrength ->
if (shouldShow) {
SatelliteIconModel.fromConnectionState(state, signalStrength)
} else {
@@ -155,10 +157,7 @@
.stateIn(scope, SharingStarted.WhileSubscribed(), null)
override val carrierText: StateFlow<String?> =
- combine(
- showIcon,
- interactor.connectionState,
- ) { shouldShow, connectionState ->
+ combine(showIcon, interactor.connectionState) { shouldShow, connectionState ->
logBuffer.log(
TAG,
LogLevel.INFO,
@@ -166,7 +165,7 @@
bool1 = shouldShow
str1 = connectionState.name
},
- { "Updating carrier text. shouldShow=$bool1 connectionState=$str1" }
+ { "Updating carrier text. shouldShow=$bool1 connectionState=$str1" },
)
if (shouldShow) {
when (connectionState) {
diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
index 9c8ef04..1c3fece 100644
--- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
@@ -242,10 +242,16 @@
}
};
- private int getLatestWallpaperType(int userId) {
- return mWallpaperManager.getWallpaperIdForUser(WallpaperManager.FLAG_LOCK, userId)
- > mWallpaperManager.getWallpaperIdForUser(WallpaperManager.FLAG_SYSTEM, userId)
- ? WallpaperManager.FLAG_LOCK : WallpaperManager.FLAG_SYSTEM;
+ private int getDefaultWallpaperColorsSource(int userId) {
+ if (com.android.systemui.shared.Flags.newCustomizationPickerUi()) {
+ // The wallpaper colors source is always the home wallpaper.
+ return WallpaperManager.FLAG_SYSTEM;
+ } else {
+ // The wallpaper colors source is based on the last set wallpaper.
+ return mWallpaperManager.getWallpaperIdForUser(WallpaperManager.FLAG_LOCK, userId)
+ > mWallpaperManager.getWallpaperIdForUser(WallpaperManager.FLAG_SYSTEM, userId)
+ ? WallpaperManager.FLAG_LOCK : WallpaperManager.FLAG_SYSTEM;
+ }
}
private boolean isSeedColorSet(JSONObject jsonObject, WallpaperColors newWallpaperColors) {
@@ -279,9 +285,9 @@
private void handleWallpaperColors(WallpaperColors wallpaperColors, int flags, int userId) {
final int currentUser = mUserTracker.getUserId();
final boolean hadWallpaperColors = mCurrentColors.get(userId) != null;
- int latestWallpaperType = getLatestWallpaperType(userId);
- boolean eventForLatestWallpaper = (flags & latestWallpaperType) != 0;
- if (eventForLatestWallpaper) {
+ int wallpaperColorsSource = getDefaultWallpaperColorsSource(userId);
+ boolean wallpaperColorsNeedUpdate = (flags & wallpaperColorsSource) != 0;
+ if (wallpaperColorsNeedUpdate) {
mCurrentColors.put(userId, wallpaperColors);
if (DEBUG) Log.d(TAG, "got new colors: " + wallpaperColors + " where: " + flags);
}
@@ -328,7 +334,7 @@
boolean userChoseLockScreenColor = COLOR_SOURCE_LOCK.equals(wallpaperPickerColorSource);
boolean preserveLockScreenColor = isDestinationHomeOnly && userChoseLockScreenColor;
- if (!userChosePresetColor && !preserveLockScreenColor && eventForLatestWallpaper
+ if (!userChosePresetColor && !preserveLockScreenColor && wallpaperColorsNeedUpdate
&& !isSeedColorSet(jsonObject, wallpaperColors)) {
mSkipSettingChange = true;
if (jsonObject.has(OVERLAY_CATEGORY_ACCENT_COLOR) || jsonObject.has(
@@ -494,7 +500,7 @@
// Upon boot, make sure we have the most up to date colors
Runnable updateColors = () -> {
WallpaperColors systemColor = mWallpaperManager.getWallpaperColors(
- getLatestWallpaperType(mUserTracker.getUserId()));
+ getDefaultWallpaperColorsSource(mUserTracker.getUserId()));
Runnable applyColors = () -> {
if (DEBUG) Log.d(TAG, "Boot colors: " + systemColor);
mCurrentColors.put(mUserTracker.getUserId(), systemColor);
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt
index 3159124..63a5b3f 100644
--- a/packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt
@@ -20,6 +20,7 @@
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.coroutineScope
import androidx.lifecycle.repeatOnLifecycle
+import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.lifecycle.repeatWhenAttached
@@ -35,7 +36,7 @@
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.stateIn
-import com.android.app.tracing.coroutines.launchTraced as launch
+import kotlinx.coroutines.plus
/** A class allowing Java classes to collect on Kotlin flows. */
@SysUISingleton
@@ -102,6 +103,22 @@
}
}
+/**
+ * Collect information for the given [flow], calling [consumer] for each emitted event on the
+ * specified [collectContext].
+ *
+ * Collection will continue until the given [scope] is cancelled.
+ */
+@JvmOverloads
+fun <T> collectFlow(
+ scope: CoroutineScope,
+ collectContext: CoroutineContext = scope.coroutineContext,
+ flow: Flow<T>,
+ consumer: Consumer<T>,
+): Job {
+ return scope.plus(collectContext).launch { flow.collect { consumer.accept(it) } }
+}
+
fun <A, B, R> combineFlows(flow1: Flow<A>, flow2: Flow<B>, bifunction: (A, B) -> R): Flow<R> {
return combine(flow1, flow2, bifunction)
}
diff --git a/packages/SystemUI/tests/src/com/android/AAAPlusPlusVerifySysuiRequiredTestPropertiesTest.java b/packages/SystemUI/tests/src/com/android/AAAPlusPlusVerifySysuiRequiredTestPropertiesTest.java
index a95735e..82cfab6 100644
--- a/packages/SystemUI/tests/src/com/android/AAAPlusPlusVerifySysuiRequiredTestPropertiesTest.java
+++ b/packages/SystemUI/tests/src/com/android/AAAPlusPlusVerifySysuiRequiredTestPropertiesTest.java
@@ -56,7 +56,6 @@
@RunWith(AndroidTestingRunner.class)
@SmallTest
public class AAAPlusPlusVerifySysuiRequiredTestPropertiesTest extends SysuiTestCase {
-
private static final String TAG = "AAA++VerifyTest";
private static final Class[] BASE_CLS_TO_INCLUDE = {
@@ -149,6 +148,9 @@
*/
private boolean isTestClass(Class<?> loadedClass) {
try {
+ if (loadedClass.getAnnotation(SkipSysuiVerification.class) != null) {
+ return false;
+ }
if (Modifier.isAbstract(loadedClass.getModifiers())) {
logDebug(String.format("Skipping abstract class %s: not a test",
loadedClass.getName()));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
index e1b8a1d..91f9cce 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
@@ -212,6 +212,20 @@
}
@Test
+ fun testDimissOnLock() {
+ val container = initializeFingerprintContainer(addToView = true)
+ assertThat(container.parent).isNotNull()
+ val root = container.rootView
+
+ // Simulate sleep/lock invocation
+ container.onStartedGoingToSleep()
+ waitForIdleSync()
+
+ assertThat(container.parent).isNull()
+ assertThat(root.isAttachedToWindow).isFalse()
+ }
+
+ @Test
fun testCredentialPasswordDismissesOnBack() {
val container = initializeCredentialPasswordContainer(addToView = true)
assertThat(container.parent).isNotNull()
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 f472fd1..7d019bf 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
@@ -155,6 +155,7 @@
import com.android.systemui.shade.ShadeControllerImpl;
import com.android.systemui.shade.ShadeExpansionStateManager;
import com.android.systemui.shade.ShadeLogger;
+import com.android.systemui.shade.StatusBarLongPressGestureDetector;
import com.android.systemui.shared.notifications.domain.interactor.NotificationSettingsInteractor;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.KeyboardShortcutListSearch;
@@ -174,8 +175,8 @@
import com.android.systemui.statusbar.StatusBarStateControllerImpl;
import com.android.systemui.statusbar.core.StatusBarConnectedDisplays;
import com.android.systemui.statusbar.core.StatusBarInitializerImpl;
-import com.android.systemui.statusbar.core.StatusBarOrchestrator;
import com.android.systemui.statusbar.data.repository.FakeStatusBarModeRepository;
+import com.android.systemui.statusbar.data.repository.StatusBarModePerDisplayRepository;
import com.android.systemui.statusbar.notification.NotifPipelineFlags;
import com.android.systemui.statusbar.notification.NotificationActivityStarter;
import com.android.systemui.statusbar.notification.NotificationLaunchAnimatorControllerProvider;
@@ -371,7 +372,7 @@
@Mock private EmergencyGestureIntentFactory mEmergencyGestureIntentFactory;
@Mock private NotificationSettingsInteractor mNotificationSettingsInteractor;
@Mock private ViewCaptureAwareWindowManager mViewCaptureAwareWindowManager;
- @Mock private StatusBarOrchestrator mStatusBarOrchestrator;
+ @Mock private StatusBarLongPressGestureDetector mStatusBarLongPressGestureDetector;
private ShadeController mShadeController;
private final FakeSystemClock mFakeSystemClock = new FakeSystemClock();
private final FakeGlobalSettings mFakeGlobalSettings = new FakeGlobalSettings();
@@ -387,6 +388,9 @@
private final BrightnessMirrorShowingInteractor mBrightnessMirrorShowingInteractor =
mKosmos.getBrightnessMirrorShowingInteractor();
+
+ private final StatusBarModePerDisplayRepository mStatusBarModePerDisplayRepository =
+ mKosmos.getStatusBarModePerDisplayRepository();
private ScrimController mScrimController;
@Before
@@ -537,6 +541,7 @@
mAutoHideController,
new StatusBarInitializerImpl(
mStatusBarWindowController,
+ mStatusBarModePerDisplayRepository,
mCollapsedStatusBarFragmentProvider,
mock(StatusBarRootFactory.class),
mock(HomeStatusBarComponent.Factory.class),
@@ -602,6 +607,7 @@
mShadeController,
mWindowRootViewVisibilityInteractor,
mStatusBarKeyguardViewManager,
+ () -> mStatusBarLongPressGestureDetector,
mViewMediatorCallback,
mInitController,
new Handler(TestableLooper.get(this).getLooper()),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
index 638f195..69efa87 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
@@ -40,14 +40,13 @@
import com.android.systemui.plugins.fakeDarkIconDispatcher
import com.android.systemui.res.R
import com.android.systemui.scene.ui.view.WindowRootView
-import com.android.systemui.shade.LongPressGestureDetector
import com.android.systemui.shade.ShadeControllerImpl
import com.android.systemui.shade.ShadeLogger
import com.android.systemui.shade.ShadeViewController
+import com.android.systemui.shade.StatusBarLongPressGestureDetector
import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor
import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.statusbar.data.repository.fakeStatusBarContentInsetsProviderStore
-import com.android.systemui.statusbar.data.repository.statusBarContentInsetsProviderStore
import com.android.systemui.statusbar.policy.Clock
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.statusbar.window.StatusBarWindowStateController
@@ -98,7 +97,7 @@
@Mock private lateinit var windowRootView: Provider<WindowRootView>
@Mock private lateinit var shadeLogger: ShadeLogger
@Mock private lateinit var viewUtil: ViewUtil
- @Mock private lateinit var longPressGestureDetector: LongPressGestureDetector
+ @Mock private lateinit var mStatusBarLongPressGestureDetector: StatusBarLongPressGestureDetector
private lateinit var statusBarWindowStateController: StatusBarWindowStateController
private lateinit var view: PhoneStatusBarView
@@ -395,7 +394,7 @@
shadeControllerImpl,
shadeViewController,
panelExpansionInteractor,
- { longPressGestureDetector },
+ { mStatusBarLongPressGestureDetector },
windowRootView,
shadeLogger,
viewUtil,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImplTest.kt
index 4d293b9..6326e73 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImplTest.kt
@@ -102,6 +102,7 @@
logBuffer = FakeLogBuffer.Factory.create(),
verboseLogBuffer = FakeLogBuffer.Factory.create(),
systemClock,
+ context.resources,
)
val connectionState by collectLastValue(underTest.connectionState)
@@ -267,11 +268,7 @@
fun satelliteProvisioned_notSupported_defaultFalse() =
testScope.runTest {
// GIVEN satellite is not supported
- setUpRepo(
- uptime = MIN_UPTIME,
- satMan = satelliteManager,
- satelliteSupported = false,
- )
+ setUpRepo(uptime = MIN_UPTIME, satMan = satelliteManager, satelliteSupported = false)
assertThat(underTest.isSatelliteProvisioned.value).isFalse()
}
@@ -280,11 +277,7 @@
fun satelliteProvisioned_supported_defaultFalse() =
testScope.runTest {
// GIVEN satellite is supported
- setUpRepo(
- uptime = MIN_UPTIME,
- satMan = satelliteManager,
- satelliteSupported = true,
- )
+ setUpRepo(uptime = MIN_UPTIME, satMan = satelliteManager, satelliteSupported = true)
// THEN default provisioned state is false
assertThat(underTest.isSatelliteProvisioned.value).isFalse()
@@ -323,6 +316,7 @@
logBuffer = FakeLogBuffer.Factory.create(),
verboseLogBuffer = FakeLogBuffer.Factory.create(),
systemClock,
+ context.resources,
)
// WHEN we try to check for provisioned status
@@ -361,6 +355,7 @@
logBuffer = FakeLogBuffer.Factory.create(),
verboseLogBuffer = FakeLogBuffer.Factory.create(),
systemClock,
+ context.resources,
)
// WHEN we try to check for provisioned status
@@ -445,11 +440,7 @@
fun satelliteProvisioned_supported_tracksCallback_reRegistersOnCrash() =
testScope.runTest {
// GIVEN satellite is supported
- setUpRepo(
- uptime = MIN_UPTIME,
- satMan = satelliteManager,
- satelliteSupported = true,
- )
+ setUpRepo(uptime = MIN_UPTIME, satMan = satelliteManager, satelliteSupported = true)
val provisioned by collectLastValue(underTest.isSatelliteProvisioned)
@@ -487,11 +478,7 @@
fun satelliteNotSupported_listenersAreNotRegistered() =
testScope.runTest {
// GIVEN satellite is not supported
- setUpRepo(
- uptime = MIN_UPTIME,
- satMan = satelliteManager,
- satelliteSupported = false,
- )
+ setUpRepo(uptime = MIN_UPTIME, satMan = satelliteManager, satelliteSupported = false)
// WHEN data is requested from the repo
val connectionState by collectLastValue(underTest.connectionState)
@@ -517,11 +504,7 @@
fun satelliteNotSupported_registersCallbackForStateChanges() =
testScope.runTest {
// GIVEN satellite is not supported
- setUpRepo(
- uptime = MIN_UPTIME,
- satMan = satelliteManager,
- satelliteSupported = false,
- )
+ setUpRepo(uptime = MIN_UPTIME, satMan = satelliteManager, satelliteSupported = false)
runCurrent()
// THEN the repo registers for state changes of satellite support
@@ -577,11 +560,7 @@
fun satelliteNotSupported_supportShowsUp_registersListeners() =
testScope.runTest {
// GIVEN satellite is not supported
- setUpRepo(
- uptime = MIN_UPTIME,
- satMan = satelliteManager,
- satelliteSupported = false,
- )
+ setUpRepo(uptime = MIN_UPTIME, satMan = satelliteManager, satelliteSupported = false)
runCurrent()
val callback =
@@ -610,11 +589,7 @@
fun repoDoesNotCheckForSupportUntilMinUptime() =
testScope.runTest {
// GIVEN we init 100ms after sysui starts up
- setUpRepo(
- uptime = 100,
- satMan = satelliteManager,
- satelliteSupported = true,
- )
+ setUpRepo(uptime = 100, satMan = satelliteManager, satelliteSupported = true)
// WHEN data is requested
val connectionState by collectLastValue(underTest.connectionState)
@@ -726,6 +701,7 @@
logBuffer = FakeLogBuffer.Factory.create(),
verboseLogBuffer = FakeLogBuffer.Factory.create(),
systemClock,
+ context.resources,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/OnTeardownRule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/OnTeardownRule.kt
new file mode 100644
index 0000000..778614b
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/OnTeardownRule.kt
@@ -0,0 +1,75 @@
+/*
+ * 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
+
+import org.junit.rules.TestWatcher
+import org.junit.runner.Description
+import org.junit.runners.model.MultipleFailureException
+
+/**
+ * Rule that allows teardown steps to be added right next to the places where it becomes clear they
+ * are needed. This can avoid the need for complicated or conditional logic in a single teardown
+ * method. Examples:
+ * ```
+ * @get:Rule teardownRule = OnTeardownRule()
+ *
+ * // setup and teardown right next to each other
+ * @Before
+ * fun setUp() {
+ * val oldTimeout = getGlobalTimeout()
+ * teardownRule.onTeardown { setGlobalTimeout(oldTimeout) }
+ * overrideGlobalTimeout(5000)
+ * }
+ *
+ * // add teardown logic for fixtures that aren't used in every test
+ * fun addCustomer() {
+ * val id = globalDatabase.addCustomer(TEST_NAME, TEST_ADDRESS, ...)
+ * teardownRule.onTeardown { globalDatabase.deleteCustomer(id) }
+ * }
+ * ```
+ */
+class OnTeardownRule : TestWatcher() {
+ private var canAdd = true
+ private val teardowns = mutableListOf<() -> Unit>()
+
+ fun onTeardown(teardownRunnable: () -> Unit) {
+ if (!canAdd) {
+ throw IllegalStateException("Cannot add new teardown routines after test complete.")
+ }
+ teardowns.add(teardownRunnable)
+ }
+
+ fun onTeardown(teardownRunnable: Runnable) {
+ if (!canAdd) {
+ throw IllegalStateException("Cannot add new teardown routines after test complete.")
+ }
+ teardowns.add { teardownRunnable.run() }
+ }
+
+ override fun finished(description: Description?) {
+ canAdd = false
+ val errors = mutableListOf<Throwable>()
+ teardowns.reversed().forEach {
+ try {
+ it()
+ } catch (e: Throwable) {
+ errors.add(e)
+ }
+ }
+ MultipleFailureException.assertEmpty(errors)
+ }
+}
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 27a2cab..153a8be 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
@@ -56,6 +56,8 @@
import java.io.FileInputStream;
import java.io.IOException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.Future;
@@ -69,6 +71,17 @@
// background on Ravenwood is available at go/ravenwood-docs
@DisabledOnRavenwood
public abstract class SysuiTestCase {
+ /**
+ * Especially when self-testing test utilities, we may have classes that look like test
+ * classes, but we don't expect to ever actually run as a top-level test.
+ * For example, {@link com.android.systemui.TryToDoABadThing}.
+ * Verifying properties on these as a part of structural tests like
+ * AAAPlusPlusVerifySysuiRequiredTestPropertiesTest is a waste of our time, and makes things
+ * look more confusing, so this lets us skip when appropriate.
+ */
+ @Retention(RetentionPolicy.RUNTIME)
+ public @interface SkipSysuiVerification {
+ }
private static final String TAG = "SysuiTestCase";
@@ -172,6 +185,15 @@
public final DexmakerShareClassLoaderRule mDexmakerShareClassLoaderRule =
new DexmakerShareClassLoaderRule();
+ @Rule public final OnTeardownRule mTearDownRule = new OnTeardownRule();
+
+ /**
+ * Schedule a cleanup routine to happen when the test state is torn down.
+ */
+ protected void onTeardown(Runnable tearDownRunnable) {
+ mTearDownRule.onTeardown(tearDownRunnable);
+ }
+
// set the highest order so it's the innermost rule
@Rule(order = Integer.MAX_VALUE)
public TestWithLooperRule mlooperRule = new TestWithLooperRule();
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
index 5eaa198..0cfdc49 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
@@ -68,6 +68,7 @@
import com.android.systemui.shade.shadeController
import com.android.systemui.shade.ui.viewmodel.notificationShadeWindowModel
import com.android.systemui.statusbar.chips.ui.viewmodel.ongoingActivityChipsViewModel
+import com.android.systemui.statusbar.data.repository.fakeStatusBarModePerDisplayRepository
import com.android.systemui.statusbar.notification.domain.interactor.seenNotificationsInteractor
import com.android.systemui.statusbar.notification.stack.domain.interactor.headsUpNotificationInteractor
import com.android.systemui.statusbar.notification.stack.domain.interactor.sharedNotificationContainerInteractor
@@ -119,6 +120,7 @@
val mobileConnectionsRepository by lazy { kosmos.fakeMobileConnectionsRepository }
val simBouncerInteractor by lazy { kosmos.simBouncerInteractor }
val statusBarStateController by lazy { kosmos.statusBarStateController }
+ val statusBarModePerDisplayRepository by lazy { kosmos.fakeStatusBarModePerDisplayRepository }
val interactionJankMonitor by lazy { kosmos.interactionJankMonitor }
val fakeSceneContainerConfig by lazy { kosmos.sceneContainerConfig }
val sceneInteractor by lazy { kosmos.sceneInteractor }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/Media3ActionFactoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/Media3ActionFactoryKosmos.kt
new file mode 100644
index 0000000..7e7eea2
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/Media3ActionFactoryKosmos.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.media.controls.domain.pipeline
+
+import com.android.systemui.kosmos.Kosmos
+import org.mockito.kotlin.mock
+
+var Kosmos.media3ActionFactory: Media3ActionFactory by Kosmos.Fixture { mock {} }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoaderKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoaderKosmos.kt
index cb7750f5..af6a0c5 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoaderKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoaderKosmos.kt
@@ -34,6 +34,7 @@
fakeMediaControllerFactory,
mediaFlags,
imageLoader,
- statusBarManager
+ statusBarManager,
+ media3ActionFactory,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/util/FakeMediaControllerFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/util/FakeMediaControllerFactory.kt
index 7f8348e..b833750 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/util/FakeMediaControllerFactory.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/util/FakeMediaControllerFactory.kt
@@ -18,21 +18,32 @@
import android.content.Context
import android.media.session.MediaController
-import android.media.session.MediaSession
import android.media.session.MediaSession.Token
+import android.os.Looper
+import androidx.media3.session.MediaController as Media3Controller
+import androidx.media3.session.SessionToken
class FakeMediaControllerFactory(context: Context) : MediaControllerFactory(context) {
private val mediaControllersForToken = mutableMapOf<Token, MediaController>()
+ private var media3Controller: Media3Controller? = null
- override fun create(token: MediaSession.Token): android.media.session.MediaController {
+ override fun create(token: Token): MediaController {
if (token !in mediaControllersForToken) {
super.create(token)
}
return mediaControllersForToken[token]!!
}
+ override suspend fun create(token: SessionToken, looper: Looper): Media3Controller {
+ return media3Controller ?: super.create(token, looper)
+ }
+
fun setControllerForToken(token: Token, mediaController: MediaController) {
mediaControllersForToken[token] = mediaController
}
+
+ fun setMedia3Controller(mediaController: Media3Controller) {
+ media3Controller = mediaController
+ }
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/util/FakeSessionTokenFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/util/FakeSessionTokenFactory.kt
new file mode 100644
index 0000000..94e0bca
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/util/FakeSessionTokenFactory.kt
@@ -0,0 +1,33 @@
+/*
+ * 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.media.controls.util
+
+import android.content.Context
+import android.media.session.MediaSession.Token
+import androidx.media3.session.SessionToken
+
+class FakeSessionTokenFactory(context: Context) : SessionTokenFactory(context) {
+ private var sessionToken: SessionToken? = null
+
+ override suspend fun createTokenFromLegacy(token: Token): SessionToken {
+ return sessionToken ?: super.createTokenFromLegacy(token)
+ }
+
+ fun setMedia3SessionToken(token: SessionToken) {
+ sessionToken = token
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/util/SessionTokenFactoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/util/SessionTokenFactoryKosmos.kt
new file mode 100644
index 0000000..8e473042
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/util/SessionTokenFactoryKosmos.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.media.controls.util
+
+import android.content.applicationContext
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.fakeSessionTokenFactory by Kosmos.Fixture { FakeSessionTokenFactory(applicationContext) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/FakeStatusBarInitializerFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/FakeStatusBarInitializerFactory.kt
index 8c218be..50a19a9 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/FakeStatusBarInitializerFactory.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/FakeStatusBarInitializerFactory.kt
@@ -16,11 +16,13 @@
package com.android.systemui.statusbar.core
+import com.android.systemui.statusbar.data.repository.StatusBarModePerDisplayRepository
import com.android.systemui.statusbar.window.StatusBarWindowController
class FakeStatusBarInitializerFactory() : StatusBarInitializer.Factory {
override fun create(
- statusBarWindowController: StatusBarWindowController
+ statusBarWindowController: StatusBarWindowController,
+ statusBarModePerDisplayRepository: StatusBarModePerDisplayRepository,
): StatusBarInitializer = FakeStatusBarInitializer()
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarInitializerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarInitializerKosmos.kt
index 303529b..6e99027 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarInitializerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarInitializerKosmos.kt
@@ -19,6 +19,7 @@
import com.android.systemui.display.data.repository.displayRepository
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.statusbar.data.repository.fakeStatusBarModeRepository
import com.android.systemui.statusbar.window.fakeStatusBarWindowControllerStore
val Kosmos.fakeStatusBarInitializer by Kosmos.Fixture { FakeStatusBarInitializer() }
@@ -37,6 +38,7 @@
displayRepository,
fakeStatusBarInitializerFactory,
fakeStatusBarWindowControllerStore,
+ fakeStatusBarModeRepository,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeStatusBarModeRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeStatusBarModeRepository.kt
index 285cebb..8712b02 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeStatusBarModeRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeStatusBarModeRepository.kt
@@ -20,8 +20,10 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.statusbar.data.model.StatusBarAppearance
import com.android.systemui.statusbar.data.model.StatusBarMode
+import com.android.systemui.statusbar.phone.fragment.dagger.HomeStatusBarComponent
import dagger.Binds
import dagger.Module
+import java.io.PrintWriter
import javax.inject.Inject
import kotlinx.coroutines.flow.MutableStateFlow
@@ -53,6 +55,14 @@
override fun clearTransient() {
isTransientShown.value = false
}
+
+ override fun start() {}
+
+ override fun stop() {}
+
+ override fun onStatusBarViewInitialized(component: HomeStatusBarComponent) {}
+
+ override fun dump(pw: PrintWriter, args: Array<out String>) {}
}
@Module
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/LightBarControllerStoreKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/LightBarControllerStoreKosmos.kt
new file mode 100644
index 0000000..5f33732
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/LightBarControllerStoreKosmos.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.data.repository
+
+import com.android.systemui.display.data.repository.displayRepository
+import com.android.systemui.display.data.repository.displayScopeRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import org.mockito.kotlin.mock
+
+val Kosmos.lightBarControllerStoreImpl by
+ Kosmos.Fixture {
+ LightBarControllerStoreImpl(
+ backgroundApplicationScope = applicationCoroutineScope,
+ displayRepository = displayRepository,
+ factory = { _, _, _ -> mock() },
+ displayScopeRepository = displayScopeRepository,
+ statusBarModeRepositoryStore = statusBarModeRepository,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryKosmos.kt
index 12db2f741..a585602 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryKosmos.kt
@@ -16,7 +16,10 @@
package com.android.systemui.statusbar.data.repository
+import com.android.systemui.display.data.repository.displayRepository
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import org.mockito.kotlin.mock
val Kosmos.fakeStatusBarModePerDisplayRepository by
Kosmos.Fixture { FakeStatusBarModePerDisplayRepository() }
@@ -24,3 +27,21 @@
val Kosmos.statusBarModeRepository: StatusBarModeRepositoryStore by
Kosmos.Fixture { fakeStatusBarModeRepository }
val Kosmos.fakeStatusBarModeRepository by Kosmos.Fixture { FakeStatusBarModeRepository() }
+val Kosmos.fakeStatusBarModePerDisplayRepositoryFactory by
+ Kosmos.Fixture { FakeStatusBarModePerDisplayRepositoryFactory() }
+
+val Kosmos.multiDisplayStatusBarModeRepositoryStore by
+ Kosmos.Fixture {
+ MultiDisplayStatusBarModeRepositoryStore(
+ applicationCoroutineScope,
+ fakeStatusBarModePerDisplayRepositoryFactory,
+ displayRepository,
+ )
+ }
+
+class FakeStatusBarModePerDisplayRepositoryFactory : StatusBarModePerDisplayRepositoryFactory {
+
+ override fun create(displayId: Int): StatusBarModePerDisplayRepositoryImpl {
+ return mock<StatusBarModePerDisplayRepositoryImpl>()
+ }
+}
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java
index 66a6890..869d854 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java
@@ -26,12 +26,10 @@
import android.os.Bundle;
import android.platform.test.annotations.RavenwoodTestRunnerInitializing;
import android.platform.test.annotations.internal.InnerRunner;
-import android.platform.test.ravenwood.RavenwoodTestStats.Result;
import android.util.Log;
import androidx.test.platform.app.InstrumentationRegistry;
-import org.junit.AssumptionViolatedException;
import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runner.Runner;
@@ -171,10 +169,11 @@
final var notifier = new RavenwoodRunNotifier(realNotifier);
final var description = getDescription();
+ RavenwoodTestStats.getInstance().attachToRunNotifier(notifier);
+
if (mRealRunner instanceof ClassSkippingTestRunner) {
- mRealRunner.run(notifier);
Log.i(TAG, "onClassSkipped: description=" + description);
- RavenwoodTestStats.getInstance().onClassSkipped(description);
+ mRealRunner.run(notifier);
return;
}
@@ -205,7 +204,6 @@
if (!skipRunnerHook) {
try {
- RavenwoodTestStats.getInstance().onClassFinished(description);
mState.exitTestClass();
} catch (Throwable th) {
notifier.reportAfterTestFailure(th);
@@ -295,8 +293,6 @@
// method-level annotations here.
if (scope == Scope.Instance && order == Order.Outer) {
if (!RavenwoodEnablementChecker.shouldEnableOnRavenwood(description, true)) {
- RavenwoodTestStats.getInstance().onTestFinished(
- classDescription, description, Result.Skipped);
return false;
}
}
@@ -317,16 +313,6 @@
// End of a test method.
mState.exitTestMethod();
- final Result result;
- if (th == null) {
- result = Result.Passed;
- } else if (th instanceof AssumptionViolatedException) {
- result = Result.Skipped;
- } else {
- result = Result.Failed;
- }
-
- RavenwoodTestStats.getInstance().onTestFinished(classDescription, description, result);
}
// If RUN_DISABLED_TESTS is set, and the method did _not_ throw, make it an error.
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java
index 0f16352..28c262d 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java
@@ -40,6 +40,7 @@
import android.os.Bundle;
import android.os.HandlerThread;
import android.os.Looper;
+import android.os.Process_ravenwood;
import android.os.ServiceManager;
import android.os.SystemProperties;
import android.provider.DeviceConfig_host;
@@ -52,6 +53,7 @@
import com.android.hoststubgen.hosthelper.HostTestUtils;
import com.android.internal.os.RuntimeInit;
import com.android.ravenwood.RavenwoodRuntimeNative;
+import com.android.ravenwood.RavenwoodRuntimeState;
import com.android.ravenwood.common.RavenwoodCommonUtils;
import com.android.ravenwood.common.RavenwoodRuntimeException;
import com.android.ravenwood.common.SneakyThrow;
@@ -199,7 +201,7 @@
*/
public static void init(RavenwoodAwareTestRunner runner) {
if (RAVENWOOD_VERBOSE_LOGGING) {
- Log.i(TAG, "init() called here: " + runner, new RuntimeException("STACKTRACE"));
+ Log.v(TAG, "init() called here: " + runner, new RuntimeException("STACKTRACE"));
}
if (sRunner == runner) {
return;
@@ -223,7 +225,9 @@
Thread.setDefaultUncaughtExceptionHandler(sUncaughtExceptionHandler);
}
- android.os.Process.init$ravenwood(config.mUid, config.mPid);
+ RavenwoodRuntimeState.sUid = config.mUid;
+ RavenwoodRuntimeState.sPid = config.mPid;
+ RavenwoodRuntimeState.sTargetSdkLevel = config.mTargetSdkLevel;
sOriginalIdentityToken = Binder.clearCallingIdentity();
reinit();
setSystemProperties(config.mSystemProperties);
@@ -310,7 +314,7 @@
*/
public static void reset() {
if (RAVENWOOD_VERBOSE_LOGGING) {
- Log.i(TAG, "reset() called here", new RuntimeException("STACKTRACE"));
+ Log.v(TAG, "reset() called here", new RuntimeException("STACKTRACE"));
}
if (sRunner == null) {
throw new RavenwoodRuntimeException("Internal error: reset() already called");
@@ -350,8 +354,8 @@
if (sOriginalIdentityToken != -1) {
Binder.restoreCallingIdentity(sOriginalIdentityToken);
}
- android.os.Process.reset$ravenwood();
-
+ RavenwoodRuntimeState.reset();
+ Process_ravenwood.reset();
DeviceConfig_host.reset();
try {
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodTestStats.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodTestStats.java
index 016de8e..7870585 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodTestStats.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodTestStats.java
@@ -18,6 +18,9 @@
import android.util.Log;
import org.junit.runner.Description;
+import org.junit.runner.notification.Failure;
+import org.junit.runner.notification.RunListener;
+import org.junit.runner.notification.RunNotifier;
import java.io.File;
import java.io.IOException;
@@ -27,7 +30,7 @@
import java.nio.file.Paths;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
-import java.util.HashMap;
+import java.util.LinkedHashMap;
import java.util.Map;
/**
@@ -39,7 +42,7 @@
*/
public class RavenwoodTestStats {
private static final String TAG = "RavenwoodTestStats";
- private static final String HEADER = "Module,Class,ClassDesc,Passed,Failed,Skipped";
+ private static final String HEADER = "Module,Class,OuterClass,Passed,Failed,Skipped";
private static RavenwoodTestStats sInstance;
@@ -66,7 +69,7 @@
private final PrintWriter mOutputWriter;
private final String mTestModuleName;
- public final Map<Description, Map<Description, Result>> mStats = new HashMap<>();
+ public final Map<String, Map<String, Result>> mStats = new LinkedHashMap<>();
/** Ctor */
public RavenwoodTestStats() {
@@ -115,75 +118,129 @@
return cwd.getName();
}
- private void addResult(Description classDescription, Description methodDescription,
+ private void addResult(String className, String methodName,
Result result) {
- mStats.compute(classDescription, (classDesc, value) -> {
+ mStats.compute(className, (className_, value) -> {
if (value == null) {
- value = new HashMap<>();
+ value = new LinkedHashMap<>();
}
- value.put(methodDescription, result);
+ // If the result is already set, don't overwrite it.
+ if (!value.containsKey(methodName)) {
+ value.put(methodName, result);
+ }
return value;
});
}
/**
- * Call it when a test class is skipped.
- */
- public void onClassSkipped(Description classDescription) {
- addResult(classDescription, Description.EMPTY, Result.Skipped);
- onClassFinished(classDescription);
- }
-
- /**
* Call it when a test method is finished.
*/
- public void onTestFinished(Description classDescription, Description testDescription,
- Result result) {
- addResult(classDescription, testDescription, result);
+ private void onTestFinished(String className, String testName, Result result) {
+ addResult(className, testName, result);
}
/**
- * Call it when a test class is finished.
+ * Dump all the results and clear it.
*/
- public void onClassFinished(Description classDescription) {
- int passed = 0;
- int skipped = 0;
- int failed = 0;
- var stats = mStats.get(classDescription);
- if (stats == null) {
- return;
- }
- for (var e : stats.values()) {
- switch (e) {
- case Passed: passed++; break;
- case Skipped: skipped++; break;
- case Failed: failed++; break;
+ private void dumpAllAndClear() {
+ for (var entry : mStats.entrySet()) {
+ int passed = 0;
+ int skipped = 0;
+ int failed = 0;
+ var className = entry.getKey();
+
+ for (var e : entry.getValue().values()) {
+ switch (e) {
+ case Passed:
+ passed++;
+ break;
+ case Skipped:
+ skipped++;
+ break;
+ case Failed:
+ failed++;
+ break;
+ }
}
+
+ mOutputWriter.printf("%s,%s,%s,%d,%d,%d\n",
+ mTestModuleName, className, getOuterClassName(className),
+ passed, failed, skipped);
}
-
- var testClass = extractTestClass(classDescription);
-
- mOutputWriter.printf("%s,%s,%s,%d,%d,%d\n",
- mTestModuleName, (testClass == null ? "?" : testClass.getCanonicalName()),
- classDescription, passed, failed, skipped);
mOutputWriter.flush();
+ mStats.clear();
}
- /**
- * Try to extract the class from a description, which is needed because
- * ParameterizedAndroidJunit4's description doesn't contain a class.
- */
- private Class<?> extractTestClass(Description desc) {
- if (desc.getTestClass() != null) {
- return desc.getTestClass();
+ private static String getOuterClassName(String className) {
+ // Just delete the '$', because I'm not sure if the className we get here is actaully a
+ // valid class name that does exist. (it might have a parameter name, etc?)
+ int p = className.indexOf('$');
+ if (p < 0) {
+ return className;
}
- // Look into the children.
- for (var child : desc.getChildren()) {
- var fromChild = extractTestClass(child);
- if (fromChild != null) {
- return fromChild;
- }
- }
- return null;
+ return className.substring(0, p);
}
+
+ public void attachToRunNotifier(RunNotifier notifier) {
+ notifier.addListener(mRunListener);
+ }
+
+ private final RunListener mRunListener = new RunListener() {
+ @Override
+ public void testSuiteStarted(Description description) {
+ Log.d(TAG, "testSuiteStarted: " + description);
+ }
+
+ @Override
+ public void testSuiteFinished(Description description) {
+ Log.d(TAG, "testSuiteFinished: " + description);
+ }
+
+ @Override
+ public void testRunStarted(Description description) {
+ Log.d(TAG, "testRunStarted: " + description);
+ }
+
+ @Override
+ public void testRunFinished(org.junit.runner.Result result) {
+ Log.d(TAG, "testRunFinished: " + result);
+
+ dumpAllAndClear();
+ }
+
+ @Override
+ public void testStarted(Description description) {
+ Log.d(TAG, " testStarted: " + description);
+ }
+
+ @Override
+ public void testFinished(Description description) {
+ Log.d(TAG, " testFinished: " + description);
+
+ // Send "Passed", but if there's already another result sent for this, this won't
+ // override it.
+ onTestFinished(description.getClassName(), description.getMethodName(), Result.Passed);
+ }
+
+ @Override
+ public void testFailure(Failure failure) {
+ Log.d(TAG, " testFailure: " + failure);
+
+ var description = failure.getDescription();
+ onTestFinished(description.getClassName(), description.getMethodName(), Result.Failed);
+ }
+
+ @Override
+ public void testAssumptionFailure(Failure failure) {
+ Log.d(TAG, " testAssumptionFailure: " + failure);
+ var description = failure.getDescription();
+ onTestFinished(description.getClassName(), description.getMethodName(), Result.Skipped);
+ }
+
+ @Override
+ public void testIgnored(Description description) {
+ Log.d(TAG, " testIgnored: " + description);
+ onTestFinished(description.getClassName(), description.getMethodName(), Result.Skipped);
+ }
+ };
}
diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodConfig.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodConfig.java
index 37b0abc..d8f2b70 100644
--- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodConfig.java
+++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodConfig.java
@@ -23,6 +23,7 @@
import android.annotation.Nullable;
import android.app.Instrumentation;
import android.content.Context;
+import android.os.Build;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
@@ -67,7 +68,7 @@
String mTargetPackageName;
int mMinSdkLevel;
- int mTargetSdkLevel;
+ int mTargetSdkLevel = Build.VERSION_CODES.CUR_DEVELOPMENT;
boolean mProvideMainThread = false;
diff --git a/ravenwood/runtime-helper-src/framework/android/os/Process_ravenwood.java b/ravenwood/runtime-helper-src/framework/android/os/Process_ravenwood.java
new file mode 100644
index 0000000..3c6a4d7
--- /dev/null
+++ b/ravenwood/runtime-helper-src/framework/android/os/Process_ravenwood.java
@@ -0,0 +1,70 @@
+/*
+ * 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.os;
+
+import android.util.Pair;
+
+public class Process_ravenwood {
+
+ private static volatile ThreadLocal<Pair<Integer, Boolean>> sThreadPriority;
+
+ static {
+ reset();
+ }
+
+ public static void reset() {
+ // Reset the thread local variable
+ sThreadPriority = ThreadLocal.withInitial(
+ () -> Pair.create(Process.THREAD_PRIORITY_DEFAULT, true));
+ }
+
+ /**
+ * Called by {@link Process#setThreadPriority(int, int)}
+ */
+ public static void setThreadPriority(int tid, int priority) {
+ if (Process.myTid() == tid) {
+ boolean backgroundOk = sThreadPriority.get().second;
+ if (priority >= Process.THREAD_PRIORITY_BACKGROUND && !backgroundOk) {
+ throw new IllegalArgumentException(
+ "Priority " + priority + " blocked by setCanSelfBackground()");
+ }
+ sThreadPriority.set(Pair.create(priority, backgroundOk));
+ } else {
+ throw new UnsupportedOperationException(
+ "Cross-thread priority management not yet available in Ravenwood");
+ }
+ }
+
+ /**
+ * Called by {@link Process#setCanSelfBackground(boolean)}
+ */
+ public static void setCanSelfBackground(boolean backgroundOk) {
+ int priority = sThreadPriority.get().first;
+ sThreadPriority.set(Pair.create(priority, backgroundOk));
+ }
+
+ /**
+ * Called by {@link Process#getThreadPriority(int)}
+ */
+ public static int getThreadPriority(int tid) {
+ if (Process.myTid() == tid) {
+ return sThreadPriority.get().first;
+ } else {
+ throw new UnsupportedOperationException(
+ "Cross-thread priority management not yet available in Ravenwood");
+ }
+ }
+}
diff --git a/ravenwood/runtime-helper-src/framework/com/android/internal/ravenwood/RavenwoodEnvironment_host.java b/ravenwood/runtime-helper-src/framework/com/android/internal/ravenwood/RavenwoodEnvironment_host.java
index e12ff24..b65668b 100644
--- a/ravenwood/runtime-helper-src/framework/com/android/internal/ravenwood/RavenwoodEnvironment_host.java
+++ b/ravenwood/runtime-helper-src/framework/com/android/internal/ravenwood/RavenwoodEnvironment_host.java
@@ -23,14 +23,6 @@
}
/**
- * Called from {@link RavenwoodEnvironment#ensureRavenwoodInitialized()}.
- */
- public static void ensureRavenwoodInitialized() {
- // Initialization is now done by RavenwoodAwareTestRunner.
- // Should we remove it?
- }
-
- /**
* Called from {@link RavenwoodEnvironment#getRavenwoodRuntimePath()}.
*/
public static String getRavenwoodRuntimePath(RavenwoodEnvironment env) {
diff --git a/ravenwood/runtime-helper-src/libcore-fake/android/system/Os.java b/ravenwood/runtime-helper-src/libcore-fake/android/system/Os.java
index c94ef31..0298171 100644
--- a/ravenwood/runtime-helper-src/libcore-fake/android/system/Os.java
+++ b/ravenwood/runtime-helper-src/libcore-fake/android/system/Os.java
@@ -16,6 +16,7 @@
package android.system;
import com.android.ravenwood.RavenwoodRuntimeNative;
+import com.android.ravenwood.RavenwoodRuntimeState;
import com.android.ravenwood.common.JvmWorkaround;
import java.io.FileDescriptor;
@@ -97,4 +98,16 @@
public static void setenv(String name, String value, boolean overwrite) throws ErrnoException {
RavenwoodRuntimeNative.setenv(name, value, overwrite);
}
+
+ public static int getpid() {
+ return RavenwoodRuntimeState.sPid;
+ }
+
+ public static int getuid() {
+ return RavenwoodRuntimeState.sUid;
+ }
+
+ public static int gettid() {
+ return RavenwoodRuntimeNative.gettid();
+ }
}
diff --git a/ravenwood/runtime-helper-src/libcore-fake/com/android/ravenwood/RavenwoodRuntimeNative.java b/ravenwood/runtime-helper-src/libcore-fake/com/android/ravenwood/RavenwoodRuntimeNative.java
index f13189f..7b940b4 100644
--- a/ravenwood/runtime-helper-src/libcore-fake/com/android/ravenwood/RavenwoodRuntimeNative.java
+++ b/ravenwood/runtime-helper-src/libcore-fake/com/android/ravenwood/RavenwoodRuntimeNative.java
@@ -58,6 +58,8 @@
public static native void clearSystemProperties();
+ public static native int gettid();
+
public static long lseek(FileDescriptor fd, long offset, int whence) throws ErrnoException {
return nLseek(JvmWorkaround.getInstance().getFdInt(fd), offset, whence);
}
diff --git a/ravenwood/runtime-helper-src/libcore-fake/com/android/ravenwood/RavenwoodRuntimeState.java b/ravenwood/runtime-helper-src/libcore-fake/com/android/ravenwood/RavenwoodRuntimeState.java
new file mode 100644
index 0000000..175e020
--- /dev/null
+++ b/ravenwood/runtime-helper-src/libcore-fake/com/android/ravenwood/RavenwoodRuntimeState.java
@@ -0,0 +1,35 @@
+/*
+ * 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.ravenwood;
+
+public class RavenwoodRuntimeState {
+ // This must match VMRuntime.SDK_VERSION_CUR_DEVELOPMENT.
+ public static final int CUR_DEVELOPMENT = 10000;
+
+ public static volatile int sUid;
+ public static volatile int sPid;
+ public static volatile int sTargetSdkLevel;
+
+ static {
+ reset();
+ }
+
+ public static void reset() {
+ sUid = -1;
+ sPid = -1;
+ sTargetSdkLevel = CUR_DEVELOPMENT;
+ }
+}
diff --git a/ravenwood/runtime-helper-src/libcore-fake/dalvik/system/VMRuntime.java b/ravenwood/runtime-helper-src/libcore-fake/dalvik/system/VMRuntime.java
index ba89f71..eaadac6 100644
--- a/ravenwood/runtime-helper-src/libcore-fake/dalvik/system/VMRuntime.java
+++ b/ravenwood/runtime-helper-src/libcore-fake/dalvik/system/VMRuntime.java
@@ -19,6 +19,7 @@
// The original is here:
// $ANDROID_BUILD_TOP/libcore/libart/src/main/java/dalvik/system/VMRuntime.java
+import com.android.ravenwood.RavenwoodRuntimeState;
import com.android.ravenwood.common.JvmWorkaround;
import java.lang.reflect.Array;
@@ -52,4 +53,8 @@
public long addressOf(Object obj) {
return JvmWorkaround.getInstance().addressOf(obj);
}
+
+ public int getTargetSdkVersion() {
+ return RavenwoodRuntimeState.sTargetSdkLevel;
+ }
}
diff --git a/ravenwood/runtime-jni/ravenwood_runtime.cpp b/ravenwood/runtime-jni/ravenwood_runtime.cpp
index 2a3c26e..5b75e98 100644
--- a/ravenwood/runtime-jni/ravenwood_runtime.cpp
+++ b/ravenwood/runtime-jni/ravenwood_runtime.cpp
@@ -17,6 +17,7 @@
#include <fcntl.h>
#include <string.h>
#include <sys/stat.h>
+#include <sys/syscall.h>
#include <unistd.h>
#include <utils/misc.h>
@@ -173,6 +174,12 @@
throwIfMinusOne(env, "setenv", setenv(name.c_str(), value.c_str(), overwrite ? 1 : 0));
}
+
+static jint Linux_gettid(JNIEnv* env, jobject) {
+ // gettid(2() was added in glibc 2.30 but Android uses an older version in prebuilt.
+ return syscall(__NR_gettid);
+}
+
static void maybeRedirectLog() {
auto ravenwoodLogOut = getenv("RAVENWOOD_LOG_OUT");
if (ravenwoodLogOut == NULL) {
@@ -207,6 +214,7 @@
{ "stat", "(Ljava/lang/String;)Landroid/system/StructStat;", (void*)Linux_stat },
{ "nOpen", "(Ljava/lang/String;II)I", (void*)Linux_open },
{ "setenv", "(Ljava/lang/String;Ljava/lang/String;Z)V", (void*)Linux_setenv },
+ { "gettid", "()I", (void*)Linux_gettid },
};
extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) {
diff --git a/ravenwood/scripts/run-ravenwood-tests.sh b/ravenwood/scripts/run-ravenwood-tests.sh
index 5d623e0..672c685 100755
--- a/ravenwood/scripts/run-ravenwood-tests.sh
+++ b/ravenwood/scripts/run-ravenwood-tests.sh
@@ -18,12 +18,38 @@
# Options:
#
# -s: "Smoke" test -- skip slow tests (SysUI, ICU)
+#
+# -x PCRE: Specify exclusion filter in PCRE
+# Example: -x '^(Cts|hoststub)' # Exclude CTS and hoststubgen tests.
+#
+# -f PCRE: Specify inclusion filter in PCRE
+
+
+# Regex to identify slow tests, in PCRE
+SLOW_TEST_RE='^(SystemUiRavenTests|CtsIcuTestCasesRavenwood)$'
smoke=0
-while getopts "s" opt; do
+include_re=""
+exclude_re=""
+smoke_exclude_re=""
+dry_run=""
+while getopts "sx:f:d" opt; do
case "$opt" in
s)
- smoke=1
+ # Remove slow tests.
+ smoke_exclude_re="$SLOW_TEST_RE"
+ ;;
+ x)
+ # Take a PCRE from the arg, and use it as an exclusion filter.
+ exclude_re="$OPTARG"
+ ;;
+ f)
+ # Take a PCRE from the arg, and use it as an inclusion filter.
+ include_re="$OPTARG"
+ ;;
+ d)
+ # Dry run
+ dry_run="echo"
;;
'?')
exit 1
@@ -35,21 +61,46 @@
all_tests=(hoststubgentest tiny-framework-dump-test hoststubgen-invoke-test ravenwood-stats-checker)
all_tests+=( $(${0%/*}/list-ravenwood-tests.sh) )
-# Regex to identify slow tests, in PCRE
-slow_tests_re='^(SystemUiRavenTests|CtsIcuTestCasesRavenwood)$'
-
-if (( $smoke )) ; then
- # Remove the slow tests.
- all_tests=( $(
- for t in "${all_tests[@]}"; do
- echo $t | grep -vP "$slow_tests_re"
- done
- ) )
-fi
-
-run() {
- echo "Running: $*"
- "${@}"
+filter() {
+ local re="$1"
+ local grep_arg="$2"
+ if [[ "$re" == "" ]] ; then
+ cat # No filtering
+ else
+ grep $grep_arg -P "$re"
+ fi
}
-run ${ATEST:-atest} "${all_tests[@]}"
+filter_in() {
+ filter "$1"
+}
+
+filter_out() {
+ filter "$1" -v
+}
+
+
+# Remove the slow tests.
+targets=( $(
+ for t in "${all_tests[@]}"; do
+ echo $t | filter_in "$include_re" | filter_out "$smoke_exclude_re" | filter_out "$exclude_re"
+ done
+) )
+
+# Show the target tests
+
+echo "Target tests:"
+for t in "${targets[@]}"; do
+ echo " $t"
+done
+
+# Calculate the removed tests.
+
+diff="$(diff <(echo "${all_tests[@]}" | tr ' ' '\n') <(echo "${targets[@]}" | tr ' ' '\n') )"
+
+if [[ "$diff" != "" ]]; then
+ echo "Excluded tests:"
+ echo "$diff"
+fi
+
+$dry_run ${ATEST:-atest} "${targets[@]}"
diff --git a/ravenwood/tests/runtime-test/Android.bp b/ravenwood/tests/runtime-test/Android.bp
index 4102920..0c0df1f 100644
--- a/ravenwood/tests/runtime-test/Android.bp
+++ b/ravenwood/tests/runtime-test/Android.bp
@@ -10,6 +10,9 @@
android_ravenwood_test {
name: "RavenwoodRuntimeTest",
+ libs: [
+ "ravenwood-helper-runtime",
+ ],
static_libs: [
"androidx.annotation_annotation",
"androidx.test.ext.junit",
diff --git a/ravenwood/tests/runtime-test/test/com/android/ravenwoodtest/runtimetest/IdentityTest.java b/ravenwood/tests/runtime-test/test/com/android/ravenwoodtest/runtimetest/IdentityTest.java
new file mode 100644
index 0000000..8e04b69
--- /dev/null
+++ b/ravenwood/tests/runtime-test/test/com/android/ravenwoodtest/runtimetest/IdentityTest.java
@@ -0,0 +1,66 @@
+/*
+ * 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.ravenwoodtest.runtimetest;
+
+import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE;
+import static android.os.Process.FIRST_APPLICATION_UID;
+
+import static org.junit.Assert.assertEquals;
+
+import android.os.Binder;
+import android.os.Build;
+import android.os.Process;
+import android.platform.test.ravenwood.RavenwoodConfig;
+import android.system.Os;
+
+import com.android.ravenwood.RavenwoodRuntimeState;
+
+import dalvik.system.VMRuntime;
+
+import org.junit.Test;
+
+public class IdentityTest {
+
+ @RavenwoodConfig.Config
+ public static final RavenwoodConfig sConfig =
+ new RavenwoodConfig.Builder()
+ .setTargetSdkLevel(UPSIDE_DOWN_CAKE)
+ .setProcessApp()
+ .build();
+
+ @Test
+ public void testUid() {
+ assertEquals(FIRST_APPLICATION_UID, RavenwoodRuntimeState.sUid);
+ assertEquals(FIRST_APPLICATION_UID, Os.getuid());
+ assertEquals(FIRST_APPLICATION_UID, Process.myUid());
+ assertEquals(FIRST_APPLICATION_UID, Binder.getCallingUid());
+ }
+
+ @Test
+ public void testPid() {
+ int pid = RavenwoodRuntimeState.sPid;
+ assertEquals(pid, Os.getpid());
+ assertEquals(pid, Process.myPid());
+ assertEquals(pid, Binder.getCallingPid());
+ }
+
+ @Test
+ public void testTargetSdkLevel() {
+ assertEquals(Build.VERSION_CODES.CUR_DEVELOPMENT, RavenwoodRuntimeState.CUR_DEVELOPMENT);
+ assertEquals(UPSIDE_DOWN_CAKE, RavenwoodRuntimeState.sTargetSdkLevel);
+ assertEquals(UPSIDE_DOWN_CAKE, VMRuntime.getRuntime().getTargetSdkVersion());
+ }
+}
diff --git a/ravenwood/tests/runtime-test/test/com/android/ravenwoodtest/runtimetest/OsTest.java b/ravenwood/tests/runtime-test/test/com/android/ravenwoodtest/runtimetest/OsTest.java
index c2230c7..c55506a 100644
--- a/ravenwood/tests/runtime-test/test/com/android/ravenwoodtest/runtimetest/OsTest.java
+++ b/ravenwood/tests/runtime-test/test/com/android/ravenwoodtest/runtimetest/OsTest.java
@@ -24,6 +24,8 @@
import static android.system.OsConstants.S_ISSOCK;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
import static java.nio.file.LinkOption.NOFOLLOW_LINKS;
@@ -51,10 +53,12 @@
import java.nio.file.attribute.PosixFilePermission;
import java.util.HashSet;
import java.util.Set;
+import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@RunWith(AndroidJUnit4.class)
public class OsTest {
+
public interface ConsumerWithThrow<T> {
void accept(T var1) throws Exception;
}
@@ -165,6 +169,35 @@
});
}
+ private static class TestThread extends Thread {
+
+ final CountDownLatch mLatch = new CountDownLatch(1);
+ int mTid;
+
+ TestThread() {
+ setDaemon(true);
+ }
+
+ @Override
+ public void run() {
+ mTid = Os.gettid();
+ mLatch.countDown();
+ }
+ }
+
+ @Test
+ public void testGetTid() throws InterruptedException {
+ var t1 = new TestThread();
+ var t2 = new TestThread();
+ t1.start();
+ t2.start();
+ // Wait for thread execution
+ assertTrue(t1.mLatch.await(1, TimeUnit.SECONDS));
+ assertTrue(t2.mLatch.await(1, TimeUnit.SECONDS));
+ // Make sure the tid is unique per-thread
+ assertNotEquals(t1.mTid, t2.mTid);
+ }
+
// Verify StructStat values from libcore against native JVM PosixFileAttributes
private static void assertAttributesEqual(PosixFileAttributes attr, StructStat stat) {
assertEquals(attr.lastModifiedTime(), convertTimespecToFileTime(stat.st_mtim));
diff --git a/ravenwood/tests/runtime-test/test/com/android/ravenwoodtest/runtimetest/ProcessTest.java b/ravenwood/tests/runtime-test/test/com/android/ravenwoodtest/runtimetest/ProcessTest.java
new file mode 100644
index 0000000..d25b5c1
--- /dev/null
+++ b/ravenwood/tests/runtime-test/test/com/android/ravenwoodtest/runtimetest/ProcessTest.java
@@ -0,0 +1,54 @@
+/*
+ * 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.ravenwoodtest.runtimetest;
+
+import static android.os.Process.THREAD_PRIORITY_BACKGROUND;
+import static android.os.Process.THREAD_PRIORITY_DEFAULT;
+import static android.os.Process.THREAD_PRIORITY_FOREGROUND;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThrows;
+
+import android.os.Process;
+import android.system.Os;
+
+import org.junit.Test;
+
+public class ProcessTest {
+
+ @Test
+ public void testGetUidPidTid() {
+ assertEquals(Os.getuid(), Process.myUid());
+ assertEquals(Os.getpid(), Process.myPid());
+ assertEquals(Os.gettid(), Process.myTid());
+ }
+
+ @Test
+ public void testThreadPriority() {
+ assertThrows(UnsupportedOperationException.class,
+ () -> Process.getThreadPriority(Process.myTid() + 1));
+ assertThrows(UnsupportedOperationException.class,
+ () -> Process.setThreadPriority(Process.myTid() + 1, THREAD_PRIORITY_DEFAULT));
+ assertEquals(THREAD_PRIORITY_DEFAULT, Process.getThreadPriority(Process.myTid()));
+ Process.setThreadPriority(THREAD_PRIORITY_FOREGROUND);
+ assertEquals(THREAD_PRIORITY_FOREGROUND, Process.getThreadPriority(Process.myTid()));
+ Process.setCanSelfBackground(false);
+ Process.setThreadPriority(THREAD_PRIORITY_DEFAULT);
+ assertEquals(THREAD_PRIORITY_DEFAULT, Process.getThreadPriority(Process.myTid()));
+ assertThrows(IllegalArgumentException.class,
+ () -> Process.setThreadPriority(THREAD_PRIORITY_BACKGROUND));
+ }
+}
diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
index 68ff972..edecb2b 100644
--- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
+++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
@@ -898,18 +898,30 @@
for (int j = 0; j < widgetCount; j++) {
Widget widget = provider.widgets.get(j);
if (targetWidget != null && targetWidget != widget) continue;
+ // Identify the user in the host process since the intent will be invoked by
+ // the host app.
+ final Host host = widget.host;
+ final UserHandle hostUser;
+ if (host != null && host.id != null) {
+ hostUser = UserHandle.getUserHandleForUid(host.id.uid);
+ } else {
+ // Fallback to the parent profile if the host is null.
+ Slog.w(TAG, "Host is null when masking widget: " + widget.appWidgetId);
+ hostUser = mUserManager.getProfileParent(appUserId).getUserHandle();
+ }
if (provider.maskedByStoppedPackage) {
Intent intent = createUpdateIntentLocked(provider,
new int[] { widget.appWidgetId });
views.setOnClickPendingIntent(android.R.id.background,
- PendingIntent.getBroadcast(mContext, widget.appWidgetId,
+ PendingIntent.getBroadcastAsUser(mContext, widget.appWidgetId,
intent, PendingIntent.FLAG_UPDATE_CURRENT
- | PendingIntent.FLAG_IMMUTABLE));
+ | PendingIntent.FLAG_IMMUTABLE, hostUser));
} else if (onClickIntent != null) {
views.setOnClickPendingIntent(android.R.id.background,
- PendingIntent.getActivity(mContext, widget.appWidgetId, onClickIntent,
- PendingIntent.FLAG_UPDATE_CURRENT
- | PendingIntent.FLAG_IMMUTABLE));
+ PendingIntent.getActivityAsUser(mContext, widget.appWidgetId,
+ onClickIntent, PendingIntent.FLAG_UPDATE_CURRENT
+ | PendingIntent.FLAG_IMMUTABLE, null /* options */,
+ hostUser));
}
if (widget.replaceWithMaskedViewsLocked(views)) {
scheduleNotifyUpdateAppWidgetLocked(widget, widget.getEffectiveViewsLocked());
diff --git a/services/companion/java/com/android/server/companion/virtual/CameraAccessController.java b/services/companion/java/com/android/server/companion/virtual/CameraAccessController.java
index ef39846..8a4b1fa 100644
--- a/services/companion/java/com/android/server/companion/virtual/CameraAccessController.java
+++ b/services/companion/java/com/android/server/companion/virtual/CameraAccessController.java
@@ -59,14 +59,14 @@
private int mObserverCount = 0;
@GuardedBy("mLock")
- private ArrayMap<String, InjectionSessionData> mPackageToSessionData = new ArrayMap<>();
+ private final ArrayMap<String, InjectionSessionData> mPackageToSessionData = new ArrayMap<>();
/**
* Mapping from camera ID to open camera app associations. Key is the camera id, value is the
* information of the app's uid and package name.
*/
@GuardedBy("mLock")
- private ArrayMap<String, OpenCameraInfo> mAppsToBlockOnVirtualDevice = new ArrayMap<>();
+ private final ArrayMap<String, OpenCameraInfo> mAppsToBlockOnVirtualDevice = new ArrayMap<>();
static class InjectionSessionData {
public int appUid;
@@ -179,6 +179,15 @@
Slog.w(TAG, "Unexpected close with observers remaining: " + mObserverCount);
}
}
+ // Clean up camera injection sessions (if any).
+ synchronized (mLock) {
+ for (InjectionSessionData sessionData : mPackageToSessionData.values()) {
+ for (CameraInjectionSession session : sessionData.cameraIdToSession.values()) {
+ session.close();
+ }
+ }
+ mPackageToSessionData.clear();
+ }
mCameraManager.unregisterAvailabilityCallback(this);
}
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
index f87e3c3..604aaa7 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
@@ -17,6 +17,7 @@
package com.android.server.companion.virtual;
import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_DEFAULT;
+import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_DEFAULT_DEVICE_CAMERA_ACCESS;
import static android.media.AudioManager.AUDIO_SESSION_ID_GENERATE;
import static com.android.server.wm.ActivityInterceptorCallback.VIRTUAL_DEVICE_SERVICE_ORDERED_ID;
@@ -27,6 +28,7 @@
import android.annotation.RequiresPermission;
import android.annotation.SuppressLint;
import android.app.ActivityOptions;
+import android.app.compat.CompatChanges;
import android.companion.AssociationInfo;
import android.companion.AssociationRequest;
import android.companion.CompanionDeviceManager;
@@ -41,11 +43,14 @@
import android.companion.virtual.flags.Flags;
import android.companion.virtual.sensor.VirtualSensor;
import android.companion.virtualnative.IVirtualDeviceManagerNative;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledAfter;
import android.content.AttributionSource;
import android.content.Context;
import android.content.Intent;
import android.hardware.display.DisplayManagerInternal;
import android.os.Binder;
+import android.os.Build;
import android.os.Handler;
import android.os.IBinder;
import android.os.LocaleList;
@@ -88,7 +93,6 @@
import java.util.function.Consumer;
import java.util.stream.Collectors;
-
@SuppressLint("LongLogTag")
public class VirtualDeviceManagerService extends SystemService {
@@ -101,6 +105,11 @@
AssociationRequest.DEVICE_PROFILE_APP_STREAMING,
AssociationRequest.DEVICE_PROFILE_NEARBY_DEVICE_STREAMING);
+ /** Enable default device camera access for apps running on virtual devices. */
+ @ChangeId
+ @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM)
+ public static final long ENABLE_DEFAULT_DEVICE_CAMERA_ACCESS = 371173368L;
+
/**
* A virtual device association id corresponding to no CDM association.
*/
@@ -110,7 +119,7 @@
private final VirtualDeviceManagerImpl mImpl;
private final VirtualDeviceManagerNativeImpl mNativeImpl;
private final VirtualDeviceManagerInternal mLocalService;
- private VirtualDeviceLog mVirtualDeviceLog = new VirtualDeviceLog(getContext());
+ private final VirtualDeviceLog mVirtualDeviceLog = new VirtualDeviceLog(getContext());
private final Handler mHandler = new Handler(Looper.getMainLooper());
private final PendingTrampolineMap mPendingTrampolines = new PendingTrampolineMap(mHandler);
@@ -236,7 +245,7 @@
}
}
- void onCameraAccessBlocked(int appUid) {
+ private void onCameraAccessBlocked(int appUid) {
ArrayList<VirtualDeviceImpl> virtualDevicesSnapshot = getVirtualDevicesSnapshot();
for (int i = 0; i < virtualDevicesSnapshot.size(); i++) {
VirtualDeviceImpl virtualDevice = virtualDevicesSnapshot.get(i);
@@ -248,8 +257,13 @@
}
}
- CameraAccessController getCameraAccessController(UserHandle userHandle) {
- if (Flags.streamCamera()) {
+ private CameraAccessController getCameraAccessController(UserHandle userHandle,
+ VirtualDeviceParams params, String callingPackage) {
+ if (CompatChanges.isChangeEnabled(ENABLE_DEFAULT_DEVICE_CAMERA_ACCESS, callingPackage,
+ userHandle)
+ && android.companion.virtualdevice.flags.Flags.defaultDeviceCameraAccessPolicy()
+ && (params.getDevicePolicy(POLICY_TYPE_DEFAULT_DEVICE_CAMERA_ACCESS)
+ == DEVICE_POLICY_DEFAULT)) {
return null;
}
int userId = userHandle.getIdentifier();
@@ -496,7 +510,8 @@
final UserHandle userHandle = getCallingUserHandle();
final CameraAccessController cameraAccessController =
- getCameraAccessController(userHandle);
+ getCameraAccessController(userHandle, params,
+ attributionSource.getPackageName());
final int deviceId = sNextUniqueIndex.getAndIncrement();
final Consumer<ArraySet<Integer>> runningAppsChangedCallback =
runningUids -> notifyRunningAppsChanged(deviceId, runningUids);
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 1c3569d..3e7bcb8 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -5155,6 +5155,11 @@
mContext.registerReceiver(new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
+ final String action = intent.getAction();
+ if (action == null) {
+ return;
+ }
+
String[] pkgs = intent.getStringArrayExtra(Intent.EXTRA_PACKAGES);
if (pkgs != null) {
for (String pkg : pkgs) {
diff --git a/services/core/java/com/android/server/appbinding/AppBindingService.java b/services/core/java/com/android/server/appbinding/AppBindingService.java
index 5db6dc7..6ccb3ee 100644
--- a/services/core/java/com/android/server/appbinding/AppBindingService.java
+++ b/services/core/java/com/android/server/appbinding/AppBindingService.java
@@ -235,6 +235,9 @@
}
final String action = intent.getAction();
+ if (action == null) {
+ return;
+ }
if (Intent.ACTION_USER_REMOVED.equals(action)) {
onUserRemoved(userId);
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 985155d..0cf55bb 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -493,7 +493,7 @@
private static final int MSG_INIT_ADI_DEVICE_STATES = 103;
private static final int MSG_INIT_INPUT_GAINS = 104;
- private static final int MSG_SET_INPUT_GAIN_INDEX = 105;
+ private static final int MSG_APPLY_INPUT_GAIN_INDEX = 105;
private static final int MSG_PERSIST_INPUT_GAIN_INDEX = 106;
// end of messages handled under wakelock
@@ -1626,7 +1626,6 @@
new InputDeviceVolumeHelper(
mSettings,
mContentResolver,
- mSettingsLock,
System.INPUT_GAIN_INDEX_SETTINGS);
}
@@ -5804,7 +5803,7 @@
// to persist).
sendMsg(
mAudioHandler,
- MSG_SET_INPUT_GAIN_INDEX,
+ MSG_APPLY_INPUT_GAIN_INDEX,
SENDMSG_QUEUE,
/*arg1*/ index,
/*arg2*/ 0,
@@ -5813,22 +5812,22 @@
}
}
- private void setInputGainIndexInt(@NonNull AudioDeviceAttributes ada, int index) {
+ private void onApplyInputGainIndex(@NonNull AudioDeviceAttributes ada, int index) {
// TODO(b/364923030): call AudioSystem to apply input gain in native layer.
// Post a persist input gain msg.
sendMsg(
mAudioHandler,
MSG_PERSIST_INPUT_GAIN_INDEX,
- SENDMSG_QUEUE,
- /*arg1*/ index,
+ SENDMSG_REPLACE,
+ /*arg1*/ 0,
/*arg2*/ 0,
/*obj*/ ada,
PERSIST_DELAY);
}
- private void persistInputGainIndex(@NonNull AudioDeviceAttributes ada, int index) {
- mInputDeviceVolumeHelper.persistInputGainIndex(ada, index);
+ private void onPersistInputGainIndex(@NonNull AudioDeviceAttributes ada) {
+ mInputDeviceVolumeHelper.persistInputGainIndex(ada);
}
/**
@@ -10213,12 +10212,12 @@
vgs.persistVolumeGroup(msg.arg1);
break;
- case MSG_SET_INPUT_GAIN_INDEX:
- setInputGainIndexInt((AudioDeviceAttributes) msg.obj, msg.arg1);
+ case MSG_APPLY_INPUT_GAIN_INDEX:
+ onApplyInputGainIndex((AudioDeviceAttributes) msg.obj, msg.arg1);
break;
case MSG_PERSIST_INPUT_GAIN_INDEX:
- persistInputGainIndex((AudioDeviceAttributes) msg.obj, msg.arg1);
+ onPersistInputGainIndex((AudioDeviceAttributes) msg.obj);
break;
case MSG_PERSIST_RINGER_MODE:
diff --git a/services/core/java/com/android/server/audio/InputDeviceVolumeHelper.java b/services/core/java/com/android/server/audio/InputDeviceVolumeHelper.java
index d83dca6..d094629 100644
--- a/services/core/java/com/android/server/audio/InputDeviceVolumeHelper.java
+++ b/services/core/java/com/android/server/audio/InputDeviceVolumeHelper.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2014 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.
@@ -43,7 +43,6 @@
private final SettingsAdapter mSettings;
private final ContentResolver mContentResolver;
- private final Object mSettingsLock;
private final String mInputGainIndexSettingsName;
// A map between device internal type (e.g. AudioSystem.DEVICE_IN_BUILTIN_MIC) to its input gain
@@ -54,11 +53,9 @@
InputDeviceVolumeHelper(
SettingsAdapter settings,
ContentResolver contentResolver,
- Object settingsLock,
String settingsName) {
mSettings = settings;
mContentResolver = contentResolver;
- mSettingsLock = settingsLock;
mInputGainIndexSettingsName = settingsName;
IntArray internalDeviceTypes = new IntArray();
@@ -82,34 +79,27 @@
readSettings();
}
- public void readSettings() {
+ private void readSettings() {
synchronized (InputDeviceVolumeHelper.class) {
for (int inputDeviceType : mSupportedDeviceTypes) {
// Retrieve current input gain for device. If no input gain stored for current
// device, use default input gain.
- int index;
- if (!hasValidSettingsName()) {
- index = INDEX_DEFAULT;
- } else {
- String name = getSettingNameForDevice(inputDeviceType);
- index =
- mSettings.getSystemIntForUser(
- mContentResolver, name, INDEX_DEFAULT, UserHandle.USER_CURRENT);
- }
+ String name = getSettingNameForDevice(inputDeviceType);
+ int index = name == null
+ ? INDEX_DEFAULT
+ : mSettings.getSystemIntForUser(
+ mContentResolver, name, INDEX_DEFAULT, UserHandle.USER_CURRENT);
mInputGainIndexMap.put(inputDeviceType, getValidIndex(index));
}
}
}
- public boolean hasValidSettingsName() {
- return mInputGainIndexSettingsName != null && !mInputGainIndexSettingsName.isEmpty();
- }
-
- public @Nullable String getSettingNameForDevice(int inputDeviceType) {
- if (!hasValidSettingsName()) {
+ private @Nullable String getSettingNameForDevice(int inputDeviceType) {
+ if (mInputGainIndexSettingsName == null || mInputGainIndexSettingsName.isEmpty()) {
return null;
}
+
final String suffix = AudioSystem.getInputDeviceName(inputDeviceType);
if (suffix.isEmpty()) {
return mInputGainIndexSettingsName;
@@ -158,29 +148,27 @@
ensureValidInputDeviceType(inputDeviceType);
int oldIndex;
- synchronized (mSettingsLock) {
- synchronized (InputDeviceVolumeHelper.class) {
- oldIndex = getInputGainIndex(ada);
- index = getValidIndex(index);
+ synchronized (InputDeviceVolumeHelper.class) {
+ oldIndex = getInputGainIndex(ada);
+ index = getValidIndex(index);
- if (oldIndex == index) {
- return false;
- }
-
- mInputGainIndexMap.put(inputDeviceType, index);
- return true;
+ if (oldIndex == index) {
+ return false;
}
+
+ mInputGainIndexMap.put(inputDeviceType, index);
+ return true;
}
}
- public void persistInputGainIndex(@NonNull AudioDeviceAttributes ada, int index) {
+ public void persistInputGainIndex(@NonNull AudioDeviceAttributes ada) {
int inputDeviceType = AudioDeviceInfo.convertDeviceTypeToInternalInputDevice(ada.getType());
- ensureValidInputDeviceType(inputDeviceType);
-
- if (hasValidSettingsName()) {
+ String name = getSettingNameForDevice(inputDeviceType);
+ if (name != null) {
+ int index = getInputGainIndex(ada);
mSettings.putSystemIntForUser(
mContentResolver,
- getSettingNameForDevice(inputDeviceType),
+ name,
index,
UserHandle.USER_CURRENT);
}
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
index b696c54..1b527da 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
@@ -273,13 +273,8 @@
private class DelayedStandbyOnActiveSourceLostRunnable implements Runnable {
@Override
public void run() {
- if (mService.getPowerManagerInternal().wasDeviceIdleFor(
- STANDBY_AFTER_ACTIVE_SOURCE_LOST_DELAY_MS)) {
+ if (!isActiveSource()) {
mService.standby();
- } else {
- mService.setAndBroadcastActiveSource(mService.getPhysicalAddress(),
- getDeviceInfo().getDeviceType(), Constants.ADDR_TV,
- "DelayedActiveSourceLostStandbyRunnable");
}
}
}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 0104373..d8483f7 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -4969,7 +4969,7 @@
final var userData = getUserData(userId);
if (Flags.refactorInsetsController()) {
setImeVisibilityOnFocusedWindowClient(false, userData,
- null /* TODO(b329229469) check statsToken */);
+ null /* TODO(b/353463205) check statsToken */);
} else {
hideCurrentInputLocked(userData.mImeBindingState.mFocusedWindow,
diff --git a/services/core/java/com/android/server/integrity/parser/RuleMetadataParser.java b/services/core/java/com/android/server/integrity/parser/RuleMetadataParser.java
deleted file mode 100644
index e831e40..0000000
--- a/services/core/java/com/android/server/integrity/parser/RuleMetadataParser.java
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.integrity.parser;
-
-import android.annotation.Nullable;
-import android.util.Xml;
-
-import com.android.modules.utils.TypedXmlPullParser;
-import com.android.server.integrity.model.RuleMetadata;
-
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-
-import java.io.IOException;
-import java.io.InputStream;
-
-/** Helper class for parsing rule metadata. */
-public class RuleMetadataParser {
-
- public static final String RULE_PROVIDER_TAG = "P";
- public static final String VERSION_TAG = "V";
-
- /** Parse the rule metadata from an input stream. */
- @Nullable
- public static RuleMetadata parse(InputStream inputStream)
- throws XmlPullParserException, IOException {
-
- String ruleProvider = "";
- String version = "";
-
- TypedXmlPullParser xmlPullParser = Xml.resolvePullParser(inputStream);
-
- int eventType;
- while ((eventType = xmlPullParser.next()) != XmlPullParser.END_DOCUMENT) {
- if (eventType == XmlPullParser.START_TAG) {
- String tag = xmlPullParser.getName();
- switch (tag) {
- case RULE_PROVIDER_TAG:
- ruleProvider = xmlPullParser.nextText();
- break;
- case VERSION_TAG:
- version = xmlPullParser.nextText();
- break;
- default:
- throw new IllegalStateException("Unknown tag in metadata: " + tag);
- }
- }
- }
-
- return new RuleMetadata(ruleProvider, version);
- }
-}
diff --git a/services/core/java/com/android/server/integrity/serializer/RuleBinarySerializer.java b/services/core/java/com/android/server/integrity/serializer/RuleBinarySerializer.java
deleted file mode 100644
index 8ba5870..0000000
--- a/services/core/java/com/android/server/integrity/serializer/RuleBinarySerializer.java
+++ /dev/null
@@ -1,324 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.integrity.serializer;
-
-import static com.android.server.integrity.model.ComponentBitSize.ATOMIC_FORMULA_START;
-import static com.android.server.integrity.model.ComponentBitSize.COMPOUND_FORMULA_END;
-import static com.android.server.integrity.model.ComponentBitSize.COMPOUND_FORMULA_START;
-import static com.android.server.integrity.model.ComponentBitSize.CONNECTOR_BITS;
-import static com.android.server.integrity.model.ComponentBitSize.DEFAULT_FORMAT_VERSION;
-import static com.android.server.integrity.model.ComponentBitSize.EFFECT_BITS;
-import static com.android.server.integrity.model.ComponentBitSize.FORMAT_VERSION_BITS;
-import static com.android.server.integrity.model.ComponentBitSize.INSTALLER_ALLOWED_BY_MANIFEST_START;
-import static com.android.server.integrity.model.ComponentBitSize.KEY_BITS;
-import static com.android.server.integrity.model.ComponentBitSize.OPERATOR_BITS;
-import static com.android.server.integrity.model.ComponentBitSize.SEPARATOR_BITS;
-import static com.android.server.integrity.model.ComponentBitSize.VALUE_SIZE_BITS;
-import static com.android.server.integrity.model.IndexingFileConstants.END_INDEXING_KEY;
-import static com.android.server.integrity.model.IndexingFileConstants.INDEXING_BLOCK_SIZE;
-import static com.android.server.integrity.model.IndexingFileConstants.START_INDEXING_KEY;
-import static com.android.server.integrity.serializer.RuleIndexingDetails.APP_CERTIFICATE_INDEXED;
-import static com.android.server.integrity.serializer.RuleIndexingDetails.NOT_INDEXED;
-import static com.android.server.integrity.serializer.RuleIndexingDetails.PACKAGE_NAME_INDEXED;
-
-import android.content.integrity.AtomicFormula;
-import android.content.integrity.CompoundFormula;
-import android.content.integrity.InstallerAllowedByManifestFormula;
-import android.content.integrity.IntegrityFormula;
-import android.content.integrity.IntegrityUtils;
-import android.content.integrity.Rule;
-
-import com.android.internal.util.Preconditions;
-import com.android.server.integrity.model.BitOutputStream;
-import com.android.server.integrity.model.ByteTrackedOutputStream;
-
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.OutputStream;
-import java.nio.charset.StandardCharsets;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
-import java.util.stream.Collectors;
-
-/** A helper class to serialize rules from the {@link Rule} model to Binary representation. */
-public class RuleBinarySerializer implements RuleSerializer {
- static final int TOTAL_RULE_SIZE_LIMIT = 200000;
- static final int INDEXED_RULE_SIZE_LIMIT = 100000;
- static final int NONINDEXED_RULE_SIZE_LIMIT = 1000;
-
- // Get the byte representation for a list of rules.
- @Override
- public byte[] serialize(List<Rule> rules, Optional<Integer> formatVersion)
- throws RuleSerializeException {
- try {
- ByteArrayOutputStream rulesOutputStream = new ByteArrayOutputStream();
- serialize(rules, formatVersion, rulesOutputStream, new ByteArrayOutputStream());
- return rulesOutputStream.toByteArray();
- } catch (Exception e) {
- throw new RuleSerializeException(e.getMessage(), e);
- }
- }
-
- // Get the byte representation for a list of rules, and write them to an output stream.
- @Override
- public void serialize(
- List<Rule> rules,
- Optional<Integer> formatVersion,
- OutputStream rulesFileOutputStream,
- OutputStream indexingFileOutputStream)
- throws RuleSerializeException {
- try {
- if (rules == null) {
- throw new IllegalArgumentException("Null rules cannot be serialized.");
- }
-
- if (rules.size() > TOTAL_RULE_SIZE_LIMIT) {
- throw new IllegalArgumentException("Too many rules provided: " + rules.size());
- }
-
- // Determine the indexing groups and the order of the rules within each indexed group.
- Map<Integer, Map<String, List<Rule>>> indexedRules =
- RuleIndexingDetailsIdentifier.splitRulesIntoIndexBuckets(rules);
-
- // Validate the rule blocks are not larger than expected limits.
- verifySize(indexedRules.get(PACKAGE_NAME_INDEXED), INDEXED_RULE_SIZE_LIMIT);
- verifySize(indexedRules.get(APP_CERTIFICATE_INDEXED), INDEXED_RULE_SIZE_LIMIT);
- verifySize(indexedRules.get(NOT_INDEXED), NONINDEXED_RULE_SIZE_LIMIT);
-
- // Serialize the rules.
- ByteTrackedOutputStream ruleFileByteTrackedOutputStream =
- new ByteTrackedOutputStream(rulesFileOutputStream);
- serializeRuleFileMetadata(formatVersion, ruleFileByteTrackedOutputStream);
- LinkedHashMap<String, Integer> packageNameIndexes =
- serializeRuleList(
- indexedRules.get(PACKAGE_NAME_INDEXED),
- ruleFileByteTrackedOutputStream);
- LinkedHashMap<String, Integer> appCertificateIndexes =
- serializeRuleList(
- indexedRules.get(APP_CERTIFICATE_INDEXED),
- ruleFileByteTrackedOutputStream);
- LinkedHashMap<String, Integer> unindexedRulesIndexes =
- serializeRuleList(
- indexedRules.get(NOT_INDEXED), ruleFileByteTrackedOutputStream);
-
- // Serialize their indexes.
- BitOutputStream indexingBitOutputStream = new BitOutputStream(indexingFileOutputStream);
- serializeIndexGroup(packageNameIndexes, indexingBitOutputStream, /* isIndexed= */ true);
- serializeIndexGroup(
- appCertificateIndexes, indexingBitOutputStream, /* isIndexed= */ true);
- serializeIndexGroup(
- unindexedRulesIndexes, indexingBitOutputStream, /* isIndexed= */ false);
- indexingBitOutputStream.flush();
- } catch (Exception e) {
- throw new RuleSerializeException(e.getMessage(), e);
- }
- }
-
- private void verifySize(Map<String, List<Rule>> ruleListMap, int ruleSizeLimit) {
- int totalRuleCount =
- ruleListMap.values().stream()
- .map(list -> list.size())
- .collect(Collectors.summingInt(Integer::intValue));
- if (totalRuleCount > ruleSizeLimit) {
- throw new IllegalArgumentException(
- "Too many rules provided in the indexing group. Provided "
- + totalRuleCount
- + " limit "
- + ruleSizeLimit);
- }
- }
-
- private void serializeRuleFileMetadata(
- Optional<Integer> formatVersion, ByteTrackedOutputStream outputStream)
- throws IOException {
- int formatVersionValue = formatVersion.orElse(DEFAULT_FORMAT_VERSION);
-
- BitOutputStream bitOutputStream = new BitOutputStream(outputStream);
- bitOutputStream.setNext(FORMAT_VERSION_BITS, formatVersionValue);
- bitOutputStream.flush();
- }
-
- private LinkedHashMap<String, Integer> serializeRuleList(
- Map<String, List<Rule>> rulesMap, ByteTrackedOutputStream outputStream)
- throws IOException {
- Preconditions.checkArgument(
- rulesMap != null, "serializeRuleList should never be called with null rule list.");
-
- BitOutputStream bitOutputStream = new BitOutputStream(outputStream);
- LinkedHashMap<String, Integer> indexMapping = new LinkedHashMap();
- indexMapping.put(START_INDEXING_KEY, outputStream.getWrittenBytesCount());
-
- List<String> sortedKeys = rulesMap.keySet().stream().sorted().collect(Collectors.toList());
- int indexTracker = 0;
- for (String key : sortedKeys) {
- if (indexTracker >= INDEXING_BLOCK_SIZE) {
- indexMapping.put(key, outputStream.getWrittenBytesCount());
- indexTracker = 0;
- }
-
- for (Rule rule : rulesMap.get(key)) {
- serializeRule(rule, bitOutputStream);
- bitOutputStream.flush();
- indexTracker++;
- }
- }
- indexMapping.put(END_INDEXING_KEY, outputStream.getWrittenBytesCount());
-
- return indexMapping;
- }
-
- private void serializeRule(Rule rule, BitOutputStream bitOutputStream) throws IOException {
- if (rule == null) {
- throw new IllegalArgumentException("Null rule can not be serialized");
- }
-
- // Start with a '1' bit to mark the start of a rule.
- bitOutputStream.setNext();
-
- serializeFormula(rule.getFormula(), bitOutputStream);
- bitOutputStream.setNext(EFFECT_BITS, rule.getEffect());
-
- // End with a '1' bit to mark the end of a rule.
- bitOutputStream.setNext();
- }
-
- private void serializeFormula(IntegrityFormula formula, BitOutputStream bitOutputStream)
- throws IOException {
- if (formula instanceof AtomicFormula) {
- serializeAtomicFormula((AtomicFormula) formula, bitOutputStream);
- } else if (formula instanceof CompoundFormula) {
- serializeCompoundFormula((CompoundFormula) formula, bitOutputStream);
- } else if (formula instanceof InstallerAllowedByManifestFormula) {
- bitOutputStream.setNext(SEPARATOR_BITS, INSTALLER_ALLOWED_BY_MANIFEST_START);
- } else {
- throw new IllegalArgumentException(
- String.format("Invalid formula type: %s", formula.getClass()));
- }
- }
-
- private void serializeCompoundFormula(
- CompoundFormula compoundFormula, BitOutputStream bitOutputStream) throws IOException {
- if (compoundFormula == null) {
- throw new IllegalArgumentException("Null compound formula can not be serialized");
- }
-
- bitOutputStream.setNext(SEPARATOR_BITS, COMPOUND_FORMULA_START);
- bitOutputStream.setNext(CONNECTOR_BITS, compoundFormula.getConnector());
- for (IntegrityFormula formula : compoundFormula.getFormulas()) {
- serializeFormula(formula, bitOutputStream);
- }
- bitOutputStream.setNext(SEPARATOR_BITS, COMPOUND_FORMULA_END);
- }
-
- private void serializeAtomicFormula(
- AtomicFormula atomicFormula, BitOutputStream bitOutputStream) throws IOException {
- if (atomicFormula == null) {
- throw new IllegalArgumentException("Null atomic formula can not be serialized");
- }
-
- bitOutputStream.setNext(SEPARATOR_BITS, ATOMIC_FORMULA_START);
- bitOutputStream.setNext(KEY_BITS, atomicFormula.getKey());
- if (atomicFormula.getTag() == AtomicFormula.STRING_ATOMIC_FORMULA_TAG) {
- AtomicFormula.StringAtomicFormula stringAtomicFormula =
- (AtomicFormula.StringAtomicFormula) atomicFormula;
- bitOutputStream.setNext(OPERATOR_BITS, AtomicFormula.EQ);
- serializeStringValue(
- stringAtomicFormula.getValue(),
- stringAtomicFormula.getIsHashedValue(),
- bitOutputStream);
- } else if (atomicFormula.getTag() == AtomicFormula.LONG_ATOMIC_FORMULA_TAG) {
- AtomicFormula.LongAtomicFormula longAtomicFormula =
- (AtomicFormula.LongAtomicFormula) atomicFormula;
- bitOutputStream.setNext(OPERATOR_BITS, longAtomicFormula.getOperator());
- // TODO(b/147880712): Temporary hack until we support long values in bitOutputStream
- long value = longAtomicFormula.getValue();
- serializeIntValue((int) (value >>> 32), bitOutputStream);
- serializeIntValue((int) value, bitOutputStream);
- } else if (atomicFormula.getTag() == AtomicFormula.BOOLEAN_ATOMIC_FORMULA_TAG) {
- AtomicFormula.BooleanAtomicFormula booleanAtomicFormula =
- (AtomicFormula.BooleanAtomicFormula) atomicFormula;
- bitOutputStream.setNext(OPERATOR_BITS, AtomicFormula.EQ);
- serializeBooleanValue(booleanAtomicFormula.getValue(), bitOutputStream);
- } else {
- throw new IllegalArgumentException(
- String.format("Invalid atomic formula type: %s", atomicFormula.getClass()));
- }
- }
-
- private void serializeIndexGroup(
- LinkedHashMap<String, Integer> indexes,
- BitOutputStream bitOutputStream,
- boolean isIndexed)
- throws IOException {
- // Output the starting location of this indexing group.
- serializeStringValue(START_INDEXING_KEY, /* isHashedValue= */ false, bitOutputStream);
- serializeIntValue(indexes.get(START_INDEXING_KEY), bitOutputStream);
-
- // If the group is indexed, output the locations of the indexes.
- if (isIndexed) {
- for (Map.Entry<String, Integer> entry : indexes.entrySet()) {
- if (!entry.getKey().equals(START_INDEXING_KEY)
- && !entry.getKey().equals(END_INDEXING_KEY)) {
- serializeStringValue(
- entry.getKey(), /* isHashedValue= */ false, bitOutputStream);
- serializeIntValue(entry.getValue(), bitOutputStream);
- }
- }
- }
-
- // Output the end location of this indexing group.
- serializeStringValue(END_INDEXING_KEY, /*isHashedValue= */ false, bitOutputStream);
- serializeIntValue(indexes.get(END_INDEXING_KEY), bitOutputStream);
- }
-
- private void serializeStringValue(
- String value, boolean isHashedValue, BitOutputStream bitOutputStream)
- throws IOException {
- if (value == null) {
- throw new IllegalArgumentException("String value can not be null.");
- }
- byte[] valueBytes = getBytesForString(value, isHashedValue);
-
- bitOutputStream.setNext(isHashedValue);
- bitOutputStream.setNext(VALUE_SIZE_BITS, valueBytes.length);
- for (byte valueByte : valueBytes) {
- bitOutputStream.setNext(/* numOfBits= */ 8, valueByte);
- }
- }
-
- private void serializeIntValue(int value, BitOutputStream bitOutputStream) throws IOException {
- bitOutputStream.setNext(/* numOfBits= */ 32, value);
- }
-
- private void serializeBooleanValue(boolean value, BitOutputStream bitOutputStream)
- throws IOException {
- bitOutputStream.setNext(value);
- }
-
- // Get the byte array for a value.
- // If the value is not hashed, use its byte array form directly.
- // If the value is hashed, get the raw form decoding of the value. All hashed values are
- // hex-encoded. Serialized values are in raw form.
- private static byte[] getBytesForString(String value, boolean isHashedValue) {
- if (!isHashedValue) {
- return value.getBytes(StandardCharsets.UTF_8);
- }
- return IntegrityUtils.getBytesFromHexDigest(value);
- }
-}
diff --git a/services/core/java/com/android/server/integrity/serializer/RuleIndexingDetails.java b/services/core/java/com/android/server/integrity/serializer/RuleIndexingDetails.java
deleted file mode 100644
index 2cbd4ede..0000000
--- a/services/core/java/com/android/server/integrity/serializer/RuleIndexingDetails.java
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.integrity.serializer;
-
-import android.annotation.IntDef;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
-/** Holds the indexing type and indexing key of a given formula. */
-class RuleIndexingDetails {
-
- static final int NOT_INDEXED = 0;
- static final int PACKAGE_NAME_INDEXED = 1;
- static final int APP_CERTIFICATE_INDEXED = 2;
-
- static final String DEFAULT_RULE_KEY = "N/A";
-
- /** Represents which indexed file the rule should be located. */
- @IntDef(
- value = {
- NOT_INDEXED,
- PACKAGE_NAME_INDEXED,
- APP_CERTIFICATE_INDEXED
- })
- @Retention(RetentionPolicy.SOURCE)
- public @interface IndexType {
- }
-
- private @IndexType int mIndexType;
- private String mRuleKey;
-
- /** Constructor without a ruleKey for {@code NOT_INDEXED}. */
- RuleIndexingDetails(@IndexType int indexType) {
- this.mIndexType = indexType;
- this.mRuleKey = DEFAULT_RULE_KEY;
- }
-
- /** Constructor with a ruleKey for indexed rules. */
- RuleIndexingDetails(@IndexType int indexType, String ruleKey) {
- this.mIndexType = indexType;
- this.mRuleKey = ruleKey;
- }
-
- /** Returns the indexing type for the rule. */
- @IndexType
- public int getIndexType() {
- return mIndexType;
- }
-
- /** Returns the identified rule key. */
- public String getRuleKey() {
- return mRuleKey;
- }
-}
diff --git a/services/core/java/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifier.java b/services/core/java/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifier.java
deleted file mode 100644
index e723559..0000000
--- a/services/core/java/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifier.java
+++ /dev/null
@@ -1,151 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.integrity.serializer;
-
-import static com.android.server.integrity.serializer.RuleIndexingDetails.APP_CERTIFICATE_INDEXED;
-import static com.android.server.integrity.serializer.RuleIndexingDetails.NOT_INDEXED;
-import static com.android.server.integrity.serializer.RuleIndexingDetails.PACKAGE_NAME_INDEXED;
-
-import android.content.integrity.AtomicFormula;
-import android.content.integrity.CompoundFormula;
-import android.content.integrity.IntegrityFormula;
-import android.content.integrity.Rule;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
-
-/** A helper class for identifying the indexing type and key of a given rule. */
-class RuleIndexingDetailsIdentifier {
-
- /**
- * Splits a given rule list into three indexing categories. Each rule category is returned as a
- * TreeMap that is sorted by their indexing keys -- where keys correspond to package name for
- * PACKAGE_NAME_INDEXED rules, app certificate for APP_CERTIFICATE_INDEXED rules and N/A for
- * NOT_INDEXED rules.
- */
- public static Map<Integer, Map<String, List<Rule>>> splitRulesIntoIndexBuckets(
- List<Rule> rules) {
- if (rules == null) {
- throw new IllegalArgumentException(
- "Index buckets cannot be created for null rule list.");
- }
-
- Map<Integer, Map<String, List<Rule>>> typeOrganizedRuleMap = new HashMap();
- typeOrganizedRuleMap.put(NOT_INDEXED, new HashMap());
- typeOrganizedRuleMap.put(PACKAGE_NAME_INDEXED, new HashMap<>());
- typeOrganizedRuleMap.put(APP_CERTIFICATE_INDEXED, new HashMap<>());
-
- // Split the rules into the appropriate indexed pattern. The Tree Maps help us to keep the
- // entries sorted by their index key.
- for (Rule rule : rules) {
- RuleIndexingDetails indexingDetails;
- try {
- indexingDetails = getIndexingDetails(rule.getFormula());
- } catch (Exception e) {
- throw new IllegalArgumentException(
- String.format("Malformed rule identified. [%s]", rule.toString()));
- }
-
- int ruleIndexType = indexingDetails.getIndexType();
- String ruleKey = indexingDetails.getRuleKey();
-
- if (!typeOrganizedRuleMap.get(ruleIndexType).containsKey(ruleKey)) {
- typeOrganizedRuleMap.get(ruleIndexType).put(ruleKey, new ArrayList());
- }
-
- typeOrganizedRuleMap.get(ruleIndexType).get(ruleKey).add(rule);
- }
-
- return typeOrganizedRuleMap;
- }
-
- private static RuleIndexingDetails getIndexingDetails(IntegrityFormula formula) {
- switch (formula.getTag()) {
- case IntegrityFormula.COMPOUND_FORMULA_TAG:
- return getIndexingDetailsForCompoundFormula((CompoundFormula) formula);
- case IntegrityFormula.STRING_ATOMIC_FORMULA_TAG:
- return getIndexingDetailsForStringAtomicFormula(
- (AtomicFormula.StringAtomicFormula) formula);
- case IntegrityFormula.LONG_ATOMIC_FORMULA_TAG:
- case IntegrityFormula.INSTALLER_ALLOWED_BY_MANIFEST_FORMULA_TAG:
- case IntegrityFormula.BOOLEAN_ATOMIC_FORMULA_TAG:
- // Package name and app certificate related formulas are string atomic formulas.
- return new RuleIndexingDetails(NOT_INDEXED);
- default:
- throw new IllegalArgumentException(
- String.format("Invalid formula tag type: %s", formula.getTag()));
- }
- }
-
- private static RuleIndexingDetails getIndexingDetailsForCompoundFormula(
- CompoundFormula compoundFormula) {
- int connector = compoundFormula.getConnector();
- List<IntegrityFormula> formulas = compoundFormula.getFormulas();
-
- switch (connector) {
- case CompoundFormula.AND:
- case CompoundFormula.OR:
- // If there is a package name related atomic rule, return package name indexed.
- Optional<RuleIndexingDetails> packageNameRule =
- formulas.stream()
- .map(formula -> getIndexingDetails(formula))
- .filter(ruleIndexingDetails -> ruleIndexingDetails.getIndexType()
- == PACKAGE_NAME_INDEXED)
- .findAny();
- if (packageNameRule.isPresent()) {
- return packageNameRule.get();
- }
-
- // If there is an app certificate related atomic rule but no package name related
- // atomic rule, return app certificate indexed.
- Optional<RuleIndexingDetails> appCertificateRule =
- formulas.stream()
- .map(formula -> getIndexingDetails(formula))
- .filter(ruleIndexingDetails -> ruleIndexingDetails.getIndexType()
- == APP_CERTIFICATE_INDEXED)
- .findAny();
- if (appCertificateRule.isPresent()) {
- return appCertificateRule.get();
- }
-
- // Do not index when there is not package name or app certificate indexing.
- return new RuleIndexingDetails(NOT_INDEXED);
- default:
- // Having a NOT operator in the indexing messes up the indexing; e.g., deny
- // installation if app certificate is NOT X (should not be indexed with app cert
- // X). We will not keep these rules indexed.
- // Also any other type of unknown operators will not be indexed.
- return new RuleIndexingDetails(NOT_INDEXED);
- }
- }
-
- private static RuleIndexingDetails getIndexingDetailsForStringAtomicFormula(
- AtomicFormula.StringAtomicFormula atomicFormula) {
- switch (atomicFormula.getKey()) {
- case AtomicFormula.PACKAGE_NAME:
- return new RuleIndexingDetails(PACKAGE_NAME_INDEXED, atomicFormula.getValue());
- case AtomicFormula.APP_CERTIFICATE:
- return new RuleIndexingDetails(APP_CERTIFICATE_INDEXED, atomicFormula.getValue());
- default:
- return new RuleIndexingDetails(NOT_INDEXED);
- }
- }
-}
-
diff --git a/services/core/java/com/android/server/integrity/serializer/RuleMetadataSerializer.java b/services/core/java/com/android/server/integrity/serializer/RuleMetadataSerializer.java
deleted file mode 100644
index 022b4b8..0000000
--- a/services/core/java/com/android/server/integrity/serializer/RuleMetadataSerializer.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.integrity.serializer;
-
-import static com.android.server.integrity.parser.RuleMetadataParser.RULE_PROVIDER_TAG;
-import static com.android.server.integrity.parser.RuleMetadataParser.VERSION_TAG;
-
-import android.util.Xml;
-
-import com.android.modules.utils.TypedXmlSerializer;
-import com.android.server.integrity.model.RuleMetadata;
-
-import org.xmlpull.v1.XmlSerializer;
-
-import java.io.IOException;
-import java.io.OutputStream;
-import java.nio.charset.StandardCharsets;
-
-/** Helper class for writing rule metadata. */
-public class RuleMetadataSerializer {
- /** Serialize the rule metadata to an output stream. */
- public static void serialize(RuleMetadata ruleMetadata, OutputStream outputStream)
- throws IOException {
- TypedXmlSerializer xmlSerializer = Xml.resolveSerializer(outputStream);
-
- serializeTaggedValue(xmlSerializer, RULE_PROVIDER_TAG, ruleMetadata.getRuleProvider());
- serializeTaggedValue(xmlSerializer, VERSION_TAG, ruleMetadata.getVersion());
-
- xmlSerializer.endDocument();
- }
-
- private static void serializeTaggedValue(TypedXmlSerializer xmlSerializer, String tag,
- String value) throws IOException {
- xmlSerializer.startTag(/* namespace= */ null, tag);
- xmlSerializer.text(value);
- xmlSerializer.endTag(/* namespace= */ null, tag);
- }
-}
diff --git a/services/core/java/com/android/server/integrity/serializer/RuleSerializeException.java b/services/core/java/com/android/server/integrity/serializer/RuleSerializeException.java
deleted file mode 100644
index 60cfc48..0000000
--- a/services/core/java/com/android/server/integrity/serializer/RuleSerializeException.java
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.integrity.serializer;
-
-import android.annotation.NonNull;
-
-/**
- * Thrown when rule serialization fails.
- */
-public class RuleSerializeException extends Exception {
- public RuleSerializeException(@NonNull String message) {
- super(message);
- }
-
- public RuleSerializeException(@NonNull String message, @NonNull Throwable cause) {
- super(message, cause);
- }
-}
diff --git a/services/core/java/com/android/server/integrity/serializer/RuleSerializer.java b/services/core/java/com/android/server/integrity/serializer/RuleSerializer.java
deleted file mode 100644
index 2941856..0000000
--- a/services/core/java/com/android/server/integrity/serializer/RuleSerializer.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.integrity.serializer;
-
-import android.content.integrity.Rule;
-
-import java.io.OutputStream;
-import java.util.List;
-import java.util.Optional;
-
-/** A helper class to serialize rules from the {@link Rule} model. */
-public interface RuleSerializer {
-
- /** Serialize rules to an output stream */
- void serialize(
- List<Rule> rules,
- Optional<Integer> formatVersion,
- OutputStream ruleFileOutputStream,
- OutputStream indexingFileOutputStream)
- throws RuleSerializeException;
-
- /** Serialize rules to a ByteArray. */
- byte[] serialize(List<Rule> rules, Optional<Integer> formatVersion)
- throws RuleSerializeException;
-}
diff --git a/services/core/java/com/android/server/pm/BackgroundInstallControlService.java b/services/core/java/com/android/server/pm/BackgroundInstallControlService.java
index 20cca969..af2bb17 100644
--- a/services/core/java/com/android/server/pm/BackgroundInstallControlService.java
+++ b/services/core/java/com/android/server/pm/BackgroundInstallControlService.java
@@ -404,9 +404,11 @@
void handlePackageRemove(String packageName, int userId) {
initBackgroundInstalledPackages();
+ if (mBackgroundInstalledPackages.contains(userId, packageName)) {
+ mCallbackHelper.notifyAllCallbacks(userId, packageName, INSTALL_EVENT_TYPE_UNINSTALL);
+ }
mBackgroundInstalledPackages.remove(userId, packageName);
writeBackgroundInstalledPackagesToDisk();
- mCallbackHelper.notifyAllCallbacks(userId, packageName, INSTALL_EVENT_TYPE_UNINSTALL);
}
void handleUsageEvent(UsageEvents.Event event, int userId) {
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 05a96d9..f4b18ce 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -1037,7 +1037,6 @@
mLastStartReason = request.reason;
mLastStartActivityTimeMs = System.currentTimeMillis();
- final ActivityRecord previousStart = mLastStartActivityRecord;
final IApplicationThread caller = request.caller;
Intent intent = request.intent;
NeededUriGrants intentGrants = request.intentGrants;
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index e827f44..07675b9 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -7072,7 +7072,7 @@
@Override
public void setImeInputTargetRequestedVisibility(boolean visible) {
if (android.view.inputmethod.Flags.refactorInsetsController()) {
- // TODO(b/329229469) we won't have the statsToken in all cases, but should still log
+ // TODO(b/353463205) we won't have the statsToken in all cases, but should still log
try {
mRemoteInsetsController.setImeInputTargetRequestedVisibility(visible);
} catch (RemoteException e) {
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 8627a47..ee07d2e 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -1735,9 +1735,9 @@
}
// Show IME over the keyguard if the target allows it.
- final boolean showImeOverKeyguard = imeTarget != null && imeTarget.isVisible()
- && win.mIsImWindow && (imeTarget.canShowWhenLocked()
- || !imeTarget.canBeHiddenByKeyguard());
+ final boolean showImeOverKeyguard =
+ imeTarget != null && imeTarget.isOnScreen() && win.mIsImWindow && (
+ imeTarget.canShowWhenLocked() || !imeTarget.canBeHiddenByKeyguard());
if (showImeOverKeyguard) {
return false;
}
diff --git a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
index e9c6e93..5ed9612 100644
--- a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
@@ -100,13 +100,13 @@
// isLeashReadyForDispatching (used to dispatch the leash of the control) is
// depending on mGivenInsetsReady. Therefore, triggering notifyControlChanged here
// again, so that the control with leash can be eventually dispatched
- if (!mGivenInsetsReady && mServerVisible && !givenInsetsPending) {
+ if (!mGivenInsetsReady && isServerVisible() && !givenInsetsPending) {
mGivenInsetsReady = true;
ImeTracker.forLogging().onProgress(mStatsToken,
ImeTracker.PHASE_WM_POST_LAYOUT_NOTIFY_CONTROLS_CHANGED);
mStateController.notifyControlChanged(mControlTarget, this);
setImeShowing(true);
- } else if (wasServerVisible && mServerVisible && mGivenInsetsReady
+ } else if (wasServerVisible && isServerVisible() && mGivenInsetsReady
&& givenInsetsPending) {
// If the server visibility didn't change (still visible), and mGivenInsetsReady
// is set, we won't call into notifyControlChanged. Therefore, we can reset the
@@ -114,7 +114,7 @@
ImeTracker.forLogging().onCancelled(mStatsToken,
ImeTracker.PHASE_WM_POST_LAYOUT_NOTIFY_CONTROLS_CHANGED);
mStatsToken = null;
- } else if (wasServerVisible && !mServerVisible) {
+ } else if (wasServerVisible && !isServerVisible()) {
setImeShowing(false);
}
}
@@ -134,11 +134,15 @@
@Override
protected boolean isLeashReadyForDispatching() {
if (android.view.inputmethod.Flags.refactorInsetsController()) {
+ // We should only dispatch the leash, if the following conditions are fulfilled:
+ // 1. parent isLeashReadyForDispatching, 2. mGivenInsetsReady (means there are no
+ // givenInsetsPending), 3. the IME surface is drawn, 4. either the IME is
+ // serverVisible (the unfrozen state)
final WindowState ws =
mWindowContainer != null ? mWindowContainer.asWindowState() : null;
final boolean isDrawn = ws != null && ws.isDrawn();
return super.isLeashReadyForDispatching()
- && mServerVisible && isDrawn && mGivenInsetsReady;
+ && isServerVisible() && isDrawn && mGivenInsetsReady;
} else {
return super.isLeashReadyForDispatching();
}
@@ -254,7 +258,7 @@
// Refer WindowState#getImeControlTarget().
target = target.getWindow().getImeControlTarget();
}
- // TODO(b/329229469) make sure that the statsToken of all callers is non-null (currently
+ // TODO(b/353463205) make sure that the statsToken of all callers is non-null (currently
// not the case)
super.updateControlForTarget(target, force, statsToken);
if (Flags.refactorInsetsController()) {
@@ -290,12 +294,14 @@
changed |= mDisplayContent.onImeInsetsClientVisibilityUpdate();
if (Flags.refactorInsetsController()) {
if (changed) {
+ ImeTracker.forLogging().onProgress(statsToken,
+ ImeTracker.PHASE_SERVER_UPDATE_CLIENT_VISIBILITY);
invokeOnImeRequestedChangedListener(mDisplayContent.getImeInputTarget(),
statsToken);
} else {
- // TODO(b/329229469) change phase and check cancelled / failed
+ // TODO(b/353463205) check cancelled / failed
ImeTracker.forLogging().onCancelled(statsToken,
- ImeTracker.PHASE_CLIENT_REPORT_REQUESTED_VISIBLE_TYPES);
+ ImeTracker.PHASE_SERVER_UPDATE_CLIENT_VISIBILITY);
}
}
return changed;
@@ -460,7 +466,7 @@
// This can later become ready, so we don't want to cancel the pending request here.
return;
}
- // TODO(b/329229469) check if this is still triggered, as we don't go into STATE_SHOW_IME
+ // TODO(b/353463205) check if this is still triggered, as we don't go into STATE_SHOW_IME
// (DefaultImeVisibilityApplier)
if (android.view.inputmethod.Flags.refactorInsetsController()) {
// The IME is drawn, so call into {@link WindowState#notifyInsetsControlChanged}
diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
index 1d4d6eb..7276007 100644
--- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
@@ -730,6 +730,10 @@
return mFakeControlTarget;
}
+ boolean isServerVisible() {
+ return mServerVisible;
+ }
+
boolean isClientVisible() {
return mClientVisible;
}
diff --git a/services/core/java/com/android/server/wm/InsetsStateController.java b/services/core/java/com/android/server/wm/InsetsStateController.java
index 5dddf36..4b2d454 100644
--- a/services/core/java/com/android/server/wm/InsetsStateController.java
+++ b/services/core/java/com/android/server/wm/InsetsStateController.java
@@ -317,9 +317,9 @@
// aborted.
provider.updateFakeControlTarget(target);
} else {
- // TODO(b/329229469) if the IME controlTarget changes, any pending requests should fail
+ // TODO(b/353463205) if the IME controlTarget changes, any pending requests should fail
provider.updateControlForTarget(target, false /* force */,
- null /* TODO(b/329229469) check if needed here */);
+ null /* TODO(b/353463205) check if needed here */);
// Get control target again in case the provider didn't accept the one we passed to it.
target = provider.getControlTarget();
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index 077127c..1bb4c41 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -715,7 +715,7 @@
if (embeddedWindow != null) {
// If there is no WindowState for the IWindow, it could be still an
// EmbeddedWindow. Therefore, check the EmbeddedWindowController as well
- // TODO(b/329229469) Use different phase here
+ // TODO(b/353463205) Use different phase here
ImeTracker.forLogging().onProgress(imeStatsToken,
ImeTracker.PHASE_WM_UPDATE_REQUESTED_VISIBLE_TYPES);
embeddedWindow.setRequestedVisibleTypes(
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
index c741c6c..077bb03 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
@@ -2562,7 +2562,13 @@
mTestLooper.dispatchAll();
// User interacted with the DUT, so the device will not go to standby.
- skipActiveSourceLostUi(0, true, true);
+ mHdmiControlService.oneTouchPlay(new IHdmiControlCallback.Stub() {
+ @Override
+ public void onComplete(int result) {
+ }
+ });
+ mTestLooper.dispatchAll();
+
assertThat(mIsOnActiveSourceLostPopupActive).isFalse();
assertThat(mPowerManager.isInteractive()).isTrue();
assertThat(mNativeWrapper.getResultMessages().contains(activeSourceFromPlayback)).isTrue();
diff --git a/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleBinarySerializerTest.java b/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleBinarySerializerTest.java
deleted file mode 100644
index 9ed2e88..0000000
--- a/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleBinarySerializerTest.java
+++ /dev/null
@@ -1,914 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.integrity.serializer;
-
-import static com.android.server.integrity.model.ComponentBitSize.ATOMIC_FORMULA_START;
-import static com.android.server.integrity.model.ComponentBitSize.COMPOUND_FORMULA_END;
-import static com.android.server.integrity.model.ComponentBitSize.COMPOUND_FORMULA_START;
-import static com.android.server.integrity.model.ComponentBitSize.CONNECTOR_BITS;
-import static com.android.server.integrity.model.ComponentBitSize.DEFAULT_FORMAT_VERSION;
-import static com.android.server.integrity.model.ComponentBitSize.EFFECT_BITS;
-import static com.android.server.integrity.model.ComponentBitSize.FORMAT_VERSION_BITS;
-import static com.android.server.integrity.model.ComponentBitSize.KEY_BITS;
-import static com.android.server.integrity.model.ComponentBitSize.OPERATOR_BITS;
-import static com.android.server.integrity.model.ComponentBitSize.SEPARATOR_BITS;
-import static com.android.server.integrity.model.ComponentBitSize.VALUE_SIZE_BITS;
-import static com.android.server.integrity.model.IndexingFileConstants.END_INDEXING_KEY;
-import static com.android.server.integrity.model.IndexingFileConstants.INDEXING_BLOCK_SIZE;
-import static com.android.server.integrity.model.IndexingFileConstants.START_INDEXING_KEY;
-import static com.android.server.integrity.serializer.RuleBinarySerializer.INDEXED_RULE_SIZE_LIMIT;
-import static com.android.server.integrity.serializer.RuleBinarySerializer.NONINDEXED_RULE_SIZE_LIMIT;
-import static com.android.server.integrity.utils.TestUtils.getBits;
-import static com.android.server.integrity.utils.TestUtils.getBytes;
-import static com.android.server.integrity.utils.TestUtils.getValueBits;
-import static com.android.server.testutils.TestUtils.assertExpectException;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.content.integrity.AppInstallMetadata;
-import android.content.integrity.AtomicFormula;
-import android.content.integrity.CompoundFormula;
-import android.content.integrity.IntegrityFormula;
-import android.content.integrity.IntegrityUtils;
-import android.content.integrity.Rule;
-
-import androidx.annotation.NonNull;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-import java.io.ByteArrayOutputStream;
-import java.nio.charset.StandardCharsets;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-import java.util.Optional;
-
-@RunWith(JUnit4.class)
-public class RuleBinarySerializerTest {
-
- private static final String SAMPLE_INSTALLER_NAME = "com.test.installer";
- private static final String SAMPLE_INSTALLER_CERT = "installer_cert";
-
- private static final String COMPOUND_FORMULA_START_BITS =
- getBits(COMPOUND_FORMULA_START, SEPARATOR_BITS);
- private static final String COMPOUND_FORMULA_END_BITS =
- getBits(COMPOUND_FORMULA_END, SEPARATOR_BITS);
- private static final String ATOMIC_FORMULA_START_BITS =
- getBits(ATOMIC_FORMULA_START, SEPARATOR_BITS);
-
- private static final String NOT = getBits(CompoundFormula.NOT, CONNECTOR_BITS);
- private static final String AND = getBits(CompoundFormula.AND, CONNECTOR_BITS);
- private static final String OR = getBits(CompoundFormula.OR, CONNECTOR_BITS);
-
- private static final String PACKAGE_NAME = getBits(AtomicFormula.PACKAGE_NAME, KEY_BITS);
- private static final String APP_CERTIFICATE = getBits(AtomicFormula.APP_CERTIFICATE, KEY_BITS);
- private static final String INSTALLER_NAME = getBits(AtomicFormula.INSTALLER_NAME, KEY_BITS);
- private static final String INSTALLER_CERTIFICATE =
- getBits(AtomicFormula.INSTALLER_CERTIFICATE, KEY_BITS);
- private static final String VERSION_CODE = getBits(AtomicFormula.VERSION_CODE, KEY_BITS);
- private static final String PRE_INSTALLED = getBits(AtomicFormula.PRE_INSTALLED, KEY_BITS);
-
- private static final String EQ = getBits(AtomicFormula.EQ, OPERATOR_BITS);
-
- private static final String IS_NOT_HASHED = "0";
- private static final String IS_HASHED = "1";
-
- private static final String DENY = getBits(Rule.DENY, EFFECT_BITS);
-
- private static final String START_BIT = "1";
- private static final String END_BIT = "1";
-
- private static final byte[] DEFAULT_FORMAT_VERSION_BYTES =
- getBytes(getBits(DEFAULT_FORMAT_VERSION, FORMAT_VERSION_BITS));
-
- private static final String SERIALIZED_START_INDEXING_KEY =
- IS_NOT_HASHED
- + getBits(START_INDEXING_KEY.length(), VALUE_SIZE_BITS)
- + getValueBits(START_INDEXING_KEY);
- private static final String SERIALIZED_END_INDEXING_KEY =
- IS_NOT_HASHED
- + getBits(END_INDEXING_KEY.length(), VALUE_SIZE_BITS)
- + getValueBits(END_INDEXING_KEY);
-
- @Test
- public void testBinaryString_serializeNullRules() {
- RuleSerializer binarySerializer = new RuleBinarySerializer();
-
- assertExpectException(
- RuleSerializeException.class,
- /* expectedExceptionMessageRegex= */ "Null rules cannot be serialized.",
- () -> binarySerializer.serialize(null, /* formatVersion= */ Optional.empty()));
- }
-
- @Test
- public void testBinaryString_emptyRules() throws Exception {
- ByteArrayOutputStream ruleOutputStream = new ByteArrayOutputStream();
- ByteArrayOutputStream indexingOutputStream = new ByteArrayOutputStream();
- RuleSerializer binarySerializer = new RuleBinarySerializer();
-
- binarySerializer.serialize(
- Collections.emptyList(),
- /* formatVersion= */ Optional.empty(),
- ruleOutputStream,
- indexingOutputStream);
-
- ByteArrayOutputStream expectedRuleOutputStream = new ByteArrayOutputStream();
- expectedRuleOutputStream.write(DEFAULT_FORMAT_VERSION_BYTES);
- assertThat(ruleOutputStream.toByteArray())
- .isEqualTo(expectedRuleOutputStream.toByteArray());
-
- ByteArrayOutputStream expectedIndexingOutputStream = new ByteArrayOutputStream();
- String serializedIndexingBytes =
- SERIALIZED_START_INDEXING_KEY
- + getBits(DEFAULT_FORMAT_VERSION_BYTES.length, /* numOfBits= */ 32)
- + SERIALIZED_END_INDEXING_KEY
- + getBits(DEFAULT_FORMAT_VERSION_BYTES.length, /* numOfBits= */ 32);
- byte[] expectedIndexingBytes =
- getBytes(
- serializedIndexingBytes
- + serializedIndexingBytes
- + serializedIndexingBytes);
- expectedIndexingOutputStream.write(expectedIndexingBytes);
- assertThat(indexingOutputStream.toByteArray())
- .isEqualTo(expectedIndexingOutputStream.toByteArray());
- }
-
- @Test
- public void testBinaryStream_serializeValidCompoundFormula() throws Exception {
- String packageName = "com.test.app";
- Rule rule =
- new Rule(
- new CompoundFormula(
- CompoundFormula.NOT,
- Collections.singletonList(
- new AtomicFormula.StringAtomicFormula(
- AtomicFormula.PACKAGE_NAME,
- packageName,
- /* isHashedValue= */ false))),
- Rule.DENY);
-
- ByteArrayOutputStream ruleOutputStream = new ByteArrayOutputStream();
- ByteArrayOutputStream indexingOutputStream = new ByteArrayOutputStream();
- RuleSerializer binarySerializer = new RuleBinarySerializer();
- binarySerializer.serialize(
- Collections.singletonList(rule),
- /* formatVersion= */ Optional.empty(),
- ruleOutputStream,
- indexingOutputStream);
-
- String expectedBits =
- START_BIT
- + COMPOUND_FORMULA_START_BITS
- + NOT
- + ATOMIC_FORMULA_START_BITS
- + PACKAGE_NAME
- + EQ
- + IS_NOT_HASHED
- + getBits(packageName.length(), VALUE_SIZE_BITS)
- + getValueBits(packageName)
- + COMPOUND_FORMULA_END_BITS
- + DENY
- + END_BIT;
- ByteArrayOutputStream expectedRuleOutputStream = new ByteArrayOutputStream();
- expectedRuleOutputStream.write(DEFAULT_FORMAT_VERSION_BYTES);
- expectedRuleOutputStream.write(getBytes(expectedBits));
- assertThat(ruleOutputStream.toByteArray())
- .isEqualTo(expectedRuleOutputStream.toByteArray());
-
- ByteArrayOutputStream expectedIndexingOutputStream = new ByteArrayOutputStream();
- String expectedIndexingBitsForIndexed =
- SERIALIZED_START_INDEXING_KEY
- + getBits(DEFAULT_FORMAT_VERSION_BYTES.length, /* numOfBits= */ 32)
- + SERIALIZED_END_INDEXING_KEY
- + getBits(DEFAULT_FORMAT_VERSION_BYTES.length, /* numOfBits= */ 32);
- String expectedIndexingBitsForUnindexed =
- SERIALIZED_START_INDEXING_KEY
- + getBits(DEFAULT_FORMAT_VERSION_BYTES.length, /* numOfBits= */ 32)
- + SERIALIZED_END_INDEXING_KEY
- + getBits(
- DEFAULT_FORMAT_VERSION_BYTES.length + getBytes(expectedBits).length,
- /* numOfBits= */ 32);
- expectedIndexingOutputStream.write(
- getBytes(
- expectedIndexingBitsForIndexed
- + expectedIndexingBitsForIndexed
- + expectedIndexingBitsForUnindexed));
-
- assertThat(indexingOutputStream.toByteArray())
- .isEqualTo(expectedIndexingOutputStream.toByteArray());
- }
-
- @Test
- public void testBinaryString_serializeValidCompoundFormula_notConnector() throws Exception {
- String packageName = "com.test.app";
- Rule rule =
- new Rule(
- new CompoundFormula(
- CompoundFormula.NOT,
- Collections.singletonList(
- new AtomicFormula.StringAtomicFormula(
- AtomicFormula.PACKAGE_NAME,
- packageName,
- /* isHashedValue= */ false))),
- Rule.DENY);
- RuleSerializer binarySerializer = new RuleBinarySerializer();
- String expectedBits =
- START_BIT
- + COMPOUND_FORMULA_START_BITS
- + NOT
- + ATOMIC_FORMULA_START_BITS
- + PACKAGE_NAME
- + EQ
- + IS_NOT_HASHED
- + getBits(packageName.length(), VALUE_SIZE_BITS)
- + getValueBits(packageName)
- + COMPOUND_FORMULA_END_BITS
- + DENY
- + END_BIT;
- ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
- byteArrayOutputStream.write(DEFAULT_FORMAT_VERSION_BYTES);
- byteArrayOutputStream.write(getBytes(expectedBits));
- byte[] expectedRules = byteArrayOutputStream.toByteArray();
-
- byte[] actualRules =
- binarySerializer.serialize(
- Collections.singletonList(rule), /* formatVersion= */ Optional.empty());
-
- assertThat(actualRules).isEqualTo(expectedRules);
- }
-
- @Test
- public void testBinaryString_serializeValidCompoundFormula_andConnector() throws Exception {
- String packageName = "com.test.app";
- String appCertificate = "test_cert";
- Rule rule =
- new Rule(
- new CompoundFormula(
- CompoundFormula.AND,
- Arrays.asList(
- new AtomicFormula.StringAtomicFormula(
- AtomicFormula.PACKAGE_NAME,
- packageName,
- /* isHashedValue= */ false),
- new AtomicFormula.StringAtomicFormula(
- AtomicFormula.APP_CERTIFICATE,
- appCertificate,
- /* isHashedValue= */ false))),
- Rule.DENY);
- RuleSerializer binarySerializer = new RuleBinarySerializer();
- String expectedBits =
- START_BIT
- + COMPOUND_FORMULA_START_BITS
- + AND
- + ATOMIC_FORMULA_START_BITS
- + PACKAGE_NAME
- + EQ
- + IS_NOT_HASHED
- + getBits(packageName.length(), VALUE_SIZE_BITS)
- + getValueBits(packageName)
- + ATOMIC_FORMULA_START_BITS
- + APP_CERTIFICATE
- + EQ
- + IS_NOT_HASHED
- + getBits(appCertificate.length(), VALUE_SIZE_BITS)
- + getValueBits(appCertificate)
- + COMPOUND_FORMULA_END_BITS
- + DENY
- + END_BIT;
- ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
- byteArrayOutputStream.write(DEFAULT_FORMAT_VERSION_BYTES);
- byteArrayOutputStream.write(getBytes(expectedBits));
- byte[] expectedRules = byteArrayOutputStream.toByteArray();
-
- byte[] actualRules =
- binarySerializer.serialize(
- Collections.singletonList(rule), /* formatVersion= */ Optional.empty());
-
- assertThat(actualRules).isEqualTo(expectedRules);
- }
-
- @Test
- public void testBinaryString_serializeValidCompoundFormula_orConnector() throws Exception {
- String packageName = "com.test.app";
- String appCertificate = "test_cert";
- Rule rule =
- new Rule(
- new CompoundFormula(
- CompoundFormula.OR,
- Arrays.asList(
- new AtomicFormula.StringAtomicFormula(
- AtomicFormula.PACKAGE_NAME,
- packageName,
- /* isHashedValue= */ false),
- new AtomicFormula.StringAtomicFormula(
- AtomicFormula.APP_CERTIFICATE,
- appCertificate,
- /* isHashedValue= */ false))),
- Rule.DENY);
- RuleSerializer binarySerializer = new RuleBinarySerializer();
- String expectedBits =
- START_BIT
- + COMPOUND_FORMULA_START_BITS
- + OR
- + ATOMIC_FORMULA_START_BITS
- + PACKAGE_NAME
- + EQ
- + IS_NOT_HASHED
- + getBits(packageName.length(), VALUE_SIZE_BITS)
- + getValueBits(packageName)
- + ATOMIC_FORMULA_START_BITS
- + APP_CERTIFICATE
- + EQ
- + IS_NOT_HASHED
- + getBits(appCertificate.length(), VALUE_SIZE_BITS)
- + getValueBits(appCertificate)
- + COMPOUND_FORMULA_END_BITS
- + DENY
- + END_BIT;
- ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
- byteArrayOutputStream.write(DEFAULT_FORMAT_VERSION_BYTES);
- byteArrayOutputStream.write(getBytes(expectedBits));
- byte[] expectedRules = byteArrayOutputStream.toByteArray();
-
- byte[] actualRules =
- binarySerializer.serialize(
- Collections.singletonList(rule), /* formatVersion= */ Optional.empty());
-
- assertThat(actualRules).isEqualTo(expectedRules);
- }
-
- @Test
- public void testBinaryString_serializeValidAtomicFormula_stringValue() throws Exception {
- String packageName = "com.test.app";
- Rule rule =
- new Rule(
- new AtomicFormula.StringAtomicFormula(
- AtomicFormula.PACKAGE_NAME,
- packageName,
- /* isHashedValue= */ false),
- Rule.DENY);
- RuleSerializer binarySerializer = new RuleBinarySerializer();
- String expectedBits =
- START_BIT
- + ATOMIC_FORMULA_START_BITS
- + PACKAGE_NAME
- + EQ
- + IS_NOT_HASHED
- + getBits(packageName.length(), VALUE_SIZE_BITS)
- + getValueBits(packageName)
- + DENY
- + END_BIT;
- ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
- byteArrayOutputStream.write(DEFAULT_FORMAT_VERSION_BYTES);
- byteArrayOutputStream.write(getBytes(expectedBits));
- byte[] expectedRules = byteArrayOutputStream.toByteArray();
-
- byte[] actualRules =
- binarySerializer.serialize(
- Collections.singletonList(rule), /* formatVersion= */ Optional.empty());
-
- assertThat(actualRules).isEqualTo(expectedRules);
- }
-
- @Test
- public void testBinaryString_serializeValidAtomicFormula_hashedValue() throws Exception {
- String appCertificate = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
- Rule rule =
- new Rule(
- new AtomicFormula.StringAtomicFormula(
- AtomicFormula.APP_CERTIFICATE,
- IntegrityUtils.getHexDigest(
- appCertificate.getBytes(StandardCharsets.UTF_8)),
- /* isHashedValue= */ true),
- Rule.DENY);
- RuleSerializer binarySerializer = new RuleBinarySerializer();
- String expectedBits =
- START_BIT
- + ATOMIC_FORMULA_START_BITS
- + APP_CERTIFICATE
- + EQ
- + IS_HASHED
- + getBits(appCertificate.length(), VALUE_SIZE_BITS)
- + getValueBits(appCertificate)
- + DENY
- + END_BIT;
- ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
- byteArrayOutputStream.write(DEFAULT_FORMAT_VERSION_BYTES);
- byteArrayOutputStream.write(getBytes(expectedBits));
- byte[] expectedRules = byteArrayOutputStream.toByteArray();
-
- byte[] actualRules =
- binarySerializer.serialize(
- Collections.singletonList(rule), /* formatVersion= */ Optional.empty());
-
- assertThat(actualRules).isEqualTo(expectedRules);
- }
-
- @Test
- public void testBinaryString_serializeValidAtomicFormula_integerValue() throws Exception {
- long versionCode = 1;
- Rule rule =
- new Rule(
- new AtomicFormula.LongAtomicFormula(
- AtomicFormula.VERSION_CODE, AtomicFormula.EQ, versionCode),
- Rule.DENY);
- RuleSerializer binarySerializer = new RuleBinarySerializer();
- String expectedBits =
- START_BIT
- + ATOMIC_FORMULA_START_BITS
- + VERSION_CODE
- + EQ
- + getBits(versionCode, /* numOfBits= */ 64)
- + DENY
- + END_BIT;
- ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
- byteArrayOutputStream.write(DEFAULT_FORMAT_VERSION_BYTES);
- byteArrayOutputStream.write(getBytes(expectedBits));
- byte[] expectedRules = byteArrayOutputStream.toByteArray();
-
- byte[] actualRules =
- binarySerializer.serialize(
- Collections.singletonList(rule), /* formatVersion= */ Optional.empty());
-
- assertThat(actualRules).isEqualTo(expectedRules);
- }
-
- @Test
- public void testBinaryString_serializeValidAtomicFormula_booleanValue() throws Exception {
- String preInstalled = "1";
- Rule rule =
- new Rule(
- new AtomicFormula.BooleanAtomicFormula(AtomicFormula.PRE_INSTALLED, true),
- Rule.DENY);
- RuleSerializer binarySerializer = new RuleBinarySerializer();
- String expectedBits =
- START_BIT
- + ATOMIC_FORMULA_START_BITS
- + PRE_INSTALLED
- + EQ
- + preInstalled
- + DENY
- + END_BIT;
- ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
- byteArrayOutputStream.write(DEFAULT_FORMAT_VERSION_BYTES);
- byteArrayOutputStream.write(getBytes(expectedBits));
- byte[] expectedRules = byteArrayOutputStream.toByteArray();
-
- byte[] actualRules =
- binarySerializer.serialize(
- Collections.singletonList(rule), /* formatVersion= */ Optional.empty());
-
- assertThat(actualRules).isEqualTo(expectedRules);
- }
-
- @Test
- public void testBinaryString_serializeInvalidFormulaType() throws Exception {
- IntegrityFormula invalidFormula = getInvalidFormula();
- Rule rule = new Rule(invalidFormula, Rule.DENY);
- RuleSerializer binarySerializer = new RuleBinarySerializer();
-
- assertExpectException(
- RuleSerializeException.class,
- /* expectedExceptionMessageRegex= */ "Malformed rule identified.",
- () ->
- binarySerializer.serialize(
- Collections.singletonList(rule),
- /* formatVersion= */ Optional.empty()));
- }
-
- @Test
- public void testBinaryString_serializeFormatVersion() throws Exception {
- int formatVersion = 1;
- RuleSerializer binarySerializer = new RuleBinarySerializer();
- String expectedBits = getBits(formatVersion, FORMAT_VERSION_BITS);
- byte[] expectedRules = getBytes(expectedBits);
-
- byte[] actualRules =
- binarySerializer.serialize(
- Collections.emptyList(), /* formatVersion= */ Optional.of(formatVersion));
-
- assertThat(actualRules).isEqualTo(expectedRules);
- }
-
- @Test
- public void testBinaryString_verifyManyRulesAreIndexedCorrectly() throws Exception {
- int ruleCount = 225;
- String packagePrefix = "package.name.";
- String appCertificatePrefix = "app.cert.";
- String installerNamePrefix = "installer.";
-
- // Create the rule set with 225 package name based rules, 225 app certificate indexed rules,
- // and 225 non-indexed rules..
- List<Rule> ruleList = new ArrayList();
- for (int count = 0; count < ruleCount; count++) {
- ruleList.add(
- getRuleWithPackageNameAndSampleInstallerName(
- String.format("%s%04d", packagePrefix, count)));
- }
- for (int count = 0; count < ruleCount; count++) {
- ruleList.add(
- getRuleWithAppCertificateAndSampleInstallerName(
- String.format("%s%04d", appCertificatePrefix, count)));
- }
- for (int count = 0; count < ruleCount; count++) {
- ruleList.add(
- getNonIndexedRuleWithInstallerName(
- String.format("%s%04d", installerNamePrefix, count)));
- }
-
- // Serialize the rules.
- ByteArrayOutputStream ruleOutputStream = new ByteArrayOutputStream();
- ByteArrayOutputStream indexingOutputStream = new ByteArrayOutputStream();
- RuleSerializer binarySerializer = new RuleBinarySerializer();
- binarySerializer.serialize(
- ruleList,
- /* formatVersion= */ Optional.empty(),
- ruleOutputStream,
- indexingOutputStream);
-
- // Verify the rules file and index files.
- ByteArrayOutputStream expectedOrderedRuleOutputStream = new ByteArrayOutputStream();
- ByteArrayOutputStream expectedIndexingOutputStream = new ByteArrayOutputStream();
-
- expectedOrderedRuleOutputStream.write(DEFAULT_FORMAT_VERSION_BYTES);
- int totalBytesWritten = DEFAULT_FORMAT_VERSION_BYTES.length;
-
- String expectedIndexingBytesForPackageNameIndexed =
- SERIALIZED_START_INDEXING_KEY + getBits(totalBytesWritten, /* numOfBits= */ 32);
- for (int count = 0; count < ruleCount; count++) {
- String packageName = String.format("%s%04d", packagePrefix, count);
- if (count > 0 && count % INDEXING_BLOCK_SIZE == 0) {
- expectedIndexingBytesForPackageNameIndexed +=
- IS_NOT_HASHED
- + getBits(packageName.length(), VALUE_SIZE_BITS)
- + getValueBits(packageName)
- + getBits(totalBytesWritten, /* numOfBits= */ 32);
- }
-
- byte[] bytesForPackage =
- getBytes(
- getSerializedCompoundRuleWithPackageNameAndSampleInstallerName(
- packageName));
- expectedOrderedRuleOutputStream.write(bytesForPackage);
- totalBytesWritten += bytesForPackage.length;
- }
- expectedIndexingBytesForPackageNameIndexed +=
- SERIALIZED_END_INDEXING_KEY + getBits(totalBytesWritten, /* numOfBits= */ 32);
-
- String expectedIndexingBytesForAppCertificateIndexed =
- SERIALIZED_START_INDEXING_KEY + getBits(totalBytesWritten, /* numOfBits= */ 32);
- for (int count = 0; count < ruleCount; count++) {
- String appCertificate = String.format("%s%04d", appCertificatePrefix, count);
- if (count > 0 && count % INDEXING_BLOCK_SIZE == 0) {
- expectedIndexingBytesForAppCertificateIndexed +=
- IS_NOT_HASHED
- + getBits(appCertificate.length(), VALUE_SIZE_BITS)
- + getValueBits(appCertificate)
- + getBits(totalBytesWritten, /* numOfBits= */ 32);
- }
-
- byte[] bytesForPackage =
- getBytes(
- getSerializedCompoundRuleWithCertificateNameAndSampleInstallerName(
- appCertificate));
- expectedOrderedRuleOutputStream.write(bytesForPackage);
- totalBytesWritten += bytesForPackage.length;
- }
- expectedIndexingBytesForAppCertificateIndexed +=
- SERIALIZED_END_INDEXING_KEY + getBits(totalBytesWritten, /* numOfBits= */ 32);
-
- String expectedIndexingBytesForUnindexed =
- SERIALIZED_START_INDEXING_KEY + getBits(totalBytesWritten, /* numOfBits= */ 32);
- for (int count = 0; count < ruleCount; count++) {
- byte[] bytesForPackage =
- getBytes(
- getSerializedCompoundRuleWithInstallerNameAndInstallerCert(
- String.format("%s%04d", installerNamePrefix, count)));
- expectedOrderedRuleOutputStream.write(bytesForPackage);
- totalBytesWritten += bytesForPackage.length;
- }
- expectedIndexingBytesForUnindexed +=
- SERIALIZED_END_INDEXING_KEY + getBits(totalBytesWritten, /* numOfBits= */ 32);
- expectedIndexingOutputStream.write(
- getBytes(
- expectedIndexingBytesForPackageNameIndexed
- + expectedIndexingBytesForAppCertificateIndexed
- + expectedIndexingBytesForUnindexed));
-
- assertThat(ruleOutputStream.toByteArray())
- .isEqualTo(expectedOrderedRuleOutputStream.toByteArray());
- assertThat(indexingOutputStream.toByteArray())
- .isEqualTo(expectedIndexingOutputStream.toByteArray());
- }
-
- @Test
- public void testBinaryString_totalRuleSizeLimitReached() {
- int ruleCount = INDEXED_RULE_SIZE_LIMIT - 1;
- String packagePrefix = "package.name.";
- String appCertificatePrefix = "app.cert.";
- String installerNamePrefix = "installer.";
-
- // Create the rule set with more rules than the system can handle in total.
- List<Rule> ruleList = new ArrayList();
- for (int count = 0; count < ruleCount; count++) {
- ruleList.add(
- getRuleWithPackageNameAndSampleInstallerName(
- String.format("%s%04d", packagePrefix, count)));
- }
- for (int count = 0; count < ruleCount; count++) {
- ruleList.add(
- getRuleWithAppCertificateAndSampleInstallerName(
- String.format("%s%04d", appCertificatePrefix, count)));
- }
- for (int count = 0; count < NONINDEXED_RULE_SIZE_LIMIT - 1; count++) {
- ruleList.add(
- getNonIndexedRuleWithInstallerName(
- String.format("%s%04d", installerNamePrefix, count)));
- }
-
- // Serialize the rules.
- ByteArrayOutputStream ruleOutputStream = new ByteArrayOutputStream();
- ByteArrayOutputStream indexingOutputStream = new ByteArrayOutputStream();
- RuleSerializer binarySerializer = new RuleBinarySerializer();
-
- assertExpectException(
- RuleSerializeException.class,
- "Too many rules provided",
- () ->
- binarySerializer.serialize(
- ruleList,
- /* formatVersion= */ Optional.empty(),
- ruleOutputStream,
- indexingOutputStream));
- }
-
- @Test
- public void testBinaryString_tooManyPackageNameIndexedRules() {
- String packagePrefix = "package.name.";
-
- // Create a rule set with too many package name indexed rules.
- List<Rule> ruleList = new ArrayList();
- for (int count = 0; count < INDEXED_RULE_SIZE_LIMIT + 1; count++) {
- ruleList.add(
- getRuleWithPackageNameAndSampleInstallerName(
- String.format("%s%04d", packagePrefix, count)));
- }
-
- // Serialize the rules.
- ByteArrayOutputStream ruleOutputStream = new ByteArrayOutputStream();
- ByteArrayOutputStream indexingOutputStream = new ByteArrayOutputStream();
- RuleSerializer binarySerializer = new RuleBinarySerializer();
-
- assertExpectException(
- RuleSerializeException.class,
- "Too many rules provided in the indexing group.",
- () ->
- binarySerializer.serialize(
- ruleList,
- /* formatVersion= */ Optional.empty(),
- ruleOutputStream,
- indexingOutputStream));
- }
-
- @Test
- public void testBinaryString_tooManyAppCertificateIndexedRules() {
- String appCertificatePrefix = "app.cert.";
-
- // Create a rule set with too many app certificate indexed rules.
- List<Rule> ruleList = new ArrayList();
- for (int count = 0; count < INDEXED_RULE_SIZE_LIMIT + 1; count++) {
- ruleList.add(
- getRuleWithAppCertificateAndSampleInstallerName(
- String.format("%s%04d", appCertificatePrefix, count)));
- }
-
- // Serialize the rules.
- ByteArrayOutputStream ruleOutputStream = new ByteArrayOutputStream();
- ByteArrayOutputStream indexingOutputStream = new ByteArrayOutputStream();
- RuleSerializer binarySerializer = new RuleBinarySerializer();
-
- assertExpectException(
- RuleSerializeException.class,
- "Too many rules provided in the indexing group.",
- () ->
- binarySerializer.serialize(
- ruleList,
- /* formatVersion= */ Optional.empty(),
- ruleOutputStream,
- indexingOutputStream));
- }
-
- @Test
- public void testBinaryString_tooManyNonIndexedRules() {
- String installerNamePrefix = "installer.";
-
- // Create a rule set with too many unindexed rules.
- List<Rule> ruleList = new ArrayList();
- for (int count = 0; count < NONINDEXED_RULE_SIZE_LIMIT + 1; count++) {
- ruleList.add(
- getNonIndexedRuleWithInstallerName(
- String.format("%s%04d", installerNamePrefix, count)));
- }
-
- // Serialize the rules.
- ByteArrayOutputStream ruleOutputStream = new ByteArrayOutputStream();
- ByteArrayOutputStream indexingOutputStream = new ByteArrayOutputStream();
- RuleSerializer binarySerializer = new RuleBinarySerializer();
-
- assertExpectException(
- RuleSerializeException.class,
- "Too many rules provided in the indexing group.",
- () ->
- binarySerializer.serialize(
- ruleList,
- /* formatVersion= */ Optional.empty(),
- ruleOutputStream,
- indexingOutputStream));
- }
-
- private Rule getRuleWithPackageNameAndSampleInstallerName(String packageName) {
- return new Rule(
- new CompoundFormula(
- CompoundFormula.AND,
- Arrays.asList(
- new AtomicFormula.StringAtomicFormula(
- AtomicFormula.PACKAGE_NAME,
- packageName,
- /* isHashedValue= */ false),
- new AtomicFormula.StringAtomicFormula(
- AtomicFormula.INSTALLER_NAME,
- SAMPLE_INSTALLER_NAME,
- /* isHashedValue= */ false))),
- Rule.DENY);
- }
-
- private String getSerializedCompoundRuleWithPackageNameAndSampleInstallerName(
- String packageName) {
- return START_BIT
- + COMPOUND_FORMULA_START_BITS
- + AND
- + ATOMIC_FORMULA_START_BITS
- + PACKAGE_NAME
- + EQ
- + IS_NOT_HASHED
- + getBits(packageName.length(), VALUE_SIZE_BITS)
- + getValueBits(packageName)
- + ATOMIC_FORMULA_START_BITS
- + INSTALLER_NAME
- + EQ
- + IS_NOT_HASHED
- + getBits(SAMPLE_INSTALLER_NAME.length(), VALUE_SIZE_BITS)
- + getValueBits(SAMPLE_INSTALLER_NAME)
- + COMPOUND_FORMULA_END_BITS
- + DENY
- + END_BIT;
- }
-
- private Rule getRuleWithAppCertificateAndSampleInstallerName(String certificate) {
- return new Rule(
- new CompoundFormula(
- CompoundFormula.AND,
- Arrays.asList(
- new AtomicFormula.StringAtomicFormula(
- AtomicFormula.APP_CERTIFICATE,
- certificate,
- /* isHashedValue= */ false),
- new AtomicFormula.StringAtomicFormula(
- AtomicFormula.INSTALLER_NAME,
- SAMPLE_INSTALLER_NAME,
- /* isHashedValue= */ false))),
- Rule.DENY);
- }
-
- private String getSerializedCompoundRuleWithCertificateNameAndSampleInstallerName(
- String appCertificate) {
- return START_BIT
- + COMPOUND_FORMULA_START_BITS
- + AND
- + ATOMIC_FORMULA_START_BITS
- + APP_CERTIFICATE
- + EQ
- + IS_NOT_HASHED
- + getBits(appCertificate.length(), VALUE_SIZE_BITS)
- + getValueBits(appCertificate)
- + ATOMIC_FORMULA_START_BITS
- + INSTALLER_NAME
- + EQ
- + IS_NOT_HASHED
- + getBits(SAMPLE_INSTALLER_NAME.length(), VALUE_SIZE_BITS)
- + getValueBits(SAMPLE_INSTALLER_NAME)
- + COMPOUND_FORMULA_END_BITS
- + DENY
- + END_BIT;
- }
-
- private Rule getNonIndexedRuleWithInstallerName(String installerName) {
- return new Rule(
- new CompoundFormula(
- CompoundFormula.AND,
- Arrays.asList(
- new AtomicFormula.StringAtomicFormula(
- AtomicFormula.INSTALLER_NAME,
- installerName,
- /* isHashedValue= */ false),
- new AtomicFormula.StringAtomicFormula(
- AtomicFormula.INSTALLER_CERTIFICATE,
- SAMPLE_INSTALLER_CERT,
- /* isHashedValue= */ false))),
- Rule.DENY);
- }
-
- private String getSerializedCompoundRuleWithInstallerNameAndInstallerCert(
- String installerName) {
- return START_BIT
- + COMPOUND_FORMULA_START_BITS
- + AND
- + ATOMIC_FORMULA_START_BITS
- + INSTALLER_NAME
- + EQ
- + IS_NOT_HASHED
- + getBits(installerName.length(), VALUE_SIZE_BITS)
- + getValueBits(installerName)
- + ATOMIC_FORMULA_START_BITS
- + INSTALLER_CERTIFICATE
- + EQ
- + IS_NOT_HASHED
- + getBits(SAMPLE_INSTALLER_CERT.length(), VALUE_SIZE_BITS)
- + getValueBits(SAMPLE_INSTALLER_CERT)
- + COMPOUND_FORMULA_END_BITS
- + DENY
- + END_BIT;
- }
-
- private static IntegrityFormula getInvalidFormula() {
- return new AtomicFormula(0) {
- @Override
- public int getTag() {
- return 0;
- }
-
- @Override
- public boolean matches(AppInstallMetadata appInstallMetadata) {
- return false;
- }
-
- @Override
- public boolean isAppCertificateFormula() {
- return false;
- }
-
- @Override
- public boolean isAppCertificateLineageFormula() {
- return false;
- }
-
- @Override
- public boolean isInstallerFormula() {
- return false;
- }
-
- @Override
- public int hashCode() {
- return super.hashCode();
- }
-
- @Override
- public boolean equals(Object obj) {
- return super.equals(obj);
- }
-
- @NonNull
- @Override
- protected Object clone() throws CloneNotSupportedException {
- return super.clone();
- }
-
- @Override
- public String toString() {
- return super.toString();
- }
-
- @Override
- protected void finalize() throws Throwable {
- super.finalize();
- }
- };
- }
-}
diff --git a/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifierTest.java b/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifierTest.java
deleted file mode 100644
index 6dccdf5..0000000
--- a/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifierTest.java
+++ /dev/null
@@ -1,344 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.integrity.serializer;
-
-import static com.android.server.integrity.serializer.RuleIndexingDetails.APP_CERTIFICATE_INDEXED;
-import static com.android.server.integrity.serializer.RuleIndexingDetails.NOT_INDEXED;
-import static com.android.server.integrity.serializer.RuleIndexingDetails.PACKAGE_NAME_INDEXED;
-import static com.android.server.integrity.serializer.RuleIndexingDetailsIdentifier.splitRulesIntoIndexBuckets;
-import static com.android.server.testutils.TestUtils.assertExpectException;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.content.integrity.AppInstallMetadata;
-import android.content.integrity.AtomicFormula;
-import android.content.integrity.CompoundFormula;
-import android.content.integrity.IntegrityFormula;
-import android.content.integrity.Rule;
-
-import androidx.annotation.NonNull;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.Map;
-
-/** Unit tests for {@link RuleIndexingDetailsIdentifier}. */
-@RunWith(JUnit4.class)
-public class RuleIndexingDetailsIdentifierTest {
-
- private static final String SAMPLE_APP_CERTIFICATE = "testcert";
- private static final String SAMPLE_INSTALLER_NAME = "com.test.installer";
- private static final String SAMPLE_INSTALLER_CERTIFICATE = "installercert";
- private static final String SAMPLE_PACKAGE_NAME = "com.test.package";
-
- private static final AtomicFormula ATOMIC_FORMULA_WITH_PACKAGE_NAME =
- new AtomicFormula.StringAtomicFormula(
- AtomicFormula.PACKAGE_NAME,
- SAMPLE_PACKAGE_NAME,
- /* isHashedValue= */ false);
- private static final AtomicFormula ATOMIC_FORMULA_WITH_APP_CERTIFICATE =
- new AtomicFormula.StringAtomicFormula(
- AtomicFormula.APP_CERTIFICATE,
- SAMPLE_APP_CERTIFICATE,
- /* isHashedValue= */ false);
- private static final AtomicFormula ATOMIC_FORMULA_WITH_INSTALLER_NAME =
- new AtomicFormula.StringAtomicFormula(
- AtomicFormula.INSTALLER_NAME,
- SAMPLE_INSTALLER_NAME,
- /* isHashedValue= */ false);
- private static final AtomicFormula ATOMIC_FORMULA_WITH_INSTALLER_CERTIFICATE =
- new AtomicFormula.StringAtomicFormula(
- AtomicFormula.INSTALLER_CERTIFICATE,
- SAMPLE_INSTALLER_CERTIFICATE,
- /* isHashedValue= */ false);
- private static final AtomicFormula ATOMIC_FORMULA_WITH_VERSION_CODE =
- new AtomicFormula.LongAtomicFormula(AtomicFormula.VERSION_CODE,
- AtomicFormula.EQ, 12);
- private static final AtomicFormula ATOMIC_FORMULA_WITH_ISPREINSTALLED =
- new AtomicFormula.BooleanAtomicFormula(
- AtomicFormula.PRE_INSTALLED, /* booleanValue= */
- true);
-
-
- private static final Rule RULE_WITH_PACKAGE_NAME =
- new Rule(
- new CompoundFormula(
- CompoundFormula.AND,
- Arrays.asList(
- ATOMIC_FORMULA_WITH_PACKAGE_NAME,
- ATOMIC_FORMULA_WITH_INSTALLER_NAME)),
- Rule.DENY);
- private static final Rule RULE_WITH_APP_CERTIFICATE =
- new Rule(
- new CompoundFormula(
- CompoundFormula.AND,
- Arrays.asList(
- ATOMIC_FORMULA_WITH_APP_CERTIFICATE,
- ATOMIC_FORMULA_WITH_INSTALLER_NAME)),
- Rule.DENY);
- private static final Rule RULE_WITH_INSTALLER_RESTRICTIONS =
- new Rule(
- new CompoundFormula(
- CompoundFormula.AND,
- Arrays.asList(
- ATOMIC_FORMULA_WITH_INSTALLER_NAME,
- ATOMIC_FORMULA_WITH_INSTALLER_CERTIFICATE)),
- Rule.DENY);
-
- private static final Rule RULE_WITH_NONSTRING_RESTRICTIONS =
- new Rule(
- new CompoundFormula(
- CompoundFormula.AND,
- Arrays.asList(
- ATOMIC_FORMULA_WITH_VERSION_CODE,
- ATOMIC_FORMULA_WITH_ISPREINSTALLED)),
- Rule.DENY);
- public static final int INVALID_FORMULA_TAG = -1;
-
- @Test
- public void getIndexType_nullRule() {
- List<Rule> ruleList = null;
-
- assertExpectException(
- IllegalArgumentException.class,
- /* expectedExceptionMessageRegex= */
- "Index buckets cannot be created for null rule list.",
- () -> splitRulesIntoIndexBuckets(ruleList));
- }
-
- @Test
- public void getIndexType_invalidFormula() {
- List<Rule> ruleList = new ArrayList();
- ruleList.add(new Rule(getInvalidFormula(), Rule.DENY));
-
- assertExpectException(
- IllegalArgumentException.class,
- /* expectedExceptionMessageRegex= */ "Malformed rule identified.",
- () -> splitRulesIntoIndexBuckets(ruleList));
- }
-
- @Test
- public void getIndexType_ruleContainingPackageNameFormula() {
- List<Rule> ruleList = new ArrayList();
- ruleList.add(RULE_WITH_PACKAGE_NAME);
-
- Map<Integer, Map<String, List<Rule>>> result = splitRulesIntoIndexBuckets(ruleList);
-
- // Verify the resulting map content.
- assertThat(result.keySet())
- .containsExactly(NOT_INDEXED, PACKAGE_NAME_INDEXED, APP_CERTIFICATE_INDEXED);
- assertThat(result.get(NOT_INDEXED)).isEmpty();
- assertThat(result.get(APP_CERTIFICATE_INDEXED)).isEmpty();
- assertThat(result.get(PACKAGE_NAME_INDEXED).keySet()).containsExactly(SAMPLE_PACKAGE_NAME);
- assertThat(result.get(PACKAGE_NAME_INDEXED).get(SAMPLE_PACKAGE_NAME))
- .containsExactly(RULE_WITH_PACKAGE_NAME);
- }
-
- @Test
- public void getIndexType_ruleContainingAppCertificateFormula() {
- List<Rule> ruleList = new ArrayList();
- ruleList.add(RULE_WITH_APP_CERTIFICATE);
-
- Map<Integer, Map<String, List<Rule>>> result = splitRulesIntoIndexBuckets(ruleList);
-
- assertThat(result.keySet())
- .containsExactly(NOT_INDEXED, PACKAGE_NAME_INDEXED, APP_CERTIFICATE_INDEXED);
- assertThat(result.get(NOT_INDEXED)).isEmpty();
- assertThat(result.get(PACKAGE_NAME_INDEXED)).isEmpty();
- assertThat(result.get(APP_CERTIFICATE_INDEXED).keySet())
- .containsExactly(SAMPLE_APP_CERTIFICATE);
- assertThat(result.get(APP_CERTIFICATE_INDEXED).get(SAMPLE_APP_CERTIFICATE))
- .containsExactly(RULE_WITH_APP_CERTIFICATE);
- }
-
- @Test
- public void getIndexType_ruleWithUnindexedCompoundFormula() {
- List<Rule> ruleList = new ArrayList();
- ruleList.add(RULE_WITH_INSTALLER_RESTRICTIONS);
-
- Map<Integer, Map<String, List<Rule>>> result = splitRulesIntoIndexBuckets(ruleList);
-
- assertThat(result.keySet())
- .containsExactly(NOT_INDEXED, PACKAGE_NAME_INDEXED, APP_CERTIFICATE_INDEXED);
- assertThat(result.get(PACKAGE_NAME_INDEXED)).isEmpty();
- assertThat(result.get(APP_CERTIFICATE_INDEXED)).isEmpty();
- assertThat(result.get(NOT_INDEXED).get("N/A"))
- .containsExactly(RULE_WITH_INSTALLER_RESTRICTIONS);
- }
-
- @Test
- public void getIndexType_ruleContainingCompoundFormulaWithIntAndBoolean() {
- List<Rule> ruleList = new ArrayList();
- ruleList.add(RULE_WITH_NONSTRING_RESTRICTIONS);
-
- Map<Integer, Map<String, List<Rule>>> result = splitRulesIntoIndexBuckets(ruleList);
-
- assertThat(result.keySet())
- .containsExactly(NOT_INDEXED, PACKAGE_NAME_INDEXED, APP_CERTIFICATE_INDEXED);
- assertThat(result.get(PACKAGE_NAME_INDEXED)).isEmpty();
- assertThat(result.get(APP_CERTIFICATE_INDEXED)).isEmpty();
- assertThat(result.get(NOT_INDEXED).get("N/A"))
- .containsExactly(RULE_WITH_NONSTRING_RESTRICTIONS);
- }
-
- @Test
- public void getIndexType_negatedRuleContainingPackageNameFormula() {
- Rule negatedRule =
- new Rule(
- new CompoundFormula(
- CompoundFormula.NOT,
- Arrays.asList(
- new CompoundFormula(
- CompoundFormula.AND,
- Arrays.asList(
- ATOMIC_FORMULA_WITH_PACKAGE_NAME,
- ATOMIC_FORMULA_WITH_APP_CERTIFICATE)))),
- Rule.DENY);
- List<Rule> ruleList = new ArrayList();
- ruleList.add(negatedRule);
-
- Map<Integer, Map<String, List<Rule>>> result = splitRulesIntoIndexBuckets(ruleList);
-
- assertThat(result.keySet())
- .containsExactly(NOT_INDEXED, PACKAGE_NAME_INDEXED, APP_CERTIFICATE_INDEXED);
- assertThat(result.get(PACKAGE_NAME_INDEXED)).isEmpty();
- assertThat(result.get(APP_CERTIFICATE_INDEXED)).isEmpty();
- assertThat(result.get(NOT_INDEXED).get("N/A")).containsExactly(negatedRule);
- }
-
- @Test
- public void getIndexType_allRulesTogetherSplitCorrectly() {
- Rule packageNameRuleA = getRuleWithPackageName("aaa");
- Rule packageNameRuleB = getRuleWithPackageName("bbb");
- Rule packageNameRuleC = getRuleWithPackageName("ccc");
- Rule certificateRule1 = getRuleWithAppCertificate("cert1");
- Rule certificateRule2 = getRuleWithAppCertificate("cert2");
- Rule certificateRule3 = getRuleWithAppCertificate("cert3");
-
- List<Rule> ruleList = new ArrayList();
- ruleList.add(packageNameRuleB);
- ruleList.add(packageNameRuleC);
- ruleList.add(packageNameRuleA);
- ruleList.add(certificateRule3);
- ruleList.add(certificateRule2);
- ruleList.add(certificateRule1);
- ruleList.add(RULE_WITH_INSTALLER_RESTRICTIONS);
- ruleList.add(RULE_WITH_NONSTRING_RESTRICTIONS);
-
- Map<Integer, Map<String, List<Rule>>> result = splitRulesIntoIndexBuckets(ruleList);
-
- assertThat(result.keySet())
- .containsExactly(NOT_INDEXED, PACKAGE_NAME_INDEXED, APP_CERTIFICATE_INDEXED);
-
- // We check asserts this way to ensure ordering based on package name.
- assertThat(result.get(PACKAGE_NAME_INDEXED).keySet()).containsExactly("aaa", "bbb", "ccc");
-
- // We check asserts this way to ensure ordering based on app certificate.
- assertThat(result.get(APP_CERTIFICATE_INDEXED).keySet()).containsExactly("cert1", "cert2",
- "cert3");
-
- assertThat(result.get(NOT_INDEXED).get("N/A"))
- .containsExactly(RULE_WITH_INSTALLER_RESTRICTIONS,
- RULE_WITH_NONSTRING_RESTRICTIONS);
- }
-
- private Rule getRuleWithPackageName(String packageName) {
- return new Rule(
- new CompoundFormula(
- CompoundFormula.AND,
- Arrays.asList(
- new AtomicFormula.StringAtomicFormula(
- AtomicFormula.PACKAGE_NAME,
- packageName,
- /* isHashedValue= */ false),
- ATOMIC_FORMULA_WITH_INSTALLER_NAME)),
- Rule.DENY);
- }
-
- private Rule getRuleWithAppCertificate(String certificate) {
- return new Rule(
- new CompoundFormula(
- CompoundFormula.AND,
- Arrays.asList(
- new AtomicFormula.StringAtomicFormula(
- AtomicFormula.APP_CERTIFICATE,
- certificate,
- /* isHashedValue= */ false),
- ATOMIC_FORMULA_WITH_INSTALLER_NAME)),
- Rule.DENY);
- }
-
- private IntegrityFormula getInvalidFormula() {
- return new AtomicFormula(0) {
- @Override
- public int getTag() {
- return INVALID_FORMULA_TAG;
- }
-
- @Override
- public boolean matches(AppInstallMetadata appInstallMetadata) {
- return false;
- }
-
- @Override
- public boolean isAppCertificateFormula() {
- return false;
- }
-
- @Override
- public boolean isAppCertificateLineageFormula() {
- return false;
- }
-
- @Override
- public boolean isInstallerFormula() {
- return false;
- }
-
- @Override
- public int hashCode() {
- return super.hashCode();
- }
-
- @Override
- public boolean equals(Object obj) {
- return super.equals(obj);
- }
-
- @NonNull
- @Override
- protected Object clone() throws CloneNotSupportedException {
- return super.clone();
- }
-
- @Override
- public String toString() {
- return super.toString();
- }
-
- @Override
- protected void finalize() throws Throwable {
- super.finalize();
- }
- };
- }
-}
diff --git a/services/tests/servicestests/src/com/android/server/pm/BackgroundInstallControlServiceTest.java b/services/tests/servicestests/src/com/android/server/pm/BackgroundInstallControlServiceTest.java
index 4a43c2e..9d7b6a1 100644
--- a/services/tests/servicestests/src/com/android/server/pm/BackgroundInstallControlServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/BackgroundInstallControlServiceTest.java
@@ -977,11 +977,19 @@
assertEquals(USER_ID_1, UserHandle.getUserId(uid));
mPackageListObserver.onPackageRemoved(PACKAGE_NAME_1, uid);
+ // Test that notifyAllCallbacks doesn't trigger for non-background-installed package
+ mPackageListObserver.onPackageRemoved(PACKAGE_NAME_3, uid);
mTestLooper.dispatchAll();
assertEquals(1, packages.size());
assertFalse(packages.contains(USER_ID_1, PACKAGE_NAME_1));
assertTrue(packages.contains(USER_ID_2, PACKAGE_NAME_2));
+
+ verify(mCallbackHelper)
+ .notifyAllCallbacks(
+ USER_ID_1,
+ PACKAGE_NAME_1,
+ BackgroundInstallControlService.INSTALL_EVENT_TYPE_UNINSTALL);
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/policy/KeyGestureEventTests.java b/services/tests/wmtests/src/com/android/server/policy/KeyGestureEventTests.java
index ad11c26..f0b1c5c 100644
--- a/services/tests/wmtests/src/com/android/server/policy/KeyGestureEventTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/KeyGestureEventTests.java
@@ -331,6 +331,7 @@
mPhoneWindowManager.setupAssistForLaunch();
mPhoneWindowManager.overrideTogglePanel();
mPhoneWindowManager.overrideInjectKeyEvent();
+ mPhoneWindowManager.overrideRoleManager();
}
@Test
diff --git a/services/usb/OWNERS b/services/usb/OWNERS
index d35dbb56..2dff392 100644
--- a/services/usb/OWNERS
+++ b/services/usb/OWNERS
@@ -1,9 +1,9 @@
-aprasath@google.com
-kumarashishg@google.com
-sarup@google.com
anothermark@google.com
+febinthattil@google.com
+aprasath@google.com
badhri@google.com
elaurent@google.com
albertccwang@google.com
jameswei@google.com
-howardyen@google.com
\ No newline at end of file
+howardyen@google.com
+kumarashishg@google.com
\ No newline at end of file