Merge "Remove erronous failure log" into main
diff --git a/core/api/current.txt b/core/api/current.txt
index 17e7d7a..2192965 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -8893,8 +8893,8 @@
}
@FlaggedApi("android.app.appfunctions.flags.enable_app_function_manager") public final class AppFunctionManager {
- method @RequiresPermission(anyOf={"android.permission.EXECUTE_APP_FUNCTIONS_TRUSTED", android.Manifest.permission.EXECUTE_APP_FUNCTIONS}, conditional=true) public void executeAppFunction(@NonNull android.app.appfunctions.ExecuteAppFunctionRequest, @NonNull java.util.concurrent.Executor, @NonNull android.os.CancellationSignal, @NonNull android.os.OutcomeReceiver<android.app.appfunctions.ExecuteAppFunctionResponse,android.app.appfunctions.AppFunctionException>);
- method @RequiresPermission(anyOf={"android.permission.EXECUTE_APP_FUNCTIONS_TRUSTED", android.Manifest.permission.EXECUTE_APP_FUNCTIONS}, conditional=true) public void isAppFunctionEnabled(@NonNull String, @NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,java.lang.Exception>);
+ method @RequiresPermission(value=android.Manifest.permission.EXECUTE_APP_FUNCTIONS, conditional=true) public void executeAppFunction(@NonNull android.app.appfunctions.ExecuteAppFunctionRequest, @NonNull java.util.concurrent.Executor, @NonNull android.os.CancellationSignal, @NonNull android.os.OutcomeReceiver<android.app.appfunctions.ExecuteAppFunctionResponse,android.app.appfunctions.AppFunctionException>);
+ method @RequiresPermission(value=android.Manifest.permission.EXECUTE_APP_FUNCTIONS, conditional=true) public void isAppFunctionEnabled(@NonNull String, @NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,java.lang.Exception>);
method public void isAppFunctionEnabled(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,java.lang.Exception>);
method public void setAppFunctionEnabled(@NonNull String, int, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,java.lang.Exception>);
field public static final int APP_FUNCTION_STATE_DEFAULT = 0; // 0x0
@@ -20785,6 +20785,7 @@
method public void registerDisplayListener(android.hardware.display.DisplayManager.DisplayListener, android.os.Handler);
method @FlaggedApi("com.android.server.display.feature.flags.display_listener_performance_improvements") public void registerDisplayListener(@NonNull java.util.concurrent.Executor, long, @NonNull android.hardware.display.DisplayManager.DisplayListener);
method public void unregisterDisplayListener(android.hardware.display.DisplayManager.DisplayListener);
+ field @FlaggedApi("com.android.server.display.feature.flags.display_category_built_in") public static final String DISPLAY_CATEGORY_BUILT_IN_DISPLAYS = "android.hardware.display.category.BUILT_IN_DISPLAYS";
field public static final String DISPLAY_CATEGORY_PRESENTATION = "android.hardware.display.category.PRESENTATION";
field @FlaggedApi("com.android.server.display.feature.flags.display_listener_performance_improvements") public static final long EVENT_TYPE_DISPLAY_ADDED = 1L; // 0x1L
field @FlaggedApi("com.android.server.display.feature.flags.display_listener_performance_improvements") public static final long EVENT_TYPE_DISPLAY_CHANGED = 4L; // 0x4L
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 41f2862..ab82411 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -150,7 +150,6 @@
field @FlaggedApi("com.android.window.flags.untrusted_embedding_any_app_permission") public static final String EMBED_ANY_APP_IN_UNTRUSTED_MODE = "android.permission.EMBED_ANY_APP_IN_UNTRUSTED_MODE";
field @FlaggedApi("android.content.pm.emergency_install_permission") public static final String EMERGENCY_INSTALL_PACKAGES = "android.permission.EMERGENCY_INSTALL_PACKAGES";
field public static final String ENTER_CAR_MODE_PRIORITIZED = "android.permission.ENTER_CAR_MODE_PRIORITIZED";
- field @FlaggedApi("android.app.appfunctions.flags.enable_app_function_manager") public static final String EXECUTE_APP_FUNCTIONS_TRUSTED = "android.permission.EXECUTE_APP_FUNCTIONS_TRUSTED";
field public static final String EXEMPT_FROM_AUDIO_RECORD_RESTRICTIONS = "android.permission.EXEMPT_FROM_AUDIO_RECORD_RESTRICTIONS";
field public static final String FORCE_BACK = "android.permission.FORCE_BACK";
field public static final String FORCE_STOP_PACKAGES = "android.permission.FORCE_STOP_PACKAGES";
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 7c1c868..0b0738e 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -909,6 +909,14 @@
method @NonNull public android.companion.AssociationInfo.Builder setTimeApproved(long);
}
+ public final class AssociationRequest implements android.os.Parcelable {
+ method public boolean isSkipRoleGrant();
+ }
+
+ public static final class AssociationRequest.Builder {
+ method @NonNull @RequiresPermission(android.Manifest.permission.ASSOCIATE_COMPANION_DEVICES) public android.companion.AssociationRequest.Builder setSkipRoleGrant(boolean);
+ }
+
public final class CompanionDeviceManager {
method @RequiresPermission("android.permission.MANAGE_COMPANION_DEVICES") public void enableSecureTransport(boolean);
}
@@ -1727,6 +1735,7 @@
method @RequiresPermission(android.Manifest.permission.OVERRIDE_DISPLAY_MODE_REQUESTS) public void setShouldAlwaysRespectAppRequestedMode(boolean);
method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void setUserDisabledHdrTypes(@NonNull int[]);
method @RequiresPermission(android.Manifest.permission.OVERRIDE_DISPLAY_MODE_REQUESTS) public boolean shouldAlwaysRespectAppRequestedMode();
+ field public static final String DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED = "android.hardware.display.category.ALL_INCLUDING_DISABLED";
field public static final String DISPLAY_CATEGORY_REAR = "android.hardware.display.category.REAR";
field public static final String HDR_OUTPUT_CONTROL_FLAG = "enable_hdr_output_control";
field public static final int SWITCHING_TYPE_ACROSS_AND_WITHIN_GROUPS = 2; // 0x2
diff --git a/core/java/android/app/AutomaticZenRule.java b/core/java/android/app/AutomaticZenRule.java
index e0a9371..9d1d9c7 100644
--- a/core/java/android/app/AutomaticZenRule.java
+++ b/core/java/android/app/AutomaticZenRule.java
@@ -162,7 +162,7 @@
* both to fields in the rule itself (such as its name) and items with sub-fields.
* @hide
*/
- public static final int MAX_STRING_LENGTH = 1000;
+ public static final int MAX_STRING_LENGTH = 500;
/**
* The maximum string length for the trigger description rule, given UI constraints.
diff --git a/core/java/android/app/appfunctions/AppFunctionManager.java b/core/java/android/app/appfunctions/AppFunctionManager.java
index 6fd8db9..0a3891f 100644
--- a/core/java/android/app/appfunctions/AppFunctionManager.java
+++ b/core/java/android/app/appfunctions/AppFunctionManager.java
@@ -72,10 +72,10 @@
* <p>To execute an app function, the caller app can retrieve the {@code functionIdentifier} from
* the {@code AppFunctionStaticMetadata} document and use it to build an {@link
* ExecuteAppFunctionRequest}. Then, invoke {@link #executeAppFunction} with the request to execute
- * the app function. Callers need the {@code android.permission.EXECUTE_APP_FUNCTIONS} or {@code
- * android.permission.EXECUTE_APP_FUNCTIONS_TRUSTED} permission to execute app functions from other
- * apps. An app can always execute its own app functions and doesn't need these permissions.
- * AppFunction SDK provides a convenient way to achieve this and is the preferred method.
+ * the app function. Callers need the {@code android.permission.EXECUTE_APP_FUNCTIONS} permission to
+ * execute app functions from other apps. An app can always execute its own app functions and
+ * doesn't need these permissions. AppFunction SDK provides a convenient way to achieve this and
+ * is the preferred method.
*
* <h3>Example</h3>
*
@@ -141,32 +141,24 @@
* Executes the app function.
*
* <p>Note: Applications can execute functions they define. To execute functions defined in
- * another component, apps would need to have {@code
- * android.permission.EXECUTE_APP_FUNCTIONS_TRUSTED} or {@code
- * android.permission.EXECUTE_APP_FUNCTIONS}.
+ * another component, apps would need to have the permission
+ * {@code android.permission.EXECUTE_APP_FUNCTIONS}.
*
* @param request the request to execute the app function
* @param executor the executor to run the callback
* @param cancellationSignal the cancellation signal to cancel the execution.
* @param callback the callback to receive the function execution result or error.
* <p>If the calling app does not own the app function or does not have {@code
- * android.permission.EXECUTE_APP_FUNCTIONS_TRUSTED} or {@code
* android.permission.EXECUTE_APP_FUNCTIONS}, the execution result will contain {@code
* AppFunctionException.ERROR_DENIED}.
- * <p>If the caller only has {@code android.permission.EXECUTE_APP_FUNCTIONS} but the
- * function requires {@code android.permission.EXECUTE_APP_FUNCTIONS_TRUSTED}, the execution
+ * <p>If the caller only has {@code android.permission.EXECUTE_APP_FUNCTIONS}, the execution
* result will contain {@code AppFunctionException.ERROR_DENIED}
* <p>If the function requested for execution is disabled, then the execution result will
* contain {@code AppFunctionException.ERROR_DISABLED}
* <p>If the cancellation signal is issued, the operation is cancelled and no response is
* returned to the caller.
*/
- @RequiresPermission(
- anyOf = {
- Manifest.permission.EXECUTE_APP_FUNCTIONS_TRUSTED,
- Manifest.permission.EXECUTE_APP_FUNCTIONS
- },
- conditional = true)
+ @RequiresPermission(value = Manifest.permission.EXECUTE_APP_FUNCTIONS, conditional = true)
@UserHandleAware
public void executeAppFunction(
@NonNull ExecuteAppFunctionRequest request,
@@ -222,9 +214,8 @@
* Returns a boolean through a callback, indicating whether the app function is enabled.
*
* <p>This method can only check app functions owned by the caller, or those where the caller
- * has visibility to the owner package and holds either the {@link
- * Manifest.permission#EXECUTE_APP_FUNCTIONS} or {@link
- * Manifest.permission#EXECUTE_APP_FUNCTIONS_TRUSTED} permission.
+ * has visibility to the owner package and holds the
+ * {@link Manifest.permission#EXECUTE_APP_FUNCTIONS} permission.
*
* <p>If the operation fails, the callback's {@link OutcomeReceiver#onError} is called with
* errors:
@@ -241,12 +232,7 @@
* @param executor the executor to run the request
* @param callback the callback to receive the function enabled check result
*/
- @RequiresPermission(
- anyOf = {
- Manifest.permission.EXECUTE_APP_FUNCTIONS_TRUSTED,
- Manifest.permission.EXECUTE_APP_FUNCTIONS
- },
- conditional = true)
+ @RequiresPermission(value = Manifest.permission.EXECUTE_APP_FUNCTIONS, conditional = true)
public void isAppFunctionEnabled(
@NonNull String functionIdentifier,
@NonNull String targetPackage,
diff --git a/core/java/android/app/appfunctions/AppFunctionManagerHelper.java b/core/java/android/app/appfunctions/AppFunctionManagerHelper.java
index 64dece9..cc3ca03 100644
--- a/core/java/android/app/appfunctions/AppFunctionManagerHelper.java
+++ b/core/java/android/app/appfunctions/AppFunctionManagerHelper.java
@@ -54,9 +54,8 @@
* Returns (through a callback) a boolean indicating whether the app function is enabled.
*
* This method can only check app functions owned by the caller, or those where the caller
- * has visibility to the owner package and holds either the {@link
- * Manifest.permission#EXECUTE_APP_FUNCTIONS} or {@link
- * Manifest.permission#EXECUTE_APP_FUNCTIONS_TRUSTED} permission.
+ * has visibility to the owner package and holds the {@link
+ * Manifest.permission#EXECUTE_APP_FUNCTIONS} permission.
*
* <p>If operation fails, the callback's {@link OutcomeReceiver#onError} is called with errors:
*
diff --git a/core/java/android/app/appfunctions/AppFunctionRuntimeMetadata.java b/core/java/android/app/appfunctions/AppFunctionRuntimeMetadata.java
index 3ddda22..7743d48 100644
--- a/core/java/android/app/appfunctions/AppFunctionRuntimeMetadata.java
+++ b/core/java/android/app/appfunctions/AppFunctionRuntimeMetadata.java
@@ -90,8 +90,7 @@
* we need to have per-package app function schemas.
*
* <p>This schema should be set visible to callers from the package owner itself and for callers
- * with {@link android.Manifest.permission#EXECUTE_APP_FUNCTIONS} or {@link
- * android.Manifest.permission#EXECUTE_APP_FUNCTIONS_TRUSTED} permissions.
+ * with the permission {@link android.Manifest.permission#EXECUTE_APP_FUNCTIONS}.
*
* @param packageName The package name to create a schema for.
*/
@@ -105,9 +104,8 @@
/**
* Creates a parent schema for all app function runtime schemas.
*
- * <p>This schema should be set visible to the owner itself and for callers with {@link
- * android.permission.EXECUTE_APP_FUNCTIONS_TRUSTED} or {@link
- * android.permission.EXECUTE_APP_FUNCTIONS} permissions.
+ * <p>This schema should be set visible to the owner itself and for callers with
+ * the permission {@link android.permission.EXECUTE_APP_FUNCTIONS}.
*/
public static AppSearchSchema createParentAppFunctionRuntimeSchema() {
return getAppFunctionRuntimeSchemaBuilder(RUNTIME_SCHEMA_TYPE).build();
diff --git a/core/java/android/app/appfunctions/IAppFunctionManager.aidl b/core/java/android/app/appfunctions/IAppFunctionManager.aidl
index 72335e4..098e1fe 100644
--- a/core/java/android/app/appfunctions/IAppFunctionManager.aidl
+++ b/core/java/android/app/appfunctions/IAppFunctionManager.aidl
@@ -34,7 +34,7 @@
* @param request the request to execute an app function.
* @param callback the callback to report the result.
*/
- @JavaPassthrough(annotation="@android.annotation.RequiresPermission(anyOf = {android.Manifest.permission.EXECUTE_APP_FUNCTIONS_TRUSTED,android.Manifest.permission.EXECUTE_APP_FUNCTIONS}, conditional = true)")
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = android.Manifest.permission.EXECUTE_APP_FUNCTIONS, conditional = true)")
ICancellationSignal executeAppFunction(
in ExecuteAppFunctionAidlRequest request,
in IExecuteAppFunctionCallback callback
diff --git a/core/java/android/companion/AssociationRequest.java b/core/java/android/companion/AssociationRequest.java
index a098a60..67dea32 100644
--- a/core/java/android/companion/AssociationRequest.java
+++ b/core/java/android/companion/AssociationRequest.java
@@ -16,6 +16,7 @@
package android.companion;
+import static android.Manifest.permission.ASSOCIATE_COMPANION_DEVICES;
import static android.Manifest.permission.REQUEST_COMPANION_SELF_MANAGED;
import static com.android.internal.util.CollectionUtils.emptyIfNull;
@@ -28,7 +29,10 @@
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.StringDef;
+import android.annotation.SuppressLint;
+import android.annotation.TestApi;
import android.annotation.UserIdInt;
+import android.app.KeyguardManager;
import android.compat.annotation.UnsupportedAppUsage;
import android.graphics.drawable.Icon;
import android.os.Build;
@@ -214,6 +218,11 @@
private final boolean mForceConfirmation;
/**
+ * Whether to skip the role grant, permission checks and consent dialog.
+ */
+ private final boolean mSkipRoleGrant;
+
+ /**
* The app package name of the application the association will belong to.
* Populated by the system.
* @hide
@@ -283,6 +292,7 @@
@Nullable CharSequence displayName,
boolean selfManaged,
boolean forceConfirmation,
+ boolean skipRoleGrant,
@Nullable Icon deviceIcon) {
mSingleDevice = singleDevice;
mDeviceFilters = requireNonNull(deviceFilters);
@@ -290,6 +300,7 @@
mDisplayName = displayName;
mSelfManaged = selfManaged;
mForceConfirmation = forceConfirmation;
+ mSkipRoleGrant = skipRoleGrant;
mCreationTime = System.currentTimeMillis();
mDeviceIcon = deviceIcon;
}
@@ -333,6 +344,18 @@
}
/**
+ * Whether to skip the role grant, permission checks and consent dialog.
+ *
+ * @see Builder#setSkipRoleGrant(boolean)
+ * @hide
+ */
+ @SuppressLint("UnflaggedApi") // @TestApi without associated feature.
+ @TestApi
+ public boolean isSkipRoleGrant() {
+ return mSkipRoleGrant;
+ }
+
+ /**
* Whether only a single device should match the provided filter.
*
* When scanning for a single device with a specific {@link BluetoothDeviceFilter} mac
@@ -407,6 +430,7 @@
private CharSequence mDisplayName;
private boolean mSelfManaged = false;
private boolean mForceConfirmation = false;
+ private boolean mSkipRoleGrant = false;
private Icon mDeviceIcon = null;
public Builder() {}
@@ -494,6 +518,27 @@
}
/**
+ * Do not attempt to grant the role corresponding to the device profile.
+ *
+ * <p>This will skip the permission checks and consent dialog but will not fail if the
+ * role cannot be granted.</p>
+ *
+ * <p>Requires that the device not to have secure lock screen and that there no locked SIM
+ * card. See {@link KeyguardManager#isKeyguardSecure()}</p>
+ *
+ * @hide
+ */
+ @RequiresPermission(ASSOCIATE_COMPANION_DEVICES)
+ @SuppressLint("UnflaggedApi") // @TestApi without associated feature.
+ @TestApi
+ @NonNull
+ public Builder setSkipRoleGrant(boolean skipRoleGrant) {
+ checkNotUsed();
+ mSkipRoleGrant = skipRoleGrant;
+ return this;
+ }
+
+ /**
* Set the device icon for the self-managed device and to display the icon in the
* self-managed association dialog.
* <p>The given device icon will be resized to 24dp x 24dp.
@@ -521,7 +566,8 @@
+ "provide the display name of the device");
}
return new AssociationRequest(mSingleDevice, emptyIfNull(mDeviceFilters),
- mDeviceProfile, mDisplayName, mSelfManaged, mForceConfirmation, mDeviceIcon);
+ mDeviceProfile, mDisplayName, mSelfManaged, mForceConfirmation, mSkipRoleGrant,
+ mDeviceIcon);
}
}
@@ -597,6 +643,7 @@
+ ", associatedDevice = " + mAssociatedDevice
+ ", selfManaged = " + mSelfManaged
+ ", forceConfirmation = " + mForceConfirmation
+ + ", skipRoleGrant = " + mSkipRoleGrant
+ ", packageName = " + mPackageName
+ ", userId = " + mUserId
+ ", deviceProfilePrivilegesDescription = " + mDeviceProfilePrivilegesDescription
@@ -617,6 +664,7 @@
&& Objects.equals(mAssociatedDevice, that.mAssociatedDevice)
&& mSelfManaged == that.mSelfManaged
&& mForceConfirmation == that.mForceConfirmation
+ && mSkipRoleGrant == that.mSkipRoleGrant
&& Objects.equals(mPackageName, that.mPackageName)
&& mUserId == that.mUserId
&& Objects.equals(mDeviceProfilePrivilegesDescription,
@@ -637,6 +685,7 @@
_hash = 31 * _hash + Objects.hashCode(mAssociatedDevice);
_hash = 31 * _hash + Boolean.hashCode(mSelfManaged);
_hash = 31 * _hash + Boolean.hashCode(mForceConfirmation);
+ _hash = 31 * _hash + Boolean.hashCode(mSkipRoleGrant);
_hash = 31 * _hash + Objects.hashCode(mPackageName);
_hash = 31 * _hash + mUserId;
_hash = 31 * _hash + Objects.hashCode(mDeviceProfilePrivilegesDescription);
@@ -659,6 +708,7 @@
if (mAssociatedDevice != null) flg |= 0x40;
if (mPackageName != null) flg |= 0x80;
if (mDeviceProfilePrivilegesDescription != null) flg |= 0x100;
+ if (mSkipRoleGrant) flg |= 0x200;
dest.writeInt(flg);
dest.writeParcelableList(mDeviceFilters, flags);
@@ -692,6 +742,7 @@
boolean selfManaged = (flg & 0x2) != 0;
boolean forceConfirmation = (flg & 0x4) != 0;
boolean skipPrompt = (flg & 0x8) != 0;
+ boolean skipRoleGrant = (flg & 0x200) != 0;
List<DeviceFilter<?>> deviceFilters = new ArrayList<>();
in.readParcelableList(deviceFilters, DeviceFilter.class.getClassLoader(),
(Class<android.companion.DeviceFilter<?>>) (Class<?>)
@@ -714,6 +765,7 @@
this.mAssociatedDevice = associatedDevice;
this.mSelfManaged = selfManaged;
this.mForceConfirmation = forceConfirmation;
+ this.mSkipRoleGrant = skipRoleGrant;
this.mPackageName = packageName;
this.mUserId = userId;
com.android.internal.util.AnnotationValidations.validate(
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 4696882..0312ad7 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -12426,8 +12426,8 @@
}
private void collectNestedIntentKeysRecur(Set<Intent> visited, boolean forceUnparcel) {
- addExtendedFlags(EXTENDED_FLAG_NESTED_INTENT_KEYS_COLLECTED);
if (mExtras != null && (forceUnparcel || !mExtras.isParcelled()) && !mExtras.isEmpty()) {
+ addExtendedFlags(EXTENDED_FLAG_NESTED_INTENT_KEYS_COLLECTED);
for (String key : mExtras.keySet()) {
Object value;
try {
diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java
index 34e86a4..a96de4b 100644
--- a/core/java/android/hardware/display/DisplayManager.java
+++ b/core/java/android/hardware/display/DisplayManager.java
@@ -146,6 +146,22 @@
"android.hardware.display.category.PRESENTATION";
/**
+ * Display category: Built in displays.
+ *
+ * <p>
+ * This category can be used to identify displays that are built into the device. The
+ * displays that are returned may be inactive or disabled at the current moment. The
+ * returned displays are useful in identifying the various sizes of built-in displays. The
+ * id from {@link Display#getDisplayId()} is not guaranteed to be stable and may change
+ * when the display becomes active.
+ * </p>
+ * @see #getDisplays(String)
+ */
+ @FlaggedApi(com.android.server.display.feature.flags.Flags.FLAG_DISPLAY_CATEGORY_BUILT_IN)
+ public static final String DISPLAY_CATEGORY_BUILT_IN_DISPLAYS =
+ "android.hardware.display.category.BUILT_IN_DISPLAYS";
+
+ /**
* Display category: Rear displays.
* <p>
* This category can be used to identify complementary internal displays that are facing away
@@ -171,6 +187,8 @@
* @see #getDisplays(String)
* @hide
*/
+ @TestApi
+ @SuppressLint("UnflaggedApi")
public static final String DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED =
"android.hardware.display.category.ALL_INCLUDING_DISABLED";
@@ -623,9 +641,6 @@
* is triggered whenever the properties of a {@link android.view.Display}, such as size,
* state, density are modified.
*
- * This event is not triggered for refresh rate changes as they can change very often.
- * To monitor refresh rate changes, subscribe to {@link EVENT_TYPE_DISPLAY_REFRESH_RATE}.
- *
* @see #registerDisplayListener(DisplayListener, Handler, long)
*
*/
@@ -732,10 +747,13 @@
* @see #DISPLAY_CATEGORY_PRESENTATION
*/
public Display[] getDisplays(String category) {
- boolean includeDisabled = (category != null
- && category.equals(DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED));
+ boolean includeDisabled = shouldIncludeDisabledDisplays(category);
final int[] displayIds = mGlobal.getDisplayIds(includeDisabled);
- if (DISPLAY_CATEGORY_PRESENTATION.equals(category)) {
+ if (Flags.displayCategoryBuiltIn()
+ && DISPLAY_CATEGORY_BUILT_IN_DISPLAYS.equals(category)) {
+ Display[] value = getDisplays(displayIds, DisplayManager::isBuiltInDisplay);
+ return value;
+ } else if (DISPLAY_CATEGORY_PRESENTATION.equals(category)) {
return getDisplays(displayIds, DisplayManager::isPresentationDisplay);
} else if (DISPLAY_CATEGORY_REAR.equals(category)) {
return getDisplays(displayIds, DisplayManager::isRearDisplay);
@@ -745,6 +763,16 @@
return new Display[0];
}
+ private boolean shouldIncludeDisabledDisplays(@Nullable String category) {
+ if (DISPLAY_CATEGORY_BUILT_IN_DISPLAYS.equals(category)) {
+ return true;
+ }
+ if (DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED.equals(category)) {
+ return true;
+ }
+ return false;
+ }
+
private Display[] getDisplays(int[] displayIds, Predicate<Display> predicate) {
ArrayList<Display> tmpDisplays = new ArrayList<>();
for (int displayId : displayIds) {
@@ -756,6 +784,13 @@
return tmpDisplays.toArray(new Display[tmpDisplays.size()]);
}
+ private static boolean isBuiltInDisplay(@Nullable Display display) {
+ if (display == null) {
+ return false;
+ }
+ return display.getType() == Display.TYPE_INTERNAL;
+ }
+
private static boolean isPresentationDisplay(@Nullable Display display) {
if (display == null || (display.getDisplayId() == DEFAULT_DISPLAY)
|| (display.getFlags() & Display.FLAG_PRESENTATION) == 0) {
@@ -804,9 +839,6 @@
* Registers a display listener to receive notifications about when
* displays are added, removed or changed.
*
- * We encourage to use {@link #registerDisplayListener(Executor, long, DisplayListener)}
- * instead to subscribe for explicit events of interest
- *
* @param listener The listener to register.
* @param handler The handler on which the listener should be invoked, or null
* if the listener should be invoked on the calling thread's looper.
@@ -815,9 +847,7 @@
*/
public void registerDisplayListener(DisplayListener listener, Handler handler) {
registerDisplayListener(listener, handler, EVENT_TYPE_DISPLAY_ADDED
- | EVENT_TYPE_DISPLAY_CHANGED
- | EVENT_TYPE_DISPLAY_REFRESH_RATE
- | EVENT_TYPE_DISPLAY_REMOVED);
+ | EVENT_TYPE_DISPLAY_CHANGED | EVENT_TYPE_DISPLAY_REMOVED);
}
/**
diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java
index 339dbf2..b5715ed 100644
--- a/core/java/android/hardware/display/DisplayManagerGlobal.java
+++ b/core/java/android/hardware/display/DisplayManagerGlobal.java
@@ -1766,23 +1766,29 @@
}
if ((eventFlags & DisplayManager.EVENT_TYPE_DISPLAY_CHANGED) != 0) {
- baseEventMask |= INTERNAL_EVENT_FLAG_DISPLAY_BASIC_CHANGED;
+ // For backward compatibility, a client subscribing to
+ // DisplayManager.EVENT_FLAG_DISPLAY_CHANGED will be enrolled to both Basic and
+ // RR changes
+ baseEventMask |= INTERNAL_EVENT_FLAG_DISPLAY_BASIC_CHANGED
+ | INTERNAL_EVENT_FLAG_DISPLAY_REFRESH_RATE;
}
- if ((eventFlags & DisplayManager.EVENT_TYPE_DISPLAY_REMOVED) != 0) {
+ if ((eventFlags
+ & DisplayManager.EVENT_TYPE_DISPLAY_REMOVED) != 0) {
baseEventMask |= INTERNAL_EVENT_FLAG_DISPLAY_REMOVED;
}
- if ((eventFlags & DisplayManager.EVENT_TYPE_DISPLAY_REFRESH_RATE) != 0) {
- baseEventMask |= INTERNAL_EVENT_FLAG_DISPLAY_REFRESH_RATE;
- }
-
if (Flags.displayListenerPerformanceImprovements()) {
+ if ((eventFlags & DisplayManager.EVENT_TYPE_DISPLAY_REFRESH_RATE) != 0) {
+ baseEventMask |= INTERNAL_EVENT_FLAG_DISPLAY_REFRESH_RATE;
+ }
+
if ((eventFlags & DisplayManager.EVENT_TYPE_DISPLAY_STATE) != 0) {
baseEventMask |= INTERNAL_EVENT_FLAG_DISPLAY_STATE;
}
}
+
return baseEventMask;
}
}
diff --git a/core/java/android/os/BaseBundle.java b/core/java/android/os/BaseBundle.java
index 1cf293d..e79b2e7 100644
--- a/core/java/android/os/BaseBundle.java
+++ b/core/java/android/os/BaseBundle.java
@@ -142,6 +142,7 @@
/** {@hide} */
@VisibleForTesting
public int mFlags;
+ private boolean mHasIntent = false;
/**
* Constructs a new, empty Bundle that uses a specific ClassLoader for
@@ -258,9 +259,20 @@
// Keep as last statement to ensure visibility of other fields
mParcelledData = parcelledData;
+ mHasIntent = from.mHasIntent;
}
}
+ /** @hide */
+ public boolean hasIntent() {
+ return mHasIntent;
+ }
+
+ /** @hide */
+ public void setHasIntent(boolean hasIntent) {
+ mHasIntent = hasIntent;
+ }
+
/**
* TODO: optimize this later (getting just the value part of a Bundle
* with a single pair) once Bundle.forPair() above is implemented
@@ -1837,6 +1849,7 @@
parcel.writeInt(length);
parcel.writeInt(mParcelledByNative ? BUNDLE_MAGIC_NATIVE : BUNDLE_MAGIC);
parcel.appendFrom(mParcelledData, 0, length);
+ parcel.writeBoolean(mHasIntent);
}
return;
}
@@ -1851,7 +1864,6 @@
int lengthPos = parcel.dataPosition();
parcel.writeInt(-1); // placeholder, will hold length
parcel.writeInt(BUNDLE_MAGIC);
-
int startPos = parcel.dataPosition();
parcel.writeArrayMapInternal(map);
int endPos = parcel.dataPosition();
@@ -1861,6 +1873,7 @@
int length = endPos - startPos;
parcel.writeInt(length);
parcel.setDataPosition(endPos);
+ parcel.writeBoolean(mHasIntent);
}
/**
@@ -1904,6 +1917,7 @@
mOwnsLazyValues = false;
initializeFromParcelLocked(parcel, /*ownsParcel*/ false, isNativeBundle);
}
+ mHasIntent = parcel.readBoolean();
return;
}
@@ -1922,6 +1936,7 @@
mOwnsLazyValues = true;
mParcelledByNative = isNativeBundle;
mParcelledData = p;
+ mHasIntent = parcel.readBoolean();
}
/** {@hide} */
diff --git a/core/java/android/os/Binder.java b/core/java/android/os/Binder.java
index 6b1e918..ee62dea 100644
--- a/core/java/android/os/Binder.java
+++ b/core/java/android/os/Binder.java
@@ -149,11 +149,6 @@
private static volatile boolean sStackTrackingEnabled = false;
/**
- * The extension binder object
- */
- private IBinder mExtension = null;
-
- /**
* Enable Binder IPC stack tracking. If enabled, every binder transaction will be logged to
* {@link TransactionTracker}.
*
@@ -1242,9 +1237,7 @@
/** @hide */
@Override
- public final @Nullable IBinder getExtension() {
- return mExtension;
- }
+ public final native @Nullable IBinder getExtension();
/**
* Set the binder extension.
@@ -1252,12 +1245,7 @@
*
* @hide
*/
- public final void setExtension(@Nullable IBinder extension) {
- mExtension = extension;
- setExtensionNative(extension);
- }
-
- private final native void setExtensionNative(@Nullable IBinder extension);
+ public final native void setExtension(@Nullable IBinder extension);
/**
* Default implementation rewinds the parcels and calls onTransact. On
diff --git a/core/java/android/os/Bundle.java b/core/java/android/os/Bundle.java
index 819d58d..a24dc57 100644
--- a/core/java/android/os/Bundle.java
+++ b/core/java/android/os/Bundle.java
@@ -398,7 +398,7 @@
if ((bundle.mFlags & FLAG_HAS_BINDERS_KNOWN) == 0) {
mFlags &= ~FLAG_HAS_BINDERS_KNOWN;
}
- mFlags |= bundle.mFlags & FLAG_HAS_INTENT;
+ setHasIntent(hasIntent() || bundle.hasIntent());
}
/**
@@ -465,7 +465,7 @@
* @hide
*/
public boolean hasIntent() {
- return (mFlags & FLAG_HAS_INTENT) != 0;
+ return super.hasIntent();
}
/** {@hide} */
@@ -591,7 +591,7 @@
mFlags &= ~FLAG_HAS_FDS_KNOWN;
mFlags &= ~FLAG_HAS_BINDERS_KNOWN;
if (intentClass != null && intentClass.isInstance(value)) {
- mFlags |= FLAG_HAS_INTENT;
+ setHasIntent(true);
}
}
diff --git a/core/java/android/view/NotificationHeaderView.java b/core/java/android/view/NotificationHeaderView.java
index df680c0..73cd5ec 100644
--- a/core/java/android/view/NotificationHeaderView.java
+++ b/core/java/android/view/NotificationHeaderView.java
@@ -32,6 +32,7 @@
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.util.AttributeSet;
+import android.util.TypedValue;
import android.widget.FrameLayout;
import android.widget.RelativeLayout;
import android.widget.RemoteViews;
@@ -265,14 +266,20 @@
? R.style.TextAppearance_DeviceDefault_Notification_Title
: R.style.TextAppearance_DeviceDefault_Notification_Info;
// Most of the time, we're showing text in the minimized state
- View headerText = findViewById(R.id.header_text);
- if (headerText instanceof TextView) {
- ((TextView) headerText).setTextAppearance(styleResId);
+ if (findViewById(R.id.header_text) instanceof TextView headerText) {
+ headerText.setTextAppearance(styleResId);
+ if (notificationsRedesignTemplates()) {
+ // TODO: b/378660052 - When inlining the redesign flag, this should be updated
+ // directly in TextAppearance_DeviceDefault_Notification_Title so we won't need to
+ // override it here.
+ float textSize = getContext().getResources().getDimension(
+ R.dimen.notification_2025_title_text_size);
+ headerText.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize);
+ }
}
// If there's no summary or text, we show the app name instead of nothing
- View appNameText = findViewById(R.id.app_name_text);
- if (appNameText instanceof TextView) {
- ((TextView) appNameText).setTextAppearance(styleResId);
+ if (findViewById(R.id.app_name_text) instanceof TextView appNameText) {
+ appNameText.setTextAppearance(styleResId);
}
}
diff --git a/core/java/android/window/DesktopModeFlags.java b/core/java/android/window/DesktopModeFlags.java
index 2918610..9468301 100644
--- a/core/java/android/window/DesktopModeFlags.java
+++ b/core/java/android/window/DesktopModeFlags.java
@@ -95,7 +95,9 @@
Flags::enableTopVisibleRootTaskPerUserTracking, true),
ENABLE_DESKTOP_RECENTS_TRANSITIONS_CORNERS_BUGFIX(
Flags::enableDesktopRecentsTransitionsCornersBugfix, false),
- ENABLE_DESKTOP_SYSTEM_DIALOGS_TRANSITIONS(Flags::enableDesktopSystemDialogsTransitions, true);
+ ENABLE_DESKTOP_SYSTEM_DIALOGS_TRANSITIONS(Flags::enableDesktopSystemDialogsTransitions, true),
+ ENABLE_DESKTOP_WINDOWING_MULTI_INSTANCE_FEATURES(
+ Flags::enableDesktopWindowingMultiInstanceFeatures, true);
/**
* Flag class, to be used in case the enum cannot be used because the flag is not accessible.
diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl
index 72cb9d1..98d1ef6 100644
--- a/core/java/com/android/internal/statusbar/IStatusBar.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl
@@ -217,9 +217,9 @@
void setUdfpsRefreshRateCallback(in IUdfpsRefreshRateRequestCallback callback);
/**
- * Notifies System UI that the display is ready to show system decorations.
+ * Notifies System UI that the system decorations should be added on the display.
*/
- void onDisplayReady(int displayId);
+ void onDisplayAddSystemDecorations(int displayId);
/**
* Notifies System UI that the system decorations should be removed from the display.
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index 3afe27e..a2f4ca2 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -280,6 +280,7 @@
],
static_libs: [
+ "android.os.flags-aconfig-cc",
"libasync_safe",
"libbinderthreadstateutils",
"libdmabufinfo",
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index aea1734..5c0b720 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -22,6 +22,7 @@
#include <android-base/parsebool.h>
#include <android-base/properties.h>
#include <android/graphics/jni_runtime.h>
+#include <android_os.h>
#include <android_runtime/AndroidRuntime.h>
#include <assert.h>
#include <binder/IBinder.h>
@@ -893,9 +894,13 @@
madviseWillNeedFileSizeOdex,
"-XMadviseWillNeedOdexFileSize:");
- parseRuntimeOption("dalvik.vm.madvise.artfile.size",
- madviseWillNeedFileSizeArt,
- "-XMadviseWillNeedArtFileSize:");
+ // Historically, dalvik.vm.madvise.artfile.size was set to UINT_MAX by default. With the
+ // disable_madvise_art_default flag rollout, we use this default only when the flag is disabled.
+ // TODO(b/382110550): Remove this property/flag entirely after validating and ramping.
+ const char* madvise_artfile_size_default =
+ android::os::disable_madvise_artfile_default() ? "" : "4294967295";
+ parseRuntimeOption("dalvik.vm.madvise.artfile.size", madviseWillNeedFileSizeArt,
+ "-XMadviseWillNeedArtFileSize:", madvise_artfile_size_default);
/*
* Profile related options.
diff --git a/core/jni/android_util_Binder.cpp b/core/jni/android_util_Binder.cpp
index 91b25c2..639f5bf 100644
--- a/core/jni/android_util_Binder.cpp
+++ b/core/jni/android_util_Binder.cpp
@@ -74,7 +74,6 @@
jmethodID mExecTransact;
jmethodID mGetInterfaceDescriptor;
jmethodID mTransactionCallback;
- jmethodID mGetExtension;
// Object state.
jfieldID mObject;
@@ -490,12 +489,8 @@
if (mVintf) {
::android::internal::Stability::markVintf(b.get());
}
- if (mSetExtensionCalled) {
- jobject javaIBinderObject = env->CallObjectMethod(obj, gBinderOffsets.mGetExtension);
- sp<IBinder> extensionFromJava = ibinderForJavaObject(env, javaIBinderObject);
- if (extensionFromJava != nullptr) {
- b.get()->setExtension(extensionFromJava);
- }
+ if (mExtension != nullptr) {
+ b.get()->setExtension(mExtension);
}
mBinder = b;
ALOGV("Creating JavaBinder %p (refs %p) for Object %p, weakCount=%" PRId32 "\n",
@@ -521,12 +516,21 @@
mVintf = false;
}
- void setExtension(const sp<IBinder>& extension) {
+ sp<IBinder> getExtension() {
AutoMutex _l(mLock);
- mSetExtensionCalled = true;
sp<JavaBBinder> b = mBinder.promote();
if (b != nullptr) {
- b.get()->setExtension(extension);
+ return b.get()->getExtension();
+ }
+ return mExtension;
+ }
+
+ void setExtension(const sp<IBinder>& extension) {
+ AutoMutex _l(mLock);
+ mExtension = extension;
+ sp<JavaBBinder> b = mBinder.promote();
+ if (b != nullptr) {
+ b.get()->setExtension(mExtension);
}
}
@@ -538,7 +542,8 @@
// is too much binder state here, we can think about making JavaBBinder an
// sp here (avoid recreating it)
bool mVintf = false;
- bool mSetExtensionCalled = false;
+
+ sp<IBinder> mExtension;
};
// ----------------------------------------------------------------------------
@@ -1244,6 +1249,10 @@
return IPCThreadState::self()->blockUntilThreadAvailable();
}
+static jobject android_os_Binder_getExtension(JNIEnv* env, jobject obj) {
+ JavaBBinderHolder* jbh = (JavaBBinderHolder*) env->GetLongField(obj, gBinderOffsets.mObject);
+ return javaObjectForIBinder(env, jbh->getExtension());
+}
static void android_os_Binder_setExtension(JNIEnv* env, jobject obj, jobject extensionObject) {
JavaBBinderHolder* jbh = (JavaBBinderHolder*) env->GetLongField(obj, gBinderOffsets.mObject);
@@ -1286,7 +1295,8 @@
{ "getNativeBBinderHolder", "()J", (void*)android_os_Binder_getNativeBBinderHolder },
{ "getNativeFinalizer", "()J", (void*)android_os_Binder_getNativeFinalizer },
{ "blockUntilThreadAvailable", "()V", (void*)android_os_Binder_blockUntilThreadAvailable },
- { "setExtensionNative", "(Landroid/os/IBinder;)V", (void*)android_os_Binder_setExtension },
+ { "getExtension", "()Landroid/os/IBinder;", (void*)android_os_Binder_getExtension },
+ { "setExtension", "(Landroid/os/IBinder;)V", (void*)android_os_Binder_setExtension },
};
// clang-format on
@@ -1303,8 +1313,6 @@
gBinderOffsets.mTransactionCallback =
GetStaticMethodIDOrDie(env, clazz, "transactionCallback", "(IIII)V");
gBinderOffsets.mObject = GetFieldIDOrDie(env, clazz, "mObject", "J");
- gBinderOffsets.mGetExtension = GetMethodIDOrDie(env, clazz, "getExtension",
- "()Landroid/os/IBinder;");
return RegisterMethodsOrDie(
env, kBinderPathName,
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index aad8f8a..005c14d 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -8308,26 +8308,9 @@
android:featureFlag="android.app.appfunctions.flags.enable_app_function_manager"
android:protectionLevel="signature" />
- <!-- Allows a trusted application to perform actions on behalf of users inside of
- applications with privacy guarantees from the system.
- <p>This permission is currently only granted to system packages in the
- {@link android.app.role.SYSTEM_UI_INTELLIGENCE} role which complies with privacy
- requirements outlined in the Android CDD section "9.8.6 Content Capture".
- <p>Apps are not able to opt-out from caller having this permission.
- <p>Protection level: internal|role
- @SystemApi
- @hide
- @FlaggedApi(android.app.appfunctions.flags.Flags.FLAG_ENABLE_APP_FUNCTION_MANAGER) -->
- <permission android:name="android.permission.EXECUTE_APP_FUNCTIONS_TRUSTED"
- android:featureFlag="android.app.appfunctions.flags.enable_app_function_manager"
- android:protectionLevel="internal|role" />
-
<!-- Allows an application to perform actions on behalf of users inside of
applications.
<p>This permission is currently only granted to privileged system apps.
- <p>Apps contributing app functions can opt to disallow callers with this permission,
- limiting to only callers with {@link android.permission.EXECUTE_APP_FUNCTIONS_TRUSTED}
- instead.
<p>Protection level: internal|privileged
@FlaggedApi(android.app.appfunctions.flags.Flags.FLAG_ENABLE_APP_FUNCTION_MANAGER) -->
<permission android:name="android.permission.EXECUTE_APP_FUNCTIONS"
diff --git a/core/res/res/layout/notification_2025_conversation_header.xml b/core/res/res/layout/notification_2025_conversation_header.xml
index 1bde173..75bd244 100644
--- a/core/res/res/layout/notification_2025_conversation_header.xml
+++ b/core/res/res/layout/notification_2025_conversation_header.xml
@@ -29,7 +29,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Title"
- android:textSize="16sp"
+ android:textSize="@dimen/notification_2025_title_text_size"
android:singleLine="true"
android:layout_weight="1"
/>
diff --git a/core/res/res/layout/notification_2025_template_collapsed_base.xml b/core/res/res/layout/notification_2025_template_collapsed_base.xml
index d29b7af..05458329 100644
--- a/core/res/res/layout/notification_2025_template_collapsed_base.xml
+++ b/core/res/res/layout/notification_2025_template_collapsed_base.xml
@@ -102,6 +102,7 @@
android:singleLine="true"
android:textAlignment="viewStart"
android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Title"
+ android:textSize="@dimen/notification_2025_title_text_size"
/>
<include layout="@layout/notification_2025_top_line_views" />
diff --git a/core/res/res/layout/notification_2025_template_collapsed_media.xml b/core/res/res/layout/notification_2025_template_collapsed_media.xml
index 5beab50..9959b66 100644
--- a/core/res/res/layout/notification_2025_template_collapsed_media.xml
+++ b/core/res/res/layout/notification_2025_template_collapsed_media.xml
@@ -104,6 +104,7 @@
android:singleLine="true"
android:textAlignment="viewStart"
android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Title"
+ android:textSize="@dimen/notification_2025_title_text_size"
/>
<include layout="@layout/notification_2025_top_line_views" />
diff --git a/core/res/res/layout/notification_2025_template_collapsed_messaging.xml b/core/res/res/layout/notification_2025_template_collapsed_messaging.xml
index d7c3263..85ca124 100644
--- a/core/res/res/layout/notification_2025_template_collapsed_messaging.xml
+++ b/core/res/res/layout/notification_2025_template_collapsed_messaging.xml
@@ -130,6 +130,7 @@
android:singleLine="true"
android:textAlignment="viewStart"
android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Title"
+ android:textSize="@dimen/notification_2025_title_text_size"
/>
<include layout="@layout/notification_2025_top_line_views" />
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index d6b8704..5644cf9 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -577,6 +577,9 @@
<dimen name="notification_text_size">14sp</dimen>
<!-- Size of notification text titles (see TextAppearance.StatusBar.EventContent.Title) -->
<dimen name="notification_title_text_size">14sp</dimen>
+ <!-- Size of notification text titles, 2025 redesign version (see TextAppearance.StatusBar.EventContent.Title) -->
+ <!-- TODO: b/378660052 - When inlining the redesign flag, this should be updated directly in TextAppearance.DeviceDefault.Notification.Title -->
+ <dimen name="notification_2025_title_text_size">16sp</dimen>
<!-- Size of big notification text titles (see TextAppearance.StatusBar.EventContent.BigTitle) -->
<dimen name="notification_big_title_text_size">16sp</dimen>
<!-- Size of smaller notification text (see TextAppearance.StatusBar.EventContent.Line2, Info, Time) -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index aca9d30..8bb3c99 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -574,6 +574,7 @@
<java-symbol type="dimen" name="notification_text_size" />
<java-symbol type="dimen" name="notification_title_text_size" />
<java-symbol type="dimen" name="notification_subtext_size" />
+ <java-symbol type="dimen" name="notification_2025_title_text_size" />
<java-symbol type="dimen" name="notification_top_pad" />
<java-symbol type="dimen" name="notification_top_pad_narrow" />
<java-symbol type="dimen" name="notification_top_pad_large_text" />
diff --git a/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java b/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java
index dc2f0a6..8fa5103 100644
--- a/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java
+++ b/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java
@@ -307,10 +307,8 @@
assertEquals(DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_ADDED,
mDisplayManagerGlobal
.mapFiltersToInternalEventFlag(DisplayManager.EVENT_TYPE_DISPLAY_ADDED, 0));
- assertEquals(DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_BASIC_CHANGED,
- mDisplayManagerGlobal
- .mapFiltersToInternalEventFlag(DisplayManager.EVENT_TYPE_DISPLAY_CHANGED,
- 0));
+ assertEquals(DISPLAY_CHANGE_EVENTS, mDisplayManagerGlobal
+ .mapFiltersToInternalEventFlag(DisplayManager.EVENT_TYPE_DISPLAY_CHANGED, 0));
assertEquals(DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_REMOVED,
mDisplayManagerGlobal.mapFiltersToInternalEventFlag(
DisplayManager.EVENT_TYPE_DISPLAY_REMOVED, 0));
diff --git a/core/tests/coretests/src/android/os/BundleTest.java b/core/tests/coretests/src/android/os/BundleTest.java
index 31e0752..9aac02d 100644
--- a/core/tests/coretests/src/android/os/BundleTest.java
+++ b/core/tests/coretests/src/android/os/BundleTest.java
@@ -76,7 +76,7 @@
/**
* Create a test bundle, parcel it and return the parcel.
*/
- private Parcel createBundleParcel(boolean withFd) throws Exception {
+ private Parcel createBundleParcel(boolean withFd, boolean hasIntent) throws Exception {
final Bundle source = new Bundle();
source.putString("string", "abc");
source.putInt("int", 1);
@@ -85,13 +85,14 @@
pipe[1].close();
source.putParcelable("fd", pipe[0]);
}
+ source.setHasIntent(hasIntent);
return getParcelledBundle(source);
}
/**
* Verify a bundle generated by {@link #createBundleParcel(boolean)}.
*/
- private void checkBundle(Bundle b, boolean withFd) {
+ private void checkBundle(Bundle b, boolean withFd, boolean hasIntent) {
// First, do the checks without actually unparceling the bundle.
// (Note looking into the contents will unparcel a bundle, so we'll do it later.)
assertTrue("mParcelledData shouldn't be null here.", b.isParcelled());
@@ -107,6 +108,8 @@
b.mFlags & (Bundle.FLAG_HAS_FDS | Bundle.FLAG_HAS_FDS_KNOWN));
}
+ assertEquals(b.hasIntent(), hasIntent);
+
// Then, check the contents.
assertEquals("abc", b.getString("string"));
assertEquals(1, b.getInt("int"));
@@ -139,42 +142,56 @@
withFd = false;
// new Bundle with p
- p = createBundleParcel(withFd);
- checkBundle(new Bundle(p), withFd);
+ p = createBundleParcel(withFd, false);
+ checkBundle(new Bundle(p), withFd, false);
p.recycle();
// new Bundle with p and length
- p = createBundleParcel(withFd);
+ p = createBundleParcel(withFd, false);
length = p.readInt();
- checkBundle(new Bundle(p, length), withFd);
+ checkBundle(new Bundle(p, length), withFd, false);
p.recycle();
// readFromParcel()
- p = createBundleParcel(withFd);
+ p = createBundleParcel(withFd, false);
b = new Bundle();
b.readFromParcel(p);
- checkBundle(b, withFd);
+ checkBundle(b, withFd, false);
+ p.recycle();
+
+ // readFromParcel()
+ p = createBundleParcel(withFd, true);
+ b = new Bundle();
+ b.readFromParcel(p);
+ checkBundle(b, withFd, true);
p.recycle();
// Same test with FDs.
withFd = true;
// new Bundle with p
- p = createBundleParcel(withFd);
- checkBundle(new Bundle(p), withFd);
+ p = createBundleParcel(withFd, false);
+ checkBundle(new Bundle(p), withFd, false);
p.recycle();
// new Bundle with p and length
- p = createBundleParcel(withFd);
+ p = createBundleParcel(withFd, false);
length = p.readInt();
- checkBundle(new Bundle(p, length), withFd);
+ checkBundle(new Bundle(p, length), withFd, false);
p.recycle();
// readFromParcel()
- p = createBundleParcel(withFd);
+ p = createBundleParcel(withFd, false);
b = new Bundle();
b.readFromParcel(p);
- checkBundle(b, withFd);
+ checkBundle(b, withFd, false);
+ p.recycle();
+
+ // readFromParcel()
+ p = createBundleParcel(withFd, true);
+ b = new Bundle();
+ b.readFromParcel(p);
+ checkBundle(b, withFd, true);
p.recycle();
}
@@ -486,6 +503,7 @@
p.writeInt(131313); // Invalid type
p.writeInt(0); // Anything, really
int end = p.dataPosition();
+ p.writeBoolean(false);
p.setDataPosition(0);
return new Bundle(p, end - start);
}
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index b8059d0..1edbffa 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -594,7 +594,6 @@
<!-- Permission required for CTS test - FileIntegrityManagerTest -->
<permission name="android.permission.SETUP_FSVERITY" />
<!-- Permissions required for CTS test - AppFunctionManagerTest -->
- <permission name="android.permission.EXECUTE_APP_FUNCTIONS_TRUSTED" />
<permission name="android.permission.EXECUTE_APP_FUNCTIONS" />
<!-- Permission required for CTS test - CtsNfcTestCases -->
<permission name="android.permission.NFC_SET_CONTROLLER_ALWAYS_ON" />
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeCompatPolicy.kt b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeCompatPolicy.kt
index 0ea3c2a..835456b 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeCompatPolicy.kt
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeCompatPolicy.kt
@@ -20,20 +20,23 @@
import android.content.Context
import android.window.DesktopModeFlags
import com.android.internal.R
+import java.util.ArrayList
/**
* Class to decide whether to apply app compat policies in desktop mode.
*/
// TODO(b/347289970): Consider replacing with API
-class DesktopModeCompatPolicy(context: Context) {
+class DesktopModeCompatPolicy(private val context: Context) {
private val systemUiPackage: String = context.resources.getString(R.string.config_systemUi)
+ private val defaultHomePackage: String?
+ get() = context.getPackageManager().getHomeActivities(ArrayList())?.packageName
/**
* If the top activity should be exempt from desktop windowing and forced back to fullscreen.
- * Currently includes all system ui activities and modal dialogs. However if the top activity is
- * not being displayed, regardless of its configuration, we will not exempt it as to remain in
- * the desktop windowing environment.
+ * Currently includes all system ui, default home and transparent stack activities. However if
+ * the top activity is not being displayed, regardless of its configuration, we will not exempt
+ * it as to remain in the desktop windowing environment.
*/
fun isTopActivityExemptFromDesktopWindowing(task: TaskInfo) =
isTopActivityExemptFromDesktopWindowing(task.baseActivity?.packageName,
@@ -43,6 +46,7 @@
numActivities: Int, isTopActivityNoDisplay: Boolean, isActivityStackTransparent: Boolean) =
DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_MODALS_POLICY.isTrue
&& ((isSystemUiTask(packageName)
+ || isPartOfDefaultHomePackage(packageName)
|| isTransparentTask(isActivityStackTransparent, numActivities))
&& !isTopActivityNoDisplay)
@@ -57,4 +61,10 @@
isActivityStackTransparent && numActivities > 0
private fun isSystemUiTask(packageName: String?) = packageName == systemUiPackage
+
+ /**
+ * Returns true if the tasks base activity is part of the default home package.
+ */
+ private fun isPartOfDefaultHomePackage(packageName: String?) =
+ packageName != null && packageName == defaultHomePackage
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java
index b4ef9f0f..55ed5fa 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java
@@ -168,7 +168,8 @@
@Override
public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
- @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
+ @NonNull SurfaceControl.Transaction startT, @NonNull SurfaceControl.Transaction finishT,
+ @NonNull IBinder mergeTarget,
@NonNull Transitions.TransitionFinishCallback finishCallback) {
mAnimationRunner.cancelAnimationFromMerge();
}
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 8dabd54..d1c7f7d 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
@@ -1463,7 +1463,9 @@
@Override
public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
- @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
+ @NonNull SurfaceControl.Transaction startT,
+ @NonNull SurfaceControl.Transaction finishT,
+ @NonNull IBinder mergeTarget,
@NonNull Transitions.TransitionFinishCallback finishCallback) {
if (mClosePrepareTransition == transition) {
mClosePrepareTransition = null;
@@ -1476,7 +1478,7 @@
if (info.getType() == TRANSIT_CLOSE_PREPARE_BACK_NAVIGATION
&& !mCloseTransitionRequested && info.getChanges().isEmpty() && mApps == null) {
finishCallback.onTransitionFinished(null);
- t.apply();
+ startT.apply();
applyFinishOpenTransition();
return;
}
@@ -1489,7 +1491,7 @@
}
// Handle the commit transition if this handler is running the open transition.
finishCallback.onTransitionFinished(null);
- t.apply();
+ startT.apply();
if (mCloseTransitionRequested) {
if (mApps == null || mApps.length == 0) {
// animation was done
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTransitions.java
index 29fb1a2..48b83ce 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTransitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTransitions.java
@@ -248,7 +248,9 @@
@Override
public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
- @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
+ @NonNull SurfaceControl.Transaction startT,
+ @NonNull SurfaceControl.Transaction finishT,
+ @NonNull IBinder mergeTarget,
@NonNull Transitions.TransitionFinishCallback finishCallback) {
}
@@ -423,7 +425,9 @@
@Override
public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
- @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
+ @NonNull SurfaceControl.Transaction startT,
+ @NonNull SurfaceControl.Transaction finishT,
+ @NonNull IBinder mergeTarget,
@NonNull Transitions.TransitionFinishCallback finishCallback) {
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java
index e69d60d..4c3bde9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java
@@ -39,6 +39,7 @@
import com.android.window.flags.Flags;
import com.android.wm.shell.common.DisplayChangeController.OnDisplayChangingListener;
import com.android.wm.shell.shared.annotations.ShellMainThread;
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
import com.android.wm.shell.sysui.ShellInit;
import java.util.ArrayList;
@@ -91,7 +92,8 @@
onDisplayAdded(displayIds[i]);
}
- if (Flags.enableConnectedDisplaysWindowDrag()) {
+ if (Flags.enableConnectedDisplaysWindowDrag()
+ && DesktopModeStatus.canEnterDesktopMode(mContext)) {
mDisplayManager.registerTopologyListener(mMainExecutor,
this::onDisplayTopologyChanged);
onDisplayTopologyChanged(mDisplayManager.getDisplayTopology());
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/UserProfileContexts.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/UserProfileContexts.kt
index 0577f9e..1693864 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/UserProfileContexts.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/UserProfileContexts.kt
@@ -25,6 +25,7 @@
import com.android.wm.shell.sysui.ShellController
import com.android.wm.shell.sysui.ShellInit
import com.android.wm.shell.sysui.UserChangeListener
+import androidx.core.util.size
/** Creates and manages contexts for all the profiles of the current user. */
class UserProfileContexts(
@@ -35,6 +36,8 @@
// Contexts for all the profiles of the current user.
private val currentProfilesContext = SparseArray<Context>()
+ private val shellUserId = baseContext.userId
+
lateinit var userContext: Context
private set
@@ -49,6 +52,9 @@
currentProfilesContext.clear()
this@UserProfileContexts.userContext = userContext
currentProfilesContext.put(newUserId, userContext)
+ if (newUserId != shellUserId) {
+ currentProfilesContext.put(shellUserId, baseContext)
+ }
}
override fun onUserProfilesChanged(profiles: List<UserInfo>) {
@@ -69,9 +75,9 @@
currentProfilesContext.put(profile.id, profileContext)
}
val profilesToRemove = buildList<Int> {
- for (i in 0..<currentProfilesContext.size()) {
+ for (i in 0..<currentProfilesContext.size) {
val userId = currentProfilesContext.keyAt(i)
- if (profiles.none { it.id == userId }) {
+ if (userId != shellUserId && profiles.none { it.id == userId }) {
add(userId)
}
}
@@ -80,4 +86,12 @@
}
operator fun get(userId: Int): Context? = currentProfilesContext.get(userId)
+
+ fun getOrCreate(userId: Int): Context {
+ val context = currentProfilesContext[userId]
+ if (context != null) return context
+ return baseContext.createContextAsUser(UserHandle.of(userId), /* flags= */ 0).also {
+ currentProfilesContext[userId] = it
+ }
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index c81838f..0f232d5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -111,6 +111,7 @@
import com.android.wm.shell.desktopmode.education.data.AppHandleEducationDatastoreRepository;
import com.android.wm.shell.desktopmode.education.data.AppToWebEducationDatastoreRepository;
import com.android.wm.shell.desktopmode.multidesks.DesksOrganizer;
+import com.android.wm.shell.desktopmode.multidesks.DesksTransitionObserver;
import com.android.wm.shell.desktopmode.multidesks.RootTaskDesksOrganizer;
import com.android.wm.shell.desktopmode.persistence.DesktopPersistentRepository;
import com.android.wm.shell.desktopmode.persistence.DesktopRepositoryInitializer;
@@ -760,6 +761,7 @@
Optional<BubbleController> bubbleController,
OverviewToDesktopTransitionObserver overviewToDesktopTransitionObserver,
DesksOrganizer desksOrganizer,
+ DesksTransitionObserver desksTransitionObserver,
UserProfileContexts userProfileContexts,
DesktopModeCompatPolicy desktopModeCompatPolicy) {
return new DesktopTasksController(
@@ -797,6 +799,7 @@
bubbleController,
overviewToDesktopTransitionObserver,
desksOrganizer,
+ desksTransitionObserver,
userProfileContexts,
desktopModeCompatPolicy);
}
@@ -1134,6 +1137,7 @@
Optional<DesktopMixedTransitionHandler> desktopMixedTransitionHandler,
Optional<BackAnimationController> backAnimationController,
DesktopWallpaperActivityTokenProvider desktopWallpaperActivityTokenProvider,
+ @NonNull DesksTransitionObserver desksTransitionObserver,
ShellInit shellInit) {
return desktopUserRepositories.flatMap(
repository ->
@@ -1146,11 +1150,20 @@
desktopMixedTransitionHandler.get(),
backAnimationController.get(),
desktopWallpaperActivityTokenProvider,
+ desksTransitionObserver,
shellInit)));
}
@WMSingleton
@Provides
+ static DesksTransitionObserver provideDesksTransitionObserver(
+ @NonNull @DynamicOverride DesktopUserRepositories desktopUserRepositories
+ ) {
+ return new DesksTransitionObserver(desktopUserRepositories);
+ }
+
+ @WMSingleton
+ @Provides
static Optional<DesktopMixedTransitionHandler> provideDesktopMixedTransitionHandler(
Context context,
Transitions transitions,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeShellCommandHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeShellCommandHandler.kt
index 164d04b..b93d2e3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeShellCommandHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeShellCommandHandler.kt
@@ -152,8 +152,8 @@
pw.println("Error: desk id should be an integer")
return false
}
- pw.println("Not implemented.")
- return false
+ controller.removeDesk(deskId)
+ return true
}
private fun runRemoveAllDesks(args: Array<String>, pw: PrintWriter): Boolean {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt
index 4ff1a5f..043b353 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt
@@ -174,6 +174,9 @@
/** Returns the number of desks in the given display. */
fun getNumberOfDesks(displayId: Int) = desktopData.getNumberOfDesks(displayId)
+ /** Returns the display the given desk is in. */
+ fun getDisplayForDesk(deskId: Int) = desktopData.getDisplayForDesk(deskId)
+
/** Adds [regionListener] to inform about changes to exclusion regions for all Desktop tasks. */
fun setExclusionRegionListener(regionListener: Consumer<Region>, executor: Executor) {
desktopGestureExclusionListener = regionListener
@@ -207,6 +210,14 @@
desktopData.createDesk(displayId, deskId)
}
+ /** Returns the ids of the existing desks in the given display. */
+ @VisibleForTesting
+ fun getDeskIds(displayId: Int): Set<Int> =
+ desktopData.desksSequence(displayId).map { desk -> desk.deskId }.toSet()
+
+ /** Returns the id of the default desk in the given display. */
+ fun getDefaultDeskId(displayId: Int): Int? = getDefaultDesk(displayId)?.deskId
+
/** Returns the default desk in the given display. */
private fun getDefaultDesk(displayId: Int): Desk? = desktopData.getDefaultDesk(displayId)
@@ -716,17 +727,13 @@
}
}
- /**
- * Removes the active desk for the given [displayId] and returns the active tasks on that desk.
- *
- * TODO: b/389960283 - add explicit [deskId] argument.
- */
- fun removeDesk(displayId: Int): ArraySet<Int> {
- val desk = desktopData.getActiveDesk(displayId)
- if (desk == null) {
- logW("Could not find desk to remove: displayId=%d", displayId)
- return ArraySet()
- }
+ /** Removes the given desk and returns the active tasks in that desk. */
+ fun removeDesk(deskId: Int): Set<Int> {
+ val desk =
+ desktopData.getDesk(deskId)
+ ?: return emptySet<Int>().also {
+ logW("Could not find desk to remove: deskId=%d", deskId)
+ }
val activeTasks = ArraySet(desk.activeTasks)
desktopData.remove(desk.deskId)
return activeTasks
@@ -1066,7 +1073,7 @@
}
override fun getDisplayForDesk(deskId: Int): Int =
- getAllActiveDesks().find { it.deskId == deskId }?.displayId
+ desksSequence().find { it.deskId == deskId }?.displayId
?: error("Display for desk=$deskId not found")
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index 10f8705..3f88e7b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -103,7 +103,9 @@
import com.android.wm.shell.desktopmode.common.ToggleTaskSizeInteraction
import com.android.wm.shell.desktopmode.desktopwallpaperactivity.DesktopWallpaperActivityTokenProvider
import com.android.wm.shell.desktopmode.minimize.DesktopWindowLimitRemoteHandler
+import com.android.wm.shell.desktopmode.multidesks.DeskTransition
import com.android.wm.shell.desktopmode.multidesks.DesksOrganizer
+import com.android.wm.shell.desktopmode.multidesks.DesksTransitionObserver
import com.android.wm.shell.desktopmode.multidesks.OnDeskRemovedListener
import com.android.wm.shell.draganddrop.DragAndDropController
import com.android.wm.shell.freeform.FreeformTaskTransitionStarter
@@ -185,6 +187,7 @@
private val bubbleController: Optional<BubbleController>,
private val overviewToDesktopTransitionObserver: OverviewToDesktopTransitionObserver,
private val desksOrganizer: DesksOrganizer,
+ private val desksTransitionObserver: DesksTransitionObserver,
private val userProfileContexts: UserProfileContexts,
private val desktopModeCompatPolicy: DesktopModeCompatPolicy,
) :
@@ -1875,9 +1878,10 @@
// need updates in some cases.
val baseActivity = callingTaskInfo.baseActivity ?: return
val fillIn: Intent =
- userProfileContexts[callingTaskInfo.userId]
- ?.packageManager
- ?.getLaunchIntentForPackage(baseActivity.packageName) ?: return
+ userProfileContexts
+ .getOrCreate(callingTaskInfo.userId)
+ .packageManager
+ .getLaunchIntentForPackage(baseActivity.packageName) ?: return
fillIn.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_MULTIPLE_TASK)
val launchIntent =
PendingIntent.getActivity(
@@ -2373,20 +2377,62 @@
)
}
- fun removeDesktop(displayId: Int) {
- if (!DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION.isTrue()) return
-
- val tasksToRemove = taskRepository.removeDesk(displayId)
- val wct = WindowContainerTransaction()
- tasksToRemove.forEach {
- val task = shellTaskOrganizer.getRunningTaskInfo(it)
- if (task != null) {
- wct.removeTask(task.token)
- } else {
- recentTasksController?.removeBackgroundTask(it)
+ /** Removes the default desk in the given display. */
+ @Deprecated("Deprecated with multi-desks.", ReplaceWith("removeDesk()"))
+ fun removeDefaultDeskInDisplay(displayId: Int) {
+ val deskId =
+ checkNotNull(taskRepository.getDefaultDeskId(displayId)) {
+ "Expected a default desk to exist"
}
+ removeDesk(displayId = displayId, deskId = deskId)
+ }
+
+ /** Removes the given desk. */
+ fun removeDesk(deskId: Int) {
+ val displayId = taskRepository.getDisplayForDesk(deskId)
+ removeDesk(displayId = displayId, deskId = deskId)
+ }
+
+ private fun removeDesk(displayId: Int, deskId: Int) {
+ if (!DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION.isTrue()) return
+ logV("removeDesk deskId=%d from displayId=%d", deskId, displayId)
+
+ val tasksToRemove =
+ if (Flags.enableMultipleDesktopsBackend()) {
+ taskRepository.getActiveTaskIdsInDesk(deskId)
+ } else {
+ // TODO: 362720497 - make sure minimized windows are also removed in WM
+ // and the repository.
+ taskRepository.removeDesk(deskId)
+ }
+
+ val wct = WindowContainerTransaction()
+ if (!Flags.enableMultipleDesktopsBackend()) {
+ tasksToRemove.forEach {
+ val task = shellTaskOrganizer.getRunningTaskInfo(it)
+ if (task != null) {
+ wct.removeTask(task.token)
+ } else {
+ recentTasksController?.removeBackgroundTask(it)
+ }
+ }
+ } else {
+ // TODO: 362720497 - double check background tasks are also removed.
+ desksOrganizer.removeDesk(wct, deskId)
}
- if (!wct.isEmpty) transitions.startTransition(TRANSIT_CLOSE, wct, null)
+ if (!Flags.enableMultipleDesktopsBackend() && wct.isEmpty) return
+ val transition = transitions.startTransition(TRANSIT_CLOSE, wct, /* handler= */ null)
+ if (Flags.enableMultipleDesktopsBackend()) {
+ desksTransitionObserver.addPendingTransition(
+ DeskTransition.RemoveDesk(
+ token = transition,
+ displayId = displayId,
+ deskId = deskId,
+ tasks = tasksToRemove,
+ onDeskRemovedListener = onDeskRemovedListener,
+ )
+ )
+ }
}
/** Enter split by using the focused desktop task in given `displayId`. */
@@ -3090,7 +3136,7 @@
override fun removeDesktop(displayId: Int) {
executeRemoteCallWithTaskPermission(controller, "removeDesktop") { c ->
- c.removeDesktop(displayId)
+ c.removeDefaultDeskInDisplay(displayId)
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt
index b364869..3ada988 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt
@@ -37,6 +37,7 @@
import com.android.wm.shell.back.BackAnimationController
import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.isExitDesktopModeTransition
import com.android.wm.shell.desktopmode.desktopwallpaperactivity.DesktopWallpaperActivityTokenProvider
+import com.android.wm.shell.desktopmode.multidesks.DesksTransitionObserver
import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
import com.android.wm.shell.shared.TransitionUtil
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
@@ -58,6 +59,7 @@
private val desktopMixedTransitionHandler: DesktopMixedTransitionHandler,
private val backAnimationController: BackAnimationController,
private val desktopWallpaperActivityTokenProvider: DesktopWallpaperActivityTokenProvider,
+ private val desksTransitionObserver: DesksTransitionObserver,
shellInit: ShellInit,
) : Transitions.TransitionObserver {
@@ -87,6 +89,7 @@
finishTransaction: SurfaceControl.Transaction,
) {
// TODO: b/332682201 Update repository state
+ desksTransitionObserver.onTransitionReady(transition, info)
if (
DesktopModeFlags.INCLUDE_TOP_TRANSPARENT_FULLSCREEN_TASK_IN_DESKTOP_HEURISTIC
.isTrue() && DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_MODALS_POLICY.isTrue()
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt
index 91f10dc..cc3d86c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt
@@ -458,7 +458,8 @@
override fun mergeAnimation(
transition: IBinder,
info: TransitionInfo,
- t: SurfaceControl.Transaction,
+ startT: SurfaceControl.Transaction,
+ finishT: SurfaceControl.Transaction,
mergeTarget: IBinder,
finishCallback: Transitions.TransitionFinishCallback,
) {
@@ -488,18 +489,18 @@
if (isEndTransition) {
setupEndDragToDesktop(
info,
- startTransaction = t,
+ startTransaction = startT,
finishTransaction = startTransactionFinishT,
)
// Call finishCallback to merge animation before startTransitionFinishCb is called
finishCallback.onTransitionFinished(/* wct= */ null)
- animateEndDragToDesktop(startTransaction = t, startTransitionFinishCb)
+ animateEndDragToDesktop(startTransaction = startT, startTransitionFinishCb)
} else if (isCancelTransition) {
info.changes.forEach { change ->
- t.show(change.leash)
+ startT.show(change.leash)
startTransactionFinishT.show(change.leash)
}
- t.apply()
+ startT.apply()
finishCallback.onTransitionFinished(/* wct= */ null)
startTransitionFinishCb.onTransitionFinished(/* wct= */ null)
clearState()
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/DeskTransition.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/DeskTransition.kt
new file mode 100644
index 0000000..47088c0
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/DeskTransition.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.wm.shell.desktopmode.multidesks
+
+import android.os.IBinder
+
+/** Represents shell-started transitions involving desks. */
+sealed class DeskTransition {
+ /** The transition token. */
+ abstract val token: IBinder
+
+ /** A transition to remove a desk and its tasks from a display. */
+ data class RemoveDesk(
+ override val token: IBinder,
+ val displayId: Int,
+ val deskId: Int,
+ val tasks: Set<Int>,
+ val onDeskRemovedListener: OnDeskRemovedListener?,
+ ) : DeskTransition()
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/DesksTransitionObserver.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/DesksTransitionObserver.kt
new file mode 100644
index 0000000..3e49b8a
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/DesksTransitionObserver.kt
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.wm.shell.desktopmode.multidesks
+
+import android.os.IBinder
+import android.view.WindowManager.TRANSIT_CLOSE
+import android.window.TransitionInfo
+import com.android.window.flags.Flags
+import com.android.wm.shell.desktopmode.DesktopUserRepositories
+
+/**
+ * Observer of desk-related transitions, such as adding, removing or activating a whole desk. It
+ * tracks pending transitions and updates repository state once they finish.
+ */
+class DesksTransitionObserver(private val desktopUserRepositories: DesktopUserRepositories) {
+ private val deskTransitions = mutableMapOf<IBinder, DeskTransition>()
+
+ /** Adds a pending desk transition to be tracked. */
+ fun addPendingTransition(transition: DeskTransition) {
+ if (!Flags.enableMultipleDesktopsBackend()) return
+ deskTransitions[transition.token] = transition
+ }
+
+ /**
+ * Called when any transition is ready, which may include transitions not tracked by this
+ * observer.
+ */
+ fun onTransitionReady(transition: IBinder, info: TransitionInfo) {
+ if (!Flags.enableMultipleDesktopsBackend()) return
+ val deskTransition = deskTransitions.remove(transition) ?: return
+ val desktopRepository = desktopUserRepositories.current
+ when (deskTransition) {
+ is DeskTransition.RemoveDesk -> {
+ check(info.type == TRANSIT_CLOSE) { "Expected close transition for desk removal" }
+ // TODO: b/362720497 - consider verifying the desk was actually removed through the
+ // DesksOrganizer. The transition info won't have changes if the desk was not
+ // visible, such as when dismissing from Overview.
+ val deskId = deskTransition.deskId
+ val displayId = deskTransition.displayId
+ desktopRepository.removeDesk(deskTransition.deskId)
+ deskTransition.onDeskRemovedListener?.onDeskRemoved(displayId, deskId)
+ }
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java
index 52b6c62..31715f0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java
@@ -175,7 +175,9 @@
@Override
public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
- @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
+ @NonNull SurfaceControl.Transaction startT,
+ @NonNull SurfaceControl.Transaction finishT,
+ @NonNull IBinder mergeTarget,
@NonNull Transitions.TransitionFinishCallback finishCallback) {
ArrayList<Animator> animations = mAnimations.get(mergeTarget);
if (animations == null) return;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java
index f8e6285..d666126 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java
@@ -277,7 +277,8 @@
@Override
public void mergeAnimation(@NonNull IBinder nextTransition, @NonNull TransitionInfo nextInfo,
- @NonNull SurfaceControl.Transaction nextT, @NonNull IBinder currentTransition,
+ @NonNull SurfaceControl.Transaction nextT, @NonNull SurfaceControl.Transaction finishT,
+ @NonNull IBinder currentTransition,
@NonNull TransitionFinishCallback nextFinishCallback) {
final StartedTransition playing = mStartedTransitions.get(currentTransition);
if (playing == null) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
index 2f3c152..f0e6ae4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
@@ -372,7 +372,9 @@
@Override
public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
- @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
+ @NonNull SurfaceControl.Transaction startT,
+ @NonNull SurfaceControl.Transaction finishT,
+ @NonNull IBinder mergeTarget,
@NonNull Transitions.TransitionFinishCallback finishCallback) {
end();
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTransition.java
index d3ae411..0fa6a11 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTransition.java
@@ -653,7 +653,9 @@
@Override
public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
- @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
+ @NonNull SurfaceControl.Transaction startT,
+ @NonNull SurfaceControl.Transaction finishT,
+ @NonNull IBinder mergeTarget,
@NonNull Transitions.TransitionFinishCallback finishCallback) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: merge animation", TAG);
if (mCurrentAnimator != null && mCurrentAnimator.isRunning()) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
index 2299624..78aa686 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
@@ -247,7 +247,9 @@
@Override
public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
- @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
+ @NonNull SurfaceControl.Transaction startT,
+ @NonNull SurfaceControl.Transaction finishT,
+ @NonNull IBinder mergeTarget,
@NonNull Transitions.TransitionFinishCallback finishCallback) {
// Just jump-cut the current animation if any, but do not merge.
if (info.getType() == TRANSIT_EXIT_PIP) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
index 5513378..8ad2e1d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
@@ -307,7 +307,9 @@
@Override
public void mergeAnimation(IBinder transition, TransitionInfo info,
- SurfaceControl.Transaction t, IBinder mergeTarget,
+ @NonNull SurfaceControl.Transaction startT,
+ @NonNull SurfaceControl.Transaction finishT,
+ IBinder mergeTarget,
Transitions.TransitionFinishCallback finishCallback) {
final RecentsController controller = findController(mergeTarget);
if (controller == null) {
@@ -315,7 +317,7 @@
"RecentsTransitionHandler.mergeAnimation: no controller found");
return;
}
- controller.merge(info, t, mergeTarget, finishCallback);
+ controller.merge(info, startT, mergeTarget, finishCallback);
}
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
index 3091be5..fed336b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
@@ -461,12 +461,14 @@
return transition;
}
- void mergeAnimation(IBinder transition, TransitionInfo info, SurfaceControl.Transaction t,
+ void mergeAnimation(IBinder transition, TransitionInfo info,
+ SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT,
IBinder mergeTarget, Transitions.TransitionFinishCallback finishCallback) {
if (mergeTarget != mAnimatingTransition) return;
if (mActiveRemoteHandler != null) {
- mActiveRemoteHandler.mergeAnimation(transition, info, t, mergeTarget, finishCallback);
+ mActiveRemoteHandler.mergeAnimation(transition, info, startT,
+ finishT, mergeTarget, finishCallback);
} else {
for (int i = mAnimations.size() - 1; i >= 0; --i) {
final Animator anim = mAnimations.get(i);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index 2174017..6783df8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -2977,10 +2977,13 @@
@Override
public void mergeAnimation(IBinder transition, TransitionInfo info,
- SurfaceControl.Transaction t, IBinder mergeTarget,
+ @NonNull SurfaceControl.Transaction startT,
+ @NonNull SurfaceControl.Transaction finishT,
+ IBinder mergeTarget,
Transitions.TransitionFinishCallback finishCallback) {
ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "mergeAnimation: transition=%d", info.getDebugId());
- mSplitTransitions.mergeAnimation(transition, info, t, mergeTarget, finishCallback);
+ mSplitTransitions.mergeAnimation(transition, info, startT, finishT, mergeTarget,
+ finishCallback);
}
/** Jump the current transition animation to the end. */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
index d8e7c2c..743bd05 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
@@ -176,7 +176,9 @@
abstract void mergeAnimation(
@NonNull IBinder transition, @NonNull TransitionInfo info,
- @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
+ @NonNull SurfaceControl.Transaction startT,
+ @NonNull SurfaceControl.Transaction finishT,
+ @NonNull IBinder mergeTarget,
@NonNull Transitions.TransitionFinishCallback finishCallback);
abstract void onTransitionConsumed(
@@ -691,7 +693,9 @@
@Override
public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
- @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
+ @NonNull SurfaceControl.Transaction startT,
+ @NonNull SurfaceControl.Transaction finishT,
+ @NonNull IBinder mergeTarget,
@NonNull Transitions.TransitionFinishCallback finishCallback) {
for (int i = 0; i < mActiveTransitions.size(); ++i) {
if (mActiveTransitions.get(i).mTransition != mergeTarget) continue;
@@ -701,7 +705,7 @@
// Already done, so no need to end it.
return;
}
- mixed.mergeAnimation(transition, info, t, mergeTarget, finishCallback);
+ mixed.mergeAnimation(transition, info, startT, finishT, mergeTarget, finishCallback);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedTransition.java
index 29a58d7..1853ffa 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedTransition.java
@@ -384,7 +384,8 @@
@Override
void mergeAnimation(
@NonNull IBinder transition, @NonNull TransitionInfo info,
- @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
+ @NonNull SurfaceControl.Transaction startT, @NonNull SurfaceControl.Transaction finishT,
+ @NonNull IBinder mergeTarget,
@NonNull Transitions.TransitionFinishCallback finishCallback) {
switch (mType) {
case TYPE_DISPLAY_AND_SPLIT_CHANGE:
@@ -394,7 +395,7 @@
case TYPE_ENTER_PIP_FROM_ACTIVITY_EMBEDDING:
mPipHandler.end();
mActivityEmbeddingController.mergeAnimation(
- transition, info, t, mergeTarget, finishCallback);
+ transition, info, startT, finishT, mergeTarget, finishCallback);
return;
case TYPE_ENTER_PIP_FROM_SPLIT:
if (mAnimType == ANIM_TYPE_GOING_HOME) {
@@ -405,26 +406,28 @@
mPipHandler.end();
if (mLeftoversHandler != null) {
mLeftoversHandler.mergeAnimation(
- transition, info, t, mergeTarget, finishCallback);
+ transition, info, startT, finishT, mergeTarget, finishCallback);
}
}
return;
case TYPE_KEYGUARD:
- mKeyguardHandler.mergeAnimation(transition, info, t, mergeTarget, finishCallback);
+ mKeyguardHandler.mergeAnimation(transition, info, startT, finishT, mergeTarget,
+ finishCallback);
return;
case TYPE_OPTIONS_REMOTE_AND_PIP_OR_DESKTOP_CHANGE:
mPipHandler.end();
if (mLeftoversHandler != null) {
mLeftoversHandler.mergeAnimation(
- transition, info, t, mergeTarget, finishCallback);
+ transition, info, startT, finishT, mergeTarget, finishCallback);
}
return;
case TYPE_UNFOLD:
- mUnfoldHandler.mergeAnimation(transition, info, t, mergeTarget, finishCallback);
+ mUnfoldHandler.mergeAnimation(transition, info, startT, finishT, mergeTarget,
+ finishCallback);
return;
case TYPE_OPEN_IN_DESKTOP:
mDesktopTasksController.mergeAnimation(
- transition, info, t, mergeTarget, finishCallback);
+ transition, info, startT, finishT, mergeTarget, finishCallback);
return;
default:
throw new IllegalStateException("Playing a default mixed transition with unknown or"
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
index ac6e4c5..28bba2e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
@@ -708,7 +708,9 @@
@Override
public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
- @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
+ @NonNull SurfaceControl.Transaction startT,
+ @NonNull SurfaceControl.Transaction finishT,
+ @NonNull IBinder mergeTarget,
@NonNull Transitions.TransitionFinishCallback finishCallback) {
ArrayList<Animator> anims = mAnimations.get(mergeTarget);
if (anims == null) return;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java
index 209fc39..ec73738 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java
@@ -96,7 +96,9 @@
@Override
public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
- @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
+ @NonNull SurfaceControl.Transaction startT,
+ @NonNull SurfaceControl.Transaction finishT,
+ @NonNull IBinder mergeTarget,
@NonNull Transitions.TransitionFinishCallback finishCallback) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Merging registered One-shot remote"
+ " transition %s for (#%d).", mRemote, info.getDebugId());
@@ -111,7 +113,7 @@
// process won't be cleared if the remote applied it. We don't actually know if the
// remote applied the transaction, but applying twice will break surfaceflinger
// so just assume the worst-case and clear the local transaction.
- t.clear();
+ startT.clear();
mMainExecutor.execute(() -> {
finishCallback.onTransitionFinished(wct);
});
@@ -121,8 +123,8 @@
// If the remote is actually in the same process, then make a copy of parameters since
// remote impls assume that they have to clean-up native references.
final SurfaceControl.Transaction remoteT =
- RemoteTransitionHandler.copyIfLocal(t, mRemote.getRemoteTransition());
- final TransitionInfo remoteInfo = remoteT == t ? info : info.localRemoteCopy();
+ RemoteTransitionHandler.copyIfLocal(startT, mRemote.getRemoteTransition());
+ final TransitionInfo remoteInfo = remoteT == startT ? info : info.localRemoteCopy();
mRemote.getRemoteTransition().mergeAnimation(
transition, remoteInfo, remoteT, mergeTarget, cb);
} catch (RemoteException e) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RecentsMixedTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RecentsMixedTransition.java
index 1847af0..f40dc8a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RecentsMixedTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RecentsMixedTransition.java
@@ -193,21 +193,24 @@
@Override
void mergeAnimation(
@NonNull IBinder transition, @NonNull TransitionInfo info,
- @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
+ @NonNull SurfaceControl.Transaction startT, @NonNull SurfaceControl.Transaction finishT,
+ @NonNull IBinder mergeTarget,
@NonNull Transitions.TransitionFinishCallback finishCallback) {
switch (mType) {
case TYPE_RECENTS_DURING_DESKTOP:
- mLeftoversHandler.mergeAnimation(transition, info, t, mergeTarget, finishCallback);
+ mLeftoversHandler.mergeAnimation(transition, info, startT, finishT, mergeTarget,
+ finishCallback);
return;
case TYPE_RECENTS_DURING_KEYGUARD:
if ((info.getFlags() & TRANSIT_FLAG_KEYGUARD_UNOCCLUDING) != 0) {
- handoverTransitionLeashes(mInfo, info, t, mFinishT);
+ handoverTransitionLeashes(mInfo, info, startT, finishT);
if (animateKeyguard(
- this, info, t, mFinishT, mFinishCB, mKeyguardHandler, mPipHandler)) {
+ this, info, startT, finishT, mFinishCB, mKeyguardHandler,
+ mPipHandler)) {
finishCallback.onTransitionFinished(null);
}
}
- mLeftoversHandler.mergeAnimation(transition, info, t, mergeTarget,
+ mLeftoversHandler.mergeAnimation(transition, info, startT, finishT, mergeTarget,
finishCallback);
return;
case TYPE_RECENTS_DURING_SPLIT:
@@ -216,7 +219,8 @@
// another pair.
mAnimType = DefaultMixedHandler.MixedTransition.ANIM_TYPE_PAIR_TO_PAIR;
}
- mLeftoversHandler.mergeAnimation(transition, info, t, mergeTarget, finishCallback);
+ mLeftoversHandler.mergeAnimation(transition, info, startT, finishT, mergeTarget,
+ finishCallback);
return;
default:
throw new IllegalStateException("Playing a Recents mixed transition with unknown or"
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java
index dec28fe..c4a410b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java
@@ -211,7 +211,9 @@
@Override
public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
- @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
+ @NonNull SurfaceControl.Transaction startT,
+ @NonNull SurfaceControl.Transaction finishT,
+ @NonNull IBinder mergeTarget,
@NonNull Transitions.TransitionFinishCallback finishCallback) {
final RemoteTransition remoteTransition = mRequestedRemotes.get(mergeTarget);
if (remoteTransition == null) return;
@@ -230,7 +232,7 @@
// process won't be cleared if the remote applied it. We don't actually know if the
// remote applied the transaction, but applying twice will break surfaceflinger
// so just assume the worst-case and clear the local transaction.
- t.clear();
+ startT.clear();
mMainExecutor.execute(() -> {
if (!mRequestedRemotes.containsKey(mergeTarget)) {
Log.e(TAG, "Merged transition finished after it's mergeTarget (the "
@@ -245,8 +247,8 @@
try {
// If the remote is actually in the same process, then make a copy of parameters since
// remote impls assume that they have to clean-up native references.
- final SurfaceControl.Transaction remoteT = copyIfLocal(t, remote);
- final TransitionInfo remoteInfo = remoteT == t ? info : info.localRemoteCopy();
+ final SurfaceControl.Transaction remoteT = copyIfLocal(startT, remote);
+ final TransitionInfo remoteInfo = remoteT == startT ? info : info.localRemoteCopy();
remote.mergeAnimation(transition, remoteInfo, remoteT, mergeTarget, cb);
} catch (RemoteException e) {
Log.e(Transitions.TAG, "Error attempting to merge remote transition.", e);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
index b83b7e2..72cbc47 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
@@ -922,7 +922,7 @@
+ " %s is still animating. Notify the animating transition"
+ " in case they can be merged", ready, playing);
mTransitionTracer.logMergeRequested(ready.mInfo.getDebugId(), playing.mInfo.getDebugId());
- playing.mHandler.mergeAnimation(ready.mToken, ready.mInfo, ready.mStartT,
+ playing.mHandler.mergeAnimation(ready.mToken, ready.mInfo, ready.mStartT, ready.mFinishT,
playing.mToken, (wct) -> onMerged(playingToken, readyToken));
}
@@ -1356,7 +1356,7 @@
// fast-forward.
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Attempt to merge sync %s"
+ " into %s via a SLEEP proxy", nextSync, playing);
- playing.mHandler.mergeAnimation(nextSync.mToken, dummyInfo, dummyT,
+ playing.mHandler.mergeAnimation(nextSync.mToken, dummyInfo, dummyT, dummyT,
playing.mToken, (wct) -> {});
// it's possible to complete immediately. If that happens, just repeat the signal
// loop until we either finish everything or start playing an animation that isn't
@@ -1404,7 +1404,9 @@
* @param finishTransaction the transaction given to the handler to be applied after the
* transition animation. Unlike startTransaction, the handler is NOT
* expected to apply this transaction. The Transition system will
- * apply it when finishCallback is called.
+ * apply it when finishCallback is called. If additional transitions
+ * are merged, then the finish transactions for those transitions
+ * will be applied after this transaction.
* @param finishCallback Call this when finished. This MUST be called on main thread.
* @return true if transition was handled, false if not (falls-back to default).
*/
@@ -1414,6 +1416,17 @@
@NonNull TransitionFinishCallback finishCallback);
/**
+ * See {@link #mergeAnimation(IBinder, TransitionInfo, SurfaceControl.Transaction, SurfaceControl.Transaction, IBinder, TransitionFinishCallback)}
+ *
+ * This deprecated method header is provided until downstream implementation can migrate to
+ * the call that takes both start & finish transactions.
+ */
+ @Deprecated
+ default void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull IBinder mergeTarget, @NonNull TransitionFinishCallback finishCallback) { }
+
+ /**
* Attempts to merge a different transition's animation into an animation that this handler
* is currently playing. If a merge is not possible/supported, this should be a no-op.
*
@@ -1430,14 +1443,25 @@
*
* @param transition This is the transition that wants to be merged.
* @param info Information about what is changing in the transition.
- * @param t Contains surface changes that resulted from the transition.
+ * @param startTransaction The start transaction containing surface changes that resulted
+ * from the incoming transition. This should be applied by this
+ * active handler only if it chooses to merge the transition.
+ * @param finishTransaction The finish transaction for the incoming transition. Unlike
+ * startTransaction, the handler is NOT expected to apply this
+ * transaction. If the transition is merged, the Transition system
+ * will apply after finishCallback is called following the finish
+ * transaction provided in `#startAnimation()`.
* @param mergeTarget This is the transition that we are attempting to merge with (ie. the
* one this handler is currently already animating).
* @param finishCallback Call this if merged. This MUST be called on main thread.
*/
default void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
- @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
- @NonNull TransitionFinishCallback finishCallback) { }
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
+ @NonNull IBinder mergeTarget, @NonNull TransitionFinishCallback finishCallback) {
+ // Call the legacy implementation by default
+ mergeAnimation(transition, info, startTransaction, mergeTarget, finishCallback);
+ }
/**
* Checks whether this handler is capable of taking over a transition matching `info`.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java
index 3e0e15a..7fd19a7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java
@@ -225,7 +225,9 @@
@Override
public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
- @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
+ @NonNull SurfaceControl.Transaction startT,
+ @NonNull SurfaceControl.Transaction finishT,
+ @NonNull IBinder mergeTarget,
@NonNull TransitionFinishCallback finishCallback) {
if (info.getType() != TRANSIT_CHANGE) {
return;
@@ -246,7 +248,7 @@
}
}
// Apply changes happening during the unfold animation immediately
- t.apply();
+ startT.apply();
finishCallback.onTransitionFinished(null);
if (getDefaultDisplayChange(info) == DefaultDisplayChange.DEFAULT_DISPLAY_FOLD) {
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 195e819..6a2a7b6 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
@@ -50,7 +50,6 @@
import android.app.ActivityTaskManager;
import android.app.IActivityManager;
import android.app.IActivityTaskManager;
-import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.graphics.Point;
@@ -1654,9 +1653,6 @@
if (mDesktopModeCompatPolicy.isTopActivityExemptFromDesktopWindowing(taskInfo)) {
return false;
}
- if (isPartOfDefaultHomePackage(taskInfo)) {
- return false;
- }
final boolean isOnLargeScreen = taskInfo.getConfiguration().smallestScreenWidthDp
>= WindowManager.LARGE_SCREEN_SMALLEST_SCREEN_WIDTH_DP;
if (!DesktopModeStatus.canEnterDesktopMode(mContext)
@@ -1672,14 +1668,6 @@
&& !taskInfo.configuration.windowConfiguration.isAlwaysOnTop();
}
- private boolean isPartOfDefaultHomePackage(RunningTaskInfo taskInfo) {
- final ComponentName currentDefaultHome =
- mContext.getPackageManager().getHomeActivities(new ArrayList<>());
- return currentDefaultHome != null && taskInfo.baseActivity != null
- && currentDefaultHome.getPackageName()
- .equals(taskInfo.baseActivity.getPackageName());
- }
-
private void createWindowDecoration(
ActivityManager.RunningTaskInfo taskInfo,
SurfaceControl taskSurface,
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 b6765c4..c1a6240 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
@@ -1364,7 +1364,7 @@
updateGenericLink();
final boolean supportsMultiInstance = mMultiInstanceHelper
.supportsMultiInstanceSplit(mTaskInfo.baseActivity, mTaskInfo.userId)
- && Flags.enableDesktopWindowingMultiInstanceFeatures();
+ && DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_MULTI_INSTANCE_FEATURES.isTrue();
final boolean shouldShowManageWindowsButton = supportsMultiInstance
&& mMinimumInstancesFound;
final boolean shouldShowChangeAspectRatioButton = HandleMenu.Companion
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/WindowDecorTaskResourceLoader.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/WindowDecorTaskResourceLoader.kt
index 1bc48f8..801048a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/WindowDecorTaskResourceLoader.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/WindowDecorTaskResourceLoader.kt
@@ -153,9 +153,7 @@
private fun loadAppResources(taskInfo: RunningTaskInfo): AppResources {
Trace.beginSection("$TAG#loadAppResources")
try {
- val pm = checkNotNull(userProfilesContexts[taskInfo.userId]?.packageManager) {
- "Could not get context for user ${taskInfo.userId}"
- }
+ val pm = userProfilesContexts.getOrCreate(taskInfo.userId).packageManager
val activityInfo = getActivityInfo(taskInfo, pm)
val appName = pm.getApplicationLabel(activityInfo.applicationInfo)
val appIconDrawable = iconProvider.getIcon(activityInfo)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java
index 56948d4..9f29ef7 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java
@@ -271,7 +271,9 @@
mController.startAnimation(mTransition, info, mStartTransaction,
mFinishTransaction, mFinishCallback);
verify(mFinishCallback, never()).onTransitionFinished(any());
- mController.mergeAnimation(mTransition, info, new SurfaceControl.Transaction(),
+ mController.mergeAnimation(mTransition, info,
+ new SurfaceControl.Transaction(),
+ new SurfaceControl.Transaction(),
mTransition, (wct) -> {});
verify(mFinishCallback).onTransitionFinished(any());
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
index e63db9a..05750a5 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
@@ -666,7 +666,7 @@
Transitions.TransitionFinishCallback mergeCallback =
mock(Transitions.TransitionFinishCallback.class);
mBackTransitionHandler.mergeAnimation(
- mock(IBinder.class), tInfo2, st, mock(IBinder.class), mergeCallback);
+ mock(IBinder.class), tInfo2, st, ft, mock(IBinder.class), mergeCallback);
mBackTransitionHandler.onAnimationFinished();
verify(callback).onTransitionFinished(any());
verify(mergeCallback).onTransitionFinished(any());
@@ -701,7 +701,7 @@
mBackTransitionHandler.mClosePrepareTransition = mock(IBinder.class);
mergeCallback = mock(Transitions.TransitionFinishCallback.class);
mBackTransitionHandler.mergeAnimation(mBackTransitionHandler.mClosePrepareTransition,
- tInfo2, st, mock(IBinder.class), mergeCallback);
+ tInfo2, st, ft, mock(IBinder.class), mergeCallback);
assertTrue("Change should be consumed", tInfo2.getChanges().isEmpty());
verify(callback).onTransitionFinished(any());
}
@@ -747,7 +747,7 @@
final TransitionInfo closeInfo = createTransitionInfo(TRANSIT_CLOSE, close);
Transitions.TransitionFinishCallback mergeCallback =
mock(Transitions.TransitionFinishCallback.class);
- mBackTransitionHandler.mergeAnimation(mock(IBinder.class), closeInfo, ft,
+ mBackTransitionHandler.mergeAnimation(mock(IBinder.class), closeInfo, st, ft,
mock(IBinder.class), mergeCallback);
verify(callback2).onTransitionFinished(any());
verify(mergeCallback, never()).onTransitionFinished(any());
@@ -766,7 +766,7 @@
openTaskId2, TRANSIT_OPEN, FLAG_MOVED_TO_TOP);
final TransitionInfo openInfo = createTransitionInfo(TRANSIT_OPEN, open2, close);
mergeCallback = mock(Transitions.TransitionFinishCallback.class);
- mBackTransitionHandler.mergeAnimation(mock(IBinder.class), openInfo, ft,
+ mBackTransitionHandler.mergeAnimation(mock(IBinder.class), openInfo, st, ft,
mock(IBinder.class), mergeCallback);
verify(callback3).onTransitionFinished(any());
verify(mergeCallback, never()).onTransitionFinished(any());
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/UserProfileContextsTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/UserProfileContextsTest.kt
index ef0b8ab..56d4017 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/UserProfileContextsTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/UserProfileContextsTest.kt
@@ -69,6 +69,7 @@
}
.whenever(baseContext)
.createContextAsUser(any<UserHandle>(), anyInt())
+ doReturn(DEFAULT_USER).whenever(baseContext).userId
// Define users and profiles
val currentUser = ActivityManager.getCurrentUser()
whenever(userManager.getProfiles(eq(currentUser)))
@@ -136,6 +137,25 @@
assertThat(userProfilesContexts[SECOND_PROFILE]?.userId).isEqualTo(SECOND_PROFILE)
}
+ @Test
+ fun onUserProfilesChanged_keepDefaultUser() {
+ val userChangeListener = retrieveUserChangeListener()
+ val newUserContext = createContextForUser(SECOND_USER)
+
+ userChangeListener.onUserChanged(SECOND_USER, newUserContext)
+ userChangeListener.onUserProfilesChanged(SECOND_PROFILES)
+
+ assertThat(userProfilesContexts[DEFAULT_USER]).isEqualTo(baseContext)
+ }
+
+ @Test
+ fun getOrCreate_newUser_shouldCreateTheUser() {
+ val newContext = userProfilesContexts.getOrCreate(SECOND_USER)
+
+ assertThat(newContext).isNotNull()
+ assertThat(userProfilesContexts[SECOND_USER]).isEqualTo(newContext)
+ }
+
private fun retrieveUserChangeListener(): UserChangeListener {
val captor = argumentCaptor<UserChangeListener>()
@@ -155,6 +175,7 @@
const val MAIN_PROFILE = 11
const val SECOND_PROFILE = 15
const val SECOND_PROFILE_2 = 17
+ const val DEFAULT_USER = 25
val SECOND_PROFILES =
listOf(
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt
index 90f342f..6a343c5 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt
@@ -27,6 +27,7 @@
import com.android.window.flags.Flags
import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_PERSISTENCE
import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_PIP
+import com.android.window.flags.Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND
import com.android.wm.shell.ShellTestCase
import com.android.wm.shell.TestShellExecutor
import com.android.wm.shell.common.ShellExecutor
@@ -35,6 +36,7 @@
import com.android.wm.shell.sysui.ShellInit
import com.google.common.truth.Truth.assertThat
import junit.framework.Assert.fail
+import kotlin.test.assertEquals
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -1076,13 +1078,37 @@
repo.addTask(displayId = DEFAULT_DISPLAY, taskId = 1, isVisible = true)
repo.minimizeTask(displayId = DEFAULT_DISPLAY, taskId = 2)
- val tasksBeforeRemoval = repo.removeDesk(displayId = DEFAULT_DISPLAY)
+ val tasksBeforeRemoval = repo.removeDesk(deskId = DEFAULT_DISPLAY)
assertThat(tasksBeforeRemoval).containsExactly(1, 2, 3).inOrder()
assertThat(repo.getActiveTasks(displayId = DEFAULT_DISPLAY)).isEmpty()
}
@Test
+ @EnableFlags(FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun removeDesk_multipleDesks_active_removes() {
+ repo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 2)
+ repo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 3)
+ repo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 3)
+
+ repo.removeDesk(deskId = 3)
+
+ assertThat(repo.getDeskIds(displayId = DEFAULT_DISPLAY)).doesNotContain(3)
+ }
+
+ @Test
+ @EnableFlags(FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun removeDesk_multipleDesks_inactive_removes() {
+ repo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 2)
+ repo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 3)
+ repo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 3)
+
+ repo.removeDesk(deskId = 2)
+
+ assertThat(repo.getDeskIds(displayId = DEFAULT_DISPLAY)).doesNotContain(2)
+ }
+
+ @Test
fun getTaskInFullImmersiveState_byDisplay() {
repo.addDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY)
repo.setActiveDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY)
@@ -1164,6 +1190,26 @@
assertThat(repo.getActiveTaskIdsInDesk(999)).contains(6)
}
+ @Test
+ @DisableFlags(FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun getDisplayForDesk() {
+ repo.addDesk(SECOND_DISPLAY, SECOND_DISPLAY)
+
+ assertEquals(SECOND_DISPLAY, repo.getDisplayForDesk(deskId = SECOND_DISPLAY))
+ }
+
+ @Test
+ @EnableFlags(FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun getDisplayForDesk_multipleDesks() {
+ repo.addDesk(DEFAULT_DISPLAY, deskId = 6)
+ repo.addDesk(DEFAULT_DISPLAY, deskId = 7)
+ repo.addDesk(SECOND_DISPLAY, deskId = 8)
+ repo.addDesk(SECOND_DISPLAY, deskId = 9)
+
+ assertEquals(DEFAULT_DISPLAY, repo.getDisplayForDesk(deskId = 7))
+ assertEquals(SECOND_DISPLAY, repo.getDisplayForDesk(deskId = 8))
+ }
+
class TestListener : DesktopRepository.ActiveTasksListener {
var activeChangesOnDefaultDisplay = 0
var activeChangesOnSecondaryDisplay = 0
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
index e7fe57d..8e7545c 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
@@ -35,6 +35,7 @@
import android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
import android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
import android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
+import android.content.pm.PackageManager
import android.content.res.Configuration.ORIENTATION_LANDSCAPE
import android.content.res.Configuration.ORIENTATION_PORTRAIT
import android.content.res.Resources
@@ -117,7 +118,9 @@
import com.android.wm.shell.desktopmode.common.ToggleTaskSizeInteraction
import com.android.wm.shell.desktopmode.desktopwallpaperactivity.DesktopWallpaperActivityTokenProvider
import com.android.wm.shell.desktopmode.minimize.DesktopWindowLimitRemoteHandler
+import com.android.wm.shell.desktopmode.multidesks.DeskTransition
import com.android.wm.shell.desktopmode.multidesks.DesksOrganizer
+import com.android.wm.shell.desktopmode.multidesks.DesksTransitionObserver
import com.android.wm.shell.desktopmode.persistence.Desktop
import com.android.wm.shell.desktopmode.persistence.DesktopPersistentRepository
import com.android.wm.shell.desktopmode.persistence.DesktopRepositoryInitializer
@@ -179,6 +182,7 @@
import org.mockito.Mockito.verify
import org.mockito.kotlin.any
import org.mockito.kotlin.anyOrNull
+import org.mockito.kotlin.argThat
import org.mockito.kotlin.argumentCaptor
import org.mockito.kotlin.atLeastOnce
import org.mockito.kotlin.capture
@@ -250,6 +254,7 @@
private lateinit var overviewToDesktopTransitionObserver: OverviewToDesktopTransitionObserver
@Mock private lateinit var desksOrganizer: DesksOrganizer
@Mock private lateinit var userProfileContexts: UserProfileContexts
+ @Mock private lateinit var desksTransitionsObserver: DesksTransitionObserver
private lateinit var controller: DesktopTasksController
private lateinit var shellInit: ShellInit
@@ -347,6 +352,7 @@
.thenReturn(ExitResult.NoExit)
whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(wallpaperToken)
whenever(userProfileContexts[anyInt()]).thenReturn(context)
+ whenever(userProfileContexts.getOrCreate(anyInt())).thenReturn(context)
controller = createController()
controller.setSplitScreenController(splitScreenController)
@@ -404,6 +410,7 @@
Optional.of(bubbleController),
overviewToDesktopTransitionObserver,
desksOrganizer,
+ desksTransitionsObserver,
userProfileContexts,
desktopModeCompatPolicy,
)
@@ -1454,6 +1461,41 @@
.isEqualTo(WINDOWING_MODE_FREEFORM)
}
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
+ fun moveRunningTaskToDesktop_defaultHomePackageWithDisplay_doesNothing() {
+ val packageManager: PackageManager = org.mockito.kotlin.mock()
+ val homeActivities = ComponentName("defaultHomePackage", /* class */ "")
+ val task =
+ setUpFullscreenTask().apply {
+ baseActivity = homeActivities
+ isTopActivityNoDisplay = false
+ }
+ mContext.setMockPackageManager(packageManager)
+ whenever(packageManager.getHomeActivities(any())).thenReturn(homeActivities)
+
+ controller.moveRunningTaskToDesktop(task, transitionSource = UNKNOWN)
+ verifyEnterDesktopWCTNotExecuted()
+ }
+
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
+ fun moveRunningTaskToDesktop_defaultHomePackageWithoutDisplay_doesNothing() {
+ val packageManager: PackageManager = org.mockito.kotlin.mock()
+ val homeActivities = ComponentName("defaultHomePackage", /* class */ "")
+ val task =
+ setUpFullscreenTask().apply {
+ baseActivity = homeActivities
+ isTopActivityNoDisplay = false
+ }
+ mContext.setMockPackageManager(packageManager)
+ whenever(packageManager.getHomeActivities(any())).thenReturn(homeActivities)
+
+ controller.moveRunningTaskToDesktop(task, transitionSource = UNKNOWN)
+
+ val wct = getLatestEnterDesktopWct()
+ assertThat(wct.changes[task.token.asBinder()]?.windowingMode)
+ .isEqualTo(WINDOWING_MODE_FREEFORM)
+ }
+
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
fun moveBackgroundTaskToDesktop_remoteTransition_usesOneShotHandler() {
@@ -3091,6 +3133,46 @@
.isEqualTo(WINDOWING_MODE_FREEFORM)
}
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
+ fun handleRequest_defaultHomePackageWithDisplay_returnSwitchToFullscreenWCT() {
+ val freeformTask = setUpFreeformTask()
+ markTaskVisible(freeformTask)
+
+ val packageManager: PackageManager = org.mockito.kotlin.mock()
+ val homeActivities = ComponentName("defaultHomePackage", /* class */ "")
+ val task =
+ setUpFullscreenTask().apply {
+ baseActivity = homeActivities
+ isTopActivityNoDisplay = false
+ }
+ mContext.setMockPackageManager(packageManager)
+ whenever(packageManager.getHomeActivities(any())).thenReturn(homeActivities)
+
+ val result = controller.handleRequest(Binder(), createTransition(task))
+ assertThat(result?.changes?.get(task.token.asBinder())?.windowingMode)
+ .isEqualTo(WINDOWING_MODE_FREEFORM)
+ }
+
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
+ fun handleRequest_defaultHomePackageWithoutDisplay_returnSwitchToFreeformWCT() {
+ val freeformTask = setUpFreeformTask()
+ markTaskVisible(freeformTask)
+
+ val packageManager: PackageManager = org.mockito.kotlin.mock()
+ val homeActivities = ComponentName("defaultHomePackage", /* class */ "")
+ val task =
+ setUpFullscreenTask().apply {
+ baseActivity = homeActivities
+ isTopActivityNoDisplay = false
+ }
+ mContext.setMockPackageManager(packageManager)
+ whenever(packageManager.getHomeActivities(any())).thenReturn(homeActivities)
+
+ val result = controller.handleRequest(Binder(), createTransition(task))
+ assertThat(result?.changes?.get(task.token.asBinder())?.windowingMode)
+ .isEqualTo(WINDOWING_MODE_FREEFORM)
+ }
+
@Test
fun handleRequest_systemUIActivityWithDisplay_returnSwitchToFullscreenWCT_enforcedDesktop() {
whenever(DesktopModeStatus.enterDesktopByDefaultOnFreeformDisplay(context)).thenReturn(true)
@@ -3542,13 +3624,14 @@
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
- fun removeDesktop_multipleTasks_removesAll() {
+ @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun removeDesk_multipleTasks_removesAll() {
val task1 = setUpFreeformTask()
val task2 = setUpFreeformTask()
val task3 = setUpFreeformTask()
taskRepository.minimizeTask(DEFAULT_DISPLAY, task2.taskId)
- controller.removeDesktop(displayId = DEFAULT_DISPLAY)
+ controller.removeDefaultDeskInDisplay(displayId = DEFAULT_DISPLAY)
val wct = getLatestWct(TRANSIT_CLOSE)
assertThat(wct.hierarchyOps).hasSize(3)
@@ -3559,14 +3642,15 @@
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
- fun removeDesktop_multipleTasksWithBackgroundTask_removesAll() {
+ @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun removeDesk_multipleTasksWithBackgroundTask_removesAll() {
val task1 = setUpFreeformTask()
val task2 = setUpFreeformTask()
val task3 = setUpFreeformTask()
taskRepository.minimizeTask(DEFAULT_DISPLAY, task2.taskId)
whenever(shellTaskOrganizer.getRunningTaskInfo(task3.taskId)).thenReturn(null)
- controller.removeDesktop(displayId = DEFAULT_DISPLAY)
+ controller.removeDefaultDeskInDisplay(displayId = DEFAULT_DISPLAY)
val wct = getLatestWct(TRANSIT_CLOSE)
assertThat(wct.hierarchyOps).hasSize(2)
@@ -3576,6 +3660,30 @@
}
@Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION,
+ Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+ )
+ fun removeDesk_multipleDesks_addsPendingTransition() {
+ val transition = Binder()
+ whenever(transitions.startTransition(eq(TRANSIT_CLOSE), any(), anyOrNull()))
+ .thenReturn(transition)
+ taskRepository.addDesk(DEFAULT_DISPLAY, deskId = 2)
+
+ controller.removeDesk(deskId = 2)
+
+ verify(desksOrganizer).removeDesk(any(), eq(2))
+ verify(desksTransitionsObserver)
+ .addPendingTransition(
+ argThat {
+ this is DeskTransition.RemoveDesk &&
+ this.token == transition &&
+ this.deskId == 2
+ }
+ )
+ }
+
+ @Test
@EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
fun dragToDesktop_landscapeDevice_resizable_undefinedOrientation_defaultLandscapeBounds() {
val spyController = spy(controller)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt
index 091159c..c29edec 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt
@@ -47,11 +47,13 @@
import com.android.wm.shell.common.ShellExecutor
import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_EXIT_DESKTOP_MODE_TASK_DRAG
import com.android.wm.shell.desktopmode.desktopwallpaperactivity.DesktopWallpaperActivityTokenProvider
+import com.android.wm.shell.desktopmode.multidesks.DesksTransitionObserver
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
import com.android.wm.shell.sysui.ShellInit
import com.android.wm.shell.transition.Transitions
import com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP
import com.android.wm.shell.transition.Transitions.TRANSIT_REMOVE_PIP
+import com.android.wm.shell.util.StubTransaction
import com.google.common.truth.Truth.assertThat
import com.google.common.truth.Truth.assertWithMessage
import org.junit.Before
@@ -92,6 +94,7 @@
private val backAnimationController = mock<BackAnimationController>()
private val desktopWallpaperActivityTokenProvider =
mock<DesktopWallpaperActivityTokenProvider>()
+ private val desksTransitionObserver = mock<DesksTransitionObserver>()
private val wallpaperToken = MockToken().token()
private lateinit var transitionObserver: DesktopTasksTransitionObserver
@@ -115,6 +118,7 @@
mixedHandler,
backAnimationController,
desktopWallpaperActivityTokenProvider,
+ desksTransitionObserver,
shellInit,
)
}
@@ -411,6 +415,21 @@
verify(taskRepository).setTaskInPip(task.displayId, task.taskId, enterPip = false)
}
+ @Test
+ fun onTransitionReady_forwardsToDesksTransitionObserver() {
+ val transition = Binder()
+ val info = TransitionInfo(TRANSIT_CLOSE, /* flags= */ 0)
+
+ transitionObserver.onTransitionReady(
+ transition = transition,
+ info = info,
+ StubTransaction(),
+ StubTransaction(),
+ )
+
+ verify(desksTransitionObserver).onTransitionReady(transition, info)
+ }
+
private fun createBackNavigationTransition(
task: RunningTaskInfo?,
type: Int = TRANSIT_TO_BACK,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt
index bf9cf00..33dc1aa 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt
@@ -284,6 +284,7 @@
cancelToken,
TransitionInfo(TRANSIT_DESKTOP_MODE_CANCEL_DRAG_TO_DESKTOP, 0),
mock<SurfaceControl.Transaction>(),
+ mock<SurfaceControl.Transaction>(),
startToken,
mock<Transitions.TransitionFinishCallback>(),
)
@@ -385,21 +386,23 @@
@Test
fun mergeAnimation_otherTransition_doesNotMerge() {
- val transaction = mock<SurfaceControl.Transaction>()
+ val mergedStartTransaction = mock<SurfaceControl.Transaction>()
+ val mergedFinishTransaction = mock<SurfaceControl.Transaction>()
val finishCallback = mock<Transitions.TransitionFinishCallback>()
val task = createTask()
startDrag(defaultHandler, task)
defaultHandler.mergeAnimation(
- transition = mock(),
+ transition = mock<IBinder>(),
info = createTransitionInfo(type = TRANSIT_OPEN, draggedTask = task),
- t = transaction,
- mergeTarget = mock(),
+ startT = mergedStartTransaction,
+ finishT = mergedFinishTransaction,
+ mergeTarget = mock<IBinder>(),
finishCallback = finishCallback,
)
// Should NOT have any transaction changes
- verifyZeroInteractions(transaction)
+ verifyZeroInteractions(mergedStartTransaction)
// Should NOT merge animation
verify(finishCallback, never()).onTransitionFinished(any())
}
@@ -408,6 +411,7 @@
fun mergeAnimation_endTransition_mergesAnimation() {
val playingFinishTransaction = mock<SurfaceControl.Transaction>()
val mergedStartTransaction = mock<SurfaceControl.Transaction>()
+ val mergedFinishTransaction = mock<SurfaceControl.Transaction>()
val finishCallback = mock<Transitions.TransitionFinishCallback>()
val task = createTask()
val startTransition =
@@ -415,13 +419,14 @@
defaultHandler.onTaskResizeAnimationListener = mock()
defaultHandler.mergeAnimation(
- transition = mock(),
+ transition = mock<IBinder>(),
info =
createTransitionInfo(
type = TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP,
draggedTask = task,
),
- t = mergedStartTransaction,
+ startT = mergedStartTransaction,
+ finishT = mergedFinishTransaction,
mergeTarget = startTransition,
finishCallback = finishCallback,
)
@@ -440,6 +445,7 @@
whenever(dragAnimator.computeCurrentVelocity()).thenReturn(PointF())
val playingFinishTransaction = mock<SurfaceControl.Transaction>()
val mergedStartTransaction = mock<SurfaceControl.Transaction>()
+ val mergedFinishTransaction = mock<SurfaceControl.Transaction>()
val finishCallback = mock<Transitions.TransitionFinishCallback>()
val task = createTask()
val startTransition =
@@ -447,13 +453,14 @@
springHandler.onTaskResizeAnimationListener = mock()
springHandler.mergeAnimation(
- transition = mock(),
+ transition = mock<IBinder>(),
info =
createTransitionInfo(
type = TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP,
draggedTask = task,
),
- t = mergedStartTransaction,
+ startT = mergedStartTransaction,
+ finishT = mergedFinishTransaction,
mergeTarget = startTransition,
finishCallback = finishCallback,
)
@@ -564,7 +571,8 @@
type = TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP,
draggedTask = task,
),
- t = mock<SurfaceControl.Transaction>(),
+ startT = mock<SurfaceControl.Transaction>(),
+ finishT = mock<SurfaceControl.Transaction>(),
mergeTarget = startTransition,
finishCallback = mock<Transitions.TransitionFinishCallback>(),
)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/multidesks/DesksTransitionObserverTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/multidesks/DesksTransitionObserverTest.kt
new file mode 100644
index 0000000..bfbaa84
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/multidesks/DesksTransitionObserverTest.kt
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.wm.shell.desktopmode.multidesks
+
+import android.os.Binder
+import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.SetFlagsRule
+import android.testing.AndroidTestingRunner
+import android.view.Display.DEFAULT_DISPLAY
+import android.view.WindowManager.TRANSIT_CLOSE
+import android.window.TransitionInfo
+import androidx.test.filters.SmallTest
+import com.android.window.flags.Flags
+import com.android.wm.shell.ShellTestCase
+import com.android.wm.shell.TestShellExecutor
+import com.android.wm.shell.desktopmode.DesktopRepository
+import com.android.wm.shell.desktopmode.DesktopUserRepositories
+import com.android.wm.shell.sysui.ShellInit
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.mock
+
+/**
+ * Tests for [DesksTransitionObserver].
+ *
+ * Build/Install/Run: atest WMShellUnitTests:DesksTransitionObserverTest
+ */
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class DesksTransitionObserverTest : ShellTestCase() {
+
+ @JvmField @Rule val setFlagsRule = SetFlagsRule()
+
+ private lateinit var desktopUserRepositories: DesktopUserRepositories
+ private lateinit var observer: DesksTransitionObserver
+
+ private val repository: DesktopRepository
+ get() = desktopUserRepositories.current
+
+ @Before
+ fun setUp() {
+ desktopUserRepositories =
+ DesktopUserRepositories(
+ context,
+ ShellInit(TestShellExecutor()),
+ /* shellController= */ mock(),
+ /* persistentRepository= */ mock(),
+ /* repositoryInitializer= */ mock(),
+ /* mainCoroutineScope= */ mock(),
+ /* userManager= */ mock(),
+ )
+ observer = DesksTransitionObserver(desktopUserRepositories)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun onTransitionReady_removeDesk_removesFromRepository() {
+ val transition = Binder()
+ val removeTransition =
+ DeskTransition.RemoveDesk(
+ transition,
+ displayId = DEFAULT_DISPLAY,
+ deskId = 5,
+ tasks = setOf(10, 11),
+ onDeskRemovedListener = null,
+ )
+ repository.addDesk(DEFAULT_DISPLAY, deskId = 5)
+
+ observer.addPendingTransition(removeTransition)
+ observer.onTransitionReady(
+ transition = transition,
+ info = TransitionInfo(TRANSIT_CLOSE, /* flags= */ 0),
+ )
+
+ assertThat(repository.getDeskIds(DEFAULT_DISPLAY)).doesNotContain(5)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun onTransitionReady_removeDesk_invokesOnRemoveListener() {
+ class FakeOnDeskRemovedListener : OnDeskRemovedListener {
+ var lastDeskRemoved: Int? = null
+
+ override fun onDeskRemoved(lastDisplayId: Int, deskId: Int) {
+ lastDeskRemoved = deskId
+ }
+ }
+ val transition = Binder()
+ val removeListener = FakeOnDeskRemovedListener()
+ val removeTransition =
+ DeskTransition.RemoveDesk(
+ transition,
+ displayId = DEFAULT_DISPLAY,
+ deskId = 5,
+ tasks = setOf(10, 11),
+ onDeskRemovedListener = removeListener,
+ )
+ repository.addDesk(DEFAULT_DISPLAY, deskId = 5)
+
+ observer.addPendingTransition(removeTransition)
+ observer.onTransitionReady(
+ transition = transition,
+ info = TransitionInfo(TRANSIT_CLOSE, /* flags= */ 0),
+ )
+
+ assertThat(removeListener.lastDeskRemoved).isEqualTo(5)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeCompatPolicyTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeCompatPolicyTest.kt
index 8c78deb..a8a7be8 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeCompatPolicyTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeCompatPolicyTest.kt
@@ -17,6 +17,7 @@
package com.android.wm.shell.shared.desktopmode
import android.content.ComponentName
+import android.content.pm.PackageManager
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.internal.R
@@ -27,6 +28,9 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.kotlin.any
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
/**
* Tests for [@link DesktopModeCompatPolicy].
@@ -110,4 +114,32 @@
isTopActivityNoDisplay = true
}))
}
+
+ @Test
+ fun testIsTopActivityExemptFromDesktopWindowing_defaultHomePackage() {
+ val packageManager: PackageManager = mock()
+ val homeActivities = ComponentName("defaultHomePackage", /* class */ "")
+ whenever(packageManager.getHomeActivities(any())).thenReturn(homeActivities)
+ mContext.setMockPackageManager(packageManager)
+ assertTrue(desktopModeCompatPolicy.isTopActivityExemptFromDesktopWindowing(
+ createFreeformTask(/* displayId */ 0)
+ .apply {
+ baseActivity = homeActivities
+ isTopActivityNoDisplay = false
+ }))
+ }
+
+ @Test
+ fun testIsTopActivityExemptFromDesktopWindowing_defaultHomePackage_notDisplayed() {
+ val packageManager: PackageManager = mock()
+ val homeActivities = ComponentName("defaultHomePackage", /* class */ "")
+ whenever(packageManager.getHomeActivities(any())).thenReturn(homeActivities)
+ mContext.setMockPackageManager(packageManager)
+ assertFalse(desktopModeCompatPolicy.isTopActivityExemptFromDesktopWindowing(
+ createFreeformTask(/* displayId */ 0)
+ .apply {
+ baseActivity = homeActivities
+ isTopActivityNoDisplay = true
+ }))
+ }
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/DefaultTransitionHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/DefaultTransitionHandlerTest.java
index e540322..82392e0 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/DefaultTransitionHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/DefaultTransitionHandlerTest.java
@@ -292,6 +292,7 @@
new Binder(),
new TransitionInfoBuilder(TRANSIT_SLEEP, FLAG_SYNC).build(),
MockTransactionPool.create(),
+ MockTransactionPool.create(),
token,
mock(Transitions.TransitionFinishCallback.class));
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
index efbc533..6f73db0 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
@@ -1759,7 +1759,9 @@
@Override
public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
- @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
+ @NonNull SurfaceControl.Transaction startT,
+ @NonNull SurfaceControl.Transaction finishT,
+ @NonNull IBinder mergeTarget,
@NonNull Transitions.TransitionFinishCallback finishCallback) {
if (mFinishOnSync && info.getType() == TRANSIT_SLEEP) {
for (int i = 0; i < mFinishes.size(); ++i) {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/unfold/UnfoldTransitionHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/unfold/UnfoldTransitionHandlerTest.java
index 5ba2f18..aad18cb 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/unfold/UnfoldTransitionHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/unfold/UnfoldTransitionHandlerTest.java
@@ -170,7 +170,8 @@
// Send fold transition request
TransitionFinishCallback mergeFinishCallback = mock(TransitionFinishCallback.class);
mUnfoldTransitionHandler.mergeAnimation(new Binder(), createFoldTransitionInfo(),
- mock(SurfaceControl.Transaction.class), mTransition, mergeFinishCallback);
+ mock(SurfaceControl.Transaction.class), mock(SurfaceControl.Transaction.class),
+ mTransition, mergeFinishCallback);
mTestLooper.dispatchAll();
// Verify that fold transition is merged into unfold and that unfold is finished
@@ -388,6 +389,7 @@
new Binder(),
new TransitionInfoBuilder(TRANSIT_CHANGE, TRANSIT_FLAG_KEYGUARD_GOING_AWAY).build(),
mock(SurfaceControl.Transaction.class),
+ mock(SurfaceControl.Transaction.class),
mTransition,
mergeCallback);
verify(finishCallback, never()).onTransitionFinished(any());
@@ -397,6 +399,7 @@
new Binder(),
new TransitionInfoBuilder(TRANSIT_CHANGE).build(),
mock(SurfaceControl.Transaction.class),
+ mock(SurfaceControl.Transaction.class),
mTransition,
mergeCallback);
verify(mergeCallback).onTransitionFinished(any());
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
index baccbee..737780e 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
@@ -28,6 +28,7 @@
import android.content.Context
import android.content.Intent
import android.content.Intent.ACTION_MAIN
+import android.content.pm.PackageManager
import android.graphics.Rect
import android.graphics.Region
import android.hardware.display.DisplayManager
@@ -96,7 +97,6 @@
import org.mockito.quality.Strictness
import java.util.function.Consumer
-
/**
* Tests of [DesktopModeWindowDecorViewModel]
* Usage: atest WMShellUnitTests:DesktopModeWindowDecorViewModelTests
@@ -307,6 +307,23 @@
}
@Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
+ fun testDecorationIsNotCreatedForDefaultHomePackage() {
+ val packageManager: PackageManager = org.mockito.kotlin.mock()
+ val homeActivities = ComponentName("defaultHomePackage", /* class */ "")
+ val task = createTask(windowingMode = WINDOWING_MODE_FULLSCREEN).apply {
+ baseActivity = homeActivities
+ isTopActivityNoDisplay = false
+ }
+ mContext.setMockPackageManager(packageManager)
+ whenever(packageManager.getHomeActivities(any())).thenReturn(homeActivities)
+
+ onTaskOpening(task)
+
+ assertFalse(windowDecorByTaskIdSpy.contains(task.taskId))
+ }
+
+ @Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_IMMERSIVE_HANDLE_HIDING)
fun testInsetsStateChanged_notifiesAllDecorsInDisplay() {
val task1 = createTask(windowingMode = WINDOWING_MODE_FREEFORM, displayId = 1)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt
index 541b19cf..7468c54 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt
@@ -150,7 +150,6 @@
val display = mock<Display>()
protected lateinit var spyContext: TestableContext
private lateinit var desktopModeEventLogger: DesktopModeEventLogger
- private lateinit var desktopModeCompatPolicy: DesktopModeCompatPolicy
private val transactionFactory = Supplier<SurfaceControl.Transaction> {
SurfaceControl.Transaction()
@@ -165,6 +164,7 @@
DisplayChangeController.OnDisplayChangingListener
internal lateinit var desktopModeOnKeyguardChangedListener: DesktopModeKeyguardChangeListener
protected lateinit var desktopModeWindowDecorViewModel: DesktopModeWindowDecorViewModel
+ protected lateinit var desktopModeCompatPolicy: DesktopModeCompatPolicy
fun setUpCommon() {
spyContext = spy(mContext)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/common/WindowDecorTaskResourceLoaderTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/common/WindowDecorTaskResourceLoaderTest.kt
index 431de89..c61e0eb 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/common/WindowDecorTaskResourceLoaderTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/common/WindowDecorTaskResourceLoaderTest.kt
@@ -86,6 +86,7 @@
spyContext.setMockPackageManager(mockPackageManager)
doReturn(spyContext).whenever(spyContext).createContextAsUser(any(), anyInt())
doReturn(spyContext).whenever(mMockUserProfileContexts)[anyInt()]
+ doReturn(spyContext).whenever(mMockUserProfileContexts).getOrCreate(anyInt())
loader =
WindowDecorTaskResourceLoader(
shellInit = shellInit,
diff --git a/libs/appfunctions/api/current.txt b/libs/appfunctions/api/current.txt
index 139ccfd..7280b12 100644
--- a/libs/appfunctions/api/current.txt
+++ b/libs/appfunctions/api/current.txt
@@ -24,8 +24,8 @@
public final class AppFunctionManager {
ctor public AppFunctionManager(android.content.Context);
- method @RequiresPermission(anyOf={android.Manifest.permission.EXECUTE_APP_FUNCTIONS_TRUSTED, android.Manifest.permission.EXECUTE_APP_FUNCTIONS}, conditional=true) public void executeAppFunction(@NonNull com.android.extensions.appfunctions.ExecuteAppFunctionRequest, @NonNull java.util.concurrent.Executor, @NonNull android.os.CancellationSignal, @NonNull android.os.OutcomeReceiver<com.android.extensions.appfunctions.ExecuteAppFunctionResponse,com.android.extensions.appfunctions.AppFunctionException>);
- method @RequiresPermission(anyOf={android.Manifest.permission.EXECUTE_APP_FUNCTIONS_TRUSTED, android.Manifest.permission.EXECUTE_APP_FUNCTIONS}, conditional=true) public void isAppFunctionEnabled(@NonNull String, @NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,java.lang.Exception>);
+ method @RequiresPermission(android.Manifest.permission.EXECUTE_APP_FUNCTIONS) public void executeAppFunction(@NonNull com.android.extensions.appfunctions.ExecuteAppFunctionRequest, @NonNull java.util.concurrent.Executor, @NonNull android.os.CancellationSignal, @NonNull android.os.OutcomeReceiver<com.android.extensions.appfunctions.ExecuteAppFunctionResponse,com.android.extensions.appfunctions.AppFunctionException>);
+ method @RequiresPermission(android.Manifest.permission.EXECUTE_APP_FUNCTIONS) public void isAppFunctionEnabled(@NonNull String, @NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,java.lang.Exception>);
method public void isAppFunctionEnabled(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,java.lang.Exception>);
method public void setAppFunctionEnabled(@NonNull String, int, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,java.lang.Exception>);
field public static final int APP_FUNCTION_STATE_DEFAULT = 0; // 0x0
diff --git a/libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionManager.java b/libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionManager.java
index 9eb66a3..1e31390 100644
--- a/libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionManager.java
+++ b/libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionManager.java
@@ -104,12 +104,7 @@
* <p>See {@link android.app.appfunctions.AppFunctionManager#executeAppFunction} for the
* documented behaviour of this method.
*/
- @RequiresPermission(
- anyOf = {
- Manifest.permission.EXECUTE_APP_FUNCTIONS_TRUSTED,
- Manifest.permission.EXECUTE_APP_FUNCTIONS
- },
- conditional = true)
+ @RequiresPermission(Manifest.permission.EXECUTE_APP_FUNCTIONS)
public void executeAppFunction(
@NonNull ExecuteAppFunctionRequest sidecarRequest,
@NonNull @CallbackExecutor Executor executor,
@@ -150,12 +145,7 @@
* <p>See {@link android.app.appfunctions.AppFunctionManager#isAppFunctionEnabled} for the
* documented behaviour of this method.
*/
- @RequiresPermission(
- anyOf = {
- Manifest.permission.EXECUTE_APP_FUNCTIONS_TRUSTED,
- Manifest.permission.EXECUTE_APP_FUNCTIONS
- },
- conditional = true)
+ @RequiresPermission(Manifest.permission.EXECUTE_APP_FUNCTIONS)
public void isAppFunctionEnabled(
@NonNull String functionIdentifier,
@NonNull String targetPackage,
diff --git a/media/java/android/media/MediaCodec.java b/media/java/android/media/MediaCodec.java
index c488683..1a84371 100644
--- a/media/java/android/media/MediaCodec.java
+++ b/media/java/android/media/MediaCodec.java
@@ -48,6 +48,7 @@
import android.os.Looper;
import android.os.Message;
import android.os.PersistableBundle;
+import android.os.Trace;
import android.view.Surface;
import java.io.IOException;
@@ -3107,6 +3108,7 @@
int index,
int offset, int size, long presentationTimeUs, int flags)
throws CryptoException {
+ Trace.traceBegin(Trace.TRACE_TAG_VIDEO, "MediaCodec::queueInputBuffer#java");
if ((flags & BUFFER_FLAG_DECODE_ONLY) != 0
&& (flags & BUFFER_FLAG_END_OF_STREAM) != 0) {
throw new InvalidBufferFlagsException(EOS_AND_DECODE_ONLY_ERROR_MESSAGE);
@@ -3126,6 +3128,8 @@
} catch (CryptoException | IllegalStateException e) {
revalidateByteBuffer(mCachedInputBuffers, index, true /* input */);
throw e;
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_VIDEO);
}
}
@@ -3167,6 +3171,7 @@
public final void queueInputBuffers(
int index,
@NonNull ArrayDeque<BufferInfo> bufferInfos) {
+ Trace.traceBegin(Trace.TRACE_TAG_VIDEO, "MediaCodec::queueInputBuffers#java");
synchronized(mBufferLock) {
if (mBufferMode == BUFFER_MODE_BLOCK) {
throw new IncompatibleWithBlockModelException("queueInputBuffers() "
@@ -3182,6 +3187,8 @@
} catch (CryptoException | IllegalStateException | IllegalArgumentException e) {
revalidateByteBuffer(mCachedInputBuffers, index, true /* input */);
throw e;
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_VIDEO);
}
}
@@ -3442,6 +3449,7 @@
@NonNull CryptoInfo info,
long presentationTimeUs,
int flags) throws CryptoException {
+ Trace.traceBegin(Trace.TRACE_TAG_VIDEO, "MediaCodec::queueSecureInputBuffer#java");
if ((flags & BUFFER_FLAG_DECODE_ONLY) != 0
&& (flags & BUFFER_FLAG_END_OF_STREAM) != 0) {
throw new InvalidBufferFlagsException(EOS_AND_DECODE_ONLY_ERROR_MESSAGE);
@@ -3461,6 +3469,8 @@
} catch (CryptoException | IllegalStateException e) {
revalidateByteBuffer(mCachedInputBuffers, index, true /* input */);
throw e;
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_VIDEO);
}
}
@@ -3491,6 +3501,7 @@
int index,
@NonNull ArrayDeque<BufferInfo> bufferInfos,
@NonNull ArrayDeque<CryptoInfo> cryptoInfos) {
+ Trace.traceBegin(Trace.TRACE_TAG_VIDEO, "MediaCodec::queueSecureInputBuffers#java");
synchronized(mBufferLock) {
if (mBufferMode == BUFFER_MODE_BLOCK) {
throw new IncompatibleWithBlockModelException("queueSecureInputBuffers() "
@@ -3506,6 +3517,8 @@
} catch (CryptoException | IllegalStateException | IllegalArgumentException e) {
revalidateByteBuffer(mCachedInputBuffers, index, true /* input */);
throw e;
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_VIDEO);
}
}
@@ -3533,6 +3546,7 @@
* @throws MediaCodec.CodecException upon codec error.
*/
public final int dequeueInputBuffer(long timeoutUs) {
+ Trace.traceBegin(Trace.TRACE_TAG_VIDEO, "MediaCodec::dequeueInputBuffer#java");
synchronized (mBufferLock) {
if (mBufferMode == BUFFER_MODE_BLOCK) {
throw new IncompatibleWithBlockModelException("dequeueInputBuffer() "
@@ -3546,6 +3560,7 @@
validateInputByteBufferLocked(mCachedInputBuffers, res);
}
}
+ Trace.traceEnd(Trace.TRACE_TAG_VIDEO);
return res;
}
diff --git a/media/java/android/media/RoutingSessionInfo.java b/media/java/android/media/RoutingSessionInfo.java
index 3b8cf3f..87bb6ea 100644
--- a/media/java/android/media/RoutingSessionInfo.java
+++ b/media/java/android/media/RoutingSessionInfo.java
@@ -85,8 +85,7 @@
@Retention(RetentionPolicy.SOURCE)
public @interface TransferReason {}
- @NonNull
- final String mId;
+ @NonNull final String mOriginalId;
@Nullable
final CharSequence mName;
@Nullable
@@ -120,7 +119,7 @@
RoutingSessionInfo(@NonNull Builder builder) {
Objects.requireNonNull(builder, "builder must not be null.");
- mId = builder.mId;
+ mOriginalId = builder.mOriginalId;
mName = builder.mName;
mOwnerPackageName = builder.mOwnerPackageName;
mClientPackageName = builder.mClientPackageName;
@@ -148,8 +147,8 @@
}
RoutingSessionInfo(@NonNull Parcel src) {
- mId = src.readString();
- Preconditions.checkArgument(!TextUtils.isEmpty(mId));
+ mOriginalId = src.readString();
+ Preconditions.checkArgument(!TextUtils.isEmpty(mOriginalId));
mName = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(src);
mOwnerPackageName = src.readString();
@@ -221,9 +220,9 @@
@NonNull
public String getId() {
if (!TextUtils.isEmpty(mProviderId)) {
- return MediaRouter2Utils.toUniqueId(mProviderId, mId);
+ return MediaRouter2Utils.toUniqueId(mProviderId, mOriginalId);
} else {
- return mId;
+ return mOriginalId;
}
}
@@ -236,12 +235,16 @@
}
/**
- * Gets the original id set by {@link Builder#Builder(String, String)}.
+ * Gets the original id as assigned by the {@link MediaRoute2ProviderService route provider}.
+ *
+ * <p>This may be different from {@link #getId()}, which may convert this original id into a
+ * unique one by adding information about the provider that created this session info.
+ *
* @hide
*/
@NonNull
public String getOriginalId() {
- return mId;
+ return mOriginalId;
}
/**
@@ -423,7 +426,7 @@
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
- dest.writeString(mId);
+ dest.writeString(mOriginalId);
dest.writeCharSequence(mName);
dest.writeString(mOwnerPackageName);
dest.writeString(mClientPackageName);
@@ -454,7 +457,7 @@
String indent = prefix + " ";
- pw.println(indent + "mId=" + mId);
+ pw.println(indent + "mOriginalId=" + mOriginalId);
pw.println(indent + "mName=" + mName);
pw.println(indent + "mOwnerPackageName=" + mOwnerPackageName);
pw.println(indent + "mClientPackageName=" + mClientPackageName);
@@ -485,7 +488,7 @@
}
RoutingSessionInfo other = (RoutingSessionInfo) obj;
- return Objects.equals(mId, other.mId)
+ return Objects.equals(mOriginalId, other.mOriginalId)
&& Objects.equals(mName, other.mName)
&& Objects.equals(mOwnerPackageName, other.mOwnerPackageName)
&& Objects.equals(mClientPackageName, other.mClientPackageName)
@@ -500,13 +503,13 @@
&& (mTransferReason == other.mTransferReason)
&& Objects.equals(mTransferInitiatorUserHandle, other.mTransferInitiatorUserHandle)
&& Objects.equals(
- mTransferInitiatorPackageName, other.mTransferInitiatorPackageName);
+ mTransferInitiatorPackageName, other.mTransferInitiatorPackageName);
}
@Override
public int hashCode() {
return Objects.hash(
- mId,
+ mOriginalId,
mName,
mOwnerPackageName,
mClientPackageName,
@@ -585,8 +588,7 @@
* Builder class for {@link RoutingSessionInfo}.
*/
public static final class Builder {
- @NonNull
- private final String mId;
+ @NonNull private final String mOriginalId;
@Nullable
private CharSequence mName;
@Nullable
@@ -616,23 +618,22 @@
/**
* Constructor for builder to create {@link RoutingSessionInfo}.
- * <p>
- * In order to ensure ID uniqueness in {@link MediaRouter2} side, the value of
- * {@link RoutingSessionInfo#getId()} can be different from what was set in
- * {@link MediaRoute2ProviderService}.
- * </p>
*
- * @param id ID of the session. Must not be empty.
- * @param clientPackageName package name of the client app which uses this session.
- * If is is unknown, then just use an empty string.
+ * <p>In order to ensure ID uniqueness in {@link MediaRouter2} side, the value of {@link
+ * RoutingSessionInfo#getId()} can be different from what was set in {@link
+ * MediaRoute2ProviderService}.
+ *
+ * @param originalId ID of the session. Must not be empty.
+ * @param clientPackageName package name of the client app which uses this session. If is is
+ * unknown, then just use an empty string.
* @see MediaRoute2Info#getId()
*/
- public Builder(@NonNull String id, @NonNull String clientPackageName) {
- if (TextUtils.isEmpty(id)) {
+ public Builder(@NonNull String originalId, @NonNull String clientPackageName) {
+ if (TextUtils.isEmpty(originalId)) {
throw new IllegalArgumentException("id must not be empty");
}
- mId = id;
+ mOriginalId = originalId;
mClientPackageName =
Objects.requireNonNull(clientPackageName, "clientPackageName must not be null");
mSelectedRoutes = new ArrayList<>();
@@ -650,7 +651,7 @@
public Builder(@NonNull RoutingSessionInfo sessionInfo) {
Objects.requireNonNull(sessionInfo, "sessionInfo must not be null");
- mId = sessionInfo.mId;
+ mOriginalId = sessionInfo.mOriginalId;
mName = sessionInfo.mName;
mClientPackageName = sessionInfo.mClientPackageName;
mProviderId = sessionInfo.mProviderId;
diff --git a/media/java/android/media/flags/media_better_together.aconfig b/media/java/android/media/flags/media_better_together.aconfig
index 94454cf..405d292 100644
--- a/media/java/android/media/flags/media_better_together.aconfig
+++ b/media/java/android/media/flags/media_better_together.aconfig
@@ -125,6 +125,13 @@
}
flag {
+ name: "enable_output_switcher_session_grouping"
+ namespace: "media_better_together"
+ description: "Enables selected items in Output Switcher to be grouped together."
+ bug: "388347018"
+}
+
+flag {
name: "enable_prevention_of_keep_alive_route_providers"
namespace: "media_solutions"
description: "Enables mechanisms to prevent route providers from keeping malicious apps alive."
diff --git a/media/java/android/media/quality/MediaQualityContract.java b/media/java/android/media/quality/MediaQualityContract.java
index e558209..e4de3e4 100644
--- a/media/java/android/media/quality/MediaQualityContract.java
+++ b/media/java/android/media/quality/MediaQualityContract.java
@@ -341,6 +341,13 @@
public static final String PARAMETER_FILM_MODE = "film_mode";
/**
+ * Enable/disable black color auto stretch
+ *
+ * @hide
+ */
+ public static final String PARAMETER_BLACK_STRETCH = "black_stretch";
+
+ /**
* Enable/disable blue color auto stretch
*
* <p>Type: BOOLEAN
@@ -457,6 +464,27 @@
* @hide
*
*/
+ public static final String PARAMETER_COLOR_TEMPERATURE_RED_GAIN =
+ "color_temperature_red_gain";
+
+ /**
+ * @hide
+ *
+ */
+ public static final String PARAMETER_COLOR_TEMPERATURE_GREEN_GAIN =
+ "color_temperature_green_gain";
+
+ /**
+ * @hide
+ *
+ */
+ public static final String PARAMETER_COLOR_TEMPERATURE_BLUE_GAIN =
+ "color_temperature_blue_gain";
+
+ /**
+ * @hide
+ *
+ */
public static final String PARAMETER_COLOR_TEMPERATURE_RED_OFFSET =
"color_temperature_red_offset";
diff --git a/media/jni/android_media_MediaCodec.cpp b/media/jni/android_media_MediaCodec.cpp
index 8419ce7..3bc238a 100644
--- a/media/jni/android_media_MediaCodec.cpp
+++ b/media/jni/android_media_MediaCodec.cpp
@@ -16,7 +16,9 @@
//#define LOG_NDEBUG 0
#define LOG_TAG "MediaCodec-JNI"
+#define ATRACE_TAG ATRACE_TAG_VIDEO
#include <utils/Log.h>
+#include <utils/Trace.h>
#include <type_traits>
@@ -2106,7 +2108,7 @@
jlong timestampUs,
jint flags) {
ALOGV("android_media_MediaCodec_queueInputBuffer");
-
+ ScopedTrace trace(ATRACE_TAG, "MediaCodec::queueInputBuffer#jni");
sp<JMediaCodec> codec = getMediaCodec(env, thiz);
if (codec == NULL || codec->initCheck() != OK) {
@@ -2192,6 +2194,7 @@
jint index,
jobjectArray objArray) {
ALOGV("android_media_MediaCodec_queueInputBuffers");
+ ScopedTrace trace(ATRACE_TAG, "MediaCodec::queueInputBuffers#jni");
sp<JMediaCodec> codec = getMediaCodec(env, thiz);
if (codec == NULL || codec->initCheck() != OK || objArray == NULL) {
throwExceptionAsNecessary(env, INVALID_OPERATION, codec);
@@ -2431,6 +2434,7 @@
jobject cryptoInfoObj,
jlong timestampUs,
jint flags) {
+ ScopedTrace trace(ATRACE_TAG, "MediaCodec::queueSecureInputBuffer#jni");
ALOGV("android_media_MediaCodec_queueSecureInputBuffer");
sp<JMediaCodec> codec = getMediaCodec(env, thiz);
@@ -2641,6 +2645,7 @@
jint index,
jobjectArray bufferInfosObjs,
jobjectArray cryptoInfoObjs) {
+ ScopedTrace trace(ATRACE_TAG, "MediaCodec::queueSecureInputBuffers#jni");
ALOGV("android_media_MediaCodec_queueSecureInputBuffers");
sp<JMediaCodec> codec = getMediaCodec(env, thiz);
@@ -2685,6 +2690,7 @@
}
static jobject android_media_MediaCodec_mapHardwareBuffer(JNIEnv *env, jclass, jobject bufferObj) {
+ ScopedTrace trace(ATRACE_TAG, "MediaCodec::mapHardwareBuffer#jni");
ALOGV("android_media_MediaCodec_mapHardwareBuffer");
AHardwareBuffer *hardwareBuffer = android_hardware_HardwareBuffer_getNativeHardwareBuffer(
env, bufferObj);
@@ -3028,6 +3034,7 @@
static void android_media_MediaCodec_native_queueLinearBlock(
JNIEnv *env, jobject thiz, jint index, jobject bufferObj,
jobjectArray cryptoInfoArray, jobjectArray objArray, jobject keys, jobject values) {
+ ScopedTrace trace(ATRACE_TAG, "MediaCodec::queueLinearBlock#jni");
ALOGV("android_media_MediaCodec_native_queueLinearBlock");
sp<JMediaCodec> codec = getMediaCodec(env, thiz);
@@ -3145,6 +3152,7 @@
JNIEnv *env, jobject thiz, jint index, jobject bufferObj,
jlong presentationTimeUs, jint flags, jobject keys, jobject values) {
ALOGV("android_media_MediaCodec_native_queueHardwareBuffer");
+ ScopedTrace trace(ATRACE_TAG, "MediaCodec::queueHardwareBuffer#jni");
sp<JMediaCodec> codec = getMediaCodec(env, thiz);
diff --git a/packages/NeuralNetworks/framework/module/java/android/app/ondeviceintelligence/InferenceInfo.java b/packages/NeuralNetworks/framework/module/java/android/app/ondeviceintelligence/InferenceInfo.java
index cae8db2..428997e 100644
--- a/packages/NeuralNetworks/framework/module/java/android/app/ondeviceintelligence/InferenceInfo.java
+++ b/packages/NeuralNetworks/framework/module/java/android/app/ondeviceintelligence/InferenceInfo.java
@@ -19,6 +19,7 @@
import static android.app.ondeviceintelligence.flags.Flags.FLAG_ENABLE_ON_DEVICE_INTELLIGENCE_MODULE;
import android.annotation.CurrentTimeMillisLong;
+import android.annotation.DurationMillisLong;
import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.SystemApi;
@@ -51,7 +52,8 @@
private final long endTimeMs;
/**
- * Suspended time in milliseconds.
+ * The total duration of the period(s) during which the inference was
+ * suspended (i.e. not running), in milliseconds.
*/
private final long suspendedTimeMs;
@@ -61,7 +63,7 @@
* @param uid Uid for the caller app.
* @param startTimeMs Inference start time (milliseconds from the epoch time).
* @param endTimeMs Inference end time (milliseconds from the epoch time).
- * @param suspendedTimeMs Suspended time in milliseconds.
+ * @param suspendedTimeMs Suspended duration, in milliseconds.
*/
InferenceInfo(int uid, long startTimeMs, long endTimeMs,
long suspendedTimeMs) {
@@ -128,11 +130,12 @@
}
/**
- * Returns the suspended time in milliseconds.
+ * Returns the suspended duration, in milliseconds.
*
- * @return the suspended time in milliseconds.
+ * @return the total duration of the period(s) during which the inference
+ * was suspended (i.e. not running), in milliseconds.
*/
- @CurrentTimeMillisLong
+ @DurationMillisLong
public long getSuspendedTimeMillis() {
return suspendedTimeMs;
}
@@ -197,12 +200,14 @@
}
/**
- * Sets the suspended time in milliseconds.
+ * Sets the suspended duration, in milliseconds.
*
- * @param suspendedTimeMs the suspended time in milliseconds.
+ * @param suspendedTimeMs the total duration of the period(s) in which
+ * the request was suspended (i.e. not running),
+ * in milliseconds.
* @return the Builder instance.
*/
- public @NonNull Builder setSuspendedTimeMillis(@CurrentTimeMillisLong long suspendedTimeMs) {
+ public @NonNull Builder setSuspendedTimeMillis(@DurationMillisLong long suspendedTimeMs) {
this.suspendedTimeMs = suspendedTimeMs;
return this;
}
diff --git a/packages/NeuralNetworks/framework/platform/java/android/app/ondeviceintelligence/InferenceInfo.java b/packages/NeuralNetworks/framework/platform/java/android/app/ondeviceintelligence/InferenceInfo.java
index cae8db2..64524fb 100644
--- a/packages/NeuralNetworks/framework/platform/java/android/app/ondeviceintelligence/InferenceInfo.java
+++ b/packages/NeuralNetworks/framework/platform/java/android/app/ondeviceintelligence/InferenceInfo.java
@@ -19,6 +19,7 @@
import static android.app.ondeviceintelligence.flags.Flags.FLAG_ENABLE_ON_DEVICE_INTELLIGENCE_MODULE;
import android.annotation.CurrentTimeMillisLong;
+import android.annotation.DurationMillisLong;
import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.SystemApi;
@@ -50,8 +51,9 @@
*/
private final long endTimeMs;
- /**
- * Suspended time in milliseconds.
+ /**
+ * The total duration of the period(s) during which the inference was
+ * suspended (i.e. not running), in milliseconds.
*/
private final long suspendedTimeMs;
@@ -61,7 +63,7 @@
* @param uid Uid for the caller app.
* @param startTimeMs Inference start time (milliseconds from the epoch time).
* @param endTimeMs Inference end time (milliseconds from the epoch time).
- * @param suspendedTimeMs Suspended time in milliseconds.
+ * @param suspendedTimeMs Suspended duration, in milliseconds.
*/
InferenceInfo(int uid, long startTimeMs, long endTimeMs,
long suspendedTimeMs) {
@@ -128,11 +130,12 @@
}
/**
- * Returns the suspended time in milliseconds.
+ * Returns the suspended duration, in milliseconds.
*
- * @return the suspended time in milliseconds.
+ * @return the total duration of the period(s) during which the inference
+ * was suspended (i.e. not running), in milliseconds.
*/
- @CurrentTimeMillisLong
+ @DurationMillisLong
public long getSuspendedTimeMillis() {
return suspendedTimeMs;
}
@@ -197,12 +200,14 @@
}
/**
- * Sets the suspended time in milliseconds.
+ * Sets the suspended duration, in milliseconds.
*
- * @param suspendedTimeMs the suspended time in milliseconds.
+ * @param suspendedTimeMs the total duration of the period(s) in which
+ * the request was suspended (i.e. not running),
+ * in milliseconds.
* @return the Builder instance.
*/
- public @NonNull Builder setSuspendedTimeMillis(@CurrentTimeMillisLong long suspendedTimeMs) {
+ public @NonNull Builder setSuspendedTimeMillis(@DurationMillisLong long suspendedTimeMs) {
this.suspendedTimeMs = suspendedTimeMs;
return this;
}
diff --git a/packages/SettingsLib/BannerMessagePreference/src/com/android/settingslib/widget/BannerMessagePreference.java b/packages/SettingsLib/BannerMessagePreference/src/com/android/settingslib/widget/BannerMessagePreference.java
index 60a9ebd..c82829d 100644
--- a/packages/SettingsLib/BannerMessagePreference/src/com/android/settingslib/widget/BannerMessagePreference.java
+++ b/packages/SettingsLib/BannerMessagePreference/src/com/android/settingslib/widget/BannerMessagePreference.java
@@ -116,8 +116,8 @@
// Default attention level is High.
private AttentionLevel mAttentionLevel = AttentionLevel.HIGH;
- private String mSubtitle;
- private String mHeader;
+ private CharSequence mSubtitle;
+ private CharSequence mHeader;
private int mButtonOrientation;
public BannerMessagePreference(Context context) {
@@ -351,7 +351,7 @@
/**
* Sets the text to be displayed in positive button.
*/
- public BannerMessagePreference setPositiveButtonText(String positiveButtonText) {
+ public BannerMessagePreference setPositiveButtonText(CharSequence positiveButtonText) {
if (!TextUtils.equals(positiveButtonText, mPositiveButtonInfo.mText)) {
mPositiveButtonInfo.mText = positiveButtonText;
notifyChanged();
@@ -369,7 +369,7 @@
/**
* Sets the text to be displayed in negative button.
*/
- public BannerMessagePreference setNegativeButtonText(String negativeButtonText) {
+ public BannerMessagePreference setNegativeButtonText(CharSequence negativeButtonText) {
if (!TextUtils.equals(negativeButtonText, mNegativeButtonInfo.mText)) {
mNegativeButtonInfo.mText = negativeButtonText;
notifyChanged();
@@ -401,7 +401,7 @@
* Sets the subtitle.
*/
@RequiresApi(Build.VERSION_CODES.S)
- public BannerMessagePreference setSubtitle(String subtitle) {
+ public BannerMessagePreference setSubtitle(CharSequence subtitle) {
if (!TextUtils.equals(subtitle, mSubtitle)) {
mSubtitle = subtitle;
notifyChanged();
@@ -421,8 +421,8 @@
* Sets the header.
*/
@RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
- public BannerMessagePreference setHeader(String header) {
- if (!TextUtils.equals(header, mSubtitle)) {
+ public BannerMessagePreference setHeader(CharSequence header) {
+ if (!TextUtils.equals(header, mHeader)) {
mHeader = header;
notifyChanged();
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
index ad196b8..4ee9ff0 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
@@ -658,12 +658,9 @@
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
RouteListingPreference routeListingPreference = getRouteListingPreference();
if (routeListingPreference != null) {
- final List<RouteListingPreference.Item> preferenceRouteListing =
- Api34Impl.composePreferenceRouteListing(
- routeListingPreference);
availableRoutes = Api34Impl.arrangeRouteListByPreference(selectedRoutes,
getAvailableRoutesFromRouter(),
- preferenceRouteListing);
+ routeListingPreference);
}
return Api34Impl.filterDuplicatedIds(availableRoutes);
} else {
@@ -760,11 +757,15 @@
@DoNotInline
static List<RouteListingPreference.Item> composePreferenceRouteListing(
RouteListingPreference routeListingPreference) {
+ boolean preferRouteListingOrdering =
+ com.android.media.flags.Flags.enableOutputSwitcherSessionGrouping()
+ && preferRouteListingOrdering(routeListingPreference);
List<RouteListingPreference.Item> finalizedItemList = new ArrayList<>();
List<RouteListingPreference.Item> itemList = routeListingPreference.getItems();
for (RouteListingPreference.Item item : itemList) {
// Put suggested devices on the top first before further organization
- if ((item.getFlags() & RouteListingPreference.Item.FLAG_SUGGESTED) != 0) {
+ if (!preferRouteListingOrdering
+ && (item.getFlags() & RouteListingPreference.Item.FLAG_SUGGESTED) != 0) {
finalizedItemList.add(0, item);
} else {
finalizedItemList.add(item);
@@ -792,7 +793,7 @@
* Returns an ordered list of available devices based on the provided {@code
* routeListingPreferenceItems}.
*
- * <p>The result has the following order:
+ * <p>The resulting order if enableOutputSwitcherSessionGrouping is disabled is:
*
* <ol>
* <li>Selected routes.
@@ -800,22 +801,54 @@
* <li>Not-selected, non-system, available routes sorted by route listing preference.
* </ol>
*
+ * <p>The resulting order if enableOutputSwitcherSessionGrouping is enabled is:
+ *
+ * <ol>
+ * <li>Selected routes sorted by route listing preference.
+ * <li>Selected routes not defined by route listing preference.
+ * <li>Not-selected system routes.
+ * <li>Not-selected, non-system, available routes sorted by route listing preference.
+ * </ol>
+ *
+ *
* @param selectedRoutes List of currently selected routes.
* @param availableRoutes List of available routes that match the app's requested route
* features.
- * @param routeListingPreferenceItems Ordered list of {@link RouteListingPreference.Item} to
- * sort routes with.
+ * @param routeListingPreference Preferences provided by the app to determine route order.
*/
@DoNotInline
static List<MediaRoute2Info> arrangeRouteListByPreference(
List<MediaRoute2Info> selectedRoutes,
List<MediaRoute2Info> availableRoutes,
- List<RouteListingPreference.Item> routeListingPreferenceItems) {
+ RouteListingPreference routeListingPreference) {
+ final List<RouteListingPreference.Item> routeListingPreferenceItems =
+ Api34Impl.composePreferenceRouteListing(routeListingPreference);
+
Set<String> sortedRouteIds = new LinkedHashSet<>();
+ boolean addSelectedRlpItemsFirst =
+ com.android.media.flags.Flags.enableOutputSwitcherSessionGrouping()
+ && preferRouteListingOrdering(routeListingPreference);
+ Set<String> selectedRouteIds = new HashSet<>();
+
+ if (addSelectedRlpItemsFirst) {
+ // Add selected RLP items first
+ for (MediaRoute2Info selectedRoute : selectedRoutes) {
+ selectedRouteIds.add(selectedRoute.getId());
+ }
+ for (RouteListingPreference.Item item: routeListingPreferenceItems) {
+ if (selectedRouteIds.contains(item.getRouteId())) {
+ sortedRouteIds.add(item.getRouteId());
+ }
+ }
+ }
+
// Add selected routes first.
- for (MediaRoute2Info selectedRoute : selectedRoutes) {
- sortedRouteIds.add(selectedRoute.getId());
+ if (com.android.media.flags.Flags.enableOutputSwitcherSessionGrouping()
+ && sortedRouteIds.size() != selectedRoutes.size()) {
+ for (MediaRoute2Info selectedRoute : selectedRoutes) {
+ sortedRouteIds.add(selectedRoute.getId());
+ }
}
// Add not-yet-added system routes.
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
index e1447dc..1a83f0a 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
@@ -48,15 +48,20 @@
import android.media.RoutingSessionInfo;
import android.media.session.MediaSessionManager;
import android.os.Build;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
+import com.android.media.flags.Flags;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
+import com.android.settingslib.media.InfoMediaManager.Api34Impl;
import com.android.settingslib.testutils.shadow.ShadowRouter2Manager;
import com.google.common.collect.ImmutableList;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -122,6 +127,8 @@
.addFeature(MediaRoute2Info.FEATURE_LIVE_AUDIO)
.build();
+ @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
@Mock
private MediaRouter2Manager mRouterManager;
@Mock
@@ -377,21 +384,26 @@
}
private RouteListingPreference setUpPreferenceList(String packageName) {
+ return setUpPreferenceList(packageName, false);
+ }
+
+ private RouteListingPreference setUpPreferenceList(
+ String packageName, boolean useSystemOrdering) {
ReflectionHelpers.setStaticField(Build.VERSION.class, "SDK_INT",
Build.VERSION_CODES.UPSIDE_DOWN_CAKE);
final List<RouteListingPreference.Item> preferenceItemList = new ArrayList<>();
- RouteListingPreference.Item item1 =
+ RouteListingPreference.Item item1 = new RouteListingPreference.Item.Builder(
+ TEST_ID_3).build();
+ RouteListingPreference.Item item2 =
new RouteListingPreference.Item.Builder(TEST_ID_4)
.setFlags(RouteListingPreference.Item.FLAG_SUGGESTED)
.build();
- RouteListingPreference.Item item2 = new RouteListingPreference.Item.Builder(
- TEST_ID_3).build();
preferenceItemList.add(item1);
preferenceItemList.add(item2);
RouteListingPreference routeListingPreference =
new RouteListingPreference.Builder().setItems(
- preferenceItemList).setUseSystemOrdering(false).build();
+ preferenceItemList).setUseSystemOrdering(useSystemOrdering).build();
when(mRouterManager.getRouteListingPreference(packageName))
.thenReturn(routeListingPreference);
return routeListingPreference;
@@ -908,4 +920,66 @@
assertThat(device.getState()).isEqualTo(STATE_SELECTED);
assertThat(mInfoMediaManager.getCurrentConnectedDevice()).isEqualTo(device);
}
+
+ @EnableFlags(Flags.FLAG_ENABLE_OUTPUT_SWITCHER_SESSION_GROUPING)
+ @Test
+ public void composePreferenceRouteListing_useSystemOrderingIsFalse() {
+ RouteListingPreference routeListingPreference =
+ setUpPreferenceList(TEST_PACKAGE_NAME, false);
+
+ List<RouteListingPreference.Item> routeOrder =
+ Api34Impl.composePreferenceRouteListing(routeListingPreference);
+
+ assertThat(routeOrder.get(0).getRouteId()).isEqualTo(TEST_ID_3);
+ assertThat(routeOrder.get(1).getRouteId()).isEqualTo(TEST_ID_4);
+ }
+
+ @EnableFlags(Flags.FLAG_ENABLE_OUTPUT_SWITCHER_SESSION_GROUPING)
+ @Test
+ public void composePreferenceRouteListing_useSystemOrderingIsTrue() {
+ RouteListingPreference routeListingPreference =
+ setUpPreferenceList(TEST_PACKAGE_NAME, true);
+
+ List<RouteListingPreference.Item> routeOrder =
+ Api34Impl.composePreferenceRouteListing(routeListingPreference);
+
+ assertThat(routeOrder.get(0).getRouteId()).isEqualTo(TEST_ID_4);
+ assertThat(routeOrder.get(1).getRouteId()).isEqualTo(TEST_ID_3);
+ }
+
+ @EnableFlags(Flags.FLAG_ENABLE_OUTPUT_SWITCHER_SESSION_GROUPING)
+ @Test
+ public void arrangeRouteListByPreference_useSystemOrderingIsFalse() {
+ RouteListingPreference routeListingPreference =
+ setUpPreferenceList(TEST_PACKAGE_NAME, false);
+ List<MediaRoute2Info> routes = setAvailableRoutesList(TEST_PACKAGE_NAME);
+ when(mRouterManager.getSelectedRoutes(any())).thenReturn(routes);
+
+ List<MediaRoute2Info> routeOrder =
+ Api34Impl.arrangeRouteListByPreference(
+ routes, routes, routeListingPreference);
+
+ assertThat(routeOrder.get(0).getId()).isEqualTo(TEST_ID_3);
+ assertThat(routeOrder.get(1).getId()).isEqualTo(TEST_ID_4);
+ assertThat(routeOrder.get(2).getId()).isEqualTo(TEST_ID_2);
+ assertThat(routeOrder.get(3).getId()).isEqualTo(TEST_ID_1);
+ }
+
+ @EnableFlags(Flags.FLAG_ENABLE_OUTPUT_SWITCHER_SESSION_GROUPING)
+ @Test
+ public void arrangeRouteListByPreference_useSystemOrderingIsTrue() {
+ RouteListingPreference routeListingPreference =
+ setUpPreferenceList(TEST_PACKAGE_NAME, true);
+ List<MediaRoute2Info> routes = setAvailableRoutesList(TEST_PACKAGE_NAME);
+ when(mRouterManager.getSelectedRoutes(any())).thenReturn(routes);
+
+ List<MediaRoute2Info> routeOrder =
+ Api34Impl.arrangeRouteListByPreference(
+ routes, routes, routeListingPreference);
+
+ assertThat(routeOrder.get(0).getId()).isEqualTo(TEST_ID_2);
+ assertThat(routeOrder.get(1).getId()).isEqualTo(TEST_ID_3);
+ assertThat(routeOrder.get(2).getId()).isEqualTo(TEST_ID_4);
+ assertThat(routeOrder.get(3).getId()).isEqualTo(TEST_ID_1);
+ }
}
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 2b4e65f..9ab8aa8 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -952,7 +952,6 @@
<uses-permission android:name="android.permission.SETUP_FSVERITY" />
<!-- Permissions required for CTS test - AppFunctionManagerTest -->
- <uses-permission android:name="android.permission.EXECUTE_APP_FUNCTIONS_TRUSTED" />
<uses-permission android:name="android.permission.EXECUTE_APP_FUNCTIONS" />
<!-- Permission required for CTS test - CtsNfcTestCases -->
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index bb0d5d7..6c96279 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -1697,13 +1697,6 @@
}
flag {
- name: "magic_portrait_wallpapers"
- namespace: "systemui"
- description: "Magic Portrait related changes in systemui"
- bug: "370863642"
-}
-
-flag {
name: "notes_role_qs_tile"
namespace: "systemui"
description: "Enables notes role qs tile which opens default notes role app in app bubbles"
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/ContentDescription.kt b/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/ContentDescription.kt
index 4a5ad65..b254963 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/ContentDescription.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/ContentDescription.kt
@@ -17,11 +17,13 @@
package com.android.systemui.common.ui.compose
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.ReadOnlyComposable
import androidx.compose.ui.res.stringResource
import com.android.systemui.common.shared.model.ContentDescription
/** Returns the loaded [String] or `null` if there isn't one. */
@Composable
+@ReadOnlyComposable
fun ContentDescription.load(): String? {
return when (this) {
is ContentDescription.Loaded -> description
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/Icon.kt b/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/Icon.kt
index 82d1436..8b0c005 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/Icon.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/Icon.kt
@@ -21,9 +21,8 @@
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.res.painterResource
-import androidx.core.graphics.drawable.toBitmap
+import com.android.compose.ui.graphics.painter.rememberDrawablePainter
import com.android.systemui.common.shared.model.Icon
/**
@@ -36,7 +35,7 @@
val contentDescription = icon.contentDescription?.load()
when (icon) {
is Icon.Loaded -> {
- Icon(icon.drawable.toBitmap().asImageBitmap(), contentDescription, modifier, tint)
+ Icon(rememberDrawablePainter(icon.drawable), contentDescription, modifier, tint)
}
is Icon.Resource -> Icon(painterResource(icon.res), contentDescription, modifier, tint)
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/TextExt.kt b/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/TextExt.kt
index 4e8121f..19adba0 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/TextExt.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/TextExt.kt
@@ -19,6 +19,7 @@
import android.content.Context
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.ReadOnlyComposable
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.AnnotatedString
import com.android.systemui.common.shared.model.Text
@@ -26,6 +27,7 @@
/** Returns the loaded [String] or `null` if there isn't one. */
@Composable
+@ReadOnlyComposable
fun Text.load(): String? {
return when (this) {
is Text.Loaded -> text
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt
index 31aebc2..2c6d09a 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt
@@ -19,13 +19,11 @@
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.Layout
import androidx.compose.ui.layout.Measurable
-import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.Constraints
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.IntRect
@@ -35,6 +33,7 @@
import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor
import com.android.systemui.communal.smartspace.SmartspaceInteractionHandler
import com.android.systemui.communal.ui.compose.section.AmbientStatusBarSection
+import com.android.systemui.communal.ui.compose.section.CommunalLockSection
import com.android.systemui.communal.ui.compose.section.CommunalPopupSection
import com.android.systemui.communal.ui.compose.section.CommunalToDreamButtonSection
import com.android.systemui.communal.ui.compose.section.HubOnboardingSection
@@ -43,7 +42,6 @@
import com.android.systemui.keyguard.ui.composable.blueprint.BlueprintAlignmentLines
import com.android.systemui.keyguard.ui.composable.section.BottomAreaSection
import com.android.systemui.keyguard.ui.composable.section.LockSection
-import com.android.systemui.res.R
import com.android.systemui.statusbar.phone.SystemUIDialogFactory
import javax.inject.Inject
import kotlin.math.min
@@ -58,6 +56,7 @@
private val communalSettingsInteractor: CommunalSettingsInteractor,
private val dialogFactory: SystemUIDialogFactory,
private val lockSection: LockSection,
+ private val communalLockSection: CommunalLockSection,
private val bottomAreaSection: BottomAreaSection,
private val ambientStatusBarSection: AmbientStatusBarSection,
private val communalPopupSection: CommunalPopupSection,
@@ -88,12 +87,9 @@
with(hubOnboardingSection) { BottomSheet() }
}
if (communalSettingsInteractor.isV2FlagEnabled()) {
- Icon(
- painter = painterResource(id = R.drawable.ic_lock),
- contentDescription = null,
- tint = MaterialTheme.colorScheme.onPrimaryContainer,
- modifier = Modifier.element(Communal.Elements.LockIcon),
- )
+ with(communalLockSection) {
+ LockIcon(modifier = Modifier.element(Communal.Elements.LockIcon))
+ }
} else {
with(lockSection) {
LockIcon(
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/section/CommunalLockSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/section/CommunalLockSection.kt
new file mode 100644
index 0000000..eab2b87
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/section/CommunalLockSection.kt
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.communal.ui.compose.section
+
+import android.content.Context
+import android.util.DisplayMetrics
+import android.view.WindowManager
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.layout.layout
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.unit.Constraints
+import androidx.compose.ui.unit.IntOffset
+import androidx.compose.ui.unit.IntRect
+import androidx.compose.ui.viewinterop.AndroidView
+import com.android.compose.animation.scene.ContentScope
+import com.android.compose.animation.scene.ElementKey
+import com.android.systemui.biometrics.AuthController
+import com.android.systemui.communal.ui.binder.CommunalLockIconViewBinder
+import com.android.systemui.communal.ui.viewmodel.CommunalLockIconViewModel
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.flags.FeatureFlagsClassic
+import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.ui.composable.blueprint.BlueprintAlignmentLines
+import com.android.systemui.keyguard.ui.view.DeviceEntryIconView
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LongPressHandlingViewLogger
+import com.android.systemui.log.dagger.LongPressTouchLog
+import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.VibratorHelper
+import dagger.Lazy
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+
+class CommunalLockSection
+@Inject
+constructor(
+ @Application private val applicationScope: CoroutineScope,
+ private val windowManager: WindowManager,
+ private val authController: AuthController,
+ private val viewModel: Lazy<CommunalLockIconViewModel>,
+ private val falsingManager: Lazy<FalsingManager>,
+ private val vibratorHelper: Lazy<VibratorHelper>,
+ private val featureFlags: FeatureFlagsClassic,
+ @LongPressTouchLog private val logBuffer: LogBuffer,
+) {
+ @Composable
+ fun ContentScope.LockIcon(modifier: Modifier = Modifier) {
+ val context = LocalContext.current
+
+ AndroidView(
+ factory = { context ->
+ DeviceEntryIconView(
+ context,
+ null,
+ logger = LongPressHandlingViewLogger(logBuffer, tag = TAG),
+ )
+ .apply {
+ id = R.id.device_entry_icon_view
+ CommunalLockIconViewBinder.bind(
+ applicationScope,
+ this,
+ viewModel.get(),
+ falsingManager.get(),
+ vibratorHelper.get(),
+ )
+ }
+ },
+ modifier =
+ modifier.element(LockIconElementKey).layout { measurable, _ ->
+ val lockIconBounds = lockIconBounds(context)
+ val placeable =
+ measurable.measure(
+ Constraints.fixed(
+ width = lockIconBounds.width,
+ height = lockIconBounds.height,
+ )
+ )
+ layout(
+ width = placeable.width,
+ height = placeable.height,
+ alignmentLines =
+ mapOf(
+ BlueprintAlignmentLines.LockIcon.Left to lockIconBounds.left,
+ BlueprintAlignmentLines.LockIcon.Top to lockIconBounds.top,
+ BlueprintAlignmentLines.LockIcon.Right to lockIconBounds.right,
+ BlueprintAlignmentLines.LockIcon.Bottom to lockIconBounds.bottom,
+ ),
+ ) {
+ placeable.place(0, 0)
+ }
+ },
+ )
+ }
+
+ /** Returns the bounds of the lock icon, in window view coordinates. */
+ private fun lockIconBounds(context: Context): IntRect {
+ val windowViewBounds = windowManager.currentWindowMetrics.bounds
+ var widthPx = windowViewBounds.right.toFloat()
+ if (featureFlags.isEnabled(Flags.LOCKSCREEN_ENABLE_LANDSCAPE)) {
+ val insets = windowManager.currentWindowMetrics.windowInsets
+ // Assumed to be initially neglected as there are no left or right insets in portrait.
+ // However, on landscape, these insets need to included when calculating the midpoint.
+ @Suppress("DEPRECATION")
+ widthPx -= (insets.systemWindowInsetLeft + insets.systemWindowInsetRight).toFloat()
+ }
+ val defaultDensity =
+ DisplayMetrics.DENSITY_DEVICE_STABLE.toFloat() /
+ DisplayMetrics.DENSITY_DEFAULT.toFloat()
+ val lockIconRadiusPx = (defaultDensity * 36).toInt()
+
+ val scaleFactor = authController.scaleFactor
+ val bottomPaddingPx =
+ context.resources.getDimensionPixelSize(
+ com.android.systemui.customization.R.dimen.lock_icon_margin_bottom
+ )
+ val heightPx = windowViewBounds.bottom.toFloat()
+ val (center, radius) =
+ Pair(
+ IntOffset(
+ x = (widthPx / 2).toInt(),
+ y = (heightPx - ((bottomPaddingPx + lockIconRadiusPx) * scaleFactor)).toInt(),
+ ),
+ (lockIconRadiusPx * scaleFactor).toInt(),
+ )
+
+ return IntRect(center, radius)
+ }
+
+ companion object {
+ private const val TAG = "CommunalLockSection"
+ }
+}
+
+private val LockIconElementKey = ElementKey("CommunalLockIcon")
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToNotificationsShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToNotificationsShadeTransition.kt
index b4c6003..73b0750 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToNotificationsShadeTransition.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToNotificationsShadeTransition.kt
@@ -17,11 +17,8 @@
package com.android.systemui.scene.ui.composable.transitions
import androidx.compose.animation.core.tween
-import com.android.compose.animation.scene.ContentKey
import com.android.compose.animation.scene.Edge
import com.android.compose.animation.scene.TransitionBuilder
-import com.android.compose.animation.scene.UserActionDistance
-import com.android.compose.animation.scene.UserActionDistanceScope
import com.android.systemui.keyguard.ui.composable.blueprint.ClockElementKeys
import com.android.systemui.notifications.ui.composable.Notifications
import com.android.systemui.notifications.ui.composable.NotificationsShade
@@ -31,9 +28,6 @@
fun TransitionBuilder.toNotificationsShadeTransition(durationScale: Double = 1.0) {
spec = tween(durationMillis = (DefaultDuration * durationScale).inWholeMilliseconds.toInt())
- distance = UserActionDistance { _, shadeContentKey, _ ->
- calculateShadePanelTargetPositionY(shadeContentKey)
- }
// Ensure the clock isn't clipped by the shade outline during the transition from lockscreen.
sharedElement(
@@ -50,12 +44,4 @@
fractionRange(start = .5f) { fade(Notifications.Elements.NotificationScrim) }
}
-/** Returns the Y position of the bottom of the shade container panel within [shadeOverlayKey]. */
-fun UserActionDistanceScope.calculateShadePanelTargetPositionY(shadeOverlayKey: ContentKey): Float {
- val marginTop = OverlayShade.Elements.Panel.targetOffset(shadeOverlayKey)?.y ?: 0f
- val panelHeight =
- OverlayShade.Elements.Panel.targetSize(shadeOverlayKey)?.height?.toFloat() ?: 0f
- return marginTop + panelHeight
-}
-
private val DefaultDuration = 300.milliseconds
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToQuickSettingsShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToQuickSettingsShadeTransition.kt
index c9fbb4d..43aa358 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToQuickSettingsShadeTransition.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToQuickSettingsShadeTransition.kt
@@ -19,18 +19,13 @@
import androidx.compose.animation.core.tween
import com.android.compose.animation.scene.Edge
import com.android.compose.animation.scene.TransitionBuilder
-import com.android.compose.animation.scene.UserActionDistance
import com.android.systemui.shade.ui.composable.OverlayShade
import kotlin.time.Duration.Companion.milliseconds
fun TransitionBuilder.toQuickSettingsShadeTransition(durationScale: Double = 1.0) {
spec = tween(durationMillis = (DefaultDuration * durationScale).inWholeMilliseconds.toInt())
- distance = UserActionDistance { _, shadeContentKey, _ ->
- calculateShadePanelTargetPositionY(shadeContentKey)
- }
translate(OverlayShade.Elements.Panel, Edge.Top)
-
fractionRange(end = .5f) { fade(OverlayShade.Elements.Scrim) }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalLockIconViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalLockIconViewModelTest.kt
new file mode 100644
index 0000000..c535831
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalLockIconViewModelTest.kt
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.communal.view.viewmodel
+
+import android.platform.test.flag.junit.FlagsParameterization
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.accessibility.domain.interactor.accessibilityInteractor
+import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
+import com.android.systemui.authentication.domain.interactor.AuthenticationResult
+import com.android.systemui.authentication.domain.interactor.authenticationInteractor
+import com.android.systemui.common.ui.domain.interactor.configurationInteractor
+import com.android.systemui.communal.ui.viewmodel.CommunalLockIconViewModel
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
+import com.android.systemui.deviceentry.domain.interactor.deviceEntrySourceInteractor
+import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor
+import com.android.systemui.flags.andSceneContainer
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
+import com.android.systemui.keyguard.ui.view.DeviceEntryIconView
+import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryIconViewModel
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.backgroundScope
+import com.android.systemui.kosmos.collectLastValue
+import com.android.systemui.kosmos.runTest
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
+import com.android.systemui.statusbar.phone.statusBarKeyguardViewManager
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.advanceTimeBy
+import org.junit.Test
+import org.junit.runner.RunWith
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(ParameterizedAndroidJunit4::class)
+class CommunalLockIconViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
+ companion object {
+ @JvmStatic
+ @Parameters(name = "{0}")
+ fun getParams(): List<FlagsParameterization> {
+ return FlagsParameterization.allCombinationsOf().andSceneContainer()
+ }
+ }
+
+ init {
+ mSetFlagsRule.setFlagsParameterization(flags)
+ }
+
+ private val kosmos = testKosmos().useUnconfinedTestDispatcher()
+
+ private val Kosmos.underTest by
+ Kosmos.Fixture {
+ CommunalLockIconViewModel(
+ context = context,
+ configurationInteractor = configurationInteractor,
+ deviceEntryInteractor = deviceEntryInteractor,
+ keyguardInteractor = keyguardInteractor,
+ keyguardViewController = { statusBarKeyguardViewManager },
+ deviceEntrySourceInteractor = deviceEntrySourceInteractor,
+ accessibilityInteractor = accessibilityInteractor,
+ )
+ }
+
+ @Test
+ fun isLongPressEnabled_unlocked() =
+ kosmos.runTest {
+ val isLongPressEnabled by collectLastValue(underTest.isLongPressEnabled)
+ setLockscreenDismissible()
+ assertThat(isLongPressEnabled).isTrue()
+ }
+
+ @Test
+ fun isLongPressEnabled_lock() =
+ kosmos.runTest {
+ val isLongPressEnabled by collectLastValue(underTest.isLongPressEnabled)
+ if (!SceneContainerFlag.isEnabled) {
+ fakeKeyguardRepository.setKeyguardDismissible(false)
+ }
+ assertThat(isLongPressEnabled).isFalse()
+ }
+
+ @Test
+ fun iconType_locked() =
+ kosmos.runTest {
+ val viewAttributes by collectLastValue(underTest.viewAttributes)
+ if (!SceneContainerFlag.isEnabled) {
+ fakeKeyguardRepository.setKeyguardDismissible(false)
+ }
+ assertThat(viewAttributes?.type).isEqualTo(DeviceEntryIconView.IconType.LOCK)
+ }
+
+ @Test
+ fun iconType_unlocked() =
+ kosmos.runTest {
+ val viewAttributes by collectLastValue(underTest.viewAttributes)
+ setLockscreenDismissible()
+ assertThat(viewAttributes?.type).isEqualTo(DeviceEntryIconView.IconType.UNLOCK)
+ }
+
+ private suspend fun Kosmos.setLockscreenDismissible() {
+ if (SceneContainerFlag.isEnabled) {
+ // Need to set up a collection for the authentication to be propagated.
+ backgroundScope.launch { kosmos.deviceUnlockedInteractor.deviceUnlockStatus.collect {} }
+ assertThat(
+ kosmos.authenticationInteractor.authenticate(
+ FakeAuthenticationRepository.DEFAULT_PIN
+ )
+ )
+ .isEqualTo(AuthenticationResult.SUCCEEDED)
+ } else {
+ fakeKeyguardRepository.setKeyguardDismissible(true)
+ }
+ testScope.advanceTimeBy(
+ DeviceEntryIconViewModel.UNLOCKED_DELAY_MS * 2
+ ) // wait for unlocked delay
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java
index 7478464..57ac906 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java
@@ -117,8 +117,8 @@
LocalMediaManager.MediaDeviceState.STATE_DISCONNECTED);
mMediaDevices.add(mMediaDevice1);
mMediaDevices.add(mMediaDevice2);
- mMediaItems.add(MediaItem.createDeviceMediaItem(mMediaDevice1));
- mMediaItems.add(MediaItem.createDeviceMediaItem(mMediaDevice2));
+ mMediaItems.add(MediaItem.createDeviceMediaItem(mMediaDevice1, true));
+ mMediaItems.add(MediaItem.createDeviceMediaItem(mMediaDevice2, false));
mMediaOutputAdapter = new MediaOutputAdapter(mMediaSwitchingController);
mMediaOutputAdapter.updateItems();
@@ -779,4 +779,120 @@
mViewHolder.getDrawableId(false /* isInputDevice */, false /* isMutedVolumeIcon */))
.isEqualTo(R.drawable.media_output_icon_volume);
}
+
+ @EnableFlags(Flags.FLAG_ENABLE_OUTPUT_SWITCHER_SESSION_GROUPING)
+ @Test
+ public void multipleSelectedDevices_verifySessionView() {
+ initializeSession();
+
+ mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter
+ .onCreateViewHolder(
+ new LinearLayout(mContext), MediaItem.MediaItemType.TYPE_DEVICE);
+ mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0);
+
+ assertThat(mViewHolder.mSeekBar.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(mViewHolder.mTitleText.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(mViewHolder.mCheckBox.getVisibility()).isEqualTo(View.GONE);
+ assertThat(mViewHolder.mEndTouchArea.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(mViewHolder.mEndClickIcon.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(mViewHolder.mTitleText.getText().toString()).isEqualTo(TEST_SESSION_NAME);
+ assertThat(mViewHolder.mSeekBar.getVolume()).isEqualTo(TEST_CURRENT_VOLUME);
+ }
+
+ @EnableFlags(Flags.FLAG_ENABLE_OUTPUT_SWITCHER_SESSION_GROUPING)
+ @Test
+ public void multipleSelectedDevices_verifyCollapsedView() {
+ initializeSession();
+
+ mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter
+ .onCreateViewHolder(
+ new LinearLayout(mContext), MediaItem.MediaItemType.TYPE_DEVICE);
+ mMediaOutputAdapter.onBindViewHolder(mViewHolder, 1);
+
+ assertThat(mViewHolder.mItemLayout.getVisibility()).isEqualTo(View.GONE);
+ assertThat(mViewHolder.mEndTouchArea.getVisibility()).isEqualTo(View.GONE);
+ }
+
+ @EnableFlags(Flags.FLAG_ENABLE_OUTPUT_SWITCHER_SESSION_GROUPING)
+ @Test
+ public void multipleSelectedDevices_expandIconClicked_verifyInitialView() {
+ initializeSession();
+ mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter
+ .onCreateViewHolder(
+ new LinearLayout(mContext), MediaItem.MediaItemType.TYPE_DEVICE);
+ mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0);
+
+ mViewHolder.mEndTouchArea.performClick();
+ mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter
+ .onCreateViewHolder(
+ new LinearLayout(mContext), MediaItem.MediaItemType.TYPE_DEVICE);
+ mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0);
+
+ assertThat(mViewHolder.mSeekBar.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(mViewHolder.mTitleText.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(mViewHolder.mCheckBox.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(mViewHolder.mEndTouchArea.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(mViewHolder.mEndClickIcon.getVisibility()).isEqualTo(View.GONE);
+ assertThat(mViewHolder.mTitleText.getText().toString()).isEqualTo(TEST_DEVICE_NAME_1);
+ }
+
+ @EnableFlags(Flags.FLAG_ENABLE_OUTPUT_SWITCHER_SESSION_GROUPING)
+ @Test
+ public void multipleSelectedDevices_expandIconClicked_verifyCollapsedView() {
+ initializeSession();
+ mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter
+ .onCreateViewHolder(
+ new LinearLayout(mContext), MediaItem.MediaItemType.TYPE_DEVICE);
+ mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0);
+
+ mViewHolder.mEndTouchArea.performClick();
+ mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter
+ .onCreateViewHolder(
+ new LinearLayout(mContext), MediaItem.MediaItemType.TYPE_DEVICE);
+ mMediaOutputAdapter.onBindViewHolder(mViewHolder, 1);
+
+ assertThat(mViewHolder.mSeekBar.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(mViewHolder.mTitleText.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(mViewHolder.mCheckBox.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(mViewHolder.mEndTouchArea.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(mViewHolder.mEndClickIcon.getVisibility()).isEqualTo(View.GONE);
+ assertThat(mViewHolder.mTitleText.getText().toString()).isEqualTo(TEST_DEVICE_NAME_2);
+ }
+
+ @EnableFlags(Flags.FLAG_ENABLE_OUTPUT_SWITCHER_SESSION_GROUPING)
+ @Test
+ public void deviceCanNotBeDeselected_verifyView() {
+ List<MediaDevice> selectedDevices = new ArrayList<>();
+ selectedDevices.add(mMediaDevice1);
+ when(mMediaSwitchingController.getSelectableMediaDevice()).thenReturn(selectedDevices);
+ when(mMediaSwitchingController.getSelectedMediaDevice()).thenReturn(selectedDevices);
+ when(mMediaSwitchingController.getDeselectableMediaDevice()).thenReturn(new ArrayList<>());
+
+ mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter
+ .onCreateViewHolder(
+ new LinearLayout(mContext), MediaItem.MediaItemType.TYPE_DEVICE);
+ mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0);
+
+ assertThat(mViewHolder.mSeekBar.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(mViewHolder.mTitleText.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(mViewHolder.mCheckBox.getVisibility()).isEqualTo(View.GONE);
+ assertThat(mViewHolder.mEndTouchArea.getVisibility()).isEqualTo(View.GONE);
+ assertThat(mViewHolder.mEndClickIcon.getVisibility()).isEqualTo(View.GONE);
+ assertThat(mViewHolder.mTitleText.getText().toString()).isEqualTo(TEST_DEVICE_NAME_1);
+ }
+
+ private void initializeSession() {
+ when(mMediaSwitchingController.getSessionVolumeMax()).thenReturn(TEST_MAX_VOLUME);
+ when(mMediaSwitchingController.getSessionVolume()).thenReturn(TEST_CURRENT_VOLUME);
+ when(mMediaSwitchingController.getSessionName()).thenReturn(TEST_SESSION_NAME);
+
+ List<MediaDevice> selectedDevices = new ArrayList<>();
+ selectedDevices.add(mMediaDevice1);
+ selectedDevices.add(mMediaDevice2);
+ when(mMediaSwitchingController.getSelectableMediaDevice()).thenReturn(selectedDevices);
+ when(mMediaSwitchingController.getSelectedMediaDevice()).thenReturn(selectedDevices);
+ when(mMediaSwitchingController.getDeselectableMediaDevice()).thenReturn(selectedDevices);
+
+ mMediaOutputAdapter.updateItems();
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/mediaprojection/data/repository/MediaProjectionManagerRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/mediaprojection/data/repository/MediaProjectionManagerRepositoryTest.kt
index ff00bfb5..63942072 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/mediaprojection/data/repository/MediaProjectionManagerRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/mediaprojection/data/repository/MediaProjectionManagerRepositoryTest.kt
@@ -17,6 +17,7 @@
package com.android.systemui.mediaprojection.data.repository
import android.hardware.display.displayManager
+import android.media.projection.MediaProjectionEvent
import android.media.projection.MediaProjectionInfo
import android.media.projection.StopReason
import android.os.Binder
@@ -32,8 +33,11 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.collectLastValue
+import com.android.systemui.kosmos.runTest
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
import com.android.systemui.log.logcatLogBuffer
import com.android.systemui.mediaprojection.data.model.MediaProjectionState
import com.android.systemui.mediaprojection.taskswitcher.FakeActivityTaskManager.Companion.createTask
@@ -42,7 +46,7 @@
import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeTasksRepository
import com.android.systemui.mediaprojection.taskswitcher.fakeActivityTaskManager
import com.android.systemui.mediaprojection.taskswitcher.fakeMediaProjectionManager
-import com.android.systemui.mediaprojection.taskswitcher.taskSwitcherKosmos
+import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.test.runTest
import org.junit.Test
@@ -55,7 +59,7 @@
@SmallTest
class MediaProjectionManagerRepositoryTest : SysuiTestCase() {
- private val kosmos = taskSwitcherKosmos()
+ private val kosmos = testKosmos().useUnconfinedTestDispatcher()
private val testScope = kosmos.testScope
private val fakeMediaProjectionManager = kosmos.fakeMediaProjectionManager
@@ -345,4 +349,40 @@
verify(fakeMediaProjectionManager.mediaProjectionManager)
.stopActiveProjection(StopReason.STOP_QS_TILE)
}
+
+ @Test
+ @EnableFlags(com.android.media.projection.flags.Flags.FLAG_SHOW_STOP_DIALOG_POST_CALL_END)
+ fun projectionStartedDuringCallAndActivePostCallEvent_flagEnabled_emitsUnit() =
+ kosmos.runTest {
+ val projectionStartedDuringCallAndActivePostCallEvent by
+ collectLastValue(repo.projectionStartedDuringCallAndActivePostCallEvent)
+
+ fakeMediaProjectionManager.dispatchEvent(
+ PROJECTION_STARTED_DURING_CALL_AND_ACTIVE_POST_CALL_EVENT
+ )
+
+ assertThat(projectionStartedDuringCallAndActivePostCallEvent).isEqualTo(Unit)
+ }
+
+ @Test
+ @DisableFlags(com.android.media.projection.flags.Flags.FLAG_SHOW_STOP_DIALOG_POST_CALL_END)
+ fun projectionStartedDuringCallAndActivePostCallEvent_flagDisabled_doesNotEmit() =
+ testScope.runTest {
+ val projectionStartedDuringCallAndActivePostCallEvent by
+ collectLastValue(repo.projectionStartedDuringCallAndActivePostCallEvent)
+
+ fakeMediaProjectionManager.dispatchEvent(
+ PROJECTION_STARTED_DURING_CALL_AND_ACTIVE_POST_CALL_EVENT
+ )
+
+ assertThat(projectionStartedDuringCallAndActivePostCallEvent).isNull()
+ }
+
+ companion object {
+ private val PROJECTION_STARTED_DURING_CALL_AND_ACTIVE_POST_CALL_EVENT =
+ MediaProjectionEvent(
+ MediaProjectionEvent.PROJECTION_STARTED_DURING_CALL_AND_ACTIVE_POST_CALL,
+ /* timestampMillis= */ 100L,
+ )
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/CommandQueueTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/CommandQueueTest.java
index 2020d0d..3d31787 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/CommandQueueTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/CommandQueueTest.java
@@ -460,17 +460,17 @@
}
@Test
- public void testOnDisplayReady() {
- mCommandQueue.onDisplayReady(DEFAULT_DISPLAY);
+ public void testonDisplayAddSystemDecorations() {
+ mCommandQueue.onDisplayAddSystemDecorations(DEFAULT_DISPLAY);
waitForIdleSync();
- verify(mCallbacks).onDisplayReady(eq(DEFAULT_DISPLAY));
+ verify(mCallbacks).onDisplayAddSystemDecorations(eq(DEFAULT_DISPLAY));
}
@Test
- public void testOnDisplayReadyForSecondaryDisplay() {
- mCommandQueue.onDisplayReady(SECONDARY_DISPLAY);
+ public void testonDisplayAddSystemDecorationsForSecondaryDisplay() {
+ mCommandQueue.onDisplayAddSystemDecorations(SECONDARY_DISPLAY);
waitForIdleSync();
- verify(mCallbacks).onDisplayReady(eq(SECONDARY_DISPLAY));
+ verify(mCallbacks).onDisplayAddSystemDecorations(eq(SECONDARY_DISPLAY));
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt
index 0a05649..a79f408 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt
@@ -45,6 +45,7 @@
import com.android.systemui.window.domain.interactor.WindowRootViewBlurInteractor
import com.android.wm.shell.appzoomout.AppZoomOut
import com.google.common.truth.Truth.assertThat
+import java.util.Optional
import java.util.function.Consumer
import org.junit.Before
import org.junit.Rule
@@ -66,7 +67,6 @@
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when`
import org.mockito.junit.MockitoJUnit
-import java.util.Optional
@RunWith(AndroidJUnit4::class)
@RunWithLooper
@@ -150,24 +150,23 @@
@Test
fun setupListeners() {
- verify(dumpManager).registerCriticalDumpable(
- anyString(), eq(notificationShadeDepthController)
- )
+ verify(dumpManager)
+ .registerCriticalDumpable(anyString(), eq(notificationShadeDepthController))
}
@Test
fun onPanelExpansionChanged_apliesBlur_ifShade() {
notificationShadeDepthController.onPanelExpansionChanged(
- ShadeExpansionChangeEvent(
- fraction = 1f, expanded = true, tracking = false))
+ ShadeExpansionChangeEvent(fraction = 1f, expanded = true, tracking = false)
+ )
verify(shadeAnimation).animateTo(eq(maxBlur))
}
@Test
fun onPanelExpansionChanged_animatesBlurIn_ifShade() {
notificationShadeDepthController.onPanelExpansionChanged(
- ShadeExpansionChangeEvent(
- fraction = 0.01f, expanded = false, tracking = false))
+ ShadeExpansionChangeEvent(fraction = 0.01f, expanded = false, tracking = false)
+ )
verify(shadeAnimation).animateTo(eq(maxBlur))
}
@@ -176,27 +175,27 @@
onPanelExpansionChanged_animatesBlurIn_ifShade()
clearInvocations(shadeAnimation)
notificationShadeDepthController.onPanelExpansionChanged(
- ShadeExpansionChangeEvent(
- fraction = 0f, expanded = false, tracking = false))
+ ShadeExpansionChangeEvent(fraction = 0f, expanded = false, tracking = false)
+ )
verify(shadeAnimation).animateTo(eq(0))
}
@Test
fun onPanelExpansionChanged_animatesBlurOut_ifFlick() {
- val event =
- ShadeExpansionChangeEvent(
- fraction = 1f, expanded = true, tracking = false)
+ val event = ShadeExpansionChangeEvent(fraction = 1f, expanded = true, tracking = false)
onPanelExpansionChanged_apliesBlur_ifShade()
clearInvocations(shadeAnimation)
notificationShadeDepthController.onPanelExpansionChanged(event)
verify(shadeAnimation, never()).animateTo(anyInt())
notificationShadeDepthController.onPanelExpansionChanged(
- event.copy(fraction = 0.9f, tracking = true))
+ event.copy(fraction = 0.9f, tracking = true)
+ )
verify(shadeAnimation, never()).animateTo(anyInt())
notificationShadeDepthController.onPanelExpansionChanged(
- event.copy(fraction = 0.8f, tracking = false))
+ event.copy(fraction = 0.8f, tracking = false)
+ )
verify(shadeAnimation).animateTo(eq(0))
}
@@ -205,16 +204,14 @@
onPanelExpansionChanged_animatesBlurOut_ifFlick()
clearInvocations(shadeAnimation)
notificationShadeDepthController.onPanelExpansionChanged(
- ShadeExpansionChangeEvent(
- fraction = 0.6f, expanded = true, tracking = true))
+ ShadeExpansionChangeEvent(fraction = 0.6f, expanded = true, tracking = true)
+ )
verify(shadeAnimation).animateTo(eq(maxBlur))
}
@Test
fun onPanelExpansionChanged_respectsMinPanelPullDownFraction() {
- val event =
- ShadeExpansionChangeEvent(
- fraction = 0.5f, expanded = true, tracking = true)
+ val event = ShadeExpansionChangeEvent(fraction = 0.5f, expanded = true, tracking = true)
notificationShadeDepthController.panelPullDownMinFraction = 0.5f
notificationShadeDepthController.onPanelExpansionChanged(event)
assertThat(notificationShadeDepthController.shadeExpansion).isEqualTo(0f)
@@ -241,8 +238,8 @@
statusBarState = StatusBarState.KEYGUARD
notificationShadeDepthController.qsPanelExpansion = 1f
notificationShadeDepthController.onPanelExpansionChanged(
- ShadeExpansionChangeEvent(
- fraction = 1f, expanded = true, tracking = false))
+ ShadeExpansionChangeEvent(fraction = 1f, expanded = true, tracking = false)
+ )
notificationShadeDepthController.updateBlurCallback.doFrame(0)
verify(blurUtils).applyBlur(any(), eq(maxBlur), eq(false))
}
@@ -252,8 +249,8 @@
statusBarState = StatusBarState.KEYGUARD
notificationShadeDepthController.qsPanelExpansion = 0.25f
notificationShadeDepthController.onPanelExpansionChanged(
- ShadeExpansionChangeEvent(
- fraction = 1f, expanded = true, tracking = false))
+ ShadeExpansionChangeEvent(fraction = 1f, expanded = true, tracking = false)
+ )
notificationShadeDepthController.updateBlurCallback.doFrame(0)
verify(wallpaperController)
.setNotificationShadeZoom(eq(ShadeInterpolation.getNotificationScrimAlpha(0.25f)))
@@ -264,8 +261,8 @@
enableSplitShade()
notificationShadeDepthController.onPanelExpansionChanged(
- ShadeExpansionChangeEvent(
- fraction = 1f, expanded = true, tracking = false))
+ ShadeExpansionChangeEvent(fraction = 1f, expanded = true, tracking = false)
+ )
notificationShadeDepthController.updateBlurCallback.doFrame(0)
verify(wallpaperController).setNotificationShadeZoom(0f)
@@ -276,8 +273,8 @@
disableSplitShade()
notificationShadeDepthController.onPanelExpansionChanged(
- ShadeExpansionChangeEvent(
- fraction = 1f, expanded = true, tracking = false))
+ ShadeExpansionChangeEvent(fraction = 1f, expanded = true, tracking = false)
+ )
notificationShadeDepthController.updateBlurCallback.doFrame(0)
verify(wallpaperController).setNotificationShadeZoom(floatThat { it > 0 })
@@ -354,8 +351,8 @@
@Test
fun updateBlurCallback_setsBlur_whenExpanded() {
notificationShadeDepthController.onPanelExpansionChanged(
- ShadeExpansionChangeEvent(
- fraction = 1f, expanded = true, tracking = false))
+ ShadeExpansionChangeEvent(fraction = 1f, expanded = true, tracking = false)
+ )
`when`(shadeAnimation.radius).thenReturn(maxBlur.toFloat())
notificationShadeDepthController.updateBlurCallback.doFrame(0)
verify(blurUtils).applyBlur(any(), eq(maxBlur), eq(false))
@@ -364,8 +361,8 @@
@Test
fun updateBlurCallback_ignoreShadeBlurUntilHidden_overridesZoom() {
notificationShadeDepthController.onPanelExpansionChanged(
- ShadeExpansionChangeEvent(
- fraction = 1f, expanded = true, tracking = false))
+ ShadeExpansionChangeEvent(fraction = 1f, expanded = true, tracking = false)
+ )
`when`(shadeAnimation.radius).thenReturn(maxBlur.toFloat())
notificationShadeDepthController.blursDisabledForAppLaunch = true
notificationShadeDepthController.updateBlurCallback.doFrame(0)
@@ -373,7 +370,7 @@
}
@Test
- @DisableFlags(Flags.FLAG_BOUNCER_UI_REVAMP)
+ @DisableFlags(Flags.FLAG_BOUNCER_UI_REVAMP, Flags.FLAG_GLANCEABLE_HUB_BLURRED_BACKGROUND)
fun ignoreShadeBlurUntilHidden_schedulesFrame() {
notificationShadeDepthController.blursDisabledForAppLaunch = true
verify(blurUtils).prepareBlur(any(), anyInt())
@@ -391,8 +388,8 @@
@Test
fun ignoreBlurForUnlock_ignores() {
notificationShadeDepthController.onPanelExpansionChanged(
- ShadeExpansionChangeEvent(
- fraction = 1f, expanded = true, tracking = false))
+ ShadeExpansionChangeEvent(fraction = 1f, expanded = true, tracking = false)
+ )
`when`(shadeAnimation.radius).thenReturn(maxBlur.toFloat())
notificationShadeDepthController.blursDisabledForAppLaunch = false
@@ -408,8 +405,8 @@
@Test
fun ignoreBlurForUnlock_doesNotIgnore() {
notificationShadeDepthController.onPanelExpansionChanged(
- ShadeExpansionChangeEvent(
- fraction = 1f, expanded = true, tracking = false))
+ ShadeExpansionChangeEvent(fraction = 1f, expanded = true, tracking = false)
+ )
`when`(shadeAnimation.radius).thenReturn(maxBlur.toFloat())
notificationShadeDepthController.blursDisabledForAppLaunch = false
@@ -435,14 +432,14 @@
}
@Test
- @DisableFlags(Flags.FLAG_BOUNCER_UI_REVAMP)
+ @DisableFlags(Flags.FLAG_BOUNCER_UI_REVAMP, Flags.FLAG_GLANCEABLE_HUB_BLURRED_BACKGROUND)
fun brightnessMirror_hidesShadeBlur() {
// Brightness mirror is fully visible
`when`(brightnessSpring.ratio).thenReturn(1f)
// And shade is blurred
notificationShadeDepthController.onPanelExpansionChanged(
- ShadeExpansionChangeEvent(
- fraction = 1f, expanded = true, tracking = false))
+ ShadeExpansionChangeEvent(fraction = 1f, expanded = true, tracking = false)
+ )
`when`(shadeAnimation.radius).thenReturn(maxBlur.toFloat())
notificationShadeDepthController.updateBlurCallback.doFrame(0)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelTest.kt
index 75262a4..03c0751 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelTest.kt
@@ -24,9 +24,9 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.Expandable
+import com.android.systemui.common.shared.model.ContentDescription.Companion.loadContentDescription
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.testScope
import com.android.systemui.plugins.activityStarter
import com.android.systemui.res.R
@@ -47,6 +47,7 @@
import com.android.systemui.statusbar.phone.ongoingcall.data.repository.ongoingCallRepository
import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallModel
import com.android.systemui.statusbar.phone.ongoingcall.shared.model.inCallModel
+import com.android.systemui.testKosmos
import com.android.systemui.util.time.fakeSystemClock
import com.google.common.truth.Truth.assertThat
import kotlin.test.Test
@@ -60,7 +61,7 @@
@SmallTest
@RunWith(AndroidJUnit4::class)
class CallChipViewModelTest : SysuiTestCase() {
- private val kosmos = Kosmos()
+ private val kosmos = testKosmos()
private val notificationListRepository = kosmos.activeNotificationListRepository
private val testScope = kosmos.testScope
private val repo = kosmos.ongoingCallRepository
@@ -162,25 +163,34 @@
@Test
@DisableFlags(StatusBarConnectedDisplays.FLAG_NAME)
- fun chip_zeroStartTime_cdFlagOff_iconIsNotifIcon() =
+ fun chip_zeroStartTime_cdFlagOff_iconIsNotifIcon_withContentDescription() =
testScope.runTest {
val latest by collectLastValue(underTest.chip)
val notifIcon = createStatusBarIconViewOrNull()
- repo.setOngoingCallState(inCallModel(startTimeMs = 0, notificationIcon = notifIcon))
+ repo.setOngoingCallState(
+ inCallModel(
+ startTimeMs = 0,
+ notificationIcon = notifIcon,
+ appName = "Fake app name",
+ )
+ )
assertThat((latest as OngoingActivityChipModel.Shown).icon)
.isInstanceOf(OngoingActivityChipModel.ChipIcon.StatusBarView::class.java)
val actualIcon =
- (((latest as OngoingActivityChipModel.Shown).icon)
- as OngoingActivityChipModel.ChipIcon.StatusBarView)
- .impl
- assertThat(actualIcon).isEqualTo(notifIcon)
+ (latest as OngoingActivityChipModel.Shown).icon
+ as OngoingActivityChipModel.ChipIcon.StatusBarView
+ assertThat(actualIcon.impl).isEqualTo(notifIcon)
+ assertThat(actualIcon.contentDescription.loadContentDescription(context))
+ .contains("Ongoing call")
+ assertThat(actualIcon.contentDescription.loadContentDescription(context))
+ .contains("Fake app name")
}
@Test
@EnableFlags(StatusBarConnectedDisplays.FLAG_NAME)
- fun chip_zeroStartTime_cdFlagOn_iconIsNotifKeyIcon() =
+ fun chip_zeroStartTime_cdFlagOn_iconIsNotifKeyIcon_withContentDescription() =
testScope.runTest {
val latest by collectLastValue(underTest.chip)
@@ -189,11 +199,22 @@
startTimeMs = 0,
notificationIcon = createStatusBarIconViewOrNull(),
notificationKey = "notifKey",
+ appName = "Fake app name",
)
)
assertThat((latest as OngoingActivityChipModel.Shown).icon)
- .isEqualTo(OngoingActivityChipModel.ChipIcon.StatusBarNotificationIcon("notifKey"))
+ .isInstanceOf(
+ OngoingActivityChipModel.ChipIcon.StatusBarNotificationIcon::class.java
+ )
+ val actualIcon =
+ (latest as OngoingActivityChipModel.Shown).icon
+ as OngoingActivityChipModel.ChipIcon.StatusBarNotificationIcon
+ assertThat(actualIcon.notificationKey).isEqualTo("notifKey")
+ assertThat(actualIcon.contentDescription.loadContentDescription(context))
+ .contains("Ongoing call")
+ assertThat(actualIcon.contentDescription.loadContentDescription(context))
+ .contains("Fake app name")
}
@Test
@@ -216,7 +237,7 @@
@Test
@EnableFlags(StatusBarConnectedDisplays.FLAG_NAME)
- fun chip_notifIconFlagOn_butNullNotifIcon_iconNotifKey() =
+ fun chip_notifIconFlagOn_butNullNotifIcon_cdFlagOn_iconIsNotifKeyIcon_withContentDescription() =
testScope.runTest {
val latest by collectLastValue(underTest.chip)
@@ -225,11 +246,22 @@
startTimeMs = 1000,
notificationIcon = null,
notificationKey = "notifKey",
+ appName = "Fake app name",
)
)
assertThat((latest as OngoingActivityChipModel.Shown).icon)
- .isEqualTo(OngoingActivityChipModel.ChipIcon.StatusBarNotificationIcon("notifKey"))
+ .isInstanceOf(
+ OngoingActivityChipModel.ChipIcon.StatusBarNotificationIcon::class.java
+ )
+ val actualIcon =
+ (latest as OngoingActivityChipModel.Shown).icon
+ as OngoingActivityChipModel.ChipIcon.StatusBarNotificationIcon
+ assertThat(actualIcon.notificationKey).isEqualTo("notifKey")
+ assertThat(actualIcon.contentDescription.loadContentDescription(context))
+ .contains("Ongoing call")
+ assertThat(actualIcon.contentDescription.loadContentDescription(context))
+ .contains("Fake app name")
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/mediaprojection/domain/interactor/MediaProjectionChipInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/mediaprojection/domain/interactor/MediaProjectionChipInteractorTest.kt
index dea3d1f..0dc2759 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/mediaprojection/domain/interactor/MediaProjectionChipInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/mediaprojection/domain/interactor/MediaProjectionChipInteractorTest.kt
@@ -22,21 +22,26 @@
import android.content.pm.PackageManager
import android.content.pm.ResolveInfo
import android.platform.test.annotations.EnableFlags
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.Flags.FLAG_STATUS_BAR_SHOW_AUDIO_ONLY_PROJECTION_CHIP
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.testCase
+import com.android.systemui.kosmos.collectLastValue
+import com.android.systemui.kosmos.runTest
import com.android.systemui.kosmos.testScope
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
import com.android.systemui.mediaprojection.data.model.MediaProjectionState
import com.android.systemui.mediaprojection.data.repository.fakeMediaProjectionRepository
import com.android.systemui.mediaprojection.taskswitcher.FakeActivityTaskManager.Companion.createTask
import com.android.systemui.statusbar.chips.mediaprojection.domain.model.ProjectionChipModel
+import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlin.test.Test
import kotlinx.coroutines.test.runTest
import org.junit.Before
+import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mockito.doAnswer
import org.mockito.kotlin.any
@@ -44,8 +49,9 @@
import org.mockito.kotlin.whenever
@SmallTest
+@RunWith(AndroidJUnit4::class)
class MediaProjectionChipInteractorTest : SysuiTestCase() {
- private val kosmos = Kosmos().also { it.testCase = this }
+ private val kosmos = testKosmos().useUnconfinedTestDispatcher()
private val testScope = kosmos.testScope
private val mediaProjectionRepo = kosmos.fakeMediaProjectionRepository
@@ -57,6 +63,26 @@
private val underTest = kosmos.mediaProjectionChipInteractor
@Test
+ fun projectionStartedDuringCallAndActivePostCallEvent_eventEmitted_isUnit() =
+ kosmos.runTest {
+ val latest by
+ collectLastValue(underTest.projectionStartedDuringCallAndActivePostCallEvent)
+
+ fakeMediaProjectionRepository.emitProjectionStartedDuringCallAndActivePostCallEvent()
+
+ assertThat(latest).isEqualTo(Unit)
+ }
+
+ @Test
+ fun projectionStartedDuringCallAndActivePostCallEvent_noEventEmitted_isNull() =
+ kosmos.runTest {
+ val latest by
+ collectLastValue(underTest.projectionStartedDuringCallAndActivePostCallEvent)
+
+ assertThat(latest).isNull()
+ }
+
+ @Test
fun projection_notProjectingState_isNotProjecting() =
testScope.runTest {
val latest by collectLastValue(underTest.projection)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/domain/interactor/SingleNotificationChipInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/domain/interactor/SingleNotificationChipInteractorTest.kt
index fe15eac..05f2585 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/domain/interactor/SingleNotificationChipInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/domain/interactor/SingleNotificationChipInteractorTest.kt
@@ -49,6 +49,7 @@
val startingNotif =
activeNotificationModel(
key = "notif1",
+ appName = "Fake Name",
statusBarChipIcon = icon,
promotedContent = PROMOTED_CONTENT,
)
@@ -58,6 +59,7 @@
val latest by collectLastValue(underTest.notificationChip)
assertThat(latest!!.key).isEqualTo("notif1")
+ assertThat(latest!!.appName).isEqualTo("Fake Name")
assertThat(latest!!.statusBarChipIconView).isEqualTo(icon)
assertThat(latest!!.promotedContent).isEqualTo(PROMOTED_CONTENT)
}
@@ -70,6 +72,7 @@
factory.create(
activeNotificationModel(
key = "notif1",
+ appName = "Fake Name",
statusBarChipIcon = originalIconView,
promotedContent = PROMOTED_CONTENT,
),
@@ -82,12 +85,14 @@
underTest.setNotification(
activeNotificationModel(
key = "notif1",
+ appName = "New Name",
statusBarChipIcon = newIconView,
promotedContent = PROMOTED_CONTENT,
)
)
assertThat(latest!!.key).isEqualTo("notif1")
+ assertThat(latest!!.appName).isEqualTo("New Name")
assertThat(latest!!.statusBarChipIconView).isEqualTo(newIconView)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt
index 942e6554..1f77ddc 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.chips.notification.ui.viewmodel
+import android.content.Context
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import android.view.View
@@ -23,6 +24,7 @@
import androidx.test.filters.SmallTest
import com.android.systemui.Flags.FLAG_PROMOTE_NOTIFICATIONS_AUTOMATICALLY
import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.shared.model.ContentDescription.Companion.loadContentDescription
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.kosmos.collectLastValue
@@ -125,7 +127,7 @@
@Test
@DisableFlags(FLAG_PROMOTE_NOTIFICATIONS_AUTOMATICALLY)
- fun chips_onePromotedNotif_statusBarIconViewMatches() =
+ fun chips_onePromotedNotif_connectedDisplaysFlagDisabled_statusBarIconViewMatches() =
kosmos.runTest {
val latest by collectLastValue(underTest.chips)
@@ -134,6 +136,7 @@
listOf(
activeNotificationModel(
key = "notif",
+ appName = "Fake App Name",
statusBarChipIcon = icon,
promotedContent = PromotedNotificationContentModel.Builder("notif").build(),
)
@@ -142,7 +145,13 @@
assertThat(latest).hasSize(1)
val chip = latest!![0]
- assertIsNotifChip(chip, icon, "notif")
+ assertIsNotifChip(
+ chip,
+ context,
+ icon,
+ expectedNotificationKey = "notif",
+ expectedContentDescriptionSubstrings = listOf("Ongoing", "Fake App Name"),
+ )
}
@Test
@@ -157,6 +166,7 @@
listOf(
activeNotificationModel(
key = notifKey,
+ appName = "Fake App Name",
statusBarChipIcon = null,
promotedContent = PromotedNotificationContentModel.Builder(notifKey).build(),
)
@@ -165,9 +175,13 @@
assertThat(latest).hasSize(1)
val chip = latest!![0]
- assertThat(chip).isInstanceOf(OngoingActivityChipModel.Shown::class.java)
- assertThat(chip.icon)
- .isEqualTo(OngoingActivityChipModel.ChipIcon.StatusBarNotificationIcon(notifKey))
+ assertIsNotifChip(
+ chip,
+ context,
+ expectedIcon = null,
+ expectedNotificationKey = "notif",
+ expectedContentDescriptionSubstrings = listOf("Ongoing", "Fake App Name"),
+ )
}
@Test
@@ -230,8 +244,8 @@
)
assertThat(latest).hasSize(2)
- assertIsNotifChip(latest!![0], firstIcon, "notif1")
- assertIsNotifChip(latest!![1], secondIcon, "notif2")
+ assertIsNotifChip(latest!![0], context, firstIcon, "notif1")
+ assertIsNotifChip(latest!![1], context, secondIcon, "notif2")
}
@Test
@@ -590,7 +604,7 @@
// THEN the "notif" chip keeps showing time
val chip = latest!![0]
assertThat(chip).isInstanceOf(OngoingActivityChipModel.Shown.ShortTimeDelta::class.java)
- assertIsNotifChip(chip, icon, "notif")
+ assertIsNotifChip(chip, context, icon, "notif")
}
@Test
@@ -705,24 +719,41 @@
companion object {
fun assertIsNotifChip(
latest: OngoingActivityChipModel?,
+ context: Context,
expectedIcon: StatusBarIconView?,
- notificationKey: String,
+ expectedNotificationKey: String,
+ expectedContentDescriptionSubstrings: List<String> = emptyList(),
) {
val shown = latest as OngoingActivityChipModel.Shown
if (StatusBarConnectedDisplays.isEnabled) {
assertThat(shown.icon)
- .isEqualTo(
- OngoingActivityChipModel.ChipIcon.StatusBarNotificationIcon(notificationKey)
+ .isInstanceOf(
+ OngoingActivityChipModel.ChipIcon.StatusBarNotificationIcon::class.java
)
+ val icon = shown.icon as OngoingActivityChipModel.ChipIcon.StatusBarNotificationIcon
+
+ assertThat(icon.notificationKey).isEqualTo(expectedNotificationKey)
+ expectedContentDescriptionSubstrings.forEach {
+ assertThat(icon.contentDescription.loadContentDescription(context)).contains(it)
+ }
} else {
- assertThat(latest.icon)
- .isEqualTo(OngoingActivityChipModel.ChipIcon.StatusBarView(expectedIcon!!))
+ assertThat(shown.icon)
+ .isInstanceOf(OngoingActivityChipModel.ChipIcon.StatusBarView::class.java)
+ val icon = shown.icon as OngoingActivityChipModel.ChipIcon.StatusBarView
+ assertThat(icon.impl).isEqualTo(expectedIcon!!)
+ expectedContentDescriptionSubstrings.forEach {
+ assertThat(icon.contentDescription.loadContentDescription(context)).contains(it)
+ }
}
}
fun assertIsNotifKey(latest: OngoingActivityChipModel?, expectedKey: String) {
- assertThat((latest as OngoingActivityChipModel.Shown).icon)
- .isEqualTo(OngoingActivityChipModel.ChipIcon.StatusBarNotificationIcon(expectedKey))
+ assertThat(
+ ((latest as OngoingActivityChipModel.Shown).icon
+ as OngoingActivityChipModel.ChipIcon.StatusBarNotificationIcon)
+ .notificationKey
+ )
+ .isEqualTo(expectedKey)
}
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModelTest.kt
index 36fc5aa..5a66888 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModelTest.kt
@@ -31,9 +31,10 @@
import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.testCase
+import com.android.systemui.kosmos.collectLastValue
+import com.android.systemui.kosmos.runTest
import com.android.systemui.kosmos.testScope
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
import com.android.systemui.mediaprojection.data.model.MediaProjectionState
import com.android.systemui.mediaprojection.data.repository.fakeMediaProjectionRepository
import com.android.systemui.mediaprojection.taskswitcher.FakeActivityTaskManager.Companion.createTask
@@ -41,6 +42,7 @@
import com.android.systemui.statusbar.chips.mediaprojection.domain.interactor.MediaProjectionChipInteractorTest.Companion.CAST_TO_OTHER_DEVICES_PACKAGE
import com.android.systemui.statusbar.chips.mediaprojection.domain.interactor.MediaProjectionChipInteractorTest.Companion.NORMAL_PACKAGE
import com.android.systemui.statusbar.chips.mediaprojection.domain.interactor.MediaProjectionChipInteractorTest.Companion.setUpPackageManagerForMediaProjection
+import com.android.systemui.statusbar.chips.mediaprojection.domain.model.MediaProjectionStopDialogModel
import com.android.systemui.statusbar.chips.sharetoapp.ui.view.EndGenericShareToAppDialogDelegate
import com.android.systemui.statusbar.chips.sharetoapp.ui.view.EndShareScreenToAppDialogDelegate
import com.android.systemui.statusbar.chips.ui.model.ColorsModel
@@ -51,6 +53,7 @@
import com.android.systemui.statusbar.phone.SystemUIDialog
import com.android.systemui.statusbar.phone.mockSystemUIDialogFactory
import com.android.systemui.statusbar.phone.ongoingcall.StatusBarChipsModernization
+import com.android.systemui.testKosmos
import com.android.systemui.util.time.fakeSystemClock
import com.google.common.truth.Truth.assertThat
import kotlin.test.Test
@@ -58,6 +61,7 @@
import org.junit.Before
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.Mockito.times
import org.mockito.kotlin.any
import org.mockito.kotlin.argumentCaptor
import org.mockito.kotlin.eq
@@ -68,7 +72,7 @@
@SmallTest
@RunWith(AndroidJUnit4::class)
class ShareToAppChipViewModelTest : SysuiTestCase() {
- private val kosmos = Kosmos().also { it.testCase = this }
+ private val kosmos = testKosmos().useUnconfinedTestDispatcher()
private val testScope = kosmos.testScope
private val mediaProjectionRepo = kosmos.fakeMediaProjectionRepository
private val systemClock = kosmos.fakeSystemClock
@@ -89,9 +93,11 @@
mock<Expandable>().apply { whenever(dialogTransitionController(any())).thenReturn(mock()) }
private val underTest = kosmos.shareToAppChipViewModel
+ private val mockDialog = mock<SystemUIDialog>()
@Before
fun setUp() {
+ underTest.start()
setUpPackageManagerForMediaProjection(kosmos)
whenever(kosmos.mockSystemUIDialogFactory.create(any<EndShareScreenToAppDialogDelegate>()))
@@ -101,6 +107,196 @@
}
@Test
+ @EnableFlags(com.android.media.projection.flags.Flags.FLAG_SHOW_STOP_DIALOG_POST_CALL_END)
+ fun chip_flagEnabled_projectionStartedDuringCallAndActivePostCallEventEmitted_chipHidden() =
+ kosmos.runTest {
+ val latestChip by collectLastValue(underTest.chip)
+
+ // Set mediaProjectionState to Projecting
+ mediaProjectionRepo.mediaProjectionState.value =
+ MediaProjectionState.Projecting.EntireScreen(NORMAL_PACKAGE)
+
+ // Verify the chip is initially shown
+ assertThat(latestChip).isInstanceOf(OngoingActivityChipModel.Shown::class.java)
+
+ fakeMediaProjectionRepository.emitProjectionStartedDuringCallAndActivePostCallEvent()
+
+ // Verify the chip is hidden
+ assertThat(latestChip).isInstanceOf(OngoingActivityChipModel.Hidden::class.java)
+ }
+
+ @Test
+ @DisableFlags(com.android.media.projection.flags.Flags.FLAG_SHOW_STOP_DIALOG_POST_CALL_END)
+ fun chip_flagDisabled_projectionStartedDuringCallAndActivePostCallEventEmitted_chipRemainsVisible() =
+ kosmos.runTest {
+ val latestChip by collectLastValue(underTest.chip)
+
+ // Set mediaProjectionState to Projecting
+ mediaProjectionRepo.mediaProjectionState.value =
+ MediaProjectionState.Projecting.EntireScreen(NORMAL_PACKAGE)
+
+ // Verify the chip is initially shown
+ assertThat(latestChip).isInstanceOf(OngoingActivityChipModel.Shown::class.java)
+
+ fakeMediaProjectionRepository.emitProjectionStartedDuringCallAndActivePostCallEvent()
+
+ // Chip is still shown
+ assertThat(latestChip).isInstanceOf(OngoingActivityChipModel.Shown::class.java)
+ }
+
+ @Test
+ @EnableFlags(com.android.media.projection.flags.Flags.FLAG_SHOW_STOP_DIALOG_POST_CALL_END)
+ fun stopDialog_flagEnabled_initialState_isHidden() =
+ kosmos.runTest {
+ val latest by collectLastValue(underTest.stopDialogToShow)
+
+ assertThat(latest).isEqualTo(MediaProjectionStopDialogModel.Hidden)
+ }
+
+ @Test
+ @DisableFlags(com.android.media.projection.flags.Flags.FLAG_SHOW_STOP_DIALOG_POST_CALL_END)
+ fun stopDialog_flagDisabled_projectionStartedDuringCallAndActivePostCallEventEmitted_dialogRemainsHidden() =
+ kosmos.runTest {
+ val latestStopDialogModel by collectLastValue(underTest.stopDialogToShow)
+
+ // Set mediaProjectionRepo state to Projecting
+ mediaProjectionRepo.mediaProjectionState.value =
+ MediaProjectionState.Projecting.EntireScreen(NORMAL_PACKAGE)
+
+ fakeMediaProjectionRepository.emitProjectionStartedDuringCallAndActivePostCallEvent()
+
+ // Verify that no dialog is shown
+ assertThat(latestStopDialogModel).isEqualTo(MediaProjectionStopDialogModel.Hidden)
+ }
+
+ @Test
+ @EnableFlags(com.android.media.projection.flags.Flags.FLAG_SHOW_STOP_DIALOG_POST_CALL_END)
+ fun stopDialog_notProjectingState_flagEnabled_remainsHidden() =
+ kosmos.runTest {
+ val latest by collectLastValue(underTest.stopDialogToShow)
+
+ // Set the state to not projecting
+ mediaProjectionRepo.mediaProjectionState.value = MediaProjectionState.NotProjecting
+
+ fakeMediaProjectionRepository.emitProjectionStartedDuringCallAndActivePostCallEvent()
+
+ // Verify that the dialog remains hidden
+ assertThat(latest).isEqualTo(MediaProjectionStopDialogModel.Hidden)
+ }
+
+ @Test
+ @EnableFlags(
+ com.android.media.projection.flags.Flags.FLAG_SHOW_STOP_DIALOG_POST_CALL_END,
+ FLAG_STATUS_BAR_SHOW_AUDIO_ONLY_PROJECTION_CHIP,
+ )
+ fun stopDialog_projectingAudio_flagEnabled_eventEmitted_showsGenericStopDialog() =
+ kosmos.runTest {
+ val latest by collectLastValue(underTest.stopDialogToShow)
+
+ // Set the state to projecting audio
+ mediaProjectionRepo.mediaProjectionState.value =
+ MediaProjectionState.Projecting.NoScreen(NORMAL_PACKAGE)
+
+ fakeMediaProjectionRepository.emitProjectionStartedDuringCallAndActivePostCallEvent()
+
+ // Verify that the generic dialog is shown
+ assertThat(latest).isInstanceOf(MediaProjectionStopDialogModel.Shown::class.java)
+ val dialogDelegate = (latest as MediaProjectionStopDialogModel.Shown).dialogDelegate
+ assertThat(dialogDelegate).isInstanceOf(EndGenericShareToAppDialogDelegate::class.java)
+ }
+
+ @Test
+ @EnableFlags(com.android.media.projection.flags.Flags.FLAG_SHOW_STOP_DIALOG_POST_CALL_END)
+ fun stopDialog_projectingEntireScreen_flagEnabled_eventEmitted_showsShareScreenToAppStopDialog() =
+ kosmos.runTest {
+ val latest by collectLastValue(underTest.stopDialogToShow)
+
+ // Set the state to projecting the entire screen
+ mediaProjectionRepo.mediaProjectionState.value =
+ MediaProjectionState.Projecting.EntireScreen(NORMAL_PACKAGE)
+
+ assertThat(latest).isInstanceOf(MediaProjectionStopDialogModel.Hidden::class.java)
+
+ fakeMediaProjectionRepository.emitProjectionStartedDuringCallAndActivePostCallEvent()
+
+ // Verify that the dialog is shown
+ assertThat(latest).isInstanceOf(MediaProjectionStopDialogModel.Shown::class.java)
+ val dialogDelegate = (latest as MediaProjectionStopDialogModel.Shown).dialogDelegate
+ assertThat(dialogDelegate).isInstanceOf(EndShareScreenToAppDialogDelegate::class.java)
+ }
+
+ @Test
+ @EnableFlags(com.android.media.projection.flags.Flags.FLAG_SHOW_STOP_DIALOG_POST_CALL_END)
+ fun stopDialog_projectingEntireScreen_eventEmitted_hasCancelBehaviour() =
+ kosmos.runTest {
+ val latestDialogModel by collectLastValue(underTest.stopDialogToShow)
+
+ mediaProjectionRepo.mediaProjectionState.value =
+ MediaProjectionState.Projecting.EntireScreen(NORMAL_PACKAGE)
+
+ fakeMediaProjectionRepository.emitProjectionStartedDuringCallAndActivePostCallEvent()
+
+ // Verify that the dialog is shown
+ assertThat(latestDialogModel)
+ .isInstanceOf(MediaProjectionStopDialogModel.Shown::class.java)
+
+ val dialogModel = latestDialogModel as MediaProjectionStopDialogModel.Shown
+
+ whenever(dialogModel.dialogDelegate.createDialog()).thenReturn(mockDialog)
+
+ dialogModel.createAndShowDialog()
+
+ // Verify dialog is shown
+ verify(mockDialog).show()
+
+ // Verify dialog is hidden when dialog is cancelled
+ argumentCaptor<DialogInterface.OnCancelListener>().apply {
+ verify(mockDialog).setOnCancelListener(capture())
+ firstValue.onCancel(mockDialog)
+ }
+ assertThat(underTest.stopDialogToShow.value)
+ .isEqualTo(MediaProjectionStopDialogModel.Hidden)
+
+ verify(mockDialog, times(1)).setOnCancelListener(any())
+ }
+
+ @Test
+ @EnableFlags(com.android.media.projection.flags.Flags.FLAG_SHOW_STOP_DIALOG_POST_CALL_END)
+ fun stopDialog_projectingEntireScreen_eventEmitted_hasDismissBehaviour() =
+ kosmos.runTest {
+ val latestDialogModel by collectLastValue(underTest.stopDialogToShow)
+
+ mediaProjectionRepo.mediaProjectionState.value =
+ MediaProjectionState.Projecting.EntireScreen(NORMAL_PACKAGE)
+
+ fakeMediaProjectionRepository.emitProjectionStartedDuringCallAndActivePostCallEvent()
+
+ // Verify that the dialog is shown
+ assertThat(latestDialogModel)
+ .isInstanceOf(MediaProjectionStopDialogModel.Shown::class.java)
+
+ val dialogModel = latestDialogModel as MediaProjectionStopDialogModel.Shown
+
+ whenever(dialogModel.dialogDelegate.createDialog()).thenReturn(mockDialog)
+
+ // Simulate showing the dialog
+ dialogModel.createAndShowDialog()
+
+ // Verify dialog is shown
+ verify(mockDialog).show()
+
+ // Verify dialog is hidden when dialog is dismissed
+ argumentCaptor<DialogInterface.OnDismissListener>().apply {
+ verify(mockDialog).setOnDismissListener(capture())
+ firstValue.onDismiss(mockDialog)
+ }
+ assertThat(underTest.stopDialogToShow.value)
+ .isEqualTo(MediaProjectionStopDialogModel.Hidden)
+
+ verify(mockDialog, times(1)).setOnDismissListener(any())
+ }
+
+ @Test
fun chip_notProjectingState_isHidden() =
testScope.runTest {
val latest by collectLastValue(underTest.chip)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt
index a4b6a84..7a33cbe 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt
@@ -289,7 +289,11 @@
fun primaryChip_screenRecordStoppedViaDialog_chipHiddenWithoutAnimation() =
testScope.runTest {
screenRecordState.value = ScreenRecordModel.Recording
- mediaProjectionState.value = MediaProjectionState.NotProjecting
+ mediaProjectionState.value =
+ MediaProjectionState.Projecting.EntireScreen(
+ NORMAL_PACKAGE,
+ hostDeviceName = "Recording Display",
+ )
callRepo.setOngoingCallState(OngoingCallModel.NoCall)
val latest by collectLastValue(underTest.primaryChip)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithNotifsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithNotifsViewModelTest.kt
index 28f3601..78103a9 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithNotifsViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithNotifsViewModelTest.kt
@@ -311,7 +311,7 @@
)
)
- assertIsNotifChip(latest!!.primary, icon, "notif")
+ assertIsNotifChip(latest!!.primary, context, icon, "notif")
assertThat(latest!!.secondary).isInstanceOf(OngoingActivityChipModel.Hidden::class.java)
}
@@ -339,8 +339,8 @@
)
)
- assertIsNotifChip(latest!!.primary, firstIcon, "firstNotif")
- assertIsNotifChip(latest!!.secondary, secondIcon, "secondNotif")
+ assertIsNotifChip(latest!!.primary, context, firstIcon, "firstNotif")
+ assertIsNotifChip(latest!!.secondary, context, secondIcon, "secondNotif")
}
@Test
@@ -374,8 +374,8 @@
)
)
- assertIsNotifChip(latest!!.primary, firstIcon, "firstNotif")
- assertIsNotifChip(latest!!.secondary, secondIcon, "secondNotif")
+ assertIsNotifChip(latest!!.primary, context, firstIcon, "firstNotif")
+ assertIsNotifChip(latest!!.secondary, context, secondIcon, "secondNotif")
}
@Test
@@ -407,7 +407,7 @@
)
assertIsCallChip(latest!!.primary, callNotificationKey)
- assertIsNotifChip(latest!!.secondary, firstIcon, "firstNotif")
+ assertIsNotifChip(latest!!.secondary, context, firstIcon, "firstNotif")
}
@Test
@@ -456,7 +456,7 @@
val latest by collectLastValue(underTest.primaryChip)
- assertIsNotifChip(latest, notifIcon, "notif")
+ assertIsNotifChip(latest, context, notifIcon, "notif")
// WHEN the higher priority call chip is added
callRepo.setOngoingCallState(
@@ -527,7 +527,7 @@
callRepo.setOngoingCallState(OngoingCallModel.NoCall)
// THEN the lower priority notif is used
- assertIsNotifChip(latest, notifIcon, "notif")
+ assertIsNotifChip(latest, context, notifIcon, "notif")
}
@Test
@@ -552,7 +552,7 @@
val latest by collectLastValue(underTest.chips)
- assertIsNotifChip(latest!!.primary, notifIcon, "notif")
+ assertIsNotifChip(latest!!.primary, context, notifIcon, "notif")
assertThat(latest!!.secondary).isInstanceOf(OngoingActivityChipModel.Hidden::class.java)
// WHEN the higher priority call chip is added
@@ -563,7 +563,7 @@
// THEN the higher priority call chip is used as primary and notif is demoted to
// secondary
assertIsCallChip(latest!!.primary, callNotificationKey)
- assertIsNotifChip(latest!!.secondary, notifIcon, "notif")
+ assertIsNotifChip(latest!!.secondary, context, notifIcon, "notif")
// WHEN the higher priority media projection chip is added
mediaProjectionState.value =
@@ -590,13 +590,13 @@
// THEN media projection and notif remain
assertIsShareToAppChip(latest!!.primary)
- assertIsNotifChip(latest!!.secondary, notifIcon, "notif")
+ assertIsNotifChip(latest!!.secondary, context, notifIcon, "notif")
// WHEN media projection is dropped
mediaProjectionState.value = MediaProjectionState.NotProjecting
// THEN notif is promoted to primary
- assertIsNotifChip(latest!!.primary, notifIcon, "notif")
+ assertIsNotifChip(latest!!.primary, context, notifIcon, "notif")
assertThat(latest!!.secondary).isInstanceOf(OngoingActivityChipModel.Hidden::class.java)
}
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 c06da4b..dc65a9e 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
@@ -18,6 +18,7 @@
import android.platform.test.annotations.EnableFlags
import android.view.Display
+import android.view.mockIWindowManager
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -31,11 +32,13 @@
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
+import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.never
import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@@ -51,75 +54,110 @@
private val fakeInitializerStore = kosmos.fakeStatusBarInitializerStore
private val fakePrivacyDotStore = kosmos.fakePrivacyDotWindowControllerStore
private val fakeLightBarStore = kosmos.fakeLightBarControllerStore
+ private val windowManager = kosmos.mockIWindowManager
+
// Lazy, so that @EnableFlags is set before initializer is instantiated.
private val underTest by lazy { kosmos.multiDisplayStatusBarStarter }
+ @Before
+ fun setup() {
+ whenever(windowManager.shouldShowSystemDecors(Display.DEFAULT_DISPLAY)).thenReturn(true)
+ whenever(windowManager.shouldShowSystemDecors(DISPLAY_1)).thenReturn(true)
+ whenever(windowManager.shouldShowSystemDecors(DISPLAY_2)).thenReturn(true)
+ whenever(windowManager.shouldShowSystemDecors(DISPLAY_3)).thenReturn(true)
+ whenever(windowManager.shouldShowSystemDecors(DISPLAY_4_NO_SYSTEM_DECOR)).thenReturn(false)
+ }
+
@Test
fun start_startsInitializersForCurrentDisplays() =
testScope.runTest {
- fakeDisplayRepository.addDisplay(displayId = 1)
- fakeDisplayRepository.addDisplay(displayId = 2)
+ fakeDisplayRepository.addDisplay(displayId = DISPLAY_1)
+ fakeDisplayRepository.addDisplay(displayId = DISPLAY_2)
+ fakeDisplayRepository.addDisplay(displayId = DISPLAY_4_NO_SYSTEM_DECOR)
underTest.start()
runCurrent()
expect
- .that(fakeInitializerStore.forDisplay(displayId = 1).startedByCoreStartable)
+ .that(fakeInitializerStore.forDisplay(displayId = DISPLAY_1).startedByCoreStartable)
.isTrue()
expect
- .that(fakeInitializerStore.forDisplay(displayId = 2).startedByCoreStartable)
+ .that(fakeInitializerStore.forDisplay(displayId = DISPLAY_2).startedByCoreStartable)
.isTrue()
+ expect
+ .that(
+ fakeInitializerStore
+ .forDisplay(displayId = DISPLAY_4_NO_SYSTEM_DECOR)
+ .startedByCoreStartable
+ )
+ .isFalse()
}
@Test
fun start_startsOrchestratorForCurrentDisplays() =
testScope.runTest {
- fakeDisplayRepository.addDisplay(displayId = 1)
- fakeDisplayRepository.addDisplay(displayId = 2)
+ fakeDisplayRepository.addDisplay(displayId = DISPLAY_1)
+ fakeDisplayRepository.addDisplay(displayId = DISPLAY_2)
+ fakeDisplayRepository.addDisplay(displayId = DISPLAY_4_NO_SYSTEM_DECOR)
underTest.start()
runCurrent()
- verify(fakeOrchestratorFactory.createdOrchestratorForDisplay(displayId = 1)!!).start()
- verify(fakeOrchestratorFactory.createdOrchestratorForDisplay(displayId = 2)!!).start()
+ verify(fakeOrchestratorFactory.createdOrchestratorForDisplay(displayId = DISPLAY_1)!!)
+ .start()
+ verify(fakeOrchestratorFactory.createdOrchestratorForDisplay(displayId = DISPLAY_2)!!)
+ .start()
+ assertThat(
+ fakeOrchestratorFactory.createdOrchestratorForDisplay(
+ displayId = DISPLAY_4_NO_SYSTEM_DECOR
+ )
+ )
+ .isNull()
}
@Test
fun start_startsPrivacyDotForCurrentDisplays() =
testScope.runTest {
- fakeDisplayRepository.addDisplay(displayId = 1)
- fakeDisplayRepository.addDisplay(displayId = 2)
+ fakeDisplayRepository.addDisplay(displayId = DISPLAY_1)
+ fakeDisplayRepository.addDisplay(displayId = DISPLAY_2)
+ fakeDisplayRepository.addDisplay(displayId = DISPLAY_4_NO_SYSTEM_DECOR)
underTest.start()
runCurrent()
- verify(fakePrivacyDotStore.forDisplay(displayId = 1)).start()
- verify(fakePrivacyDotStore.forDisplay(displayId = 2)).start()
+ verify(fakePrivacyDotStore.forDisplay(displayId = DISPLAY_1)).start()
+ verify(fakePrivacyDotStore.forDisplay(displayId = DISPLAY_2)).start()
+ verify(fakePrivacyDotStore.forDisplay(displayId = DISPLAY_4_NO_SYSTEM_DECOR), never())
+ .start()
}
@Test
fun start_doesNotStartLightBarControllerForCurrentDisplays() =
testScope.runTest {
- fakeDisplayRepository.addDisplay(displayId = 1)
- fakeDisplayRepository.addDisplay(displayId = 2)
+ fakeDisplayRepository.addDisplay(displayId = DISPLAY_1)
+ fakeDisplayRepository.addDisplay(displayId = DISPLAY_2)
+ fakeDisplayRepository.addDisplay(displayId = DISPLAY_4_NO_SYSTEM_DECOR)
underTest.start()
runCurrent()
- verify(fakeLightBarStore.forDisplay(displayId = 1), never()).start()
- verify(fakeLightBarStore.forDisplay(displayId = 2), never()).start()
+ verify(fakeLightBarStore.forDisplay(displayId = DISPLAY_1), never()).start()
+ verify(fakeLightBarStore.forDisplay(displayId = DISPLAY_2), never()).start()
+ verify(fakeLightBarStore.forDisplay(displayId = DISPLAY_4_NO_SYSTEM_DECOR), never())
+ .start()
}
@Test
fun start_createsLightBarControllerForCurrentDisplays() =
testScope.runTest {
- fakeDisplayRepository.addDisplay(displayId = 1)
- fakeDisplayRepository.addDisplay(displayId = 2)
+ fakeDisplayRepository.addDisplay(displayId = DISPLAY_1)
+ fakeDisplayRepository.addDisplay(displayId = DISPLAY_2)
+ fakeDisplayRepository.addDisplay(displayId = DISPLAY_4_NO_SYSTEM_DECOR)
underTest.start()
runCurrent()
- assertThat(fakeLightBarStore.perDisplayMocks.keys).containsExactly(1, 2)
+ assertThat(fakeLightBarStore.perDisplayMocks.keys).containsExactly(1, DISPLAY_2)
}
@Test
@@ -135,121 +173,174 @@
}
@Test
- fun displayAdded_orchestratorForNewDisplayIsStarted() =
+ fun displayAdded_orchestratorForNewDisplay() =
testScope.runTest {
underTest.start()
runCurrent()
- fakeDisplayRepository.addDisplay(displayId = 3)
+ fakeDisplayRepository.addDisplay(displayId = DISPLAY_3)
+ fakeDisplayRepository.addDisplay(displayId = DISPLAY_4_NO_SYSTEM_DECOR)
runCurrent()
- verify(fakeOrchestratorFactory.createdOrchestratorForDisplay(displayId = 3)!!).start()
+ verify(fakeOrchestratorFactory.createdOrchestratorForDisplay(displayId = DISPLAY_3)!!)
+ .start()
+ assertThat(
+ fakeOrchestratorFactory.createdOrchestratorForDisplay(
+ displayId = DISPLAY_4_NO_SYSTEM_DECOR
+ )
+ )
+ .isNull()
}
@Test
- fun displayAdded_initializerForNewDisplayIsStarted() =
+ fun displayAdded_initializerForNewDisplay() =
testScope.runTest {
underTest.start()
runCurrent()
- fakeDisplayRepository.addDisplay(displayId = 3)
+ fakeDisplayRepository.addDisplay(displayId = DISPLAY_3)
+ fakeDisplayRepository.addDisplay(displayId = DISPLAY_4_NO_SYSTEM_DECOR)
runCurrent()
expect
- .that(fakeInitializerStore.forDisplay(displayId = 3).startedByCoreStartable)
+ .that(fakeInitializerStore.forDisplay(displayId = DISPLAY_3).startedByCoreStartable)
.isTrue()
+ expect
+ .that(
+ fakeInitializerStore
+ .forDisplay(displayId = DISPLAY_4_NO_SYSTEM_DECOR)
+ .startedByCoreStartable
+ )
+ .isFalse()
}
@Test
- fun displayAdded_privacyDotForNewDisplayIsStarted() =
+ fun displayAdded_privacyDotForNewDisplay() =
testScope.runTest {
underTest.start()
runCurrent()
- fakeDisplayRepository.addDisplay(displayId = 3)
+ fakeDisplayRepository.addDisplay(displayId = DISPLAY_3)
+ fakeDisplayRepository.addDisplay(displayId = DISPLAY_4_NO_SYSTEM_DECOR)
runCurrent()
- verify(fakePrivacyDotStore.forDisplay(displayId = 3)).start()
+ verify(fakePrivacyDotStore.forDisplay(displayId = DISPLAY_3)).start()
+ verify(fakePrivacyDotStore.forDisplay(displayId = DISPLAY_4_NO_SYSTEM_DECOR), never())
+ .start()
}
@Test
- fun displayAdded_lightBarForNewDisplayIsCreated() =
+ fun displayAdded_lightBarForNewDisplayCreate() =
testScope.runTest {
underTest.start()
runCurrent()
- fakeDisplayRepository.addDisplay(displayId = 3)
+ fakeDisplayRepository.addDisplay(displayId = DISPLAY_3)
+ fakeDisplayRepository.addDisplay(displayId = DISPLAY_4_NO_SYSTEM_DECOR)
runCurrent()
- assertThat(fakeLightBarStore.perDisplayMocks.keys).containsExactly(3)
+ assertThat(fakeLightBarStore.perDisplayMocks.keys).containsExactly(DISPLAY_3)
}
@Test
- fun displayAdded_lightBarForNewDisplayIsNotStarted() =
+ fun displayAdded_lightBarForNewDisplayStart() =
testScope.runTest {
underTest.start()
runCurrent()
- fakeDisplayRepository.addDisplay(displayId = 3)
+ fakeDisplayRepository.addDisplay(displayId = DISPLAY_3)
+ fakeDisplayRepository.addDisplay(displayId = DISPLAY_4_NO_SYSTEM_DECOR)
runCurrent()
- verify(fakeLightBarStore.forDisplay(displayId = 3), never()).start()
+ verify(fakeLightBarStore.forDisplay(displayId = DISPLAY_3), never()).start()
+ verify(fakeLightBarStore.forDisplay(displayId = DISPLAY_4_NO_SYSTEM_DECOR), never())
+ .start()
}
@Test
- fun displayAddedDuringStart_initializerForNewDisplayIsStarted() =
+ fun displayAddedDuringStart_initializerForNewDisplay() =
testScope.runTest {
underTest.start()
- fakeDisplayRepository.addDisplay(displayId = 3)
+ fakeDisplayRepository.addDisplay(displayId = DISPLAY_3)
+ fakeDisplayRepository.addDisplay(displayId = DISPLAY_4_NO_SYSTEM_DECOR)
runCurrent()
expect
- .that(fakeInitializerStore.forDisplay(displayId = 3).startedByCoreStartable)
+ .that(fakeInitializerStore.forDisplay(displayId = DISPLAY_3).startedByCoreStartable)
.isTrue()
+ expect
+ .that(
+ fakeInitializerStore
+ .forDisplay(displayId = DISPLAY_4_NO_SYSTEM_DECOR)
+ .startedByCoreStartable
+ )
+ .isFalse()
}
@Test
- fun displayAddedDuringStart_orchestratorForNewDisplayIsStarted() =
+ fun displayAddedDuringStart_orchestratorForNewDisplay() =
testScope.runTest {
underTest.start()
- fakeDisplayRepository.addDisplay(displayId = 3)
+ fakeDisplayRepository.addDisplay(displayId = DISPLAY_3)
+ fakeDisplayRepository.addDisplay(displayId = DISPLAY_4_NO_SYSTEM_DECOR)
runCurrent()
- verify(fakeOrchestratorFactory.createdOrchestratorForDisplay(displayId = 3)!!).start()
+ verify(fakeOrchestratorFactory.createdOrchestratorForDisplay(displayId = DISPLAY_3)!!)
+ .start()
+ assertThat(
+ fakeOrchestratorFactory.createdOrchestratorForDisplay(
+ displayId = DISPLAY_4_NO_SYSTEM_DECOR
+ )
+ )
+ .isNull()
}
@Test
- fun displayAddedDuringStart_privacyDotForNewDisplayIsStarted() =
+ fun displayAddedDuringStart_privacyDotForNewDisplay() =
testScope.runTest {
underTest.start()
- fakeDisplayRepository.addDisplay(displayId = 3)
+ fakeDisplayRepository.addDisplay(displayId = DISPLAY_3)
+ fakeDisplayRepository.addDisplay(displayId = DISPLAY_4_NO_SYSTEM_DECOR)
runCurrent()
- verify(fakePrivacyDotStore.forDisplay(displayId = 3)).start()
+ verify(fakePrivacyDotStore.forDisplay(displayId = DISPLAY_3)).start()
+ verify(fakePrivacyDotStore.forDisplay(displayId = DISPLAY_4_NO_SYSTEM_DECOR), never())
+ .start()
}
@Test
- fun displayAddedDuringStart_lightBarForNewDisplayIsCreated() =
+ fun displayAddedDuringStart_lightBarForNewDisplayCreate() =
testScope.runTest {
underTest.start()
- fakeDisplayRepository.addDisplay(displayId = 3)
+ fakeDisplayRepository.addDisplay(displayId = DISPLAY_3)
+ fakeDisplayRepository.addDisplay(displayId = DISPLAY_4_NO_SYSTEM_DECOR)
runCurrent()
- assertThat(fakeLightBarStore.perDisplayMocks.keys).containsExactly(3)
+ assertThat(fakeLightBarStore.perDisplayMocks.keys).containsExactly(DISPLAY_3)
}
@Test
- fun displayAddedDuringStart_lightBarForNewDisplayIsNotStarted() =
+ fun displayAddedDuringStart_lightBarForNewDisplayStart() =
testScope.runTest {
underTest.start()
- fakeDisplayRepository.addDisplay(displayId = 3)
+ fakeDisplayRepository.addDisplay(displayId = DISPLAY_3)
+ fakeDisplayRepository.addDisplay(displayId = DISPLAY_4_NO_SYSTEM_DECOR)
runCurrent()
- verify(fakeLightBarStore.forDisplay(displayId = 3), never()).start()
+ verify(fakeLightBarStore.forDisplay(displayId = DISPLAY_3), never()).start()
+ verify(fakeLightBarStore.forDisplay(displayId = DISPLAY_4_NO_SYSTEM_DECOR), never())
+ .start()
}
+
+ companion object {
+ const val DISPLAY_1 = 1
+ const val DISPLAY_2 = 2
+ const val DISPLAY_3 = 3
+ const val DISPLAY_4_NO_SYSTEM_DECOR = 4
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationsListInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationsListInteractorTest.kt
index 5d9aa71..35b19c1 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationsListInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationsListInteractorTest.kt
@@ -47,7 +47,7 @@
private val notifsRepository = kosmos.activeNotificationListRepository
private val notifsInteractor = kosmos.activeNotificationsInteractor
private val underTest =
- RenderNotificationListInteractor(notifsRepository, sectionStyleProvider = mock())
+ RenderNotificationListInteractor(notifsRepository, sectionStyleProvider = mock(), context)
@Test
fun setRenderedList_preservesOrdering() =
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewModel.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewModel.kt
index a1c910d..0223484 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewModel.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewModel.kt
@@ -20,6 +20,7 @@
import android.graphics.Rect
import android.view.View
import com.android.systemui.plugins.DarkIconDispatcher
+import com.android.systemui.statusbar.chips.mediaprojection.domain.model.MediaProjectionStopDialogModel
import com.android.systemui.statusbar.chips.ui.model.MultipleOngoingActivityChipsModel
import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
import com.android.systemui.statusbar.events.shared.model.SystemEventAnimationState.Idle
@@ -46,6 +47,9 @@
override val statusBarPopupChips = MutableStateFlow(emptyList<PopupChipModel.Shown>())
+ override val mediaProjectionStopDialogDueToCallEndedState =
+ MutableStateFlow(MediaProjectionStopDialogModel.Hidden)
+
override val isHomeStatusBarAllowedByScene = MutableStateFlow(false)
override val shouldHomeStatusBarBeVisible = MutableStateFlow(false)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelImplTest.kt
index e74d009..46f625f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelImplTest.kt
@@ -45,8 +45,8 @@
import com.android.systemui.kosmos.collectLastValue
import com.android.systemui.kosmos.collectValues
import com.android.systemui.kosmos.runTest
-import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
import com.android.systemui.log.assertLogsWtf
import com.android.systemui.mediaprojection.data.model.MediaProjectionState
import com.android.systemui.mediaprojection.data.repository.fakeMediaProjectionRepository
@@ -58,7 +58,9 @@
import com.android.systemui.shade.shadeTestUtil
import com.android.systemui.statusbar.chips.mediaprojection.domain.interactor.MediaProjectionChipInteractorTest.Companion.NORMAL_PACKAGE
import com.android.systemui.statusbar.chips.mediaprojection.domain.interactor.MediaProjectionChipInteractorTest.Companion.setUpPackageManagerForMediaProjection
+import com.android.systemui.statusbar.chips.mediaprojection.domain.model.MediaProjectionStopDialogModel
import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
+import com.android.systemui.statusbar.chips.sharetoapp.ui.viewmodel.shareToAppChipViewModel
import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipsViewModelTest.Companion.assertIsScreenRecordChip
import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipsViewModelTest.Companion.assertIsShareToAppChip
@@ -89,7 +91,7 @@
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.runBlocking
-import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -98,9 +100,7 @@
@OptIn(ExperimentalCoroutinesApi::class)
@RunWith(AndroidJUnit4::class)
class HomeStatusBarViewModelImplTest : SysuiTestCase() {
- private val kosmos by lazy {
- testKosmos().also { it.testDispatcher = UnconfinedTestDispatcher() }
- }
+ private val kosmos = testKosmos().useUnconfinedTestDispatcher()
private val Kosmos.underTest by Kosmos.Fixture { kosmos.homeStatusBarViewModel }
@Before
@@ -112,6 +112,55 @@
fun addDisplays() = runBlocking { kosmos.displayRepository.fake.addDisplay(DEFAULT_DISPLAY) }
@Test
+ @EnableFlags(com.android.media.projection.flags.Flags.FLAG_SHOW_STOP_DIALOG_POST_CALL_END)
+ fun mediaProjectionStopDialogDueToCallEndedState_initiallyHidden() =
+ kosmos.runTest {
+ shareToAppChipViewModel.start()
+ val latest by collectLastValue(underTest.mediaProjectionStopDialogDueToCallEndedState)
+
+ // Verify that the stop dialog is initially hidden
+ assertThat(latest).isInstanceOf(MediaProjectionStopDialogModel.Hidden::class.java)
+ }
+
+ @Test
+ @EnableFlags(com.android.media.projection.flags.Flags.FLAG_SHOW_STOP_DIALOG_POST_CALL_END)
+ fun mediaProjectionStopDialogDueToCallEndedState_flagEnabled_mediaIsProjecting_projectionStartedDuringCallAndActivePostCallEventEmitted_isShown() =
+ kosmos.runTest {
+ shareToAppChipViewModel.start()
+
+ val latest by
+ collectLastValue(
+ homeStatusBarViewModel.mediaProjectionStopDialogDueToCallEndedState
+ )
+
+ fakeMediaProjectionRepository.mediaProjectionState.value =
+ MediaProjectionState.Projecting.EntireScreen(NORMAL_PACKAGE)
+
+ fakeMediaProjectionRepository.emitProjectionStartedDuringCallAndActivePostCallEvent()
+
+ assertThat(latest).isInstanceOf(MediaProjectionStopDialogModel.Shown::class.java)
+ }
+
+ @Test
+ @DisableFlags(com.android.media.projection.flags.Flags.FLAG_SHOW_STOP_DIALOG_POST_CALL_END)
+ fun mediaProjectionStopDialogDueToCallEndedState_flagDisabled_mediaIsProjecting_projectionStartedDuringCallAndActivePostCallEventEmitted_isHidden() =
+ kosmos.runTest {
+ shareToAppChipViewModel.start()
+
+ val latest by
+ collectLastValue(
+ homeStatusBarViewModel.mediaProjectionStopDialogDueToCallEndedState
+ )
+
+ fakeMediaProjectionRepository.mediaProjectionState.value =
+ MediaProjectionState.Projecting.EntireScreen(NORMAL_PACKAGE)
+
+ fakeMediaProjectionRepository.emitProjectionStartedDuringCallAndActivePostCallEvent()
+
+ assertThat(latest).isInstanceOf(MediaProjectionStopDialogModel.Hidden::class.java)
+ }
+
+ @Test
fun isTransitioningFromLockscreenToOccluded_started_isTrue() =
kosmos.runTest {
val latest by collectLastValue(underTest.isTransitioningFromLockscreenToOccluded)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/wallpapers/data/repository/WallpaperRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/wallpapers/data/repository/WallpaperRepositoryImplTest.kt
index 115edd0..2b16c00 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/wallpapers/data/repository/WallpaperRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/wallpapers/data/repository/WallpaperRepositoryImplTest.kt
@@ -25,16 +25,15 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.internal.R
-import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.keyguard.data.repository.FakeKeyguardClockRepository
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.res.R as SysUIR
import com.android.systemui.shared.Flags as SharedFlags
import com.android.systemui.user.data.model.SelectedUserModel
import com.android.systemui.user.data.model.SelectionStatus
import com.android.systemui.user.data.repository.FakeUserRepository
-import com.android.systemui.wallpapers.data.repository.WallpaperRepositoryImpl.Companion.MAGIC_PORTRAIT_CLASSNAME
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.StandardTestDispatcher
@@ -72,9 +71,12 @@
)
}
+ lateinit var focalAreaTarget: String
+
@Before
fun setUp() {
whenever(wallpaperManager.isWallpaperSupported).thenReturn(true)
+ focalAreaTarget = context.resources.getString(SysUIR.string.focal_area_target)
}
@Test
@@ -248,17 +250,17 @@
}
@Test
- @EnableFlags(Flags.FLAG_MAGIC_PORTRAIT_WALLPAPERS)
- fun shouldSendNotificationLayout_setMagicPortraitWallpaper_launchSendLayoutJob() =
+ @EnableFlags(SharedFlags.FLAG_EXTENDED_WALLPAPER_EFFECTS)
+ fun shouldSendNotificationLayout_setExtendedEffectsWallpaper_launchSendLayoutJob() =
testScope.runTest {
val latest by collectLastValue(underTest.shouldSendFocalArea)
- val magicPortraitWallpaper =
+ val extedendEffectsWallpaper =
mock<WallpaperInfo>().apply {
- whenever(this.component)
- .thenReturn(ComponentName(context, MAGIC_PORTRAIT_CLASSNAME))
+ whenever(this.component).thenReturn(ComponentName(context, focalAreaTarget))
}
+
whenever(wallpaperManager.getWallpaperInfoForUser(any()))
- .thenReturn(magicPortraitWallpaper)
+ .thenReturn(extedendEffectsWallpaper)
fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
context,
Intent(Intent.ACTION_WALLPAPER_CHANGED),
@@ -269,13 +271,16 @@
}
@Test
- @EnableFlags(Flags.FLAG_MAGIC_PORTRAIT_WALLPAPERS)
- fun shouldSendNotificationLayout_setNotMagicPortraitWallpaper_cancelSendLayoutJob() =
+ @EnableFlags(SharedFlags.FLAG_EXTENDED_WALLPAPER_EFFECTS)
+ fun shouldSendNotificationLayout_setNotExtendedEffectsWallpaper_cancelSendLayoutJob() =
testScope.runTest {
val latest by collectLastValue(underTest.shouldSendFocalArea)
- val magicPortraitWallpaper = MAGIC_PORTRAIT_WP
+ val extendedEffectsWallpaper =
+ mock<WallpaperInfo>().apply {
+ whenever(this.component).thenReturn(ComponentName("", focalAreaTarget))
+ }
whenever(wallpaperManager.getWallpaperInfoForUser(any()))
- .thenReturn(magicPortraitWallpaper)
+ .thenReturn(extendedEffectsWallpaper)
fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
context,
Intent(Intent.ACTION_WALLPAPER_CHANGED),
@@ -284,9 +289,7 @@
assertThat(underTest.sendLockscreenLayoutJob).isNotNull()
assertThat(underTest.sendLockscreenLayoutJob!!.isActive).isEqualTo(true)
- val nonMagicPortraitWallpaper = UNSUPPORTED_WP
- whenever(wallpaperManager.getWallpaperInfoForUser(any()))
- .thenReturn(nonMagicPortraitWallpaper)
+ whenever(wallpaperManager.getWallpaperInfoForUser(any())).thenReturn(UNSUPPORTED_WP)
fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
context,
Intent(Intent.ACTION_WALLPAPER_CHANGED),
@@ -303,10 +306,5 @@
val USER_WITH_SUPPORTED_WP = UserInfo(/* id= */ 4, /* name= */ "user4", /* flags= */ 0)
val SUPPORTED_WP =
mock<WallpaperInfo>().apply { whenever(this.supportsAmbientMode()).thenReturn(true) }
-
- val MAGIC_PORTRAIT_WP =
- mock<WallpaperInfo>().apply {
- whenever(this.component).thenReturn(ComponentName("", MAGIC_PORTRAIT_CLASSNAME))
- }
}
}
diff --git a/packages/SystemUI/res/drawable/media_output_item_expand_group.xml b/packages/SystemUI/res/drawable/media_output_item_expand_group.xml
new file mode 100644
index 0000000..833843d
--- /dev/null
+++ b/packages/SystemUI/res/drawable/media_output_item_expand_group.xml
@@ -0,0 +1,26 @@
+<!--
+ ~ Copyright (C) 2025 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M12,15.4 L6,9.4l1.4,-1.4 4.6,4.6 4.6,-4.6 1.4,1.4 -6,6Z" />
+</vector>
diff --git a/packages/SystemUI/res/layout/notification_2025_hybrid.xml b/packages/SystemUI/res/layout/notification_2025_hybrid.xml
index 8c34cd4..8fd10fb 100644
--- a/packages/SystemUI/res/layout/notification_2025_hybrid.xml
+++ b/packages/SystemUI/res/layout/notification_2025_hybrid.xml
@@ -29,6 +29,7 @@
android:layout_height="wrap_content"
android:singleLine="true"
android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Notification.Title"
+ android:textSize="@*android:dimen/notification_2025_title_text_size"
android:paddingEnd="4dp"
/>
<TextView
diff --git a/packages/SystemUI/res/layout/notification_2025_hybrid_conversation.xml b/packages/SystemUI/res/layout/notification_2025_hybrid_conversation.xml
index a338e4c..35f2ef9 100644
--- a/packages/SystemUI/res/layout/notification_2025_hybrid_conversation.xml
+++ b/packages/SystemUI/res/layout/notification_2025_hybrid_conversation.xml
@@ -54,6 +54,7 @@
android:singleLine="true"
android:paddingEnd="4dp"
android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Notification.Title"
+ android:textSize="@*android:dimen/notification_2025_title_text_size"
/>
<TextView
diff --git a/packages/SystemUI/res/values-xlarge-land/config.xml b/packages/SystemUI/res/values-xlarge-land/config.xml
index 6d8b64a..4c77f30 100644
--- a/packages/SystemUI/res/values-xlarge-land/config.xml
+++ b/packages/SystemUI/res/values-xlarge-land/config.xml
@@ -16,5 +16,5 @@
<resources>
<item name="shortcut_helper_screen_width_fraction" format="float" type="dimen">0.8</item>
- <bool name="center_align_magic_portrait_shape">true</bool>
+ <bool name="center_align_focal_area_shape">true</bool>
</resources>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 68e33f2..9b8926e 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -1107,7 +1107,7 @@
<!-- The dream component used when the device is low light environment. -->
<string translatable="false" name="config_lowLightDreamComponent"/>
- <!--Whether we should position magic portrait shape effects in the center of lockscreen
- it's false by default, and only be true in tablet landscape -->
- <bool name="center_align_magic_portrait_shape">false</bool>
+ <!-- Configuration for wallpaper focal area -->
+ <bool name="center_align_focal_area_shape">false</bool>
+ <string name="focal_area_target" translatable="false" />
</resources>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 84c859c..7b2e812 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -588,6 +588,12 @@
<!-- Content description of the cast label showing what we are connected to. [CHAR LIMIT=NONE] -->
<string name="accessibility_cast_name">Connected to <xliff:g id="cast" example="TV">%s</xliff:g>.</string>
+ <!-- Content description of the button to expand the group of devices. [CHAR LIMIT=NONE] -->
+ <string name="accessibility_expand_group">Expand group.</string>
+
+ <!-- Content description of the button to open the application . [CHAR LIMIT=NONE] -->
+ <string name="accessibility_open_application">Open application.</string>
+
<!-- Content description of an item with no signal and no connection for accessibility (not shown on the screen) [CHAR LIMIT=NONE] -->
<string name="accessibility_not_connected">Not connected.</string>
<!-- Content description of the roaming data connection type. [CHAR LIMIT=NONE] -->
@@ -3395,6 +3401,8 @@
<!-- Content description for a chip in the status bar showing that the user is currently on a call. [CHAR LIMIT=NONE] -->
<string name="ongoing_call_content_description">Ongoing call</string>
+ <!-- Content description for a chip in the status bar showing that the user currently has an ongoing activity. [CHAR LIMIT=NONE]-->
+ <string name="ongoing_notification_extra_content_description">Ongoing</string>
<!-- Provider Model: Default title of the mobile network in the mobile layout. [CHAR LIMIT=50] -->
<string name="mobile_data_settings_title">Mobile data</string>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ILauncherProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ILauncherProxy.aidl
index b43ffc5..10b9303 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ILauncherProxy.aidl
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ILauncherProxy.aidl
@@ -146,9 +146,9 @@
void onUnbind(IRemoteCallback reply) = 35;
/**
- * Sent when {@link TaskbarDelegate#onDisplayReady} is called.
+ * Sent when {@link TaskbarDelegate#onDisplayAddSystemDecorations} is called.
*/
- void onDisplayReady(int displayId) = 36;
+ void onDisplayAddSystemDecorations(int displayId) = 36;
/**
* Sent when {@link TaskbarDelegate#onDisplayRemoved} is called.
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
index b4e6e93..237a19c 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
@@ -186,28 +186,32 @@
* Whether communal hub should be shown automatically, depending on the user's [WhenToDream]
* state.
*/
- val shouldShowCommunal: Flow<Boolean> =
+ val shouldShowCommunal: StateFlow<Boolean> =
allOf(
- isCommunalAvailable,
- communalSettingsInteractor.whenToDream
- .flatMapLatest { whenToDream ->
- when (whenToDream) {
- WhenToDream.NEVER -> flowOf(false)
+ isCommunalAvailable,
+ communalSettingsInteractor.whenToDream
+ .flatMapLatest { whenToDream ->
+ when (whenToDream) {
+ WhenToDream.NEVER -> flowOf(false)
- WhenToDream.WHILE_CHARGING -> batteryInteractor.isDevicePluggedIn
+ WhenToDream.WHILE_CHARGING -> batteryInteractor.isDevicePluggedIn
- WhenToDream.WHILE_DOCKED ->
- allOf(
- batteryInteractor.isDevicePluggedIn,
- dockManager.retrieveIsDocked(),
- )
+ WhenToDream.WHILE_DOCKED ->
+ allOf(
+ batteryInteractor.isDevicePluggedIn,
+ dockManager.retrieveIsDocked(),
+ )
- WhenToDream.WHILE_POSTURED ->
- allOf(batteryInteractor.isDevicePluggedIn, posturingInteractor.postured)
+ WhenToDream.WHILE_POSTURED ->
+ allOf(
+ batteryInteractor.isDevicePluggedIn,
+ posturingInteractor.postured,
+ )
+ }
}
- }
- .flowOn(bgDispatcher),
- )
+ .flowOn(bgDispatcher),
+ )
+ .stateIn(scope = bgScope, started = SharingStarted.Eagerly, initialValue = false)
private val _isDisclaimerDismissed = MutableStateFlow(false)
val isDisclaimerDismissed: Flow<Boolean> = _isDisclaimerDismissed.asStateFlow()
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/binder/CommunalLockIconViewBinder.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/binder/CommunalLockIconViewBinder.kt
new file mode 100644
index 0000000..b1407da
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/binder/CommunalLockIconViewBinder.kt
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.communal.ui.binder
+
+import android.annotation.SuppressLint
+import android.content.res.ColorStateList
+import android.util.Log
+import android.util.StateSet
+import android.view.HapticFeedbackConstants
+import android.view.View
+import androidx.core.view.isInvisible
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.app.tracing.coroutines.launchTraced as launch
+import com.android.systemui.common.ui.view.LongPressHandlingView
+import com.android.systemui.communal.ui.viewmodel.CommunalLockIconViewModel
+import com.android.systemui.keyguard.ui.view.DeviceEntryIconView
+import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.VibratorHelper
+import com.android.systemui.util.kotlin.DisposableHandles
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.DisposableHandle
+
+object CommunalLockIconViewBinder {
+ private const val TAG = "CommunalLockIconViewBinder"
+
+ /**
+ * Updates UI for:
+ * - device entry containing view (parent view for the below views)
+ * - long-press handling view (transparent, no UI)
+ * - foreground icon view (lock/unlock)
+ */
+ @SuppressLint("ClickableViewAccessibility")
+ @JvmStatic
+ fun bind(
+ applicationScope: CoroutineScope,
+ view: DeviceEntryIconView,
+ viewModel: CommunalLockIconViewModel,
+ falsingManager: FalsingManager,
+ vibratorHelper: VibratorHelper,
+ ): DisposableHandle {
+ val disposables = DisposableHandles()
+ val longPressHandlingView = view.longPressHandlingView
+ val fgIconView = view.iconView
+ val bgView = view.bgView
+ longPressHandlingView.listener =
+ object : LongPressHandlingView.Listener {
+ override fun onLongPressDetected(
+ view: View,
+ x: Int,
+ y: Int,
+ isA11yAction: Boolean,
+ ) {
+ if (
+ !isA11yAction && falsingManager.isFalseLongTap(FalsingManager.LOW_PENALTY)
+ ) {
+ Log.d(
+ TAG,
+ "Long press rejected because it is not a11yAction " +
+ "and it is a falseLongTap",
+ )
+ return
+ }
+ vibratorHelper.performHapticFeedback(view, HapticFeedbackConstants.CONFIRM)
+ applicationScope.launch {
+ view.clearFocus()
+ view.clearAccessibilityFocus()
+ viewModel.onUserInteraction()
+ }
+ }
+ }
+
+ longPressHandlingView.isInvisible = false
+ view.isClickable = true
+ longPressHandlingView.longPressDuration = {
+ view.resources.getInteger(R.integer.config_lockIconLongPress).toLong()
+ }
+ bgView.visibility = View.GONE
+
+ disposables +=
+ view.repeatWhenAttached {
+ repeatOnLifecycle(Lifecycle.State.CREATED) {
+ launch("$TAG#viewModel.isLongPressEnabled") {
+ viewModel.isLongPressEnabled.collect { isEnabled ->
+ longPressHandlingView.setLongPressHandlingEnabled(isEnabled)
+ }
+ }
+ launch("$TAG#viewModel.accessibilityDelegateHint") {
+ viewModel.accessibilityDelegateHint.collect { hint ->
+ view.accessibilityHintType = hint
+ if (hint != DeviceEntryIconView.AccessibilityHintType.NONE) {
+ view.setOnClickListener {
+ vibratorHelper.performHapticFeedback(
+ view,
+ HapticFeedbackConstants.CONFIRM,
+ )
+ applicationScope.launch {
+ view.clearFocus()
+ view.clearAccessibilityFocus()
+ viewModel.onUserInteraction()
+ }
+ }
+ } else {
+ view.setOnClickListener(null)
+ }
+ }
+ }
+ }
+ }
+
+ disposables +=
+ fgIconView.repeatWhenAttached {
+ repeatOnLifecycle(Lifecycle.State.STARTED) {
+ // Start with an empty state
+ fgIconView.setImageState(StateSet.NOTHING, /* merge */ false)
+ launch("$TAG#fpIconView.viewModel") {
+ viewModel.viewAttributes.collect { attributes ->
+ if (attributes.type.contentDescriptionResId != -1) {
+ fgIconView.contentDescription =
+ fgIconView.resources.getString(
+ attributes.type.contentDescriptionResId
+ )
+ }
+ fgIconView.imageTintList = ColorStateList.valueOf(attributes.tint)
+ fgIconView.setPadding(
+ attributes.padding,
+ attributes.padding,
+ attributes.padding,
+ attributes.padding,
+ )
+ // Set image state at the end after updating other view state. This
+ // method forces the ImageView to recompute the bounds of the drawable.
+ fgIconView.setImageState(
+ view.getIconState(attributes.type, false),
+ /* merge */ false,
+ )
+ // Invalidate, just in case the padding changes just after icon changes
+ fgIconView.invalidate()
+ }
+ }
+ }
+ }
+ return disposables
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalLockIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalLockIconViewModel.kt
new file mode 100644
index 0000000..19eeabd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalLockIconViewModel.kt
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.communal.ui.viewmodel
+
+import android.content.Context
+import com.android.keyguard.KeyguardViewController
+import com.android.settingslib.Utils
+import com.android.systemui.accessibility.domain.interactor.AccessibilityInteractor
+import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntrySourceInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.keyguard.ui.view.DeviceEntryIconView
+import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryIconViewModel
+import com.android.systemui.keyguard.ui.viewmodel.toAccessibilityHintType
+import com.android.systemui.res.R
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
+import com.android.systemui.shade.ShadeDisplayAware
+import com.android.systemui.util.kotlin.emitOnStart
+import dagger.Lazy
+import javax.inject.Inject
+import kotlin.math.roundToInt
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flow
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
+
+/**
+ * Simpler implementation of [DeviceEntryIconViewModel] for use in glanceable hub, where fingerprint
+ * is not supported.
+ */
+@OptIn(ExperimentalCoroutinesApi::class)
+@SysUISingleton
+class CommunalLockIconViewModel
+@Inject
+constructor(
+ @ShadeDisplayAware val context: Context,
+ @ShadeDisplayAware configurationInteractor: ConfigurationInteractor,
+ private val deviceEntryInteractor: DeviceEntryInteractor,
+ keyguardInteractor: KeyguardInteractor,
+ private val keyguardViewController: Lazy<KeyguardViewController>,
+ private val deviceEntrySourceInteractor: DeviceEntrySourceInteractor,
+ accessibilityInteractor: AccessibilityInteractor,
+) {
+
+ private val isUnlocked: Flow<Boolean> =
+ if (SceneContainerFlag.isEnabled) {
+ deviceEntryInteractor.isUnlocked
+ } else {
+ keyguardInteractor.isKeyguardDismissible
+ }
+ .flatMapLatest { isUnlocked ->
+ if (!isUnlocked) {
+ flowOf(false)
+ } else {
+ flow {
+ // delay in case device ends up transitioning away from the lock screen;
+ // we don't want to animate to the unlocked icon and just let the
+ // icon fade with the transition to GONE
+ delay(DeviceEntryIconViewModel.UNLOCKED_DELAY_MS)
+ emit(true)
+ }
+ }
+ }
+
+ private val iconType: Flow<DeviceEntryIconView.IconType> =
+ isUnlocked.map { unlocked ->
+ if (unlocked) {
+ DeviceEntryIconView.IconType.UNLOCK
+ } else {
+ DeviceEntryIconView.IconType.LOCK
+ }
+ }
+
+ val isLongPressEnabled: Flow<Boolean> =
+ iconType.map { deviceEntryStatus ->
+ when (deviceEntryStatus) {
+ DeviceEntryIconView.IconType.UNLOCK -> true
+ DeviceEntryIconView.IconType.LOCK,
+ DeviceEntryIconView.IconType.FINGERPRINT,
+ DeviceEntryIconView.IconType.NONE -> false
+ }
+ }
+
+ val accessibilityDelegateHint: Flow<DeviceEntryIconView.AccessibilityHintType> =
+ accessibilityInteractor.isEnabled.flatMapLatest { touchExplorationEnabled ->
+ if (touchExplorationEnabled) {
+ iconType.map { it.toAccessibilityHintType() }
+ } else {
+ flowOf(DeviceEntryIconView.AccessibilityHintType.NONE)
+ }
+ }
+
+ private val padding: Flow<Int> =
+ configurationInteractor.scaleForResolution.map { scale ->
+ (context.resources.getDimensionPixelSize(R.dimen.lock_icon_padding) * scale)
+ .roundToInt()
+ }
+
+ private fun getColor() =
+ Utils.getColorAttrDefaultColor(context, R.attr.wallpaperTextColorAccent)
+
+ private val color: Flow<Int> =
+ configurationInteractor.onAnyConfigurationChange
+ .emitOnStart()
+ .map { getColor() }
+ .distinctUntilChanged()
+
+ suspend fun onUserInteraction() {
+ if (SceneContainerFlag.isEnabled) {
+ deviceEntryInteractor.attemptDeviceEntry()
+ } else {
+ keyguardViewController.get().showPrimaryBouncer(/* scrim */ true)
+ }
+ deviceEntrySourceInteractor.attemptEnterDeviceFromDeviceEntryIcon()
+ }
+
+ val viewAttributes: Flow<CommunalLockIconAttributes> =
+ combine(iconType, color, padding) { iconType, color, padding ->
+ CommunalLockIconAttributes(type = iconType, tint = color, padding = padding)
+ }
+}
+
+data class CommunalLockIconAttributes(
+ val type: DeviceEntryIconView.IconType,
+ val tint: Int,
+ val padding: Int,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamViewModel.kt b/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamViewModel.kt
index 02e1824..11b7e9d 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamViewModel.kt
@@ -20,6 +20,7 @@
import com.android.systemui.Flags.glanceableHubAllowKeyguardWhenDreaming
import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
import com.android.systemui.communal.domain.interactor.CommunalInteractor
+import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dump.DumpManager
import com.android.systemui.keyguard.domain.interactor.FromDreamingTransitionInteractor
@@ -51,6 +52,7 @@
private val toLockscreenTransitionViewModel: DreamingToLockscreenTransitionViewModel,
private val fromDreamingTransitionInteractor: FromDreamingTransitionInteractor,
private val communalInteractor: CommunalInteractor,
+ private val communalSettingsInteractor: CommunalSettingsInteractor,
private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
private val userTracker: UserTracker,
dumpManager: DumpManager,
@@ -58,8 +60,12 @@
fun startTransitionFromDream() {
val showGlanceableHub =
- communalInteractor.isCommunalEnabled.value &&
- !keyguardUpdateMonitor.isEncryptedOrLockdown(userTracker.userId)
+ if (communalSettingsInteractor.isV2FlagEnabled()) {
+ communalInteractor.shouldShowCommunal.value
+ } else {
+ communalInteractor.isCommunalEnabled.value &&
+ !keyguardUpdateMonitor.isEncryptedOrLockdown(userTracker.userId)
+ }
fromDreamingTransitionInteractor.startToLockscreenOrGlanceableHubTransition(
showGlanceableHub && !glanceableHubAllowKeyguardWhenDreaming()
)
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt
index 0054dd7..6395bb73 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt
Binary files differ
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
index 621cc46..aaad101 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
@@ -326,10 +326,7 @@
fun setShortcutAbsoluteTop(top: Float)
- /**
- * Set bottom of notifications from notification stack, and Magic Portrait will layout base on
- * this value
- */
+ /** Set bottom of notifications from notification stack */
fun setNotificationStackAbsoluteBottom(bottom: Float)
fun setWallpaperFocalAreaBounds(bounds: RectF)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt
index 8429c23..0b116de 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt
@@ -15,9 +15,11 @@
*/
package com.android.systemui.keyguard.domain.interactor
+import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.keyguard.logging.ScrimLogger
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.keyguard.data.repository.DEFAULT_REVEAL_DURATION
import com.android.systemui.keyguard.data.repository.LightRevealScrimRepository
import com.android.systemui.keyguard.shared.model.Edge
@@ -31,12 +33,13 @@
import com.android.systemui.util.kotlin.sample
import dagger.Lazy
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
-import com.android.app.tracing.coroutines.launchTraced as launch
+import kotlinx.coroutines.flow.flowOn
@SysUISingleton
class LightRevealScrimInteractor
@@ -47,6 +50,7 @@
@Application private val scope: CoroutineScope,
private val scrimLogger: ScrimLogger,
private val powerInteractor: Lazy<PowerInteractor>,
+ @Background backgroundDispatcher: CoroutineDispatcher,
) {
init {
listenForStartedKeyguardTransitionStep()
@@ -113,6 +117,7 @@
repository.maxAlpha
}
}
+ .flowOn(backgroundDispatcher)
val revealAmount =
repository.revealAmount.filter {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WallpaperFocalAreaInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WallpaperFocalAreaInteractor.kt
index 934afe2..9c744d6 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WallpaperFocalAreaInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WallpaperFocalAreaInteractor.kt
@@ -50,8 +50,7 @@
keyguardClockRepository: KeyguardClockRepository,
wallpaperRepository: WallpaperRepository,
) {
- // When there's notifications in splitshade, magic portrait shape effects should be left
- // aligned in foldable
+ // When there's notifications in splitshade, the focal area shape effect should be left aligned
private val notificationInShadeWideLayout: Flow<Boolean> =
combine(
shadeRepository.isShadeLayoutWide,
@@ -104,7 +103,7 @@
)
val (left, right) =
// tablet landscape
- if (context.resources.getBoolean(R.bool.center_align_magic_portrait_shape)) {
+ if (context.resources.getBoolean(R.bool.center_align_focal_area_shape)) {
Pair(
scaledBounds.centerX() - maxFocalAreaWidth / 2F,
scaledBounds.centerX() + maxFocalAreaWidth / 2F,
@@ -129,7 +128,7 @@
wallpaperZoomedInScale
val top =
// tablet landscape
- if (context.resources.getBoolean(R.bool.center_align_magic_portrait_shape)) {
+ if (context.resources.getBoolean(R.bool.center_align_focal_area_shape)) {
// no strict constraints for top, use bottom margin to make it symmetric
// vertically
scaledBounds.top + scaledBottomMargin
@@ -169,8 +168,8 @@
)
}
- // A max width for magic portrait shape effects bounds, to avoid it going too large
- // in large screen portrait mode
+ // A max width for focal area shape effects bounds, to avoid
+ // it becoming too large in large screen portrait mode
const val FOCAL_AREA_MAX_WIDTH_DP = 500
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/SplitShadeKeyguardBlueprint.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/SplitShadeKeyguardBlueprint.kt
index 856e1d6..8b213be 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/SplitShadeKeyguardBlueprint.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/SplitShadeKeyguardBlueprint.kt
@@ -23,6 +23,7 @@
import com.android.systemui.keyguard.ui.view.layout.sections.AccessibilityActionsSection
import com.android.systemui.keyguard.ui.view.layout.sections.AodBurnInSection
import com.android.systemui.keyguard.ui.view.layout.sections.AodNotificationIconsSection
+import com.android.systemui.keyguard.ui.view.layout.sections.AodPromotedNotificationSection
import com.android.systemui.keyguard.ui.view.layout.sections.ClockSection
import com.android.systemui.keyguard.ui.view.layout.sections.DefaultDeviceEntrySection
import com.android.systemui.keyguard.ui.view.layout.sections.DefaultIndicationAreaSection
@@ -58,6 +59,7 @@
defaultStatusBarSection: DefaultStatusBarSection,
splitShadeNotificationStackScrollLayoutSection: SplitShadeNotificationStackScrollLayoutSection,
splitShadeGuidelines: SplitShadeGuidelines,
+ aodPromotedNotificationSection: AodPromotedNotificationSection,
aodNotificationIconsSection: AodNotificationIconsSection,
aodBurnInSection: AodBurnInSection,
clockSection: ClockSection,
@@ -76,6 +78,7 @@
defaultStatusBarSection,
splitShadeNotificationStackScrollLayoutSection,
splitShadeGuidelines,
+ aodPromotedNotificationSection,
aodNotificationIconsSection,
smartspaceSection,
aodBurnInSection,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodPromotedNotificationSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodPromotedNotificationSection.kt
index ed1bdb0..ea4acce 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodPromotedNotificationSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodPromotedNotificationSection.kt
@@ -26,6 +26,7 @@
import androidx.constraintlayout.widget.ConstraintSet.TOP
import com.android.systemui.keyguard.shared.model.KeyguardSection
import com.android.systemui.res.R
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.statusbar.notification.promoted.AODPromotedNotification
import com.android.systemui.statusbar.notification.promoted.PromotedNotificationLogger
import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUiAod
@@ -36,6 +37,7 @@
@Inject
constructor(
private val viewModelFactory: AODPromotedNotificationViewModel.Factory,
+ private val shadeInteractor: ShadeInteractor,
private val logger: PromotedNotificationLogger,
) : KeyguardSection() {
var view: ComposeView? = null
@@ -77,9 +79,12 @@
checkNotNull(view)
constraintSet.apply {
+ val isShadeLayoutWide = shadeInteractor.isShadeLayoutWide.value
+ val endGuidelineId = if (isShadeLayoutWide) R.id.split_shade_guideline else PARENT_ID
+
connect(viewId, TOP, R.id.smart_space_barrier_bottom, BOTTOM, 0)
connect(viewId, START, PARENT_ID, START, 0)
- connect(viewId, END, PARENT_ID, END, 0)
+ connect(viewId, END, endGuidelineId, END, 0)
constrainWidth(viewId, ConstraintSet.MATCH_CONSTRAINT)
constrainHeight(viewId, ConstraintSet.WRAP_CONTENT)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt
index 84fdc6e..13cd583 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt
@@ -262,16 +262,6 @@
deviceEntrySourceInteractor.attemptEnterDeviceFromDeviceEntryIcon()
}
- private fun DeviceEntryIconView.IconType.toAccessibilityHintType():
- DeviceEntryIconView.AccessibilityHintType {
- return when (this) {
- DeviceEntryIconView.IconType.FINGERPRINT,
- DeviceEntryIconView.IconType.LOCK -> DeviceEntryIconView.AccessibilityHintType.BOUNCER
- DeviceEntryIconView.IconType.UNLOCK -> DeviceEntryIconView.AccessibilityHintType.ENTER
- DeviceEntryIconView.IconType.NONE -> DeviceEntryIconView.AccessibilityHintType.NONE
- }
- }
-
companion object {
const val UNLOCKED_DELAY_MS = 50L
}
@@ -282,3 +272,13 @@
val y: Int, // current y burn in offset based on the aodTransitionAmount
val progress: Float, // current progress based on the aodTransitionAmount
)
+
+fun DeviceEntryIconView.IconType.toAccessibilityHintType():
+ DeviceEntryIconView.AccessibilityHintType {
+ return when (this) {
+ DeviceEntryIconView.IconType.FINGERPRINT,
+ DeviceEntryIconView.IconType.LOCK -> DeviceEntryIconView.AccessibilityHintType.BOUNCER
+ DeviceEntryIconView.IconType.UNLOCK -> DeviceEntryIconView.AccessibilityHintType.ENTER
+ DeviceEntryIconView.IconType.NONE -> DeviceEntryIconView.AccessibilityHintType.NONE
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaItem.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaItem.java
index 4496b25..7b1c62e2 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaItem.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaItem.java
@@ -36,6 +36,7 @@
private final String mTitle;
@MediaItemType
private final int mMediaItemType;
+ private final boolean mIsFirstDeviceInGroup;
@Retention(RetentionPolicy.SOURCE)
@IntDef({
@@ -54,7 +55,18 @@
* name.
*/
public static MediaItem createDeviceMediaItem(@NonNull MediaDevice device) {
- return new MediaItem(device, device.getName(), MediaItemType.TYPE_DEVICE);
+ return new MediaItem(device, device.getName(), MediaItemType.TYPE_DEVICE, false);
+ }
+
+ /**
+ * Returns a new {@link MediaItemType#TYPE_DEVICE} {@link MediaItem} with its {@link
+ * #getMediaDevice() media device} set to {@code device} and its title set to {@code device}'s
+ * name.
+ */
+ public static MediaItem createDeviceMediaItem(
+ @NonNull MediaDevice device, boolean isFirstDeviceInGroup) {
+ return new MediaItem(
+ device, device.getName(), MediaItemType.TYPE_DEVICE, isFirstDeviceInGroup);
}
/**
@@ -63,7 +75,10 @@
*/
public static MediaItem createPairNewDeviceMediaItem() {
return new MediaItem(
- /* device */ null, /* title */ null, MediaItemType.TYPE_PAIR_NEW_DEVICE);
+ /* device */ null,
+ /* title */ null,
+ MediaItemType.TYPE_PAIR_NEW_DEVICE,
+ /* mIsFirstDeviceInGroup */ false);
}
/**
@@ -71,14 +86,22 @@
* title and a {@code null} {@link #getMediaDevice() media device}.
*/
public static MediaItem createGroupDividerMediaItem(@Nullable String title) {
- return new MediaItem(/* device */ null, title, MediaItemType.TYPE_GROUP_DIVIDER);
+ return new MediaItem(
+ /* device */ null,
+ title,
+ MediaItemType.TYPE_GROUP_DIVIDER,
+ /* misFirstDeviceInGroup */ false);
}
private MediaItem(
- @Nullable MediaDevice device, @Nullable String title, @MediaItemType int type) {
+ @Nullable MediaDevice device,
+ @Nullable String title,
+ @MediaItemType int type,
+ boolean isFirstDeviceInGroup) {
this.mMediaDeviceOptional = Optional.ofNullable(device);
this.mTitle = title;
this.mMediaItemType = type;
+ this.mIsFirstDeviceInGroup = isFirstDeviceInGroup;
}
public Optional<MediaDevice> getMediaDevice() {
@@ -106,4 +129,8 @@
public int getMediaItemType() {
return mMediaItemType;
}
+
+ public boolean isFirstDeviceInGroup() {
+ return mIsFirstDeviceInGroup;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
index 53f3b3a..52b3c3ec 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
@@ -21,6 +21,7 @@
import static com.android.settingslib.media.MediaDevice.SelectionBehavior.SELECTION_BEHAVIOR_TRANSFER;
import android.annotation.DrawableRes;
+import android.annotation.StringRes;
import android.content.Context;
import android.content.res.ColorStateList;
import android.graphics.drawable.AnimatedVectorDrawable;
@@ -38,6 +39,7 @@
import androidx.recyclerview.widget.RecyclerView;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.media.flags.Flags;
import com.android.settingslib.media.LocalMediaManager.MediaDeviceState;
import com.android.settingslib.media.MediaDevice;
import com.android.systemui.res.R;
@@ -55,6 +57,7 @@
private static final float DEVICE_DISCONNECTED_ALPHA = 0.5f;
private static final float DEVICE_CONNECTED_ALPHA = 1f;
protected List<MediaItem> mMediaItemList = new CopyOnWriteArrayList<>();
+ private boolean mShouldGroupSelectedMediaItems = Flags.enableOutputSwitcherSessionGrouping();
public MediaOutputAdapter(MediaSwitchingController controller) {
super(controller);
@@ -65,6 +68,12 @@
public void updateItems() {
mMediaItemList.clear();
mMediaItemList.addAll(mController.getMediaItemList());
+ if (mShouldGroupSelectedMediaItems) {
+ if (mController.getSelectedMediaDevice().size() == 1) {
+ // Don't group devices if initially there isn't more than one selected.
+ mShouldGroupSelectedMediaItems = false;
+ }
+ }
notifyDataSetChanged();
}
@@ -101,7 +110,7 @@
break;
case MediaItem.MediaItemType.TYPE_DEVICE:
((MediaDeviceViewHolder) viewHolder).onBind(
- currentMediaItem.getMediaDevice().get(),
+ currentMediaItem,
position);
break;
default:
@@ -141,8 +150,8 @@
super(view);
}
- @Override
- void onBind(MediaDevice device, int position) {
+ void onBind(MediaItem mediaItem, int position) {
+ MediaDevice device = mediaItem.getMediaDevice().get();
super.onBind(device, position);
boolean isMutingExpectedDeviceExist = mController.hasMutingExpectedDevice();
final boolean currentlyConnected = isCurrentlyConnected(device);
@@ -150,6 +159,7 @@
if (mCurrentActivePosition == position) {
mCurrentActivePosition = -1;
}
+ mItemLayout.setVisibility(View.VISIBLE);
mStatusIcon.setVisibility(View.GONE);
enableFocusPropertyForView(mContainerLayout);
@@ -174,6 +184,30 @@
updateFullItemClickListener(v -> onItemClick(v, device));
setSingleLineLayout(device.getName());
initFakeActiveDevice(device);
+ } else if (mShouldGroupSelectedMediaItems
+ && mController.getSelectedMediaDevice().size() > 1
+ && isDeviceIncluded(mController.getSelectedMediaDevice(), device)) {
+ if (!mediaItem.isFirstDeviceInGroup()) {
+ mItemLayout.setVisibility(View.GONE);
+ mEndTouchArea.setVisibility(View.GONE);
+ } else {
+ String sessionName = mController.getSessionName().toString();
+ updateUnmutedVolumeIcon(null);
+ updateEndClickAreaWithIcon(
+ v -> {
+ mShouldGroupSelectedMediaItems = false;
+ notifyDataSetChanged();
+ },
+ R.drawable.media_output_item_expand_group,
+ R.string.accessibility_expand_group);
+ disableFocusPropertyForView(mContainerLayout);
+ setUpContentDescriptionForView(mSeekBar, mContext.getString(
+ R.string.accessibility_cast_name, sessionName));
+ setSingleLineLayout(sessionName, true /* showSeekBar */,
+ false /* showProgressBar */, false /* showCheckBox */,
+ true /* showEndTouchArea */);
+ initGroupSeekbar(isCurrentSeekbarInvisible);
+ }
} else if (device.hasSubtext()) {
boolean isActiveWithOngoingSession =
(device.hasOngoingSession() && (currentlyConnected || isDeviceIncluded(
@@ -237,6 +271,8 @@
// selected device in group
boolean isDeviceDeselectable = isDeviceIncluded(
mController.getDeselectableMediaDevice(), device);
+ boolean showEndArea = !Flags.enableOutputSwitcherSessionGrouping()
+ || isDeviceDeselectable;
updateUnmutedVolumeIcon(device);
updateGroupableCheckBox(true, isDeviceDeselectable, device);
updateEndClickArea(device, isDeviceDeselectable);
@@ -244,7 +280,7 @@
setUpContentDescriptionForView(mSeekBar, device);
setSingleLineLayout(device.getName(), true /* showSeekBar */,
false /* showProgressBar */, true /* showCheckBox */,
- true /* showEndTouchArea */);
+ showEndArea /* showEndTouchArea */);
initSeekbar(device, isCurrentSeekbarInvisible);
} else if (!mController.hasAdjustVolumeUserRestriction()
&& currentlyConnected) {
@@ -335,19 +371,29 @@
}
private void updateEndClickAreaAsSessionEditing(MediaDevice device, @DrawableRes int id) {
- mEndClickIcon.setOnClickListener(null);
- mEndTouchArea.setOnClickListener(null);
+ updateEndClickAreaWithIcon(
+ v -> mController.tryToLaunchInAppRoutingIntent(device.getId(), v),
+ id,
+ R.string.accessibility_open_application);
+ }
+
+ private void updateEndClickAreaWithIcon(View.OnClickListener clickListener,
+ @DrawableRes int iconDrawableId,
+ @StringRes int accessibilityStringId) {
updateEndClickAreaColor(mController.getColorSeekbarProgress());
mEndClickIcon.setImageTintList(
ColorStateList.valueOf(mController.getColorItemContent()));
- mEndClickIcon.setOnClickListener(
- v -> mController.tryToLaunchInAppRoutingIntent(device.getId(), v));
+ mEndClickIcon.setOnClickListener(clickListener);
mEndTouchArea.setOnClickListener(v -> mEndClickIcon.performClick());
- Drawable drawable = mContext.getDrawable(id);
+ Drawable drawable = mContext.getDrawable(iconDrawableId);
mEndClickIcon.setImageDrawable(drawable);
if (drawable instanceof AnimatedVectorDrawable) {
((AnimatedVectorDrawable) drawable).start();
}
+ if (Flags.enableOutputSwitcherSessionGrouping()) {
+ setUpContentDescriptionForView(
+ mEndClickIcon, mContext.getString(accessibilityStringId));
+ }
}
public void updateEndClickAreaColor(int color) {
@@ -479,12 +525,17 @@
}
private void setUpContentDescriptionForView(View view, MediaDevice device) {
- view.setContentDescription(
+ setUpContentDescriptionForView(
+ view,
mContext.getString(device.getDeviceType()
== MediaDevice.MediaDeviceType.TYPE_BLUETOOTH_DEVICE
? R.string.accessibility_bluetooth_name
: R.string.accessibility_cast_name, device.getName()));
}
+
+ protected void setUpContentDescriptionForView(View view, String description) {
+ view.setContentDescription(description);
+ }
}
class MediaGroupDividerViewHolder extends RecyclerView.ViewHolder {
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
index 9b24c69..ee2d8aa 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
@@ -42,6 +42,7 @@
import androidx.annotation.VisibleForTesting;
import androidx.recyclerview.widget.RecyclerView;
+import com.android.media.flags.Flags;
import com.android.settingslib.media.InputMediaDevice;
import com.android.settingslib.media.MediaDevice;
import com.android.settingslib.utils.ThreadUtils;
@@ -211,6 +212,10 @@
mTitleText.setText(title);
mCheckBox.setVisibility(showCheckBox ? View.VISIBLE : View.GONE);
mEndTouchArea.setVisibility(showEndTouchArea ? View.VISIBLE : View.GONE);
+ if (Flags.enableOutputSwitcherSessionGrouping()) {
+ mEndClickIcon.setVisibility(
+ !showCheckBox && showEndTouchArea ? View.VISIBLE : View.GONE);
+ }
ViewGroup.MarginLayoutParams params =
(ViewGroup.MarginLayoutParams) mItemLayout.getLayoutParams();
params.rightMargin = showEndTouchArea ? mController.getItemMarginEndSelectable()
@@ -265,14 +270,8 @@
mController.getActiveRadius(), 0, 0});
}
- void initSeekbar(MediaDevice device, boolean isCurrentSeekbarInvisible) {
- if (!mController.isVolumeControlEnabled(device)) {
- disableSeekBar();
- } else {
- enableSeekBar(device);
- }
- mSeekBar.setMaxVolume(device.getMaxVolume());
- final int currentVolume = device.getCurrentVolume();
+ private void initializeSeekbarVolume(
+ MediaDevice device, int currentVolume, boolean isCurrentSeekbarInvisible) {
if (!mIsDragging) {
if (mSeekBar.getVolume() != currentVolume && (mLatestUpdateVolume == -1
|| currentVolume == mLatestUpdateVolume)) {
@@ -307,54 +306,75 @@
if (mIsInitVolumeFirstTime) {
mIsInitVolumeFirstTime = false;
}
- mSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
- boolean mStartFromMute = false;
+ }
+
+ void initSeekbar(MediaDevice device, boolean isCurrentSeekbarInvisible) {
+ SeekBarVolumeControl volumeControl = new SeekBarVolumeControl() {
@Override
- public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
- if (device == null || !fromUser) {
- return;
- }
-
- final String percentageString = mContext.getResources().getString(
- R.string.media_output_dialog_volume_percentage,
- mSeekBar.getPercentage());
- mVolumeValueText.setText(percentageString);
-
- if (mStartFromMute) {
- updateUnmutedVolumeIcon(device);
- mStartFromMute = false;
- }
- int seekBarVolume = MediaOutputSeekbar.scaleProgressToVolume(progress);
- if (seekBarVolume != device.getCurrentVolume()) {
- mLatestUpdateVolume = seekBarVolume;
- mController.adjustVolume(device, seekBarVolume);
- }
+ public int getVolume() {
+ return device.getCurrentVolume();
+ }
+ @Override
+ public void setVolume(int volume) {
+ mController.adjustVolume(device, volume);
}
@Override
- public void onStartTrackingTouch(SeekBar seekBar) {
- mTitleIcon.setVisibility(View.INVISIBLE);
- mVolumeValueText.setVisibility(View.VISIBLE);
- int currentVolume = MediaOutputSeekbar.scaleProgressToVolume(
- seekBar.getProgress());
- mStartFromMute = (currentVolume == 0);
- mIsDragging = true;
+ public void onMute() {
+ mController.logInteractionUnmuteDevice(device);
}
+ };
+ if (!mController.isVolumeControlEnabled(device)) {
+ disableSeekBar();
+ } else {
+ enableSeekBar(volumeControl);
+ }
+ mSeekBar.setMaxVolume(device.getMaxVolume());
+ final int currentVolume = device.getCurrentVolume();
+ initializeSeekbarVolume(device, currentVolume, isCurrentSeekbarInvisible);
+
+ mSeekBar.setOnSeekBarChangeListener(new MediaSeekBarChangedListener(
+ device, volumeControl) {
@Override
- public void onStopTrackingTouch(SeekBar seekBar) {
- int currentVolume = MediaOutputSeekbar.scaleProgressToVolume(
- seekBar.getProgress());
- if (currentVolume == 0) {
- seekBar.setProgress(0);
- updateMutedVolumeIcon(device);
- } else {
- updateUnmutedVolumeIcon(device);
- }
- mTitleIcon.setVisibility(View.VISIBLE);
- mVolumeValueText.setVisibility(View.GONE);
+ public void onStopTrackingTouch(SeekBar seekbar) {
+ super.onStopTrackingTouch(seekbar);
mController.logInteractionAdjustVolume(device);
- mIsDragging = false;
+ }
+ });
+ }
+
+ // Initializes the seekbar for a group of devices.
+ void initGroupSeekbar(boolean isCurrentSeekbarInvisible) {
+ SeekBarVolumeControl volumeControl = new SeekBarVolumeControl() {
+ @Override
+ public int getVolume() {
+ return mController.getSessionVolume();
+ }
+
+ @Override
+ public void setVolume(int volume) {
+ mController.adjustSessionVolume(volume);
+ }
+
+ @Override
+ public void onMute() {}
+ };
+
+ if (!mController.isVolumeControlEnabledForSession()) {
+ disableSeekBar();
+ } else {
+ enableSeekBar(volumeControl);
+ }
+ mSeekBar.setMaxVolume(mController.getSessionVolumeMax());
+
+ final int currentVolume = mController.getSessionVolume();
+ initializeSeekbarVolume(null, currentVolume, isCurrentSeekbarInvisible);
+ mSeekBar.setOnSeekBarChangeListener(new MediaSeekBarChangedListener(
+ null, volumeControl) {
+ @Override
+ protected boolean shouldHandleProgressChanged() {
+ return true;
}
});
}
@@ -385,7 +405,7 @@
int getDrawableId(boolean isInputDevice, boolean isMutedVolumeIcon) {
// Returns the microphone icon when the flag is enabled and the device is an input
// device.
- if (com.android.media.flags.Flags.enableAudioInputDeviceRoutingAndVolumeControl()
+ if (Flags.enableAudioInputDeviceRoutingAndVolumeControl()
&& isInputDevice) {
return isMutedVolumeIcon ? R.drawable.ic_mic_off : R.drawable.ic_mic_26dp;
}
@@ -452,27 +472,28 @@
updateIconAreaClickListener(null);
}
- private void enableSeekBar(MediaDevice device) {
+ private void enableSeekBar(SeekBarVolumeControl volumeControl) {
mSeekBar.setEnabled(true);
+
mSeekBar.setOnTouchListener((v, event) -> false);
updateIconAreaClickListener((v) -> {
- if (device.getCurrentVolume() == 0) {
- mController.logInteractionUnmuteDevice(device);
+ if (volumeControl.getVolume() == 0) {
mSeekBar.setVolume(UNMUTE_DEFAULT_VOLUME);
- mController.adjustVolume(device, UNMUTE_DEFAULT_VOLUME);
- updateUnmutedVolumeIcon(device);
+ volumeControl.setVolume(UNMUTE_DEFAULT_VOLUME);
+ updateUnmutedVolumeIcon(null);
mIconAreaLayout.setOnTouchListener(((iconV, event) -> false));
} else {
- mController.logInteractionMuteDevice(device);
+ volumeControl.onMute();
mSeekBar.resetVolume();
- mController.adjustVolume(device, 0);
- updateMutedVolumeIcon(device);
+ volumeControl.setVolume(0);
+ updateMutedVolumeIcon(null);
mIconAreaLayout.setOnTouchListener(((iconV, event) -> {
mSeekBar.dispatchTouchEvent(event);
return false;
}));
}
});
+
}
protected void setUpDeviceIcon(MediaDevice device) {
@@ -488,5 +509,74 @@
});
});
}
+
+ interface SeekBarVolumeControl {
+ int getVolume();
+ void setVolume(int volume);
+ void onMute();
+ }
+
+ private abstract class MediaSeekBarChangedListener
+ implements SeekBar.OnSeekBarChangeListener {
+ boolean mStartFromMute = false;
+ private MediaDevice mMediaDevice;
+ private SeekBarVolumeControl mVolumeControl;
+
+ MediaSeekBarChangedListener(MediaDevice device, SeekBarVolumeControl volumeControl) {
+ mMediaDevice = device;
+ mVolumeControl = volumeControl;
+ }
+
+ @Override
+ public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
+ if (!shouldHandleProgressChanged() || !fromUser) {
+ return;
+ }
+
+ final String percentageString = mContext.getResources().getString(
+ R.string.media_output_dialog_volume_percentage,
+ mSeekBar.getPercentage());
+ mVolumeValueText.setText(percentageString);
+
+ if (mStartFromMute) {
+ updateUnmutedVolumeIcon(mMediaDevice);
+ mStartFromMute = false;
+ }
+
+ int seekBarVolume = MediaOutputSeekbar.scaleProgressToVolume(progress);
+ if (seekBarVolume != mVolumeControl.getVolume()) {
+ mLatestUpdateVolume = seekBarVolume;
+ mVolumeControl.setVolume(seekBarVolume);
+ }
+ }
+
+ @Override
+ public void onStartTrackingTouch(SeekBar seekBar) {
+ mTitleIcon.setVisibility(View.INVISIBLE);
+ mVolumeValueText.setVisibility(View.VISIBLE);
+ int currentVolume = MediaOutputSeekbar.scaleProgressToVolume(
+ seekBar.getProgress());
+ mStartFromMute = (currentVolume == 0);
+ mIsDragging = true;
+ }
+
+ @Override
+ public void onStopTrackingTouch(SeekBar seekBar) {
+ int currentVolume = MediaOutputSeekbar.scaleProgressToVolume(
+ seekBar.getProgress());
+ if (currentVolume == 0) {
+ seekBar.setProgress(0);
+ updateMutedVolumeIcon(mMediaDevice);
+ } else {
+ updateUnmutedVolumeIcon(mMediaDevice);
+ }
+ mTitleIcon.setVisibility(View.VISIBLE);
+ mVolumeValueText.setVisibility(View.GONE);
+ mIsDragging = false;
+ }
+ protected boolean shouldHandleProgressChanged() {
+ return mMediaDevice != null;
+ }
+ };
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaSwitchingController.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaSwitchingController.java
index 15afd22..35c872f 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaSwitchingController.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaSwitchingController.java
@@ -760,14 +760,26 @@
if (connectedMediaDevice != null) {
selectedDevicesIds.add(connectedMediaDevice.getId());
}
+ boolean groupSelectedDevices =
+ com.android.media.flags.Flags.enableOutputSwitcherSessionGrouping();
+ int nextSelectedItemIndex = 0;
boolean suggestedDeviceAdded = false;
boolean displayGroupAdded = false;
+ boolean selectedDeviceAdded = false;
for (MediaDevice device : devices) {
if (needToHandleMutingExpectedDevice && device.isMutingExpectedDevice()) {
finalMediaItems.add(0, MediaItem.createDeviceMediaItem(device));
+ nextSelectedItemIndex++;
} else if (!needToHandleMutingExpectedDevice && selectedDevicesIds.contains(
device.getId())) {
- finalMediaItems.add(0, MediaItem.createDeviceMediaItem(device));
+ if (groupSelectedDevices) {
+ finalMediaItems.add(
+ nextSelectedItemIndex++,
+ MediaItem.createDeviceMediaItem(device, !selectedDeviceAdded));
+ selectedDeviceAdded = true;
+ } else {
+ finalMediaItems.add(0, MediaItem.createDeviceMediaItem(device));
+ }
} else {
if (device.isSuggestedDevice() && !suggestedDeviceAdded) {
addSuggestedDeviceGroupDivider(finalMediaItems);
@@ -1331,6 +1343,10 @@
return !device.isVolumeFixed();
}
+ boolean isVolumeControlEnabledForSession() {
+ return mLocalMediaManager.isMediaSessionAvailableForVolumeControl();
+ }
+
private void startActivity(Intent intent, ActivityTransitionAnimator.Controller controller) {
// Media Output dialog can be shown from the volume panel. This makes sure the panel is
// closed when navigating to another activity, so it doesn't stays on top of it
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/data/repository/MediaProjectionManagerRepository.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/data/repository/MediaProjectionManagerRepository.kt
index ea0f63c..d503fb7 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/data/repository/MediaProjectionManagerRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/data/repository/MediaProjectionManagerRepository.kt
@@ -18,6 +18,7 @@
import android.app.ActivityManager.RunningTaskInfo
import android.hardware.display.DisplayManager
+import android.media.projection.MediaProjectionEvent
import android.media.projection.MediaProjectionInfo
import android.media.projection.MediaProjectionManager
import android.media.projection.StopReason
@@ -43,6 +44,9 @@
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.filterNot
+import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.withContext
@@ -83,48 +87,59 @@
}
}
- override val mediaProjectionState: Flow<MediaProjectionState> =
- conflatedCallbackFlow {
- val callback =
- object : MediaProjectionManager.Callback() {
- override fun onStart(info: MediaProjectionInfo?) {
- logger.log(
- TAG,
- LogLevel.DEBUG,
- {},
- { "MediaProjectionManager.Callback#onStart" },
- )
- trySendWithFailureLogging(CallbackEvent.OnStart(info), TAG)
- }
+ private val callbackEventsFlow = conflatedCallbackFlow {
+ val callback =
+ object : MediaProjectionManager.Callback() {
+ override fun onStart(info: MediaProjectionInfo?) {
+ logger.log(TAG, LogLevel.DEBUG, {}, { "Callback#onStart" })
+ trySendWithFailureLogging(CallbackEvent.OnStart(info), TAG)
+ }
- override fun onStop(info: MediaProjectionInfo?) {
- logger.log(
- TAG,
- LogLevel.DEBUG,
- {},
- { "MediaProjectionManager.Callback#onStop" },
- )
- trySendWithFailureLogging(CallbackEvent.OnStop, TAG)
- }
+ override fun onStop(info: MediaProjectionInfo?) {
+ logger.log(TAG, LogLevel.DEBUG, {}, { "Callback#onStop" })
+ trySendWithFailureLogging(CallbackEvent.OnStop, TAG)
+ }
- override fun onRecordingSessionSet(
- info: MediaProjectionInfo,
- session: ContentRecordingSession?,
- ) {
- logger.log(
- TAG,
- LogLevel.DEBUG,
- { str1 = session.toString() },
- { "MediaProjectionManager.Callback#onSessionStarted: $str1" },
- )
- trySendWithFailureLogging(
- CallbackEvent.OnRecordingSessionSet(info, session),
- TAG,
- )
- }
+ override fun onRecordingSessionSet(
+ info: MediaProjectionInfo,
+ session: ContentRecordingSession?,
+ ) {
+ logger.log(
+ TAG,
+ LogLevel.DEBUG,
+ { str1 = session.toString() },
+ { "Callback#onSessionSet: $str1" },
+ )
+ trySendWithFailureLogging(
+ CallbackEvent.OnRecordingSessionSet(info, session),
+ TAG,
+ )
+ }
+
+ override fun onMediaProjectionEvent(
+ event: MediaProjectionEvent,
+ info: MediaProjectionInfo?,
+ session: ContentRecordingSession?,
+ ) {
+ if (com.android.media.projection.flags.Flags.showStopDialogPostCallEnd()) {
+ logger.log(
+ TAG,
+ LogLevel.DEBUG,
+ { str1 = event.toString() },
+ { "Callback#onMediaProjectionEvent : $str1" },
+ )
+ trySendWithFailureLogging(CallbackEvent.OnMediaProjectionEvent(event), TAG)
}
- mediaProjectionManager.addCallback(callback, handler)
- awaitClose { mediaProjectionManager.removeCallback(callback) }
+ }
+ }
+ mediaProjectionManager.addCallback(callback, handler)
+ awaitClose { mediaProjectionManager.removeCallback(callback) }
+ }
+
+ override val mediaProjectionState: Flow<MediaProjectionState> =
+ callbackEventsFlow
+ .filterNot {
+ it is CallbackEvent.OnMediaProjectionEvent // Exclude OnMediaProjectionEvent
}
// When we get an #onRecordingSessionSet event, we need to do some work in the
// background before emitting the right state value. But when we get an #onStop
@@ -159,6 +174,11 @@
}
is CallbackEvent.OnStop -> MediaProjectionState.NotProjecting
is CallbackEvent.OnRecordingSessionSet -> stateForSession(it.info, it.session)
+ is CallbackEvent.OnMediaProjectionEvent ->
+ throw IllegalStateException(
+ "Unexpected OnMediaProjectionEvent in mediaProjectionState flow. It " +
+ "should have been filtered out."
+ )
}
}
.stateIn(
@@ -167,6 +187,16 @@
initialValue = MediaProjectionState.NotProjecting,
)
+ override val projectionStartedDuringCallAndActivePostCallEvent: Flow<Unit> =
+ callbackEventsFlow
+ .filter {
+ com.android.media.projection.flags.Flags.showStopDialogPostCallEnd() &&
+ it is CallbackEvent.OnMediaProjectionEvent &&
+ it.event.eventType ==
+ MediaProjectionEvent.PROJECTION_STARTED_DURING_CALL_AND_ACTIVE_POST_CALL
+ }
+ .map {}
+
private suspend fun stateForSession(
info: MediaProjectionInfo,
session: ContentRecordingSession?,
@@ -206,6 +236,8 @@
val info: MediaProjectionInfo,
val session: ContentRecordingSession?,
) : CallbackEvent
+
+ data class OnMediaProjectionEvent(val event: MediaProjectionEvent) : CallbackEvent
}
companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/data/repository/MediaProjectionRepository.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/data/repository/MediaProjectionRepository.kt
index a01d8c2..826ee58 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/data/repository/MediaProjectionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/data/repository/MediaProjectionRepository.kt
@@ -32,4 +32,10 @@
/** Represents the current [MediaProjectionState]. */
val mediaProjectionState: Flow<MediaProjectionState>
+
+ /**
+ * Emits each time a call ends but media projection is still active and media projection was
+ * starting during the call.
+ */
+ val projectionStartedDuringCallAndActivePostCallEvent: Flow<Unit>
}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java
index ebda376..babb640 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java
@@ -284,7 +284,10 @@
}
@Override
- public void onDisplayReady(int displayId) {
+ public void onDisplayAddSystemDecorations(int displayId) {
+ if (enableDisplayContentModeManagement()) {
+ mHasNavBar.put(displayId, true);
+ }
Display display = mDisplayManager.getDisplay(displayId);
mIsLargeScreen = isLargeScreen(mContext);
createNavigationBar(display, null /* savedState */, null /* result */);
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
index 9d89430..c4d847f 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
@@ -238,16 +238,16 @@
}
@Override
- public void onDisplayReady(int displayId) {
- CommandQueue.Callbacks.super.onDisplayReady(displayId);
+ public void onDisplayAddSystemDecorations(int displayId) {
+ CommandQueue.Callbacks.super.onDisplayAddSystemDecorations(displayId);
if (mLauncherProxyService.getProxy() == null) {
return;
}
try {
- mLauncherProxyService.getProxy().onDisplayReady(displayId);
+ mLauncherProxyService.getProxy().onDisplayAddSystemDecorations(displayId);
} catch (RemoteException e) {
- Log.e(TAG, "onDisplayReady() failed", e);
+ Log.e(TAG, "onDisplayAddSystemDecorations() failed", e);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt b/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
index a3893bc..85b677b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
@@ -311,7 +311,7 @@
SceneTransitionLayout(state = sceneState, modifier = Modifier.fillMaxSize()) {
scene(QuickSettings) {
LaunchedEffect(Unit) { viewModel.onQSOpen() }
- QuickSettingsElement()
+ QuickSettingsElement(Modifier.element(QuickSettings.rootElementKey))
}
scene(QuickQuickSettings) {
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java
index 6844f05..eae0ba6 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java
@@ -16,6 +16,8 @@
package com.android.systemui.settings.brightness;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.content.Intent.EXTRA_BRIGHTNESS_DIALOG_IS_FULL_WIDTH;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
@@ -157,6 +159,7 @@
}
void setBrightnessDialogViewAttributes(View container) {
+ Configuration configuration = getResources().getConfiguration();
// The brightness mirror container is INVISIBLE by default.
container.setVisibility(View.VISIBLE);
ViewGroup.MarginLayoutParams lp =
@@ -171,9 +174,16 @@
R.dimen.notification_guts_option_vertical_padding);
lp.topMargin = verticalMargin;
+ // If in multi-window or freeform, increase the top margin so the brightness dialog
+ // doesn't get cut off.
+ final int windowingMode = configuration.windowConfiguration.getWindowingMode();
+ if (windowingMode == WINDOWING_MODE_MULTI_WINDOW
+ || windowingMode == WINDOWING_MODE_FREEFORM) {
+ lp.topMargin += 50;
+ }
+
lp.bottomMargin = verticalMargin;
- Configuration configuration = getResources().getConfiguration();
int orientation = configuration.orientation;
int windowWidth = getWindowAvailableWidth();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index dcea8d8..1720898 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -111,7 +111,7 @@
private static final int MSG_COLLAPSE_PANELS = 4 << MSG_SHIFT;
private static final int MSG_EXPAND_SETTINGS = 5 << MSG_SHIFT;
private static final int MSG_SYSTEM_BAR_CHANGED = 6 << MSG_SHIFT;
- private static final int MSG_DISPLAY_READY = 7 << MSG_SHIFT;
+ private static final int MSG_DISPLAY_ADD_SYSTEM_DECORATIONS = 7 << MSG_SHIFT;
private static final int MSG_SHOW_IME_BUTTON = 8 << MSG_SHIFT;
private static final int MSG_TOGGLE_RECENT_APPS = 9 << MSG_SHIFT;
private static final int MSG_PRELOAD_RECENT_APPS = 10 << MSG_SHIFT;
@@ -415,9 +415,9 @@
}
/**
- * @see IStatusBar#onDisplayReady(int)
+ * @see IStatusBar#onDisplayAddSystemDecorations(int)
*/
- default void onDisplayReady(int displayId) {
+ default void onDisplayAddSystemDecorations(int displayId) {
}
/**
@@ -1205,9 +1205,9 @@
}
@Override
- public void onDisplayReady(int displayId) {
+ public void onDisplayAddSystemDecorations(int displayId) {
synchronized (mLock) {
- mHandler.obtainMessage(MSG_DISPLAY_READY, displayId, 0).sendToTarget();
+ mHandler.obtainMessage(MSG_DISPLAY_ADD_SYSTEM_DECORATIONS, displayId, 0).sendToTarget();
}
}
@@ -1851,9 +1851,9 @@
mCallbacks.get(i).showPinningEscapeToast();
}
break;
- case MSG_DISPLAY_READY:
+ case MSG_DISPLAY_ADD_SYSTEM_DECORATIONS:
for (int i = 0; i < mCallbacks.size(); i++) {
- mCallbacks.get(i).onDisplayReady(msg.arg1);
+ mCallbacks.get(i).onDisplayAddSystemDecorations(msg.arg1);
}
break;
case MSG_DISPLAY_REMOVE_SYSTEM_DECORATIONS:
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt
index 541a07c..98b7521 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.chips.call.ui.viewmodel
+import android.content.Context
import android.view.View
import com.android.internal.jank.Cuj
import com.android.systemui.animation.ActivityTransitionAnimator
@@ -23,6 +24,7 @@
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.core.LogLevel
import com.android.systemui.plugins.ActivityStarter
@@ -52,6 +54,7 @@
open class CallChipViewModel
@Inject
constructor(
+ @Main private val context: Context,
@Application private val scope: CoroutineScope,
interactor: CallChipInteractor,
systemClock: SystemClock,
@@ -65,15 +68,18 @@
is OngoingCallModel.NoCall,
is OngoingCallModel.InCallWithVisibleApp -> OngoingActivityChipModel.Hidden()
is OngoingCallModel.InCall -> {
+ val contentDescription = getContentDescription(state.appName)
val icon =
if (state.notificationIconView != null) {
StatusBarConnectedDisplays.assertInLegacyMode()
OngoingActivityChipModel.ChipIcon.StatusBarView(
- state.notificationIconView
+ state.notificationIconView,
+ contentDescription,
)
} else if (StatusBarConnectedDisplays.isEnabled) {
OngoingActivityChipModel.ChipIcon.StatusBarNotificationIcon(
- state.notificationKey
+ state.notificationKey,
+ contentDescription,
)
} else {
OngoingActivityChipModel.ChipIcon.SingleColorIcon(phoneIcon)
@@ -155,6 +161,17 @@
)
}
+ private fun getContentDescription(appName: String): ContentDescription {
+ val ongoingCallDescription = context.getString(R.string.ongoing_call_content_description)
+ return ContentDescription.Loaded(
+ context.getString(
+ R.string.accessibility_desc_notification_icon,
+ appName,
+ ongoingCallDescription,
+ )
+ )
+ }
+
companion object {
private val phoneIcon =
Icon.Resource(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/domain/interactor/MediaProjectionChipInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/domain/interactor/MediaProjectionChipInteractor.kt
index 49c4479..49d69f2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/domain/interactor/MediaProjectionChipInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/domain/interactor/MediaProjectionChipInteractor.kt
@@ -32,6 +32,7 @@
import com.android.systemui.statusbar.chips.mediaprojection.domain.model.ProjectionChipModel
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.map
@@ -53,6 +54,9 @@
private val packageManager: PackageManager,
@StatusBarChipsLog private val logger: LogBuffer,
) {
+ val projectionStartedDuringCallAndActivePostCallEvent: Flow<Unit> =
+ mediaProjectionRepository.projectionStartedDuringCallAndActivePostCallEvent
+
val projection: StateFlow<ProjectionChipModel> =
mediaProjectionRepository.mediaProjectionState
.map { state ->
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/domain/model/MediaProjectionStopDialogModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/domain/model/MediaProjectionStopDialogModel.kt
new file mode 100644
index 0000000..b37c762
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/domain/model/MediaProjectionStopDialogModel.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.chips.mediaprojection.domain.model
+
+import com.android.systemui.statusbar.phone.SystemUIDialog
+
+/** Represents the visibility state of a media projection stop dialog. */
+sealed interface MediaProjectionStopDialogModel {
+ /** The dialog is hidden and not visible to the user. */
+ data object Hidden : MediaProjectionStopDialogModel
+
+ /** The dialog is shown to the user. */
+ data class Shown(
+ val dialogDelegate: SystemUIDialog.Delegate,
+ private val onDismissAction: () -> Unit,
+ ) : MediaProjectionStopDialogModel {
+ /**
+ * Creates and shows the dialog. Ensures that onDismissAction callback is invoked when the
+ * dialog is canceled or dismissed.
+ */
+ fun createAndShowDialog() {
+ val dialog = dialogDelegate.createDialog()
+ dialog.setOnCancelListener { onDismissAction.invoke() }
+ dialog.setOnDismissListener { onDismissAction.invoke() }
+ dialog.show()
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/interactor/SingleNotificationChipInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/interactor/SingleNotificationChipInteractor.kt
index cece521..a933888 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/interactor/SingleNotificationChipInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/interactor/SingleNotificationChipInteractor.kt
@@ -138,7 +138,7 @@
}
}
- return NotificationChipModel(key, statusBarChipIconView, promotedContent)
+ return NotificationChipModel(key, appName, statusBarChipIconView, promotedContent)
}
@AssistedFactory
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/model/NotificationChipModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/model/NotificationChipModel.kt
index c6759da..e7a9080 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/model/NotificationChipModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/model/NotificationChipModel.kt
@@ -22,6 +22,8 @@
/** Modeling all the data needed to render a status bar notification chip. */
data class NotificationChipModel(
val key: String,
+ /** The user-readable name of the app that posted the call notification. */
+ val appName: String,
val statusBarChipIconView: StatusBarIconView?,
val promotedContent: PromotedNotificationContentModel,
)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt
index 46456b8..b0da642 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt
@@ -16,10 +16,14 @@
package com.android.systemui.statusbar.chips.notification.ui.viewmodel
+import android.content.Context
import android.view.View
import com.android.systemui.Flags
+import com.android.systemui.common.shared.model.ContentDescription
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.chips.notification.domain.interactor.StatusBarNotificationChipsInteractor
import com.android.systemui.statusbar.chips.notification.domain.model.NotificationChipModel
import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
@@ -43,6 +47,7 @@
class NotifChipsViewModel
@Inject
constructor(
+ @Main private val context: Context,
@Application private val applicationScope: CoroutineScope,
private val notifChipsInteractor: StatusBarNotificationChipsInteractor,
headsUpNotificationInteractor: HeadsUpNotificationInteractor,
@@ -65,13 +70,20 @@
headsUpState: TopPinnedState
): OngoingActivityChipModel.Shown {
StatusBarNotifChips.assertInNewMode()
+ val contentDescription = getContentDescription(this.appName)
val icon =
if (this.statusBarChipIconView != null) {
StatusBarConnectedDisplays.assertInLegacyMode()
- OngoingActivityChipModel.ChipIcon.StatusBarView(this.statusBarChipIconView)
+ OngoingActivityChipModel.ChipIcon.StatusBarView(
+ this.statusBarChipIconView,
+ contentDescription,
+ )
} else {
StatusBarConnectedDisplays.assertInNewMode()
- OngoingActivityChipModel.ChipIcon.StatusBarNotificationIcon(this.key)
+ OngoingActivityChipModel.ChipIcon.StatusBarNotificationIcon(
+ this.key,
+ contentDescription,
+ )
}
val colors = this.promotedContent.toCustomColorsModel()
@@ -79,6 +91,7 @@
// The notification pipeline needs everything to run on the main thread, so keep
// this event on the main thread.
applicationScope.launch {
+ // TODO(b/364653005): Move accessibility focus to the HUN when chip is tapped.
notifChipsInteractor.onPromotedNotificationChipTapped(this@toActivityChipModel.key)
}
}
@@ -173,4 +186,16 @@
}
}
}
+
+ private fun getContentDescription(appName: String): ContentDescription {
+ val ongoingDescription =
+ context.getString(R.string.ongoing_notification_extra_content_description)
+ return ContentDescription.Loaded(
+ context.getString(
+ R.string.accessibility_desc_notification_icon,
+ appName,
+ ongoingDescription,
+ )
+ )
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModel.kt
index 6654d4a..7a46fff 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModel.kt
@@ -19,6 +19,7 @@
import android.content.Context
import androidx.annotation.DrawableRes
import com.android.internal.jank.Cuj
+import com.android.systemui.CoreStartable
import com.android.systemui.animation.DialogCuj
import com.android.systemui.animation.DialogTransitionAnimator
import com.android.systemui.common.shared.model.ContentDescription
@@ -31,6 +32,7 @@
import com.android.systemui.statusbar.chips.StatusBarChipLogTags.pad
import com.android.systemui.statusbar.chips.StatusBarChipsLog
import com.android.systemui.statusbar.chips.mediaprojection.domain.interactor.MediaProjectionChipInteractor
+import com.android.systemui.statusbar.chips.mediaprojection.domain.model.MediaProjectionStopDialogModel
import com.android.systemui.statusbar.chips.mediaprojection.domain.model.ProjectionChipModel
import com.android.systemui.statusbar.chips.mediaprojection.ui.view.EndMediaProjectionDialogHelper
import com.android.systemui.statusbar.chips.sharetoapp.ui.view.EndGenericShareToAppDialogDelegate
@@ -41,13 +43,18 @@
import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipViewModel
import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipViewModel.Companion.createDialogLaunchOnClickCallback
import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipViewModel.Companion.createDialogLaunchOnClickListener
+import com.android.systemui.util.kotlin.sample
import com.android.systemui.util.time.SystemClock
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.launch
/**
* View model for the share-to-app chip, shown when sharing your phone screen content to another app
@@ -64,7 +71,59 @@
private val endMediaProjectionDialogHelper: EndMediaProjectionDialogHelper,
private val dialogTransitionAnimator: DialogTransitionAnimator,
@StatusBarChipsLog private val logger: LogBuffer,
-) : OngoingActivityChipViewModel {
+) : OngoingActivityChipViewModel, CoreStartable {
+
+ private val _stopDialogToShow: MutableStateFlow<MediaProjectionStopDialogModel> =
+ MutableStateFlow(MediaProjectionStopDialogModel.Hidden)
+
+ /**
+ * Represents the current state of the media projection stop dialog. Emits
+ * [MediaProjectionStopDialogModel.Shown] when the dialog should be displayed, and
+ * [MediaProjectionStopDialogModel.Hidden] when it is dismissed.
+ */
+ val stopDialogToShow: StateFlow<MediaProjectionStopDialogModel> =
+ _stopDialogToShow.asStateFlow()
+
+ /**
+ * Emits a [MediaProjectionStopDialogModel] based on the current projection state when a
+ * projectionStartedDuringCallAndActivePostCallEvent event is emitted. If projecting, determines
+ * the appropriate dialog type to show. Otherwise, emits a hidden dialog state.
+ */
+ private val stopDialogDueToCallEndedState: StateFlow<MediaProjectionStopDialogModel> =
+ mediaProjectionChipInteractor.projectionStartedDuringCallAndActivePostCallEvent
+ .sample(mediaProjectionChipInteractor.projection) { _, currentProjection ->
+ when (currentProjection) {
+ is ProjectionChipModel.NotProjecting -> MediaProjectionStopDialogModel.Hidden
+ is ProjectionChipModel.Projecting -> {
+ when (currentProjection.receiver) {
+ ProjectionChipModel.Receiver.ShareToApp -> {
+ when (currentProjection.contentType) {
+ ProjectionChipModel.ContentType.Screen ->
+ createShareScreenToAppStopDialog(currentProjection)
+ ProjectionChipModel.ContentType.Audio ->
+ createGenericShareScreenToAppStopDialog()
+ }
+ }
+ ProjectionChipModel.Receiver.CastToOtherDevice ->
+ MediaProjectionStopDialogModel.Hidden
+ }
+ }
+ }
+ }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), MediaProjectionStopDialogModel.Hidden)
+
+ /**
+ * Initializes background flow collector during SysUI startup for events determining the
+ * visibility of media projection stop dialogs.
+ */
+ override fun start() {
+ if (com.android.media.projection.flags.Flags.showStopDialogPostCallEnd()) {
+ scope.launch {
+ stopDialogDueToCallEndedState.collect { event -> _stopDialogToShow.value = event }
+ }
+ }
+ }
+
private val internalChip =
mediaProjectionChipInteractor.projection
.map { projectionModel ->
@@ -92,7 +151,25 @@
private val chipTransitionHelper = ChipTransitionHelper(scope)
override val chip: StateFlow<OngoingActivityChipModel> =
- chipTransitionHelper.createChipFlow(internalChip)
+ combine(chipTransitionHelper.createChipFlow(internalChip), stopDialogToShow) {
+ currentChip,
+ stopDialog ->
+ if (
+ com.android.media.projection.flags.Flags.showStopDialogPostCallEnd() &&
+ stopDialog is MediaProjectionStopDialogModel.Shown
+ ) {
+ logger.log(
+ TAG,
+ LogLevel.INFO,
+ {},
+ { "Hiding the chip as stop dialog is being shown" },
+ )
+ OngoingActivityChipModel.Hidden()
+ } else {
+ currentChip
+ }
+ }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), OngoingActivityChipModel.Hidden())
/**
* Notifies this class that the user just stopped a screen recording from the dialog that's
@@ -108,6 +185,12 @@
chipTransitionHelper.onActivityStoppedFromDialog()
}
+ /** Called when the stop dialog is dismissed or cancelled. */
+ private fun onStopDialogDismissed() {
+ logger.log(TAG, LogLevel.INFO, {}, { "The media projection stop dialog was dismissed" })
+ _stopDialogToShow.value = MediaProjectionStopDialogModel.Hidden
+ }
+
/** Stops the currently active projection. */
private fun stopProjectingFromDialog() {
logger.log(TAG, LogLevel.INFO, {}, { "Stop sharing requested from dialog" })
@@ -115,6 +198,24 @@
mediaProjectionChipInteractor.stopProjecting()
}
+ private fun createShareScreenToAppStopDialog(
+ projectionModel: ProjectionChipModel.Projecting
+ ): MediaProjectionStopDialogModel {
+ val dialogDelegate = createShareScreenToAppDialogDelegate(projectionModel)
+ return MediaProjectionStopDialogModel.Shown(
+ dialogDelegate,
+ onDismissAction = ::onStopDialogDismissed,
+ )
+ }
+
+ private fun createGenericShareScreenToAppStopDialog(): MediaProjectionStopDialogModel {
+ val dialogDelegate = createGenericShareToAppDialogDelegate()
+ return MediaProjectionStopDialogModel.Shown(
+ dialogDelegate,
+ onDismissAction = ::onStopDialogDismissed,
+ )
+ }
+
private fun createShareScreenToAppChip(
state: ProjectionChipModel.Projecting
): OngoingActivityChipModel.Shown {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/OngoingActivityChipBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/OngoingActivityChipBinder.kt
index f5764d5..de9d497 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/OngoingActivityChipBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/OngoingActivityChipBinder.kt
@@ -26,6 +26,8 @@
import android.widget.ImageView
import android.widget.TextView
import androidx.annotation.UiThread
+import com.android.systemui.common.shared.model.ContentDescription
+import com.android.systemui.common.ui.binder.ContentDescriptionViewBinder
import com.android.systemui.common.ui.binder.IconViewBinder
import com.android.systemui.res.R
import com.android.systemui.statusbar.StatusBarIconView
@@ -187,7 +189,13 @@
}
is OngoingActivityChipModel.ChipIcon.StatusBarView -> {
StatusBarConnectedDisplays.assertInLegacyMode()
- setStatusBarIconView(defaultIconView, icon.impl, iconTint, backgroundView)
+ setStatusBarIconView(
+ defaultIconView,
+ icon.impl,
+ icon.contentDescription,
+ iconTint,
+ backgroundView,
+ )
}
is OngoingActivityChipModel.ChipIcon.StatusBarNotificationIcon -> {
StatusBarConnectedDisplays.assertInNewMode()
@@ -196,7 +204,13 @@
// This means that the notification key doesn't exist anymore.
return
}
- setStatusBarIconView(defaultIconView, iconView, iconTint, backgroundView)
+ setStatusBarIconView(
+ defaultIconView,
+ iconView,
+ icon.contentDescription,
+ iconTint,
+ backgroundView,
+ )
}
}
}
@@ -215,6 +229,7 @@
private fun setStatusBarIconView(
defaultIconView: ImageView,
iconView: StatusBarIconView,
+ iconContentDescription: ContentDescription,
iconTint: Int,
backgroundView: ChipBackgroundContainer,
) {
@@ -224,9 +239,12 @@
// 1. Set up the right visual params.
with(iconView) {
id = CUSTOM_ICON_VIEW_ID
- // TODO(b/354930838): For RON chips, use the app name for the content description.
- contentDescription =
- context.resources.getString(R.string.ongoing_call_content_description)
+ if (StatusBarNotifChips.isEnabled) {
+ ContentDescriptionViewBinder.bind(iconContentDescription, this)
+ } else {
+ contentDescription =
+ context.resources.getString(R.string.ongoing_call_content_description)
+ }
tintView(iconTint)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/OngoingActivityChip.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/OngoingActivityChip.kt
index 647f3bd..816f291 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/OngoingActivityChip.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/OngoingActivityChip.kt
@@ -37,11 +37,14 @@
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.dimensionResource
+import androidx.compose.ui.semantics.contentDescription
+import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.unit.dp
import androidx.compose.ui.viewinterop.AndroidView
import com.android.compose.animation.Expandable
import com.android.systemui.animation.Expandable
import com.android.systemui.common.ui.compose.Icon
+import com.android.systemui.common.ui.compose.load
import com.android.systemui.res.R
import com.android.systemui.statusbar.chips.ui.model.ColorsModel
import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
@@ -82,11 +85,25 @@
val isClickable = onClick != {}
val hasEmbeddedIcon = model.icon is OngoingActivityChipModel.ChipIcon.StatusBarView
+ val contentDescription =
+ when (val icon = model.icon) {
+ is OngoingActivityChipModel.ChipIcon.StatusBarView -> icon.contentDescription.load()
+ is OngoingActivityChipModel.ChipIcon.StatusBarNotificationIcon ->
+ icon.contentDescription.load()
+ is OngoingActivityChipModel.ChipIcon.SingleColorIcon -> null
+ null -> null
+ }
+
// Use a Box with `fillMaxHeight` to create a larger click surface for the chip. The visible
// height of the chip is determined by the height of the background of the Row below.
Box(
contentAlignment = Alignment.Center,
- modifier = modifier.fillMaxHeight().clickable(enabled = isClickable, onClick = onClick),
+ modifier =
+ modifier.fillMaxHeight().clickable(enabled = isClickable, onClick = onClick).semantics {
+ if (contentDescription != null) {
+ this.contentDescription = contentDescription
+ }
+ },
) {
Row(
horizontalArrangement = Arrangement.Center,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt
index e0c7645..d44646c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt
@@ -18,6 +18,7 @@
import android.view.View
import com.android.systemui.animation.Expandable
+import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.statusbar.StatusBarIconView
import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
@@ -140,7 +141,10 @@
* The icon is a custom icon, which is set on [impl]. The icon was likely created by an
* external app.
*/
- data class StatusBarView(val impl: StatusBarIconView) : ChipIcon {
+ data class StatusBarView(
+ val impl: StatusBarIconView,
+ val contentDescription: ContentDescription,
+ ) : ChipIcon {
init {
StatusBarConnectedDisplays.assertInLegacyMode()
}
@@ -150,7 +154,10 @@
* The icon is a custom icon, which is set on a notification, and can be looked up using the
* provided [notificationKey]. The icon was likely created by an external app.
*/
- data class StatusBarNotificationIcon(val notificationKey: String) : ChipIcon {
+ data class StatusBarNotificationIcon(
+ val notificationKey: String,
+ val contentDescription: ContentDescription,
+ ) : ChipIcon {
init {
StatusBarConnectedDisplays.assertInNewMode()
}
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 b057fb0..eeb7a40 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarStarter.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarStarter.kt
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.core
import android.view.Display
+import android.view.IWindowManager
import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
@@ -54,6 +55,7 @@
private val autoHideControllerStore: AutoHideControllerStore,
private val privacyDotWindowControllerStore: PrivacyDotWindowControllerStore,
private val lightBarControllerStore: LightBarControllerStore,
+ private val windowManager: IWindowManager,
) : CoreStartable {
init {
@@ -68,7 +70,13 @@
}
.onStart { emit(displayRepository.displays.value) }
.collect { newDisplays ->
- newDisplays.forEach { createAndStartComponentsForDisplay(it) }
+ newDisplays.forEach {
+ // TODO(b/393191204): Split navbar, status bar, etc. functionality
+ // from WindowManager#shouldShowSystemDecors.
+ if (windowManager.shouldShowSystemDecors(it.displayId)) {
+ createAndStartComponentsForDisplay(it)
+ }
+ }
}
}
}
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 351cdc8..b5a781e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt
@@ -24,6 +24,7 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.LogBufferFactory
+import com.android.systemui.statusbar.chips.sharetoapp.ui.viewmodel.ShareToAppChipViewModel
import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
import com.android.systemui.statusbar.data.StatusBarDataLayerModule
import com.android.systemui.statusbar.data.repository.LightBarControllerStore
@@ -140,6 +141,20 @@
@Provides
@SysUISingleton
@IntoMap
+ @ClassKey(ShareToAppChipViewModel::class)
+ fun providesShareToAppChipViewModel(
+ shareToAppChipViewModelLazy: Lazy<ShareToAppChipViewModel>
+ ): CoreStartable {
+ return if (com.android.media.projection.flags.Flags.showStopDialogPostCallEnd()) {
+ shareToAppChipViewModelLazy.get()
+ } else {
+ CoreStartable.NOP
+ }
+ }
+
+ @Provides
+ @SysUISingleton
+ @IntoMap
@ClassKey(MultiDisplayStatusBarWindowControllerStore::class)
fun multiDisplayControllerStoreAsCoreStartable(
storeLazy: Lazy<MultiDisplayStatusBarWindowControllerStore>
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractor.kt
index fd5973e..bde3c4d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractor.kt
@@ -21,10 +21,12 @@
import android.app.Notification.CallStyle.CALL_TYPE_UNKNOWN
import android.app.Notification.EXTRA_CALL_TYPE
import android.app.PendingIntent
+import android.content.Context
import android.graphics.drawable.Icon
import android.service.notification.StatusBarNotification
import android.util.ArrayMap
import com.android.app.tracing.traceSection
+import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.statusbar.StatusBarIconView
import com.android.systemui.statusbar.notification.collection.GroupEntry
import com.android.systemui.statusbar.notification.collection.ListEntry
@@ -50,6 +52,7 @@
constructor(
private val repository: ActiveNotificationListRepository,
private val sectionStyleProvider: SectionStyleProvider,
+ @Main private val context: Context,
) {
/**
* Sets the current list of rendered notification entries as displayed in the notification list.
@@ -57,7 +60,7 @@
fun setRenderedList(entries: List<ListEntry>) {
traceSection("RenderNotificationListInteractor.setRenderedList") {
repository.activeNotifications.update { existingModels ->
- buildActiveNotificationsStore(existingModels, sectionStyleProvider) {
+ buildActiveNotificationsStore(existingModels, sectionStyleProvider, context) {
entries.forEach(::addListEntry)
setRankingsMap(entries)
}
@@ -69,13 +72,17 @@
private fun buildActiveNotificationsStore(
existingModels: ActiveNotificationsStore,
sectionStyleProvider: SectionStyleProvider,
+ context: Context,
block: ActiveNotificationsStoreBuilder.() -> Unit,
): ActiveNotificationsStore =
- ActiveNotificationsStoreBuilder(existingModels, sectionStyleProvider).apply(block).build()
+ ActiveNotificationsStoreBuilder(existingModels, sectionStyleProvider, context)
+ .apply(block)
+ .build()
private class ActiveNotificationsStoreBuilder(
private val existingModels: ActiveNotificationsStore,
private val sectionStyleProvider: SectionStyleProvider,
+ private val context: Context,
) {
private val builder = ActiveNotificationsStore.Builder()
@@ -154,6 +161,7 @@
statusBarChipIconView = icons.statusBarChipIcon,
uid = sbn.uid,
packageName = sbn.packageName,
+ appName = sbn.notification.loadHeaderAppName(context),
contentIntent = sbn.notification.contentIntent,
instanceId = sbn.instanceId?.id,
isGroupSummary = sbn.notification.isGroupSummary,
@@ -180,6 +188,7 @@
statusBarChipIconView: StatusBarIconView?,
uid: Int,
packageName: String,
+ appName: String,
contentIntent: PendingIntent?,
instanceId: Int?,
isGroupSummary: Boolean,
@@ -206,6 +215,7 @@
instanceId = instanceId,
isGroupSummary = isGroupSummary,
packageName = packageName,
+ appName = appName,
contentIntent = contentIntent,
bucket = bucket,
callType = callType,
@@ -230,6 +240,7 @@
instanceId = instanceId,
isGroupSummary = isGroupSummary,
packageName = packageName,
+ appName = appName,
contentIntent = contentIntent,
bucket = bucket,
callType = callType,
@@ -253,6 +264,7 @@
statusBarChipIconView: StatusBarIconView?,
uid: Int,
packageName: String,
+ appName: String,
contentIntent: PendingIntent?,
instanceId: Int?,
isGroupSummary: Boolean,
@@ -278,6 +290,7 @@
instanceId != this.instanceId -> false
isGroupSummary != this.isGroupSummary -> false
packageName != this.packageName -> false
+ appName != this.appName -> false
contentIntent != this.contentIntent -> false
bucket != this.bucket -> false
callType != this.callType -> false
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/ActiveNotificationModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/ActiveNotificationModel.kt
index ab8be30..f00c3ae 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/ActiveNotificationModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/ActiveNotificationModel.kt
@@ -68,6 +68,8 @@
val uid: Int,
/** The notifying app's packageName. */
val packageName: String,
+ /** The notifying app's display name. */
+ val appName: String,
/** The intent to execute if UI related to this notification is clicked. */
val contentIntent: PendingIntent?,
/** A small per-notification ID, used for statsd logging. */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
index f740144..ece1803 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
@@ -28,6 +28,7 @@
import com.android.systemui.keyguard.ui.viewmodel.ViewStateAccessor
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.scene.shared.flag.SceneContainerFlag
+import com.android.systemui.shared.Flags.extendedWallpaperEffects
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
import com.android.systemui.statusbar.notification.stack.NotificationStackSizeCalculator
import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer
@@ -143,7 +144,7 @@
}
if (!SceneContainerFlag.isEnabled) {
- if (Flags.magicPortraitWallpapers()) {
+ if (extendedWallpaperEffects()) {
launch {
combine(
viewModel.getNotificationStackAbsoluteBottom(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
index a29934fa..949cb0a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
@@ -160,6 +160,7 @@
notificationIconView = currentInfo.notificationIconView,
intent = currentInfo.intent,
notificationKey = currentInfo.key,
+ appName = currentInfo.appName,
promotedContent = currentInfo.promotedContent,
)
} else {
@@ -217,6 +218,7 @@
notifModel.statusBarChipIconView,
notifModel.contentIntent,
notifModel.uid,
+ notifModel.appName,
notifModel.promotedContent,
isOngoing = true,
statusBarSwipedAway = callNotificationInfo?.statusBarSwipedAway ?: false,
@@ -337,6 +339,7 @@
val notificationIconView: StatusBarIconView?,
val intent: PendingIntent?,
val uid: Int,
+ val appName: String,
/**
* If the call notification also meets promoted notification criteria, this field is filled
* in with the content related to promotion. Otherwise null.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/domain/interactor/OngoingCallInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/domain/interactor/OngoingCallInteractor.kt
index ba7628f..2fd7d82 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/domain/interactor/OngoingCallInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/domain/interactor/OngoingCallInteractor.kt
@@ -163,6 +163,7 @@
notificationIconView = model.statusBarChipIconView,
intent = model.contentIntent,
notificationKey = model.key,
+ appName = model.appName,
promotedContent = model.promotedContent,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/shared/model/OngoingCallModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/shared/model/OngoingCallModel.kt
index 7d00e9d..6507b72 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/shared/model/OngoingCallModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/shared/model/OngoingCallModel.kt
@@ -42,6 +42,7 @@
* @property notificationIconView the [android.app.Notification.getSmallIcon] that's set on the
* call notification. We may use this icon in the chip instead of the default phone icon.
* @property intent the intent associated with the call notification.
+ * @property appName the user-readable name of the app that posted the call notification.
* @property promotedContent if the call notification also meets promoted notification criteria,
* this field is filled in with the content related to promotion. Otherwise null.
*/
@@ -50,6 +51,7 @@
val notificationIconView: StatusBarIconView?,
val intent: PendingIntent?,
val notificationKey: String,
+ val appName: String,
val promotedContent: PromotedNotificationContentModel?,
) : OngoingCallModel
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/HomeStatusBarViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/HomeStatusBarViewBinder.kt
index 7e76d77..bd69060 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/HomeStatusBarViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/HomeStatusBarViewBinder.kt
@@ -28,6 +28,7 @@
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.res.R
import com.android.systemui.scene.shared.flag.SceneContainerFlag
+import com.android.systemui.statusbar.chips.mediaprojection.domain.model.MediaProjectionStopDialogModel
import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
import com.android.systemui.statusbar.chips.ui.binder.OngoingActivityChipBinder
import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
@@ -115,6 +116,17 @@
}
}
+ if (com.android.media.projection.flags.Flags.showStopDialogPostCallEnd()) {
+ launch {
+ viewModel.mediaProjectionStopDialogDueToCallEndedState.collect { stopDialog
+ ->
+ if (stopDialog is MediaProjectionStopDialogModel.Shown) {
+ stopDialog.createAndShowDialog()
+ }
+ }
+ }
+ }
+
if (!StatusBarNotifChips.isEnabled && !StatusBarChipsModernization.isEnabled) {
val primaryChipViewBinding =
OngoingActivityChipBinder.createBinding(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModel.kt
index a59d95f..b116b47 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModel.kt
@@ -36,7 +36,9 @@
import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.statusbar.chips.mediaprojection.domain.model.MediaProjectionStopDialogModel
import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
+import com.android.systemui.statusbar.chips.sharetoapp.ui.viewmodel.ShareToAppChipViewModel
import com.android.systemui.statusbar.chips.ui.model.MultipleOngoingActivityChipsModel
import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipsViewModel
@@ -96,6 +98,12 @@
val transitionFromLockscreenToDreamStartedEvent: Flow<Unit>
/**
+ * The current media projection stop dialog to be shown, or
+ * `MediaProjectionStopDialogModel.Hidden` if no dialog is visible.
+ */
+ val mediaProjectionStopDialogDueToCallEndedState: StateFlow<MediaProjectionStopDialogModel>
+
+ /**
* The ongoing activity chip that should be primarily shown on the left-hand side of the status
* bar. If there are multiple ongoing activity chips, this one should take priority.
*/
@@ -180,6 +188,7 @@
sceneInteractor: SceneInteractor,
sceneContainerOcclusionInteractor: SceneContainerOcclusionInteractor,
shadeInteractor: ShadeInteractor,
+ shareToAppChipViewModel: ShareToAppChipViewModel,
ongoingActivityChipsViewModel: OngoingActivityChipsViewModel,
statusBarPopupChipsViewModel: StatusBarPopupChipsViewModel,
animations: SystemStatusEventAnimationInteractor,
@@ -206,6 +215,9 @@
.filter { it.transitionState == TransitionState.STARTED }
.map {}
+ override val mediaProjectionStopDialogDueToCallEndedState =
+ shareToAppChipViewModel.stopDialogToShow
+
override val primaryOngoingActivityChip = ongoingActivityChipsViewModel.primaryChip
override val ongoingActivityChips = ongoingActivityChipsViewModel.chips
diff --git a/packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/WallpaperRepository.kt b/packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/WallpaperRepository.kt
index 9794c61..79a9630 100644
--- a/packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/WallpaperRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/WallpaperRepository.kt
@@ -27,12 +27,13 @@
import androidx.annotation.VisibleForTesting
import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.internal.R
-import com.android.systemui.Flags
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.keyguard.data.repository.KeyguardRepository
+import com.android.systemui.res.R as SysUIR
import com.android.systemui.shared.Flags.ambientAod
+import com.android.systemui.shared.Flags.extendedWallpaperEffects
import com.android.systemui.user.data.model.SelectedUserModel
import com.android.systemui.user.data.model.SelectionStatus
import com.android.systemui.user.data.repository.UserRepository
@@ -66,7 +67,7 @@
/** Set rootView to get its windowToken afterwards */
var rootView: View?
- /** when we use magic portrait wallpapers, we should always get its bounds from keyguard */
+ /** some wallpapers require bounds to be sent from keyguard */
val shouldSendFocalArea: StateFlow<Boolean>
}
@@ -80,7 +81,7 @@
userRepository: UserRepository,
keyguardRepository: KeyguardRepository,
private val wallpaperManager: WallpaperManager,
- context: Context,
+ private val context: Context,
) : WallpaperRepository {
private val wallpaperChanged: Flow<Unit> =
broadcastDispatcher
@@ -125,8 +126,8 @@
override val shouldSendFocalArea =
wallpaperInfo
.map {
- val shouldSendNotificationLayout =
- it?.component?.className == MAGIC_PORTRAIT_CLASSNAME
+ val focalAreaTarget = context.resources.getString(SysUIR.string.focal_area_target)
+ val shouldSendNotificationLayout = it?.component?.className == focalAreaTarget
if (shouldSendNotificationLayout) {
sendLockscreenLayoutJob =
scope.launch {
@@ -167,9 +168,8 @@
}
.stateIn(
scope,
- // Always be listening for wallpaper changes when magic portrait flag is on
- if (Flags.magicPortraitWallpapers()) SharingStarted.Eagerly else WhileSubscribed(),
- initialValue = Flags.magicPortraitWallpapers(),
+ if (extendedWallpaperEffects()) SharingStarted.Eagerly else WhileSubscribed(),
+ initialValue = extendedWallpaperEffects(),
)
private suspend fun getWallpaper(selectedUser: SelectedUserModel): WallpaperInfo? {
@@ -177,9 +177,4 @@
wallpaperManager.getWallpaperInfoForUser(selectedUser.userInfo.id)
}
}
-
- companion object {
- const val MAGIC_PORTRAIT_CLASSNAME =
- "com.google.android.apps.magicportrait.service.MagicPortraitWallpaperService"
- }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaSwitchingControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaSwitchingControllerTest.java
index 86063ac..2715cb3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaSwitchingControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaSwitchingControllerTest.java
@@ -1512,6 +1512,60 @@
assertThat(getNumberOfConnectDeviceButtons()).isEqualTo(1);
}
+ @EnableFlags(Flags.FLAG_ENABLE_OUTPUT_SWITCHER_SESSION_GROUPING)
+ @Test
+ public void selectedDevicesAddedInSameOrder() {
+ when(mLocalMediaManager.isPreferenceRouteListingExist()).thenReturn(true);
+ doReturn(mMediaDevices)
+ .when(mLocalMediaManager)
+ .getSelectedMediaDevice();
+ mMediaSwitchingController.start(mCb);
+ reset(mCb);
+ mMediaSwitchingController.getMediaItemList().clear();
+
+ mMediaSwitchingController.onDeviceListUpdate(mMediaDevices);
+
+ List<MediaItem> items = mMediaSwitchingController.getMediaItemList();
+ assertThat(items.get(0).getMediaDevice().get()).isEqualTo(mMediaDevice1);
+ assertThat(items.get(1).getMediaDevice().get()).isEqualTo(mMediaDevice2);
+ }
+
+ @DisableFlags(Flags.FLAG_ENABLE_OUTPUT_SWITCHER_SESSION_GROUPING)
+ @Test
+ public void selectedDevicesAddedInReverseOrder() {
+ when(mLocalMediaManager.isPreferenceRouteListingExist()).thenReturn(true);
+ doReturn(mMediaDevices)
+ .when(mLocalMediaManager)
+ .getSelectedMediaDevice();
+ mMediaSwitchingController.start(mCb);
+ reset(mCb);
+ mMediaSwitchingController.getMediaItemList().clear();
+
+ mMediaSwitchingController.onDeviceListUpdate(mMediaDevices);
+
+ List<MediaItem> items = mMediaSwitchingController.getMediaItemList();
+ assertThat(items.get(0).getMediaDevice().get()).isEqualTo(mMediaDevice2);
+ assertThat(items.get(1).getMediaDevice().get()).isEqualTo(mMediaDevice1);
+ }
+
+ @EnableFlags(Flags.FLAG_ENABLE_OUTPUT_SWITCHER_SESSION_GROUPING)
+ @Test
+ public void firstSelectedDeviceIsFirstDeviceInGroupIsTrue() {
+ when(mLocalMediaManager.isPreferenceRouteListingExist()).thenReturn(true);
+ doReturn(mMediaDevices)
+ .when(mLocalMediaManager)
+ .getSelectedMediaDevice();
+ mMediaSwitchingController.start(mCb);
+ reset(mCb);
+ mMediaSwitchingController.getMediaItemList().clear();
+
+ mMediaSwitchingController.onDeviceListUpdate(mMediaDevices);
+
+ List<MediaItem> items = mMediaSwitchingController.getMediaItemList();
+ assertThat(items.get(0).isFirstDeviceInGroup()).isTrue();
+ assertThat(items.get(1).isFirstDeviceInGroup()).isFalse();
+ }
+
private int getNumberOfConnectDeviceButtons() {
int numberOfConnectDeviceButtons = 0;
for (MediaItem item : mMediaSwitchingController.getMediaItemList()) {
diff --git a/packages/SystemUI/tests/utils/src/android/view/WindowManagerKosmos.kt b/packages/SystemUI/tests/utils/src/android/view/WindowManagerKosmos.kt
index 025f556..80254d5 100644
--- a/packages/SystemUI/tests/utils/src/android/view/WindowManagerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/android/view/WindowManagerKosmos.kt
@@ -24,4 +24,6 @@
val Kosmos.mockWindowManager: WindowManager by Kosmos.Fixture { mock(WindowManager::class.java) }
+val Kosmos.mockIWindowManager: IWindowManager by Kosmos.Fixture { mock(IWindowManager::class.java) }
+
var Kosmos.windowManager: WindowManager by Kosmos.Fixture { mockWindowManager }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepositoryKosmos.kt
index ef9bd82..5793695 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepositoryKosmos.kt
@@ -62,6 +62,7 @@
defaultSettingsPopupMenuSection = mock(),
defaultStatusBarSection = mock(),
defaultNotificationStackScrollLayoutSection = mock(),
+ aodPromotedNotificationSection = mock(),
aodNotificationIconsSection = mock(),
aodBurnInSection = mock(),
clockSection = keyguardClockSection,
@@ -69,7 +70,6 @@
keyguardSliceViewSection = mock(),
udfpsAccessibilityOverlaySection = mock(),
accessibilityActionsSection = mock(),
- aodPromotedNotificationSection = mock(),
)
}
@@ -84,6 +84,7 @@
defaultStatusBarSection = mock(),
splitShadeNotificationStackScrollLayoutSection = mock(),
splitShadeGuidelines = mock(),
+ aodPromotedNotificationSection = mock(),
aodNotificationIconsSection = mock(),
aodBurnInSection = mock(),
clockSection = keyguardClockSection,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorKosmos.kt
index d5bdbdb..1b1fe59 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorKosmos.kt
@@ -20,6 +20,7 @@
import com.android.systemui.keyguard.data.lightRevealScrimRepository
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.power.domain.interactor.powerInteractor
val Kosmos.lightRevealScrimInteractor by
@@ -30,5 +31,6 @@
applicationCoroutineScope,
scrimLogger,
{ powerInteractor },
+ backgroundDispatcher = testDispatcher,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/mediaprojection/data/repository/FakeMediaProjectionRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/mediaprojection/data/repository/FakeMediaProjectionRepository.kt
index e6256a5..6c52d54 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/mediaprojection/data/repository/FakeMediaProjectionRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/mediaprojection/data/repository/FakeMediaProjectionRepository.kt
@@ -19,6 +19,8 @@
import android.app.ActivityManager
import android.media.projection.StopReason
import com.android.systemui.mediaprojection.data.model.MediaProjectionState
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
class FakeMediaProjectionRepository : MediaProjectionRepository {
@@ -27,9 +29,18 @@
override val mediaProjectionState: MutableStateFlow<MediaProjectionState> =
MutableStateFlow(MediaProjectionState.NotProjecting)
+ private val _projectionStartedDuringCallAndActivePostCallEvent = MutableSharedFlow<Unit>()
+
+ override val projectionStartedDuringCallAndActivePostCallEvent: Flow<Unit> =
+ _projectionStartedDuringCallAndActivePostCallEvent
+
var stopProjectingInvoked = false
override suspend fun stopProjecting(@StopReason stopReason: Int) {
stopProjectingInvoked = true
}
+
+ suspend fun emitProjectionStartedDuringCallAndActivePostCallEvent() {
+ _projectionStartedDuringCallAndActivePostCallEvent.emit(Unit)
+ }
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/mediaprojection/taskswitcher/FakeMediaProjectionManager.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/mediaprojection/taskswitcher/FakeMediaProjectionManager.kt
index 2b6032c..9b60051 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/mediaprojection/taskswitcher/FakeMediaProjectionManager.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/mediaprojection/taskswitcher/FakeMediaProjectionManager.kt
@@ -16,6 +16,7 @@
package com.android.systemui.mediaprojection.taskswitcher
+import android.media.projection.MediaProjectionEvent
import android.media.projection.MediaProjectionInfo
import android.media.projection.MediaProjectionManager
import android.os.Binder
@@ -61,14 +62,22 @@
fun dispatchOnSessionSet(
info: MediaProjectionInfo = DEFAULT_INFO,
- session: ContentRecordingSession?
+ session: ContentRecordingSession?,
) {
callbacks.forEach { it.onRecordingSessionSet(info, session) }
}
+ fun dispatchEvent(
+ event: MediaProjectionEvent,
+ info: MediaProjectionInfo? = DEFAULT_INFO,
+ session: ContentRecordingSession? = null,
+ ) {
+ callbacks.forEach { it.onMediaProjectionEvent(event, info, session) }
+ }
+
companion object {
fun createDisplaySession(): ContentRecordingSession =
- ContentRecordingSession.createDisplaySession(/* displayToMirror = */ 123)
+ ContentRecordingSession.createDisplaySession(/* displayToMirror= */ 123)
fun createSingleTaskSession(token: IBinder = Binder()): ContentRecordingSession =
ContentRecordingSession.createTaskSession(token)
@@ -76,10 +85,6 @@
private const val DEFAULT_PACKAGE_NAME = "com.media.projection.test"
private val DEFAULT_USER_HANDLE = UserHandle.getUserHandleForUid(UserHandle.myUserId())
private val DEFAULT_INFO =
- MediaProjectionInfo(
- DEFAULT_PACKAGE_NAME,
- DEFAULT_USER_HANDLE,
- /* launchCookie = */ null
- )
+ MediaProjectionInfo(DEFAULT_PACKAGE_NAME, DEFAULT_USER_HANDLE, /* launchCookie= */ null)
}
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelKosmos.kt
index 1e304d9..ab61a3e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelKosmos.kt
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.chips.call.ui.viewmodel
+import android.content.applicationContext
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.plugins.activityStarter
@@ -26,6 +27,7 @@
val Kosmos.callChipViewModel: CallChipViewModel by
Kosmos.Fixture {
CallChipViewModel(
+ applicationContext,
scope = applicationCoroutineScope,
interactor = callChipInteractor,
systemClock = fakeSystemClock,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelKosmos.kt
index d0c80c7..878c2de 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelKosmos.kt
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.chips.notification.ui.viewmodel
+import android.content.applicationContext
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.statusbar.chips.notification.domain.interactor.statusBarNotificationChipsInteractor
@@ -24,6 +25,7 @@
val Kosmos.notifChipsViewModel: NotifChipsViewModel by
Kosmos.Fixture {
NotifChipsViewModel(
+ applicationContext,
applicationCoroutineScope,
statusBarNotificationChipsInteractor,
headsUpNotificationInteractor,
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 8c37bd7..bdcab5f 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
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.core
import android.content.testableContext
+import android.view.mockIWindowManager
import com.android.systemui.bouncer.domain.interactor.primaryBouncerInteractor
import com.android.systemui.display.data.repository.displayRepository
import com.android.systemui.display.data.repository.displayScopeRepository
@@ -84,5 +85,6 @@
multiDisplayAutoHideControllerStore,
privacyDotWindowControllerStore,
lightBarControllerStore,
+ mockIWindowManager,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/model/ActiveNotificationModelBuilder.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/model/ActiveNotificationModelBuilder.kt
index c6ae15d..63085e1 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/model/ActiveNotificationModelBuilder.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/model/ActiveNotificationModelBuilder.kt
@@ -43,6 +43,7 @@
instanceId: Int? = null,
isGroupSummary: Boolean = false,
packageName: String = "pkg",
+ appName: String = "appName",
contentIntent: PendingIntent? = null,
bucket: Int = BUCKET_UNKNOWN,
callType: CallType = CallType.None,
@@ -64,6 +65,7 @@
statusBarChipIconView = statusBarChipIcon,
uid = uid,
packageName = packageName,
+ appName = appName,
contentIntent = contentIntent,
instanceId = instanceId,
isGroupSummary = isGroupSummary,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractorKosmos.kt
index f7acae9..0acf98f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractorKosmos.kt
@@ -16,11 +16,16 @@
package com.android.systemui.statusbar.notification.domain.interactor
+import android.content.applicationContext
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.statusbar.notification.collection.provider.sectionStyleProvider
import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository
val Kosmos.renderNotificationListInteractor by
Kosmos.Fixture {
- RenderNotificationListInteractor(activeNotificationListRepository, sectionStyleProvider)
+ RenderNotificationListInteractor(
+ activeNotificationListRepository,
+ sectionStyleProvider,
+ applicationContext,
+ )
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/ongoingcall/shared/model/OngoingCallModelBuilder.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/ongoingcall/shared/model/OngoingCallModelBuilder.kt
index f4e74fe..923b36d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/ongoingcall/shared/model/OngoingCallModelBuilder.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/ongoingcall/shared/model/OngoingCallModelBuilder.kt
@@ -26,5 +26,14 @@
notificationIcon: StatusBarIconView? = null,
intent: PendingIntent? = null,
notificationKey: String = "test",
+ appName: String = "",
promotedContent: PromotedNotificationContentModel? = null,
-) = OngoingCallModel.InCall(startTimeMs, notificationIcon, intent, notificationKey, promotedContent)
+) =
+ OngoingCallModel.InCall(
+ startTimeMs,
+ notificationIcon,
+ intent,
+ notificationKey,
+ appName,
+ promotedContent,
+ )
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelKosmos.kt
index f8bf3c3..1626904 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelKosmos.kt
@@ -25,6 +25,7 @@
import com.android.systemui.scene.domain.interactor.sceneContainerOcclusionInteractor
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.shade.domain.interactor.shadeInteractor
+import com.android.systemui.statusbar.chips.sharetoapp.ui.viewmodel.shareToAppChipViewModel
import com.android.systemui.statusbar.chips.ui.viewmodel.ongoingActivityChipsViewModel
import com.android.systemui.statusbar.events.domain.interactor.systemStatusEventAnimationInteractor
import com.android.systemui.statusbar.featurepods.popups.ui.viewmodel.statusBarPopupChipsViewModel
@@ -53,6 +54,7 @@
sceneInteractor,
sceneContainerOcclusionInteractor,
shadeInteractor,
+ shareToAppChipViewModel,
ongoingActivityChipsViewModel,
statusBarPopupChipsViewModel,
systemStatusEventAnimationInteractor,
diff --git a/services/appfunctions/java/com/android/server/appfunctions/CallerValidator.java b/services/appfunctions/java/com/android/server/appfunctions/CallerValidator.java
index 6191767..98ef974 100644
--- a/services/appfunctions/java/com/android/server/appfunctions/CallerValidator.java
+++ b/services/appfunctions/java/com/android/server/appfunctions/CallerValidator.java
@@ -60,9 +60,7 @@
* Validates that the caller can execute the specified app function.
*
* <p>The caller can execute if the app function's package name is the same as the caller's
- * package or the caller has either {@link Manifest.permission#EXECUTE_APP_FUNCTIONS_TRUSTED} or
- * {@link Manifest.permission#EXECUTE_APP_FUNCTIONS} granted. In some cases, app functions can
- * still opt-out of caller having {@link Manifest.permission#EXECUTE_APP_FUNCTIONS}.
+ * package or the caller has the {@link Manifest.permission#EXECUTE_APP_FUNCTIONS} granted.
*
* @param callingUid The calling uid.
* @param callingPid The calling pid.
diff --git a/services/appfunctions/java/com/android/server/appfunctions/CallerValidatorImpl.java b/services/appfunctions/java/com/android/server/appfunctions/CallerValidatorImpl.java
index 69481c3..fe163d7 100644
--- a/services/appfunctions/java/com/android/server/appfunctions/CallerValidatorImpl.java
+++ b/services/appfunctions/java/com/android/server/appfunctions/CallerValidatorImpl.java
@@ -18,7 +18,6 @@
import static android.app.appfunctions.AppFunctionStaticMetadataHelper.APP_FUNCTION_STATIC_METADATA_DB;
import static android.app.appfunctions.AppFunctionStaticMetadataHelper.APP_FUNCTION_STATIC_NAMESPACE;
-import static android.app.appfunctions.AppFunctionStaticMetadataHelper.STATIC_PROPERTY_RESTRICT_CALLERS_WITH_EXECUTE_APP_FUNCTIONS;
import static android.app.appfunctions.AppFunctionStaticMetadataHelper.getDocumentIdForAppFunction;
import static com.android.server.appfunctions.AppFunctionExecutors.THREAD_POOL_EXECUTOR;
@@ -84,12 +83,7 @@
}
@Override
- @RequiresPermission(
- anyOf = {
- Manifest.permission.EXECUTE_APP_FUNCTIONS_TRUSTED,
- Manifest.permission.EXECUTE_APP_FUNCTIONS
- },
- conditional = true)
+ @RequiresPermission(Manifest.permission.EXECUTE_APP_FUNCTIONS)
public AndroidFuture<Boolean> verifyCallerCanExecuteAppFunction(
int callingUid,
int callingPid,
@@ -101,17 +95,6 @@
return AndroidFuture.completedFuture(true);
}
- boolean hasTrustedExecutionPermission =
- mContext.checkPermission(
- Manifest.permission.EXECUTE_APP_FUNCTIONS_TRUSTED,
- callingPid,
- callingUid)
- == PackageManager.PERMISSION_GRANTED;
-
- if (hasTrustedExecutionPermission) {
- return AndroidFuture.completedFuture(true);
- }
-
boolean hasExecutionPermission =
mContext.checkPermission(
Manifest.permission.EXECUTE_APP_FUNCTIONS, callingPid, callingUid)
@@ -138,7 +121,8 @@
.build())
.thenApply(
batchResult -> getGenericDocumentFromBatchResult(batchResult, documentId))
- .thenApply(document -> !getRestrictCallersWithExecuteAppFunctionsProperty(document))
+ // At this point, already checked the app has the permission.
+ .thenApply(document -> true)
.whenComplete(
(result, throwable) -> {
futureAppSearchSession.close();
@@ -160,12 +144,6 @@
+ failedResult.getErrorMessage());
}
- private static boolean getRestrictCallersWithExecuteAppFunctionsProperty(
- GenericDocument genericDocument) {
- return genericDocument.getPropertyBoolean(
- STATIC_PROPERTY_RESTRICT_CALLERS_WITH_EXECUTE_APP_FUNCTIONS);
- }
-
@Override
public boolean verifyEnterprisePolicyIsAllowed(
@NonNull UserHandle callingUser, @NonNull UserHandle targetUser) {
diff --git a/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncAdapter.java b/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncAdapter.java
index cc73288..9d13e37 100644
--- a/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncAdapter.java
+++ b/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncAdapter.java
@@ -78,7 +78,6 @@
// Hidden constants in {@link SetSchemaRequest} that restricts runtime metadata visibility
// by permissions.
public static final int EXECUTE_APP_FUNCTIONS = 9;
- public static final int EXECUTE_APP_FUNCTIONS_TRUSTED = 10;
public MetadataSyncAdapter(
@NonNull PackageManager packageManager, @NonNull AppSearchManager appSearchManager) {
@@ -281,8 +280,6 @@
new PackageIdentifier(packageName, packageCert));
setSchemaRequestBuilder.addRequiredPermissionsForSchemaTypeVisibility(
runtimeMetadataSchema.getSchemaType(), Set.of(EXECUTE_APP_FUNCTIONS));
- setSchemaRequestBuilder.addRequiredPermissionsForSchemaTypeVisibility(
- runtimeMetadataSchema.getSchemaType(), Set.of(EXECUTE_APP_FUNCTIONS_TRUSTED));
}
return setSchemaRequestBuilder.build();
}
diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
index aef1c08..d47aab0 100644
--- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
+++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
@@ -4673,12 +4673,6 @@
keep.add(providerId);
// Use the new AppWidgetProviderInfo.
provider.setPartialInfoLocked(info);
- // Clear old previews
- if (remoteViewsProto()) {
- clearGeneratedPreviewsAsync(provider);
- } else {
- provider.clearGeneratedPreviewsLocked();
- }
// If it's enabled
final int M = provider.widgets.size();
if (M > 0) {
@@ -5104,6 +5098,10 @@
AndroidFuture<RemoteViews> result = new AndroidFuture<>();
mSavePreviewsHandler.post(() -> {
SparseArray<RemoteViews> previews = loadGeneratedPreviews(provider);
+ if (previews.size() == 0 && provider.info.generatedPreviewCategories != 0) {
+ // Failed to read previews from file, clear the file and update providers.
+ saveGeneratedPreviews(provider, previews, /* notify= */ true);
+ }
for (int i = 0; i < previews.size(); i++) {
if ((widgetCategory & previews.keyAt(i)) != 0) {
result.complete(previews.valueAt(i));
@@ -5222,8 +5220,14 @@
continue;
}
ProtoInputStream input = new ProtoInputStream(previewsFile.readFully());
- provider.info.generatedPreviewCategories = readGeneratedPreviewCategoriesFromProto(
- input);
+ try {
+ provider.info.generatedPreviewCategories = readGeneratedPreviewCategoriesFromProto(
+ input);
+ } catch (IOException e) {
+ Slog.e(TAG, "Failed to read generated previews from file for " + provider, e);
+ previewsFile.delete();
+ provider.info.generatedPreviewCategories = 0;
+ }
if (DEBUG) {
Slog.i(TAG, TextUtils.formatSimple(
"loadGeneratedPreviewCategoriesLocked %d %s categories %d", profileId,
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index 02a8f62..521f676 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -47,6 +47,7 @@
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
import android.app.AppOpsManager;
+import android.app.KeyguardManager;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.ecm.EnhancedConfirmationManager;
@@ -302,8 +303,17 @@
enforceCallerCanManageAssociationsForPackage(getContext(), userId, packageName,
"create associations");
- mAssociationRequestsProcessor.processNewAssociationRequest(
- request, packageName, userId, callback);
+ if (request.isSkipRoleGrant()) {
+ checkCallerCanSkipRoleGrant();
+ mAssociationRequestsProcessor.createAssociation(userId, packageName,
+ /* macAddress= */ null, request.getDisplayName(),
+ request.getDeviceProfile(), /* associatedDevice= */ null,
+ request.isSelfManaged(), callback, /* resultReceiver= */ null,
+ request.getDeviceIcon(), /* skipRoleGrant= */ true);
+ } else {
+ mAssociationRequestsProcessor.processNewAssociationRequest(
+ request, packageName, userId, callback);
+ }
}
@Override
@@ -669,7 +679,7 @@
final MacAddress macAddressObj = MacAddress.fromString(macAddress);
mAssociationRequestsProcessor.createAssociation(userId, packageName, macAddressObj,
- null, null, null, false, null, null, null);
+ null, null, null, false, null, null, null, false);
}
private void checkCanCallNotificationApi(String callingPackage, int userId) {
@@ -684,6 +694,19 @@
"App must have an association before calling this API");
}
+ private void checkCallerCanSkipRoleGrant() {
+ final KeyguardManager keyguardManager =
+ getContext().getSystemService(KeyguardManager.class);
+ if (keyguardManager != null && keyguardManager.isKeyguardSecure()) {
+ throw new SecurityException("Skipping CDM role grant requires insecure keyguard.");
+ }
+ if (getContext().checkCallingPermission(ASSOCIATE_COMPANION_DEVICES)
+ != PERMISSION_GRANTED) {
+ throw new SecurityException(
+ "Skipping CDM role grant requires ASSOCIATE_COMPANION_DEVICES permission.");
+ }
+ }
+
@Override
public boolean canPairWithoutPrompt(String packageName, String macAddress, int userId) {
final AssociationInfo association =
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
index 3508f2f..e7d1460 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
@@ -106,8 +106,9 @@
boolean selfManaged = getNextBooleanArg();
final MacAddress macAddress = MacAddress.fromString(address);
mAssociationRequestsProcessor.createAssociation(userId, packageName, macAddress,
- deviceProfile, deviceProfile, /* associatedDevice */ null, selfManaged,
- /* callback */ null, /* resultReceiver */ null, /* deviceIcon */ null);
+ deviceProfile, deviceProfile, /* associatedDevice= */ null, selfManaged,
+ /* callback= */ null, /* resultReceiver= */ null,
+ /* deviceIcon= */ null, /* skipRoleGrant= */ false);
}
break;
diff --git a/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java b/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java
index 899b302..8c2c63c 100644
--- a/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java
+++ b/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java
@@ -282,8 +282,8 @@
Binder.withCleanCallingIdentity(() -> {
createAssociation(userId, packageName, macAddress, request.getDisplayName(),
request.getDeviceProfile(), request.getAssociatedDevice(),
- request.isSelfManaged(),
- callback, resultReceiver, request.getDeviceIcon());
+ request.isSelfManaged(), callback, resultReceiver, request.getDeviceIcon(),
+ /* skipRoleGrant= */ false);
});
}
@@ -294,7 +294,8 @@
@Nullable MacAddress macAddress, @Nullable CharSequence displayName,
@Nullable String deviceProfile, @Nullable AssociatedDevice associatedDevice,
boolean selfManaged, @Nullable IAssociationRequestCallback callback,
- @Nullable ResultReceiver resultReceiver, @Nullable Icon deviceIcon) {
+ @Nullable ResultReceiver resultReceiver, @Nullable Icon deviceIcon,
+ boolean skipRoleGrant) {
final int id = mAssociationStore.getNextId();
final long timestamp = System.currentTimeMillis();
@@ -303,8 +304,17 @@
selfManaged, /* notifyOnDeviceNearby */ false, /* revoked */ false,
/* pending */ false, timestamp, Long.MAX_VALUE, /* systemDataSyncFlags */ 0,
deviceIcon, /* deviceId */ null);
- // Add role holder for association (if specified) and add new association to store.
- maybeGrantRoleAndStoreAssociation(association, callback, resultReceiver);
+
+ if (skipRoleGrant) {
+ Slog.i(TAG, "Created association for " + association.getDeviceProfile() + " and userId="
+ + association.getUserId() + ", packageName="
+ + association.getPackageName() + " without granting role");
+ mAssociationStore.addAssociation(association);
+ sendCallbackAndFinish(association, callback, resultReceiver);
+ } else {
+ // Add role holder for association (if specified) and add new association to store.
+ maybeGrantRoleAndStoreAssociation(association, callback, resultReceiver);
+ }
}
/**
diff --git a/services/core/java/com/android/server/TradeInModeService.java b/services/core/java/com/android/server/TradeInModeService.java
index 70a0330..a696673 100644
--- a/services/core/java/com/android/server/TradeInModeService.java
+++ b/services/core/java/com/android/server/TradeInModeService.java
@@ -137,12 +137,13 @@
Slog.i(TAG, "Not starting trade-in mode, device is setup.");
return false;
}
- if (SystemProperties.getInt("ro.debuggable", 0) == 1) {
- // We don't want to force adbd into TIM on debug builds.
- Slog.e(TAG, "Not starting trade-in mode, device is debuggable.");
- return false;
- }
- if (isAdbEnabled()) {
+ if (isDebuggable()) {
+ if (!isForceEnabledForTesting()) {
+ // We don't want to force adbd into TIM on debug builds.
+ Slog.e(TAG, "Not starting trade-in mode, device is debuggable.");
+ return false;
+ }
+ } else if (isAdbEnabled()) {
Slog.e(TAG, "Not starting trade-in mode, adb is already enabled.");
return false;
}
@@ -234,6 +235,10 @@
return SystemProperties.getInt("ro.debuggable", 0) == 1;
}
+ private boolean isForceEnabledForTesting() {
+ return SystemProperties.getInt("persist.adb.test_tradeinmode", 0) == 1;
+ }
+
private boolean isAdbEnabled() {
final ContentResolver cr = mContext.getContentResolver();
return Settings.Global.getInt(cr, Settings.Global.ADB_ENABLED, 0) == 1;
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 0603c45..6ece265 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -19397,13 +19397,13 @@
if (((intent.getExtendedFlags() & Intent.EXTENDED_FLAG_NESTED_INTENT_KEYS_COLLECTED) == 0)
&& intent.getExtras() != null && intent.getExtras().hasIntent()) {
Slog.wtf(TAG,
- "[IntentRedirect] The intent does not have its nested keys collected as a "
+ "[IntentRedirect Hardening] The intent does not have its nested keys collected as a "
+ "preparation for creating intent creator tokens. Intent: "
+ intent + "; creatorPackage: " + creatorPackage);
if (preventIntentRedirectShowToastIfNestedKeysNotCollectedRW()) {
UiThread.getHandler().post(
() -> Toast.makeText(mContext,
- "Nested keys not collected. go/report-bug-intentRedir to report a"
+ "Nested keys not collected, activity launch won't be blocked. go/report-bug-intentRedir to report a"
+ " bug", Toast.LENGTH_LONG).show());
}
if (preventIntentRedirectThrowExceptionIfNestedKeysNotCollected()) {
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index 8335998..e8a2226 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -237,6 +237,13 @@
*/
private static final int CURRENT_VERSION = 1;
+ /**
+ * The upper limit of total number of attributed op entries that can be returned in a binder
+ * transaction to avoid TransactionTooLargeException
+ */
+ private static final int NUM_ATTRIBUTED_OP_ENTRY_THRESHOLD = 2000;
+
+
private SensorPrivacyManager mSensorPrivacyManager;
// Write at most every 30 minutes.
@@ -1702,6 +1709,8 @@
Manifest.permission.GET_APP_OPS_STATS,
Binder.getCallingPid(), Binder.getCallingUid())
== PackageManager.PERMISSION_GRANTED;
+ int totalAttributedOpEntryCount = 0;
+
if (ops == null) {
resOps = new ArrayList<>();
for (int j = 0; j < pkgOps.size(); j++) {
@@ -1709,7 +1718,12 @@
if (opRestrictsRead(curOp.op) && !shouldReturnRestrictedAppOps) {
continue;
}
- resOps.add(getOpEntryForResult(curOp, persistentDeviceId));
+ if (totalAttributedOpEntryCount > NUM_ATTRIBUTED_OP_ENTRY_THRESHOLD) {
+ break;
+ }
+ OpEntry opEntry = getOpEntryForResult(curOp, persistentDeviceId);
+ resOps.add(opEntry);
+ totalAttributedOpEntryCount += opEntry.getAttributedOpEntries().size();
}
} else {
for (int j = 0; j < ops.length; j++) {
@@ -1721,10 +1735,21 @@
if (opRestrictsRead(curOp.op) && !shouldReturnRestrictedAppOps) {
continue;
}
- resOps.add(getOpEntryForResult(curOp, persistentDeviceId));
+ if (totalAttributedOpEntryCount > NUM_ATTRIBUTED_OP_ENTRY_THRESHOLD) {
+ break;
+ }
+ OpEntry opEntry = getOpEntryForResult(curOp, persistentDeviceId);
+ resOps.add(opEntry);
+ totalAttributedOpEntryCount += opEntry.getAttributedOpEntries().size();
}
}
}
+
+ if (totalAttributedOpEntryCount > NUM_ATTRIBUTED_OP_ENTRY_THRESHOLD) {
+ Slog.w(TAG, "The number of attributed op entries has exceeded the threshold. This "
+ + "could be due to DoS attack from malicious apps. The result is throttled.");
+ }
+
return resOps;
}
diff --git a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
index 373287d..a1e8f08 100644
--- a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
+++ b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
@@ -61,7 +61,7 @@
private final FlagState mDisplayTopology = new FlagState(
Flags.FLAG_DISPLAY_TOPOLOGY,
- Flags::displayTopology);
+ DesktopExperienceFlags.DISPLAY_TOPOLOGY::isTrue);
private final FlagState mConnectedDisplayErrorHandlingFlagState = new FlagState(
Flags.FLAG_ENABLE_CONNECTED_DISPLAY_ERROR_HANDLING,
@@ -267,7 +267,7 @@
private final FlagState mBaseDensityForExternalDisplays = new FlagState(
Flags.FLAG_BASE_DENSITY_FOR_EXTERNAL_DISPLAYS,
- Flags::baseDensityForExternalDisplays
+ DesktopExperienceFlags.BASE_DENSITY_FOR_EXTERNAL_DISPLAYS::isTrue
);
private final FlagState mFramerateOverrideTriggersRrCallbacks = new FlagState(
diff --git a/services/core/java/com/android/server/display/feature/display_flags.aconfig b/services/core/java/com/android/server/display/feature/display_flags.aconfig
index bbd0e41..7890db1 100644
--- a/services/core/java/com/android/server/display/feature/display_flags.aconfig
+++ b/services/core/java/com/android/server/display/feature/display_flags.aconfig
@@ -509,3 +509,11 @@
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "display_category_built_in"
+ namespace: "display_manager"
+ description: "Add a new category to get the built in displays."
+ bug: "293651324"
+ is_fixed_read_only: false
+}
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 43fb8d0..d00ac4d 100644
--- a/services/core/java/com/android/server/media/quality/MediaQualityService.java
+++ b/services/core/java/com/android/server/media/quality/MediaQualityService.java
@@ -35,6 +35,7 @@
import android.hardware.tv.mediaquality.ISoundProfileAdjustmentListener;
import android.hardware.tv.mediaquality.ISoundProfileChangedListener;
import android.hardware.tv.mediaquality.ParamCapability;
+import android.hardware.tv.mediaquality.ParameterRange;
import android.hardware.tv.mediaquality.PictureParameter;
import android.hardware.tv.mediaquality.PictureParameters;
import android.hardware.tv.mediaquality.SoundParameter;
@@ -1133,7 +1134,39 @@
@Override
public List<ParameterCapability> getParameterCapabilities(
List<String> names, UserHandle user) {
- return new ArrayList<>();
+ byte[] byteArray = MediaQualityUtils.convertParameterToByteArray(names);
+ ParamCapability[] caps = new ParamCapability[byteArray.length];
+ try {
+ mMediaQuality.getParamCaps(byteArray, caps);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to get parameter capabilities", e);
+ }
+
+ return getListParameterCapability(caps);
+ }
+
+ private List<ParameterCapability> getListParameterCapability(ParamCapability[] caps) {
+ List<ParameterCapability> pcList = new ArrayList<>();
+ for (ParamCapability pcHal : caps) {
+ String name = MediaQualityUtils.getParameterName(pcHal.name);
+ boolean isSupported = pcHal.isSupported;
+ int type = pcHal.defaultValue == null ? 0 : pcHal.defaultValue.getTag() + 1;
+ Bundle bundle = convertToCaps(pcHal.range);
+
+ pcList.add(new ParameterCapability(name, isSupported, type, bundle));
+ }
+ return pcList;
+ }
+
+ private Bundle convertToCaps(ParameterRange range) {
+ Bundle bundle = new Bundle();
+ bundle.putObject("INT_MIN_MAX", range.numRange.getIntMinMax());
+ bundle.putObject("INT_VALUES_SUPPORTED", range.numRange.getIntValuesSupported());
+ bundle.putObject("DOUBLE_MIN_MAX", range.numRange.getDoubleMinMax());
+ bundle.putObject("DOUBLE_VALUES_SUPPORTED", range.numRange.getDoubleValuesSupported());
+ bundle.putObject("LONG_MIN_MAX", range.numRange.getLongMinMax());
+ bundle.putObject("LONG_VALUES_SUPPORTED", range.numRange.getLongValuesSupported());
+ return bundle;
}
@GuardedBy("mPictureProfileLock")
diff --git a/services/core/java/com/android/server/media/quality/MediaQualityUtils.java b/services/core/java/com/android/server/media/quality/MediaQualityUtils.java
index a74f977..5bd4420 100644
--- a/services/core/java/com/android/server/media/quality/MediaQualityUtils.java
+++ b/services/core/java/com/android/server/media/quality/MediaQualityUtils.java
@@ -20,6 +20,7 @@
import android.database.Cursor;
import android.hardware.tv.mediaquality.DolbyAudioProcessing;
import android.hardware.tv.mediaquality.DtsVirtualX;
+import android.hardware.tv.mediaquality.ParameterName;
import android.hardware.tv.mediaquality.PictureParameter;
import android.hardware.tv.mediaquality.SoundParameter;
import android.media.quality.MediaQualityContract.BaseParameters;
@@ -37,8 +38,11 @@
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
+import java.util.Map;
import java.util.UUID;
/**
@@ -1012,6 +1016,495 @@
);
}
+ /**
+ * Convert parameter to byte array.
+ */
+ public static byte[] convertParameterToByteArray(List<String> names) {
+ /**
+ * TODO Add following to ParameterName & add conversion here.
+ * - PICTURE_QUALITY_EVENT_TYPE
+ * - PANEL_INIT_MAX_LUMINCE_NITS
+ */
+
+ HashSet<String> nameMap = new HashSet<>(names);
+
+ List<Byte> bytes = new ArrayList<>();
+ // Picture Quality parameters
+ if (nameMap.contains(PictureQuality.PARAMETER_BRIGHTNESS)) {
+ bytes.add(ParameterName.BRIGHTNESS);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_BRIGHTNESS)) {
+ bytes.add(ParameterName.BRIGHTNESS);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_CONTRAST)) {
+ bytes.add(ParameterName.CONTRAST);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_SHARPNESS)) {
+ bytes.add(ParameterName.SHARPNESS);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_SATURATION)) {
+ bytes.add(ParameterName.SATURATION);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_HUE)) {
+ bytes.add(ParameterName.HUE);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_BRIGHTNESS)) {
+ bytes.add(ParameterName.BRIGHTNESS);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_COLOR_TUNER_BRIGHTNESS)) {
+ bytes.add(ParameterName.COLOR_TUNER_BRIGHTNESS);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_SATURATION)) {
+ bytes.add(ParameterName.SATURATION);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_COLOR_TUNER_SATURATION)) {
+ bytes.add(ParameterName.COLOR_TUNER_SATURATION);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_HUE)) {
+ bytes.add(ParameterName.HUE);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_COLOR_TUNER_HUE)) {
+ bytes.add(ParameterName.COLOR_TUNER_HUE);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_COLOR_TUNER_RED_OFFSET)) {
+ bytes.add(ParameterName.COLOR_TUNER_RED_OFFSET);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_COLOR_TUNER_GREEN_OFFSET)) {
+ bytes.add(ParameterName.COLOR_TUNER_GREEN_OFFSET);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_COLOR_TUNER_BLUE_OFFSET)) {
+ bytes.add(ParameterName.COLOR_TUNER_BLUE_OFFSET);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_COLOR_TUNER_RED_GAIN)) {
+ bytes.add(ParameterName.COLOR_TUNER_RED_GAIN);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_COLOR_TUNER_GREEN_GAIN)) {
+ bytes.add(ParameterName.COLOR_TUNER_GREEN_GAIN);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_COLOR_TUNER_BLUE_GAIN)) {
+ bytes.add(ParameterName.COLOR_TUNER_BLUE_GAIN);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_NOISE_REDUCTION)) {
+ bytes.add(ParameterName.NOISE_REDUCTION);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_MPEG_NOISE_REDUCTION)) {
+ bytes.add(ParameterName.MPEG_NOISE_REDUCTION);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_FLESH_TONE)) {
+ bytes.add(ParameterName.FLASH_TONE);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_DECONTOUR)) {
+ bytes.add(ParameterName.DE_CONTOUR);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_DYNAMIC_LUMA_CONTROL)) {
+ bytes.add(ParameterName.DYNAMIC_LUMA_CONTROL);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_FILM_MODE)) {
+ bytes.add(ParameterName.FILM_MODE);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_BLUE_STRETCH)) {
+ bytes.add(ParameterName.BLUE_STRETCH);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_COLOR_TUNE)) {
+ bytes.add(ParameterName.COLOR_TUNE);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_COLOR_TEMPERATURE)) {
+ bytes.add(ParameterName.COLOR_TEMPERATURE);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_GLOBAL_DIMMING)) {
+ bytes.add(ParameterName.GLOBE_DIMMING);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_AUTO_PICTURE_QUALITY_ENABLED)) {
+ bytes.add(ParameterName.AUTO_PICTUREQUALITY_ENABLED);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_AUTO_SUPER_RESOLUTION_ENABLED)) {
+ bytes.add(ParameterName.AUTO_SUPER_RESOLUTION_ENABLED);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_LEVEL_RANGE)) {
+ bytes.add(ParameterName.LEVEL_RANGE);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_GAMUT_MAPPING)) {
+ bytes.add(ParameterName.GAMUT_MAPPING);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_PC_MODE)) {
+ bytes.add(ParameterName.PC_MODE);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_LOW_LATENCY)) {
+ bytes.add(ParameterName.LOW_LATENCY);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_VRR)) {
+ bytes.add(ParameterName.VRR);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_CVRR)) {
+ bytes.add(ParameterName.CVRR);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_HDMI_RGB_RANGE)) {
+ bytes.add(ParameterName.HDMI_RGB_RANGE);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_COLOR_SPACE)) {
+ bytes.add(ParameterName.COLOR_SPACE);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_PANEL_INIT_MAX_LUMINCE_VALID)) {
+ bytes.add(ParameterName.PANEL_INIT_MAX_LUMINCE_VALID);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_GAMMA)) {
+ bytes.add(ParameterName.GAMMA);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_COLOR_TEMPERATURE_RED_OFFSET)) {
+ bytes.add(ParameterName.COLOR_TEMPERATURE_RED_OFFSET);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_COLOR_TEMPERATURE_GREEN_OFFSET)) {
+ bytes.add(ParameterName.COLOR_TEMPERATURE_GREEN_OFFSET);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_COLOR_TEMPERATURE_BLUE_OFFSET)) {
+ bytes.add(ParameterName.COLOR_TEMPERATURE_BLUE_OFFSET);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_ELEVEN_POINT_RED)) {
+ bytes.add(ParameterName.ELEVEN_POINT_RED);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_ELEVEN_POINT_GREEN)) {
+ bytes.add(ParameterName.ELEVEN_POINT_GREEN);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_ELEVEN_POINT_BLUE)) {
+ bytes.add(ParameterName.ELEVEN_POINT_BLUE);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_LOW_BLUE_LIGHT)) {
+ bytes.add(ParameterName.LOW_BLUE_LIGHT);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_LD_MODE)) {
+ bytes.add(ParameterName.LD_MODE);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_OSD_RED_GAIN)) {
+ bytes.add(ParameterName.OSD_RED_GAIN);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_OSD_GREEN_GAIN)) {
+ bytes.add(ParameterName.OSD_GREEN_GAIN);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_OSD_BLUE_GAIN)) {
+ bytes.add(ParameterName.OSD_BLUE_GAIN);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_OSD_RED_OFFSET)) {
+ bytes.add(ParameterName.OSD_RED_OFFSET);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_OSD_GREEN_OFFSET)) {
+ bytes.add(ParameterName.OSD_GREEN_OFFSET);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_OSD_BLUE_OFFSET)) {
+ bytes.add(ParameterName.OSD_BLUE_OFFSET);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_OSD_HUE)) {
+ bytes.add(ParameterName.OSD_HUE);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_OSD_SATURATION)) {
+ bytes.add(ParameterName.OSD_SATURATION);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_OSD_CONTRAST)) {
+ bytes.add(ParameterName.OSD_CONTRAST);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_COLOR_TUNER_SWITCH)) {
+ bytes.add(ParameterName.COLOR_TUNER_SWITCH);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_COLOR_TUNER_HUE_RED)) {
+ bytes.add(ParameterName.COLOR_TUNER_HUE_RED);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_COLOR_TUNER_HUE_GREEN)) {
+ bytes.add(ParameterName.COLOR_TUNER_HUE_GREEN);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_COLOR_TUNER_HUE_BLUE)) {
+ bytes.add(ParameterName.COLOR_TUNER_HUE_BLUE);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_COLOR_TUNER_HUE_CYAN)) {
+ bytes.add(ParameterName.COLOR_TUNER_HUE_CYAN);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_COLOR_TUNER_HUE_MAGENTA)) {
+ bytes.add(ParameterName.COLOR_TUNER_HUE_MAGENTA);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_COLOR_TUNER_HUE_YELLOW)) {
+ bytes.add(ParameterName.COLOR_TUNER_HUE_YELLOW);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_COLOR_TUNER_HUE_FLESH)) {
+ bytes.add(ParameterName.COLOR_TUNER_HUE_FLESH);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_RED)) {
+ bytes.add(ParameterName.COLOR_TUNER_SATURATION_RED);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_GREEN)) {
+ bytes.add(ParameterName.COLOR_TUNER_SATURATION_GREEN);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_BLUE)) {
+ bytes.add(ParameterName.COLOR_TUNER_SATURATION_BLUE);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_CYAN)) {
+ bytes.add(ParameterName.COLOR_TUNER_SATURATION_CYAN);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_MAGENTA)) {
+ bytes.add(ParameterName.COLOR_TUNER_SATURATION_MAGENTA);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_YELLOW)) {
+ bytes.add(ParameterName.COLOR_TUNER_SATURATION_YELLOW);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_FLESH)) {
+ bytes.add(ParameterName.COLOR_TUNER_SATURATION_FLESH);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_RED)) {
+ bytes.add(ParameterName.COLOR_TUNER_LUMINANCE_RED);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_GREEN)) {
+ bytes.add(ParameterName.COLOR_TUNER_LUMINANCE_GREEN);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_BLUE)) {
+ bytes.add(ParameterName.COLOR_TUNER_LUMINANCE_BLUE);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_CYAN)) {
+ bytes.add(ParameterName.COLOR_TUNER_LUMINANCE_CYAN);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_MAGENTA)) {
+ bytes.add(ParameterName.COLOR_TUNER_LUMINANCE_MAGENTA);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_YELLOW)) {
+ bytes.add(ParameterName.COLOR_TUNER_LUMINANCE_YELLOW);
+ }
+ if (nameMap.contains(PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_FLESH)) {
+ bytes.add(ParameterName.COLOR_TUNER_LUMINANCE_FLESH);
+ }
+
+ // Sound Quality parameters
+ if (nameMap.contains(SoundQuality.PARAMETER_BALANCE)) {
+ bytes.add(ParameterName.BALANCE);
+ }
+ if (nameMap.contains(SoundQuality.PARAMETER_BASS)) {
+ bytes.add(ParameterName.BASS);
+ }
+ if (nameMap.contains(SoundQuality.PARAMETER_TREBLE)) {
+ bytes.add(ParameterName.TREBLE);
+ }
+ if (nameMap.contains(SoundQuality.PARAMETER_SURROUND_SOUND)) {
+ bytes.add(ParameterName.SURROUND_SOUND_ENABLED);
+ }
+ if (nameMap.contains(SoundQuality.PARAMETER_EQUALIZER_DETAIL)) {
+ bytes.add(ParameterName.EQUALIZER_DETAIL);
+ }
+ if (nameMap.contains(SoundQuality.PARAMETER_SPEAKERS)) {
+ bytes.add(ParameterName.SPEAKERS_ENABLED);
+ }
+ if (nameMap.contains(SoundQuality.PARAMETER_SPEAKERS_DELAY_MILLIS)) {
+ bytes.add(ParameterName.SPEAKERS_DELAY_MS);
+ }
+ if (nameMap.contains(SoundQuality.PARAMETER_EARC)) {
+ bytes.add(ParameterName.ENHANCED_AUDIO_RETURN_CHANNEL_ENABLED);
+ }
+ if (nameMap.contains(SoundQuality.PARAMETER_AUTO_VOLUME_CONTROL)) {
+ bytes.add(ParameterName.AUTO_VOLUME_CONTROL);
+ }
+ if (nameMap.contains(SoundQuality.PARAMETER_DOWN_MIX_MODE)) {
+ bytes.add(ParameterName.DOWNMIX_MODE);
+ }
+ if (nameMap.contains(SoundQuality.PARAMETER_DTS_DRC)) {
+ bytes.add(ParameterName.DTS_DRC);
+ }
+ if (nameMap.contains(SoundQuality.PARAMETER_DOLBY_AUDIO_PROCESSING)) {
+ bytes.add(ParameterName.DOLBY_AUDIO_PROCESSING);
+ }
+ if (nameMap.contains(SoundQuality.PARAMETER_DIALOGUE_ENHANCER)) {
+ bytes.add(ParameterName.DOLBY_DIALOGUE_ENHANCER);
+ }
+ if (nameMap.contains(SoundQuality.PARAMETER_DTS_VIRTUAL_X)) {
+ bytes.add(ParameterName.DTS_VIRTUAL_X);
+ }
+ if (nameMap.contains(SoundQuality.PARAMETER_DIGITAL_OUTPUT_DELAY_MILLIS)) {
+ bytes.add(ParameterName.DIGITAL_OUTPUT_DELAY_MS);
+ }
+ if (nameMap.contains(SoundQuality.PARAMETER_DIGITAL_OUTPUT_MODE)) {
+ bytes.add(ParameterName.DIGITAL_OUTPUT);
+ }
+ if (nameMap.contains(SoundQuality.PARAMETER_SOUND_STYLE)) {
+ bytes.add(ParameterName.SOUND_STYLE);
+ }
+
+ byte[] byteArray = new byte[bytes.size()];
+ for (int i = 0; i < bytes.size(); i++) {
+ byteArray[i] = bytes.get(i);
+ }
+ return byteArray;
+ }
+
+ /**
+ * Get Parameter Name based on byte.
+ */
+ public static String getParameterName(byte pn) {
+ Map<Byte, String> parameterNameMap = new HashMap<>();
+ parameterNameMap.put(ParameterName.BRIGHTNESS, PictureQuality.PARAMETER_BRIGHTNESS);
+ parameterNameMap.put(ParameterName.CONTRAST, PictureQuality.PARAMETER_CONTRAST);
+ parameterNameMap.put(ParameterName.SHARPNESS, PictureQuality.PARAMETER_SHARPNESS);
+ parameterNameMap.put(ParameterName.SATURATION, PictureQuality.PARAMETER_SATURATION);
+ parameterNameMap.put(ParameterName.HUE, PictureQuality.PARAMETER_HUE);
+ parameterNameMap.put(ParameterName.COLOR_TUNER_BRIGHTNESS,
+ PictureQuality.PARAMETER_COLOR_TUNER_BRIGHTNESS);
+ parameterNameMap.put(ParameterName.COLOR_TUNER_SATURATION,
+ PictureQuality.PARAMETER_COLOR_TUNER_SATURATION);
+ parameterNameMap.put(ParameterName.COLOR_TUNER_HUE,
+ PictureQuality.PARAMETER_COLOR_TUNER_HUE);
+ parameterNameMap.put(ParameterName.COLOR_TUNER_RED_OFFSET,
+ PictureQuality.PARAMETER_COLOR_TUNER_RED_OFFSET);
+ parameterNameMap.put(ParameterName.COLOR_TUNER_GREEN_OFFSET,
+ PictureQuality.PARAMETER_COLOR_TUNER_GREEN_OFFSET);
+ parameterNameMap.put(ParameterName.COLOR_TUNER_BLUE_OFFSET,
+ PictureQuality.PARAMETER_COLOR_TUNER_BLUE_OFFSET);
+ parameterNameMap.put(ParameterName.COLOR_TUNER_RED_GAIN,
+ PictureQuality.PARAMETER_COLOR_TUNER_RED_GAIN);
+ parameterNameMap.put(ParameterName.COLOR_TUNER_GREEN_GAIN,
+ PictureQuality.PARAMETER_COLOR_TUNER_GREEN_GAIN);
+ parameterNameMap.put(ParameterName.COLOR_TUNER_BLUE_GAIN,
+ PictureQuality.PARAMETER_COLOR_TUNER_BLUE_GAIN);
+ parameterNameMap.put(ParameterName.NOISE_REDUCTION,
+ PictureQuality.PARAMETER_NOISE_REDUCTION);
+ parameterNameMap.put(ParameterName.MPEG_NOISE_REDUCTION,
+ PictureQuality.PARAMETER_MPEG_NOISE_REDUCTION);
+ parameterNameMap.put(ParameterName.FLASH_TONE, PictureQuality.PARAMETER_FLESH_TONE);
+ parameterNameMap.put(ParameterName.DE_CONTOUR, PictureQuality.PARAMETER_DECONTOUR);
+ parameterNameMap.put(ParameterName.DYNAMIC_LUMA_CONTROL,
+ PictureQuality.PARAMETER_DYNAMIC_LUMA_CONTROL);
+ parameterNameMap.put(ParameterName.FILM_MODE,
+ PictureQuality.PARAMETER_FILM_MODE);
+ parameterNameMap.put(ParameterName.BLACK_STRETCH,
+ PictureQuality.PARAMETER_BLACK_STRETCH);
+ parameterNameMap.put(ParameterName.BLUE_STRETCH,
+ PictureQuality.PARAMETER_BLUE_STRETCH);
+ parameterNameMap.put(ParameterName.COLOR_TUNE,
+ PictureQuality.PARAMETER_COLOR_TUNE);
+ parameterNameMap.put(ParameterName.COLOR_TEMPERATURE,
+ PictureQuality.PARAMETER_COLOR_TEMPERATURE);
+ parameterNameMap.put(ParameterName.GLOBE_DIMMING,
+ PictureQuality.PARAMETER_GLOBAL_DIMMING);
+ parameterNameMap.put(ParameterName.AUTO_PICTUREQUALITY_ENABLED,
+ PictureQuality.PARAMETER_AUTO_PICTURE_QUALITY_ENABLED);
+ parameterNameMap.put(ParameterName.AUTO_SUPER_RESOLUTION_ENABLED,
+ PictureQuality.PARAMETER_AUTO_SUPER_RESOLUTION_ENABLED);
+ parameterNameMap.put(ParameterName.LEVEL_RANGE, PictureQuality.PARAMETER_LEVEL_RANGE);
+ parameterNameMap.put(ParameterName.GAMUT_MAPPING,
+ PictureQuality.PARAMETER_GAMUT_MAPPING);
+ parameterNameMap.put(ParameterName.PC_MODE, PictureQuality.PARAMETER_PC_MODE);
+ parameterNameMap.put(ParameterName.LOW_LATENCY, PictureQuality.PARAMETER_LOW_LATENCY);
+ parameterNameMap.put(ParameterName.VRR, PictureQuality.PARAMETER_VRR);
+ parameterNameMap.put(ParameterName.CVRR, PictureQuality.PARAMETER_CVRR);
+ parameterNameMap.put(ParameterName.HDMI_RGB_RANGE,
+ PictureQuality.PARAMETER_HDMI_RGB_RANGE);
+ parameterNameMap.put(ParameterName.COLOR_SPACE, PictureQuality.PARAMETER_COLOR_SPACE);
+ parameterNameMap.put(ParameterName.PANEL_INIT_MAX_LUMINCE_VALID,
+ PictureQuality.PARAMETER_PANEL_INIT_MAX_LUMINCE_VALID);
+ parameterNameMap.put(ParameterName.GAMMA, PictureQuality.PARAMETER_GAMMA);
+ parameterNameMap.put(ParameterName.COLOR_TEMPERATURE_RED_GAIN,
+ PictureQuality.PARAMETER_COLOR_TEMPERATURE_RED_GAIN);
+ parameterNameMap.put(ParameterName.COLOR_TEMPERATURE_GREEN_GAIN,
+ PictureQuality.PARAMETER_COLOR_TEMPERATURE_GREEN_GAIN);
+ parameterNameMap.put(ParameterName.COLOR_TEMPERATURE_BLUE_GAIN,
+ PictureQuality.PARAMETER_COLOR_TEMPERATURE_BLUE_GAIN);
+ parameterNameMap.put(ParameterName.COLOR_TEMPERATURE_RED_OFFSET,
+ PictureQuality.PARAMETER_COLOR_TEMPERATURE_RED_OFFSET);
+ parameterNameMap.put(ParameterName.COLOR_TEMPERATURE_GREEN_OFFSET,
+ PictureQuality.PARAMETER_COLOR_TEMPERATURE_GREEN_OFFSET);
+ parameterNameMap.put(ParameterName.COLOR_TEMPERATURE_BLUE_OFFSET,
+ PictureQuality.PARAMETER_COLOR_TEMPERATURE_BLUE_OFFSET);
+ parameterNameMap.put(ParameterName.ELEVEN_POINT_RED,
+ PictureQuality.PARAMETER_ELEVEN_POINT_RED);
+ parameterNameMap.put(ParameterName.ELEVEN_POINT_GREEN,
+ PictureQuality.PARAMETER_ELEVEN_POINT_GREEN);
+ parameterNameMap.put(ParameterName.ELEVEN_POINT_BLUE,
+ PictureQuality.PARAMETER_ELEVEN_POINT_BLUE);
+ parameterNameMap.put(ParameterName.LOW_BLUE_LIGHT,
+ PictureQuality.PARAMETER_LOW_BLUE_LIGHT);
+ parameterNameMap.put(ParameterName.LD_MODE, PictureQuality.PARAMETER_LD_MODE);
+ parameterNameMap.put(ParameterName.OSD_RED_GAIN, PictureQuality.PARAMETER_OSD_RED_GAIN);
+ parameterNameMap.put(ParameterName.OSD_GREEN_GAIN,
+ PictureQuality.PARAMETER_OSD_GREEN_GAIN);
+ parameterNameMap.put(ParameterName.OSD_BLUE_GAIN,
+ PictureQuality.PARAMETER_OSD_BLUE_GAIN);
+ parameterNameMap.put(ParameterName.OSD_RED_OFFSET,
+ PictureQuality.PARAMETER_OSD_RED_OFFSET);
+ parameterNameMap.put(ParameterName.OSD_GREEN_OFFSET,
+ PictureQuality.PARAMETER_OSD_GREEN_OFFSET);
+ parameterNameMap.put(ParameterName.OSD_BLUE_OFFSET,
+ PictureQuality.PARAMETER_OSD_BLUE_OFFSET);
+ parameterNameMap.put(ParameterName.OSD_HUE, PictureQuality.PARAMETER_OSD_HUE);
+ parameterNameMap.put(ParameterName.OSD_SATURATION,
+ PictureQuality.PARAMETER_OSD_SATURATION);
+ parameterNameMap.put(ParameterName.OSD_CONTRAST,
+ PictureQuality.PARAMETER_OSD_CONTRAST);
+ parameterNameMap.put(ParameterName.COLOR_TUNER_SWITCH,
+ PictureQuality.PARAMETER_COLOR_TUNER_SWITCH);
+ parameterNameMap.put(ParameterName.COLOR_TUNER_HUE_RED,
+ PictureQuality.PARAMETER_COLOR_TUNER_HUE_RED);
+ parameterNameMap.put(ParameterName.COLOR_TUNER_HUE_GREEN,
+ PictureQuality.PARAMETER_COLOR_TUNER_HUE_GREEN);
+ parameterNameMap.put(ParameterName.COLOR_TUNER_HUE_BLUE,
+ PictureQuality.PARAMETER_COLOR_TUNER_HUE_BLUE);
+ parameterNameMap.put(ParameterName.COLOR_TUNER_HUE_CYAN,
+ PictureQuality.PARAMETER_COLOR_TUNER_HUE_CYAN);
+ parameterNameMap.put(ParameterName.COLOR_TUNER_HUE_MAGENTA,
+ PictureQuality.PARAMETER_COLOR_TUNER_HUE_MAGENTA);
+ parameterNameMap.put(ParameterName.COLOR_TUNER_HUE_YELLOW,
+ PictureQuality.PARAMETER_COLOR_TUNER_HUE_YELLOW);
+ parameterNameMap.put(ParameterName.COLOR_TUNER_HUE_FLESH,
+ PictureQuality.PARAMETER_COLOR_TUNER_HUE_FLESH);
+ parameterNameMap.put(ParameterName.COLOR_TUNER_SATURATION_RED,
+ PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_RED);
+ parameterNameMap.put(ParameterName.COLOR_TUNER_SATURATION_GREEN,
+ PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_GREEN);
+ parameterNameMap.put(ParameterName.COLOR_TUNER_SATURATION_BLUE,
+ PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_BLUE);
+ parameterNameMap.put(ParameterName.COLOR_TUNER_SATURATION_CYAN,
+ PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_CYAN);
+ parameterNameMap.put(ParameterName.COLOR_TUNER_SATURATION_MAGENTA,
+ PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_MAGENTA);
+ parameterNameMap.put(ParameterName.COLOR_TUNER_SATURATION_YELLOW,
+ PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_YELLOW);
+ parameterNameMap.put(ParameterName.COLOR_TUNER_SATURATION_FLESH,
+ PictureQuality.PARAMETER_COLOR_TUNER_SATURATION_FLESH);
+ parameterNameMap.put(ParameterName.COLOR_TUNER_LUMINANCE_RED,
+ PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_RED);
+ parameterNameMap.put(ParameterName.COLOR_TUNER_LUMINANCE_GREEN,
+ PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_GREEN);
+ parameterNameMap.put(ParameterName.COLOR_TUNER_LUMINANCE_BLUE,
+ PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_BLUE);
+ parameterNameMap.put(ParameterName.COLOR_TUNER_LUMINANCE_CYAN,
+ PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_CYAN);
+ parameterNameMap.put(ParameterName.COLOR_TUNER_LUMINANCE_MAGENTA,
+ PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_MAGENTA);
+ parameterNameMap.put(ParameterName.COLOR_TUNER_LUMINANCE_YELLOW,
+ PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_YELLOW);
+ parameterNameMap.put(ParameterName.COLOR_TUNER_LUMINANCE_FLESH,
+ PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_FLESH);
+ parameterNameMap.put(ParameterName.BALANCE, SoundQuality.PARAMETER_BALANCE);
+ parameterNameMap.put(ParameterName.BASS, SoundQuality.PARAMETER_BASS);
+ parameterNameMap.put(ParameterName.TREBLE, SoundQuality.PARAMETER_TREBLE);
+ parameterNameMap.put(ParameterName.SURROUND_SOUND_ENABLED,
+ SoundQuality.PARAMETER_SURROUND_SOUND);
+ parameterNameMap.put(ParameterName.EQUALIZER_DETAIL,
+ SoundQuality.PARAMETER_EQUALIZER_DETAIL);
+ parameterNameMap.put(ParameterName.SPEAKERS_ENABLED, SoundQuality.PARAMETER_SPEAKERS);
+ parameterNameMap.put(ParameterName.SPEAKERS_DELAY_MS,
+ SoundQuality.PARAMETER_SPEAKERS_DELAY_MILLIS);
+ parameterNameMap.put(ParameterName.ENHANCED_AUDIO_RETURN_CHANNEL_ENABLED,
+ SoundQuality.PARAMETER_EARC);
+ parameterNameMap.put(ParameterName.AUTO_VOLUME_CONTROL,
+ SoundQuality.PARAMETER_AUTO_VOLUME_CONTROL);
+ parameterNameMap.put(ParameterName.DOWNMIX_MODE, SoundQuality.PARAMETER_DOWN_MIX_MODE);
+ parameterNameMap.put(ParameterName.DTS_DRC, SoundQuality.PARAMETER_DTS_DRC);
+ parameterNameMap.put(ParameterName.DOLBY_AUDIO_PROCESSING,
+ SoundQuality.PARAMETER_DOLBY_AUDIO_PROCESSING);
+ parameterNameMap.put(ParameterName.DOLBY_DIALOGUE_ENHANCER,
+ SoundQuality.PARAMETER_DIALOGUE_ENHANCER);
+ parameterNameMap.put(ParameterName.DTS_VIRTUAL_X,
+ SoundQuality.PARAMETER_DTS_VIRTUAL_X);
+ parameterNameMap.put(ParameterName.DIGITAL_OUTPUT,
+ SoundQuality.PARAMETER_DIGITAL_OUTPUT_MODE);
+ parameterNameMap.put(ParameterName.DIGITAL_OUTPUT_DELAY_MS,
+ SoundQuality.PARAMETER_DIGITAL_OUTPUT_DELAY_MILLIS);
+ parameterNameMap.put(ParameterName.SOUND_STYLE, SoundQuality.PARAMETER_SOUND_STYLE);
+
+ return parameterNameMap.get(pn);
+ }
+
private static String getTempId(BiMap<Long, String> map, Cursor cursor) {
int colIndex = cursor.getColumnIndex(BaseParameters.PARAMETER_ID);
Long dbId = colIndex != -1 ? cursor.getLong(colIndex) : null;
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index a16b122..6d565d2 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -679,6 +679,8 @@
WorkerHandler mHandler;
private final HandlerThread mRankingThread = new HandlerThread("ranker",
Process.THREAD_PRIORITY_BACKGROUND);
+ @FlaggedApi(Flags.FLAG_NM_BINDER_PERF_THROTTLE_EFFECTS_SUPPRESSOR_BROADCAST)
+ private Handler mBroadcastsHandler;
private final SparseArray<ArraySet<ComponentName>> mListenersDisablingEffects =
new SparseArray<>();
@@ -2682,7 +2684,7 @@
// TODO: All tests should use this init instead of the one-off setters above.
@VisibleForTesting
- void init(WorkerHandler handler, RankingHandler rankingHandler,
+ void init(WorkerHandler handler, RankingHandler rankingHandler, Handler broadcastsHandler,
IPackageManager packageManager, PackageManager packageManagerClient,
LightsManager lightsManager, NotificationListeners notificationListeners,
NotificationAssistants notificationAssistants, ConditionProviders conditionProviders,
@@ -2702,6 +2704,9 @@
ConnectivityManager connectivityManager,
PostNotificationTrackerFactory postNotificationTrackerFactory) {
mHandler = handler;
+ if (Flags.nmBinderPerfThrottleEffectsSuppressorBroadcast()) {
+ mBroadcastsHandler = broadcastsHandler;
+ }
Resources resources = getContext().getResources();
mMaxPackageEnqueueRate = Settings.Global.getFloat(getContext().getContentResolver(),
Settings.Global.MAX_NOTIFICATION_ENQUEUE_RATE,
@@ -3045,13 +3050,22 @@
WorkerHandler handler = new WorkerHandler(Looper.myLooper());
+ Handler broadcastsHandler;
+ if (Flags.nmBinderPerfThrottleEffectsSuppressorBroadcast()) {
+ HandlerThread broadcastsThread = new HandlerThread("NMS Broadcasts");
+ broadcastsThread.start();
+ broadcastsHandler = new Handler(broadcastsThread.getLooper());
+ } else {
+ broadcastsHandler = null;
+ }
+
mShowReviewPermissionsNotification = getContext().getResources().getBoolean(
R.bool.config_notificationReviewPermissions);
mDefaultUnsupportedAdjustments = getContext().getResources().getStringArray(
R.array.config_notificationDefaultUnsupportedAdjustments);
- init(handler, new RankingHandlerWorker(mRankingThread.getLooper()),
+ init(handler, new RankingHandlerWorker(mRankingThread.getLooper()), broadcastsHandler,
AppGlobals.getPackageManager(), getContext().getPackageManager(),
getLocalService(LightsManager.class),
new NotificationListeners(getContext(), mNotificationLock, mUserProfiles,
@@ -3297,10 +3311,11 @@
* so that e.g. rapidly changing some value A -> B -> C will only produce a broadcast for C
* (instead of every time because the extras are different).
*/
+ @FlaggedApi(Flags.FLAG_NM_BINDER_PERF_THROTTLE_EFFECTS_SUPPRESSOR_BROADCAST)
private void sendZenBroadcastWithDelay(Intent intent) {
String token = "zen_broadcast:" + intent.getAction();
- mHandler.removeCallbacksAndEqualMessages(token);
- mHandler.postDelayed(() -> sendRegisteredOnlyBroadcast(intent), token,
+ mBroadcastsHandler.removeCallbacksAndEqualMessages(token);
+ mBroadcastsHandler.postDelayed(() -> sendRegisteredOnlyBroadcast(intent), token,
ZEN_BROADCAST_DELAY.toMillis());
}
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
index 0ed5228..a80b1b2 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
@@ -171,11 +171,11 @@
void onProposedRotationChanged(int displayId, int rotation, boolean isValid);
/**
- * Notifies System UI that the display is ready to show system decorations.
+ * Notifies System UI that the system decorations should be added on the display.
*
* @param displayId display ID
*/
- void onDisplayReady(int displayId);
+ void onDisplayAddSystemDecorations(int displayId);
/**
* Notifies System UI that the system decorations should be removed from the display.
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index 4301c93..c546388 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -770,10 +770,11 @@
}
@Override
- public void onDisplayReady(int displayId) {
+ public void onDisplayAddSystemDecorations(int displayId) {
if (isVisibleBackgroundUserOnDisplay(displayId)) {
if (SPEW) {
- Slog.d(TAG, "Skipping onDisplayReady for visible background user "
+ Slog.d(TAG, "Skipping onDisplayAddSystemDecorations for visible background "
+ + "user "
+ mUserManagerInternal.getUserAssignedToDisplay(displayId));
}
return;
@@ -781,7 +782,7 @@
IStatusBar bar = mBar;
if (bar != null) {
try {
- bar.onDisplayReady(displayId);
+ bar.onDisplayAddSystemDecorations(displayId);
} catch (RemoteException ex) {}
}
}
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerInternal.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerInternal.java
index 872ab59..f413fe3 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerInternal.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerInternal.java
@@ -26,7 +26,7 @@
/**
* Notifies the display is ready for adding wallpaper on it.
*/
- public abstract void onDisplayReady(int displayId);
+ public abstract void onDisplayAddSystemDecorations(int displayId);
/** Notifies when display stop showing system decorations and wallpaper. */
public abstract void onDisplayRemoveSystemDecorations(int displayId);
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index db530e7..09b1073 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -1636,8 +1636,8 @@
private final class LocalService extends WallpaperManagerInternal {
@Override
- public void onDisplayReady(int displayId) {
- onDisplayReadyInternal(displayId);
+ public void onDisplayAddSystemDecorations(int displayId) {
+ onDisplayAddSystemDecorationsInternal(displayId);
}
@Override
@@ -3944,7 +3944,7 @@
return (wallpaper != null) ? wallpaper.allowBackup : false;
}
- private void onDisplayReadyInternal(int displayId) {
+ private void onDisplayAddSystemDecorationsInternal(int displayId) {
synchronized (mLock) {
if (mLastWallpaper == null) {
return;
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index c7d4467..6f76618 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -3655,7 +3655,7 @@
private static String getIntentRedirectPreventedLogMessage(@NonNull String message,
@NonNull Intent intent, int intentCreatorUid, @Nullable String intentCreatorPackage,
int callingUid, @Nullable String callingPackage) {
- return "[IntentRedirect]" + message + " intentCreatorUid: " + intentCreatorUid
+ return "[IntentRedirect Hardening] " + message + " intentCreatorUid: " + intentCreatorUid
+ "; intentCreatorPackage: " + intentCreatorPackage + "; callingUid: " + callingUid
+ "; callingPackage: " + callingPackage + "; intent: " + intent;
}
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 5b16199..5329e3b 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -1887,17 +1887,17 @@
mCanSystemBarsBeShownByUser = canBeShown;
}
- void notifyDisplayReady() {
+ void notifyDisplayAddSystemDecorations() {
mHandler.post(() -> {
final int displayId = getDisplayId();
StatusBarManagerInternal statusBar = getStatusBarManagerInternal();
if (statusBar != null) {
- statusBar.onDisplayReady(displayId);
+ statusBar.onDisplayAddSystemDecorations(displayId);
}
final WallpaperManagerInternal wpMgr = LocalServices
.getService(WallpaperManagerInternal.class);
if (wpMgr != null) {
- wpMgr.onDisplayReady(displayId);
+ wpMgr.onDisplayAddSystemDecorations(displayId);
}
});
}
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index abd26b5..cf464c7 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -2845,7 +2845,13 @@
}
startHomeOnDisplay(mCurrentUser, reason, displayContent.getDisplayId());
- displayContent.getDisplayPolicy().notifyDisplayReady();
+ if (enableDisplayContentModeManagement()) {
+ if (displayContent.isSystemDecorationsSupported()) {
+ displayContent.getDisplayPolicy().notifyDisplayAddSystemDecorations();
+ }
+ } else {
+ displayContent.getDisplayPolicy().notifyDisplayAddSystemDecorations();
+ }
}
@Override
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index bf4595c..b37bcc7 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -602,12 +602,14 @@
return std::to_string(displayId.val());
};
dump += StringPrintf(INDENT "Display not interactive: %s\n",
- dumpSet(mLocked.nonInteractiveDisplays, streamableToString).c_str());
+ dumpContainer(mLocked.nonInteractiveDisplays, streamableToString)
+ .c_str());
dump += StringPrintf(INDENT "System UI Lights Out: %s\n",
toString(mLocked.systemUiLightsOut));
dump += StringPrintf(INDENT "Pointer Speed: %" PRId32 "\n", mLocked.pointerSpeed);
dump += StringPrintf(INDENT "Display with Mouse Scaling Disabled: %s\n",
- dumpSet(mLocked.displaysWithMouseScalingDisabled, streamableToString)
+ dumpContainer(mLocked.displaysWithMouseScalingDisabled,
+ streamableToString)
.c_str());
dump += StringPrintf(INDENT "Pointer Gestures Enabled: %s\n",
toString(mLocked.pointerGesturesEnabled));
diff --git a/services/supervision/java/com/android/server/supervision/SupervisionService.java b/services/supervision/java/com/android/server/supervision/SupervisionService.java
index ea85710..a96c477 100644
--- a/services/supervision/java/com/android/server/supervision/SupervisionService.java
+++ b/services/supervision/java/com/android/server/supervision/SupervisionService.java
@@ -177,23 +177,24 @@
}
}
- /** Ensures that supervision is enabled when the supervision app is the profile owner. */
+ /**
+ * Ensures that supervision is enabled when the supervision app is the profile owner.
+ *
+ * <p>The state syncing with the DevicePolicyManager can only enable supervision and never
+ * disable. Supervision can only be disabled explicitly via calls to the
+ * {@link #setSupervisionEnabledForUser} method.
+ */
private void syncStateWithDevicePolicyManager(@UserIdInt int userId) {
final DevicePolicyManagerInternal dpmInternal = mInjector.getDpmInternal();
final ComponentName po =
dpmInternal != null ? dpmInternal.getProfileOwnerAsUser(userId) : null;
if (po != null && po.getPackageName().equals(getSystemSupervisionPackage())) {
- setSupervisionEnabledForUserInternal(userId, true, po.getPackageName());
+ setSupervisionEnabledForUserInternal(userId, true, getSystemSupervisionPackage());
} else if (po != null && po.equals(getSupervisionProfileOwnerComponent())) {
// TODO(b/392071637): Consider not enabling supervision in case profile owner is given
// to the legacy supervision profile owner component.
setSupervisionEnabledForUserInternal(userId, true, po.getPackageName());
- } else {
- // TODO(b/381428475): Avoid disabling supervision when the app is not the profile owner.
- // This might only be possible after introducing specific and public APIs to enable
- // and disable supervision.
- setSupervisionEnabledForUserInternal(userId, false, /* supervisionAppPackage= */ null);
}
}
@@ -279,7 +280,7 @@
}
@VisibleForTesting
- @SuppressLint("MissingPermission") // not needed for a service
+ @SuppressLint("MissingPermission") // not needed for a system service
void registerProfileOwnerListener() {
IntentFilter poIntentFilter = new IntentFilter();
poIntentFilter.addAction(DevicePolicyManager.ACTION_PROFILE_OWNER_CHANGED);
diff --git a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java
index b92afc5..d80fd20 100644
--- a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java
@@ -731,7 +731,7 @@
// WHEN display ID, 2, is ready.
WallpaperManagerInternal wallpaperManagerInternal = LocalServices.getService(
WallpaperManagerInternal.class);
- wallpaperManagerInternal.onDisplayReady(testDisplayId);
+ wallpaperManagerInternal.onDisplayAddSystemDecorations(testDisplayId);
// Then there is a connection established for the system & lock wallpaper for display ID, 2.
verify(mockIWallpaperService).attach(
@@ -771,7 +771,7 @@
// WHEN display ID, 2, is ready.
WallpaperManagerInternal wallpaperManagerInternal = LocalServices.getService(
WallpaperManagerInternal.class);
- wallpaperManagerInternal.onDisplayReady(testDisplayId);
+ wallpaperManagerInternal.onDisplayAddSystemDecorations(testDisplayId);
// Then there is a connection established for the system wallpaper for display ID, 2.
verify(mockIWallpaperService).attach(
@@ -818,7 +818,7 @@
// WHEN display ID, 2, is ready.
WallpaperManagerInternal wallpaperManagerInternal = LocalServices.getService(
WallpaperManagerInternal.class);
- wallpaperManagerInternal.onDisplayReady(testDisplayId);
+ wallpaperManagerInternal.onDisplayAddSystemDecorations(testDisplayId);
// Then there is a connection established for the fallback wallpaper for display ID, 2.
verify(mockIWallpaperService).attach(
@@ -856,7 +856,7 @@
// GIVEN wallpaper connections have been established for display ID, 2.
WallpaperManagerInternal wallpaperManagerInternal = LocalServices.getService(
WallpaperManagerInternal.class);
- wallpaperManagerInternal.onDisplayReady(testDisplayId);
+ wallpaperManagerInternal.onDisplayAddSystemDecorations(testDisplayId);
// Save displayConnector for displayId 2 before display removal.
WallpaperManagerService.DisplayConnector displayConnector =
wallpaper.connection.getDisplayConnectorOrCreate(testDisplayId);
@@ -894,7 +894,7 @@
// GIVEN wallpaper connections have been established for display ID, 2.
WallpaperManagerInternal wallpaperManagerInternal = LocalServices.getService(
WallpaperManagerInternal.class);
- wallpaperManagerInternal.onDisplayReady(testDisplayId);
+ wallpaperManagerInternal.onDisplayAddSystemDecorations(testDisplayId);
// Save displayConnectors for display ID, 2, before display removal.
WallpaperManagerService.DisplayConnector systemWallpaperDisplayConnector =
systemWallpaper.connection.getDisplayConnectorOrCreate(testDisplayId);
@@ -930,7 +930,7 @@
// GIVEN wallpaper connections have been established for display ID, 2.
WallpaperManagerInternal wallpaperManagerInternal = LocalServices.getService(
WallpaperManagerInternal.class);
- wallpaperManagerInternal.onDisplayReady(testDisplayId);
+ wallpaperManagerInternal.onDisplayAddSystemDecorations(testDisplayId);
// Save fallback wallpaper displayConnector for display ID, 2, before display removal.
WallpaperManagerService.DisplayConnector fallbackWallpaperConnector =
mService.mFallbackWallpaper.connection.getDisplayConnectorOrCreate(testDisplayId);
@@ -977,7 +977,7 @@
// GIVEN wallpaper connections have been established for displayID, 2.
WallpaperManagerInternal wallpaperManagerInternal = LocalServices.getService(
WallpaperManagerInternal.class);
- wallpaperManagerInternal.onDisplayReady(testDisplayId);
+ wallpaperManagerInternal.onDisplayAddSystemDecorations(testDisplayId);
// Save displayConnector for displayId 2 before display removal.
WallpaperManagerService.DisplayConnector displayConnector =
wallpaper.connection.getDisplayConnectorOrCreate(testDisplayId);
@@ -1011,7 +1011,7 @@
// GIVEN wallpaper connections have been established for displayID, 2.
WallpaperManagerInternal wallpaperManagerInternal = LocalServices.getService(
WallpaperManagerInternal.class);
- wallpaperManagerInternal.onDisplayReady(testDisplayId);
+ wallpaperManagerInternal.onDisplayAddSystemDecorations(testDisplayId);
// Save displayConnectors for displayId 2 before display removal.
WallpaperManagerService.DisplayConnector systemWallpaperDisplayConnector =
systemWallpaper.connection.getDisplayConnectorOrCreate(testDisplayId);
diff --git a/services/tests/servicestests/src/com/android/server/media/projection/OWNERS b/services/tests/servicestests/src/com/android/server/media/projection/OWNERS
index 832bcd9..3caf7fa 100644
--- a/services/tests/servicestests/src/com/android/server/media/projection/OWNERS
+++ b/services/tests/servicestests/src/com/android/server/media/projection/OWNERS
@@ -1 +1,2 @@
+# Bug component: 1345447
include /media/java/android/media/projection/OWNERS
diff --git a/services/tests/servicestests/src/com/android/server/supervision/SupervisionServiceTest.kt b/services/tests/servicestests/src/com/android/server/supervision/SupervisionServiceTest.kt
index b150b14..da02278 100644
--- a/services/tests/servicestests/src/com/android/server/supervision/SupervisionServiceTest.kt
+++ b/services/tests/servicestests/src/com/android/server/supervision/SupervisionServiceTest.kt
@@ -161,6 +161,18 @@
@Test
@RequiresFlagsEnabled(Flags.FLAG_ENABLE_SYNC_WITH_DPM)
+ fun profileOwnerChanged_supervisionAppIsNotProfileOwner_doesNotDisableSupervision() {
+ service.mInternal.setSupervisionEnabledForUser(USER_ID, true)
+ whenever(mockDpmInternal.getProfileOwnerAsUser(USER_ID))
+ .thenReturn(ComponentName("other.package", "MainActivity"))
+
+ broadcastProfileOwnerChanged(USER_ID)
+
+ assertThat(service.isSupervisionEnabledForUser(USER_ID)).isTrue()
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_ENABLE_SYNC_WITH_DPM)
fun profileOwnerChanged_supervisionAppIsNotProfileOwner_doesNotEnableSupervision() {
whenever(mockDpmInternal.getProfileOwnerAsUser(USER_ID))
.thenReturn(ComponentName("other.package", "MainActivity"))
@@ -258,7 +270,7 @@
private companion object {
const val USER_ID = 100
- val APP_UID = USER_ID * UserHandle.PER_USER_RANGE
+ const val APP_UID = USER_ID * UserHandle.PER_USER_RANGE
}
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 1a95984..f8c8a1d 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -263,6 +263,7 @@
import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
+import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.Parcel;
@@ -578,6 +579,7 @@
NetworkCapabilities mWifiNetworkCapabilities;
private NotificationManagerService.WorkerHandler mWorkerHandler;
+ private Handler mBroadcastsHandler;
private class TestableToastCallback extends ITransientNotification.Stub {
@Override
@@ -814,14 +816,16 @@
when(mUmInternal.isUserInitialized(anyInt())).thenReturn(true);
mWorkerHandler = spy(mService.new WorkerHandler(mTestableLooper.getLooper()));
- mService.init(mWorkerHandler, mRankingHandler, mPackageManager, mPackageManagerClient,
- mLightsManager, mListeners, mAssistants, mConditionProviders, mCompanionMgr,
- mSnoozeHelper, mUsageStats, mPolicyFile, mActivityManager, mGroupHelper, mAm, mAtm,
- mAppUsageStats, mDevicePolicyManager, mUgm, mUgmInternal,
- mAppOpsManager, mUm, mHistoryManager, mStatsManager,
- mAmi, mToastRateLimiter, mPermissionHelper, mock(UsageStatsManagerInternal.class),
- mTelecomManager, mLogger, mTestFlagResolver, mPermissionManager,
- mPowerManager, mConnectivityManager, mPostNotificationTrackerFactory);
+ mBroadcastsHandler = new Handler(mTestableLooper.getLooper());
+
+ mService.init(mWorkerHandler, mRankingHandler, mBroadcastsHandler, mPackageManager,
+ mPackageManagerClient, mLightsManager, mListeners, mAssistants, mConditionProviders,
+ mCompanionMgr, mSnoozeHelper, mUsageStats, mPolicyFile, mActivityManager,
+ mGroupHelper, mAm, mAtm, mAppUsageStats, mDevicePolicyManager, mUgm, mUgmInternal,
+ mAppOpsManager, mUm, mHistoryManager, mStatsManager, mAmi, mToastRateLimiter,
+ mPermissionHelper, mock(UsageStatsManagerInternal.class), mTelecomManager, mLogger,
+ mTestFlagResolver, mPermissionManager, mPowerManager, mConnectivityManager,
+ mPostNotificationTrackerFactory);
mService.setAttentionHelper(mAttentionHelper);
mService.setLockPatternUtils(mock(LockPatternUtils.class));
@@ -1003,6 +1007,9 @@
// problematic interactions with mocks when they're no longer working as expected).
mWorkerHandler.removeCallbacksAndMessages(null);
}
+ if (mBroadcastsHandler != null) {
+ mBroadcastsHandler.removeCallbacksAndMessages(null);
+ }
if (mTestableLooper != null) {
// Must remove static reference to this test object to prevent leak (b/261039202)
@@ -17647,8 +17654,19 @@
}
@Test
- @EnableFlags(android.app.Flags.FLAG_API_RICH_ONGOING)
- public void testSetCanBePromoted_granted() throws Exception {
+ @EnableFlags({android.app.Flags.FLAG_API_RICH_ONGOING})
+ public void testSetCanBePromoted_granted_noui() throws Exception {
+ testSetCanBePromoted_granted();
+ }
+
+ @Test
+ @EnableFlags({android.app.Flags.FLAG_API_RICH_ONGOING,
+ android.app.Flags.FLAG_UI_RICH_ONGOING })
+ public void testSetCanBePromoted_granted_ui() throws Exception {
+ testSetCanBePromoted_granted();
+ }
+
+ private void testSetCanBePromoted_granted() throws Exception {
// qualifying posted notification
Notification n = new Notification.Builder(mContext, mTestNotificationChannel.getId())
.setSmallIcon(android.R.drawable.sym_def_app_icon)
@@ -17703,6 +17721,11 @@
mService.addNotification(r);
mService.addEnqueuedNotification(r1);
+ // GIVEN - make sure the promoted value does not depend on the default value.
+ mBinderService.setCanBePromoted(mPkg, mUid, false, true);
+ waitForIdle();
+ clearInvocations(mListeners);
+
mBinderService.setCanBePromoted(mPkg, mUid, true, true);
waitForIdle();
@@ -17725,7 +17748,18 @@
@Test
@EnableFlags(android.app.Flags.FLAG_API_RICH_ONGOING)
- public void testSetCanBePromoted_granted_onlyNotifiesOnce() throws Exception {
+ public void testSetCanBePromoted_granted_onlyNotifiesOnce_noui() throws Exception {
+ testSetCanBePromoted_granted_onlyNotifiesOnce();
+ }
+
+ @Test
+ @EnableFlags({android.app.Flags.FLAG_API_RICH_ONGOING,
+ android.app.Flags.FLAG_UI_RICH_ONGOING})
+ public void testSetCanBePromoted_granted_onlyNotifiesOnce_ui() throws Exception {
+ testSetCanBePromoted_granted_onlyNotifiesOnce();
+ }
+
+ private void testSetCanBePromoted_granted_onlyNotifiesOnce() throws Exception {
// qualifying posted notification
Notification n = new Notification.Builder(mContext, mTestNotificationChannel.getId())
.setSmallIcon(android.R.drawable.sym_def_app_icon)
@@ -17741,6 +17775,10 @@
NotificationRecord r = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
mService.addNotification(r);
+ // GIVEN - make sure the promoted value does not depend on the default value.
+ mBinderService.setCanBePromoted(mPkg, mUid, false, true);
+ waitForIdle();
+ clearInvocations(mListeners);
mBinderService.setCanBePromoted(mPkg, mUid, true, true);
waitForIdle();
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java b/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java
index 82d87d4..ad900fe 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java
@@ -48,13 +48,13 @@
import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
import android.net.ConnectivityManager;
+import android.os.Handler;
import android.os.Looper;
import android.os.PowerManager;
import android.os.UserHandle;
import android.os.UserManager;
import android.permission.PermissionManager;
import android.telecom.TelecomManager;
-import android.telephony.TelephonyManager;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.testing.TestableLooper.RunWithLooper;
@@ -152,7 +152,7 @@
try {
mService.init(mService.new WorkerHandler(mTestableLooper.getLooper()),
- mock(RankingHandler.class),
+ mock(RankingHandler.class), new Handler(mTestableLooper.getLooper()),
mock(IPackageManager.class), mock(PackageManager.class),
mock(LightsManager.class),
mock(NotificationListeners.class), mock(NotificationAssistants.class),