Merge "Small refactor AudioDeviceVolumeManager in AudioService" 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/apex/jobscheduler/framework/aconfig/job.aconfig b/apex/jobscheduler/framework/aconfig/job.aconfig
index debd850..79aef1e 100644
--- a/apex/jobscheduler/framework/aconfig/job.aconfig
+++ b/apex/jobscheduler/framework/aconfig/job.aconfig
@@ -37,3 +37,11 @@
description: "Ignore the important_while_foreground flag and change the related APIs to be not effective"
bug: "374175032"
}
+
+flag {
+ name: "get_pending_job_reasons_api"
+ is_exported: true
+ namespace: "backstage_power"
+ description: "Introduce a new getPendingJobReasons() API which returns reasons why a job may not have executed. Also deprecate the existing getPendingJobReason() API."
+ bug: "372031023"
+}
diff --git a/core/api/current.txt b/core/api/current.txt
index 2cb7a30..0153213 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -62048,6 +62048,11 @@
method public void markSyncReady();
}
+ @FlaggedApi("com.android.window.flags.predictive_back_system_override_callback") public final class SystemOnBackInvokedCallbacks {
+ method @FlaggedApi("com.android.window.flags.predictive_back_system_override_callback") @NonNull public static android.window.OnBackInvokedCallback finishAndRemoveTaskCallback(@NonNull android.app.Activity);
+ method @FlaggedApi("com.android.window.flags.predictive_back_system_override_callback") @NonNull public static android.window.OnBackInvokedCallback moveTaskToBackCallback(@NonNull android.app.Activity);
+ }
+
@FlaggedApi("com.android.window.flags.trusted_presentation_listener_for_window") public final class TrustedPresentationThresholds implements android.os.Parcelable {
ctor @FlaggedApi("com.android.window.flags.trusted_presentation_listener_for_window") public TrustedPresentationThresholds(@FloatRange(from=0.0f, fromInclusive=false, to=1.0f) float, @FloatRange(from=0.0f, fromInclusive=false, to=1.0f) float, @IntRange(from=1) int);
method @FlaggedApi("com.android.window.flags.trusted_presentation_listener_for_window") public int describeContents();
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 047a0df..02cd00d 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -7234,6 +7234,7 @@
field @RequiresPermission(allOf={android.Manifest.permission.MODIFY_PHONE_STATE, android.Manifest.permission.MODIFY_AUDIO_ROUTING}) public static final int USAGE_CALL_ASSISTANT = 17; // 0x11
field @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static final int USAGE_EMERGENCY = 1000; // 0x3e8
field @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static final int USAGE_SAFETY = 1001; // 0x3e9
+ field @FlaggedApi("android.media.audio.speaker_cleanup_usage") @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static final int USAGE_SPEAKER_CLEANUP = 1004; // 0x3ec
field @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static final int USAGE_VEHICLE_STATUS = 1002; // 0x3ea
}
diff --git a/core/java/android/app/PropertyInvalidatedCache.java b/core/java/android/app/PropertyInvalidatedCache.java
index a39cf84..038dcdb 100644
--- a/core/java/android/app/PropertyInvalidatedCache.java
+++ b/core/java/android/app/PropertyInvalidatedCache.java
@@ -17,6 +17,7 @@
package android.app;
import static android.text.TextUtils.formatSimple;
+import static com.android.internal.util.Preconditions.checkArgumentPositive;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -40,6 +41,7 @@
import dalvik.annotation.optimization.CriticalNative;
import dalvik.annotation.optimization.FastNative;
+import dalvik.annotation.optimization.NeverCompile;
import java.io.ByteArrayOutputStream;
import java.io.FileOutputStream;
@@ -201,6 +203,23 @@
}
/**
+ * The list of known and legal modules. The list is not sorted.
+ */
+ private static final String[] sValidModule = {
+ MODULE_SYSTEM, MODULE_BLUETOOTH, MODULE_TELEPHONY, MODULE_TEST,
+ };
+
+ /**
+ * Verify that the module string is in the legal list. Throw if it is not.
+ */
+ private static void throwIfInvalidModule(@NonNull String name) {
+ for (int i = 0; i < sValidModule.length; i++) {
+ if (sValidModule[i].equals(name)) return;
+ }
+ throw new IllegalArgumentException("invalid module: " + name);
+ }
+
+ /**
* All legal keys start with one of the following strings.
*/
private static final String[] sValidKeyPrefix = {
@@ -254,8 +273,11 @@
// written to global store.
private static final int NONCE_BYPASS = 3;
+ // The largest reserved nonce value. Update this whenever a reserved nonce is added.
+ private static final int MAX_RESERVED_NONCE = NONCE_BYPASS;
+
private static boolean isReservedNonce(long n) {
- return n >= NONCE_UNSET && n <= NONCE_BYPASS;
+ return n >= NONCE_UNSET && n <= MAX_RESERVED_NONCE;
}
/**
@@ -293,7 +315,7 @@
private long mMisses = 0;
@GuardedBy("mLock")
- private long[] mSkips = new long[]{ 0, 0, 0, 0 };
+ private long[] mSkips = new long[MAX_RESERVED_NONCE + 1];
@GuardedBy("mLock")
private long mMissOverflow = 0;
@@ -854,6 +876,73 @@
}
/**
+ * A public argument builder to configure cache behavior. The root instance requires a
+ * module; this is immutable. New instances are created with member methods. It is important
+ * to note that the member methods create new instances: they do not modify 'this'. The api
+ * is allowed to be null in the record constructor to facility reuse of Args instances.
+ * @hide
+ */
+ public static record Args(@NonNull String mModule, @Nullable String mApi, int mMaxEntries) {
+
+ // Validation: the module must be one of the known module strings and the maxEntries must
+ // be positive.
+ public Args {
+ throwIfInvalidModule(mModule);
+ checkArgumentPositive(mMaxEntries, "max cache size must be positive");
+ }
+
+ // The base constructor must include the module. Modules do not change in a source file,
+ // so even if the Args is reused, the module will not/should not change. The api is null,
+ // which is not legal, but there is no reasonable default. Clients must call the api
+ // method to set the field properly.
+ public Args(@NonNull String module) {
+ this(module, /* api */ null, /* maxEntries */ 32);
+ }
+
+ public Args api(@NonNull String api) {
+ return new Args(mModule, api, mMaxEntries);
+ }
+
+ public Args maxEntries(int val) {
+ return new Args(mModule, mApi, val);
+ }
+ }
+
+ /**
+ * Make a new property invalidated cache. The key is computed from the module and api
+ * parameters.
+ *
+ * @param args The cache configuration.
+ * @param cacheName Name of this cache in debug and dumpsys
+ * @param computer The code to compute values that are not in the cache.
+ * @hide
+ */
+ public PropertyInvalidatedCache(@NonNull Args args, @NonNull String cacheName,
+ @Nullable QueryHandler<Query, Result> computer) {
+ mPropertyName = createPropertyName(args.mModule, args.mApi);
+ mCacheName = cacheName;
+ mNonce = getNonceHandler(mPropertyName);
+ mMaxEntries = args.mMaxEntries;
+ mCache = createMap();
+ mComputer = (computer != null) ? computer : new DefaultComputer<>(this);
+ registerCache();
+ }
+
+ /**
+ * Burst a property name into module and api. Throw if the key is invalid. This method is
+ * used in to transition legacy cache constructors to the args constructor.
+ */
+ private static Args parseProperty(@NonNull String name) {
+ throwIfInvalidCacheKey(name);
+ // Strip off the leading well-known prefix.
+ String base = name.substring(CACHE_KEY_PREFIX.length() + 1);
+ int dot = base.indexOf(".");
+ String module = base.substring(0, dot);
+ String api = base.substring(dot + 1);
+ return new Args(module).api(api);
+ }
+
+ /**
* Make a new property invalidated cache. This constructor names the cache after the
* property name. New clients should prefer the constructor that takes an explicit
* cache name.
@@ -867,7 +956,7 @@
* @hide
*/
public PropertyInvalidatedCache(int maxEntries, @NonNull String propertyName) {
- this(maxEntries, propertyName, propertyName);
+ this(parseProperty(propertyName).maxEntries(maxEntries), propertyName, null);
}
/**
@@ -883,13 +972,7 @@
*/
public PropertyInvalidatedCache(int maxEntries, @NonNull String propertyName,
@NonNull String cacheName) {
- mPropertyName = propertyName;
- mCacheName = cacheName;
- mNonce = getNonceHandler(mPropertyName);
- mMaxEntries = maxEntries;
- mComputer = new DefaultComputer<>(this);
- mCache = createMap();
- registerCache();
+ this(parseProperty(propertyName).maxEntries(maxEntries), cacheName, null);
}
/**
@@ -907,13 +990,7 @@
@TestApi
public PropertyInvalidatedCache(int maxEntries, @NonNull String module, @NonNull String api,
@NonNull String cacheName, @NonNull QueryHandler<Query, Result> computer) {
- mPropertyName = createPropertyName(module, api);
- mCacheName = cacheName;
- mNonce = getNonceHandler(mPropertyName);
- mMaxEntries = maxEntries;
- mComputer = computer;
- mCache = createMap();
- registerCache();
+ this(new Args(module).maxEntries(maxEntries).api(api), cacheName, computer);
}
// Create a map. This should be called only from the constructor.
@@ -1181,7 +1258,8 @@
public @Nullable Result query(@NonNull Query query) {
// Let access to mDisabled race: it's atomic anyway.
long currentNonce = (!isDisabled()) ? getCurrentNonce() : NONCE_DISABLED;
- if (bypass(query)) {
+ if (!isReservedNonce(currentNonce)
+ && bypass(query)) {
currentNonce = NONCE_BYPASS;
}
for (;;) {
@@ -1649,54 +1727,62 @@
return false;
}
- /**
- * helper method to check if dump should be skipped due to zero values
- * @param args takes command arguments to check if -brief is present
- * @return True if dump should be skipped
- */
- private boolean skipDump(String[] args) {
- for (String a : args) {
- if (a.equals(BRIEF)) {
- return (mSkips[NONCE_CORKED] + mSkips[NONCE_UNSET] + mSkips[NONCE_DISABLED]
- + mSkips[NONCE_BYPASS] + mHits + mMisses) == 0;
- }
+ @GuardedBy("mLock")
+ private long getSkipsLocked() {
+ int sum = 0;
+ for (int i = 0; i < mSkips.length; i++) {
+ sum += mSkips[i];
}
- return false;
+ return sum;
}
+ // Return true if this cache has had any activity. If the hits, misses, and skips are all
+ // zero then the client never tried to use the cache.
+ private boolean isActive() {
+ synchronized (mLock) {
+ return mHits + mMisses + getSkipsLocked() > 0;
+ }
+ }
+
+ @NeverCompile
private void dumpContents(PrintWriter pw, boolean detailed, String[] args) {
// If the user has requested specific caches and this is not one of them, return
// immediately.
if (detailed && !showDetailed(args)) {
return;
}
+ // Does the user want brief output?
+ boolean brief = false;
+ for (String a : args) brief |= a.equals(BRIEF);
NonceHandler.Stats stats = mNonce.getStats();
synchronized (mLock) {
- if (!skipDump(args)) {
- pw.println(formatSimple(" Cache Name: %s", cacheName()));
- pw.println(formatSimple(" Property: %s", mPropertyName));
- final long skips =
- mSkips[NONCE_CORKED] + mSkips[NONCE_UNSET] + mSkips[NONCE_DISABLED]
- + mSkips[NONCE_BYPASS];
- pw.println(formatSimple(
- " Hits: %d, Misses: %d, Skips: %d, Clears: %d",
- mHits, mMisses, skips, mClears));
- pw.println(formatSimple(
- " Skip-corked: %d, Skip-unset: %d, Skip-bypass: %d, Skip-other: %d",
- mSkips[NONCE_CORKED], mSkips[NONCE_UNSET],
- mSkips[NONCE_BYPASS], mSkips[NONCE_DISABLED]));
- pw.println(formatSimple(
- " Nonce: 0x%016x, Invalidates: %d, CorkedInvalidates: %d",
- mLastSeenNonce, stats.invalidated, stats.corkedInvalidates));
- pw.println(formatSimple(
- " Current Size: %d, Max Size: %d, HW Mark: %d, Overflows: %d",
- mCache.size(), mMaxEntries, mHighWaterMark, mMissOverflow));
- pw.println(formatSimple(" Enabled: %s", mDisabled ? "false" : "true"));
- pw.println("");
+ if (brief && !isActive()) {
+ return;
}
+ pw.println(formatSimple(" Cache Name: %s", cacheName()));
+ pw.println(formatSimple(" Property: %s", mPropertyName));
+ pw.println(formatSimple(
+ " Hits: %d, Misses: %d, Skips: %d, Clears: %d",
+ mHits, mMisses, getSkipsLocked(), mClears));
+
+ // Print all the skip reasons.
+ pw.format(" Skip-%s: %d", sNonceName[0], mSkips[0]);
+ for (int i = 1; i < mSkips.length; i++) {
+ pw.format(", Skip-%s: %d", sNonceName[i], mSkips[i]);
+ }
+ pw.println();
+
+ pw.println(formatSimple(
+ " Nonce: 0x%016x, Invalidates: %d, CorkedInvalidates: %d",
+ mLastSeenNonce, stats.invalidated, stats.corkedInvalidates));
+ pw.println(formatSimple(
+ " Current Size: %d, Max Size: %d, HW Mark: %d, Overflows: %d",
+ mCache.size(), mMaxEntries, mHighWaterMark, mMissOverflow));
+ pw.println(formatSimple(" Enabled: %s", mDisabled ? "false" : "true"));
+
// No specific cache was requested. This is the default, and no details
// should be dumped.
if (!detailed) {
@@ -1723,6 +1809,7 @@
* specific caches (selection is by cache name or property name); if these switches
* are used then the output includes both cache statistics and cache entries.
*/
+ @NeverCompile
private static void dumpCacheInfo(@NonNull PrintWriter pw, @NonNull String[] args) {
if (!sEnabled) {
pw.println(" Caching is disabled in this process.");
@@ -1755,6 +1842,7 @@
* are used then the output includes both cache statistics and cache entries.
* @hide
*/
+ @NeverCompile
public static void dumpCacheInfo(@NonNull ParcelFileDescriptor pfd, @NonNull String[] args) {
// Create a PrintWriter that uses a byte array. The code can safely write to
// this array without fear of blocking. The completed byte array will be sent
diff --git a/core/java/android/app/TaskInfo.java b/core/java/android/app/TaskInfo.java
index 0a4d8f2..9c6f509 100644
--- a/core/java/android/app/TaskInfo.java
+++ b/core/java/android/app/TaskInfo.java
@@ -488,7 +488,7 @@
&& Objects.equals(topActivity, that.topActivity)
&& isTopActivityTransparent == that.isTopActivityTransparent
&& isTopActivityStyleFloating == that.isTopActivityStyleFloating
- && lastNonFullscreenBounds == this.lastNonFullscreenBounds
+ && Objects.equals(lastNonFullscreenBounds, that.lastNonFullscreenBounds)
&& Objects.equals(capturedLink, that.capturedLink)
&& capturedLinkTimestamp == that.capturedLinkTimestamp
&& requestedVisibleTypes == that.requestedVisibleTypes
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/input/InputSettings.java b/core/java/android/hardware/input/InputSettings.java
index 4a9efe0..c456698 100644
--- a/core/java/android/hardware/input/InputSettings.java
+++ b/core/java/android/hardware/input/InputSettings.java
@@ -20,16 +20,18 @@
import static com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_MOUSE_KEYS;
import static com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_SLOW_KEYS_FLAG;
import static com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_STICKY_KEYS_FLAG;
+import static com.android.hardware.input.Flags.enableCustomizableInputGestures;
import static com.android.hardware.input.Flags.keyboardA11yBounceKeysFlag;
+import static com.android.hardware.input.Flags.keyboardA11yMouseKeys;
import static com.android.hardware.input.Flags.keyboardA11ySlowKeysFlag;
import static com.android.hardware.input.Flags.keyboardA11yStickyKeysFlag;
-import static com.android.hardware.input.Flags.keyboardA11yMouseKeys;
import static com.android.hardware.input.Flags.mouseReverseVerticalScrolling;
import static com.android.hardware.input.Flags.mouseSwapPrimaryButton;
import static com.android.hardware.input.Flags.touchpadTapDragging;
+import static com.android.hardware.input.Flags.touchpadThreeFingerTapShortcut;
import static com.android.hardware.input.Flags.touchpadVisualizer;
-import static com.android.input.flags.Flags.enableInputFilterRustImpl;
import static com.android.input.flags.Flags.FLAG_KEYBOARD_REPEAT_KEYS;
+import static com.android.input.flags.Flags.enableInputFilterRustImpl;
import static com.android.input.flags.Flags.keyboardRepeatKeys;
import android.Manifest;
@@ -379,6 +381,15 @@
}
/**
+ * Returns true if the feature flag for the touchpad three-finger tap shortcut is enabled.
+ *
+ * @hide
+ */
+ public static boolean isTouchpadThreeFingerTapShortcutFeatureFlagEnabled() {
+ return enableCustomizableInputGestures() && touchpadThreeFingerTapShortcut();
+ }
+
+ /**
* Returns true if the feature flag for mouse reverse vertical scrolling is enabled.
* @hide
*/
@@ -498,6 +509,22 @@
}
/**
+ * Returns true if three-finger taps on the touchpad should trigger a customizable shortcut
+ * rather than a middle click.
+ *
+ * The returned value only applies to gesture-compatible touchpads.
+ *
+ * @param context The application context.
+ * @return Whether three-finger taps should trigger the shortcut.
+ *
+ * @hide
+ */
+ public static boolean useTouchpadThreeFingerTapShortcut(@NonNull Context context) {
+ // TODO(b/365063048): determine whether to enable the shortcut based on the settings.
+ return isTouchpadThreeFingerTapShortcutFeatureFlagEnabled();
+ }
+
+ /**
* Whether a pointer icon will be shown over the location of a stylus pointer.
*
* @hide
diff --git a/core/java/android/hardware/input/input_framework.aconfig b/core/java/android/hardware/input/input_framework.aconfig
index 71c91e9..f9cb94a 100644
--- a/core/java/android/hardware/input/input_framework.aconfig
+++ b/core/java/android/hardware/input/input_framework.aconfig
@@ -158,3 +158,10 @@
description: "Allows privileged focused windows to capture power key events."
bug: "357144512"
}
+
+flag {
+ name: "touchpad_three_finger_tap_shortcut"
+ namespace: "input"
+ description: "Turns three-finger touchpad taps into a customizable shortcut."
+ bug: "365063048"
+}
diff --git a/core/java/android/os/IpcDataCache.java b/core/java/android/os/IpcDataCache.java
index 7875c23..8db1567 100644
--- a/core/java/android/os/IpcDataCache.java
+++ b/core/java/android/os/IpcDataCache.java
@@ -23,6 +23,7 @@
import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.app.PropertyInvalidatedCache;
+import android.app.PropertyInvalidatedCache.Args;
import android.text.TextUtils;
import android.util.ArraySet;
@@ -341,7 +342,7 @@
public IpcDataCache(int maxEntries, @NonNull @IpcDataCacheModule String module,
@NonNull String api, @NonNull String cacheName,
@NonNull QueryHandler<Query, Result> computer) {
- super(maxEntries, module, api, cacheName, computer);
+ super(new Args(module).maxEntries(maxEntries).api(api), cacheName, computer);
}
/**
@@ -563,7 +564,8 @@
* @hide
*/
public IpcDataCache(@NonNull Config config, @NonNull QueryHandler<Query, Result> computer) {
- super(config.maxEntries(), config.module(), config.api(), config.name(), computer);
+ super(new Args(config.module()).maxEntries(config.maxEntries()).api(config.api()),
+ config.name(), computer);
}
/**
diff --git a/core/java/android/service/games/GameSession.java b/core/java/android/service/games/GameSession.java
index e8d53d3..531e0b1 100644
--- a/core/java/android/service/games/GameSession.java
+++ b/core/java/android/service/games/GameSession.java
@@ -516,6 +516,8 @@
options,
future);
+ trampolineIntent.collectExtraIntentKeys();
+
try {
int result = ActivityTaskManager.getService().startActivityFromGameSession(
mContext.getIApplicationThread(), mContext.getPackageName(), "GameSession",
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/window/OnBackInvokedCallbackInfo.java b/core/java/android/window/OnBackInvokedCallbackInfo.java
index bb5fe96..44c7bd9 100644
--- a/core/java/android/window/OnBackInvokedCallbackInfo.java
+++ b/core/java/android/window/OnBackInvokedCallbackInfo.java
@@ -16,6 +16,8 @@
package android.window;
+import static android.window.SystemOverrideOnBackInvokedCallback.OVERRIDE_UNDEFINED;
+
import android.annotation.NonNull;
import android.os.Parcel;
import android.os.Parcelable;
@@ -29,19 +31,23 @@
private final IOnBackInvokedCallback mCallback;
private @OnBackInvokedDispatcher.Priority int mPriority;
private final boolean mIsAnimationCallback;
+ private final @SystemOverrideOnBackInvokedCallback.OverrideBehavior int mOverrideBehavior;
public OnBackInvokedCallbackInfo(@NonNull IOnBackInvokedCallback callback,
int priority,
- boolean isAnimationCallback) {
+ boolean isAnimationCallback,
+ int overrideBehavior) {
mCallback = callback;
mPriority = priority;
mIsAnimationCallback = isAnimationCallback;
+ mOverrideBehavior = overrideBehavior;
}
private OnBackInvokedCallbackInfo(@NonNull Parcel in) {
mCallback = IOnBackInvokedCallback.Stub.asInterface(in.readStrongBinder());
mPriority = in.readInt();
mIsAnimationCallback = in.readBoolean();
+ mOverrideBehavior = in.readInt();
}
@Override
@@ -54,6 +60,7 @@
dest.writeStrongInterface(mCallback);
dest.writeInt(mPriority);
dest.writeBoolean(mIsAnimationCallback);
+ dest.writeInt(mOverrideBehavior);
}
public static final Creator<OnBackInvokedCallbackInfo> CREATOR =
@@ -70,7 +77,8 @@
};
public boolean isSystemCallback() {
- return mPriority == OnBackInvokedDispatcher.PRIORITY_SYSTEM;
+ return mPriority == OnBackInvokedDispatcher.PRIORITY_SYSTEM
+ || mOverrideBehavior != OVERRIDE_UNDEFINED;
}
@NonNull
@@ -87,12 +95,18 @@
return mIsAnimationCallback;
}
+ @SystemOverrideOnBackInvokedCallback.OverrideBehavior
+ public int getOverrideBehavior() {
+ return mOverrideBehavior;
+ }
+
@Override
public String toString() {
return "OnBackInvokedCallbackInfo{"
+ "mCallback=" + mCallback
+ ", mPriority=" + mPriority
+ ", mIsAnimationCallback=" + mIsAnimationCallback
+ + ", mOverrideBehavior=" + mOverrideBehavior
+ '}';
}
}
diff --git a/core/java/android/window/SystemOnBackInvokedCallbacks.java b/core/java/android/window/SystemOnBackInvokedCallbacks.java
new file mode 100644
index 0000000..f67520b
--- /dev/null
+++ b/core/java/android/window/SystemOnBackInvokedCallbacks.java
@@ -0,0 +1,168 @@
+/*
+ * 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.window;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.app.Activity;
+import android.util.ArrayMap;
+
+import com.android.window.flags.Flags;
+
+import java.lang.ref.WeakReference;
+
+/**
+ * Utility class providing {@link OnBackInvokedCallback}s to override the default behavior when
+ * system back is invoked. e.g. {@link Activity#finish}
+ *
+ * <p>By registering these callbacks with the {@link OnBackInvokedDispatcher}, the system can
+ * trigger specific behaviors and play corresponding ahead-of-time animations when the back
+ * gesture is invoked.
+ *
+ * <p>For example, to trigger the {@link Activity#moveTaskToBack} behavior:
+ * <pre>
+ * OnBackInvokedDispatcher dispatcher = activity.getOnBackInvokedDispatcher();
+ * dispatcher.registerOnBackInvokedCallback(
+ * OnBackInvokedDispatcher.PRIORITY_DEFAULT,
+ * SystemOnBackInvokedCallbacks.moveTaskToBackCallback(activity));
+ * </pre>
+ */
+@SuppressWarnings("SingularCallback")
+@FlaggedApi(Flags.FLAG_PREDICTIVE_BACK_SYSTEM_OVERRIDE_CALLBACK)
+public final class SystemOnBackInvokedCallbacks {
+ private static final OverrideCallbackFactory<Activity> sMoveTaskToBackFactory = new
+ MoveTaskToBackCallbackFactory();
+ private static final OverrideCallbackFactory<Activity> sFinishAndRemoveTaskFactory = new
+ FinishAndRemoveTaskCallbackFactory();
+
+ private SystemOnBackInvokedCallbacks() {
+ throw new UnsupportedOperationException("This is a utility class and cannot be "
+ + "instantiated");
+ }
+
+ /**
+ * <p>Get a callback to triggers {@link Activity#moveTaskToBack(boolean)} on the associated
+ * {@link Activity}, moving the task containing the activity to the background. The system
+ * will play the corresponding transition animation, regardless of whether the activity
+ * is the root activity of the task.</p>
+ *
+ * @param activity The associated {@link Activity}
+ * @see Activity#moveTaskToBack(boolean)
+ */
+ @FlaggedApi(Flags.FLAG_PREDICTIVE_BACK_SYSTEM_OVERRIDE_CALLBACK)
+ @NonNull
+ public static OnBackInvokedCallback moveTaskToBackCallback(@NonNull Activity activity) {
+ return sMoveTaskToBackFactory.getOverrideCallback(activity);
+ }
+
+ /**
+ * <p>Get a callback to triggers {@link Activity#finishAndRemoveTask()} on the associated
+ * {@link Activity}. If the activity is the root activity of its task, the entire task
+ * will be removed from the recents task. The activity will be finished in all cases.
+ * The system will play the corresponding transition animation.</p>
+ *
+ * @param activity The associated {@link Activity}
+ * @see Activity#finishAndRemoveTask()
+ */
+ @FlaggedApi(Flags.FLAG_PREDICTIVE_BACK_SYSTEM_OVERRIDE_CALLBACK)
+ @NonNull
+ public static OnBackInvokedCallback finishAndRemoveTaskCallback(@NonNull Activity activity) {
+ return sFinishAndRemoveTaskFactory.getOverrideCallback(activity);
+ }
+
+ /**
+ * Abstract factory for creating system override {@link SystemOverrideOnBackInvokedCallback}
+ * instances.
+ *
+ * <p>Concrete implementations of this factory are responsible for creating callbacks that
+ * override the default system back navigation behavior. These callbacks should be used
+ * exclusively for system overrides and should never be invoked directly.</p>
+ */
+ private abstract static class OverrideCallbackFactory<TYPE> {
+ private final ArrayMap<WeakReference<TYPE>,
+ WeakReference<SystemOverrideOnBackInvokedCallback>> mObjectMap = new ArrayMap<>();
+
+ protected abstract SystemOverrideOnBackInvokedCallback createCallback(
+ @NonNull TYPE context);
+
+ @NonNull SystemOverrideOnBackInvokedCallback getOverrideCallback(@NonNull TYPE object) {
+ if (object == null) {
+ throw new NullPointerException("Input object cannot be null");
+ }
+ synchronized (mObjectMap) {
+ WeakReference<SystemOverrideOnBackInvokedCallback> callback = null;
+ for (int i = mObjectMap.size() - 1; i >= 0; --i) {
+ final WeakReference<TYPE> next = mObjectMap.keyAt(i);
+ if (next.get() == object) {
+ callback = mObjectMap.get(next);
+ break;
+ }
+ }
+ if (callback != null) {
+ return callback.get();
+ }
+ final SystemOverrideOnBackInvokedCallback contextCallback = createCallback(object);
+ if (contextCallback != null) {
+ mObjectMap.put(new WeakReference<>(object),
+ new WeakReference<>(contextCallback));
+ }
+ return contextCallback;
+ }
+ }
+ }
+
+ private static class MoveTaskToBackCallbackFactory extends OverrideCallbackFactory<Activity> {
+ @Override
+ protected SystemOverrideOnBackInvokedCallback createCallback(Activity activity) {
+ final WeakReference<Activity> activityRef = new WeakReference<>(activity);
+ return new SystemOverrideOnBackInvokedCallback() {
+ @Override
+ public void onBackInvoked() {
+ if (activityRef.get() != null) {
+ activityRef.get().moveTaskToBack(true /* nonRoot */);
+ }
+ }
+
+ @Override
+ public int overrideBehavior() {
+ return OVERRIDE_MOVE_TASK_TO_BACK;
+ }
+ };
+ }
+ }
+
+ private static class FinishAndRemoveTaskCallbackFactory extends
+ OverrideCallbackFactory<Activity> {
+ @Override
+ protected SystemOverrideOnBackInvokedCallback createCallback(Activity activity) {
+ final WeakReference<Activity> activityRef = new WeakReference<>(activity);
+ return new SystemOverrideOnBackInvokedCallback() {
+ @Override
+ public void onBackInvoked() {
+ if (activityRef.get() != null) {
+ activityRef.get().finishAndRemoveTask();
+ }
+ }
+
+ @Override
+ public int overrideBehavior() {
+ return OVERRIDE_FINISH_AND_REMOVE_TASK;
+ }
+ };
+ }
+ }
+}
diff --git a/core/java/android/window/SystemOverrideOnBackInvokedCallback.java b/core/java/android/window/SystemOverrideOnBackInvokedCallback.java
new file mode 100644
index 0000000..3360a19
--- /dev/null
+++ b/core/java/android/window/SystemOverrideOnBackInvokedCallback.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.window;
+
+import android.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Non-default ahead-of-time system OnBackInvokedCallback.
+ * @hide
+ */
+public interface SystemOverrideOnBackInvokedCallback extends OnBackInvokedCallback {
+ /**
+ * No override request
+ */
+ int OVERRIDE_UNDEFINED = 0;
+
+ /**
+ * Navigating back will bring the task to back
+ */
+ int OVERRIDE_MOVE_TASK_TO_BACK = 1;
+
+ /**
+ * Navigating back will finish activity, and remove the task if this activity is root activity.
+ */
+ int OVERRIDE_FINISH_AND_REMOVE_TASK = 2;
+
+ /** @hide */
+ @IntDef({
+ OVERRIDE_UNDEFINED,
+ OVERRIDE_MOVE_TASK_TO_BACK,
+ OVERRIDE_FINISH_AND_REMOVE_TASK,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @interface OverrideBehavior {
+ }
+
+ /**
+ * @return Override type of this callback.
+ */
+ @OverrideBehavior
+ default int overrideBehavior() {
+ return OVERRIDE_UNDEFINED;
+ }
+}
diff --git a/core/java/android/window/WindowOnBackInvokedDispatcher.java b/core/java/android/window/WindowOnBackInvokedDispatcher.java
index c9d458f..0ea4bb4 100644
--- a/core/java/android/window/WindowOnBackInvokedDispatcher.java
+++ b/core/java/android/window/WindowOnBackInvokedDispatcher.java
@@ -16,6 +16,9 @@
package android.window;
+import static android.window.SystemOverrideOnBackInvokedCallback.OVERRIDE_UNDEFINED;
+
+import static com.android.window.flags.Flags.predictiveBackSystemOverrideCallback;
import static com.android.window.flags.Flags.predictiveBackPrioritySystemNavigationObserver;
import static com.android.window.flags.Flags.predictiveBackTimestampApi;
@@ -201,6 +204,15 @@
mImeDispatcher.registerOnBackInvokedCallback(priority, callback);
return;
}
+ if (predictiveBackPrioritySystemNavigationObserver()
+ && predictiveBackSystemOverrideCallback()) {
+ if (priority == PRIORITY_SYSTEM_NAVIGATION_OBSERVER
+ && callback instanceof SystemOverrideOnBackInvokedCallback) {
+ Log.e(TAG, "System override callbacks cannot be registered to "
+ + "NAVIGATION_OBSERVER");
+ return;
+ }
+ }
if (predictiveBackPrioritySystemNavigationObserver()) {
if (priority == PRIORITY_SYSTEM_NAVIGATION_OBSERVER) {
registerSystemNavigationObserverCallback(callback);
@@ -365,7 +377,8 @@
public void tryInvokeSystemNavigationObserverCallback() {
OnBackInvokedCallback topCallback = getTopCallback();
Integer callbackPriority = mAllCallbacks.getOrDefault(topCallback, null);
- if (callbackPriority != null && callbackPriority == PRIORITY_SYSTEM) {
+ final boolean isSystemOverride = topCallback instanceof SystemOverrideOnBackInvokedCallback;
+ if ((callbackPriority != null && callbackPriority == PRIORITY_SYSTEM) || isSystemOverride) {
invokeSystemNavigationObserverCallback();
}
}
@@ -384,14 +397,22 @@
OnBackInvokedCallbackInfo callbackInfo = null;
if (callback != null) {
int priority = mAllCallbacks.get(callback);
+ int overrideAnimation = OVERRIDE_UNDEFINED;
+ if (callback instanceof SystemOverrideOnBackInvokedCallback) {
+ overrideAnimation = ((SystemOverrideOnBackInvokedCallback) callback)
+ .overrideBehavior();
+ }
+ final boolean isSystemCallback = priority == PRIORITY_SYSTEM
+ || overrideAnimation != OVERRIDE_UNDEFINED;
final IOnBackInvokedCallback iCallback = new OnBackInvokedCallbackWrapper(callback,
mTouchTracker, mProgressAnimator, mHandler, this::callOnKeyPreIme,
this::invokeSystemNavigationObserverCallback,
- /*isSystemCallback*/ priority == PRIORITY_SYSTEM);
+ isSystemCallback /*isSystemCallback*/);
callbackInfo = new OnBackInvokedCallbackInfo(
iCallback,
priority,
- callback instanceof OnBackAnimationCallback);
+ callback instanceof OnBackAnimationCallback,
+ overrideAnimation);
}
mWindowSession.setOnBackInvokedCallbackInfo(mWindow, callbackInfo);
} catch (RemoteException e) {
diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig
index 3a03508..11f6849 100644
--- a/core/java/android/window/flags/windowing_frontend.aconfig
+++ b/core/java/android/window/flags/windowing_frontend.aconfig
@@ -49,6 +49,17 @@
}
flag {
+ name: "respect_animation_clip"
+ namespace: "windowing_frontend"
+ description: "Fix missing clip transformation of animation"
+ bug: "376601866"
+ is_fixed_read_only: true
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "edge_to_edge_by_default"
namespace: "windowing_frontend"
description: "Make app go edge-to-edge by default when targeting SDK 35 or greater"
@@ -350,6 +361,17 @@
}
flag {
+ name: "defer_predictive_animation_if_no_snapshot"
+ namespace: "windowing_frontend"
+ description: "If no snapshot for previous window, start animation until the client has draw."
+ bug: "374621014"
+ is_fixed_read_only: true
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "disallow_app_progress_embedded_window"
namespace: "windowing_frontend"
description: "Pilfer pointers when app transfer input gesture to embedded window."
@@ -358,4 +380,12 @@
metadata {
purpose: PURPOSE_BUGFIX
}
+}
+
+flag {
+ name: "predictive_back_system_override_callback"
+ namespace: "windowing_frontend"
+ description: "Provide pre-make predictive back API extension"
+ is_fixed_read_only: true
+ bug: "362938401"
}
\ No newline at end of file
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index 2bb6e71..2541258 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -76,7 +76,6 @@
"android_content_res_ApkAssets.cpp",
"android_os_SystemClock.cpp",
"android_os_SystemProperties.cpp",
- "android_os_Trace.cpp",
"android_text_AndroidCharacter.cpp",
"android_util_AssetManager.cpp",
"android_util_EventLog.cpp",
@@ -104,10 +103,6 @@
"system/media/private/camera/include",
],
- shared_libs: [
- "libtracing_perfetto",
- ],
-
static_libs: [
"libziparchive_for_incfs",
"libguiflags",
@@ -190,6 +185,7 @@
"android_os_ServiceManagerNative.cpp",
"android_os_SharedMemory.cpp",
"android_os_storage_StorageManager.cpp",
+ "android_os_Trace.cpp",
"android_os_UEventObserver.cpp",
"android_os_incremental_IncrementalManager.cpp",
"android_net_LocalSocketImpl.cpp",
diff --git a/core/jni/platform/host/HostRuntime.cpp b/core/jni/platform/host/HostRuntime.cpp
index 3747299..7fca117 100644
--- a/core/jni/platform/host/HostRuntime.cpp
+++ b/core/jni/platform/host/HostRuntime.cpp
@@ -87,7 +87,6 @@
extern int register_android_os_Parcel(JNIEnv* env);
extern int register_android_os_SystemClock(JNIEnv* env);
extern int register_android_os_SystemProperties(JNIEnv* env);
-extern int register_android_os_Trace(JNIEnv* env);
extern int register_android_text_AndroidCharacter(JNIEnv* env);
extern int register_android_util_EventLog(JNIEnv* env);
extern int register_android_util_Log(JNIEnv* env);
@@ -133,7 +132,6 @@
#endif
{"android.os.SystemClock", REG_JNI(register_android_os_SystemClock)},
{"android.os.SystemProperties", REG_JNI(register_android_os_SystemProperties)},
- {"android.os.Trace", REG_JNI(register_android_os_Trace)},
{"android.text.AndroidCharacter", REG_JNI(register_android_text_AndroidCharacter)},
{"android.util.EventLog", REG_JNI(register_android_util_EventLog)},
{"android.util.Log", REG_JNI(register_android_util_Log)},
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index ac9bb93..7402a2f 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -3087,6 +3087,12 @@
<!-- Whether UI for multi user should be shown -->
<bool name="config_enableMultiUserUI">false</bool>
+ <!-- Indicates the boot strategy in Headless System User Mode (HSUM)
+ This config has no effect if the device is not in HSUM.
+ 0 (Default) : boot to the previous foreground user if there is one, otherwise the first switchable user.
+ 1 : boot to the first switchable full user for initial boot (unprovisioned device), else to the headless system user, i.e. user 0. -->
+ <integer name="config_hsumBootStrategy">0</integer>
+
<!-- Whether to boot system with the headless system user, i.e. user 0. If set to true,
system will be booted with the headless system user, or user 0. It has no effect if device
is not in Headless System User Mode (HSUM). -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 515ebd5..b7cb198 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -312,6 +312,7 @@
<java-symbol type="bool" name="config_disableLockscreenByDefault" />
<java-symbol type="bool" name="config_enableBurnInProtection" />
<java-symbol type="bool" name="config_hotswapCapable" />
+ <java-symbol type="integer" name="config_hsumBootStrategy" />
<java-symbol type="bool" name="config_mms_content_disposition_support" />
<java-symbol type="bool" name="config_networkSamplingWakesDevice" />
<java-symbol type="bool" name="config_showMenuShortcutsWhenKeyboardPresent" />
diff --git a/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java b/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java
index c2d8f91..da1fffa 100644
--- a/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java
+++ b/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java
@@ -17,6 +17,9 @@
package android.app;
import static android.app.PropertyInvalidatedCache.NONCE_UNSET;
+import static android.app.PropertyInvalidatedCache.MODULE_BLUETOOTH;
+import static android.app.PropertyInvalidatedCache.MODULE_SYSTEM;
+import static android.app.PropertyInvalidatedCache.MODULE_TEST;
import static android.app.PropertyInvalidatedCache.NonceStore.INVALID_NONCE_INDEX;
import static com.android.internal.os.Flags.FLAG_APPLICATION_SHARED_MEMORY_ENABLED;
@@ -27,6 +30,8 @@
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
+import android.app.PropertyInvalidatedCache.Args;
+import android.annotation.SuppressLint;
import com.android.internal.os.ApplicationSharedMemory;
import android.platform.test.annotations.IgnoreUnderRavenwood;
@@ -57,7 +62,7 @@
DeviceFlagsValueProvider.createCheckFlagsRule();
// Configuration for creating caches
- private static final String MODULE = PropertyInvalidatedCache.MODULE_TEST;
+ private static final String MODULE = MODULE_TEST;
private static final String API = "testApi";
// This class is a proxy for binder calls. It contains a counter that increments
@@ -245,6 +250,12 @@
mQuery = query;
}
+ // Create a cache from the args. The name of the cache is the api.
+ TestCache(Args args, TestQuery query) {
+ super(args, args.mApi(), query);
+ mQuery = query;
+ }
+
public int getRecomputeCount() {
return mQuery.getRecomputeCount();
}
@@ -374,14 +385,11 @@
@Test
public void testPropertyNames() {
String n1;
- n1 = PropertyInvalidatedCache.createPropertyName(
- PropertyInvalidatedCache.MODULE_SYSTEM, "getPackageInfo");
+ n1 = PropertyInvalidatedCache.createPropertyName(MODULE_SYSTEM, "getPackageInfo");
assertEquals(n1, "cache_key.system_server.get_package_info");
- n1 = PropertyInvalidatedCache.createPropertyName(
- PropertyInvalidatedCache.MODULE_SYSTEM, "get_package_info");
+ n1 = PropertyInvalidatedCache.createPropertyName(MODULE_SYSTEM, "get_package_info");
assertEquals(n1, "cache_key.system_server.get_package_info");
- n1 = PropertyInvalidatedCache.createPropertyName(
- PropertyInvalidatedCache.MODULE_BLUETOOTH, "getState");
+ n1 = PropertyInvalidatedCache.createPropertyName(MODULE_BLUETOOTH, "getState");
assertEquals(n1, "cache_key.bluetooth.get_state");
}
@@ -391,7 +399,7 @@
reason = "SystemProperties doesn't have permission check")
public void testPermissionFailure() {
// Create a cache that will write a system nonce.
- TestCache sysCache = new TestCache(PropertyInvalidatedCache.MODULE_SYSTEM, "mode1");
+ TestCache sysCache = new TestCache(MODULE_SYSTEM, "mode1");
try {
// Invalidate the cache, which writes the system property. There must be a permission
// failure.
@@ -407,7 +415,7 @@
@Test
public void testTestMode() {
// Create a cache that will write a system nonce.
- TestCache sysCache = new TestCache(PropertyInvalidatedCache.MODULE_SYSTEM, "mode1");
+ TestCache sysCache = new TestCache(MODULE_SYSTEM, "mode1");
sysCache.testPropertyName();
// Invalidate the cache. This must succeed because the property has been marked for
@@ -416,7 +424,7 @@
// Create a cache that uses MODULE_TEST. Invalidation succeeds whether or not the
// property is tagged as being tested.
- TestCache testCache = new TestCache(PropertyInvalidatedCache.MODULE_TEST, "mode2");
+ TestCache testCache = new TestCache(MODULE_TEST, "mode2");
testCache.invalidateCache();
testCache.testPropertyName();
testCache.invalidateCache();
@@ -432,7 +440,7 @@
// The expected exception.
}
// Configuring a property for testing must fail if test mode is false.
- TestCache cache2 = new TestCache(PropertyInvalidatedCache.MODULE_SYSTEM, "mode3");
+ TestCache cache2 = new TestCache(MODULE_SYSTEM, "mode3");
try {
cache2.testPropertyName();
fail("expected an IllegalStateException");
@@ -444,6 +452,34 @@
PropertyInvalidatedCache.setTestMode(true);
}
+ // Test the Args-style constructor.
+ @Test
+ public void testArgsConstructor() {
+ // Create a cache with a maximum of four entries.
+ TestCache cache = new TestCache(new Args(MODULE_TEST).api("init1").maxEntries(4),
+ new TestQuery());
+
+ cache.invalidateCache();
+ for (int i = 1; i <= 4; i++) {
+ assertEquals("foo" + i, cache.query(i));
+ assertEquals(i, cache.getRecomputeCount());
+ }
+ // Everything is in the cache. The recompute count must not increase.
+ for (int i = 1; i <= 4; i++) {
+ assertEquals("foo" + i, cache.query(i));
+ assertEquals(4, cache.getRecomputeCount());
+ }
+ // Overflow the max entries. The recompute count increases by one.
+ assertEquals("foo5", cache.query(5));
+ assertEquals(5, cache.getRecomputeCount());
+ // The oldest entry (1) has been evicted. Iterating through the first four entries will
+ // sequentially evict them all because the loop is proceeding oldest to newest.
+ for (int i = 1; i <= 4; i++) {
+ assertEquals("foo" + i, cache.query(i));
+ assertEquals(5+i, cache.getRecomputeCount());
+ }
+ }
+
// Verify the behavior of shared memory nonce storage. This does not directly test the cache
// storing nonces in shared memory.
@RequiresFlagsEnabled(FLAG_APPLICATION_SHARED_MEMORY_ENABLED)
@@ -495,4 +531,43 @@
shmem.close();
}
+
+ // Verify that an invalid module causes an exception.
+ private void testInvalidModule(String module) {
+ try {
+ @SuppressLint("UnusedVariable")
+ Args arg = new Args(module);
+ fail("expected an invalid module exception: module=" + module);
+ } catch (IllegalArgumentException e) {
+ // Expected exception.
+ }
+ }
+
+ // Test various instantiation errors. The good path is tested in other methods.
+ @Test
+ public void testArgumentErrors() {
+ // Verify that an illegal module throws an exception.
+ testInvalidModule(MODULE_SYSTEM.substring(0, MODULE_SYSTEM.length() - 1));
+ testInvalidModule(MODULE_SYSTEM + "x");
+ testInvalidModule("mymodule");
+
+ // Verify that a negative max entries throws.
+ Args arg = new Args(MODULE_SYSTEM);
+ try {
+ arg.maxEntries(0);
+ fail("expected an invalid maxEntries exception");
+ } catch (IllegalArgumentException e) {
+ // Expected exception.
+ }
+
+ // Verify that creating a cache with an invalid property string throws.
+ try {
+ final String badKey = "cache_key.volume_list";
+ @SuppressLint("UnusedVariable")
+ var cache = new PropertyInvalidatedCache<Integer, Void>(4, badKey);
+ fail("expected bad property exception: prop=" + badKey);
+ } catch (IllegalArgumentException e) {
+ // Expected exception.
+ }
+ }
}
diff --git a/libs/WindowManager/Shell/res/drawable/desktop_mode_ic_handle_menu_change_aspect_ratio.xml b/libs/WindowManager/Shell/res/drawable/desktop_mode_ic_handle_menu_change_aspect_ratio.xml
new file mode 100644
index 0000000..4442e9d
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/desktop_mode_ic_handle_menu_change_aspect_ratio.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="20dp"
+ android:height="20dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:fillColor="@color/compat_controls_text"
+ android:pathData="M19,12h-2v3h-3v2h5v-5zM7,9h3L10,7L5,7v5h2L7,9zM21,3L3,3c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h18c1.1,0 2,-0.9 2,-2L23,5c0,-1.1 -0.9,-2 -2,-2zM21,19.01L3,19.01L3,4.99h18v14.02z"/>
+</vector>
diff --git a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml
index 5609663..f90e165 100644
--- a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml
+++ b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml
@@ -157,6 +157,14 @@
android:drawableStart="@drawable/desktop_mode_ic_handle_menu_manage_windows"
android:drawableTint="?androidprv:attr/materialColorOnSurface"
style="@style/DesktopModeHandleMenuActionButton" />
+
+ <Button
+ android:id="@+id/change_aspect_ratio_button"
+ android:contentDescription="@string/change_aspect_ratio_text"
+ android:text="@string/change_aspect_ratio_text"
+ android:drawableStart="@drawable/desktop_mode_ic_handle_menu_change_aspect_ratio"
+ android:drawableTint="?androidprv:attr/materialColorOnSurface"
+ style="@style/DesktopModeHandleMenuActionButton" />
</LinearLayout>
<LinearLayout
diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index 34f950c..249e9a2 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -523,8 +523,9 @@
<dimen name="desktop_mode_handle_menu_width">216dp</dimen>
<!-- The maximum height of the handle menu in desktop mode. Three pills at 52dp each,
- additional actions pill 156dp, plus 2dp spacing between them plus 4dp top padding. -->
- <dimen name="desktop_mode_handle_menu_height">322dp</dimen>
+ additional actions pill 208dp, plus 2dp spacing between them plus 4dp top padding.
+ 52*3 + 52*4 + (4-1)*2 + 4 = 374 -->
+ <dimen name="desktop_mode_handle_menu_height">374dp</dimen>
<!-- The elevation set on the handle menu pills. -->
<dimen name="desktop_mode_handle_menu_pill_elevation">1dp</dimen>
@@ -547,6 +548,9 @@
<!-- The height of the handle menu's "Open in browser" pill in desktop mode. -->
<dimen name="desktop_mode_handle_menu_open_in_browser_pill_height">52dp</dimen>
+ <!-- The height of the handle menu's "Change aspect ratio" pill in desktop mode. -->
+ <dimen name="desktop_mode_handle_menu_change_aspect_ratio_height">52dp</dimen>
+
<!-- The margin between pills of the handle menu in desktop mode. -->
<dimen name="desktop_mode_handle_menu_pill_spacing_margin">2dp</dimen>
diff --git a/libs/WindowManager/Shell/res/values/strings.xml b/libs/WindowManager/Shell/res/values/strings.xml
index 52585d4..8f1ef6c 100644
--- a/libs/WindowManager/Shell/res/values/strings.xml
+++ b/libs/WindowManager/Shell/res/values/strings.xml
@@ -305,6 +305,8 @@
<string name="new_window_text">New Window</string>
<!-- Accessibility text for the handle menu new window button [CHAR LIMIT=NONE] -->
<string name="manage_windows_text">Manage Windows</string>
+ <!-- Accessibility text for the handle menu change aspect ratio button [CHAR LIMIT=NONE] -->
+ <string name="change_aspect_ratio_text">Change aspect ratio</string>
<!-- Accessibility text for the handle menu close button [CHAR LIMIT=NONE] -->
<string name="close_text">Close</string>
<!-- Accessibility text for the handle menu close menu button [CHAR LIMIT=NONE] -->
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
index f296c71..b9a3050 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
@@ -29,6 +29,7 @@
import static com.android.internal.jank.InteractionJankMonitor.CUJ_PREDICTIVE_BACK_HOME;
import static com.android.window.flags.Flags.migratePredictiveBackTransition;
import static com.android.window.flags.Flags.predictiveBackSystemAnims;
+import static com.android.window.flags.Flags.unifyBackNavigationTransition;
import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BACK_PREVIEW;
import static com.android.wm.shell.shared.ShellSharedConstants.KEY_EXTRA_SHELL_BACK_ANIMATION;
@@ -1279,6 +1280,13 @@
if (transition == mClosePrepareTransition && aborted) {
mClosePrepareTransition = null;
applyFinishOpenTransition();
+ } else if (!aborted && unifyBackNavigationTransition()) {
+ // Since the closing target participates in the predictive back transition, the
+ // merged transition must be applied with the first transition to ensure a seamless
+ // animation.
+ if (mFinishOpenTransaction != null && finishTransaction != null) {
+ mFinishOpenTransaction.merge(finishTransaction);
+ }
}
}
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/compatui/CompatUIController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
index 6146ecd..886330f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
@@ -688,6 +688,12 @@
private void launchUserAspectRatioSettings(
@NonNull TaskInfo taskInfo, @NonNull ShellTaskOrganizer.TaskListener taskListener) {
+ launchUserAspectRatioSettings(mContext, taskInfo);
+ }
+
+ /** Launch the user aspect ratio settings for the package of the given task. */
+ public static void launchUserAspectRatioSettings(
+ @NonNull Context context, @NonNull TaskInfo taskInfo) {
final Intent intent = new Intent(Settings.ACTION_MANAGE_USER_ASPECT_RATIO_SETTINGS);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
@@ -697,7 +703,7 @@
intent.setData(packageUri);
}
final UserHandle userHandle = UserHandle.of(taskInfo.userId);
- mContext.startActivityAsUser(intent, userHandle);
+ context.startActivityAsUser(intent, userHandle);
}
@VisibleForTesting
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultSurfaceAnimator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultSurfaceAnimator.java
index a1a9ca9..4ea4613 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultSurfaceAnimator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultSurfaceAnimator.java
@@ -136,6 +136,7 @@
@NonNull final Animation mAnim;
@Nullable final Point mPosition;
@Nullable final Rect mClipRect;
+ @Nullable private final Rect mAnimClipRect;
final float mCornerRadius;
final boolean mIsActivity;
@@ -147,6 +148,7 @@
mPosition = (position != null && (position.x != 0 || position.y != 0))
? position : null;
mClipRect = (clipRect != null && !clipRect.isEmpty()) ? clipRect : null;
+ mAnimClipRect = mClipRect != null ? new Rect() : null;
mCornerRadius = cornerRadius;
mIsActivity = isActivity;
}
@@ -169,18 +171,26 @@
t.setAlpha(leash, transformation.getAlpha());
if (mClipRect != null) {
- Rect clipRect = mClipRect;
+ boolean needCrop = false;
+ mAnimClipRect.set(mClipRect);
+ if (transformation.hasClipRect()
+ && com.android.window.flags.Flags.respectAnimationClip()) {
+ mAnimClipRect.intersectUnchecked(transformation.getClipRect());
+ needCrop = true;
+ }
final Insets extensionInsets = Insets.min(transformation.getInsets(), Insets.NONE);
if (!extensionInsets.equals(Insets.NONE)) {
// Clip out any overflowing edge extension.
- clipRect = new Rect(mClipRect);
- clipRect.inset(extensionInsets);
- t.setCrop(leash, clipRect);
+ mAnimClipRect.inset(extensionInsets);
+ needCrop = true;
}
if (mCornerRadius > 0 && mAnim.hasRoundedCorners()) {
// Rounded corner can only be applied if a crop is set.
- t.setCrop(leash, clipRect);
t.setCornerRadius(leash, mCornerRadius);
+ needCrop = true;
+ }
+ if (needCrop) {
+ t.setCrop(leash, mAnimClipRect);
}
}
}
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 67775f7..e1683f3 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
@@ -103,6 +103,7 @@
import com.android.wm.shell.common.MultiInstanceHelper;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.compatui.CompatUIController;
import com.android.wm.shell.desktopmode.DesktopActivityOrientationChangeHandler;
import com.android.wm.shell.desktopmode.DesktopModeEventLogger;
import com.android.wm.shell.desktopmode.DesktopModeVisualIndicator;
@@ -1572,6 +1573,10 @@
onManageWindows(windowDecoration);
return Unit.INSTANCE;
});
+ windowDecoration.setOnChangeAspectRatioClickListener(() -> {
+ CompatUIController.launchUserAspectRatioSettings(mContext, taskInfo);
+ return Unit.INSTANCE;
+ });
windowDecoration.setCaptionListeners(
touchEventListener, touchEventListener, touchEventListener, touchEventListener);
windowDecoration.setExclusionRegionListener(mExclusionRegionListener);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
index af7cd05..d97632a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
@@ -154,6 +154,7 @@
private Function0<Unit> mOnToSplitscreenClickListener;
private Function0<Unit> mOnNewWindowClickListener;
private Function0<Unit> mOnManageWindowsClickListener;
+ private Function0<Unit> mOnChangeAspectRatioClickListener;
private DragPositioningCallback mDragPositioningCallback;
private DragResizeInputListener mDragResizeListener;
private Runnable mCurrentViewHostRunnable = null;
@@ -364,6 +365,11 @@
mOnManageWindowsClickListener = listener;
}
+ /** Registers a listener to be called when the aspect ratio action is triggered. */
+ void setOnChangeAspectRatioClickListener(Function0<Unit> listener) {
+ mOnChangeAspectRatioClickListener = listener;
+ }
+
void setCaptionListeners(
View.OnClickListener onCaptionButtonClickListener,
View.OnTouchListener onCaptionTouchListener,
@@ -1358,6 +1364,8 @@
&& Flags.enableDesktopWindowingMultiInstanceFeatures();
final boolean shouldShowManageWindowsButton = supportsMultiInstance
&& mMinimumInstancesFound;
+ final boolean shouldShowChangeAspectRatioButton = HandleMenu.Companion
+ .shouldShowChangeAspectRatioButton(mTaskInfo);
final boolean inDesktopImmersive = mDesktopRepository
.isTaskInFullImmersiveState(mTaskInfo.taskId);
mHandleMenu = mHandleMenuFactory.create(
@@ -1370,6 +1378,7 @@
canEnterDesktopMode(mContext),
supportsMultiInstance,
shouldShowManageWindowsButton,
+ shouldShowChangeAspectRatioButton,
getBrowserLink(),
mResult.mCaptionWidth,
mResult.mCaptionHeight,
@@ -1390,6 +1399,7 @@
/* onToSplitScreenClickListener= */ mOnToSplitscreenClickListener,
/* onNewWindowClickListener= */ mOnNewWindowClickListener,
/* onManageWindowsClickListener= */ mOnManageWindowsClickListener,
+ /* onAspectRatioSettingsClickListener= */ mOnChangeAspectRatioClickListener,
/* openInBrowserClickListener= */ (intent) -> {
mOpenInBrowserClickListener.accept(intent);
onCapturedLinkExpired();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt
index 93bd929..2edc380 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt
@@ -19,6 +19,7 @@
import android.annotation.DimenRes
import android.annotation.SuppressLint
import android.app.ActivityManager.RunningTaskInfo
+import android.app.WindowConfiguration
import android.content.Context
import android.content.Intent
import android.content.res.ColorStateList
@@ -71,6 +72,7 @@
private val shouldShowWindowingPill: Boolean,
private val shouldShowNewWindowButton: Boolean,
private val shouldShowManageWindowsButton: Boolean,
+ private val shouldShowChangeAspectRatioButton: Boolean,
private val openInBrowserIntent: Intent?,
private val captionWidth: Int,
private val captionHeight: Int,
@@ -111,6 +113,10 @@
private val shouldShowBrowserPill: Boolean
get() = openInBrowserIntent != null
+ private val shouldShowMoreActionsPill: Boolean
+ get() = SHOULD_SHOW_SCREENSHOT_BUTTON || shouldShowNewWindowButton ||
+ shouldShowManageWindowsButton || shouldShowChangeAspectRatioButton
+
init {
updateHandleMenuPillPositions(captionX, captionY)
}
@@ -121,6 +127,7 @@
onToSplitScreenClickListener: () -> Unit,
onNewWindowClickListener: () -> Unit,
onManageWindowsClickListener: () -> Unit,
+ onChangeAspectRatioClickListener: () -> Unit,
openInBrowserClickListener: (Intent) -> Unit,
onOpenByDefaultClickListener: () -> Unit,
onCloseMenuClickListener: () -> Unit,
@@ -138,6 +145,7 @@
onToSplitScreenClickListener = onToSplitScreenClickListener,
onNewWindowClickListener = onNewWindowClickListener,
onManageWindowsClickListener = onManageWindowsClickListener,
+ onChangeAspectRatioClickListener = onChangeAspectRatioClickListener,
openInBrowserClickListener = openInBrowserClickListener,
onOpenByDefaultClickListener = onOpenByDefaultClickListener,
onCloseMenuClickListener = onCloseMenuClickListener,
@@ -158,6 +166,7 @@
onToSplitScreenClickListener: () -> Unit,
onNewWindowClickListener: () -> Unit,
onManageWindowsClickListener: () -> Unit,
+ onChangeAspectRatioClickListener: () -> Unit,
openInBrowserClickListener: (Intent) -> Unit,
onOpenByDefaultClickListener: () -> Unit,
onCloseMenuClickListener: () -> Unit,
@@ -171,14 +180,16 @@
shouldShowWindowingPill = shouldShowWindowingPill,
shouldShowBrowserPill = shouldShowBrowserPill,
shouldShowNewWindowButton = shouldShowNewWindowButton,
- shouldShowManageWindowsButton = shouldShowManageWindowsButton
+ shouldShowManageWindowsButton = shouldShowManageWindowsButton,
+ shouldShowChangeAspectRatioButton = shouldShowChangeAspectRatioButton
).apply {
- bind(taskInfo, appIconBitmap, appName)
+ bind(taskInfo, appIconBitmap, appName, shouldShowMoreActionsPill)
this.onToDesktopClickListener = onToDesktopClickListener
this.onToFullscreenClickListener = onToFullscreenClickListener
this.onToSplitScreenClickListener = onToSplitScreenClickListener
this.onNewWindowClickListener = onNewWindowClickListener
this.onManageWindowsClickListener = onManageWindowsClickListener
+ this.onChangeAspectRatioClickListener = onChangeAspectRatioClickListener
this.onOpenInBrowserClickListener = {
openInBrowserClickListener.invoke(openInBrowserIntent!!)
}
@@ -392,8 +403,11 @@
R.dimen.desktop_mode_handle_menu_manage_windows_height
)
}
- if (!SHOULD_SHOW_SCREENSHOT_BUTTON && !shouldShowNewWindowButton
- && !shouldShowManageWindowsButton) {
+ if (!shouldShowChangeAspectRatioButton) {
+ menuHeight -= loadDimensionPixelSize(
+ R.dimen.desktop_mode_handle_menu_change_aspect_ratio_height)
+ }
+ if (!shouldShowMoreActionsPill) {
menuHeight -= pillTopMargin
}
if (!shouldShowBrowserPill) {
@@ -427,7 +441,8 @@
private val shouldShowWindowingPill: Boolean,
private val shouldShowBrowserPill: Boolean,
private val shouldShowNewWindowButton: Boolean,
- private val shouldShowManageWindowsButton: Boolean
+ private val shouldShowManageWindowsButton: Boolean,
+ private val shouldShowChangeAspectRatioButton: Boolean
) {
val rootView = LayoutInflater.from(context)
.inflate(R.layout.desktop_mode_window_decor_handle_menu, null /* root */) as View
@@ -454,6 +469,8 @@
private val newWindowBtn = moreActionsPill.requireViewById<Button>(R.id.new_window_button)
private val manageWindowBtn = moreActionsPill
.requireViewById<Button>(R.id.manage_windows_button)
+ private val changeAspectRatioBtn = moreActionsPill
+ .requireViewById<Button>(R.id.change_aspect_ratio_button)
// Open in Browser Pill.
private val openInBrowserPill = rootView.requireViewById<View>(R.id.open_in_browser_pill)
@@ -472,6 +489,7 @@
var onToSplitScreenClickListener: (() -> Unit)? = null
var onNewWindowClickListener: (() -> Unit)? = null
var onManageWindowsClickListener: (() -> Unit)? = null
+ var onChangeAspectRatioClickListener: (() -> Unit)? = null
var onOpenInBrowserClickListener: (() -> Unit)? = null
var onOpenByDefaultClickListener: (() -> Unit)? = null
var onCloseMenuClickListener: (() -> Unit)? = null
@@ -488,6 +506,7 @@
collapseMenuButton.setOnClickListener { onCloseMenuClickListener?.invoke() }
newWindowBtn.setOnClickListener { onNewWindowClickListener?.invoke() }
manageWindowBtn.setOnClickListener { onManageWindowsClickListener?.invoke() }
+ changeAspectRatioBtn.setOnClickListener { onChangeAspectRatioClickListener?.invoke() }
rootView.setOnTouchListener { _, event ->
if (event.actionMasked == ACTION_OUTSIDE) {
@@ -499,7 +518,12 @@
}
/** Binds the menu views to the new data. */
- fun bind(taskInfo: RunningTaskInfo, appIconBitmap: Bitmap?, appName: CharSequence?) {
+ fun bind(
+ taskInfo: RunningTaskInfo,
+ appIconBitmap: Bitmap?,
+ appName: CharSequence?,
+ shouldShowMoreActionsPill: Boolean
+ ) {
this.taskInfo = taskInfo
this.style = calculateMenuStyle(taskInfo)
@@ -507,7 +531,10 @@
if (shouldShowWindowingPill) {
bindWindowingPill(style)
}
- bindMoreActionsPill(style)
+ moreActionsPill.isGone = !shouldShowMoreActionsPill
+ if (shouldShowMoreActionsPill) {
+ bindMoreActionsPill(style)
+ }
bindOpenInBrowserPill(style)
}
@@ -616,27 +643,20 @@
}
private fun bindMoreActionsPill(style: MenuStyle) {
- moreActionsPill.apply {
- isGone = !shouldShowNewWindowButton && !SHOULD_SHOW_SCREENSHOT_BUTTON
- && !shouldShowManageWindowsButton
- }
- screenshotBtn.apply {
- isGone = !SHOULD_SHOW_SCREENSHOT_BUTTON
- background.setTint(style.backgroundColor)
- setTextColor(style.textColor)
- compoundDrawableTintList = ColorStateList.valueOf(style.textColor)
- }
- newWindowBtn.apply {
- isGone = !shouldShowNewWindowButton
- background.setTint(style.backgroundColor)
- setTextColor(style.textColor)
- compoundDrawableTintList = ColorStateList.valueOf(style.textColor)
- }
- manageWindowBtn.apply {
- isGone = !shouldShowManageWindowsButton
- background.setTint(style.backgroundColor)
- setTextColor(style.textColor)
- compoundDrawableTintList = ColorStateList.valueOf(style.textColor)
+ arrayOf(
+ screenshotBtn to SHOULD_SHOW_SCREENSHOT_BUTTON,
+ newWindowBtn to shouldShowNewWindowButton,
+ manageWindowBtn to shouldShowManageWindowsButton,
+ changeAspectRatioBtn to shouldShowChangeAspectRatioButton,
+ ).forEach {
+ val button = it.first
+ val shouldShow = it.second
+ button.apply {
+ isGone = !shouldShow
+ background.setTint(style.backgroundColor)
+ setTextColor(style.textColor)
+ compoundDrawableTintList = ColorStateList.valueOf(style.textColor)
+ }
}
}
@@ -664,6 +684,14 @@
companion object {
private const val TAG = "HandleMenu"
private const val SHOULD_SHOW_SCREENSHOT_BUTTON = false
+
+ /**
+ * Returns whether the aspect ratio button should be shown for the task. It usually means
+ * that the task is on a large screen with ignore-orientation-request.
+ */
+ fun shouldShowChangeAspectRatioButton(taskInfo: RunningTaskInfo): Boolean =
+ taskInfo.appCompatTaskInfo.eligibleForUserAspectRatioButton() &&
+ taskInfo.windowingMode == WindowConfiguration.WINDOWING_MODE_FULLSCREEN
}
}
@@ -679,6 +707,7 @@
shouldShowWindowingPill: Boolean,
shouldShowNewWindowButton: Boolean,
shouldShowManageWindowsButton: Boolean,
+ shouldShowChangeAspectRatioButton: Boolean,
openInBrowserIntent: Intent?,
captionWidth: Int,
captionHeight: Int,
@@ -699,6 +728,7 @@
shouldShowWindowingPill: Boolean,
shouldShowNewWindowButton: Boolean,
shouldShowManageWindowsButton: Boolean,
+ shouldShowChangeAspectRatioButton: Boolean,
openInBrowserIntent: Intent?,
captionWidth: Int,
captionHeight: Int,
@@ -715,6 +745,7 @@
shouldShowWindowingPill,
shouldShowNewWindowButton,
shouldShowManageWindowsButton,
+ shouldShowChangeAspectRatioButton,
openInBrowserIntent,
captionWidth,
captionHeight,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
index 86ec675..41f57ae 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
@@ -262,8 +262,8 @@
doReturn(defaultDisplay).when(mMockDisplayController).getDisplay(Display.DEFAULT_DISPLAY);
doReturn(mInsetsState).when(mMockDisplayController).getInsetsState(anyInt());
when(mMockHandleMenuFactory.create(any(), any(), anyInt(), any(), any(), any(),
- anyBoolean(), anyBoolean(), anyBoolean(), any(), anyInt(), anyInt(), anyInt(),
- anyInt()))
+ anyBoolean(), anyBoolean(), anyBoolean(), anyBoolean(), any(), anyInt(), anyInt(),
+ anyInt(), anyInt()))
.thenReturn(mMockHandleMenu);
when(mMockMultiInstanceHelper.supportsMultiInstanceSplit(any())).thenReturn(false);
when(mMockAppHeaderViewHolderFactory.create(any(), any(), any(), any(), any(), any(), any(),
@@ -1178,6 +1178,7 @@
any(),
any(),
any(),
+ any(),
openInBrowserCaptor.capture(),
any(),
any(),
@@ -1208,6 +1209,7 @@
any(),
any(),
any(),
+ any(),
openInBrowserCaptor.capture(),
any(),
any(),
@@ -1263,6 +1265,7 @@
any(),
any(),
any(),
+ any(),
closeClickListener.capture(),
any(),
anyBoolean()
@@ -1294,6 +1297,7 @@
any(),
any(),
any(),
+ any(),
/* forceShowSystemBars= */ eq(true)
);
}
@@ -1438,7 +1442,7 @@
private void verifyHandleMenuCreated(@Nullable Uri uri) {
verify(mMockHandleMenuFactory).create(any(), any(), anyInt(), any(), any(),
- any(), anyBoolean(), anyBoolean(), anyBoolean(),
+ any(), anyBoolean(), anyBoolean(), anyBoolean(), anyBoolean(),
argThat(intent -> (uri == null && intent == null) || intent.getData().equals(uri)),
anyInt(), anyInt(), anyInt(), anyInt());
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt
index 9544fa8..ade17c6 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt
@@ -266,6 +266,7 @@
WindowManagerWrapper(mockWindowManager),
layoutId, appIcon, appName, splitScreenController, shouldShowWindowingPill = true,
shouldShowNewWindowButton = true, shouldShowManageWindowsButton = false,
+ shouldShowChangeAspectRatioButton = false,
null /* openInBrowserLink */, captionWidth = HANDLE_WIDTH, captionHeight = 50,
captionX = captionX,
captionY = 0,
@@ -276,6 +277,7 @@
onToSplitScreenClickListener = mock(),
onNewWindowClickListener = mock(),
onManageWindowsClickListener = mock(),
+ onChangeAspectRatioClickListener = mock(),
openInBrowserClickListener = mock(),
onOpenByDefaultClickListener = mock(),
onCloseMenuClickListener = mock(),
diff --git a/media/java/android/media/AudioAttributes.java b/media/java/android/media/AudioAttributes.java
index 1db7198..5b90547 100644
--- a/media/java/android/media/AudioAttributes.java
+++ b/media/java/android/media/AudioAttributes.java
@@ -16,11 +16,15 @@
package android.media;
+import static android.media.audio.Flags.FLAG_SPEAKER_CLEANUP_USAGE;
+
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.annotation.TestApi;
+// TODO switch from HIDL imports to AIDL
import android.audio.policy.configuration.V7_0.AudioUsage;
import android.compat.annotation.UnsupportedAppUsage;
import android.media.audiopolicy.AudioProductStrategy;
@@ -247,6 +251,16 @@
public static final int USAGE_ANNOUNCEMENT = SYSTEM_USAGE_OFFSET + 3;
/**
+ * @hide
+ * Usage value to use when a system application plays a signal intended to clean up the
+ * speaker transducers and free them of deposits of dust or water.
+ */
+ @FlaggedApi(FLAG_SPEAKER_CLEANUP_USAGE)
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+ public static final int USAGE_SPEAKER_CLEANUP = SYSTEM_USAGE_OFFSET + 4;
+
+ /**
* IMPORTANT: when adding new usage types, add them to SDK_USAGES and update SUPPRESSIBLE_USAGES
* if applicable, as well as audioattributes.proto.
* Also consider adding them to <aaudio/AAudio.h> for the NDK.
@@ -1521,6 +1535,8 @@
return "USAGE_VEHICLE_STATUS";
case USAGE_ANNOUNCEMENT:
return "USAGE_ANNOUNCEMENT";
+ case USAGE_SPEAKER_CLEANUP:
+ return "USAGE_SPEAKER_CLEANUP";
default:
return "unknown usage " + usage;
}
@@ -1661,12 +1677,8 @@
}
/**
- * @param usage one of {@link AttributeSystemUsage},
- * {@link AttributeSystemUsage#USAGE_CALL_ASSISTANT},
- * {@link AttributeSystemUsage#USAGE_EMERGENCY},
- * {@link AttributeSystemUsage#USAGE_SAFETY},
- * {@link AttributeSystemUsage#USAGE_VEHICLE_STATUS},
- * {@link AttributeSystemUsage#USAGE_ANNOUNCEMENT}
+ * Returns whether the given usage can only be used by system-privileged components
+ * @param usage one of {@link AttributeSystemUsage}.
* @return boolean indicating if the usage is a system usage or not
* @hide
*/
@@ -1676,7 +1688,8 @@
|| usage == USAGE_EMERGENCY
|| usage == USAGE_SAFETY
|| usage == USAGE_VEHICLE_STATUS
- || usage == USAGE_ANNOUNCEMENT);
+ || usage == USAGE_ANNOUNCEMENT
+ || usage == USAGE_SPEAKER_CLEANUP);
}
/**
@@ -1790,6 +1803,7 @@
case USAGE_SAFETY:
case USAGE_VEHICLE_STATUS:
case USAGE_ANNOUNCEMENT:
+ case USAGE_SPEAKER_CLEANUP:
case USAGE_UNKNOWN:
return AudioSystem.STREAM_MUSIC;
default:
@@ -1829,7 +1843,8 @@
USAGE_EMERGENCY,
USAGE_SAFETY,
USAGE_VEHICLE_STATUS,
- USAGE_ANNOUNCEMENT
+ USAGE_ANNOUNCEMENT,
+ USAGE_SPEAKER_CLEANUP
})
@Retention(RetentionPolicy.SOURCE)
public @interface AttributeSystemUsage {}
@@ -1879,6 +1894,7 @@
USAGE_SAFETY,
USAGE_VEHICLE_STATUS,
USAGE_ANNOUNCEMENT,
+ USAGE_SPEAKER_CLEANUP,
})
@Retention(RetentionPolicy.SOURCE)
public @interface AttributeUsage {}
diff --git a/media/java/android/media/quality/AmbientBacklightEvent.aidl b/media/java/android/media/quality/AmbientBacklightEvent.aidl
new file mode 100644
index 0000000..174cd46
--- /dev/null
+++ b/media/java/android/media/quality/AmbientBacklightEvent.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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.media.quality;
+
+parcelable AmbientBacklightEvent;
diff --git a/media/java/android/media/quality/AmbientBacklightEvent.java b/media/java/android/media/quality/AmbientBacklightEvent.java
new file mode 100644
index 0000000..3bc6b86
--- /dev/null
+++ b/media/java/android/media/quality/AmbientBacklightEvent.java
@@ -0,0 +1,136 @@
+/*
+ * 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.media.quality;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
+
+/**
+ * @hide
+ */
+public final class AmbientBacklightEvent implements Parcelable {
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({AMBIENT_BACKLIGHT_EVENT_ENABLED, AMBIENT_BACKLIGHT_EVENT_DISABLED,
+ AMBIENT_BACKLIGHT_EVENT_METADATA,
+ AMBIENT_BACKLIGHT_EVENT_INTERRUPTED})
+ public @interface AmbientBacklightEventTypes {}
+
+ /**
+ * Event type for ambient backlight events. The ambient backlight is enabled.
+ */
+ public static final int AMBIENT_BACKLIGHT_EVENT_ENABLED = 1;
+
+ /**
+ * Event type for ambient backlight events. The ambient backlight is disabled.
+ */
+ public static final int AMBIENT_BACKLIGHT_EVENT_DISABLED = 2;
+
+ /**
+ * Event type for ambient backlight events. The ambient backlight metadata is
+ * available.
+ */
+ public static final int AMBIENT_BACKLIGHT_EVENT_METADATA = 3;
+
+ /**
+ * Event type for ambient backlight events. The ambient backlight event is preempted by another
+ * application.
+ */
+ public static final int AMBIENT_BACKLIGHT_EVENT_INTERRUPTED = 4;
+
+ private final int mEventType;
+ @Nullable
+ private final AmbientBacklightMetadata mMetadata;
+
+ public AmbientBacklightEvent(int eventType,
+ @Nullable AmbientBacklightMetadata metadata) {
+ mEventType = eventType;
+ mMetadata = metadata;
+ }
+
+ private AmbientBacklightEvent(Parcel in) {
+ mEventType = in.readInt();
+ mMetadata = in.readParcelable(AmbientBacklightMetadata.class.getClassLoader());
+ }
+
+ public int getEventType() {
+ return mEventType;
+ }
+
+ @Nullable
+ public AmbientBacklightMetadata getMetadata() {
+ return mMetadata;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeInt(mEventType);
+ dest.writeParcelable(mMetadata, flags);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ public static final @NonNull Parcelable.Creator<AmbientBacklightEvent> CREATOR =
+ new Parcelable.Creator<AmbientBacklightEvent>() {
+ public AmbientBacklightEvent createFromParcel(Parcel in) {
+ return new AmbientBacklightEvent(in);
+ }
+
+ public AmbientBacklightEvent[] newArray(int size) {
+ return new AmbientBacklightEvent[size];
+ }
+ };
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+
+ if (!(obj instanceof AmbientBacklightEvent)) {
+ return false;
+ }
+
+ AmbientBacklightEvent other = (AmbientBacklightEvent) obj;
+ return mEventType == other.mEventType
+ && Objects.equals(mMetadata, other.mMetadata);
+ }
+
+ @Override
+ public int hashCode() {
+ return mEventType * 31 + (mMetadata != null ? mMetadata.hashCode() : 0);
+ }
+
+ @Override
+ public String toString() {
+ return "AmbientBacklightEvent{"
+ + "mEventType=" + mEventType
+ + ", mMetadata=" + mMetadata
+ + '}';
+ }
+}
diff --git a/media/java/android/media/quality/AmbientBacklightMetadata.aidl b/media/java/android/media/quality/AmbientBacklightMetadata.aidl
new file mode 100644
index 0000000..b95a474f
--- /dev/null
+++ b/media/java/android/media/quality/AmbientBacklightMetadata.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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.media.quality;
+
+parcelable AmbientBacklightMetadata;
\ No newline at end of file
diff --git a/media/java/android/media/quality/AmbientBacklightMetadata.java b/media/java/android/media/quality/AmbientBacklightMetadata.java
new file mode 100644
index 0000000..fc77934
--- /dev/null
+++ b/media/java/android/media/quality/AmbientBacklightMetadata.java
@@ -0,0 +1,127 @@
+/*
+ * 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.media.quality;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import androidx.annotation.NonNull;
+
+import java.util.Arrays;
+
+/**
+ * @hide
+ */
+public class AmbientBacklightMetadata implements Parcelable {
+ @NonNull
+ private final String mPackageName;
+ private final int mCompressAlgorithm;
+ private final int mSource;
+ private final int mColorFormat;
+ private final int mHorizontalZonesNumber;
+ private final int mVerticalZonesNumber;
+ @NonNull
+ private final int[] mZonesColors;
+
+ public AmbientBacklightMetadata(@NonNull String packageName, int compressAlgorithm,
+ int source, int colorFormat, int horizontalZonesNumber, int verticalZonesNumber,
+ @NonNull int[] zonesColors) {
+ mPackageName = packageName;
+ mCompressAlgorithm = compressAlgorithm;
+ mSource = source;
+ mColorFormat = colorFormat;
+ mHorizontalZonesNumber = horizontalZonesNumber;
+ mVerticalZonesNumber = verticalZonesNumber;
+ mZonesColors = zonesColors;
+ }
+
+ private AmbientBacklightMetadata(Parcel in) {
+ mPackageName = in.readString();
+ mCompressAlgorithm = in.readInt();
+ mSource = in.readInt();
+ mColorFormat = in.readInt();
+ mHorizontalZonesNumber = in.readInt();
+ mVerticalZonesNumber = in.readInt();
+ mZonesColors = in.createIntArray();
+ }
+
+ @NonNull
+ public String getPackageName() {
+ return mPackageName;
+ }
+
+ public int getCompressAlgorithm() {
+ return mCompressAlgorithm;
+ }
+
+ public int getSource() {
+ return mSource;
+ }
+
+ public int getColorFormat() {
+ return mColorFormat;
+ }
+
+ public int getHorizontalZonesNumber() {
+ return mHorizontalZonesNumber;
+ }
+
+ public int getVerticalZonesNumber() {
+ return mVerticalZonesNumber;
+ }
+
+ @NonNull
+ public int[] getZonesColors() {
+ return mZonesColors;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeString(mPackageName);
+ dest.writeInt(mCompressAlgorithm);
+ dest.writeInt(mSource);
+ dest.writeInt(mColorFormat);
+ dest.writeInt(mHorizontalZonesNumber);
+ dest.writeInt(mVerticalZonesNumber);
+ dest.writeIntArray(mZonesColors);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ public static final @NonNull Parcelable.Creator<AmbientBacklightMetadata> CREATOR =
+ new Parcelable.Creator<AmbientBacklightMetadata>() {
+ public AmbientBacklightMetadata createFromParcel(Parcel in) {
+ return new AmbientBacklightMetadata(in);
+ }
+
+ public AmbientBacklightMetadata[] newArray(int size) {
+ return new AmbientBacklightMetadata[size];
+ }
+ };
+
+ @Override
+ public String toString() {
+ return "AmbientBacklightMetadata{packageName=" + mPackageName
+ + ", compressAlgorithm=" + mCompressAlgorithm + ", source=" + mSource
+ + ", colorFormat=" + mColorFormat + ", horizontalZonesNumber="
+ + mHorizontalZonesNumber + ", verticalZonesNumber=" + mVerticalZonesNumber
+ + ", zonesColors=" + Arrays.toString(mZonesColors) + "}";
+ }
+}
diff --git a/media/java/android/media/quality/AmbientBacklightSettings.aidl b/media/java/android/media/quality/AmbientBacklightSettings.aidl
new file mode 100644
index 0000000..e2cdd03
--- /dev/null
+++ b/media/java/android/media/quality/AmbientBacklightSettings.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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.media.quality;
+
+parcelable AmbientBacklightSettings;
diff --git a/media/java/android/media/quality/AmbientBacklightSettings.java b/media/java/android/media/quality/AmbientBacklightSettings.java
new file mode 100644
index 0000000..391eb22
--- /dev/null
+++ b/media/java/android/media/quality/AmbientBacklightSettings.java
@@ -0,0 +1,203 @@
+/*
+ * 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.media.quality;
+
+import android.annotation.IntDef;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import androidx.annotation.NonNull;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * @hide
+ */
+public class AmbientBacklightSettings implements Parcelable {
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({SOURCE_NONE, SOURCE_AUDIO, SOURCE_VIDEO, SOURCE_AUDIO_VIDEO})
+ public @interface Source {}
+
+ /**
+ * The detection is disabled.
+ */
+ public static final int SOURCE_NONE = 0;
+
+ /**
+ * The detection is enabled for audio.
+ */
+ public static final int SOURCE_AUDIO = 1;
+
+ /**
+ * The detection is enabled for video.
+ */
+ public static final int SOURCE_VIDEO = 2;
+
+ /**
+ * The detection is enabled for audio and video.
+ */
+ public static final int SOURCE_AUDIO_VIDEO = 3;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({COLOR_FORMAT_RGB888})
+ public @interface ColorFormat {}
+
+ /**
+ * The color format is RGB888.
+ */
+ public static final int COLOR_FORMAT_RGB888 = 1;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({ALGORITHM_NONE, ALGORITHM_RLE})
+ public @interface CompressAlgorithm {}
+
+ /**
+ * The compress algorithm is disabled.
+ */
+ public static final int ALGORITHM_NONE = 0;
+
+ /**
+ * The compress algorithm is RLE.
+ */
+ public static final int ALGORITHM_RLE = 1;
+
+ /**
+ * The source of the ambient backlight.
+ */
+ private final int mSource;
+
+ /**
+ * The maximum framerate for the ambient backlight.
+ */
+ private final int mMaxFps;
+
+ /**
+ * The color format for the ambient backlight.
+ */
+ private final int mColorFormat;
+
+ /**
+ * The number of zones in horizontal direction.
+ */
+ private final int mHorizontalZonesNumber;
+
+ /**
+ * The number of zones in vertical direction.
+ */
+ private final int mVerticalZonesNumber;
+
+ /**
+ * The flag to indicate whether the letterbox is omitted.
+ */
+ private final boolean mIsLetterboxOmitted;
+
+ /**
+ * The color threshold for the ambient backlight.
+ */
+ private final int mThreshold;
+
+ public AmbientBacklightSettings(int source, int maxFps, int colorFormat,
+ int horizontalZonesNumber, int verticalZonesNumber, boolean isLetterboxOmitted,
+ int threshold) {
+ mSource = source;
+ mMaxFps = maxFps;
+ mColorFormat = colorFormat;
+ mHorizontalZonesNumber = horizontalZonesNumber;
+ mVerticalZonesNumber = verticalZonesNumber;
+ mIsLetterboxOmitted = isLetterboxOmitted;
+ mThreshold = threshold;
+ }
+
+ private AmbientBacklightSettings(Parcel in) {
+ mSource = in.readInt();
+ mMaxFps = in.readInt();
+ mColorFormat = in.readInt();
+ mHorizontalZonesNumber = in.readInt();
+ mVerticalZonesNumber = in.readInt();
+ mIsLetterboxOmitted = in.readBoolean();
+ mThreshold = in.readInt();
+ }
+
+ @Source
+ public int getSource() {
+ return mSource;
+ }
+
+ public int getMaxFps() {
+ return mMaxFps;
+ }
+
+ @ColorFormat
+ public int getColorFormat() {
+ return mColorFormat;
+ }
+
+ public int getHorizontalZonesNumber() {
+ return mHorizontalZonesNumber;
+ }
+
+ public int getVerticalZonesNumber() {
+ return mVerticalZonesNumber;
+ }
+
+ public boolean isLetterboxOmitted() {
+ return mIsLetterboxOmitted;
+ }
+
+ public int getThreshold() {
+ return mThreshold;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeInt(mSource);
+ dest.writeInt(mMaxFps);
+ dest.writeInt(mColorFormat);
+ dest.writeInt(mHorizontalZonesNumber);
+ dest.writeInt(mVerticalZonesNumber);
+ dest.writeBoolean(mIsLetterboxOmitted);
+ dest.writeInt(mThreshold);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ public static final @NonNull Parcelable.Creator<AmbientBacklightSettings> CREATOR =
+ new Parcelable.Creator<AmbientBacklightSettings>() {
+ public AmbientBacklightSettings createFromParcel(Parcel in) {
+ return new AmbientBacklightSettings(in);
+ }
+
+ public AmbientBacklightSettings[] newArray(int size) {
+ return new AmbientBacklightSettings[size];
+ }
+ };
+
+ @Override
+ public String toString() {
+ return "AmbientBacklightSettings{Source=" + mSource + ", MaxFps=" + mMaxFps
+ + ", ColorFormat=" + mColorFormat + ", HorizontalZonesNumber="
+ + mHorizontalZonesNumber + ", VerticalZonesNumber=" + mVerticalZonesNumber
+ + ", IsLetterboxOmitted=" + mIsLetterboxOmitted + ", Threshold=" + mThreshold + "}";
+ }
+}
diff --git a/media/java/android/media/quality/IAmbientBacklightCallback.aidl b/media/java/android/media/quality/IAmbientBacklightCallback.aidl
new file mode 100644
index 0000000..159f5b7
--- /dev/null
+++ b/media/java/android/media/quality/IAmbientBacklightCallback.aidl
@@ -0,0 +1,24 @@
+/*
+ * 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.media.quality;
+
+import android.media.quality.AmbientBacklightEvent;
+
+/** @hide */
+oneway interface IAmbientBacklightCallback {
+ void onAmbientBacklightEvent(in AmbientBacklightEvent event);
+}
diff --git a/media/java/android/media/quality/IMediaQualityManager.aidl b/media/java/android/media/quality/IMediaQualityManager.aidl
index 83f8e79..e6c79dd 100644
--- a/media/java/android/media/quality/IMediaQualityManager.aidl
+++ b/media/java/android/media/quality/IMediaQualityManager.aidl
@@ -16,6 +16,8 @@
package android.media.quality;
+import android.media.quality.AmbientBacklightSettings;
+import android.media.quality.IAmbientBacklightCallback;
import android.media.quality.IPictureProfileCallback;
import android.media.quality.ISoundProfileCallback;
import android.media.quality.ParamCapability;
@@ -45,6 +47,7 @@
void registerPictureProfileCallback(in IPictureProfileCallback cb);
void registerSoundProfileCallback(in ISoundProfileCallback cb);
+ void registerAmbientBacklightCallback(in IAmbientBacklightCallback cb);
List<ParamCapability> getParamCapabilities(in List<String> names);
@@ -55,4 +58,7 @@
boolean isSuperResolutionEnabled();
void setAutoSoundQualityEnabled(in boolean enabled);
boolean isAutoSoundQualityEnabled();
-}
\ No newline at end of file
+
+ void setAmbientBacklightSettings(in AmbientBacklightSettings settings);
+ void setAmbientBacklightEnabled(in boolean enabled);
+}
diff --git a/media/java/android/media/quality/MediaQualityManager.java b/media/java/android/media/quality/MediaQualityManager.java
index 23f3b59..1237d67 100644
--- a/media/java/android/media/quality/MediaQualityManager.java
+++ b/media/java/android/media/quality/MediaQualityManager.java
@@ -50,6 +50,9 @@
private final List<PictureProfileCallbackRecord> mPpCallbackRecords = new ArrayList<>();
// @GuardedBy("mLock")
private final List<SoundProfileCallbackRecord> mSpCallbackRecords = new ArrayList<>();
+ // @GuardedBy("mLock")
+ private final List<AmbientBacklightCallbackRecord> mAbCallbackRecords = new ArrayList<>();
+
/**
* @hide
@@ -115,10 +118,22 @@
}
}
};
+ IAmbientBacklightCallback abCallback = new IAmbientBacklightCallback.Stub() {
+ @Override
+ public void onAmbientBacklightEvent(AmbientBacklightEvent event) {
+ synchronized (mLock) {
+ for (AmbientBacklightCallbackRecord record : mAbCallbackRecords) {
+ record.postAmbientBacklightEvent(event);
+ }
+ }
+ }
+ };
+
try {
if (mService != null) {
mService.registerPictureProfileCallback(ppCallback);
mService.registerSoundProfileCallback(spCallback);
+ mService.registerAmbientBacklightCallback(abCallback);
}
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
@@ -455,6 +470,68 @@
}
}
+ /**
+ * Registers a {@link AmbientBacklightCallback}.
+ * @hide
+ */
+ public void registerAmbientBacklightCallback(
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull AmbientBacklightCallback callback) {
+ Preconditions.checkNotNull(callback);
+ Preconditions.checkNotNull(executor);
+ synchronized (mLock) {
+ mAbCallbackRecords.add(new AmbientBacklightCallbackRecord(callback, executor));
+ }
+ }
+
+ /**
+ * Unregisters the existing {@link AmbientBacklightCallback}.
+ * @hide
+ */
+ public void unregisterAmbientBacklightCallback(
+ @NonNull final AmbientBacklightCallback callback) {
+ Preconditions.checkNotNull(callback);
+ synchronized (mLock) {
+ for (Iterator<AmbientBacklightCallbackRecord> it = mAbCallbackRecords.iterator();
+ it.hasNext(); ) {
+ AmbientBacklightCallbackRecord record = it.next();
+ if (record.getCallback() == callback) {
+ it.remove();
+ break;
+ }
+ }
+ }
+ }
+
+ /**
+ * Set the ambient backlight settings.
+ *
+ * @param settings The settings to use for the backlight detector.
+ */
+ public void setAmbientBacklightSettings(
+ @NonNull AmbientBacklightSettings settings) {
+ Preconditions.checkNotNull(settings);
+ try {
+ mService.setAmbientBacklightSettings(settings);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Enables or disables the ambient backlight detection.
+ *
+ * @param enabled {@code true} to enable, {@code false} to disable.
+ */
+ public void setAmbientBacklightEnabled(boolean enabled) {
+ try {
+ mService.setAmbientBacklightEnabled(enabled);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+
private static final class PictureProfileCallbackRecord {
private final PictureProfileCallback mCallback;
private final Executor mExecutor;
@@ -539,6 +616,29 @@
}
}
+ private static final class AmbientBacklightCallbackRecord {
+ private final AmbientBacklightCallback mCallback;
+ private final Executor mExecutor;
+
+ AmbientBacklightCallbackRecord(AmbientBacklightCallback callback, Executor executor) {
+ mCallback = callback;
+ mExecutor = executor;
+ }
+
+ public AmbientBacklightCallback getCallback() {
+ return mCallback;
+ }
+
+ public void postAmbientBacklightEvent(AmbientBacklightEvent event) {
+ mExecutor.execute(new Runnable() {
+ @Override
+ public void run() {
+ mCallback.onAmbientBacklightEvent(event);
+ }
+ });
+ }
+ }
+
/**
* Callback used to monitor status of picture profiles.
* @hide
@@ -592,4 +692,16 @@
public void onError(int errorCode) {
}
}
+
+ /**
+ * Callback used to monitor status of ambient backlight.
+ * @hide
+ */
+ public abstract static class AmbientBacklightCallback {
+ /**
+ * Called when new ambient backlight event is emitted.
+ */
+ public void onAmbientBacklightEvent(AmbientBacklightEvent event) {
+ }
+ }
}
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 9cb872a..cf6bf70 100644
--- a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGraphBuilder.kt
+++ b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGraphBuilder.kt
@@ -270,7 +270,8 @@
summary = textProto { string = it.toString() }
}
}
- if (metadata.icon != 0) icon = metadata.icon
+ val metadataIcon = metadata.getPreferenceIcon(context)
+ if (metadataIcon != 0) icon = metadataIcon
if (metadata.keywords != 0) keywords = metadata.keywords
val preferenceExtras = metadata.extras(context)
preferenceExtras?.let { extras = it.toProto() }
diff --git a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBindings.kt b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBindings.kt
index d40a6f6..762f5ea 100644
--- a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBindings.kt
+++ b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBindings.kt
@@ -23,7 +23,6 @@
import androidx.preference.SwitchPreferenceCompat
import androidx.preference.TwoStatePreference
import com.android.settingslib.metadata.EXTRA_BINDING_SCREEN_KEY
-import com.android.settingslib.metadata.PersistentPreference
import com.android.settingslib.metadata.PreferenceMetadata
import com.android.settingslib.metadata.PreferenceScreenMetadata
import com.android.settingslib.metadata.PreferenceTitleProvider
@@ -71,10 +70,10 @@
override fun bind(preference: Preference, metadata: PreferenceMetadata) {
super.bind(preference, metadata)
- (metadata as? PersistentPreference<*>)
- ?.storage(preference.context)
- ?.getValue(metadata.key, Boolean::class.javaObjectType)
- ?.let { (preference as TwoStatePreference).isChecked = it }
+ (preference as TwoStatePreference).apply {
+ // "false" is kind of placeholder, metadata datastore should provide the default value
+ isChecked = preferenceDataStore!!.getBoolean(key, false)
+ }
}
}
diff --git a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceHierarchyInflater.kt b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceHierarchyInflater.kt
index c26ba18..657f69a 100644
--- a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceHierarchyInflater.kt
+++ b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceHierarchyInflater.kt
@@ -40,11 +40,11 @@
addPreference(preferenceGroup)
preferenceGroup.inflatePreferenceHierarchy(preferenceBindingFactory, it)
} else {
- preferenceBindingFactory.bind(widget, it, preferenceBinding)
(metadata as? PersistentPreference<*>)?.storage(context)?.let { storage ->
widget.preferenceDataStore =
storages.getOrPut(storage) { PreferenceDataStoreAdapter(storage) }
}
+ preferenceBindingFactory.bind(widget, it, preferenceBinding)
// MUST add preference after binding for persistent preference to get initial value
// (preference key is set within bind method)
addPreference(widget)
diff --git a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceScreenBindingHelper.kt b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceScreenBindingHelper.kt
index d75f3e8..022fb1d 100644
--- a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceScreenBindingHelper.kt
+++ b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceScreenBindingHelper.kt
@@ -236,12 +236,12 @@
preference.bindRecursively(preferenceBindingFactory, preferences, storages)
} else {
preferences[preference.key]?.let {
- preferenceBindingFactory.bind(preference, it)
val metadata = it.metadata
(metadata as? PersistentPreference<*>)?.storage(context)?.let { storage ->
preference.preferenceDataStore =
storages.getOrPut(storage) { PreferenceDataStoreAdapter(storage) }
}
+ preferenceBindingFactory.bind(preference, it)
}
}
}
diff --git a/packages/SettingsLib/Preference/testutils/com/android/settingslib/preference/PreferenceBindingTestUtils.kt b/packages/SettingsLib/Preference/testutils/com/android/settingslib/preference/PreferenceBindingTestUtils.kt
new file mode 100644
index 0000000..f3142d0
--- /dev/null
+++ b/packages/SettingsLib/Preference/testutils/com/android/settingslib/preference/PreferenceBindingTestUtils.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.preference
+
+import android.content.Context
+import androidx.annotation.VisibleForTesting
+import androidx.preference.Preference
+import com.android.settingslib.metadata.PersistentPreference
+import com.android.settingslib.metadata.PreferenceMetadata
+
+/** Creates [Preference] widget and binds with metadata. */
+@VisibleForTesting
+fun <P : Preference> PreferenceMetadata.createAndBindWidget(context: Context): P {
+ val binding = DefaultPreferenceBindingFactory.getPreferenceBinding(this)
+ return (binding.createWidget(context) as P).also {
+ if (this is PersistentPreference<*>) {
+ storage(context)?.let { keyValueStore ->
+ it.preferenceDataStore = PreferenceDataStoreAdapter(keyValueStore)
+ }
+ }
+ binding.bind(it, this)
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingPendingIntentAction.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingPendingIntentAction.java
index 3d4282c..b997e4d 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingPendingIntentAction.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingPendingIntentAction.java
@@ -17,7 +17,6 @@
package com.android.settingslib.bluetooth.devicesettings;
import android.app.PendingIntent;
-import android.content.Intent;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
@@ -47,7 +46,7 @@
/** Read a {@link DeviceSettingPendingIntentAction} instance from {@link Parcel} */
@NonNull
public static DeviceSettingPendingIntentAction readFromParcel(@NonNull Parcel in) {
- PendingIntent pendingIntent = in.readParcelable(Intent.class.getClassLoader());
+ PendingIntent pendingIntent = in.readParcelable(PendingIntent.class.getClassLoader());
Bundle extras = in.readBundle(Bundle.class.getClassLoader());
return new DeviceSettingPendingIntentAction(pendingIntent, extras);
}
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..66a0908 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,
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/shade/NotificationShadeWindowViewControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
index 59d0d70..041d1a6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
@@ -238,7 +238,7 @@
primaryBouncerInteractor,
alternateBouncerInteractor,
mock(BouncerViewBinder::class.java),
- mock(ConfigurationForwarder::class.java),
+ { mock(ConfigurationForwarder::class.java) },
brightnessMirrorShowingInteractor,
)
underTest.setupExpandedStatusBar()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
index 9b91fc7..5d1ce7c5 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
@@ -203,7 +203,7 @@
primaryBouncerInteractor,
alternateBouncerInteractor,
mock(),
- configurationForwarder,
+ { configurationForwarder },
brightnessMirrorShowingInteractor,
)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarStarterTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarStarterTest.kt
index 8dcc444..5be5fb4 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarStarterTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarStarterTest.kt
@@ -17,11 +17,13 @@
package com.android.systemui.statusbar.core
import android.platform.test.annotations.EnableFlags
+import android.view.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.statusbar.data.repository.fakePrivacyDotWindowControllerStore
import com.android.systemui.testKosmos
import com.google.common.truth.Expect
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -30,6 +32,7 @@
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.kotlin.never
import org.mockito.kotlin.verify
@OptIn(ExperimentalCoroutinesApi::class)
@@ -39,16 +42,12 @@
class MultiDisplayStatusBarStarterTest : SysuiTestCase() {
@get:Rule val expect: Expect = Expect.create()
- private val kosmos =
- testKosmos().also {
- it.statusBarOrchestratorFactory = it.fakeStatusBarOrchestratorFactory
- it.statusBarInitializerStore = it.fakeStatusBarInitializerStore
- }
+ private val kosmos = testKosmos()
private val testScope = kosmos.testScope
private val fakeDisplayRepository = kosmos.displayRepository
private val fakeOrchestratorFactory = kosmos.fakeStatusBarOrchestratorFactory
private val fakeInitializerStore = kosmos.fakeStatusBarInitializerStore
-
+ private val fakePrivacyDotStore = kosmos.fakePrivacyDotWindowControllerStore
// Lazy, so that @EnableFlags is set before initializer is instantiated.
private val underTest by lazy { kosmos.multiDisplayStatusBarStarter }
@@ -83,6 +82,31 @@
}
@Test
+ fun start_startsPrivacyDotForCurrentDisplays() =
+ testScope.runTest {
+ fakeDisplayRepository.addDisplay(displayId = 1)
+ fakeDisplayRepository.addDisplay(displayId = 2)
+
+ underTest.start()
+ runCurrent()
+
+ verify(fakePrivacyDotStore.forDisplay(displayId = 1)).start()
+ verify(fakePrivacyDotStore.forDisplay(displayId = 2)).start()
+ }
+
+ @Test
+ fun start_doesNotStartPrivacyDotForDefaultDisplay() =
+ testScope.runTest {
+ fakeDisplayRepository.addDisplay(displayId = Display.DEFAULT_DISPLAY)
+
+ underTest.start()
+ runCurrent()
+
+ verify(fakePrivacyDotStore.forDisplay(displayId = Display.DEFAULT_DISPLAY), never())
+ .start()
+ }
+
+ @Test
fun displayAdded_orchestratorForNewDisplayIsStarted() =
testScope.runTest {
underTest.start()
@@ -109,6 +133,18 @@
}
@Test
+ fun displayAdded_privacyDotForNewDisplayIsStarted() =
+ testScope.runTest {
+ underTest.start()
+ runCurrent()
+
+ fakeDisplayRepository.addDisplay(displayId = 3)
+ runCurrent()
+
+ verify(fakePrivacyDotStore.forDisplay(displayId = 3)).start()
+ }
+
+ @Test
fun displayAddedDuringStart_initializerForNewDisplayIsStarted() =
testScope.runTest {
underTest.start()
@@ -129,8 +165,17 @@
fakeDisplayRepository.addDisplay(displayId = 3)
runCurrent()
- expect
- .that(fakeInitializerStore.forDisplay(displayId = 3).startedByCoreStartable)
- .isTrue()
+ verify(fakeOrchestratorFactory.createdOrchestratorForDisplay(displayId = 3)!!).start()
+ }
+
+ @Test
+ fun displayAddedDuringStart_privacyDotForNewDisplayIsStarted() =
+ testScope.runTest {
+ underTest.start()
+
+ fakeDisplayRepository.addDisplay(displayId = 3)
+ runCurrent()
+
+ verify(fakePrivacyDotStore.forDisplay(displayId = 3)).start()
}
}
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/PrivacyDotWindowControllerStoreImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/PrivacyDotWindowControllerStoreImplTest.kt
new file mode 100644
index 0000000..ae734b3
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/PrivacyDotWindowControllerStoreImplTest.kt
@@ -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.systemui.statusbar.data.repository
+
+import android.platform.test.annotations.EnableFlags
+import android.view.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.statusbar.core.StatusBarConnectedDisplays
+import com.android.systemui.testKosmos
+import kotlinx.coroutines.runBlocking
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@EnableFlags(StatusBarConnectedDisplays.FLAG_NAME)
+class PrivacyDotWindowControllerStoreImplTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+ private val underTest by lazy { kosmos.privacyDotWindowControllerStoreImpl }
+
+ @Before
+ fun installDisplays() = runBlocking {
+ kosmos.displayRepository.addDisplay(displayId = Display.DEFAULT_DISPLAY)
+ kosmos.displayRepository.addDisplay(displayId = Display.DEFAULT_DISPLAY + 1)
+ }
+
+ @Test(expected = IllegalArgumentException::class)
+ fun forDisplay_defaultDisplay_throws() {
+ underTest.forDisplay(displayId = Display.DEFAULT_DISPLAY)
+ }
+
+ @Test
+ fun forDisplay_nonDefaultDisplay_doesNotThrow() {
+ underTest.forDisplay(displayId = Display.DEFAULT_DISPLAY + 1)
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/SystemEventChipAnimationControllerStoreImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/SystemEventChipAnimationControllerStoreImplTest.kt
new file mode 100644
index 0000000..e65c04c
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/SystemEventChipAnimationControllerStoreImplTest.kt
@@ -0,0 +1,73 @@
+/*
+ * 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.never
+import org.mockito.kotlin.verify
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@EnableFlags(StatusBarConnectedDisplays.FLAG_NAME)
+class SystemEventChipAnimationControllerStoreImplTest : SysuiTestCase() {
+
+ private val kosmos = testKosmos().useUnconfinedTestDispatcher()
+ private val testScope = kosmos.testScope
+ private val fakeDisplayRepository = kosmos.displayRepository
+
+ // Lazy so that @EnableFlags has time to run before underTest is instantiated.
+ private val underTest by lazy { kosmos.systemEventChipAnimationControllerStoreImpl }
+
+ @Before
+ fun start() {
+ underTest.start()
+ }
+
+ @Before fun addDisplays() = runBlocking { fakeDisplayRepository.addDisplay(DEFAULT_DISPLAY) }
+
+ @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/events/MultiDisplaySystemEventChipAnimationControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/events/MultiDisplaySystemEventChipAnimationControllerTest.kt
new file mode 100644
index 0000000..d4007d7
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/events/MultiDisplaySystemEventChipAnimationControllerTest.kt
@@ -0,0 +1,108 @@
+/*
+ * 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.events
+
+import android.platform.test.annotations.EnableFlags
+import androidx.core.animation.AnimatorSet
+import androidx.core.animation.ValueAnimator
+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.statusbar.core.StatusBarConnectedDisplays
+import com.android.systemui.statusbar.data.repository.systemEventChipAnimationControllerStore
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.runBlocking
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.any
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@EnableFlags(StatusBarConnectedDisplays.FLAG_NAME)
+class MultiDisplaySystemEventChipAnimationControllerTest : SysuiTestCase() {
+
+ private val kosmos = testKosmos()
+ private val displayRepository = kosmos.displayRepository
+ private val store = kosmos.systemEventChipAnimationControllerStore
+
+ // Lazy so that @EnableFlags has time to switch the flags before the instance is created.
+ private val underTest by lazy { kosmos.multiDisplaySystemEventChipAnimationController }
+
+ @Before
+ fun installDisplays() = runBlocking {
+ INSTALLED_DISPLAY_IDS.forEach { displayRepository.addDisplay(displayId = it) }
+ }
+
+ @Test
+ fun init_forwardsToAllControllers() {
+ underTest.init()
+
+ INSTALLED_DISPLAY_IDS.forEach { verify(store.forDisplay(it)).init() }
+ }
+
+ @Test
+ fun stop_forwardsToAllControllers() {
+ underTest.stop()
+
+ INSTALLED_DISPLAY_IDS.forEach { verify(store.forDisplay(it)).stop() }
+ }
+
+ @Test
+ fun announceForAccessibility_forwardsToAllControllers() {
+ val contentDescription = "test content description"
+ underTest.announceForAccessibility(contentDescription)
+
+ INSTALLED_DISPLAY_IDS.forEach {
+ verify(store.forDisplay(it)).announceForAccessibility(contentDescription)
+ }
+ }
+
+ @Test
+ fun onSystemEventAnimationBegin_returnsAnimatorSetWithOneAnimatorPerDisplay() {
+ INSTALLED_DISPLAY_IDS.forEach {
+ val controller = store.forDisplay(it)
+ whenever(controller.onSystemEventAnimationBegin()).thenReturn(ValueAnimator.ofInt(0, 1))
+ }
+ val animator = underTest.onSystemEventAnimationBegin() as AnimatorSet
+
+ assertThat(animator.childAnimations).hasSize(INSTALLED_DISPLAY_IDS.size)
+ }
+
+ @Test
+ fun onSystemEventAnimationFinish_returnsAnimatorSetWithOneAnimatorPerDisplay() {
+ INSTALLED_DISPLAY_IDS.forEach {
+ val controller = store.forDisplay(it)
+ whenever(controller.onSystemEventAnimationFinish(any()))
+ .thenReturn(ValueAnimator.ofInt(0, 1))
+ }
+ val animator =
+ underTest.onSystemEventAnimationFinish(hasPersistentDot = true) as AnimatorSet
+
+ assertThat(animator.childAnimations).hasSize(INSTALLED_DISPLAY_IDS.size)
+ }
+
+ companion object {
+ private const val DISPLAY_ID_1 = 123
+ private const val DISPLAY_ID_2 = 456
+ private val INSTALLED_DISPLAY_IDS = listOf(DISPLAY_ID_1, DISPLAY_ID_2)
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/events/PrivacyDotWindowControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/events/PrivacyDotWindowControllerTest.kt
new file mode 100644
index 0000000..6bcd735
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/events/PrivacyDotWindowControllerTest.kt
@@ -0,0 +1,185 @@
+/*
+ * 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.events
+
+import android.view.Gravity.BOTTOM
+import android.view.Gravity.LEFT
+import android.view.Gravity.RIGHT
+import android.view.Gravity.TOP
+import android.view.Surface
+import android.view.View
+import android.view.WindowManager
+import android.view.fakeWindowManager
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.concurrency.fakeExecutor
+import com.android.systemui.res.R
+import com.android.systemui.testKosmos
+import com.google.common.truth.Expect
+import com.google.common.truth.Truth.assertThat
+import org.junit.After
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.mock
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class PrivacyDotWindowControllerTest : SysuiTestCase() {
+
+ @get:Rule val expect: Expect = Expect.create()
+
+ private val kosmos = testKosmos()
+ private val underTest = kosmos.privacyDotWindowController
+ private val viewController = kosmos.privacyDotViewController
+ private val windowManager = kosmos.fakeWindowManager
+ private val executor = kosmos.fakeExecutor
+
+ @After
+ fun cleanUpCustomDisplay() {
+ context.display = null
+ }
+
+ @Test
+ fun start_beforeUiThreadExecutes_doesNotAddWindows() {
+ underTest.start()
+
+ assertThat(windowManager.addedViews).isEmpty()
+ }
+
+ @Test
+ fun start_beforeUiThreadExecutes_doesNotInitializeViewController() {
+ underTest.start()
+
+ assertThat(viewController.isInitialized).isFalse()
+ }
+
+ @Test
+ fun start_afterUiThreadExecutes_addsWindowsOnUiThread() {
+ underTest.start()
+
+ executor.runAllReady()
+
+ assertThat(windowManager.addedViews).hasSize(4)
+ }
+
+ @Test
+ fun start_afterUiThreadExecutes_initializesViewController() {
+ underTest.start()
+
+ executor.runAllReady()
+
+ assertThat(viewController.isInitialized).isTrue()
+ }
+
+ @Test
+ fun start_initializesTopLeft() {
+ underTest.start()
+ executor.runAllReady()
+
+ assertThat(viewController.topLeft?.id).isEqualTo(R.id.privacy_dot_top_left_container)
+ }
+
+ @Test
+ fun start_initializesTopRight() {
+ underTest.start()
+ executor.runAllReady()
+
+ assertThat(viewController.topRight?.id).isEqualTo(R.id.privacy_dot_top_right_container)
+ }
+
+ @Test
+ fun start_initializesTopBottomLeft() {
+ underTest.start()
+ executor.runAllReady()
+
+ assertThat(viewController.bottomLeft?.id).isEqualTo(R.id.privacy_dot_bottom_left_container)
+ }
+
+ @Test
+ fun start_initializesBottomRight() {
+ underTest.start()
+ executor.runAllReady()
+
+ assertThat(viewController.bottomRight?.id)
+ .isEqualTo(R.id.privacy_dot_bottom_right_container)
+ }
+
+ @Test
+ fun start_viewsAddedInRespectiveCorners() {
+ context.display = mock { on { rotation } doReturn Surface.ROTATION_0 }
+
+ underTest.start()
+ executor.runAllReady()
+
+ expect.that(gravityForView(viewController.topLeft!!)).isEqualTo(TOP or LEFT)
+ expect.that(gravityForView(viewController.topRight!!)).isEqualTo(TOP or RIGHT)
+ expect.that(gravityForView(viewController.bottomLeft!!)).isEqualTo(BOTTOM or LEFT)
+ expect.that(gravityForView(viewController.bottomRight!!)).isEqualTo(BOTTOM or RIGHT)
+ }
+
+ @Test
+ fun start_rotation90_viewsPositionIsShifted90degrees() {
+ context.display = mock { on { rotation } doReturn Surface.ROTATION_90 }
+
+ underTest.start()
+ executor.runAllReady()
+
+ expect.that(gravityForView(viewController.topLeft!!)).isEqualTo(BOTTOM or LEFT)
+ expect.that(gravityForView(viewController.topRight!!)).isEqualTo(TOP or LEFT)
+ expect.that(gravityForView(viewController.bottomLeft!!)).isEqualTo(BOTTOM or RIGHT)
+ expect.that(gravityForView(viewController.bottomRight!!)).isEqualTo(TOP or RIGHT)
+ }
+
+ @Test
+ fun start_rotation180_viewsPositionIsShifted180degrees() {
+ context.display = mock { on { rotation } doReturn Surface.ROTATION_180 }
+
+ underTest.start()
+ executor.runAllReady()
+
+ expect.that(gravityForView(viewController.topLeft!!)).isEqualTo(BOTTOM or RIGHT)
+ expect.that(gravityForView(viewController.topRight!!)).isEqualTo(BOTTOM or LEFT)
+ expect.that(gravityForView(viewController.bottomLeft!!)).isEqualTo(TOP or RIGHT)
+ expect.that(gravityForView(viewController.bottomRight!!)).isEqualTo(TOP or LEFT)
+ }
+
+ @Test
+ fun start_rotation270_viewsPositionIsShifted270degrees() {
+ context.display = mock { on { rotation } doReturn Surface.ROTATION_270 }
+
+ underTest.start()
+ executor.runAllReady()
+
+ expect.that(gravityForView(viewController.topLeft!!)).isEqualTo(TOP or RIGHT)
+ expect.that(gravityForView(viewController.topRight!!)).isEqualTo(BOTTOM or RIGHT)
+ expect.that(gravityForView(viewController.bottomLeft!!)).isEqualTo(TOP or LEFT)
+ expect.that(gravityForView(viewController.bottomRight!!)).isEqualTo(BOTTOM or LEFT)
+ }
+
+ private fun paramsForView(view: View): WindowManager.LayoutParams {
+ return windowManager.addedViews.entries
+ .first { it.key == view || it.key.findViewById<View>(view.id) != null }
+ .value
+ }
+
+ private fun gravityForView(view: View): Int {
+ return paramsForView(view).gravity
+ }
+}
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..12f6825 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,15 @@
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());
mLightBarController.start();
}
@@ -121,7 +119,7 @@
new AppearanceRegion(APPEARANCE_LIGHT_STATUS_BARS, secondBounds)
);
- mStatusBarModeRepository.getDefaultDisplay().getStatusBarAppearance().setValue(
+ mStatusBarModeRepository.getStatusBarAppearance().setValue(
new StatusBarAppearance(
StatusBarMode.TRANSPARENT,
STATUS_BAR_BOUNDS,
@@ -142,7 +140,7 @@
new AppearanceRegion(0 /* appearance */, secondBounds)
);
- mStatusBarModeRepository.getDefaultDisplay().getStatusBarAppearance().setValue(
+ mStatusBarModeRepository.getStatusBarAppearance().setValue(
new StatusBarAppearance(
StatusBarMode.TRANSPARENT,
STATUS_BAR_BOUNDS,
@@ -165,7 +163,7 @@
new AppearanceRegion(APPEARANCE_LIGHT_STATUS_BARS, secondBounds)
);
- mStatusBarModeRepository.getDefaultDisplay().getStatusBarAppearance().setValue(
+ mStatusBarModeRepository.getStatusBarAppearance().setValue(
new StatusBarAppearance(
StatusBarMode.TRANSPARENT,
STATUS_BAR_BOUNDS,
@@ -190,7 +188,7 @@
new AppearanceRegion(APPEARANCE_LIGHT_STATUS_BARS, thirdBounds)
);
- mStatusBarModeRepository.getDefaultDisplay().getStatusBarAppearance().setValue(
+ mStatusBarModeRepository.getStatusBarAppearance().setValue(
new StatusBarAppearance(
StatusBarMode.TRANSPARENT,
STATUS_BAR_BOUNDS,
@@ -214,7 +212,7 @@
new AppearanceRegion(0 /* appearance */, secondBounds)
);
- mStatusBarModeRepository.getDefaultDisplay().getStatusBarAppearance().setValue(
+ mStatusBarModeRepository.getStatusBarAppearance().setValue(
new StatusBarAppearance(
StatusBarMode.TRANSPARENT,
STATUS_BAR_BOUNDS,
@@ -231,7 +229,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 +247,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 +264,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 +274,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 +296,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 +309,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/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/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/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
index 46e45aa..66b3e189 100644
--- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
+++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
@@ -905,7 +905,18 @@
return lp;
}
- private WindowManager.LayoutParams getWindowLayoutBaseParams() {
+ public static WindowManager.LayoutParams getWindowLayoutBaseParams() {
+ return getWindowLayoutBaseParams(/* excludeFromScreenshots= */ true);
+ }
+
+ /**
+ * Creates the base {@link WindowManager.LayoutParams} that are used for all decoration windows.
+ *
+ * @param excludeFromScreenshots whether to set the {@link
+ * WindowManager.LayoutParams#PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY} flag.
+ */
+ public static WindowManager.LayoutParams getWindowLayoutBaseParams(
+ boolean excludeFromScreenshots) {
final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
@@ -921,7 +932,7 @@
// FLAG_SLIPPERY can only be set by trusted overlays
lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
- if (!DEBUG_SCREENSHOT_ROUNDED_CORNERS) {
+ if (!DEBUG_SCREENSHOT_ROUNDED_CORNERS && excludeFromScreenshots) {
lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY;
}
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..b0f5e5e 100644
--- a/packages/SystemUI/src/com/android/systemui/common/data/CommonDataLayerModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/data/CommonDataLayerModule.kt
@@ -26,11 +26,6 @@
@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..e0756fc 100644
--- a/packages/SystemUI/src/com/android/systemui/common/ui/ConfigurationStateModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/ui/ConfigurationStateModule.kt
@@ -17,6 +17,7 @@
package com.android.systemui.common.ui
import android.content.Context
+import com.android.systemui.common.ui.data.repository.ConfigurationRepositoryModule
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.statusbar.policy.ConfigurationController
@@ -28,7 +29,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..31a7fa4 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,8 +134,7 @@
maxDisplayMode.physicalWidth,
maxDisplayMode.physicalHeight,
displayInfo.value.naturalWidth,
- displayInfo.value.naturalHeight
- )
+ displayInfo.value.naturalHeight)
return if (scaleFactor == Float.POSITIVE_INFINITY) 1f else scaleFactor
}
return 1f
@@ -144,9 +143,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/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/decor/PrivacyDotDecorProviderFactory.kt b/packages/SystemUI/src/com/android/systemui/decor/PrivacyDotDecorProviderFactory.kt
index 9aa7fd1..78d8d8f 100644
--- a/packages/SystemUI/src/com/android/systemui/decor/PrivacyDotDecorProviderFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/decor/PrivacyDotDecorProviderFactory.kt
@@ -81,7 +81,7 @@
override val viewId: Int,
@DisplayCutout.BoundsPosition override val alignedBound1: Int,
@DisplayCutout.BoundsPosition override val alignedBound2: Int,
- private val layoutId: Int,
+ val layoutId: Int,
) : CornerDecorProvider() {
override fun onReloadResAndMeasure(
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/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/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
index be2bf82..f2c3906 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
@@ -88,6 +88,7 @@
import java.util.function.Consumer;
import javax.inject.Inject;
+import javax.inject.Provider;
/**
* Controller for {@link NotificationShadeWindowView}.
@@ -193,7 +194,7 @@
PrimaryBouncerInteractor primaryBouncerInteractor,
AlternateBouncerInteractor alternateBouncerInteractor,
BouncerViewBinder bouncerViewBinder,
- @ShadeDisplayAware ConfigurationForwarder configurationForwarder,
+ @ShadeDisplayAware Provider<ConfigurationForwarder> configurationForwarder,
BrightnessMirrorShowingInteractor brightnessMirrorShowingInteractor) {
mLockscreenShadeTransitionController = transitionController;
mFalsingCollector = falsingCollector;
@@ -257,7 +258,7 @@
}
if (ShadeWindowGoesAround.isEnabled()) {
- mView.setConfigurationForwarder(configurationForwarder);
+ mView.setConfigurationForwarder(configurationForwarder.get());
}
dumpManager.registerDumpable(this);
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt
index 51f1f81..32255c6 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
@@ -79,12 +83,12 @@
fun provideShadeWindowConfigurationController(
@ShadeDisplayAware shadeContext: Context,
factory: ConfigurationControllerImpl.Factory,
- @GlobalConfig globalConfigConfigController: ConfigurationController,
+ @GlobalConfig globalConfigController: ConfigurationController,
): ConfigurationController {
return if (ShadeWindowGoesAround.isEnabled) {
factory.create(shadeContext)
} else {
- globalConfigConfigController
+ globalConfigController
}
}
@@ -93,12 +97,40 @@
@SysUISingleton
fun provideShadeWindowConfigurationForwarder(
@ShadeDisplayAware shadeConfigurationController: ConfigurationController,
- @GlobalConfig globalConfigController: 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) {
- shadeConfigurationController
+ factory.create(context, configurationController)
} else {
- globalConfigController
+ 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/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/domain/startable/ShadeStartable.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/startable/ShadeStartable.kt
index e5d08a0..a8d616c 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
@@ -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/MultiDisplayStatusBarStarter.kt b/packages/SystemUI/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarStarter.kt
index e115922..9b3513e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarStarter.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarStarter.kt
@@ -17,11 +17,13 @@
package com.android.systemui.statusbar.core
import android.view.Display
+import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.display.data.repository.DisplayRepository
import com.android.systemui.display.data.repository.DisplayScopeRepository
+import com.android.systemui.statusbar.data.repository.PrivacyDotWindowControllerStore
import com.android.systemui.statusbar.data.repository.StatusBarModeRepositoryStore
import com.android.systemui.statusbar.window.StatusBarWindowControllerStore
import com.android.systemui.statusbar.window.data.repository.StatusBarWindowStateRepositoryStore
@@ -29,7 +31,6 @@
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.onStart
-import com.android.app.tracing.coroutines.launchTraced as launch
/**
* Responsible for creating and starting the status bar components for each display. Also does it
@@ -48,6 +49,7 @@
private val initializerStore: StatusBarInitializerStore,
private val statusBarWindowControllerStore: StatusBarWindowControllerStore,
private val statusBarInitializerStore: StatusBarInitializerStore,
+ private val privacyDotWindowControllerStore: PrivacyDotWindowControllerStore,
) : CoreStartable {
init {
@@ -71,6 +73,7 @@
val displayId = display.displayId
createAndStartOrchestratorForDisplay(displayId)
createAndStartInitializerForDisplay(displayId)
+ startPrivacyDotForDisplay(displayId)
}
private fun createAndStartOrchestratorForDisplay(displayId: Int) {
@@ -89,4 +92,12 @@
private fun createAndStartInitializerForDisplay(displayId: Int) {
statusBarInitializerStore.forDisplay(displayId).start()
}
+
+ private fun startPrivacyDotForDisplay(displayId: Int) {
+ if (displayId == Display.DEFAULT_DISPLAY) {
+ // For the default display, privacy dot is started via ScreenDecorations
+ return
+ }
+ privacyDotWindowControllerStore.forDisplay(displayId).start()
+ }
}
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 c416bf7..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,10 +16,12 @@
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
import com.android.systemui.statusbar.data.repository.StatusBarModeRepositoryModule
+import com.android.systemui.statusbar.data.repository.SystemEventChipAnimationControllerStoreModule
import com.android.systemui.statusbar.phone.data.StatusBarPhoneDataLayerModule
import dagger.Module
@@ -27,11 +29,13 @@
includes =
[
KeyguardStatusBarRepositoryModule::class,
+ LightBarControllerStoreModule::class,
RemoteInputRepositoryModule::class,
StatusBarConfigurationControllerModule::class,
StatusBarContentInsetsProviderStoreModule::class,
StatusBarModeRepositoryModule::class,
StatusBarPhoneDataLayerModule::class,
+ SystemEventChipAnimationControllerStoreModule::class,
]
)
object StatusBarDataLayerModule
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/PrivacyDotWindowControllerStore.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/PrivacyDotWindowControllerStore.kt
new file mode 100644
index 0000000..a1f5655
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/PrivacyDotWindowControllerStore.kt
@@ -0,0 +1,99 @@
+/*
+ * 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
+import android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL
+import com.android.app.viewcapture.ViewCaptureAwareWindowManager
+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.DisplayWindowPropertiesRepository
+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.events.PrivacyDotWindowController
+import dagger.Binds
+import dagger.Lazy
+import dagger.Module
+import dagger.Provides
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+
+/** Providers per display instances of [PrivacyDotWindowController]. */
+interface PrivacyDotWindowControllerStore : PerDisplayStore<PrivacyDotWindowController>
+
+@SysUISingleton
+class PrivacyDotWindowControllerStoreImpl
+@Inject
+constructor(
+ @Background backgroundApplicationScope: CoroutineScope,
+ displayRepository: DisplayRepository,
+ private val windowControllerFactory: PrivacyDotWindowController.Factory,
+ private val displayWindowPropertiesRepository: DisplayWindowPropertiesRepository,
+ private val privacyDotViewControllerStore: PrivacyDotViewControllerStore,
+ private val viewCaptureAwareWindowManagerFactory: ViewCaptureAwareWindowManager.Factory,
+) :
+ PrivacyDotWindowControllerStore,
+ PerDisplayStoreImpl<PrivacyDotWindowController>(backgroundApplicationScope, displayRepository) {
+
+ init {
+ StatusBarConnectedDisplays.assertInNewMode()
+ }
+
+ override fun createInstanceForDisplay(displayId: Int): PrivacyDotWindowController {
+ if (displayId == Display.DEFAULT_DISPLAY) {
+ throw IllegalArgumentException("This class should only be used for connected displays")
+ }
+ val displayWindowProperties =
+ displayWindowPropertiesRepository.get(displayId, TYPE_NAVIGATION_BAR_PANEL)
+ return windowControllerFactory.create(
+ displayId = displayId,
+ privacyDotViewController = privacyDotViewControllerStore.forDisplay(displayId),
+ viewCaptureAwareWindowManager =
+ viewCaptureAwareWindowManagerFactory.create(displayWindowProperties.windowManager),
+ inflater = displayWindowProperties.layoutInflater,
+ )
+ }
+
+ override val instanceClass = PrivacyDotWindowController::class.java
+}
+
+@Module
+interface PrivacyDotWindowControllerStoreModule {
+
+ @Binds fun store(impl: PrivacyDotWindowControllerStoreImpl): PrivacyDotWindowControllerStore
+
+ companion object {
+ @Provides
+ @SysUISingleton
+ @IntoMap
+ @ClassKey(PrivacyDotWindowControllerStore::class)
+ fun storeAsCoreStartable(
+ storeLazy: Lazy<PrivacyDotWindowControllerStoreImpl>
+ ): CoreStartable {
+ return if (StatusBarConnectedDisplays.isEnabled) {
+ storeLazy.get()
+ } else {
+ CoreStartable.NOP
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/SystemEventChipAnimationControllerStore.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/SystemEventChipAnimationControllerStore.kt
new file mode 100644
index 0000000..7760f58
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/SystemEventChipAnimationControllerStore.kt
@@ -0,0 +1,104 @@
+/*
+ * 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.WindowManager.LayoutParams.TYPE_STATUS_BAR
+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.DisplayWindowPropertiesRepository
+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.events.SystemEventChipAnimationController
+import com.android.systemui.statusbar.events.SystemEventChipAnimationControllerImpl
+import com.android.systemui.statusbar.window.StatusBarWindowControllerStore
+import dagger.Binds
+import dagger.Lazy
+import dagger.Module
+import dagger.Provides
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+
+/** Provides per display instances of [SystemEventChipAnimationController]. */
+interface SystemEventChipAnimationControllerStore :
+ PerDisplayStore<SystemEventChipAnimationController>
+
+@SysUISingleton
+class SystemEventChipAnimationControllerStoreImpl
+@Inject
+constructor(
+ @Background backgroundApplicationScope: CoroutineScope,
+ displayRepository: DisplayRepository,
+ private val factory: SystemEventChipAnimationControllerImpl.Factory,
+ private val displayWindowPropertiesRepository: DisplayWindowPropertiesRepository,
+ private val statusBarWindowControllerStore: StatusBarWindowControllerStore,
+ private val statusBarContentInsetsProviderStore: StatusBarContentInsetsProviderStore,
+) :
+ SystemEventChipAnimationControllerStore,
+ PerDisplayStoreImpl<SystemEventChipAnimationController>(
+ backgroundApplicationScope,
+ displayRepository,
+ ) {
+
+ init {
+ StatusBarConnectedDisplays.assertInNewMode()
+ }
+
+ override fun createInstanceForDisplay(displayId: Int): SystemEventChipAnimationController {
+ return factory.create(
+ displayWindowPropertiesRepository.get(displayId, TYPE_STATUS_BAR).context,
+ statusBarWindowControllerStore.forDisplay(displayId),
+ statusBarContentInsetsProviderStore.forDisplay(displayId),
+ )
+ }
+
+ override suspend fun onDisplayRemovalAction(instance: SystemEventChipAnimationController) {
+ instance.stop()
+ }
+
+ override val instanceClass = SystemEventChipAnimationController::class.java
+}
+
+@Module
+interface SystemEventChipAnimationControllerStoreModule {
+
+ @Binds
+ @SysUISingleton
+ fun store(
+ impl: SystemEventChipAnimationControllerStoreImpl
+ ): SystemEventChipAnimationControllerStore
+
+ companion object {
+ @Provides
+ @SysUISingleton
+ @IntoMap
+ @ClassKey(SystemEventChipAnimationControllerStore::class)
+ fun storeAsCoreStartable(
+ implLazy: Lazy<SystemEventChipAnimationControllerStoreImpl>
+ ): CoreStartable {
+ return if (StatusBarConnectedDisplays.isEnabled) {
+ implLazy.get()
+ } else {
+ CoreStartable.NOP
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/MultiDisplaySystemEventChipAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/MultiDisplaySystemEventChipAnimationController.kt
new file mode 100644
index 0000000..f2bb7b1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/MultiDisplaySystemEventChipAnimationController.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.events
+
+import androidx.core.animation.Animator
+import androidx.core.animation.AnimatorSet
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.display.data.repository.DisplayRepository
+import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
+import com.android.systemui.statusbar.data.repository.SystemEventChipAnimationControllerStore
+import javax.inject.Inject
+
+/**
+ * A [SystemEventChipAnimationController] that handles animations for multiple displays. It
+ * delegates the animation tasks to individual controllers for each display.
+ */
+@SysUISingleton
+class MultiDisplaySystemEventChipAnimationController
+@Inject
+constructor(
+ private val displayRepository: DisplayRepository,
+ private val controllerStore: SystemEventChipAnimationControllerStore,
+) : SystemEventChipAnimationController {
+
+ init {
+ StatusBarConnectedDisplays.assertInNewMode()
+ }
+
+ override fun prepareChipAnimation(viewCreator: ViewCreator) {
+ forEachController { it.prepareChipAnimation(viewCreator) }
+ }
+
+ override fun init() {
+ forEachController { it.init() }
+ }
+
+ override fun stop() {
+ forEachController { it.stop() }
+ }
+
+ override fun announceForAccessibility(contentDescriptions: String) {
+ forEachController { it.announceForAccessibility(contentDescriptions) }
+ }
+
+ override fun onSystemEventAnimationBegin(): Animator {
+ val animators = controllersForAllDisplays().map { it.onSystemEventAnimationBegin() }
+ return AnimatorSet().apply { playTogether(animators) }
+ }
+
+ override fun onSystemEventAnimationFinish(hasPersistentDot: Boolean): Animator {
+ val animators =
+ controllersForAllDisplays().map { it.onSystemEventAnimationFinish(hasPersistentDot) }
+ return AnimatorSet().apply { playTogether(animators) }
+ }
+
+ private fun forEachController(consumer: (SystemEventChipAnimationController) -> Unit) {
+ controllersForAllDisplays().forEach { consumer(it) }
+ }
+
+ private fun controllersForAllDisplays() =
+ displayRepository.displays.value.map { controllerStore.forDisplay(it.displayId) }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotWindowController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotWindowController.kt
new file mode 100644
index 0000000..9928ac6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotWindowController.kt
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.events
+
+import android.view.Display
+import android.view.DisplayCutout.BOUNDS_POSITION_BOTTOM
+import android.view.DisplayCutout.BOUNDS_POSITION_LEFT
+import android.view.DisplayCutout.BOUNDS_POSITION_RIGHT
+import android.view.DisplayCutout.BOUNDS_POSITION_TOP
+import android.view.LayoutInflater
+import android.view.View
+import android.view.WindowManager.LayoutParams.WRAP_CONTENT
+import android.widget.FrameLayout
+import com.android.app.viewcapture.ViewCaptureAwareWindowManager
+import com.android.systemui.ScreenDecorations
+import com.android.systemui.ScreenDecorationsThread
+import com.android.systemui.decor.DecorProvider
+import com.android.systemui.decor.PrivacyDotCornerDecorProviderImpl
+import com.android.systemui.decor.PrivacyDotDecorProviderFactory
+import com.android.systemui.statusbar.events.PrivacyDotCorner.BottomLeft
+import com.android.systemui.statusbar.events.PrivacyDotCorner.BottomRight
+import com.android.systemui.statusbar.events.PrivacyDotCorner.TopLeft
+import com.android.systemui.statusbar.events.PrivacyDotCorner.TopRight
+import com.android.systemui.util.containsExactly
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import java.util.concurrent.Executor
+
+/**
+ * Responsible for adding the privacy dot to a window.
+ *
+ * It will create one window per corner (top left, top right, bottom left, bottom right), which are
+ * used dependant on the display's rotation.
+ */
+class PrivacyDotWindowController
+@AssistedInject
+constructor(
+ @Assisted private val displayId: Int,
+ @Assisted private val privacyDotViewController: PrivacyDotViewController,
+ @Assisted private val viewCaptureAwareWindowManager: ViewCaptureAwareWindowManager,
+ @Assisted private val inflater: LayoutInflater,
+ @ScreenDecorationsThread private val uiExecutor: Executor,
+ private val dotFactory: PrivacyDotDecorProviderFactory,
+) {
+
+ fun start() {
+ uiExecutor.execute { startOnUiThread() }
+ }
+
+ private fun startOnUiThread() {
+ val providers = dotFactory.providers
+
+ val topLeft = providers.inflate(BOUNDS_POSITION_TOP, BOUNDS_POSITION_LEFT)
+ val topRight = providers.inflate(BOUNDS_POSITION_TOP, BOUNDS_POSITION_RIGHT)
+ val bottomLeft = providers.inflate(BOUNDS_POSITION_BOTTOM, BOUNDS_POSITION_LEFT)
+ val bottomRight = providers.inflate(BOUNDS_POSITION_BOTTOM, BOUNDS_POSITION_RIGHT)
+
+ topLeft.addToWindow(TopLeft)
+ topRight.addToWindow(TopRight)
+ bottomLeft.addToWindow(BottomLeft)
+ bottomRight.addToWindow(BottomRight)
+
+ privacyDotViewController.initialize(topLeft, topRight, bottomLeft, bottomRight)
+ }
+
+ private fun List<DecorProvider>.inflate(alignedBound1: Int, alignedBound2: Int): View {
+ val provider =
+ first { it.alignedBounds.containsExactly(alignedBound1, alignedBound2) }
+ as PrivacyDotCornerDecorProviderImpl
+ return inflater.inflate(/* resource= */ provider.layoutId, /* root= */ null)
+ }
+
+ private fun View.addToWindow(corner: PrivacyDotCorner) {
+ val excludeFromScreenshots = displayId == Display.DEFAULT_DISPLAY
+ val params =
+ ScreenDecorations.getWindowLayoutBaseParams(excludeFromScreenshots).apply {
+ width = WRAP_CONTENT
+ height = WRAP_CONTENT
+ gravity = corner.rotatedCorner(context.display.rotation).gravity
+ title = "PrivacyDot${corner.title}$displayId"
+ }
+ // PrivacyDotViewController expects the dot view to have a FrameLayout parent.
+ val rootView = FrameLayout(context)
+ rootView.addView(this)
+ viewCaptureAwareWindowManager.addView(rootView, params)
+ }
+
+ @AssistedFactory
+ fun interface Factory {
+ fun create(
+ displayId: Int,
+ privacyDotViewController: PrivacyDotViewController,
+ viewCaptureAwareWindowManager: ViewCaptureAwareWindowManager,
+ inflater: LayoutInflater,
+ ): PrivacyDotWindowController
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventChipAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventChipAnimationController.kt
index b286605..1038ad4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventChipAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventChipAnimationController.kt
@@ -32,13 +32,16 @@
import androidx.core.animation.ValueAnimator
import com.android.internal.annotations.VisibleForTesting
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Default
import com.android.systemui.res.R
+import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
import com.android.systemui.statusbar.data.repository.StatusBarContentInsetsProviderStore
import com.android.systemui.statusbar.phone.StatusBarContentInsetsChangedListener
import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider
import com.android.systemui.statusbar.window.StatusBarWindowController
import com.android.systemui.statusbar.window.StatusBarWindowControllerStore
import com.android.systemui.util.animation.AnimationUtil.Companion.frames
+import dagger.Lazy
import dagger.Module
import dagger.Provides
import dagger.assisted.Assisted
@@ -57,6 +60,8 @@
fun init()
+ fun stop()
+
/** Announces [contentDescriptions] for accessibility. */
fun announceForAccessibility(contentDescriptions: String)
@@ -287,6 +292,26 @@
return animSet
}
+ private val statusBarContentInsetsChangedListener =
+ object : StatusBarContentInsetsChangedListener {
+ override fun onStatusBarContentInsetsChanged() {
+ val newContentArea =
+ contentInsetsProvider.getStatusBarContentAreaForCurrentRotation()
+ updateDimens(newContentArea)
+
+ // If we are currently animating, we have to re-solve for the chip bounds. If
+ // we're not animating then [prepareChipAnimation] will take care of it for us.
+ currentAnimatedView?.let {
+ updateChipBounds(it, newContentArea)
+ // Since updateCurrentAnimatedView can only be called during an animation,
+ // we have to create a no-op animator here to apply the new chip bounds.
+ val animator = ValueAnimator.ofInt(0, 1).setDuration(0)
+ animator.addUpdateListener { updateCurrentAnimatedView() }
+ animator.start()
+ }
+ }
+ }
+
override fun init() {
initialized = true
themedContext = ContextThemeWrapper(context, R.style.Theme_SystemUI_QuickSettings)
@@ -303,28 +328,11 @@
// Use contentInsetsProvider rather than configuration controller, since we only care
// about status bar dimens
- contentInsetsProvider.addCallback(
- object : StatusBarContentInsetsChangedListener {
- override fun onStatusBarContentInsetsChanged() {
- val newContentArea =
- contentInsetsProvider.getStatusBarContentAreaForCurrentRotation()
- updateDimens(newContentArea)
+ contentInsetsProvider.addCallback(statusBarContentInsetsChangedListener)
+ }
- // If we are currently animating, we have to re-solve for the chip bounds. If
- // we're
- // not animating then [prepareChipAnimation] will take care of it for us
- currentAnimatedView?.let {
- updateChipBounds(it, newContentArea)
- // Since updateCurrentAnimatedView can only be called during an animation,
- // we
- // have to create a dummy animator here to apply the new chip bounds
- val animator = ValueAnimator.ofInt(0, 1).setDuration(0)
- animator.addUpdateListener { updateCurrentAnimatedView() }
- animator.start()
- }
- }
- }
- )
+ override fun stop() {
+ contentInsetsProvider.removeCallback(statusBarContentInsetsChangedListener)
}
override fun announceForAccessibility(contentDescriptions: String) {
@@ -418,7 +426,7 @@
}
@AssistedFactory
- interface Factory {
+ fun interface Factory {
fun create(
context: Context,
statusBarWindowController: StatusBarWindowController,
@@ -446,20 +454,36 @@
private const val RIGHT = 2
@Module
-object SystemEventChipAnimationControllerModule {
+interface SystemEventChipAnimationControllerModule {
- @Provides
- @SysUISingleton
- fun controller(
- factory: SystemEventChipAnimationControllerImpl.Factory,
- context: Context,
- statusBarWindowControllerStore: StatusBarWindowControllerStore,
- contentInsetsProviderStore: StatusBarContentInsetsProviderStore,
- ): SystemEventChipAnimationController {
- return factory.create(
- context,
- statusBarWindowControllerStore.defaultDisplay,
- contentInsetsProviderStore.defaultDisplay,
- )
+ companion object {
+ @Provides
+ @Default
+ @SysUISingleton
+ fun defaultController(
+ factory: SystemEventChipAnimationControllerImpl.Factory,
+ context: Context,
+ statusBarWindowControllerStore: StatusBarWindowControllerStore,
+ contentInsetsProviderStore: StatusBarContentInsetsProviderStore,
+ ): SystemEventChipAnimationController {
+ return factory.create(
+ context,
+ statusBarWindowControllerStore.defaultDisplay,
+ contentInsetsProviderStore.defaultDisplay,
+ )
+ }
+
+ @Provides
+ @SysUISingleton
+ fun controller(
+ @Default defaultLazy: Lazy<SystemEventChipAnimationController>,
+ multiDisplayLazy: Lazy<MultiDisplaySystemEventChipAnimationController>,
+ ): SystemEventChipAnimationController {
+ return if (StatusBarConnectedDisplays.isEnabled) {
+ multiDisplayLazy.get()
+ } else {
+ defaultLazy.get()
+ }
+ }
}
}
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..dceee22 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);
};
}
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..b5b3162
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.kt
@@ -0,0 +1,69 @@
+/*
+ * 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 setBiometricUnlockController(biometricUnlockController: BiometricUnlockController)
+
+ 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 82%
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..6ff9f4c 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,10 +67,13 @@
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 final NavigationModeController mNavModeController;
+ private final DumpManager mDumpManager;
+ private final StatusBarModePerDisplayRepository mStatusBarModeRepository;
+ private final CoroutineContext mMainContext;
private BiometricUnlockController mBiometricUnlockController;
private LightBarTransitionsController mNavigationBarController;
@@ -119,42 +124,59 @@
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) {
+ 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;
+ 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();
}
+ @Override
public void setBiometricUnlockController(
BiometricUnlockController biometricUnlockController) {
mBiometricUnlockController = biometricUnlockController;
@@ -202,6 +224,7 @@
mNavbarColorManagedByIme = navbarColorManagedByIme;
}
+ @Override
public void onNavigationBarAppearanceChanged(@Appearance int appearance, boolean nbModeChanged,
int navigationBarMode, boolean navbarColorManagedByIme) {
int diff = appearance ^ mAppearance;
@@ -244,6 +267,7 @@
mNavbarColorManagedByIme = navbarColorManagedByIme;
}
+ @Override
public void onNavigationBarModeChanged(int newBarMode) {
mHasLightNavigationBar = isLight(mAppearance, newBarMode, APPEARANCE_LIGHT_NAVIGATION_BARS);
}
@@ -258,30 +282,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 +325,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;
@@ -387,20 +407,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 +485,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 23f3482..94de3510 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
@@ -32,6 +32,7 @@
import com.android.systemui.statusbar.core.StatusBarOrchestrator
import com.android.systemui.statusbar.core.StatusBarSimpleFragment
import com.android.systemui.statusbar.data.repository.PrivacyDotViewControllerStoreModule
+import com.android.systemui.statusbar.data.repository.PrivacyDotWindowControllerStoreModule
import com.android.systemui.statusbar.data.repository.StatusBarModeRepositoryStore
import com.android.systemui.statusbar.events.PrivacyDotViewControllerModule
import com.android.systemui.statusbar.phone.CentralSurfacesCommandQueueCallbacks
@@ -48,7 +49,12 @@
/** Similar in purpose to [StatusBarModule], but scoped only to phones */
@Module(
- includes = [PrivacyDotViewControllerModule::class, PrivacyDotViewControllerStoreModule::class]
+ includes =
+ [
+ PrivacyDotViewControllerModule::class,
+ PrivacyDotWindowControllerStoreModule::class,
+ PrivacyDotViewControllerStoreModule::class,
+ ]
)
interface StatusBarPhoneModule {
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/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/systemui/animation/TransitionAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/TransitionAnimatorTest.kt
index 071acfa..288ed4d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/TransitionAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/TransitionAnimatorTest.kt
@@ -77,7 +77,10 @@
@get:Rule(order = 2) val animatorTestRule = android.animation.AnimatorTestRule(this)
@get:Rule(order = 3)
val motionRule =
- MotionTestRule(AnimatorTestRuleToolkit(animatorTestRule, kosmos.testScope), pathManager)
+ MotionTestRule(
+ AnimatorTestRuleToolkit(animatorTestRule, kosmos.testScope) { activityRule.scenario },
+ pathManager,
+ )
@Test
fun backgroundAnimation_whenLaunching() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
index 6069b44..05ec85a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
@@ -729,7 +729,9 @@
}
@Test
- @DisableFlags({FooterViewRefactor.FLAG_NAME, ModesEmptyShadeFix.FLAG_NAME})
+ @DisableFlags({FooterViewRefactor.FLAG_NAME,
+ ModesEmptyShadeFix.FLAG_NAME,
+ NotifRedesignFooter.FLAG_NAME})
public void testReInflatesFooterViews() {
when(mEmptyShadeView.getTextResource()).thenReturn(R.string.empty_shade_text);
clearInvocations(mStackScroller);
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..11b5603 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,7 +175,6 @@
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.notification.NotifPipelineFlags;
import com.android.systemui.statusbar.notification.NotificationActivityStarter;
@@ -371,7 +371,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();
@@ -602,6 +602,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/android/view/FakeWindowManager.kt b/packages/SystemUI/tests/utils/src/android/view/FakeWindowManager.kt
new file mode 100644
index 0000000..c28449f
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/android/view/FakeWindowManager.kt
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view
+
+import android.content.Context
+import android.graphics.Region
+import android.view.WindowManager.LayoutParams
+
+class FakeWindowManager(private val context: Context) : WindowManager {
+
+ val addedViews = mutableMapOf<View, LayoutParams>()
+
+ override fun addView(view: View, params: ViewGroup.LayoutParams) {
+ addedViews[view] = params as LayoutParams
+ }
+
+ override fun removeView(view: View) {
+ addedViews.remove(view)
+ }
+
+ override fun updateViewLayout(view: View, params: ViewGroup.LayoutParams) {
+ addedViews[view] = params as LayoutParams
+ }
+
+ override fun getApplicationLaunchKeyboardShortcuts(deviceId: Int): KeyboardShortcutGroup {
+ return KeyboardShortcutGroup("Fake group")
+ }
+
+ override fun getCurrentImeTouchRegion(): Region {
+ return Region.obtain()
+ }
+
+ override fun getDefaultDisplay(): Display {
+ return context.display
+ }
+
+ override fun removeViewImmediate(view: View) {
+ addedViews.remove(view)
+ }
+
+ override fun requestAppKeyboardShortcuts(
+ receiver: WindowManager.KeyboardShortcutsReceiver,
+ deviceId: Int,
+ ) {
+ receiver.onKeyboardShortcutsReceived(emptyList())
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/android/view/WindowManagerKosmos.kt b/packages/SystemUI/tests/utils/src/android/view/WindowManagerKosmos.kt
index d5451ee..025f556 100644
--- a/packages/SystemUI/tests/utils/src/android/view/WindowManagerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/android/view/WindowManagerKosmos.kt
@@ -16,9 +16,12 @@
package android.view
+import android.content.applicationContext
import com.android.systemui.kosmos.Kosmos
import org.mockito.Mockito.mock
+val Kosmos.fakeWindowManager by Kosmos.Fixture { FakeWindowManager(applicationContext) }
+
val Kosmos.mockWindowManager: WindowManager by Kosmos.Fixture { mock(WindowManager::class.java) }
var Kosmos.windowManager: WindowManager by Kosmos.Fixture { mockWindowManager }
diff --git a/packages/SystemUI/tests/utils/src/com/android/app/viewcapture/ViewCaptureAwareWindowManagerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/app/viewcapture/ViewCaptureAwareWindowManagerKosmos.kt
index e1c6699..021c7bb 100644
--- a/packages/SystemUI/tests/utils/src/com/android/app/viewcapture/ViewCaptureAwareWindowManagerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/app/viewcapture/ViewCaptureAwareWindowManagerKosmos.kt
@@ -16,11 +16,21 @@
package com.android.app.viewcapture
+import android.view.fakeWindowManager
import com.android.systemui.kosmos.Kosmos
import org.mockito.kotlin.mock
val Kosmos.mockViewCaptureAwareWindowManager by
Kosmos.Fixture { mock<ViewCaptureAwareWindowManager>() }
+val Kosmos.realCaptureAwareWindowManager by
+ Kosmos.Fixture {
+ ViewCaptureAwareWindowManager(
+ fakeWindowManager,
+ lazyViewCapture = lazy { mock<ViewCapture>() },
+ isViewCaptureEnabled = false,
+ )
+ }
+
var Kosmos.viewCaptureAwareWindowManager: ViewCaptureAwareWindowManager by
Kosmos.Fixture { mockViewCaptureAwareWindowManager }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestableContext.java b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestableContext.java
index 3041240..b8be6aa 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestableContext.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestableContext.java
@@ -29,6 +29,8 @@
import android.util.Log;
import android.view.Display;
+import androidx.annotation.Nullable;
+
import com.android.internal.annotations.GuardedBy;
import com.android.systemui.res.R;
@@ -43,6 +45,9 @@
private final Map<UserHandle, Context> mContextForUser = new HashMap<>();
private final Map<String, Context> mContextForPackage = new HashMap<>();
+ @Nullable
+ private Display mCustomDisplay;
+
public SysuiTestableContext(Context base) {
super(base);
setTheme(R.style.Theme_SystemUI);
@@ -64,6 +69,18 @@
return context;
}
+ public void setDisplay(Display display) {
+ mCustomDisplay = display;
+ }
+
+ @Override
+ public Display getDisplay() {
+ if (mCustomDisplay != null) {
+ return mCustomDisplay;
+ }
+ return super.getDisplay();
+ }
+
public SysuiTestableContext createDefaultDisplayContext() {
Display display = getBaseContext().getSystemService(DisplayManager.class).getDisplays()[0];
return (SysuiTestableContext) createDisplayContext(display);
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/decor/PrivacyDotDecorProviderFactoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/decor/PrivacyDotDecorProviderFactoryKosmos.kt
new file mode 100644
index 0000000..4bcff55
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/decor/PrivacyDotDecorProviderFactoryKosmos.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.decor
+
+import android.content.testableContext
+import com.android.systemui.kosmos.Kosmos
+
+var Kosmos.privacyDotDecorProviderFactory by
+ Kosmos.Fixture {
+ PrivacyDotDecorProviderFactory(testableContext.orCreateTestableResources.resources)
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarOrchestratorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarOrchestratorKosmos.kt
index 87f7142..ad2654a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarOrchestratorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarOrchestratorKosmos.kt
@@ -29,6 +29,7 @@
import com.android.systemui.shade.mockNotificationShadeWindowViewController
import com.android.systemui.shade.mockShadeSurface
import com.android.systemui.statusbar.data.repository.fakeStatusBarModePerDisplayRepository
+import com.android.systemui.statusbar.data.repository.privacyDotWindowControllerStore
import com.android.systemui.statusbar.data.repository.statusBarModeRepository
import com.android.systemui.statusbar.mockNotificationRemoteInputManager
import com.android.systemui.statusbar.phone.mockAutoHideController
@@ -77,5 +78,6 @@
statusBarInitializerStore,
statusBarWindowControllerStore,
statusBarInitializerStore,
+ privacyDotWindowControllerStore,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakePrivacyDotViewControllerStore.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakePrivacyDotViewControllerStore.kt
new file mode 100644
index 0000000..27845aa
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakePrivacyDotViewControllerStore.kt
@@ -0,0 +1,32 @@
+/*
+ * 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
+import com.android.systemui.statusbar.events.PrivacyDotViewController
+import org.mockito.kotlin.mock
+
+class FakePrivacyDotViewControllerStore : PrivacyDotViewControllerStore {
+ private val perDisplayMockControllers = mutableMapOf<Int, PrivacyDotViewController>()
+
+ override val defaultDisplay: PrivacyDotViewController
+ get() = forDisplay(Display.DEFAULT_DISPLAY)
+
+ override fun forDisplay(displayId: Int): PrivacyDotViewController {
+ return perDisplayMockControllers.computeIfAbsent(displayId) { mock() }
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakePrivacyDotWindowControllerStore.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakePrivacyDotWindowControllerStore.kt
new file mode 100644
index 0000000..f0aacc0d
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakePrivacyDotWindowControllerStore.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.statusbar.data.repository
+
+import android.view.Display
+import com.android.systemui.statusbar.events.PrivacyDotWindowController
+import org.mockito.kotlin.mock
+
+class FakePrivacyDotWindowControllerStore : PrivacyDotWindowControllerStore {
+
+ private val perDisplayMockControllers = mutableMapOf<Int, PrivacyDotWindowController>()
+
+ override val defaultDisplay: PrivacyDotWindowController
+ get() = forDisplay(Display.DEFAULT_DISPLAY)
+
+ override fun forDisplay(displayId: Int): PrivacyDotWindowController {
+ return perDisplayMockControllers.computeIfAbsent(displayId) { mock() }
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeSystemEventChipAnimationControllerStore.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeSystemEventChipAnimationControllerStore.kt
new file mode 100644
index 0000000..fa9f1bf
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeSystemEventChipAnimationControllerStore.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.statusbar.data.repository
+
+import android.view.Display
+import com.android.systemui.statusbar.events.SystemEventChipAnimationController
+import org.mockito.kotlin.mock
+
+class FakeSystemEventChipAnimationControllerStore : SystemEventChipAnimationControllerStore {
+
+ private val perDisplayMocks = mutableMapOf<Int, SystemEventChipAnimationController>()
+
+ override val defaultDisplay: SystemEventChipAnimationController
+ get() = forDisplay(Display.DEFAULT_DISPLAY)
+
+ override fun forDisplay(displayId: Int): SystemEventChipAnimationController {
+ return perDisplayMocks.computeIfAbsent(displayId) { mock() }
+ }
+}
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/PrivacyDotViewControllerStoreKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/PrivacyDotViewControllerStoreKosmos.kt
new file mode 100644
index 0000000..3d428a1
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/PrivacyDotViewControllerStoreKosmos.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.data.repository
+
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.fakePrivacyDotViewControllerStore by
+ Kosmos.Fixture { FakePrivacyDotViewControllerStore() }
+
+var Kosmos.privacyDotViewControllerStore: PrivacyDotViewControllerStore by
+ Kosmos.Fixture { fakePrivacyDotViewControllerStore }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/PrivacyDotWindowControllerStoreKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/PrivacyDotWindowControllerStoreKosmos.kt
new file mode 100644
index 0000000..aae32cfa
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/PrivacyDotWindowControllerStoreKosmos.kt
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.data.repository
+
+import android.view.WindowManager
+import com.android.app.viewcapture.ViewCaptureAwareWindowManager
+import com.android.systemui.display.data.repository.displayRepository
+import com.android.systemui.display.data.repository.displayWindowPropertiesRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import org.mockito.kotlin.mock
+
+val Kosmos.fakePrivacyDotWindowControllerStore by
+ Kosmos.Fixture { FakePrivacyDotWindowControllerStore() }
+
+val Kosmos.privacyDotWindowControllerStoreImpl by
+ Kosmos.Fixture {
+ PrivacyDotWindowControllerStoreImpl(
+ backgroundApplicationScope = applicationCoroutineScope,
+ displayRepository = displayRepository,
+ windowControllerFactory = { _, _, _, _ -> mock() },
+ displayWindowPropertiesRepository = displayWindowPropertiesRepository,
+ privacyDotViewControllerStore = privacyDotViewControllerStore,
+ viewCaptureAwareWindowManagerFactory =
+ object : ViewCaptureAwareWindowManager.Factory {
+ override fun create(
+ windowManager: WindowManager
+ ): ViewCaptureAwareWindowManager {
+ return mock()
+ }
+ },
+ )
+ }
+
+var Kosmos.privacyDotWindowControllerStore: PrivacyDotWindowControllerStore by
+ Kosmos.Fixture { fakePrivacyDotWindowControllerStore }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/SystemEventChipAnimationControllerStoreKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/SystemEventChipAnimationControllerStoreKosmos.kt
new file mode 100644
index 0000000..f0c8f4b
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/SystemEventChipAnimationControllerStoreKosmos.kt
@@ -0,0 +1,42 @@
+/*
+ * 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.displayWindowPropertiesRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.statusbar.window.statusBarWindowControllerStore
+import org.mockito.kotlin.mock
+
+val Kosmos.fakeSystemEventChipAnimationControllerStore by
+ Kosmos.Fixture { FakeSystemEventChipAnimationControllerStore() }
+
+val Kosmos.systemEventChipAnimationControllerStoreImpl by
+ Kosmos.Fixture {
+ SystemEventChipAnimationControllerStoreImpl(
+ backgroundApplicationScope = applicationCoroutineScope,
+ displayRepository = displayRepository,
+ factory = { _, _, _ -> mock() },
+ displayWindowPropertiesRepository = displayWindowPropertiesRepository,
+ statusBarWindowControllerStore = statusBarWindowControllerStore,
+ statusBarContentInsetsProviderStore = statusBarContentInsetsProviderStore,
+ )
+ }
+
+var Kosmos.systemEventChipAnimationControllerStore by
+ Kosmos.Fixture { fakeSystemEventChipAnimationControllerStore }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/events/FakePrivacyDotViewController.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/events/FakePrivacyDotViewController.kt
new file mode 100644
index 0000000..53c39a6
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/events/FakePrivacyDotViewController.kt
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.events
+
+import android.view.View
+
+class FakePrivacyDotViewController : PrivacyDotViewController {
+
+ var topLeft: View? = null
+ private set
+
+ var topRight: View? = null
+ private set
+
+ var bottomLeft: View? = null
+ private set
+
+ var bottomRight: View? = null
+ private set
+
+ var isInitialized = false
+ private set
+
+ override fun stop() {}
+
+ override var currentViewState: ViewState = ViewState()
+
+ override var showingListener: PrivacyDotViewController.ShowingListener? = null
+
+ override fun setNewRotation(rot: Int) {}
+
+ override fun hideDotView(dot: View, animate: Boolean) {}
+
+ override fun showDotView(dot: View, animate: Boolean) {}
+
+ override fun updateRotations(rotation: Int, paddingTop: Int) {}
+
+ override fun setCornerSizes(state: ViewState) {}
+
+ override fun initialize(topLeft: View, topRight: View, bottomLeft: View, bottomRight: View) {
+ this.topLeft = topLeft
+ this.topRight = topRight
+ this.bottomLeft = bottomLeft
+ this.bottomRight = bottomRight
+ isInitialized = true
+ }
+
+ override fun updateDotView(state: ViewState) {}
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/events/PrivacyDotViewControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/events/PrivacyDotViewControllerKosmos.kt
new file mode 100644
index 0000000..9cbc975
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/events/PrivacyDotViewControllerKosmos.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.events
+
+import com.android.systemui.kosmos.Kosmos
+import org.mockito.kotlin.mock
+
+val Kosmos.mockPrivacyDotViewController by Kosmos.Fixture { mock<PrivacyDotViewController>() }
+
+val Kosmos.fakePrivacyDotViewController by Kosmos.Fixture { FakePrivacyDotViewController() }
+
+var Kosmos.privacyDotViewController by Kosmos.Fixture { fakePrivacyDotViewController }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/events/PrivacyDotWindowControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/events/PrivacyDotWindowControllerKosmos.kt
new file mode 100644
index 0000000..c738387
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/events/PrivacyDotWindowControllerKosmos.kt
@@ -0,0 +1,36 @@
+/*
+ * 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.events
+
+import android.content.testableContext
+import android.view.layoutInflater
+import com.android.app.viewcapture.realCaptureAwareWindowManager
+import com.android.systemui.concurrency.fakeExecutor
+import com.android.systemui.decor.privacyDotDecorProviderFactory
+import com.android.systemui.kosmos.Kosmos
+
+var Kosmos.privacyDotWindowController by
+ Kosmos.Fixture {
+ PrivacyDotWindowController(
+ testableContext.displayId,
+ privacyDotViewController,
+ realCaptureAwareWindowManager,
+ layoutInflater,
+ fakeExecutor,
+ privacyDotDecorProviderFactory,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/events/SystemEventChipAnimationControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/events/SystemEventChipAnimationControllerKosmos.kt
new file mode 100644
index 0000000..186b045
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/events/SystemEventChipAnimationControllerKosmos.kt
@@ -0,0 +1,29 @@
+/*
+ * 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.events
+
+import com.android.systemui.display.data.repository.displayRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.statusbar.data.repository.systemEventChipAnimationControllerStore
+
+val Kosmos.multiDisplaySystemEventChipAnimationController by
+ Kosmos.Fixture {
+ MultiDisplaySystemEventChipAnimationController(
+ displayRepository,
+ systemEventChipAnimationControllerStore,
+ )
+ }
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index de3e2c9..08632fe 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -114,6 +114,7 @@
import static com.android.server.am.ProcessList.PERCEPTIBLE_RECENT_FOREGROUND_APP_ADJ;
import static com.android.server.am.ProcessList.PERSISTENT_SERVICE_ADJ;
import static com.android.server.am.ProcessList.PREVIOUS_APP_ADJ;
+import static com.android.server.am.ProcessList.PREVIOUS_APP_MAX_ADJ;
import static com.android.server.am.ProcessList.SCHED_GROUP_BACKGROUND;
import static com.android.server.am.ProcessList.SCHED_GROUP_DEFAULT;
import static com.android.server.am.ProcessList.SCHED_GROUP_FOREGROUND_WINDOW;
@@ -696,7 +697,7 @@
// In case the app goes from non-cached to cached but it doesn't have other reachable
// processes, its adj could be still unknown as of now, assign one.
processes.add(app);
- assignCachedAdjIfNecessary(processes);
+ applyLruAdjust(processes);
applyOomAdjLSP(app, false, mInjector.getUptimeMillis(),
mInjector.getElapsedRealtimeMillis(), oomAdjReason);
}
@@ -1086,7 +1087,7 @@
}
mProcessesInCycle.clear();
- assignCachedAdjIfNecessary(mProcessList.getLruProcessesLOSP());
+ applyLruAdjust(mProcessList.getLruProcessesLOSP());
postUpdateOomAdjInnerLSP(oomAdjReason, activeUids, now, nowElapsed, oldTime, true);
@@ -1148,8 +1149,9 @@
}
@GuardedBy({"mService", "mProcLock"})
- protected void assignCachedAdjIfNecessary(ArrayList<ProcessRecord> lruList) {
+ protected void applyLruAdjust(ArrayList<ProcessRecord> lruList) {
final int numLru = lruList.size();
+ int nextPreviousAppAdj = PREVIOUS_APP_ADJ;
if (mConstants.USE_TIERED_CACHED_ADJ) {
final long now = mInjector.getUptimeMillis();
int uiTargetAdj = 10;
@@ -1159,9 +1161,12 @@
ProcessRecord app = lruList.get(i);
final ProcessStateRecord state = app.mState;
final ProcessCachedOptimizerRecord opt = app.mOptRecord;
- if (!app.isKilledByAm() && app.getThread() != null
- && (state.getCurAdj() >= UNKNOWN_ADJ
- || (state.hasShownUi() && state.getCurAdj() >= CACHED_APP_MIN_ADJ))) {
+ final int curAdj = state.getCurAdj();
+ if (PREVIOUS_APP_ADJ <= curAdj && curAdj <= PREVIOUS_APP_MAX_ADJ) {
+ state.setCurAdj(nextPreviousAppAdj);
+ nextPreviousAppAdj = Math.min(nextPreviousAppAdj + 1, PREVIOUS_APP_MAX_ADJ);
+ } else if (!app.isKilledByAm() && app.getThread() != null && (curAdj >= UNKNOWN_ADJ
+ || (state.hasShownUi() && curAdj >= CACHED_APP_MIN_ADJ))) {
final ProcessServiceRecord psr = app.mServices;
int targetAdj = CACHED_APP_MIN_ADJ;
@@ -1228,10 +1233,13 @@
for (int i = numLru - 1; i >= 0; i--) {
ProcessRecord app = lruList.get(i);
final ProcessStateRecord state = app.mState;
- // If we haven't yet assigned the final cached adj
- // to the process, do that now.
- if (!app.isKilledByAm() && app.getThread() != null && state.getCurAdj()
- >= UNKNOWN_ADJ) {
+ final int curAdj = state.getCurAdj();
+ if (PREVIOUS_APP_ADJ <= curAdj && curAdj <= PREVIOUS_APP_MAX_ADJ) {
+ state.setCurAdj(nextPreviousAppAdj);
+ nextPreviousAppAdj = Math.min(nextPreviousAppAdj + 1, PREVIOUS_APP_MAX_ADJ);
+ } else if (!app.isKilledByAm() && app.getThread() != null
+ && curAdj >= UNKNOWN_ADJ) {
+ // If we haven't yet assigned the final cached adj to the process, do that now.
final ProcessServiceRecord psr = app.mServices;
switch (state.getCurProcState()) {
case PROCESS_STATE_LAST_ACTIVITY:
diff --git a/services/core/java/com/android/server/am/OomAdjusterModernImpl.java b/services/core/java/com/android/server/am/OomAdjusterModernImpl.java
index e452c45..8b66055 100644
--- a/services/core/java/com/android/server/am/OomAdjusterModernImpl.java
+++ b/services/core/java/com/android/server/am/OomAdjusterModernImpl.java
@@ -54,6 +54,7 @@
import static com.android.server.am.ProcessList.PERSISTENT_PROC_ADJ;
import static com.android.server.am.ProcessList.PERSISTENT_SERVICE_ADJ;
import static com.android.server.am.ProcessList.PREVIOUS_APP_ADJ;
+import static com.android.server.am.ProcessList.PREVIOUS_APP_MAX_ADJ;
import static com.android.server.am.ProcessList.SCHED_GROUP_BACKGROUND;
import static com.android.server.am.ProcessList.SERVICE_ADJ;
import static com.android.server.am.ProcessList.SERVICE_B_ADJ;
@@ -968,7 +969,7 @@
mTmpOomAdjusterArgs.update(topApp, now, UNKNOWN_ADJ, oomAdjReason, null, true);
computeConnectionsLSP();
- assignCachedAdjIfNecessary(mProcessList.getLruProcessesLOSP());
+ applyLruAdjust(mProcessList.getLruProcessesLOSP());
postUpdateOomAdjInnerLSP(oomAdjReason, mActiveUids, now, nowElapsed, oldTime, true);
}
@@ -1049,20 +1050,24 @@
// Now traverse and compute the connections of processes with changed importance.
computeConnectionsLSP();
- boolean unassignedAdj = false;
+ boolean needLruAdjust = false;
for (int i = 0, size = reachables.size(); i < size; i++) {
final ProcessStateRecord state = reachables.get(i).mState;
state.setReachable(false);
state.setCompletedAdjSeq(mAdjSeq);
- if (state.getCurAdj() >= UNKNOWN_ADJ) {
- unassignedAdj = true;
+ final int curAdj = state.getCurAdj();
+ // Processes assigned the PREV oomscore will have a laddered oomscore with respect to
+ // their positions in the LRU list. i.e. prev+0, prev+1, prev+2, etc.
+ final boolean isPrevApp = PREVIOUS_APP_ADJ <= curAdj && curAdj <= PREVIOUS_APP_MAX_ADJ;
+ if (curAdj >= UNKNOWN_ADJ || (Flags.oomadjusterPrevLaddering() && isPrevApp)) {
+ needLruAdjust = true;
}
}
// If all processes have an assigned adj, no need to calculate and assign cached adjs.
- if (unassignedAdj) {
+ if (needLruAdjust) {
// TODO: b/319163103 - optimize cache adj assignment to not require the whole lru list.
- assignCachedAdjIfNecessary(mProcessList.getLruProcessesLOSP());
+ applyLruAdjust(mProcessList.getLruProcessesLOSP());
}
// Repopulate any uid record that may have changed.
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index cdb0188..f86474f 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -225,6 +225,7 @@
// UI flow such as clicking on a URI in the e-mail app to view in the browser,
// and then pressing back to return to e-mail.
public static final int PREVIOUS_APP_ADJ = 700;
+ public static final int PREVIOUS_APP_MAX_ADJ = Flags.oomadjusterPrevLaddering() ? 799 : 700;
// This is a process holding the home application -- we want to try
// avoiding killing it, even if it would normally be in the background,
diff --git a/services/core/java/com/android/server/am/flags.aconfig b/services/core/java/com/android/server/am/flags.aconfig
index 56cfdfb..7b4d6c7 100644
--- a/services/core/java/com/android/server/am/flags.aconfig
+++ b/services/core/java/com/android/server/am/flags.aconfig
@@ -242,4 +242,12 @@
metadata {
purpose: PURPOSE_BUGFIX
}
+}
+
+flag {
+ name: "oomadjuster_prev_laddering"
+ namespace: "system_performance"
+ is_fixed_read_only: true
+ description: "Add +X to the prev scores according to their positions in the process LRU list"
+ bug: "359912586"
}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/audio/MediaFocusControl.java b/services/core/java/com/android/server/audio/MediaFocusControl.java
index 1da62d7..1604e94 100644
--- a/services/core/java/com/android/server/audio/MediaFocusControl.java
+++ b/services/core/java/com/android/server/audio/MediaFocusControl.java
@@ -1068,6 +1068,7 @@
switch (attr.getUsage()) {
case AudioAttributes.USAGE_MEDIA:
case AudioAttributes.USAGE_GAME:
+ case AudioAttributes.USAGE_SPEAKER_CLEANUP:
return 1000;
case AudioAttributes.USAGE_ALARM:
case AudioAttributes.USAGE_NOTIFICATION_RINGTONE:
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index 26929f5..eefa15e 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -64,7 +64,6 @@
import android.hardware.input.IStickyModifierStateListener;
import android.hardware.input.ITabletModeChangedListener;
import android.hardware.input.InputDeviceIdentifier;
-import android.hardware.input.InputGestureData;
import android.hardware.input.InputManager;
import android.hardware.input.InputSensorInfo;
import android.hardware.input.InputSettings;
@@ -2313,6 +2312,12 @@
// Native callback.
@SuppressWarnings("unused")
+ private void notifyTouchpadThreeFingerTap() {
+ mKeyGestureController.handleTouchpadThreeFingerTap();
+ }
+
+ // Native callback.
+ @SuppressWarnings("unused")
private void notifySwitch(long whenNanos, int switchValues, int switchMask) {
if (DEBUG) {
Slog.d(TAG, "notifySwitch: values=" + Integer.toHexString(switchValues)
diff --git a/services/core/java/com/android/server/input/InputSettingsObserver.java b/services/core/java/com/android/server/input/InputSettingsObserver.java
index d1a6d3b..420db90 100644
--- a/services/core/java/com/android/server/input/InputSettingsObserver.java
+++ b/services/core/java/com/android/server/input/InputSettingsObserver.java
@@ -143,6 +143,10 @@
observer.accept("just booted");
}
+ // TODO(b/365063048): add an entry to mObservers that calls this instead, once we have a
+ // setting that can be observed.
+ updateTouchpadThreeFingerTapShortcutEnabled();
+
configureUserActivityPokeInterval();
}
@@ -205,6 +209,11 @@
mNative.setTouchpadRightClickZoneEnabled(InputSettings.useTouchpadRightClickZone(mContext));
}
+ private void updateTouchpadThreeFingerTapShortcutEnabled() {
+ mNative.setTouchpadThreeFingerTapShortcutEnabled(
+ InputSettings.useTouchpadThreeFingerTapShortcut(mContext));
+ }
+
private void updateShowTouches() {
mNative.setShowTouches(getBoolean(Settings.System.SHOW_TOUCHES, false));
}
diff --git a/services/core/java/com/android/server/input/KeyGestureController.java b/services/core/java/com/android/server/input/KeyGestureController.java
index ebeef65..96ef070 100644
--- a/services/core/java/com/android/server/input/KeyGestureController.java
+++ b/services/core/java/com/android/server/input/KeyGestureController.java
@@ -1134,6 +1134,13 @@
handleKeyGesture(event, null /*focusedToken*/);
}
+ public void handleTouchpadThreeFingerTap() {
+ // TODO(b/365063048): trigger a custom shortcut based on the three-finger tap.
+ if (DEBUG) {
+ Slog.d(TAG, "Three-finger touchpad tap occurred");
+ }
+ }
+
@MainThread
public void setCurrentUserId(@UserIdInt int userId) {
synchronized (mUserLock) {
diff --git a/services/core/java/com/android/server/input/NativeInputManagerService.java b/services/core/java/com/android/server/input/NativeInputManagerService.java
index 283fdea..8903c27 100644
--- a/services/core/java/com/android/server/input/NativeInputManagerService.java
+++ b/services/core/java/com/android/server/input/NativeInputManagerService.java
@@ -143,6 +143,8 @@
void setTouchpadRightClickZoneEnabled(boolean enabled);
+ void setTouchpadThreeFingerTapShortcutEnabled(boolean enabled);
+
void setShowTouches(boolean enabled);
void setNonInteractiveDisplays(int[] displayIds);
@@ -427,6 +429,9 @@
public native void setTouchpadRightClickZoneEnabled(boolean enabled);
@Override
+ public native void setTouchpadThreeFingerTapShortcutEnabled(boolean enabled);
+
+ @Override
public native void setShowTouches(boolean enabled);
@Override
diff --git a/services/core/java/com/android/server/media/quality/MediaQualityService.java b/services/core/java/com/android/server/media/quality/MediaQualityService.java
index e15d414..a45ea1d 100644
--- a/services/core/java/com/android/server/media/quality/MediaQualityService.java
+++ b/services/core/java/com/android/server/media/quality/MediaQualityService.java
@@ -17,6 +17,8 @@
package com.android.server.media.quality;
import android.content.Context;
+import android.media.quality.AmbientBacklightSettings;
+import android.media.quality.IAmbientBacklightCallback;
import android.media.quality.IMediaQualityManager;
import android.media.quality.IPictureProfileCallback;
import android.media.quality.ISoundProfileCallback;
@@ -119,6 +121,17 @@
public void registerSoundProfileCallback(final ISoundProfileCallback callback) {
}
+ @Override
+ public void registerAmbientBacklightCallback(IAmbientBacklightCallback callback) {
+ }
+
+ @Override
+ public void setAmbientBacklightSettings(AmbientBacklightSettings settings) {
+ }
+
+ @Override
+ public void setAmbientBacklightEnabled(boolean enabled) {
+ }
@Override
public List<ParamCapability> getParamCapabilities(List<String> names) {
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 7963d09..198e14a 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -1854,6 +1854,8 @@
}
assertPackageMatchesCallingUid(callingPackage);
+ mAmInternal.addCreatorToken(intent, callingPackage);
+
final ActivityOptions activityOptions = ActivityOptions.makeBasic();
activityOptions.setLaunchTaskId(taskId);
// Pass in the system UID to allow setting launch taskId with MANAGE_GAME_ACTIVITY.
diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java
index 2fe023e..4ed8b09 100644
--- a/services/core/java/com/android/server/wm/BackNavigationController.java
+++ b/services/core/java/com/android/server/wm/BackNavigationController.java
@@ -26,6 +26,8 @@
import static android.view.WindowManager.TRANSIT_OLD_NONE;
import static android.view.WindowManager.TRANSIT_PREPARE_BACK_NAVIGATION;
import static android.view.WindowManager.TRANSIT_TO_BACK;
+import static android.window.SystemOverrideOnBackInvokedCallback.OVERRIDE_FINISH_AND_REMOVE_TASK;
+import static android.window.SystemOverrideOnBackInvokedCallback.OVERRIDE_UNDEFINED;
import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_BACK_PREVIEW;
import static com.android.server.wm.BackNavigationProto.ANIMATION_IN_PROGRESS;
@@ -60,6 +62,7 @@
import android.window.IBackAnimationFinishedCallback;
import android.window.IWindowlessStartingSurfaceCallback;
import android.window.OnBackInvokedCallbackInfo;
+import android.window.SystemOverrideOnBackInvokedCallback;
import android.window.TaskSnapshot;
import com.android.internal.annotations.VisibleForTesting;
@@ -226,10 +229,19 @@
&& callbackInfo.isAnimationCallback());
mNavigationMonitor.startMonitor(window, navigationObserver);
+ int requestOverride = callbackInfo.getOverrideBehavior();
ProtoLog.d(WM_DEBUG_BACK_PREVIEW, "startBackNavigation currentTask=%s, "
+ "topRunningActivity=%s, callbackInfo=%s, currentFocus=%s",
currentTask, currentActivity, callbackInfo, window);
-
+ if (requestOverride == OVERRIDE_FINISH_AND_REMOVE_TASK) {
+ final ActivityRecord rootR = currentTask != null ? currentTask.getRootActivity()
+ : null;
+ if (currentActivity != null && rootR != currentActivity) {
+ // The top activity is not root activity, the activity cannot remove task when
+ // finishAndRemoveTask called.
+ requestOverride = OVERRIDE_UNDEFINED;
+ }
+ }
// Clear the pointer down outside focus if any.
mWindowManagerService.clearPointerDownOutsideFocusRunnable();
@@ -274,7 +286,8 @@
} else if (hasTranslucentActivity(currentActivity, prevActivities)) {
// skip if one of participant activity is translucent
backType = BackNavigationInfo.TYPE_CALLBACK;
- } else if (prevActivities.size() > 0) {
+ } else if (prevActivities.size() > 0
+ && requestOverride == SystemOverrideOnBackInvokedCallback.OVERRIDE_UNDEFINED) {
if ((!isOccluded || isAllActivitiesCanShowWhenLocked(prevActivities))
&& isAllActivitiesCreated(prevActivities)) {
// We have another Activity in the same currentTask to go to
@@ -1025,6 +1038,12 @@
return;
}
+ if (mWindowManagerService.mRoot.mTransitionController.isCollecting()) {
+ Slog.v(TAG, "Skip predictive back transition, another transition is collecting");
+ cancelPendingAnimation();
+ return;
+ }
+
// Ensure the final animation targets which hidden by transition could be visible.
for (int i = 0; i < targets.size(); i++) {
final WindowContainer wc = targets.get(i).mContainer;
@@ -1053,6 +1072,7 @@
Slog.e(TAG, "Remote animation gone", e);
}
mPendingAnimationBuilder = null;
+ mNavigationMonitor.stopMonitorTransition();
}
/**
@@ -1550,6 +1570,9 @@
}
void createStartingSurface(@Nullable TaskSnapshot snapshot) {
+ if (Flags.deferPredictiveAnimationIfNoSnapshot() && snapshot == null) {
+ return;
+ }
if (mAdaptors[0].mSwitchType == DIALOG_CLOSE) {
return;
}
@@ -1851,7 +1874,7 @@
tc.requestStartTransition(prepareOpen,
null /*startTask */, null /* remoteTransition */,
null /* displayChange */);
- prepareOpen.setReady(makeVisibles.get(0), true);
+ prepareOpen.setReady(mCloseTarget, true);
return prepareOpen;
} else if (mSnapshot == null) {
return setLaunchBehind(visibleOpenActivities);
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index c7d57fe..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;
}
@@ -3053,7 +3053,7 @@
@InsetsType int insetsType) {
for (int i = insetsState.sourceSize() - 1; i >= 0; i--) {
final InsetsSource source = insetsState.sourceAt(i);
- if ((source.getType() & insetsType) == 0 || !source.isVisible()) {
+ if ((source.getType() & insetsType) == 0) {
continue;
}
if (Rect.intersects(bounds, source.getFrame())) {
diff --git a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
index e9c6e93..71adb80 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();
}
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/SnapshotController.java b/services/core/java/com/android/server/wm/SnapshotController.java
index 52994c7..3ee2e60 100644
--- a/services/core/java/com/android/server/wm/SnapshotController.java
+++ b/services/core/java/com/android/server/wm/SnapshotController.java
@@ -152,7 +152,10 @@
if (mOpenActivities.isEmpty()) {
return false;
}
- if (Flags.alwaysCaptureActivitySnapshot()) {
+ // TODO (b/362183912) always capture activity snapshot will cause performance
+ // regression, remove flag after ramp up
+ if (!Flags.deferPredictiveAnimationIfNoSnapshot()
+ && Flags.alwaysCaptureActivitySnapshot()) {
return true;
}
for (int i = mOpenActivities.size() - 1; i >= 0; --i) {
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index e13577c..7473acc 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -3918,7 +3918,9 @@
sb.append(" aI=");
sb.append(affinityIntent.getComponent().flattenToShortString());
}
- sb.append(" isResizeable=").append(isResizeable());
+ if (!isResizeable()) {
+ sb.append(" nonResizable");
+ }
if (mMinWidth != INVALID_MIN_SIZE || mMinHeight != INVALID_MIN_SIZE) {
sb.append(" minWidth=").append(mMinWidth);
sb.append(" minHeight=").append(mMinHeight);
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 166d74b..dac8f69 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -813,6 +813,10 @@
mService.mTaskSupervisor.setDeferRootVisibilityUpdate(false /* deferUpdate */);
if (deferResume) {
mService.mTaskSupervisor.endDeferResume();
+ // Transient launching the Recents via HIERARCHY_OP_TYPE_PENDING_INTENT directly
+ // resume the Recents activity with no TRANSACT_EFFECTS_LIFECYCLE. Explicitly
+ // checks if the top resumed activity should be updated after defer-resume ended.
+ mService.mTaskSupervisor.updateTopResumedActivityIfNeeded("endWCT");
}
mService.continueWindowLayout();
}
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index 416e60f..e4ac826 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -110,6 +110,7 @@
jmethodID notifyInputDevicesChanged;
jmethodID notifyTouchpadHardwareState;
jmethodID notifyTouchpadGestureInfo;
+ jmethodID notifyTouchpadThreeFingerTap;
jmethodID notifySwitch;
jmethodID notifyInputChannelBroken;
jmethodID notifyNoFocusedWindowAnr;
@@ -345,6 +346,7 @@
void setTouchpadTapDraggingEnabled(bool enabled);
void setShouldNotifyTouchpadHardwareState(bool enabled);
void setTouchpadRightClickZoneEnabled(bool enabled);
+ void setTouchpadThreeFingerTapShortcutEnabled(bool enabled);
void setInputDeviceEnabled(uint32_t deviceId, bool enabled);
void setShowTouches(bool enabled);
void setNonInteractiveDisplays(const std::set<ui::LogicalDisplayId>& displayIds);
@@ -370,6 +372,7 @@
void notifyTouchpadHardwareState(const SelfContainedHardwareState& schs,
int32_t deviceId) override;
void notifyTouchpadGestureInfo(enum GestureType type, int32_t deviceId) override;
+ void notifyTouchpadThreeFingerTap() override;
std::shared_ptr<KeyCharacterMap> getKeyboardLayoutOverlay(
const InputDeviceIdentifier& identifier,
const std::optional<KeyboardLayoutInfo> keyboardLayoutInfo) override;
@@ -510,6 +513,10 @@
// into context (a.k.a. "right") clicks.
bool touchpadRightClickZoneEnabled{false};
+ // True to use three-finger tap as a customizable shortcut; false to use it as a
+ // middle-click.
+ bool touchpadThreeFingerTapShortcutEnabled{false};
+
// True if a pointer icon should be shown for stylus pointers.
bool stylusPointerIconEnabled{false};
@@ -780,6 +787,8 @@
outConfig->touchpadTapDraggingEnabled = mLocked.touchpadTapDraggingEnabled;
outConfig->shouldNotifyTouchpadHardwareState = mLocked.shouldNotifyTouchpadHardwareState;
outConfig->touchpadRightClickZoneEnabled = mLocked.touchpadRightClickZoneEnabled;
+ outConfig->touchpadThreeFingerTapShortcutEnabled =
+ mLocked.touchpadThreeFingerTapShortcutEnabled;
outConfig->disabledDevices = mLocked.disabledInputDevices;
@@ -1034,6 +1043,13 @@
checkAndClearExceptionFromCallback(env, "notifyTouchpadGestureInfo");
}
+void NativeInputManager::notifyTouchpadThreeFingerTap() {
+ ATRACE_CALL();
+ JNIEnv* env = jniEnv();
+ env->CallVoidMethod(mServiceObj, gServiceClassInfo.notifyTouchpadThreeFingerTap);
+ checkAndClearExceptionFromCallback(env, "notifyTouchpadThreeFingerTap");
+}
+
std::shared_ptr<KeyCharacterMap> NativeInputManager::getKeyboardLayoutOverlay(
const InputDeviceIdentifier& identifier,
const std::optional<KeyboardLayoutInfo> keyboardLayoutInfo) {
@@ -1495,6 +1511,22 @@
InputReaderConfiguration::Change::TOUCHPAD_SETTINGS);
}
+void NativeInputManager::setTouchpadThreeFingerTapShortcutEnabled(bool enabled) {
+ { // acquire lock
+ std::scoped_lock _l(mLock);
+
+ if (mLocked.touchpadThreeFingerTapShortcutEnabled == enabled) {
+ return;
+ }
+
+ ALOGI("Setting touchpad three finger tap shortcut to %s.", toString(enabled));
+ mLocked.touchpadThreeFingerTapShortcutEnabled = enabled;
+ } // release lock
+
+ mInputManager->getReader().requestRefreshConfiguration(
+ InputReaderConfiguration::Change::TOUCHPAD_SETTINGS);
+}
+
void NativeInputManager::setInputDeviceEnabled(uint32_t deviceId, bool enabled) {
bool refresh = false;
@@ -2437,6 +2469,11 @@
im->setTouchpadRightClickZoneEnabled(enabled);
}
+static void nativeSetTouchpadThreeFingerTapShortcutEnabled(JNIEnv* env, jobject nativeImplObj,
+ jboolean enabled) {
+ getNativeInputManager(env, nativeImplObj)->setTouchpadThreeFingerTapShortcutEnabled(enabled);
+}
+
static void nativeSetShowTouches(JNIEnv* env, jobject nativeImplObj, jboolean enabled) {
NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
@@ -3119,6 +3156,8 @@
{"setShouldNotifyTouchpadHardwareState", "(Z)V",
(void*)nativeSetShouldNotifyTouchpadHardwareState},
{"setTouchpadRightClickZoneEnabled", "(Z)V", (void*)nativeSetTouchpadRightClickZoneEnabled},
+ {"setTouchpadThreeFingerTapShortcutEnabled", "(Z)V",
+ (void*)nativeSetTouchpadThreeFingerTapShortcutEnabled},
{"setShowTouches", "(Z)V", (void*)nativeSetShowTouches},
{"setNonInteractiveDisplays", "([I)V", (void*)nativeSetNonInteractiveDisplays},
{"reloadCalibration", "()V", (void*)nativeReloadCalibration},
@@ -3229,6 +3268,8 @@
GET_METHOD_ID(gServiceClassInfo.notifyTouchpadGestureInfo, clazz, "notifyTouchpadGestureInfo",
"(II)V")
+ GET_METHOD_ID(gServiceClassInfo.notifyTouchpadThreeFingerTap, clazz,
+ "notifyTouchpadThreeFingerTap", "()V")
GET_METHOD_ID(gServiceClassInfo.notifySwitch, clazz,
"notifySwitch", "(JII)V");
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
index b005358..4a131558 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
@@ -60,6 +60,7 @@
import static com.android.server.am.ProcessList.PERSISTENT_PROC_ADJ;
import static com.android.server.am.ProcessList.PERSISTENT_SERVICE_ADJ;
import static com.android.server.am.ProcessList.PREVIOUS_APP_ADJ;
+import static com.android.server.am.ProcessList.PREVIOUS_APP_MAX_ADJ;
import static com.android.server.am.ProcessList.SCHED_GROUP_BACKGROUND;
import static com.android.server.am.ProcessList.SCHED_GROUP_DEFAULT;
import static com.android.server.am.ProcessList.SCHED_GROUP_FOREGROUND_WINDOW;
@@ -129,6 +130,7 @@
import java.util.Collections;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Consumer;
/**
* Test class for {@link OomAdjuster}.
@@ -899,8 +901,25 @@
@SuppressWarnings("GuardedBy")
@Test
+ public void testUpdateOomAdj_DoPending_PreviousApp() {
+ testUpdateOomAdj_PreviousApp(apps -> {
+ for (ProcessRecord app : apps) {
+ mProcessStateController.enqueueUpdateTarget(app);
+ }
+ mProcessStateController.runPendingUpdate(OOM_ADJ_REASON_NONE);
+ });
+ }
+
+ @SuppressWarnings("GuardedBy")
+ @Test
public void testUpdateOomAdj_DoAll_PreviousApp() {
- final int numberOfApps = 15;
+ testUpdateOomAdj_PreviousApp(apps -> {
+ mProcessStateController.runFullUpdate(OOM_ADJ_REASON_NONE);
+ });
+ }
+
+ private void testUpdateOomAdj_PreviousApp(Consumer<ProcessRecord[]> updater) {
+ final int numberOfApps = 105;
final ProcessRecord[] apps = new ProcessRecord[numberOfApps];
for (int i = 0; i < numberOfApps; i++) {
apps[i] = spy(makeDefaultProcessRecord(MOCKAPP_PID + i, MOCKAPP_UID + i,
@@ -911,10 +930,11 @@
}
setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
setProcessesToLru(apps);
- mProcessStateController.runFullUpdate(OOM_ADJ_REASON_NONE);
-
+ updater.accept(apps);
for (int i = 0; i < numberOfApps; i++) {
- assertProcStates(apps[i], PROCESS_STATE_LAST_ACTIVITY, PREVIOUS_APP_ADJ,
+ final int mruIndex = numberOfApps - i - 1;
+ final int expectedAdj = Math.min(PREVIOUS_APP_ADJ + mruIndex, PREVIOUS_APP_MAX_ADJ);
+ assertProcStates(apps[i], PROCESS_STATE_LAST_ACTIVITY, expectedAdj,
SCHED_GROUP_BACKGROUND, "previous");
}
@@ -3184,7 +3204,8 @@
setProcessesToLru(app1, app2);
mProcessStateController.runFullUpdate(OOM_ADJ_REASON_NONE);
- assertProcStates(app1, PROCESS_STATE_LAST_ACTIVITY, PREVIOUS_APP_ADJ,
+ assertProcStates(app1, PROCESS_STATE_LAST_ACTIVITY,
+ PREVIOUS_APP_ADJ + (Flags.oomadjusterPrevLaddering() ? 1 : 0),
SCHED_GROUP_BACKGROUND, "recent-provider");
assertProcStates(app2, PROCESS_STATE_LAST_ACTIVITY, PREVIOUS_APP_ADJ,
SCHED_GROUP_BACKGROUND, "recent-provider");
diff --git a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
index 20dcdde..d6be915 100644
--- a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
@@ -24,7 +24,9 @@
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
+import static android.view.WindowManager.TRANSIT_PREPARE_BACK_NAVIGATION;
import static android.window.BackNavigationInfo.typeToString;
+import static android.window.SystemOverrideOnBackInvokedCallback.OVERRIDE_UNDEFINED;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
@@ -103,6 +105,13 @@
@Before
public void setUp() throws Exception {
+ final TransitionController transitionController = mAtm.getTransitionController();
+ final Transition fakeTransition = new Transition(TRANSIT_PREPARE_BACK_NAVIGATION,
+ 0 /* flag */, transitionController, transitionController.mSyncEngine);
+ spyOn(transitionController);
+ doReturn(fakeTransition).when(transitionController)
+ .createTransition(anyInt(), anyInt());
+
final BackNavigationController original = new BackNavigationController();
original.setWindowManager(mWm);
mBackNavigationController = Mockito.spy(original);
@@ -111,6 +120,7 @@
LocalServices.addService(WindowManagerInternal.class, mWindowManagerInternal);
mBackAnimationAdapter = mock(BackAnimationAdapter.class);
doReturn(true).when(mBackAnimationAdapter).isAnimatable(anyInt());
+ Mockito.doNothing().when(mBackNavigationController).startAnimation();
mNavigationMonitor = mock(BackNavigationController.NavigationMonitor.class);
mRootHomeTask = initHomeActivity();
}
@@ -446,7 +456,7 @@
new OnBackInvokedCallbackInfo(
callback,
OnBackInvokedDispatcher.PRIORITY_DEFAULT,
- /* isAnimationCallback = */ false));
+ /* isAnimationCallback = */ false, OVERRIDE_UNDEFINED));
BackNavigationInfo backNavigationInfo = startBackNavigation();
assertWithMessage("BackNavigationInfo").that(backNavigationInfo).isNotNull();
@@ -467,7 +477,7 @@
new OnBackInvokedCallbackInfo(
callback,
OnBackInvokedDispatcher.PRIORITY_DEFAULT,
- /* isAnimationCallback = */ true));
+ /* isAnimationCallback = */ true, OVERRIDE_UNDEFINED));
BackNavigationInfo backNavigationInfo = startBackNavigation();
assertWithMessage("BackNavigationInfo").that(backNavigationInfo).isNotNull();
@@ -608,7 +618,7 @@
new OnBackInvokedCallbackInfo(
callback,
OnBackInvokedDispatcher.PRIORITY_DEFAULT,
- /* isAnimationCallback = */ false));
+ /* isAnimationCallback = */ false, OVERRIDE_UNDEFINED));
BackNavigationInfo backNavigationInfo = startBackNavigation();
assertThat(backNavigationInfo).isNull();
@@ -722,7 +732,7 @@
new OnBackInvokedCallbackInfo(
callback,
OnBackInvokedDispatcher.PRIORITY_SYSTEM,
- /* isAnimationCallback = */ false));
+ /* isAnimationCallback = */ false, OVERRIDE_UNDEFINED));
return callback;
}
@@ -732,7 +742,7 @@
new OnBackInvokedCallbackInfo(
callback,
OnBackInvokedDispatcher.PRIORITY_DEFAULT,
- /* isAnimationCallback = */ false));
+ /* isAnimationCallback = */ false, OVERRIDE_UNDEFINED));
return callback;
}
diff --git a/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt b/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt
index 927958e..f2d3229 100644
--- a/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt
+++ b/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt
@@ -206,6 +206,7 @@
verify(native).setTouchpadTapDraggingEnabled(anyBoolean())
verify(native).setShouldNotifyTouchpadHardwareState(anyBoolean())
verify(native).setTouchpadRightClickZoneEnabled(anyBoolean())
+ verify(native).setTouchpadThreeFingerTapShortcutEnabled(anyBoolean())
verify(native).setShowTouches(anyBoolean())
verify(native).setMotionClassifierEnabled(anyBoolean())
verify(native).setMaximumObscuringOpacityForTouch(anyFloat())
diff --git a/tests/testables/src/android/animation/AnimatorTestRuleToolkit.kt b/tests/testables/src/android/animation/AnimatorTestRuleToolkit.kt
index b27b826..ded4679 100644
--- a/tests/testables/src/android/animation/AnimatorTestRuleToolkit.kt
+++ b/tests/testables/src/android/animation/AnimatorTestRuleToolkit.kt
@@ -17,7 +17,13 @@
package android.animation
import android.animation.AnimatorTestRuleToolkit.Companion.TAG
+import android.graphics.Bitmap
+import android.graphics.drawable.Drawable
import android.util.Log
+import android.view.View
+import androidx.core.graphics.drawable.toBitmap
+import androidx.test.core.app.ActivityScenario
+import java.util.concurrent.TimeUnit
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.MutableStateFlow
@@ -36,13 +42,45 @@
import platform.test.motion.golden.TimeSeries
import platform.test.motion.golden.TimeSeriesCaptureScope
import platform.test.motion.golden.TimestampFrameId
+import platform.test.screenshot.captureToBitmapAsync
-class AnimatorTestRuleToolkit(val animatorTestRule: AnimatorTestRule, val testScope: TestScope) {
+class AnimatorTestRuleToolkit(
+ internal val animatorTestRule: AnimatorTestRule,
+ internal val testScope: TestScope,
+ internal val currentActivityScenario: () -> ActivityScenario<*>,
+) {
internal companion object {
const val TAG = "AnimatorRuleToolkit"
}
}
+/** Capture utility to extract a [Bitmap] from a [drawable]. */
+fun captureDrawable(drawable: Drawable): Bitmap {
+ val width = drawable.bounds.right - drawable.bounds.left
+ val height = drawable.bounds.bottom - drawable.bounds.top
+
+ // If either dimension is 0 this will fail, so we set it to 1 pixel instead.
+ return drawable.toBitmap(
+ width =
+ if (width > 0) {
+ width
+ } else {
+ 1
+ },
+ height =
+ if (height > 0) {
+ height
+ } else {
+ 1
+ },
+ )
+}
+
+/** Capture utility to extract a [Bitmap] from a [view]. */
+fun captureView(view: View): Bitmap {
+ return view.captureToBitmapAsync().get(10, TimeUnit.SECONDS)
+}
+
/**
* Controls the timing of the motion recording.
*
@@ -71,24 +109,39 @@
/** Time interval between frame captures, in milliseconds. */
val frameDurationMs: Long = 16L,
- /** Produces the time-series, invoked on each animation frame. */
+ /** Whether a sequence of screenshots should also be recorded. */
+ val visualCapture: ((captureRoot: T) -> Bitmap)? = null,
+
+ /** Produces the time-series, invoked on each animation frame. */
val timeSeriesCapture: TimeSeriesCaptureScope<T>.() -> Unit,
)
/** Records the time-series of the features specified in [recordingSpec]. */
fun <T> MotionTestRule<AnimatorTestRuleToolkit>.recordMotion(
- recordingSpec: AnimatorRuleRecordingSpec<T>,
+ recordingSpec: AnimatorRuleRecordingSpec<T>
): RecordedMotion {
with(toolkit.animatorTestRule) {
+ val activityScenario = toolkit.currentActivityScenario()
val frameIdCollector = mutableListOf<FrameId>()
val propertyCollector = mutableMapOf<String, MutableList<DataPoint<*>>>()
+ val screenshotCollector =
+ if (recordingSpec.visualCapture != null) {
+ mutableListOf<Bitmap>()
+ } else {
+ null
+ }
fun recordFrame(frameId: FrameId) {
Log.i(TAG, "recordFrame($frameId)")
frameIdCollector.add(frameId)
- recordingSpec.timeSeriesCapture.invoke(
- TimeSeriesCaptureScope(recordingSpec.captureRoot, propertyCollector)
- )
+ activityScenario.onActivity {
+ recordingSpec.timeSeriesCapture.invoke(
+ TimeSeriesCaptureScope(recordingSpec.captureRoot, propertyCollector)
+ )
+ }
+
+ val bitmap = recordingSpec.visualCapture?.invoke(recordingSpec.captureRoot)
+ if (bitmap != null) screenshotCollector!!.add(bitmap)
}
val motionControl =
@@ -101,10 +154,13 @@
Log.i(TAG, "recordMotion() begin recording")
- val startFrameTime = currentTime
+ var startFrameTime: Long? = null
+ toolkit.currentActivityScenario().onActivity { startFrameTime = currentTime }
while (!motionControl.recordingEnded) {
- recordFrame(TimestampFrameId(currentTime - startFrameTime))
- motionControl.nextFrame()
+ var time: Long? = null
+ toolkit.currentActivityScenario().onActivity { time = currentTime }
+ recordFrame(TimestampFrameId(time!! - startFrameTime!!))
+ toolkit.currentActivityScenario().onActivity { motionControl.nextFrame() }
}
Log.i(TAG, "recordMotion() end recording")
@@ -115,7 +171,7 @@
propertyCollector.entries.map { entry -> Feature(entry.key, entry.value) },
)
- return create(timeSeries, null)
+ return create(timeSeries, screenshotCollector)
}
}
diff --git a/tests/testables/tests/Android.bp b/tests/testables/tests/Android.bp
index 7110564..f0cda53 100644
--- a/tests/testables/tests/Android.bp
+++ b/tests/testables/tests/Android.bp
@@ -37,10 +37,11 @@
"androidx.core_core-ktx",
"androidx.test.ext.junit",
"androidx.test.rules",
- "androidx.test.ext.junit",
"hamcrest-library",
"kotlinx_coroutines_test",
"mockito-target-inline-minus-junit4",
+ "platform-screenshot-diff-core",
+ "platform-test-annotations",
"testables",
"truth",
],
@@ -55,6 +56,7 @@
"android.test.mock.stubs.system",
],
certificate: "platform",
+ test_config: "AndroidTest.xml",
test_suites: [
"device-tests",
"automotive-tests",
diff --git a/tests/testables/tests/AndroidManifest.xml b/tests/testables/tests/AndroidManifest.xml
index 2bfb04f..6cba598 100644
--- a/tests/testables/tests/AndroidManifest.xml
+++ b/tests/testables/tests/AndroidManifest.xml
@@ -23,6 +23,10 @@
<application android:debuggable="true">
<uses-library android:name="android.test.runner" />
+ <activity
+ android:name="platform.test.screenshot.ScreenshotActivity"
+ android:exported="true">
+ </activity>
</application>
<instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
diff --git a/tests/testables/tests/AndroidTest.xml b/tests/testables/tests/AndroidTest.xml
new file mode 100644
index 0000000..85f6e62
--- /dev/null
+++ b/tests/testables/tests/AndroidTest.xml
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<configuration description="Runs Tests for Testables.">
+ <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
+ <option name="test-file-name" value="TestablesTests.apk" />
+ <option name="install-arg" value="-t" />
+ </target_preparer>
+
+ <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer">
+ <option name="force-root" value="true" />
+ </target_preparer>
+
+ <target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
+ <option name="screen-always-on" value="on" />
+ </target_preparer>
+
+ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+ <option name="run-command" value="input keyevent KEYCODE_WAKEUP" />
+ <option name="run-command" value="wm dismiss-keyguard" />
+ </target_preparer>
+
+ <option name="test-suite-tag" value="apct" />
+ <option name="test-suite-tag" value="framework-base-presubmit" />
+ <option name="test-tag" value="TestableTests" />
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+ <option name="package" value="com.android.testables" />
+ <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+ <option name="test-filter-dir" value="/data/data/com.android.testables" />
+ <option name="hidden-api-checks" value="false"/>
+ </test>
+
+ <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
+ <option name="directory-keys" value="/data/user/0/com.android.testables/files"/>
+ <option name="collect-on-run-ended-only" value="true"/>
+ <option name="clean-up" value="true"/>
+ </metrics_collector>
+</configuration>
diff --git a/tests/testables/tests/goldens/recordFilmstrip_withAnimator.png b/tests/testables/tests/goldens/recordFilmstrip_withAnimator.png
new file mode 100644
index 0000000..9aed2e9
--- /dev/null
+++ b/tests/testables/tests/goldens/recordFilmstrip_withAnimator.png
Binary files differ
diff --git a/tests/testables/tests/goldens/recordFilmstrip_withSpring.png b/tests/testables/tests/goldens/recordFilmstrip_withSpring.png
new file mode 100644
index 0000000..1d0c0c3
--- /dev/null
+++ b/tests/testables/tests/goldens/recordFilmstrip_withSpring.png
Binary files differ
diff --git a/tests/testables/tests/goldens/recordMotion_withAnimator.json b/tests/testables/tests/goldens/recordTimeSeries_withAnimator.json
similarity index 97%
rename from tests/testables/tests/goldens/recordMotion_withAnimator.json
rename to tests/testables/tests/goldens/recordTimeSeries_withAnimator.json
index 87fece5..73eb6c7 100644
--- a/tests/testables/tests/goldens/recordMotion_withAnimator.json
+++ b/tests/testables/tests/goldens/recordTimeSeries_withAnimator.json
@@ -29,7 +29,7 @@
],
"features": [
{
- "name": "value",
+ "name": "alpha",
"type": "float",
"data_points": [
1,
diff --git a/tests/testables/tests/goldens/recordMotion_withSpring.json b/tests/testables/tests/goldens/recordTimeSeries_withSpring.json
similarity index 96%
rename from tests/testables/tests/goldens/recordMotion_withSpring.json
rename to tests/testables/tests/goldens/recordTimeSeries_withSpring.json
index e9fb5b4..2b97bad 100644
--- a/tests/testables/tests/goldens/recordMotion_withSpring.json
+++ b/tests/testables/tests/goldens/recordTimeSeries_withSpring.json
@@ -21,7 +21,7 @@
],
"features": [
{
- "name": "value",
+ "name": "alpha",
"type": "float",
"data_points": [
1,
diff --git a/tests/testables/tests/src/android/animation/AnimatorTestRuleToolkitTest.kt b/tests/testables/tests/src/android/animation/AnimatorTestRuleToolkitTest.kt
index fbef489..993c3fe 100644
--- a/tests/testables/tests/src/android/animation/AnimatorTestRuleToolkitTest.kt
+++ b/tests/testables/tests/src/android/animation/AnimatorTestRuleToolkitTest.kt
@@ -16,10 +16,15 @@
package android.animation
-import android.util.FloatProperty
+import android.graphics.Color
+import android.platform.test.annotations.MotionTest
+import android.view.ViewGroup
+import android.widget.FrameLayout
+import androidx.test.ext.junit.rules.ActivityScenarioRule
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
+import com.android.internal.dynamicanimation.animation.DynamicAnimation
import com.android.internal.dynamicanimation.animation.SpringAnimation
import com.android.internal.dynamicanimation.animation.SpringForce
import kotlinx.coroutines.test.TestScope
@@ -28,102 +33,169 @@
import org.junit.runner.RunWith
import platform.test.motion.MotionTestRule
import platform.test.motion.RecordedMotion
-import platform.test.motion.golden.FeatureCapture
-import platform.test.motion.golden.asDataPoint
import platform.test.motion.testing.createGoldenPathManager
+import platform.test.motion.view.ViewFeatureCaptures
+import platform.test.screenshot.DeviceEmulationRule
+import platform.test.screenshot.DeviceEmulationSpec
+import platform.test.screenshot.DisplaySpec
+import platform.test.screenshot.ScreenshotActivity
+import platform.test.screenshot.ScreenshotTestRule
@SmallTest
+@MotionTest
@RunWith(AndroidJUnit4::class)
class AnimatorTestRuleToolkitTest {
companion object {
private val GOLDEN_PATH_MANAGER =
createGoldenPathManager("frameworks/base/tests/testables/tests/goldens")
- private val TEST_PROPERTY =
- object : FloatProperty<TestState>("value") {
- override fun get(state: TestState): Float {
- return state.animatedValue
- }
-
- override fun setValue(state: TestState, value: Float) {
- state.animatedValue = value
- }
- }
+ private val EMULATION_SPEC =
+ DeviceEmulationSpec(DisplaySpec("phone", width = 320, height = 690, densityDpi = 160))
}
- @get:Rule(order = 0) val animatorTestRule = AnimatorTestRule(this)
- @get:Rule(order = 1)
+ @get:Rule(order = 0) val deviceEmulationRule = DeviceEmulationRule(EMULATION_SPEC)
+ @get:Rule(order = 1) val activityRule = ActivityScenarioRule(ScreenshotActivity::class.java)
+ @get:Rule(order = 2) val animatorTestRule = AnimatorTestRule(this)
+ @get:Rule(order = 3) val screenshotRule = ScreenshotTestRule(GOLDEN_PATH_MANAGER)
+ @get:Rule(order = 4)
val motionRule =
- MotionTestRule(AnimatorTestRuleToolkit(animatorTestRule, TestScope()), GOLDEN_PATH_MANAGER)
+ MotionTestRule(
+ AnimatorTestRuleToolkit(animatorTestRule, TestScope()) { activityRule.scenario },
+ GOLDEN_PATH_MANAGER,
+ bitmapDiffer = screenshotRule,
+ )
@Test
- fun recordMotion_withAnimator() {
- val state = TestState()
- AnimatorSet().apply {
- duration = 500
- play(
- ValueAnimator.ofFloat(state.animatedValue, 0f).apply {
- addUpdateListener { state.animatedValue = it.animatedValue as Float }
- }
+ fun recordFilmstrip_withAnimator() {
+ val animatedBox = createScene()
+ createAnimator(animatedBox).apply { getInstrumentation().runOnMainSync { start() } }
+
+ val recordedMotion =
+ record(
+ animatedBox,
+ MotionControl { awaitFrames(count = 26) },
+ sampleIntervalMs = 20L,
+ recordScreenshots = true,
)
+
+ motionRule.assertThat(recordedMotion).filmstripMatchesGolden("recordFilmstrip_withAnimator")
+ }
+
+ @Test
+ fun recordTimeSeries_withAnimator() {
+ val animatedBox = createScene()
+ createAnimator(animatedBox).apply { getInstrumentation().runOnMainSync { start() } }
+
+ val recordedMotion =
+ record(
+ animatedBox,
+ MotionControl { awaitFrames(count = 26) },
+ sampleIntervalMs = 20L,
+ recordScreenshots = false,
+ )
+
+ motionRule
+ .assertThat(recordedMotion)
+ .timeSeriesMatchesGolden("recordTimeSeries_withAnimator")
+ }
+
+ @Test
+ fun recordFilmstrip_withSpring() {
+ val animatedBox = createScene()
+ var isDone = false
+ createSpring(animatedBox).apply {
+ addEndListener { _, _, _, _ -> isDone = true }
getInstrumentation().runOnMainSync { start() }
}
val recordedMotion =
- record(state, MotionControl { awaitFrames(count = 26) }, sampleIntervalMs = 20L)
+ record(
+ animatedBox,
+ MotionControl { awaitCondition { isDone } },
+ sampleIntervalMs = 16L,
+ recordScreenshots = true,
+ )
- motionRule.assertThat(recordedMotion).timeSeriesMatchesGolden("recordMotion_withAnimator")
+ motionRule.assertThat(recordedMotion).filmstripMatchesGolden("recordFilmstrip_withSpring")
}
@Test
- fun recordMotion_withSpring() {
- val state = TestState()
+ fun recordTimeSeries_withSpring() {
+ val animatedBox = createScene()
var isDone = false
- SpringAnimation(state, TEST_PROPERTY).apply {
+ createSpring(animatedBox).apply {
+ addEndListener { _, _, _, _ -> isDone = true }
+ getInstrumentation().runOnMainSync { start() }
+ }
+
+ val recordedMotion =
+ record(
+ animatedBox,
+ MotionControl { awaitCondition { isDone } },
+ sampleIntervalMs = 16L,
+ recordScreenshots = false,
+ )
+
+ motionRule.assertThat(recordedMotion).timeSeriesMatchesGolden("recordTimeSeries_withSpring")
+ }
+
+ private fun createScene(): ViewGroup {
+ lateinit var sceneRoot: ViewGroup
+ activityRule.scenario.onActivity { activity ->
+ sceneRoot = FrameLayout(activity).apply { setBackgroundColor(Color.BLACK) }
+ activity.setContentView(sceneRoot)
+ }
+ getInstrumentation().waitForIdleSync()
+ return sceneRoot
+ }
+
+ private fun createAnimator(animatedBox: ViewGroup): AnimatorSet {
+ return AnimatorSet().apply {
+ duration = 500
+ play(
+ ValueAnimator.ofFloat(animatedBox.alpha, 0f).apply {
+ addUpdateListener { animatedBox.alpha = it.animatedValue as Float }
+ }
+ )
+ }
+ }
+
+ private fun createSpring(animatedBox: ViewGroup): SpringAnimation {
+ return SpringAnimation(animatedBox, DynamicAnimation.ALPHA).apply {
spring =
SpringForce(0f).apply {
stiffness = 500f
dampingRatio = 0.95f
}
- setStartValue(1f)
+ setStartValue(animatedBox.alpha)
setMinValue(0f)
setMaxValue(1f)
minimumVisibleChange = 0.01f
-
- addEndListener { _, _, _, _ -> isDone = true }
- getInstrumentation().runOnMainSync { start() }
}
-
- val recordedMotion =
- record(state, MotionControl { awaitCondition { isDone } }, sampleIntervalMs = 16L)
-
- motionRule.assertThat(recordedMotion).timeSeriesMatchesGolden("recordMotion_withSpring")
}
private fun record(
- state: TestState,
+ container: ViewGroup,
motionControl: MotionControl,
sampleIntervalMs: Long,
+ recordScreenshots: Boolean,
): RecordedMotion {
- var recordedMotion: RecordedMotion? = null
- getInstrumentation().runOnMainSync {
- recordedMotion =
- motionRule.recordMotion(
- AnimatorRuleRecordingSpec(
- state,
- motionControl,
- sampleIntervalMs,
- ) {
- feature(
- FeatureCapture("value") { state -> state.animatedValue.asDataPoint() },
- "value",
- )
- }
- )
- }
- return recordedMotion!!
+ val visualCapture =
+ if (recordScreenshots) {
+ ::captureView
+ } else {
+ null
+ }
+ return motionRule.recordMotion(
+ AnimatorRuleRecordingSpec(
+ container,
+ motionControl,
+ sampleIntervalMs,
+ visualCapture,
+ ) {
+ feature(ViewFeatureCaptures.alpha, "alpha")
+ }
+ )
}
-
- data class TestState(var animatedValue: Float = 1f)
}