Merge changes I03e37561,I8f25255a into main
* changes:
Move onBeforeUserSwitching call to the beginning of the user switch.
Revert "Move showing keyguard after the UserSwitchObservers."
diff --git a/api/StubLibraries.bp b/api/StubLibraries.bp
index b3a674f..d1aa23c 100644
--- a/api/StubLibraries.bp
+++ b/api/StubLibraries.bp
@@ -466,6 +466,32 @@
}
java_library {
+ name: "android-non-updatable.stubs.system_server",
+ defaults: ["android-non-updatable_defaults"],
+ static_libs: [
+ "android-non-updatable.stubs.system_server.from-source",
+ ],
+ product_variables: {
+ build_from_text_stub: {
+ static_libs: [
+ "android-non-updatable.stubs.system_server.from-text",
+ ],
+ exclude_static_libs: [
+ "android-non-updatable.stubs.system_server.from-source",
+ ],
+ },
+ },
+}
+
+java_library {
+ name: "android-non-updatable.stubs.exportable.system_server",
+ defaults: ["android-non-updatable_defaults"],
+ static_libs: [
+ "android-non-updatable.stubs.exportable.system_server.from-source",
+ ],
+}
+
+java_library {
name: "android-non-updatable.stubs.from-source",
defaults: [
"android-non-updatable_defaults",
@@ -561,6 +587,30 @@
},
}
+java_library {
+ name: "android-non-updatable.stubs.system_server.from-source",
+ defaults: [
+ "android-non-updatable_defaults",
+ "android-non-updatable_from_source_defaults",
+ ],
+ srcs: [":services-non-updatable-stubs"],
+ libs: non_updatable_api_deps_on_modules,
+}
+
+java_library {
+ name: "android-non-updatable.stubs.exportable.system_server.from-source",
+ defaults: [
+ "android-non-updatable_defaults",
+ "android-non-updatable_from_source_defaults",
+ "android-non-updatable_exportable_from_source_defaults",
+ ],
+ srcs: [":services-non-updatable-stubs{.exportable}"],
+ libs: non_updatable_api_deps_on_modules,
+ dist: {
+ dir: "apistubs/android/system-server",
+ },
+}
+
java_defaults {
name: "android-non-updatable_from_text_defaults",
defaults: ["android-non-updatable-stubs-libs-defaults"],
@@ -662,6 +712,25 @@
libs: ["all-modules-system-stubs"],
}
+java_api_library {
+ name: "android-non-updatable.stubs.system_server.from-text",
+ api_surface: "system_server",
+ api_contributions: [
+ "api-stubs-docs-non-updatable.api.contribution",
+ "system-api-stubs-docs-non-updatable.api.contribution",
+ "module-lib-api-stubs-docs-non-updatable.api.contribution",
+ "services-non-updatable-stubs.api.contribution",
+ ],
+ defaults: [
+ "module-classpath-java-defaults",
+ "android-non-updatable_everything_from_text_defaults",
+ ],
+
+ // Use full Android API not just the non-updatable API as the latter is incomplete
+ // and can result in incorrect behavior.
+ previous_api: ":android.api.combined.system-server.latest",
+}
+
java_defaults {
name: "android_stubs_dists_default",
dist: {
@@ -813,9 +882,9 @@
defaults: [
"android.jar_defaults",
],
- srcs: [":services-non-updatable-stubs"],
installable: false,
static_libs: [
+ "android-non-updatable.stubs.system_server",
"android_module_lib_stubs_current",
],
visibility: ["//frameworks/base/services"],
@@ -827,9 +896,9 @@
"android.jar_defaults",
"android_stubs_dists_default",
],
- srcs: [":services-non-updatable-stubs{.exportable}"],
installable: false,
static_libs: [
+ "android-non-updatable.stubs.exportable.system_server",
"android_module_lib_stubs_current_exportable",
],
dist: {
diff --git a/cmds/uiautomator/library/Android.bp b/cmds/uiautomator/library/Android.bp
index 966bf13..5c5b220 100644
--- a/cmds/uiautomator/library/Android.bp
+++ b/cmds/uiautomator/library/Android.bp
@@ -28,9 +28,9 @@
"testrunner-src/**/*.java",
],
libs: [
- "android.test.runner",
+ "android.test.runner.stubs.system",
"junit",
- "android.test.base",
+ "android.test.base.stubs.system",
"unsupportedappusage",
],
installable: false,
@@ -56,9 +56,9 @@
":uiautomator-stubs",
],
libs: [
- "android.test.runner",
+ "android.test.runner.stubs",
"junit",
- "android.test.base",
+ "android.test.base.stubs",
],
sdk_version: "current",
installable: false,
diff --git a/core/api/current.txt b/core/api/current.txt
index 5d134c6..542543d 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -32613,6 +32613,13 @@
method public boolean isCharging();
field public static final String ACTION_CHARGING = "android.os.action.CHARGING";
field public static final String ACTION_DISCHARGING = "android.os.action.DISCHARGING";
+ field @FlaggedApi("android.os.battery_part_status_api") public static final int BATTERY_CAPACITY_LEVEL_CRITICAL = 1; // 0x1
+ field @FlaggedApi("android.os.battery_part_status_api") public static final int BATTERY_CAPACITY_LEVEL_FULL = 5; // 0x5
+ field @FlaggedApi("android.os.battery_part_status_api") public static final int BATTERY_CAPACITY_LEVEL_HIGH = 4; // 0x4
+ field @FlaggedApi("android.os.battery_part_status_api") public static final int BATTERY_CAPACITY_LEVEL_LOW = 2; // 0x2
+ field @FlaggedApi("android.os.battery_part_status_api") public static final int BATTERY_CAPACITY_LEVEL_NORMAL = 3; // 0x3
+ field @FlaggedApi("android.os.battery_part_status_api") public static final int BATTERY_CAPACITY_LEVEL_UNKNOWN = 0; // 0x0
+ field @FlaggedApi("android.os.battery_part_status_api") public static final int BATTERY_CAPACITY_LEVEL_UNSUPPORTED = -1; // 0xffffffff
field public static final int BATTERY_HEALTH_COLD = 7; // 0x7
field public static final int BATTERY_HEALTH_DEAD = 4; // 0x4
field public static final int BATTERY_HEALTH_GOOD = 2; // 0x2
@@ -32637,6 +32644,7 @@
field public static final int BATTERY_STATUS_NOT_CHARGING = 4; // 0x4
field public static final int BATTERY_STATUS_UNKNOWN = 1; // 0x1
field public static final String EXTRA_BATTERY_LOW = "battery_low";
+ field @FlaggedApi("android.os.battery_part_status_api") public static final String EXTRA_CAPACITY_LEVEL = "android.os.extra.CAPACITY_LEVEL";
field public static final String EXTRA_CHARGING_STATUS = "android.os.extra.CHARGING_STATUS";
field public static final String EXTRA_CYCLE_COUNT = "android.os.extra.CYCLE_COUNT";
field public static final String EXTRA_HEALTH = "health";
diff --git a/core/java/android/app/appfunctions/AppFunctionService.java b/core/java/android/app/appfunctions/AppFunctionService.java
index c27141a..0d981ea 100644
--- a/core/java/android/app/appfunctions/AppFunctionService.java
+++ b/core/java/android/app/appfunctions/AppFunctionService.java
@@ -26,6 +26,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.Service;
+import android.content.Context;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
@@ -60,29 +61,53 @@
@NonNull
public static final String SERVICE_INTERFACE = "android.app.appfunctions.AppFunctionService";
- private final Binder mBinder =
- new IAppFunctionService.Stub() {
- @Override
- public void executeAppFunction(
- @NonNull ExecuteAppFunctionRequest request,
- @NonNull IExecuteAppFunctionCallback callback) {
- if (AppFunctionService.this.checkCallingPermission(BIND_APP_FUNCTION_SERVICE)
- == PERMISSION_DENIED) {
- throw new SecurityException("Can only be called by the system server.");
- }
- SafeOneTimeExecuteAppFunctionCallback safeCallback =
- new SafeOneTimeExecuteAppFunctionCallback(callback);
- try {
- AppFunctionService.this.onExecuteFunction(request, safeCallback::onResult);
- } catch (Exception ex) {
- // Apps should handle exceptions. But if they don't, report the error on
- // behalf of them.
- safeCallback.onResult(
- ExecuteAppFunctionResponse.newFailure(
- getResultCode(ex), ex.getMessage(), /* extras= */ null));
- }
+ /**
+ * Functional interface to represent the execution logic of an app function.
+ *
+ * @hide
+ */
+ @FunctionalInterface
+ public interface OnExecuteFunction {
+ /**
+ * Performs the semantic of executing the function specified by the provided request and
+ * return the response through the provided callback.
+ */
+ void perform(
+ @NonNull ExecuteAppFunctionRequest request,
+ @NonNull Consumer<ExecuteAppFunctionResponse> callback);
+ }
+
+ /** @hide */
+ @NonNull
+ public static Binder createBinder(
+ @NonNull Context context, @NonNull OnExecuteFunction onExecuteFunction) {
+ return new IAppFunctionService.Stub() {
+ @Override
+ public void executeAppFunction(
+ @NonNull ExecuteAppFunctionRequest request,
+ @NonNull IExecuteAppFunctionCallback callback) {
+ if (context.checkCallingPermission(BIND_APP_FUNCTION_SERVICE)
+ == PERMISSION_DENIED) {
+ throw new SecurityException("Can only be called by the system server.");
}
- };
+ SafeOneTimeExecuteAppFunctionCallback safeCallback =
+ new SafeOneTimeExecuteAppFunctionCallback(callback);
+ try {
+ onExecuteFunction.perform(request, safeCallback::onResult);
+ } catch (Exception ex) {
+ // Apps should handle exceptions. But if they don't, report the error on
+ // behalf of them.
+ safeCallback.onResult(
+ ExecuteAppFunctionResponse.newFailure(
+ getResultCode(ex), ex.getMessage(), /* extras= */ null));
+ }
+ }
+ };
+ }
+
+ private final Binder mBinder = createBinder(
+ AppFunctionService.this,
+ AppFunctionService.this::onExecuteFunction);
@NonNull
@Override
diff --git a/core/java/android/content/pm/TEST_MAPPING b/core/java/android/content/pm/TEST_MAPPING
index ffadd1e..2cdae21 100644
--- a/core/java/android/content/pm/TEST_MAPPING
+++ b/core/java/android/content/pm/TEST_MAPPING
@@ -206,6 +206,17 @@
]
},
{
+ "name": "CtsPackageInstallerCUJDeviceAdminTestCases",
+ "options":[
+ {
+ "exclude-annotation":"androidx.test.filters.FlakyTest"
+ },
+ {
+ "exclude-annotation":"org.junit.Ignore"
+ }
+ ]
+ },
+ {
"name": "CtsPackageInstallerCUJInstallationTestCases",
"options":[
{
@@ -217,6 +228,17 @@
]
},
{
+ "name": "CtsPackageInstallerCUJMultiUsersTestCases",
+ "options":[
+ {
+ "exclude-annotation":"androidx.test.filters.FlakyTest"
+ },
+ {
+ "exclude-annotation":"org.junit.Ignore"
+ }
+ ]
+ },
+ {
"name": "CtsPackageInstallerCUJUninstallationTestCases",
"options":[
{
diff --git a/core/java/android/net/vcn/VcnGatewayConnectionConfig.java b/core/java/android/net/vcn/VcnGatewayConnectionConfig.java
index 6f11d3a..af93c96 100644
--- a/core/java/android/net/vcn/VcnGatewayConnectionConfig.java
+++ b/core/java/android/net/vcn/VcnGatewayConnectionConfig.java
@@ -35,7 +35,6 @@
import android.util.ArraySet;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.ArrayUtils;
import com.android.internal.util.Preconditions;
import com.android.server.vcn.util.PersistableBundleUtils;
@@ -434,7 +433,14 @@
@NonNull
public int[] getExposedCapabilities() {
// Sorted set guarantees ordering
- return ArrayUtils.convertToIntArray(new ArrayList<>(mExposedCapabilities));
+ final int[] caps = new int[mExposedCapabilities.size()];
+
+ int i = 0;
+ for (int c : mExposedCapabilities) {
+ caps[i++] = c;
+ }
+
+ return caps;
}
/**
diff --git a/core/java/android/net/vcn/VcnUnderlyingNetworkSpecifier.java b/core/java/android/net/vcn/VcnUnderlyingNetworkSpecifier.java
index a975637..e1d1b3c6 100644
--- a/core/java/android/net/vcn/VcnUnderlyingNetworkSpecifier.java
+++ b/core/java/android/net/vcn/VcnUnderlyingNetworkSpecifier.java
@@ -24,7 +24,6 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.annotations.VisibleForTesting.Visibility;
-import com.android.internal.util.ArrayUtils;
import java.util.Arrays;
import java.util.Objects;
@@ -114,8 +113,13 @@
@Override
public boolean canBeSatisfiedBy(NetworkSpecifier other) {
if (other instanceof TelephonyNetworkSpecifier) {
- return ArrayUtils.contains(
- mSubIds, ((TelephonyNetworkSpecifier) other).getSubscriptionId());
+ final int targetSubId = ((TelephonyNetworkSpecifier) other).getSubscriptionId();
+ for (int subId : mSubIds) {
+ if (targetSubId == subId) {
+ return true;
+ }
+ }
+ return false;
}
// TODO(b/180140053): Allow matching against WifiNetworkAgentSpecifier
diff --git a/core/java/android/os/BatteryManager.java b/core/java/android/os/BatteryManager.java
index f3efd89..8b267bf 100644
--- a/core/java/android/os/BatteryManager.java
+++ b/core/java/android/os/BatteryManager.java
@@ -141,6 +141,7 @@
/**
* Extra for {@link android.content.Intent#ACTION_BATTERY_CHANGED}:
* integer containing the charge counter present in the battery.
+ * It shows the available battery power in µAh
* {@hide}
*/
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
@@ -166,6 +167,76 @@
public static final String EXTRA_CHARGING_STATUS = "android.os.extra.CHARGING_STATUS";
/**
+ * Extra for {@link android.content.Intent#ACTION_BATTERY_CHANGED}:
+ * Int value representing the battery's capacity level. These constants are key indicators of
+ * battery status and system capabilities, guiding power management decisions for both the
+ * system and apps:
+ * {@link #BATTERY_CAPACITY_LEVEL_UNSUPPORTED}: Feature not supported on this device.
+ * {@link #BATTERY_CAPACITY_LEVEL_UNKNOWN}: Battery status is unavailable or uninitialized.
+ * {@link #BATTERY_CAPACITY_LEVEL_CRITICAL}: Battery is critically low and the Android
+ * framework has been notified to schedule a shutdown by this value
+ * {@link #BATTERY_CAPACITY_LEVEL_LOW}: Android framework must limit background jobs to
+ * avoid impacting charging speed
+ * {@link #BATTERY_CAPACITY_LEVEL_NORMAL}: Battery level and charging rates are normal,
+ * battery temperature is within normal range and adapter power is enough to charge the
+ * battery at an acceptable rate. Android framework can run light background tasks without
+ * affecting charging performance severely.
+ * {@link #BATTERY_CAPACITY_LEVEL_HIGH}: Battery level is high, battery temperature is
+ * within normal range and adapter power is enough to charge the battery at an acceptable
+ * rate while running background loads. Android framework can run background tasks without
+ * affecting charging or battery performance.
+ * {@link #BATTERY_CAPACITY_LEVEL_FULL}: The battery is full, battery temperature is
+ * within normal range and adapter power is enough to sustain running background loads.
+ * Android framework can run background tasks without affecting the battery level or
+ * battery performance.
+ */
+
+ @FlaggedApi(FLAG_BATTERY_PART_STATUS_API)
+ public static final String EXTRA_CAPACITY_LEVEL = "android.os.extra.CAPACITY_LEVEL";
+
+ /**
+ * Battery capacity level is unsupported. @see EXTRA_CAPACITY_LEVEL
+ */
+ @FlaggedApi(FLAG_BATTERY_PART_STATUS_API)
+ public static final int BATTERY_CAPACITY_LEVEL_UNSUPPORTED = -1;
+
+ /**
+ * Battery capacity level is unknown. @see EXTRA_CAPACITY_LEVEL
+ */
+ @FlaggedApi(FLAG_BATTERY_PART_STATUS_API)
+ public static final int BATTERY_CAPACITY_LEVEL_UNKNOWN = 0;
+
+ /**
+ * Battery capacity level is critical. @see EXTRA_CAPACITY_LEVEL
+ */
+ @FlaggedApi(FLAG_BATTERY_PART_STATUS_API)
+ public static final int BATTERY_CAPACITY_LEVEL_CRITICAL = 1;
+
+ /**
+ * Battery capacity level is low. @see EXTRA_CAPACITY_LEVEL
+ */
+ @FlaggedApi(FLAG_BATTERY_PART_STATUS_API)
+ public static final int BATTERY_CAPACITY_LEVEL_LOW = 2;
+
+ /**
+ * Battery capacity level is normal. @see EXTRA_CAPACITY_LEVEL
+ */
+ @FlaggedApi(FLAG_BATTERY_PART_STATUS_API)
+ public static final int BATTERY_CAPACITY_LEVEL_NORMAL = 3;
+
+ /**
+ * Battery capacity level is high. @see EXTRA_CAPACITY_LEVEL
+ */
+ @FlaggedApi(FLAG_BATTERY_PART_STATUS_API)
+ public static final int BATTERY_CAPACITY_LEVEL_HIGH = 4;
+
+ /**
+ * Battery capacity level is full. @see EXTRA_CAPACITY_LEVEL
+ */
+ @FlaggedApi(FLAG_BATTERY_PART_STATUS_API)
+ public static final int BATTERY_CAPACITY_LEVEL_FULL = 5;
+
+ /**
* Extra for {@link android.content.Intent#ACTION_BATTERY_LEVEL_CHANGED}:
* Contains list of Bundles representing battery events
* @hide
diff --git a/core/java/android/text/ClientFlags.java b/core/java/android/text/ClientFlags.java
index c2ad508..ca88764 100644
--- a/core/java/android/text/ClientFlags.java
+++ b/core/java/android/text/ClientFlags.java
@@ -16,21 +16,14 @@
package android.text;
-import com.android.text.flags.Flags;
-
/**
* An aconfig feature flags that can be accessible from application process without
* ContentProvider IPCs.
*
* When you add new flags, you have to add flag string to {@link TextFlags#TEXT_ACONFIGS_FLAGS}.
*
+ * TODO(nona): Remove this class.
* @hide
*/
public class ClientFlags {
- /**
- * @see Flags#fixMisalignedContextMenu()
- */
- public static boolean fixMisalignedContextMenu() {
- return TextFlags.isFeatureEnabled(Flags.FLAG_FIX_MISALIGNED_CONTEXT_MENU);
- }
}
diff --git a/core/java/android/text/TextFlags.java b/core/java/android/text/TextFlags.java
index 076721f..f69a333 100644
--- a/core/java/android/text/TextFlags.java
+++ b/core/java/android/text/TextFlags.java
@@ -19,11 +19,10 @@
import android.annotation.NonNull;
import android.app.AppGlobals;
-import com.android.text.flags.Flags;
-
/**
* Flags in the "text" namespace.
*
+ * TODO(nona): Remove this class.
* @hide
*/
public final class TextFlags {
@@ -55,7 +54,6 @@
* List of text flags to be transferred to the application process.
*/
public static final String[] TEXT_ACONFIGS_FLAGS = {
- Flags.FLAG_FIX_MISALIGNED_CONTEXT_MENU,
};
/**
@@ -64,7 +62,6 @@
* The order must be the same to the TEXT_ACONFIG_FLAGS.
*/
public static final boolean[] TEXT_ACONFIG_DEFAULT_VALUE = {
- Flags.fixMisalignedContextMenu(),
};
/**
diff --git a/core/java/android/text/flags/flags.aconfig b/core/java/android/text/flags/flags.aconfig
index 3846972..c83285a 100644
--- a/core/java/android/text/flags/flags.aconfig
+++ b/core/java/android/text/flags/flags.aconfig
@@ -84,16 +84,6 @@
}
flag {
- name: "fix_misaligned_context_menu"
- namespace: "text"
- description: "Fix the context menu misalignment and incosistent icon size."
- bug: "332542108"
- metadata {
- purpose: PURPOSE_BUGFIX
- }
-}
-
-flag {
name: "missing_getter_apis"
namespace: "text"
description: "Fix the lint warning about missing getters."
diff --git a/core/java/android/window/flags/windowing_sdk.aconfig b/core/java/android/window/flags/windowing_sdk.aconfig
index 13648de..9ae3fc1 100644
--- a/core/java/android/window/flags/windowing_sdk.aconfig
+++ b/core/java/android/window/flags/windowing_sdk.aconfig
@@ -122,3 +122,10 @@
description: "Requires apps to opt-in to overlay pass through touches and provide APIs to opt-in"
bug: "358129114"
}
+
+flag {
+ namespace: "windowing_sdk"
+ name: "wlinfo_oncreate"
+ description: "Makes WindowLayoutInfo accessible without racing in the Activity#onCreate()"
+ bug: "337820752"
+}
diff --git a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
index 4db9ddf..fbc058c 100644
--- a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
+++ b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
@@ -210,6 +210,8 @@
DataSourceParams
.PERFETTO_DS_BUFFER_EXHAUSTED_POLICY_DROP)
.build();
+ // NOTE: Registering that datasource is an async operation, so there may be no data traced
+ // for some messages logged right after the construction of this class.
mDataSource.register(params);
this.mViewerConfigInputStreamProvider = viewerConfigInputStreamProvider;
this.mViewerConfigReader = viewerConfigReader;
diff --git a/core/java/com/android/internal/view/menu/ListMenuItemView.java b/core/java/com/android/internal/view/menu/ListMenuItemView.java
index b73cacb..bdb33c4 100644
--- a/core/java/com/android/internal/view/menu/ListMenuItemView.java
+++ b/core/java/com/android/internal/view/menu/ListMenuItemView.java
@@ -33,8 +33,6 @@
import android.widget.RadioButton;
import android.widget.TextView;
-import com.android.text.flags.Flags;
-
/**
* The item view for each item in the ListView-based MenuViews.
*/
@@ -283,10 +281,7 @@
private void insertIconView() {
LayoutInflater inflater = getInflater();
- mIconView = (ImageView) inflater.inflate(
- !Flags.fixMisalignedContextMenu()
- ? com.android.internal.R.layout.list_menu_item_fixed_size_icon :
- com.android.internal.R.layout.list_menu_item_icon,
+ mIconView = (ImageView) inflater.inflate(com.android.internal.R.layout.list_menu_item_icon,
this, false);
addContentView(mIconView, 0);
}
diff --git a/core/java/com/android/internal/view/menu/StandardMenuPopup.java b/core/java/com/android/internal/view/menu/StandardMenuPopup.java
index c43a8c6..8e2536a 100644
--- a/core/java/com/android/internal/view/menu/StandardMenuPopup.java
+++ b/core/java/com/android/internal/view/menu/StandardMenuPopup.java
@@ -35,8 +35,6 @@
import android.widget.PopupWindow.OnDismissListener;
import android.widget.TextView;
-import com.android.text.flags.Flags;
-
import java.util.Objects;
/**
@@ -122,8 +120,7 @@
mMenu = menu;
mOverflowOnly = overflowOnly;
final LayoutInflater inflater = LayoutInflater.from(context);
- mAdapter = new MenuAdapter(menu, inflater, mOverflowOnly,
- Flags.fixMisalignedContextMenu() ? ITEM_LAYOUT_MATERIAL : ITEM_LAYOUT);
+ mAdapter = new MenuAdapter(menu, inflater, mOverflowOnly, ITEM_LAYOUT_MATERIAL);
mPopupStyleAttr = popupStyleAttr;
mPopupStyleRes = popupStyleRes;
diff --git a/graphics/java/android/graphics/Bitmap.java b/graphics/java/android/graphics/Bitmap.java
index 6d31578..e07471c 100644
--- a/graphics/java/android/graphics/Bitmap.java
+++ b/graphics/java/android/graphics/Bitmap.java
@@ -128,6 +128,22 @@
private static final WeakHashMap<Bitmap, Void> sAllBitmaps = new WeakHashMap<>();
/**
+ * @hide
+ */
+ private static NativeAllocationRegistry getRegistry(boolean malloc, long size) {
+ final long free = nativeGetNativeFinalizer();
+ if (com.android.libcore.Flags.nativeMetrics()) {
+ Class cls = Bitmap.class;
+ return malloc ? NativeAllocationRegistry.createMalloced(cls, free, size)
+ : NativeAllocationRegistry.createNonmalloced(cls, free, size);
+ } else {
+ ClassLoader loader = Bitmap.class.getClassLoader();
+ return malloc ? NativeAllocationRegistry.createMalloced(loader, free, size)
+ : NativeAllocationRegistry.createNonmalloced(loader, free, size);
+ }
+ }
+
+ /**
* Private constructor that must receive an already allocated native bitmap
* int (pointer).
*/
@@ -151,7 +167,6 @@
mWidth = width;
mHeight = height;
mRequestPremultiplied = requestPremultiplied;
-
mNinePatchChunk = ninePatchChunk;
mNinePatchInsets = ninePatchInsets;
if (density >= 0) {
@@ -159,17 +174,9 @@
}
mNativePtr = nativeBitmap;
-
final int allocationByteCount = getAllocationByteCount();
- NativeAllocationRegistry registry;
- if (fromMalloc) {
- registry = NativeAllocationRegistry.createMalloced(
- Bitmap.class.getClassLoader(), nativeGetNativeFinalizer(), allocationByteCount);
- } else {
- registry = NativeAllocationRegistry.createNonmalloced(
- Bitmap.class.getClassLoader(), nativeGetNativeFinalizer(), allocationByteCount);
- }
- registry.registerNativeAllocation(this, nativeBitmap);
+ getRegistry(fromMalloc, allocationByteCount).registerNativeAllocation(this, mNativePtr);
+
synchronized (Bitmap.class) {
sAllBitmaps.put(this, null);
}
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java
index dd86a1a..83619ef 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java
@@ -90,9 +90,6 @@
/** The maximum override density allowed for tasks inside the desktop. */
private static final int DESKTOP_DENSITY_MAX = 1000;
- /** The number of [WindowDecorViewHost] instances to warm up on system start. */
- private static final int WINDOW_DECOR_PRE_WARM_SIZE = 2;
-
/**
* Sysprop declaring whether to enters desktop mode by default when the windowing mode of the
* display's root TaskDisplayArea is set to WINDOWING_MODE_FREEFORM.
@@ -115,14 +112,6 @@
private static final String MAX_TASK_LIMIT_SYS_PROP = "persist.wm.debug.desktop_max_task_limit";
/**
- * Sysprop declaring the number of [WindowDecorViewHost] instances to warm up on system start.
- *
- * <p>If it is not defined, then [WINDOW_DECOR_PRE_WARM_SIZE] is used.
- */
- private static final String WINDOW_DECOR_PRE_WARM_SIZE_SYS_PROP =
- "persist.wm.debug.desktop_window_decor_pre_warm_size";
-
- /**
* Return {@code true} if veiled resizing is active. If false, fluid resizing is used.
*/
public static boolean isVeiledResizeEnabled() {
@@ -162,12 +151,6 @@
context.getResources().getInteger(R.integer.config_maxDesktopWindowingActiveTasks));
}
- /** The number of [WindowDecorViewHost] instances to warm up on system start. */
- public static int getWindowDecorPreWarmSize() {
- return SystemProperties.getInt(WINDOW_DECOR_PRE_WARM_SIZE_SYS_PROP,
- WINDOW_DECOR_PRE_WARM_SIZE);
- }
-
/**
* Return {@code true} if the current device supports desktop mode.
*/
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index c545d73..af4a0c5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -1241,8 +1241,9 @@
mBubbleData.dismissBubbleWithKey(
bubbleKey, Bubbles.DISMISS_USER_GESTURE_FROM_LAUNCHER, timestamp);
}
- if (selectedBubbleKey != null && !selectedBubbleKey.equals(bubbleKey)) {
- // We did not remove the selected bubble. Expand it again
+ if (mBubbleData.hasBubbles()) {
+ // We still have bubbles, if we dragged an individual bubble to dismiss we were expanded
+ // so re-expand to whatever is selected.
showExpandedViewForBubbleBar();
}
}
@@ -2007,7 +2008,7 @@
@Override
public void selectionChanged(BubbleViewProvider selectedBubble) {
// Only need to update the layer view if we're currently expanded for selection changes.
- if (mLayerView != null && isStackExpanded()) {
+ if (mLayerView != null && mLayerView.isExpanded()) {
mLayerView.showExpandedView(selectedBubble);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
index 1c9c195..1367b7e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
@@ -186,6 +186,10 @@
if (expandedView == null) {
return;
}
+ if (mExpandedBubble != null && mIsExpanded && b.getKey().equals(mExpandedBubble.getKey())) {
+ // Already showing this bubble, skip animating
+ return;
+ }
if (mExpandedBubble != null && !b.getKey().equals(mExpandedBubble.getKey())) {
removeView(mExpandedView);
mExpandedView = null;
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 b47adb4..2f4d77b 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
@@ -116,8 +116,6 @@
import com.android.wm.shell.windowdecor.DesktopModeWindowDecorViewModel;
import com.android.wm.shell.windowdecor.WindowDecorViewModel;
import com.android.wm.shell.windowdecor.viewhost.DefaultWindowDecorViewHostSupplier;
-import com.android.wm.shell.windowdecor.viewhost.PooledWindowDecorViewHostSupplier;
-import com.android.wm.shell.windowdecor.viewhost.ReusableWindowDecorViewHost;
import com.android.wm.shell.windowdecor.viewhost.WindowDecorViewHostSupplier;
import dagger.Binds;
@@ -384,19 +382,8 @@
@WMSingleton
@Provides
static WindowDecorViewHostSupplier provideWindowDecorViewHostSupplier(
- @NonNull Context context,
- @ShellMainThread @NonNull CoroutineScope mainScope,
- @NonNull ShellInit shellInit) {
- if (DesktopModeStatus.canEnterDesktopMode(context)
- && Flags.enableDesktopWindowingScvhCache()) {
- final int maxPoolSize = DesktopModeStatus.getMaxTaskLimit(context);
- final int preWarmSize = DesktopModeStatus.getWindowDecorPreWarmSize();
- return new PooledWindowDecorViewHostSupplier(
- context, mainScope, shellInit,
- ReusableWindowDecorViewHost.DefaultFactory.INSTANCE, maxPoolSize, preWarmSize);
- } else {
- return new DefaultWindowDecorViewHostSupplier(mainScope);
- }
+ @ShellMainThread @NonNull CoroutineScope mainScope) {
+ return new DefaultWindowDecorViewHostSupplier(mainScope);
}
//
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 853284a..b8ebbcd 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
@@ -305,13 +305,18 @@
private fun getSplitFocusedTask(task1: RunningTaskInfo, task2: RunningTaskInfo) =
if (task1.taskId == task2.parentTaskId) task2 else task1
- private fun isFreeformDisplay(displayId: Int): Boolean {
+ private fun forceEnterDesktop(displayId: Int): Boolean {
+ if (!DesktopModeStatus.enterDesktopByDefaultOnFreeformDisplay(context)) {
+ return false
+ }
+
val tdaInfo = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(displayId)
requireNotNull(tdaInfo) {
"This method can only be called with the ID of a display having non-null DisplayArea."
}
val tdaWindowingMode = tdaInfo.configuration.windowConfiguration.windowingMode
- return tdaWindowingMode == WINDOWING_MODE_FREEFORM
+ val isFreeformDisplay = tdaWindowingMode == WINDOWING_MODE_FREEFORM
+ return isFreeformDisplay
}
/** Moves task to desktop mode if task is running, else launches it in desktop mode. */
@@ -1191,10 +1196,11 @@
val wct = WindowContainerTransaction()
if (!isDesktopModeShowing(task.displayId)) {
logD("Bring desktop tasks to front on transition=taskId=%d", task.taskId)
- // We are outside of desktop mode and already existing desktop task is being launched.
- // We should make this task go to fullscreen instead of freeform. Note that this means
- // any re-launch of a freeform window outside of desktop will be in fullscreen.
- if (taskRepository.isActiveTask(task.taskId)) {
+ if (taskRepository.isActiveTask(task.taskId) && !forceEnterDesktop(task.displayId)) {
+ // We are outside of desktop mode and already existing desktop task is being
+ // launched. We should make this task go to fullscreen instead of freeform. Note
+ // that this means any re-launch of a freeform window outside of desktop will be in
+ // fullscreen as long as default-desktop flag is disabled.
addMoveToFullscreenChanges(wct, task)
return wct
}
@@ -1231,9 +1237,7 @@
transition: IBinder
): WindowContainerTransaction? {
logV("handleFullscreenTaskLaunch")
- val forceEnterDesktop = DesktopModeStatus.enterDesktopByDefaultOnFreeformDisplay(context) &&
- isFreeformDisplay(task.displayId)
- if (isDesktopModeShowing(task.displayId) || forceEnterDesktop) {
+ if (isDesktopModeShowing(task.displayId) || forceEnterDesktop(task.displayId)) {
logD("Switch fullscreen task to freeform on transition: taskId=%d", task.taskId)
return WindowContainerTransaction().also { wct ->
addMoveToDesktopChanges(wct, task)
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 5a905cf..e8eb10c 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
@@ -2456,6 +2456,7 @@
final StageChangeRecord record = new StageChangeRecord();
final int transitType = info.getType();
TransitionInfo.Change pipChange = null;
+ int closingSplitTaskId = -1;
for (int iC = 0; iC < info.getChanges().size(); ++iC) {
final TransitionInfo.Change change = info.getChanges().get(iC);
if (change.getMode() == TRANSIT_CHANGE
@@ -2516,21 +2517,31 @@
+ " with " + taskInfo.taskId + " before startAnimation().");
}
}
+ if (isClosingType(change.getMode()) &&
+ getStageOfTask(change.getTaskInfo().taskId) != STAGE_TYPE_UNDEFINED) {
+ // If either one of the 2 stages is closing we're assuming we'll break split
+ closingSplitTaskId = change.getTaskInfo().taskId;
+ }
}
if (pipChange != null) {
TransitionInfo.Change pipReplacingChange = getPipReplacingChange(info, pipChange,
mMainStage.mRootTaskInfo.taskId, mSideStage.mRootTaskInfo.taskId,
getSplitItemStage(pipChange.getLastParent()));
- if (pipReplacingChange != null) {
+ boolean keepSplitWithPip = pipReplacingChange != null && closingSplitTaskId == -1;
+ if (keepSplitWithPip) {
// Set an enter transition for when startAnimation gets called again
mSplitTransitions.setEnterTransition(transition, /*remoteTransition*/ null,
TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE, /*resizeAnim*/ false);
+ } else {
+ int finalClosingTaskId = closingSplitTaskId;
+ mRecentTasks.ifPresent(recentTasks ->
+ recentTasks.removeSplitPair(finalClosingTaskId));
+ logExit(EXIT_REASON_FULLSCREEN_REQUEST);
}
mMixedHandler.animatePendingEnterPipFromSplit(transition, info,
- startTransaction, finishTransaction, finishCallback,
- pipReplacingChange != null);
+ startTransaction, finishTransaction, finishCallback, keepSplitWithPip);
notifySplitAnimationFinished();
return true;
}
@@ -2821,8 +2832,12 @@
}
callbackWct.setReparentLeafTaskIfRelaunch(mRootTaskInfo.token, false);
mWindowDecorViewModel.ifPresent(viewModel -> {
- viewModel.onTaskInfoChanged(finalMainChild.getTaskInfo());
- viewModel.onTaskInfoChanged(finalSideChild.getTaskInfo());
+ if (finalMainChild != null) {
+ viewModel.onTaskInfoChanged(finalMainChild.getTaskInfo());
+ }
+ if (finalSideChild != null) {
+ viewModel.onTaskInfoChanged(finalSideChild.getTaskInfo());
+ }
});
mPausingTasks.clear();
});
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 02c818f..caac2f6 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
@@ -1191,8 +1191,10 @@
: SPLIT_POSITION_TOP_OR_LEFT;
final RunningTaskInfo oppositeTaskInfo =
mSplitScreenController.getTaskInfo(oppositePosition);
- mWindowDecorByTaskId.get(oppositeTaskInfo.taskId)
- .disposeStatusBarInputLayer();
+ if (oppositeTaskInfo != null) {
+ mWindowDecorByTaskId.get(oppositeTaskInfo.taskId)
+ .disposeStatusBarInputLayer();
+ }
}
}
mMoveToDesktopAnimator = null;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
index 6f3f411..6eb5cca 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
@@ -26,6 +26,8 @@
import android.graphics.Rect;
import android.os.Handler;
import android.os.IBinder;
+import android.os.Looper;
+import android.view.Choreographer;
import android.view.Surface;
import android.view.SurfaceControl;
import android.window.TransitionInfo;
@@ -124,6 +126,11 @@
@Override
public Rect onDragPositioningMove(float x, float y) {
+ if (Looper.myLooper() != mHandler.getLooper()) {
+ // This method must run on the shell main thread to use the correct Choreographer
+ // instance below.
+ throw new IllegalStateException("This method must run on the shell main thread.");
+ }
PointF delta = DragPositioningCallbackUtility.calculateDelta(x, y, mRepositionStartPoint);
if (isResizing() && DragPositioningCallbackUtility.changeBounds(mCtrlType,
mRepositionTaskBounds, mTaskBoundsAtDragStart, mStableBounds, delta,
@@ -141,6 +148,7 @@
final SurfaceControl.Transaction t = mTransactionSupplier.get();
DragPositioningCallbackUtility.setPositionOnDrag(mDesktopWindowDecoration,
mRepositionTaskBounds, mTaskBoundsAtDragStart, mRepositionStartPoint, t, x, y);
+ t.setFrameTimeline(Choreographer.getInstance().getVsyncId());
t.apply();
}
return new Rect(mRepositionTaskBounds);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewhost/DefaultWindowDecorViewHost.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewhost/DefaultWindowDecorViewHost.kt
index 5156e47..139e679 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewhost/DefaultWindowDecorViewHost.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewhost/DefaultWindowDecorViewHost.kt
@@ -19,33 +19,51 @@
import android.content.res.Configuration
import android.view.Display
import android.view.SurfaceControl
+import android.view.SurfaceControlViewHost
import android.view.View
import android.view.WindowManager
+import android.view.WindowlessWindowManager
import androidx.tracing.Trace
import com.android.internal.annotations.VisibleForTesting
import com.android.wm.shell.shared.annotations.ShellMainThread
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
+typealias SurfaceControlViewHostFactory =
+ (Context, Display, WindowlessWindowManager, String) -> SurfaceControlViewHost
/**
- * A default implementation of [WindowDecorViewHost] backed by a [SurfaceControlViewHostAdapter].
+ * A default implementation of [WindowDecorViewHost] backed by a [SurfaceControlViewHost].
*
- * It supports asynchronously updating the view hierarchy using [updateViewAsync], in which
+ * It does not support swapping the root view added to the VRI of the [SurfaceControlViewHost], and
+ * any attempts to do will throw, which means that once a [View] is added using [updateView] or
+ * [updateViewAsync], only its properties and binding may be changed, its children views may be
+ * added, removed or changed and its [WindowManager.LayoutParams] may be changed.
+ * It also supports asynchronously updating the view hierarchy using [updateViewAsync], in which
* case the update work will be posted on the [ShellMainThread] with no delay.
*/
class DefaultWindowDecorViewHost(
- context: Context,
+ private val context: Context,
@ShellMainThread private val mainScope: CoroutineScope,
- display: Display,
- @VisibleForTesting val viewHostAdapter: SurfaceControlViewHostAdapter =
- SurfaceControlViewHostAdapter(context, display)
+ private val display: Display,
+ private val surfaceControlViewHostFactory: SurfaceControlViewHostFactory = { c, d, wwm, s ->
+ SurfaceControlViewHost(c, d, wwm, s)
+ }
) : WindowDecorViewHost {
+ private val rootSurface: SurfaceControl = SurfaceControl.Builder()
+ .setName("DefaultWindowDecorViewHost surface")
+ .setContainerLayer()
+ .setCallsite("DefaultWindowDecorViewHost#init")
+ .build()
+
+ private var wwm: WindowlessWindowManager? = null
+ @VisibleForTesting
+ var viewHost: SurfaceControlViewHost? = null
private var currentUpdateJob: Job? = null
override val surfaceControl: SurfaceControl
- get() = viewHostAdapter.rootSurface
+ get() = rootSurface
override fun updateView(
view: View,
@@ -74,7 +92,8 @@
override fun release(t: SurfaceControl.Transaction) {
clearCurrentUpdateJob()
- viewHostAdapter.release(t)
+ viewHost?.release()
+ t.remove(rootSurface)
}
private fun updateViewHost(
@@ -83,15 +102,45 @@
configuration: Configuration,
onDrawTransaction: SurfaceControl.Transaction?
) {
- viewHostAdapter.prepareViewHost(configuration)
- onDrawTransaction?.let {
- viewHostAdapter.applyTransactionOnDraw(it)
+ Trace.beginSection("DefaultWindowDecorViewHost#updateViewHost")
+ if (wwm == null) {
+ wwm = WindowlessWindowManager(configuration, rootSurface, null)
}
- viewHostAdapter.updateView(view, attrs)
+ requireWindowlessWindowManager().setConfiguration(configuration)
+ if (viewHost == null) {
+ viewHost = surfaceControlViewHostFactory.invoke(
+ context,
+ display,
+ requireWindowlessWindowManager(),
+ "DefaultWindowDecorViewHost#updateViewHost"
+ )
+ }
+ onDrawTransaction?.let {
+ requireViewHost().rootSurfaceControl.applyTransactionOnDraw(it)
+ }
+ if (requireViewHost().view == null) {
+ Trace.beginSection("DefaultWindowDecorViewHost#updateViewHost-setView")
+ requireViewHost().setView(view, attrs)
+ Trace.endSection()
+ } else {
+ check(requireViewHost().view == view) { "Changing view is not allowed" }
+ Trace.beginSection("DefaultWindowDecorViewHost#updateViewHost-relayout")
+ requireViewHost().relayout(attrs)
+ Trace.endSection()
+ }
+ Trace.endSection()
}
private fun clearCurrentUpdateJob() {
currentUpdateJob?.cancel()
currentUpdateJob = null
}
+
+ private fun requireWindowlessWindowManager(): WindowlessWindowManager {
+ return wwm ?: error("Expected non-null windowless window manager")
+ }
+
+ private fun requireViewHost(): SurfaceControlViewHost {
+ return viewHost ?: error("Expected non-null view host")
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewhost/PooledWindowDecorViewHostSupplier.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewhost/PooledWindowDecorViewHostSupplier.kt
deleted file mode 100644
index b04188f..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewhost/PooledWindowDecorViewHostSupplier.kt
+++ /dev/null
@@ -1,105 +0,0 @@
-/*
- * 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.windowdecor.viewhost
-
-import android.content.Context
-import android.os.Trace
-import android.util.Pools
-import android.view.Display
-import android.view.SurfaceControl
-import com.android.wm.shell.shared.annotations.ShellMainThread
-import com.android.wm.shell.sysui.ShellInit
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.launch
-
-/**
- * A [WindowDecorViewHostSupplier] backed by a pool to allow recycling view hosts which may be
- * expensive to recreate for each new/updated window decoration.
- *
- * Callers can obtain [ReusableWindowDecorViewHost] using [acquire], which will return a pooled
- * object if available, or create a new instance and return it if needed. When done using a
- * [ReusableWindowDecorViewHost], it must be released using [release] to allow it to be sent back
- * into the pool and reused later on.
- *
- * This class also supports pre-warming [ReusableWindowDecorViewHost] instances, which will be put
- * into the pool immediately after creation.
- */
-class PooledWindowDecorViewHostSupplier(
- private val context: Context,
- @ShellMainThread private val mainScope: CoroutineScope,
- shellInit: ShellInit,
- private val viewHostFactory: ReusableWindowDecorViewHost.Factory =
- ReusableWindowDecorViewHost.DefaultFactory,
- maxPoolSize: Int,
- private val preWarmSize: Int,
-) : WindowDecorViewHostSupplier<ReusableWindowDecorViewHost> {
-
- private val pool: Pools.Pool<ReusableWindowDecorViewHost> = Pools.SynchronizedPool(maxPoolSize)
- private var nextDecorViewHostId = 0
-
- init {
- require(preWarmSize <= maxPoolSize) { "Pre-warm size should not exceed pool size" }
- shellInit.addInitCallback(this::onShellInit, this)
- }
-
- private fun onShellInit() {
- if (preWarmSize <= 0) {
- return
- }
- preWarmViewHosts(preWarmSize)
- }
-
- private fun preWarmViewHosts(preWarmSize: Int) {
- mainScope.launch {
- // Applying isn't needed, as the surface was never actually shown.
- val t = SurfaceControl.Transaction()
- repeat(preWarmSize) {
- val warmedViewHost = create(context, context.display).apply {
- warmUp()
- }
- // Put the warmed view host in the pool by releasing it.
- release(warmedViewHost, t)
- }
- }
- }
-
- override fun acquire(context: Context, display: Display): ReusableWindowDecorViewHost {
- val reusedDecorViewHost = pool.acquire()
- if (reusedDecorViewHost != null) {
- return reusedDecorViewHost
- }
- Trace.beginSection("WindowDecorViewHostPool#acquire-new")
- val newDecorViewHost = create(context, display)
- Trace.endSection()
- return newDecorViewHost
- }
-
- override fun release(viewHost: ReusableWindowDecorViewHost, t: SurfaceControl.Transaction) {
- val cached = pool.release(viewHost)
- if (!cached) {
- viewHost.release(t)
- }
- }
-
- private fun create(context: Context, display: Display): ReusableWindowDecorViewHost {
- return viewHostFactory.create(
- context,
- mainScope,
- display,
- nextDecorViewHostId++
- )
- }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewhost/ReusableWindowDecorViewHost.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewhost/ReusableWindowDecorViewHost.kt
deleted file mode 100644
index 64536d1..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewhost/ReusableWindowDecorViewHost.kt
+++ /dev/null
@@ -1,161 +0,0 @@
-/*
- * 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.windowdecor.viewhost
-
-import android.content.Context
-import android.content.res.Configuration
-import android.graphics.PixelFormat
-import android.os.Trace
-import android.view.Display
-import android.view.SurfaceControl
-import android.view.SurfaceControlViewHost
-import android.view.View
-import android.view.WindowManager
-import android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
-import android.view.WindowManager.LayoutParams.FLAG_SPLIT_TOUCH
-import android.view.WindowManager.LayoutParams.TYPE_APPLICATION
-import android.widget.FrameLayout
-import com.android.internal.annotations.VisibleForTesting
-import com.android.wm.shell.shared.annotations.ShellMainThread
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Job
-import kotlinx.coroutines.launch
-
-/**
- * An implementation of [WindowDecorViewHost] that supports:
- * 1) Replacing the root [View], meaning [WindowDecorViewHost.updateView] maybe be
- * called with different [View] instances. This is useful when reusing [WindowDecorViewHost]s
- * instances for vastly different view hierarchies, such as Desktop Windowing's App Handles and
- * App Headers.
- * 2) Pre-warming of the underlying [SurfaceControlViewHost]s. Useful because their creation and
- * first root view assignment are expensive, which is undesirable in latency-sensitive code
- * paths like during a shell transition.
- */
-class ReusableWindowDecorViewHost(
- private val context: Context,
- @ShellMainThread private val mainScope: CoroutineScope,
- display: Display,
- val id: Int,
- @VisibleForTesting val viewHostAdapter: SurfaceControlViewHostAdapter =
- SurfaceControlViewHostAdapter(context, display)
-) : WindowDecorViewHost, Warmable {
-
- @VisibleForTesting
- val rootView = FrameLayout(context)
-
- private var currentUpdateJob: Job? = null
-
- override val surfaceControl: SurfaceControl
- get() = viewHostAdapter.rootSurface
-
- override fun warmUp() {
- if (viewHostAdapter.isInitialized()) {
- // Already warmed up.
- return
- }
- Trace.beginSection("$TAG#warmUp")
- viewHostAdapter.prepareViewHost(context.resources.configuration)
- viewHostAdapter.updateView(
- rootView,
- WindowManager.LayoutParams(
- 0 /* width*/,
- 0 /* height */,
- TYPE_APPLICATION,
- FLAG_NOT_FOCUSABLE or FLAG_SPLIT_TOUCH,
- PixelFormat.TRANSPARENT
- ).apply {
- setTitle("View root of $TAG#$id")
- setTrustedOverlay()
- }
- )
- Trace.endSection()
- }
-
- override fun updateView(
- view: View,
- attrs: WindowManager.LayoutParams,
- configuration: Configuration,
- onDrawTransaction: SurfaceControl.Transaction?
- ) {
- clearCurrentUpdateJob()
- updateViewHost(view, attrs, configuration, onDrawTransaction)
- }
-
- override fun updateViewAsync(
- view: View,
- attrs: WindowManager.LayoutParams,
- configuration: Configuration
- ) {
- clearCurrentUpdateJob()
- currentUpdateJob = mainScope.launch {
- updateViewHost(view, attrs, configuration, onDrawTransaction = null)
- }
- }
-
- override fun release(t: SurfaceControl.Transaction) {
- clearCurrentUpdateJob()
- viewHostAdapter.release(t)
- }
-
- private fun updateViewHost(
- view: View,
- attrs: WindowManager.LayoutParams,
- configuration: Configuration,
- onDrawTransaction: SurfaceControl.Transaction?
- ) {
- viewHostAdapter.prepareViewHost(configuration)
- onDrawTransaction?.let {
- viewHostAdapter.applyTransactionOnDraw(it)
- }
- rootView.removeAllViews()
- rootView.addView(view)
- viewHostAdapter.updateView(rootView, attrs)
- }
-
- private fun clearCurrentUpdateJob() {
- currentUpdateJob?.cancel()
- currentUpdateJob = null
- }
-
- interface Factory {
- fun create(
- context: Context,
- @ShellMainThread mainScope: CoroutineScope,
- display: Display,
- id: Int
- ): ReusableWindowDecorViewHost
- }
-
- object DefaultFactory : Factory {
- override fun create(
- context: Context,
- @ShellMainThread mainScope: CoroutineScope,
- display: Display,
- id: Int
- ): ReusableWindowDecorViewHost {
- return ReusableWindowDecorViewHost(
- context,
- mainScope,
- display,
- id
- )
- }
- }
-
- companion object {
- private const val TAG = "ReusableWindowDecorViewHost"
- }
-}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewhost/SurfaceControlViewHostAdapter.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewhost/SurfaceControlViewHostAdapter.kt
deleted file mode 100644
index a54c9ba..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewhost/SurfaceControlViewHostAdapter.kt
+++ /dev/null
@@ -1,111 +0,0 @@
-/*
- * 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.windowdecor.viewhost
-
-import android.content.Context
-import android.content.res.Configuration
-import android.view.AttachedSurfaceControl
-import android.view.Display
-import android.view.SurfaceControl
-import android.view.SurfaceControlViewHost
-import android.view.View
-import android.view.WindowManager
-import android.view.WindowlessWindowManager
-import androidx.tracing.Trace
-import com.android.internal.annotations.VisibleForTesting
-typealias SurfaceControlViewHostFactory =
- (Context, Display, WindowlessWindowManager, String) -> SurfaceControlViewHost
-
-/**
- * Adapter for a [SurfaceControlViewHost] and its backing [SurfaceControl].
- *
- * It does not support swapping the root view added to the VRI of the [SurfaceControlViewHost], and
- * any attempts to do will throw, which means that once a [View] is added using [updateView], only
- * its properties and binding may be changed, its children views may be added, removed or changed
- * and its [WindowManager.LayoutParams] may be changed.
- */
-class SurfaceControlViewHostAdapter(
- private val context: Context,
- private val display: Display,
- private val surfaceControlViewHostFactory: SurfaceControlViewHostFactory = { c, d, wwm, s ->
- SurfaceControlViewHost(c, d, wwm, s)
- }
-) {
- val rootSurface: SurfaceControl = SurfaceControl.Builder()
- .setName("SurfaceControlViewHostAdapter surface")
- .setContainerLayer()
- .setCallsite("SurfaceControlViewHostAdapter#init")
- .build()
-
- private var wwm: WindowlessWindowManager? = null
- @VisibleForTesting
- var viewHost: SurfaceControlViewHost? = null
-
- /** Initialize the [SurfaceControlViewHost] if needed. */
- fun prepareViewHost(configuration: Configuration) {
- if (wwm == null) {
- wwm = WindowlessWindowManager(configuration, rootSurface, null)
- }
- requireWindowlessWindowManager().setConfiguration(configuration)
- if (viewHost == null) {
- viewHost = surfaceControlViewHostFactory.invoke(
- context,
- display,
- requireWindowlessWindowManager(),
- "SurfaceControlViewHostAdapter#prepareViewHost"
- )
- }
- }
-
- /**
- * Request to apply the transaction atomically with the next draw of the view hierarchy.
- * See [AttachedSurfaceControl.applyTransactionOnDraw].
- */
- fun applyTransactionOnDraw(t: SurfaceControl.Transaction) {
- requireViewHost().rootSurfaceControl.applyTransactionOnDraw(t)
- }
-
- /** Update the view hierarchy of the view host. */
- fun updateView(view: View, attrs: WindowManager.LayoutParams) {
- if (requireViewHost().view == null) {
- Trace.beginSection("SurfaceControlViewHostAdapter#updateView-setView")
- requireViewHost().setView(view, attrs)
- Trace.endSection()
- } else {
- check(requireViewHost().view == view) { "Changing view is not allowed" }
- Trace.beginSection("SurfaceControlViewHostAdapter#updateView-relayout")
- requireViewHost().relayout(attrs)
- Trace.endSection()
- }
- }
-
- /** Release the view host and remove the backing surface. */
- fun release(t: SurfaceControl.Transaction) {
- viewHost?.release()
- t.remove(rootSurface)
- }
-
- /** Whether the view host has had a view hierarchy set. */
- fun isInitialized(): Boolean = viewHost?.view != null
-
- private fun requireWindowlessWindowManager(): WindowlessWindowManager {
- return wwm ?: error("Expected non-null windowless window manager")
- }
-
- private fun requireViewHost(): SurfaceControlViewHost {
- return viewHost ?: error("Expected non-null view host")
- }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewhost/Warmable.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewhost/Warmable.kt
deleted file mode 100644
index 0df9bfa..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewhost/Warmable.kt
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * 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.windowdecor.viewhost
-
-/**
- * An interface for an object that can be warmed up before it's needed.
- */
-interface Warmable {
- fun warmUp()
-}
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 e610ebd..8f20841 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
@@ -1764,6 +1764,37 @@
}
@Test
+ fun handleRequest_freeformTask_relaunchTask_enforceDesktop_freeformDisplay_noWinModeChange() {
+ assumeTrue(ENABLE_SHELL_TRANSITIONS)
+ whenever(DesktopModeStatus.enterDesktopByDefaultOnFreeformDisplay(context)).thenReturn(true)
+ val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!!
+ tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FREEFORM
+
+ val freeformTask = setUpFreeformTask()
+ markTaskHidden(freeformTask)
+ val wct = controller.handleRequest(Binder(), createTransition(freeformTask))
+
+ assertNotNull(wct, "should handle request")
+ assertFalse(wct.anyWindowingModeChange(freeformTask.token))
+ }
+
+ @Test
+ fun handleRequest_freeformTask_relaunchTask_enforceDesktop_fullscreenDisplay_becomesUndefined() {
+ assumeTrue(ENABLE_SHELL_TRANSITIONS)
+ whenever(DesktopModeStatus.enterDesktopByDefaultOnFreeformDisplay(context)).thenReturn(true)
+ val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!!
+ tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FULLSCREEN
+
+ val freeformTask = setUpFreeformTask()
+ markTaskHidden(freeformTask)
+ val wct = controller.handleRequest(Binder(), createTransition(freeformTask))
+
+ assertNotNull(wct, "should handle request")
+ assertThat(wct.changes[freeformTask.token.asBinder()]?.windowingMode)
+ .isEqualTo(WINDOWING_MODE_UNDEFINED)
+ }
+
+ @Test
@DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
fun handleRequest_freeformTask_desktopWallpaperDisabled_freeformNotVisible_reorderedToTop() {
assumeTrue(ENABLE_SHELL_TRANSITIONS)
@@ -3493,6 +3524,14 @@
} ?: false
}
+private fun WindowContainerTransaction?.anyWindowingModeChange(
+ token: WindowContainerToken
+): Boolean {
+return this?.changes?.any { change ->
+ change.key == token.asBinder() && change.value.windowingMode >= 0
+} ?: false
+}
+
private fun createTaskInfo(id: Int) =
RecentTaskInfo().apply {
taskId = id
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt
index ab41d9c..1273ee8 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt
@@ -23,6 +23,7 @@
import android.graphics.Rect
import android.os.Handler
import android.os.IBinder
+import android.os.Looper
import android.testing.AndroidTestingRunner
import android.view.Display
import android.view.Surface.ROTATION_0
@@ -34,6 +35,7 @@
import android.window.TransitionInfo
import android.window.WindowContainerToken
import androidx.test.filters.SmallTest
+import androidx.test.internal.runner.junit4.statement.UiThreadStatement.runOnUiThread
import com.android.internal.jank.InteractionJankMonitor
import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.ShellTestCase
@@ -108,8 +110,7 @@
private lateinit var mockResources: Resources
@Mock
private lateinit var mockInteractionJankMonitor: InteractionJankMonitor
- @Mock
- private lateinit var mockHandler: Handler
+ private val mainHandler = Handler(Looper.getMainLooper())
private lateinit var taskPositioner: VeiledResizeTaskPositioner
@@ -159,12 +160,12 @@
mockTransactionFactory,
mockTransitions,
mockInteractionJankMonitor,
- mockHandler,
+ mainHandler,
)
}
@Test
- fun testDragResize_noMove_doesNotShowResizeVeil() {
+ fun testDragResize_noMove_doesNotShowResizeVeil() = runOnUiThread {
taskPositioner.onDragPositioningStart(
CTRL_TYPE_TOP or CTRL_TYPE_RIGHT,
STARTING_BOUNDS.left.toFloat(),
@@ -176,6 +177,7 @@
STARTING_BOUNDS.left.toFloat(),
STARTING_BOUNDS.top.toFloat()
)
+
verify(mockTransitions, never()).startTransition(eq(TRANSIT_CHANGE), argThat { wct ->
return@argThat wct.changes.any { (token, change) ->
token == taskBinder &&
@@ -186,7 +188,7 @@
}
@Test
- fun testDragResize_movesTask_doesNotShowResizeVeil() {
+ fun testDragResize_movesTask_doesNotShowResizeVeil() = runOnUiThread {
taskPositioner.onDragPositioningStart(
CTRL_TYPE_UNDEFINED,
STARTING_BOUNDS.left.toFloat(),
@@ -221,7 +223,7 @@
}
@Test
- fun testDragResize_resize_boundsUpdateOnEnd() {
+ fun testDragResize_resize_boundsUpdateOnEnd() = runOnUiThread {
taskPositioner.onDragPositioningStart(
CTRL_TYPE_RIGHT or CTRL_TYPE_TOP,
STARTING_BOUNDS.right.toFloat(),
@@ -262,7 +264,7 @@
}
@Test
- fun testDragResize_noEffectiveMove_skipsTransactionOnEnd() {
+ fun testDragResize_noEffectiveMove_skipsTransactionOnEnd() = runOnUiThread {
taskPositioner.onDragPositioningStart(
CTRL_TYPE_TOP or CTRL_TYPE_RIGHT,
STARTING_BOUNDS.left.toFloat(),
@@ -294,9 +296,8 @@
})
}
-
@Test
- fun testDragResize_drag_setBoundsNotRunIfDragEndsInDisallowedEndArea() {
+ fun testDragResize_drag_setBoundsNotRunIfDragEndsInDisallowedEndArea() = runOnUiThread {
taskPositioner.onDragPositioningStart(
CTRL_TYPE_UNDEFINED, // drag
STARTING_BOUNDS.left.toFloat(),
@@ -321,7 +322,7 @@
}
@Test
- fun testDragResize_resize_resizingTaskReorderedToTopWhenNotFocused() {
+ fun testDragResize_resize_resizingTaskReorderedToTopWhenNotFocused() = runOnUiThread {
mockDesktopWindowDecoration.mTaskInfo.isFocused = false
taskPositioner.onDragPositioningStart(
CTRL_TYPE_RIGHT, // Resize right
@@ -337,7 +338,7 @@
}
@Test
- fun testDragResize_resize_resizingTaskNotReorderedToTopWhenFocused() {
+ fun testDragResize_resize_resizingTaskNotReorderedToTopWhenFocused() = runOnUiThread {
mockDesktopWindowDecoration.mTaskInfo.isFocused = true
taskPositioner.onDragPositioningStart(
CTRL_TYPE_RIGHT, // Resize right
@@ -353,7 +354,7 @@
}
@Test
- fun testDragResize_drag_draggedTaskNotReorderedToTop() {
+ fun testDragResize_drag_draggedTaskNotReorderedToTop() = runOnUiThread {
mockDesktopWindowDecoration.mTaskInfo.isFocused = false
taskPositioner.onDragPositioningStart(
CTRL_TYPE_UNDEFINED, // drag
@@ -370,7 +371,7 @@
}
@Test
- fun testDragResize_drag_updatesStableBoundsOnRotate() {
+ fun testDragResize_drag_updatesStableBoundsOnRotate() = runOnUiThread {
// Test landscape stable bounds
performDrag(STARTING_BOUNDS.right.toFloat(), STARTING_BOUNDS.bottom.toFloat(),
STARTING_BOUNDS.right.toFloat() + 2000, STARTING_BOUNDS.bottom.toFloat() + 2000,
@@ -416,7 +417,7 @@
}
@Test
- fun testIsResizingOrAnimatingResizeSet() {
+ fun testIsResizingOrAnimatingResizeSet() = runOnUiThread {
Assert.assertFalse(taskPositioner.isResizingOrAnimating)
taskPositioner.onDragPositioningStart(
@@ -443,7 +444,7 @@
}
@Test
- fun testIsResizingOrAnimatingResizeResetAfterStartAnimation() {
+ fun testIsResizingOrAnimatingResizeResetAfterStartAnimation() = runOnUiThread {
performDrag(
STARTING_BOUNDS.left.toFloat(), STARTING_BOUNDS.top.toFloat(),
STARTING_BOUNDS.left.toFloat() - 20, STARTING_BOUNDS.top.toFloat() - 20,
@@ -457,7 +458,7 @@
}
@Test
- fun testStartAnimation_useEndRelOffset() {
+ fun testStartAnimation_useEndRelOffset() = runOnUiThread {
val changeMock = mock(TransitionInfo.Change::class.java)
val startTransaction = mock(Transaction::class.java)
val finishTransaction = mock(Transaction::class.java)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/viewhost/DefaultWindowDecorViewHostTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/viewhost/DefaultWindowDecorViewHostTest.kt
index 1b0b7d9..1b2ce9e 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/viewhost/DefaultWindowDecorViewHostTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/viewhost/DefaultWindowDecorViewHostTest.kt
@@ -18,6 +18,7 @@
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.view.SurfaceControl
+import android.view.SurfaceControlViewHost
import android.view.View
import android.view.WindowManager
import androidx.test.filters.SmallTest
@@ -27,6 +28,7 @@
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.advanceUntilIdle
import kotlinx.coroutines.test.runTest
+import org.junit.Assert.assertThrows
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito.mock
@@ -57,8 +59,54 @@
onDrawTransaction = null
)
- assertThat(windowDecorViewHost.viewHostAdapter.isInitialized()).isTrue()
- assertThat(windowDecorViewHost.view()).isEqualTo(view)
+ assertThat(windowDecorViewHost.viewHost).isNotNull()
+ assertThat(windowDecorViewHost.viewHost!!.view).isEqualTo(view)
+ }
+
+ @Test
+ fun updateView_alreadyLaidOut_relayouts() = runTest {
+ val windowDecorViewHost = createDefaultViewHost()
+ val view = View(context)
+ windowDecorViewHost.updateView(
+ view = view,
+ attrs = WindowManager.LayoutParams(100, 100),
+ configuration = context.resources.configuration,
+ onDrawTransaction = null
+ )
+
+ val otherParams = WindowManager.LayoutParams(200, 200)
+ windowDecorViewHost.updateView(
+ view = view,
+ attrs = otherParams,
+ configuration = context.resources.configuration,
+ onDrawTransaction = null
+ )
+
+ assertThat(windowDecorViewHost.viewHost!!.view).isEqualTo(view)
+ assertThat(windowDecorViewHost.viewHost!!.view!!.layoutParams.width)
+ .isEqualTo(otherParams.width)
+ }
+
+ @Test
+ fun updateView_replacingView_throws() = runTest {
+ val windowDecorViewHost = createDefaultViewHost()
+ val view = View(context)
+ windowDecorViewHost.updateView(
+ view = view,
+ attrs = WindowManager.LayoutParams(100, 100),
+ configuration = context.resources.configuration,
+ onDrawTransaction = null
+ )
+
+ val otherView = View(context)
+ assertThrows(Exception::class.java) {
+ windowDecorViewHost.updateView(
+ view = otherView,
+ attrs = WindowManager.LayoutParams(100, 100),
+ configuration = context.resources.configuration,
+ onDrawTransaction = null
+ )
+ }
}
@OptIn(ExperimentalCoroutinesApi::class)
@@ -77,7 +125,7 @@
)
// No view host yet, since the coroutine hasn't run.
- assertThat(windowDecorViewHost.viewHostAdapter.isInitialized()).isFalse()
+ assertThat(windowDecorViewHost.viewHost).isNull()
windowDecorViewHost.updateView(
view = syncView,
@@ -89,13 +137,14 @@
// Would run coroutine if it hadn't been cancelled.
advanceUntilIdle()
- assertThat(windowDecorViewHost.viewHostAdapter.isInitialized()).isTrue()
- assertThat(windowDecorViewHost.view()).isNotNull()
+ assertThat(windowDecorViewHost.viewHost).isNotNull()
+ assertThat(windowDecorViewHost.viewHost!!.view).isNotNull()
// View host view/attrs should match the ones from the sync call, plus, since the
// sync/async were made with different views, if the job hadn't been cancelled there
// would've been an exception thrown as replacing views isn't allowed.
- assertThat(windowDecorViewHost.view()).isEqualTo(syncView)
- assertThat(windowDecorViewHost.view()!!.layoutParams.width).isEqualTo(syncAttrs.width)
+ assertThat(windowDecorViewHost.viewHost!!.view).isEqualTo(syncView)
+ assertThat(windowDecorViewHost.viewHost!!.view!!.layoutParams.width)
+ .isEqualTo(syncAttrs.width)
}
@OptIn(ExperimentalCoroutinesApi::class)
@@ -111,11 +160,11 @@
configuration = context.resources.configuration,
)
- assertThat(windowDecorViewHost.viewHostAdapter.isInitialized()).isFalse()
+ assertThat(windowDecorViewHost.viewHost).isNull()
advanceUntilIdle()
- assertThat(windowDecorViewHost.viewHostAdapter.isInitialized()).isTrue()
+ assertThat(windowDecorViewHost.viewHost).isNotNull()
}
@OptIn(ExperimentalCoroutinesApi::class)
@@ -138,8 +187,9 @@
advanceUntilIdle()
- assertThat(windowDecorViewHost.viewHostAdapter.isInitialized()).isTrue()
- assertThat(windowDecorViewHost.view()).isEqualTo(otherView)
+ assertThat(windowDecorViewHost.viewHost).isNotNull()
+ assertThat(windowDecorViewHost.viewHost!!.view).isNotNull()
+ assertThat(windowDecorViewHost.viewHost!!.view).isEqualTo(otherView)
}
@Test
@@ -157,15 +207,16 @@
val t = mock(SurfaceControl.Transaction::class.java)
windowDecorViewHost.release(t)
- verify(windowDecorViewHost.viewHostAdapter).release(t)
+ verify(windowDecorViewHost.viewHost!!).release()
+ verify(t).remove(windowDecorViewHost.surfaceControl)
}
private fun CoroutineScope.createDefaultViewHost() = DefaultWindowDecorViewHost(
context = context,
mainScope = this,
display = context.display,
- viewHostAdapter = spy(SurfaceControlViewHostAdapter(context, context.display)),
+ surfaceControlViewHostFactory = { c, d, wwm, s ->
+ spy(SurfaceControlViewHost(c, d, wwm, s))
+ }
)
-
- private fun DefaultWindowDecorViewHost.view(): View? = viewHostAdapter.viewHost?.view
}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/viewhost/PooledWindowDecorViewHostSupplierTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/viewhost/PooledWindowDecorViewHostSupplierTest.kt
deleted file mode 100644
index a7e4213..0000000
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/viewhost/PooledWindowDecorViewHostSupplierTest.kt
+++ /dev/null
@@ -1,181 +0,0 @@
-/*
- * 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.windowdecor.viewhost
-
-import android.testing.AndroidTestingRunner
-import android.view.SurfaceControl
-import androidx.test.filters.SmallTest
-import com.android.wm.shell.ShellTestCase
-import com.android.wm.shell.TestShellExecutor
-import com.android.wm.shell.sysui.ShellInit
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.advanceUntilIdle
-import kotlinx.coroutines.test.runTest
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.Mock
-import org.mockito.MockitoAnnotations
-import org.mockito.kotlin.any
-import org.mockito.kotlin.mock
-import org.mockito.kotlin.never
-import org.mockito.kotlin.verify
-import org.mockito.kotlin.whenever
-
-/**
- * Tests for [PooledWindowDecorViewHostSupplier].
- *
- * Build/Install/Run:
- * atest WMShellUnitTests:PooledWindowDecorViewHostSupplierTest
- */
-@OptIn(ExperimentalCoroutinesApi::class)
-@SmallTest
-@RunWith(AndroidTestingRunner::class)
-class PooledWindowDecorViewHostSupplierTest : ShellTestCase() {
-
- private val testExecutor = TestShellExecutor()
- private val testShellInit = ShellInit(testExecutor)
- @Mock
- private lateinit var mockViewHostFactory: ReusableWindowDecorViewHost.Factory
-
- private lateinit var supplier: PooledWindowDecorViewHostSupplier
-
- @Test
- fun setUp() {
- MockitoAnnotations.initMocks(this)
- }
-
- @Test
- fun onInit_warmsAndPoolsViewHosts() = runTest {
- supplier = createSupplier(maxPoolSize = 5, preWarmSize = 2)
- val mockViewHost1 = mock<ReusableWindowDecorViewHost>()
- val mockViewHost2 = mock<ReusableWindowDecorViewHost>()
- whenever(mockViewHostFactory
- .create(context, this, context.display, id = 0))
- .thenReturn(mockViewHost1)
- whenever(mockViewHostFactory
- .create(context, this, context.display, id = 1))
- .thenReturn(mockViewHost2)
-
- testExecutor.flushAll()
- advanceUntilIdle()
-
- // Both were warmed up.
- verify(mockViewHost1).warmUp()
- verify(mockViewHost2).warmUp()
- // Both were released, so re-acquiring them provides the same instance.
- assertThat(mockViewHost2)
- .isEqualTo(supplier.acquire(context, context.display))
- assertThat(mockViewHost1)
- .isEqualTo(supplier.acquire(context, context.display))
- }
-
- @Test(expected = Throwable::class)
- fun onInit_warmUpSizeExceedsPoolSize_throws() = runTest {
- createSupplier(maxPoolSize = 3, preWarmSize = 4)
- }
-
- @Test
- fun acquire_poolHasInstances_reuses() = runTest {
- supplier = createSupplier(maxPoolSize = 5, preWarmSize = 0)
-
- // Prepare the pool with one instance.
- val mockViewHost = mock<ReusableWindowDecorViewHost>()
- supplier.release(mockViewHost, SurfaceControl.Transaction())
-
- assertThat(mockViewHost)
- .isEqualTo(supplier.acquire(context, context.display))
- verify(mockViewHostFactory, never()).create(any(), any(), any(), any())
- }
-
- @Test
- fun acquire_pooledHasZeroInstances_creates() = runTest {
- supplier = createSupplier(maxPoolSize = 5, preWarmSize = 0)
-
- supplier.acquire(context, context.display)
-
- verify(mockViewHostFactory).create(context, this, context.display, id = 0)
- }
-
- @Test
- fun release_poolBelowLimit_caches() = runTest {
- supplier = createSupplier(maxPoolSize = 5, preWarmSize = 0)
-
- val mockViewHost = mock<ReusableWindowDecorViewHost>()
- val mockT = mock<SurfaceControl.Transaction>()
- supplier.release(mockViewHost, mockT)
-
- assertThat(mockViewHost)
- .isEqualTo(supplier.acquire(context, context.display))
- }
-
- @Test
- fun release_poolBelowLimit_doesNotReleaseViewHost() = runTest {
- supplier = createSupplier(maxPoolSize = 5, preWarmSize = 0)
-
- val mockViewHost = mock<ReusableWindowDecorViewHost>()
- val mockT = mock<SurfaceControl.Transaction>()
- supplier.release(mockViewHost, mockT)
-
- verify(mockViewHost, never()).release(mockT)
- }
-
- @Test
- fun release_poolAtLimit_doesNotCache() = runTest {
- supplier = createSupplier(maxPoolSize = 1, preWarmSize = 0)
- val mockT = mock<SurfaceControl.Transaction>()
- val mockViewHost = mock<ReusableWindowDecorViewHost>()
- supplier.release(mockViewHost, mockT) // Maxes pool.
-
- val mockViewHost2 = mock<ReusableWindowDecorViewHost>()
- supplier.release(mockViewHost2, mockT) // Beyond limit.
-
- assertThat(mockViewHost)
- .isEqualTo(supplier.acquire(context, context.display))
- // Second one wasn't cached, so the acquired one should've been a new instance.
- assertThat(mockViewHost2)
- .isNotEqualTo(supplier.acquire(context, context.display))
- }
-
- @Test
- fun release_poolAtLimit_releasesViewHost() = runTest {
- supplier = createSupplier(maxPoolSize = 1, preWarmSize = 0)
- val mockT = mock<SurfaceControl.Transaction>()
- val mockViewHost = mock<ReusableWindowDecorViewHost>()
- supplier.release(mockViewHost, mockT) // Maxes pool.
-
- val mockViewHost2 = mock<ReusableWindowDecorViewHost>()
- supplier.release(mockViewHost2, mockT) // Beyond limit.
-
- // Second one doesn't fit, so it needs to be released.
- verify(mockViewHost2).release(mockT)
- }
-
- private fun CoroutineScope.createSupplier(
- maxPoolSize: Int,
- preWarmSize: Int
- ) = PooledWindowDecorViewHostSupplier(
- context,
- this,
- testShellInit,
- mockViewHostFactory,
- maxPoolSize,
- preWarmSize
- ).also {
- testShellInit.init()
- }
-}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/viewhost/ReusableWindowDecorViewHostTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/viewhost/ReusableWindowDecorViewHostTest.kt
deleted file mode 100644
index de2444e..0000000
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/viewhost/ReusableWindowDecorViewHostTest.kt
+++ /dev/null
@@ -1,182 +0,0 @@
-/*
- * 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.windowdecor.viewhost
-
-import android.testing.AndroidTestingRunner
-import android.testing.TestableLooper
-import android.view.SurfaceControl
-import android.view.View
-import android.view.WindowManager
-import androidx.test.filters.SmallTest
-import com.android.wm.shell.ShellTestCase
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.advanceUntilIdle
-import kotlinx.coroutines.test.runTest
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.Mockito.mock
-import org.mockito.kotlin.spy
-import org.mockito.kotlin.verify
-
-/**
- * Tests for [ReusableWindowDecorViewHost].
- *
- * Build/Install/Run:
- * atest WMShellUnitTests:ReusableWindowDecorViewHostTest
- */
-@SmallTest
-@TestableLooper.RunWithLooper
-@RunWith(AndroidTestingRunner::class)
-class ReusableWindowDecorViewHostTest : ShellTestCase() {
-
- @Test
- fun warmUp_addsRootView() = runTest {
- val reusableVH = createReusableViewHost().apply {
- warmUp()
- }
-
- assertThat(reusableVH.viewHostAdapter.isInitialized()).isTrue()
- assertThat(reusableVH.view()).isEqualTo(reusableVH.rootView)
- }
-
- @Test
- fun update_differentView_replacesView() = runTest {
- val view = View(context)
- val lp = WindowManager.LayoutParams()
- val reusableVH = createReusableViewHost()
- reusableVH.updateView(view, lp, context.resources.configuration, null)
-
- assertThat(reusableVH.rootView.childCount).isEqualTo(1)
- assertThat(reusableVH.rootView.getChildAt(0)).isEqualTo(view)
-
- val newView = View(context)
- val newLp = WindowManager.LayoutParams()
- reusableVH.updateView(newView, newLp, context.resources.configuration, null)
-
- assertThat(reusableVH.rootView.childCount).isEqualTo(1)
- assertThat(reusableVH.rootView.getChildAt(0)).isEqualTo(newView)
- }
-
- @OptIn(ExperimentalCoroutinesApi::class)
- @Test
- fun updateView_clearsPendingAsyncJob() = runTest {
- val reusableVH = createReusableViewHost()
- val asyncView = View(context)
- val syncView = View(context)
- val asyncAttrs = WindowManager.LayoutParams(100, 100)
- val syncAttrs = WindowManager.LayoutParams(200, 200)
-
- reusableVH.updateViewAsync(
- view = asyncView,
- attrs = asyncAttrs,
- configuration = context.resources.configuration,
- )
-
- // No view host yet, since the coroutine hasn't run.
- assertThat(reusableVH.viewHostAdapter.isInitialized()).isFalse()
-
- reusableVH.updateView(
- view = syncView,
- attrs = syncAttrs,
- configuration = context.resources.configuration,
- onDrawTransaction = null
- )
-
- // Would run coroutine if it hadn't been cancelled.
- advanceUntilIdle()
-
- assertThat(reusableVH.viewHostAdapter.isInitialized()).isTrue()
- // View host view/attrs should match the ones from the sync call, plus, since the
- // sync/async were made with different views, if the job hadn't been cancelled there
- // would've been an exception thrown as replacing views isn't allowed.
- assertThat(reusableVH.rootView.getChildAt(0)).isEqualTo(syncView)
- assertThat(reusableVH.view()!!.layoutParams.width).isEqualTo(syncAttrs.width)
- }
-
- @OptIn(ExperimentalCoroutinesApi::class)
- @Test
- fun updateViewAsync() = runTest {
- val reusableVH = createReusableViewHost()
- val view = View(context)
- val attrs = WindowManager.LayoutParams(100, 100)
-
- reusableVH.updateViewAsync(
- view = view,
- attrs = attrs,
- configuration = context.resources.configuration,
- )
-
- assertThat(reusableVH.viewHostAdapter.isInitialized()).isFalse()
-
- advanceUntilIdle()
-
- assertThat(reusableVH.viewHostAdapter.isInitialized()).isTrue()
- }
-
- @OptIn(ExperimentalCoroutinesApi::class)
- @Test
- fun updateViewAsync_clearsPendingAsyncJob() = runTest {
- val reusableVH = createReusableViewHost()
-
- val view = View(context)
- reusableVH.updateViewAsync(
- view = view,
- attrs = WindowManager.LayoutParams(100, 100),
- configuration = context.resources.configuration,
- )
- val otherView = View(context)
- reusableVH.updateViewAsync(
- view = otherView,
- attrs = WindowManager.LayoutParams(100, 100),
- configuration = context.resources.configuration,
- )
-
- advanceUntilIdle()
-
- assertThat(reusableVH.viewHostAdapter.isInitialized()).isTrue()
- assertThat(reusableVH.rootView.getChildAt(0)).isEqualTo(otherView)
- }
-
- @Test
- fun release() = runTest {
- val reusableVH = createReusableViewHost()
-
- val view = View(context)
- reusableVH.updateView(
- view = view,
- attrs = WindowManager.LayoutParams(100, 100),
- configuration = context.resources.configuration,
- onDrawTransaction = null
- )
-
- val t = mock(SurfaceControl.Transaction::class.java)
- reusableVH.release(t)
-
- verify(reusableVH.viewHostAdapter).release(t)
- }
-
- private fun CoroutineScope.createReusableViewHost() = ReusableWindowDecorViewHost(
- context = context,
- mainScope = this,
- display = context.display,
- id = 1,
- viewHostAdapter = spy(SurfaceControlViewHostAdapter(context, context.display)),
- )
-
- private fun ReusableWindowDecorViewHost.view(): View? = viewHostAdapter.viewHost?.view
-}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/viewhost/SurfaceControlViewHostAdapterTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/viewhost/SurfaceControlViewHostAdapterTest.kt
deleted file mode 100644
index d6c80a7..0000000
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/viewhost/SurfaceControlViewHostAdapterTest.kt
+++ /dev/null
@@ -1,144 +0,0 @@
-/*
- * 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.windowdecor.viewhost
-
-import android.testing.AndroidTestingRunner
-import android.testing.TestableLooper
-import android.view.SurfaceControl
-import android.view.SurfaceControlViewHost
-import android.view.View
-import android.view.WindowManager
-import androidx.test.filters.SmallTest
-import com.android.wm.shell.ShellTestCase
-import com.google.common.truth.Truth.assertThat
-import org.junit.Assert.assertThrows
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.Mockito.mock
-import org.mockito.kotlin.spy
-import org.mockito.kotlin.verify
-
-/**
- * Tests for [SurfaceControlViewHostAdapter].
- *
- * Build/Install/Run:
- * atest WMShellUnitTests:SurfaceControlViewHostAdapterTest
- */
-@SmallTest
-@TestableLooper.RunWithLooper
-@RunWith(AndroidTestingRunner::class)
-class SurfaceControlViewHostAdapterTest : ShellTestCase() {
-
- private lateinit var adapter: SurfaceControlViewHostAdapter
-
- @Before
- fun setUp() {
- adapter = SurfaceControlViewHostAdapter(
- context,
- context.display,
- surfaceControlViewHostFactory = { c, d, wwm, s ->
- spy(SurfaceControlViewHost(c, d, wwm, s))
- }
- )
- }
-
- @Test
- fun prepareViewHost() {
- adapter.prepareViewHost(context.resources.configuration)
-
- assertThat(adapter.viewHost).isNotNull()
- }
-
- @Test
- fun prepareViewHost_alreadyCreated_skips() {
- adapter.prepareViewHost(context.resources.configuration)
-
- val viewHost = adapter.viewHost!!
-
- adapter.prepareViewHost(context.resources.configuration)
-
- assertThat(adapter.viewHost).isEqualTo(viewHost)
- }
-
- @Test
- fun updateView_layoutInViewHost() {
- val view = View(context)
- adapter.prepareViewHost(context.resources.configuration)
-
- adapter.updateView(
- view = view,
- attrs = WindowManager.LayoutParams(100, 100)
- )
-
- assertThat(adapter.isInitialized()).isTrue()
- assertThat(adapter.view()).isEqualTo(view)
- }
-
- @Test
- fun updateView_alreadyLaidOut_relayouts() {
- val view = View(context)
- adapter.prepareViewHost(context.resources.configuration)
- adapter.updateView(
- view = view,
- attrs = WindowManager.LayoutParams(100, 100)
- )
-
- val otherParams = WindowManager.LayoutParams(200, 200)
- adapter.updateView(
- view = view,
- attrs = otherParams
- )
-
- assertThat(adapter.view()).isEqualTo(view)
- assertThat(adapter.view()!!.layoutParams.width).isEqualTo(otherParams.width)
- }
-
- @Test
- fun updateView_replacingView_throws() {
- val view = View(context)
- adapter.prepareViewHost(context.resources.configuration)
- adapter.updateView(
- view = view,
- attrs = WindowManager.LayoutParams(100, 100)
- )
-
- val otherView = View(context)
- assertThrows(Exception::class.java) {
- adapter.updateView(
- view = otherView,
- attrs = WindowManager.LayoutParams(100, 100)
- )
- }
- }
-
- @Test
- fun release() {
- adapter.prepareViewHost(context.resources.configuration)
- adapter.updateView(
- view = View(context),
- attrs = WindowManager.LayoutParams(100, 100)
- )
-
- val mockT = mock(SurfaceControl.Transaction::class.java)
- adapter.release(mockT)
-
- verify(adapter.viewHost!!).release()
- verify(mockT).remove(adapter.rootSurface)
- }
-
- private fun SurfaceControlViewHostAdapter.view(): View? = viewHost?.view
-}
diff --git a/libs/appfunctions/Android.bp b/libs/appfunctions/Android.bp
new file mode 100644
index 0000000..09e2f42
--- /dev/null
+++ b/libs/appfunctions/Android.bp
@@ -0,0 +1,31 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+java_sdk_library {
+ name: "com.google.android.appfunctions.sidecar",
+ owner: "google",
+ srcs: ["java/**/*.java"],
+ api_packages: ["com.google.android.appfunctions.sidecar"],
+ dex_preopt: {
+ enabled: false,
+ },
+ system_ext_specific: true,
+ no_dist: true,
+ unsafe_ignore_missing_latest_api: true,
+}
diff --git a/libs/appfunctions/api/current.txt b/libs/appfunctions/api/current.txt
new file mode 100644
index 0000000..504e329
--- /dev/null
+++ b/libs/appfunctions/api/current.txt
@@ -0,0 +1,49 @@
+// Signature format: 2.0
+package com.google.android.appfunctions.sidecar {
+
+ public final class AppFunctionManager {
+ ctor public AppFunctionManager(android.content.Context);
+ method public void executeAppFunction(@NonNull com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse>);
+ }
+
+ public abstract class AppFunctionService extends android.app.Service {
+ ctor public AppFunctionService();
+ method @NonNull public final android.os.IBinder onBind(@Nullable android.content.Intent);
+ method @MainThread public abstract void onExecuteFunction(@NonNull com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest, @NonNull java.util.function.Consumer<com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse>);
+ field @NonNull public static final String BIND_APP_FUNCTION_SERVICE = "android.permission.BIND_APP_FUNCTION_SERVICE";
+ field @NonNull public static final String SERVICE_INTERFACE = "android.app.appfunctions.AppFunctionService";
+ }
+
+ public final class ExecuteAppFunctionRequest {
+ method @NonNull public android.os.Bundle getExtras();
+ method @NonNull public String getFunctionIdentifier();
+ method @NonNull public android.app.appsearch.GenericDocument getParameters();
+ method @NonNull public String getTargetPackageName();
+ }
+
+ public static final class ExecuteAppFunctionRequest.Builder {
+ ctor public ExecuteAppFunctionRequest.Builder(@NonNull String, @NonNull String);
+ method @NonNull public com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest build();
+ method @NonNull public com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest.Builder setExtras(@NonNull android.os.Bundle);
+ method @NonNull public com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest.Builder setParameters(@NonNull android.app.appsearch.GenericDocument);
+ }
+
+ public final class ExecuteAppFunctionResponse {
+ method @Nullable public String getErrorMessage();
+ method @NonNull public android.os.Bundle getExtras();
+ method public int getResultCode();
+ method @NonNull public android.app.appsearch.GenericDocument getResultDocument();
+ method public boolean isSuccess();
+ method @NonNull public static com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse newFailure(int, @Nullable String, @Nullable android.os.Bundle);
+ method @NonNull public static com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse newSuccess(@NonNull android.app.appsearch.GenericDocument, @Nullable android.os.Bundle);
+ field public static final String PROPERTY_RETURN_VALUE = "returnValue";
+ field public static final int RESULT_APP_UNKNOWN_ERROR = 2; // 0x2
+ field public static final int RESULT_DENIED = 1; // 0x1
+ field public static final int RESULT_INTERNAL_ERROR = 3; // 0x3
+ field public static final int RESULT_INVALID_ARGUMENT = 4; // 0x4
+ field public static final int RESULT_OK = 0; // 0x0
+ field public static final int RESULT_TIMED_OUT = 5; // 0x5
+ }
+
+}
+
diff --git a/libs/appfunctions/api/removed.txt b/libs/appfunctions/api/removed.txt
new file mode 100644
index 0000000..d802177
--- /dev/null
+++ b/libs/appfunctions/api/removed.txt
@@ -0,0 +1 @@
+// Signature format: 2.0
diff --git a/libs/appfunctions/api/system-current.txt b/libs/appfunctions/api/system-current.txt
new file mode 100644
index 0000000..d802177
--- /dev/null
+++ b/libs/appfunctions/api/system-current.txt
@@ -0,0 +1 @@
+// Signature format: 2.0
diff --git a/libs/appfunctions/api/system-removed.txt b/libs/appfunctions/api/system-removed.txt
new file mode 100644
index 0000000..d802177
--- /dev/null
+++ b/libs/appfunctions/api/system-removed.txt
@@ -0,0 +1 @@
+// Signature format: 2.0
diff --git a/libs/appfunctions/api/test-current.txt b/libs/appfunctions/api/test-current.txt
new file mode 100644
index 0000000..d802177
--- /dev/null
+++ b/libs/appfunctions/api/test-current.txt
@@ -0,0 +1 @@
+// Signature format: 2.0
diff --git a/libs/appfunctions/api/test-removed.txt b/libs/appfunctions/api/test-removed.txt
new file mode 100644
index 0000000..d802177
--- /dev/null
+++ b/libs/appfunctions/api/test-removed.txt
@@ -0,0 +1 @@
+// Signature format: 2.0
diff --git a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionManager.java b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionManager.java
new file mode 100644
index 0000000..b1dd467
--- /dev/null
+++ b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionManager.java
@@ -0,0 +1,82 @@
+/*
+ * 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.google.android.appfunctions.sidecar;
+
+import android.annotation.CallbackExecutor;
+import android.annotation.NonNull;
+import android.content.Context;
+
+import java.util.Objects;
+import java.util.concurrent.Executor;
+import java.util.function.Consumer;
+
+
+/**
+ * Provides app functions related functionalities.
+ *
+ * <p>App function is a specific piece of functionality that an app offers to the system. These
+ * functionalities can be integrated into various system features.
+ *
+ * <p>This class wraps {@link android.app.appfunctions.AppFunctionManager} functionalities and
+ * exposes it here as a sidecar library (avoiding direct dependency on the platform API).
+ */
+// TODO(b/357551503): Implement get and set enabled app function APIs.
+// TODO(b/367329899): Add sidecar library to Android B builds.
+public final class AppFunctionManager {
+ private final android.app.appfunctions.AppFunctionManager mManager;
+ private final Context mContext;
+
+ /**
+ * Creates an instance.
+ *
+ * @param context A {@link Context}.
+ * @throws java.lang.IllegalStateException if the underlying {@link
+ * android.app.appfunctions.AppFunctionManager} is not found.
+ */
+ public AppFunctionManager(Context context) {
+ mContext = Objects.requireNonNull(context);
+ mManager = context.getSystemService(android.app.appfunctions.AppFunctionManager.class);
+ if (mManager == null) {
+ throw new IllegalStateException(
+ "Underlying AppFunctionManager system service not found.");
+ }
+ }
+
+ /**
+ * Executes the app function.
+ *
+ * <p>Proxies request and response to the underlying {@link
+ * android.app.appfunctions.AppFunctionManager#executeAppFunction}, converting the request and
+ * response in the appropriate type required by the function.
+ */
+ public void executeAppFunction(
+ @NonNull ExecuteAppFunctionRequest sidecarRequest,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull Consumer<ExecuteAppFunctionResponse> callback) {
+ Objects.requireNonNull(sidecarRequest);
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(callback);
+
+ android.app.appfunctions.ExecuteAppFunctionRequest platformRequest =
+ SidecarConverter.getPlatformExecuteAppFunctionRequest(sidecarRequest);
+ mManager.executeAppFunction(
+ platformRequest, executor, (platformResponse) -> {
+ callback.accept(SidecarConverter.getSidecarExecuteAppFunctionResponse(
+ platformResponse));
+ });
+ }
+}
diff --git a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionService.java b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionService.java
new file mode 100644
index 0000000..65959df
--- /dev/null
+++ b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionService.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.appfunctions.sidecar;
+
+import static android.Manifest.permission.BIND_APP_FUNCTION_SERVICE;
+
+import android.annotation.MainThread;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.Service;
+import android.content.Intent;
+import android.os.Binder;
+import android.os.IBinder;
+
+import java.util.function.Consumer;
+
+/**
+ * Abstract base class to provide app functions to the system.
+ *
+ * <p>Include the following in the manifest:
+ *
+ * <pre>
+ * {@literal
+ * <service android:name=".YourService"
+ * android:permission="android.permission.BIND_APP_FUNCTION_SERVICE">
+ * <intent-filter>
+ * <action android:name="android.app.appfunctions.AppFunctionService" />
+ * </intent-filter>
+ * </service>
+ * }
+ * </pre>
+ *
+ * <p>This class wraps {@link android.app.appfunctions.AppFunctionService} functionalities and
+ * exposes it here as a sidecar library (avoiding direct dependency on the platform API).
+ *
+ * @see AppFunctionManager
+ */
+public abstract class AppFunctionService extends Service {
+ /**
+ * The permission to only allow system access to the functions through {@link
+ * AppFunctionManagerService}.
+ */
+ @NonNull
+ public static final String BIND_APP_FUNCTION_SERVICE =
+ "android.permission.BIND_APP_FUNCTION_SERVICE";
+
+ /**
+ * The {@link Intent} that must be declared as handled by the service. To be supported, the
+ * service must also require the {@link BIND_APP_FUNCTION_SERVICE} permission so that other
+ * applications can not abuse it.
+ */
+ @NonNull
+ public static final String SERVICE_INTERFACE = "android.app.appfunctions.AppFunctionService";
+
+ private final Binder mBinder =
+ android.app.appfunctions.AppFunctionService.createBinder(
+ /* context= */ this,
+ /* onExecuteFunction= */ (platformRequest, callback) -> {
+ AppFunctionService.this.onExecuteFunction(
+ SidecarConverter.getSidecarExecuteAppFunctionRequest(
+ platformRequest),
+ (sidecarResponse) -> {
+ callback.accept(
+ SidecarConverter.getPlatformExecuteAppFunctionResponse(
+ sidecarResponse));
+ });
+ }
+ );
+
+ @NonNull
+ @Override
+ public final IBinder onBind(@Nullable Intent intent) {
+ return mBinder;
+ }
+
+ /**
+ * Called by the system to execute a specific app function.
+ *
+ * <p>This method is triggered when the system requests your AppFunctionService to handle a
+ * particular function you have registered and made available.
+ *
+ * <p>To ensure proper routing of function requests, assign a unique identifier to each
+ * function. This identifier doesn't need to be globally unique, but it must be unique within
+ * your app. For example, a function to order food could be identified as "orderFood". In most
+ * cases this identifier should come from the ID automatically generated by the AppFunctions
+ * SDK. You can determine the specific function to invoke by calling {@link
+ * ExecuteAppFunctionRequest#getFunctionIdentifier()}.
+ *
+ * <p>This method is always triggered in the main thread. You should run heavy tasks on a worker
+ * thread and dispatch the result with the given callback. You should always report back the
+ * result using the callback, no matter if the execution was successful or not.
+ *
+ * @param request The function execution request.
+ * @param callback A callback to report back the result.
+ */
+ @MainThread
+ public abstract void onExecuteFunction(
+ @NonNull ExecuteAppFunctionRequest request,
+ @NonNull Consumer<ExecuteAppFunctionResponse> callback);
+}
diff --git a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionRequest.java b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionRequest.java
new file mode 100644
index 0000000..fa6d2ff
--- /dev/null
+++ b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionRequest.java
@@ -0,0 +1,137 @@
+/*
+ * 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.google.android.appfunctions.sidecar;
+
+import android.annotation.NonNull;
+import android.app.appsearch.GenericDocument;
+import android.os.Bundle;
+
+import java.util.Objects;
+
+/**
+ * A request to execute an app function.
+ *
+ * <p>This class copies {@link android.app.appfunctions.ExecuteAppFunctionRequest} without parcel
+ * functionality and exposes it here as a sidecar library (avoiding direct dependency on the
+ * platform API).
+ */
+public final class ExecuteAppFunctionRequest {
+ /** Returns the package name of the app that hosts the function. */
+ @NonNull private final String mTargetPackageName;
+
+ /**
+ * Returns the unique string identifier of the app function to be executed. TODO(b/357551503):
+ * Document how callers can get the available function identifiers.
+ */
+ @NonNull private final String mFunctionIdentifier;
+
+ /** Returns additional metadata relevant to this function execution request. */
+ @NonNull private final Bundle mExtras;
+
+ /**
+ * Returns the parameters required to invoke this function. Within this [GenericDocument], the
+ * property names are the names of the function parameters and the property values are the
+ * values of those parameters.
+ *
+ * <p>The document may have missing parameters. Developers are advised to implement defensive
+ * handling measures.
+ *
+ * <p>TODO(b/357551503): Document how function parameters can be obtained for function execution
+ */
+ @NonNull private final GenericDocument mParameters;
+
+ private ExecuteAppFunctionRequest(
+ @NonNull String targetPackageName,
+ @NonNull String functionIdentifier,
+ @NonNull Bundle extras,
+ @NonNull GenericDocument parameters) {
+ mTargetPackageName = Objects.requireNonNull(targetPackageName);
+ mFunctionIdentifier = Objects.requireNonNull(functionIdentifier);
+ mExtras = Objects.requireNonNull(extras);
+ mParameters = Objects.requireNonNull(parameters);
+ }
+
+ /** Returns the package name of the app that hosts the function. */
+ @NonNull
+ public String getTargetPackageName() {
+ return mTargetPackageName;
+ }
+
+ /** Returns the unique string identifier of the app function to be executed. */
+ @NonNull
+ public String getFunctionIdentifier() {
+ return mFunctionIdentifier;
+ }
+
+ /**
+ * Returns the function parameters. The key is the parameter name, and the value is the
+ * parameter value.
+ *
+ * <p>The bundle may have missing parameters. Developers are advised to implement defensive
+ * handling measures.
+ */
+ @NonNull
+ public GenericDocument getParameters() {
+ return mParameters;
+ }
+
+ /** Returns the additional data relevant to this function execution. */
+ @NonNull
+ public Bundle getExtras() {
+ return mExtras;
+ }
+
+ /** Builder for {@link ExecuteAppFunctionRequest}. */
+ public static final class Builder {
+ @NonNull private final String mTargetPackageName;
+ @NonNull private final String mFunctionIdentifier;
+ @NonNull private Bundle mExtras = Bundle.EMPTY;
+
+ @NonNull
+ private GenericDocument mParameters = new GenericDocument.Builder<>("", "", "").build();
+
+ public Builder(@NonNull String targetPackageName, @NonNull String functionIdentifier) {
+ mTargetPackageName = Objects.requireNonNull(targetPackageName);
+ mFunctionIdentifier = Objects.requireNonNull(functionIdentifier);
+ }
+
+ /** Sets the additional data relevant to this function execution. */
+ @NonNull
+ public Builder setExtras(@NonNull Bundle extras) {
+ mExtras = Objects.requireNonNull(extras);
+ return this;
+ }
+
+ /** Sets the function parameters. */
+ @NonNull
+ public Builder setParameters(@NonNull GenericDocument parameters) {
+ Objects.requireNonNull(parameters);
+ mParameters = parameters;
+ return this;
+ }
+
+ /** Builds the {@link ExecuteAppFunctionRequest}. */
+ @NonNull
+ public ExecuteAppFunctionRequest build() {
+ return new ExecuteAppFunctionRequest(
+ mTargetPackageName,
+ mFunctionIdentifier,
+ mExtras,
+ mParameters);
+ }
+ }
+}
diff --git a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionResponse.java b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionResponse.java
new file mode 100644
index 0000000..60c25fa
--- /dev/null
+++ b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionResponse.java
@@ -0,0 +1,240 @@
+/*
+ * 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.google.android.appfunctions.sidecar;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.appsearch.GenericDocument;
+import android.os.Bundle;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
+
+/**
+ * The response to an app function execution.
+ *
+ * <p>This class copies {@link android.app.appfunctions.ExecuteAppFunctionResponse} without parcel
+ * functionality and exposes it here as a sidecar library (avoiding direct dependency on the
+ * platform API).
+ */
+public final class ExecuteAppFunctionResponse {
+ /**
+ * The name of the property that stores the function return value within the {@code
+ * resultDocument}.
+ *
+ * <p>See {@link GenericDocument#getProperty(String)} for more information.
+ *
+ * <p>If the function returns {@code void} or throws an error, the {@code resultDocument} will
+ * be empty {@link GenericDocument}.
+ *
+ * <p>If the {@code resultDocument} is empty, {@link GenericDocument#getProperty(String)} will
+ * return {@code null}.
+ *
+ * <p>See {@link #getResultDocument} for more information on extracting the return value.
+ */
+ public static final String PROPERTY_RETURN_VALUE = "returnValue";
+
+ /** The call was successful. */
+ public static final int RESULT_OK = 0;
+
+ /** The caller does not have the permission to execute an app function. */
+ public static final int RESULT_DENIED = 1;
+
+ /** An unknown error occurred while processing the call in the AppFunctionService. */
+ public static final int RESULT_APP_UNKNOWN_ERROR = 2;
+
+ /**
+ * An internal error occurred within AppFunctionManagerService.
+ *
+ * <p>This error may be considered similar to {@link IllegalStateException}
+ */
+ public static final int RESULT_INTERNAL_ERROR = 3;
+
+ /**
+ * The caller supplied invalid arguments to the call.
+ *
+ * <p>This error may be considered similar to {@link IllegalArgumentException}.
+ */
+ public static final int RESULT_INVALID_ARGUMENT = 4;
+
+ /** The operation was timed out. */
+ public static final int RESULT_TIMED_OUT = 5;
+
+ /** The result code of the app function execution. */
+ @ResultCode private final int mResultCode;
+
+ /**
+ * The error message associated with the result, if any. This is {@code null} if the result code
+ * is {@link #RESULT_OK}.
+ */
+ @Nullable private final String mErrorMessage;
+
+ /**
+ * Returns the return value of the executed function.
+ *
+ * <p>The return value is stored in a {@link GenericDocument} with the key {@link
+ * #PROPERTY_RETURN_VALUE}.
+ *
+ * <p>See {@link #getResultDocument} for more information on extracting the return value.
+ */
+ @NonNull private final GenericDocument mResultDocument;
+
+ /** Returns the additional metadata data relevant to this function execution response. */
+ @NonNull private final Bundle mExtras;
+
+ private ExecuteAppFunctionResponse(
+ @NonNull GenericDocument resultDocument,
+ @NonNull Bundle extras,
+ @ResultCode int resultCode,
+ @Nullable String errorMessage) {
+ mResultDocument = Objects.requireNonNull(resultDocument);
+ mExtras = Objects.requireNonNull(extras);
+ mResultCode = resultCode;
+ mErrorMessage = errorMessage;
+ }
+
+ /**
+ * Returns result codes from throwable.
+ *
+ * @hide
+ */
+ static @ResultCode int getResultCode(@NonNull Throwable t) {
+ if (t instanceof IllegalArgumentException) {
+ return ExecuteAppFunctionResponse.RESULT_INVALID_ARGUMENT;
+ }
+ return ExecuteAppFunctionResponse.RESULT_APP_UNKNOWN_ERROR;
+ }
+
+ /**
+ * Returns a successful response.
+ *
+ * @param resultDocument The return value of the executed function.
+ * @param extras The additional metadata data relevant to this function execution response.
+ */
+ @NonNull
+ public static ExecuteAppFunctionResponse newSuccess(
+ @NonNull GenericDocument resultDocument, @Nullable Bundle extras) {
+ Objects.requireNonNull(resultDocument);
+ Bundle actualExtras = getActualExtras(extras);
+
+ return new ExecuteAppFunctionResponse(
+ resultDocument, actualExtras, RESULT_OK, /* errorMessage= */ null);
+ }
+
+ /**
+ * Returns a failure response.
+ *
+ * @param resultCode The result code of the app function execution.
+ * @param extras The additional metadata data relevant to this function execution response.
+ * @param errorMessage The error message associated with the result, if any.
+ */
+ @NonNull
+ public static ExecuteAppFunctionResponse newFailure(
+ @ResultCode int resultCode, @Nullable String errorMessage, @Nullable Bundle extras) {
+ if (resultCode == RESULT_OK) {
+ throw new IllegalArgumentException("resultCode must not be RESULT_OK");
+ }
+ Bundle actualExtras = getActualExtras(extras);
+ GenericDocument emptyDocument = new GenericDocument.Builder<>("", "", "").build();
+ return new ExecuteAppFunctionResponse(
+ emptyDocument, actualExtras, resultCode, errorMessage);
+ }
+
+ private static Bundle getActualExtras(@Nullable Bundle extras) {
+ if (extras == null) {
+ return Bundle.EMPTY;
+ }
+ return extras;
+ }
+
+ /**
+ * Returns a generic document containing the return value of the executed function.
+ *
+ * <p>The {@link #PROPERTY_RETURN_VALUE} key can be used to obtain the return value.
+ *
+ * <p>An empty document is returned if {@link #isSuccess} is {@code false} or if the executed
+ * function does not produce a return value.
+ *
+ * <p>Sample code for extracting the return value:
+ *
+ * <pre>
+ * GenericDocument resultDocument = response.getResultDocument();
+ * Object returnValue = resultDocument.getProperty(PROPERTY_RETURN_VALUE);
+ * if (returnValue != null) {
+ * // Cast returnValue to expected type, or use {@link GenericDocument#getPropertyString},
+ * // {@link GenericDocument#getPropertyLong} etc.
+ * // Do something with the returnValue
+ * }
+ * </pre>
+ */
+ @NonNull
+ public GenericDocument getResultDocument() {
+ return mResultDocument;
+ }
+
+ /** Returns the extras of the app function execution. */
+ @NonNull
+ public Bundle getExtras() {
+ return mExtras;
+ }
+
+ /**
+ * Returns {@code true} if {@link #getResultCode} equals {@link
+ * ExecuteAppFunctionResponse#RESULT_OK}.
+ */
+ public boolean isSuccess() {
+ return getResultCode() == RESULT_OK;
+ }
+
+ /**
+ * Returns one of the {@code RESULT} constants defined in {@link ExecuteAppFunctionResponse}.
+ */
+ @ResultCode
+ public int getResultCode() {
+ return mResultCode;
+ }
+
+ /**
+ * Returns the error message associated with this result.
+ *
+ * <p>If {@link #isSuccess} is {@code true}, the error message is always {@code null}.
+ */
+ @Nullable
+ public String getErrorMessage() {
+ return mErrorMessage;
+ }
+
+ /**
+ * Result codes.
+ *
+ * @hide
+ */
+ @IntDef(
+ prefix = {"RESULT_"},
+ value = {
+ RESULT_OK,
+ RESULT_DENIED,
+ RESULT_APP_UNKNOWN_ERROR,
+ RESULT_INTERNAL_ERROR,
+ RESULT_INVALID_ARGUMENT,
+ RESULT_TIMED_OUT,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ResultCode {}
+}
diff --git a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/SidecarConverter.java b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/SidecarConverter.java
new file mode 100644
index 0000000..b1b05f7
--- /dev/null
+++ b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/SidecarConverter.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.appfunctions.sidecar;
+
+import android.annotation.NonNull;
+
+/**
+ * Utility class containing methods to convert Sidecar objects of AppFunctions API into the
+ * underlying platform classes.
+ *
+ * @hide
+ */
+public final class SidecarConverter {
+ private SidecarConverter() {}
+
+ /**
+ * Converts sidecar's {@link com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest}
+ * into platform's {@link android.app.appfunctions.ExecuteAppFunctionRequest}
+ *
+ * @hide
+ */
+ @NonNull
+ public static android.app.appfunctions.ExecuteAppFunctionRequest
+ getPlatformExecuteAppFunctionRequest(@NonNull ExecuteAppFunctionRequest request) {
+ return new
+ android.app.appfunctions.ExecuteAppFunctionRequest.Builder(
+ request.getTargetPackageName(),
+ request.getFunctionIdentifier())
+ .setExtras(request.getExtras())
+ .setParameters(request.getParameters())
+ .build();
+ }
+
+ /**
+ * Converts sidecar's {@link com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse}
+ * into platform's {@link android.app.appfunctions.ExecuteAppFunctionResponse}
+ *
+ * @hide
+ */
+ @NonNull
+ public static android.app.appfunctions.ExecuteAppFunctionResponse
+ getPlatformExecuteAppFunctionResponse(@NonNull ExecuteAppFunctionResponse response) {
+ if (response.isSuccess()) {
+ return android.app.appfunctions.ExecuteAppFunctionResponse.newSuccess(
+ response.getResultDocument(), response.getExtras());
+ } else {
+ return android.app.appfunctions.ExecuteAppFunctionResponse.newFailure(
+ response.getResultCode(),
+ response.getErrorMessage(),
+ response.getExtras());
+ }
+ }
+
+ /**
+ * Converts platform's {@link android.app.appfunctions.ExecuteAppFunctionRequest}
+ * into sidecar's {@link com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest}
+ *
+ * @hide
+ */
+ @NonNull
+ public static ExecuteAppFunctionRequest getSidecarExecuteAppFunctionRequest(
+ @NonNull android.app.appfunctions.ExecuteAppFunctionRequest request) {
+ return new ExecuteAppFunctionRequest.Builder(
+ request.getTargetPackageName(),
+ request.getFunctionIdentifier())
+ .setExtras(request.getExtras())
+ .setParameters(request.getParameters())
+ .build();
+ }
+
+ /**
+ * Converts platform's {@link android.app.appfunctions.ExecuteAppFunctionResponse}
+ * into sidecar's {@link com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse}
+ *
+ * @hide
+ */
+ @NonNull
+ public static ExecuteAppFunctionResponse getSidecarExecuteAppFunctionResponse(
+ @NonNull android.app.appfunctions.ExecuteAppFunctionResponse response) {
+ if (response.isSuccess()) {
+ return ExecuteAppFunctionResponse.newSuccess(
+ response.getResultDocument(), response.getExtras());
+ } else {
+ return ExecuteAppFunctionResponse.newFailure(
+ response.getResultCode(),
+ response.getErrorMessage(),
+ response.getExtras());
+ }
+ }
+}
diff --git a/libs/hwui/hwui/Bitmap.cpp b/libs/hwui/hwui/Bitmap.cpp
index 1854361..84bd45d 100644
--- a/libs/hwui/hwui/Bitmap.cpp
+++ b/libs/hwui/hwui/Bitmap.cpp
@@ -264,6 +264,7 @@
, mPixelStorageType(PixelStorageType::Heap) {
mPixelStorage.heap.address = address;
mPixelStorage.heap.size = size;
+ traceBitmapCreate();
}
Bitmap::Bitmap(SkPixelRef& pixelRef, const SkImageInfo& info)
@@ -272,6 +273,7 @@
, mPixelStorageType(PixelStorageType::WrappedPixelRef) {
pixelRef.ref();
mPixelStorage.wrapped.pixelRef = &pixelRef;
+ traceBitmapCreate();
}
Bitmap::Bitmap(void* address, int fd, size_t mappedSize, const SkImageInfo& info, size_t rowBytes)
@@ -281,6 +283,7 @@
mPixelStorage.ashmem.address = address;
mPixelStorage.ashmem.fd = fd;
mPixelStorage.ashmem.size = mappedSize;
+ traceBitmapCreate();
}
#ifdef __ANDROID__ // Layoutlib does not support hardware acceleration
@@ -297,10 +300,12 @@
setImmutable(); // HW bitmaps are always immutable
mImage = SkImages::DeferredFromAHardwareBuffer(buffer, mInfo.alphaType(),
mInfo.refColorSpace());
+ traceBitmapCreate();
}
#endif
Bitmap::~Bitmap() {
+ traceBitmapDelete();
switch (mPixelStorageType) {
case PixelStorageType::WrappedPixelRef:
mPixelStorage.wrapped.pixelRef->unref();
@@ -572,4 +577,28 @@
mGainmap = std::move(gainmap);
}
+std::mutex Bitmap::mLock{};
+size_t Bitmap::mTotalBitmapBytes = 0;
+size_t Bitmap::mTotalBitmapCount = 0;
+
+void Bitmap::traceBitmapCreate() {
+ if (ATRACE_ENABLED()) {
+ std::lock_guard lock{mLock};
+ mTotalBitmapBytes += getAllocationByteCount();
+ mTotalBitmapCount++;
+ ATRACE_INT64("Bitmap Memory", mTotalBitmapBytes);
+ ATRACE_INT64("Bitmap Count", mTotalBitmapCount);
+ }
+}
+
+void Bitmap::traceBitmapDelete() {
+ if (ATRACE_ENABLED()) {
+ std::lock_guard lock{mLock};
+ mTotalBitmapBytes -= getAllocationByteCount();
+ mTotalBitmapCount--;
+ ATRACE_INT64("Bitmap Memory", mTotalBitmapBytes);
+ ATRACE_INT64("Bitmap Count", mTotalBitmapCount);
+ }
+}
+
} // namespace android
diff --git a/libs/hwui/hwui/Bitmap.h b/libs/hwui/hwui/Bitmap.h
index dd344e2..3d55d85 100644
--- a/libs/hwui/hwui/Bitmap.h
+++ b/libs/hwui/hwui/Bitmap.h
@@ -25,6 +25,7 @@
#include <cutils/compiler.h>
#include <utils/StrongPointer.h>
+#include <mutex>
#include <optional>
#ifdef __ANDROID__ // Layoutlib does not support hardware acceleration
@@ -227,6 +228,13 @@
} mPixelStorage;
sk_sp<SkImage> mImage; // Cache is used only for HW Bitmaps with Skia pipeline.
+
+ // for tracing total number and memory usage of bitmaps
+ static std::mutex mLock;
+ static size_t mTotalBitmapBytes;
+ static size_t mTotalBitmapCount;
+ void traceBitmapCreate();
+ void traceBitmapDelete();
};
} // namespace android
diff --git a/nfc/api/system-current.txt b/nfc/api/system-current.txt
index bc8a7af..9603c0a 100644
--- a/nfc/api/system-current.txt
+++ b/nfc/api/system-current.txt
@@ -60,8 +60,13 @@
method @FlaggedApi("android.nfc.nfc_oem_extension") @NonNull public java.util.List<java.lang.String> getActiveNfceeList();
method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void maybeTriggerFirmwareUpdate();
method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void registerCallback(@NonNull java.util.concurrent.Executor, @NonNull android.nfc.NfcOemExtension.Callback);
+ method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public void setControllerAlwaysOn(int);
method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void synchronizeScreenState();
method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void unregisterCallback(@NonNull android.nfc.NfcOemExtension.Callback);
+ field @FlaggedApi("android.nfc.nfc_oem_extension") public static final int DISABLE = 0; // 0x0
+ field @FlaggedApi("android.nfc.nfc_oem_extension") public static final int ENABLE_DEFAULT = 1; // 0x1
+ field @FlaggedApi("android.nfc.nfc_oem_extension") public static final int ENABLE_EE = 3; // 0x3
+ field @FlaggedApi("android.nfc.nfc_oem_extension") public static final int ENABLE_TRANSPARENT = 2; // 0x2
field public static final int HCE_ACTIVATE = 1; // 0x1
field public static final int HCE_DATA_TRANSFERRED = 2; // 0x2
field public static final int HCE_DEACTIVATE = 3; // 0x3
diff --git a/nfc/java/android/nfc/INfcAdapter.aidl b/nfc/java/android/nfc/INfcAdapter.aidl
index e2ec952..246efc7 100644
--- a/nfc/java/android/nfc/INfcAdapter.aidl
+++ b/nfc/java/android/nfc/INfcAdapter.aidl
@@ -73,7 +73,7 @@
boolean setNfcSecure(boolean enable);
NfcAntennaInfo getNfcAntennaInfo();
- boolean setControllerAlwaysOn(boolean value);
+ void setControllerAlwaysOn(int mode);
boolean isControllerAlwaysOn();
boolean isControllerAlwaysOnSupported();
void registerControllerAlwaysOnListener(in INfcControllerAlwaysOnListener listener);
diff --git a/nfc/java/android/nfc/NfcAdapter.java b/nfc/java/android/nfc/NfcAdapter.java
index f478793..de85f1e 100644
--- a/nfc/java/android/nfc/NfcAdapter.java
+++ b/nfc/java/android/nfc/NfcAdapter.java
@@ -559,6 +559,18 @@
@Retention(RetentionPolicy.SOURCE)
public @interface TagIntentAppPreferenceResult {}
+ /**
+ * Mode Type for {@link NfcOemExtension#setControllerAlwaysOn(int)}.
+ * @hide
+ */
+ public static final int CONTROLLER_ALWAYS_ON_MODE_DEFAULT = 1;
+
+ /**
+ * Mode Type for {@link NfcOemExtension#setControllerAlwaysOn(int)}.
+ * @hide
+ */
+ public static final int CONTROLLER_ALWAYS_ON_DISABLE = 0;
+
// Guarded by sLock
static boolean sIsInitialized = false;
static boolean sHasNfcFeature;
@@ -2330,7 +2342,8 @@
* FEATURE_NFC_HOST_CARD_EMULATION, FEATURE_NFC_HOST_CARD_EMULATION_NFCF,
* FEATURE_NFC_OFF_HOST_CARD_EMULATION_UICC and FEATURE_NFC_OFF_HOST_CARD_EMULATION_ESE
* are unavailable
- * @return void
+ * @return true if feature is supported by the device and operation has bee initiated,
+ * false if the feature is not supported by the device.
* @hide
*/
@SystemApi
@@ -2339,8 +2352,13 @@
if (!sHasNfcFeature && !sHasCeFeature) {
throw new UnsupportedOperationException();
}
- return callServiceReturn(() -> sService.setControllerAlwaysOn(value), false);
-
+ int mode = value ? CONTROLLER_ALWAYS_ON_MODE_DEFAULT : CONTROLLER_ALWAYS_ON_DISABLE;
+ try {
+ callService(() -> sService.setControllerAlwaysOn(mode));
+ } catch (UnsupportedOperationException e) {
+ return false;
+ }
+ return true;
}
/**
diff --git a/nfc/java/android/nfc/NfcOemExtension.java b/nfc/java/android/nfc/NfcOemExtension.java
index d51b704..45038d4 100644
--- a/nfc/java/android/nfc/NfcOemExtension.java
+++ b/nfc/java/android/nfc/NfcOemExtension.java
@@ -70,6 +70,58 @@
private boolean mRfDiscoveryStarted = false;
/**
+ * Mode Type for {@link #setControllerAlwaysOn(int)}.
+ * Enables the controller in default mode when NFC is disabled (existing API behavior).
+ * works same as {@link NfcAdapter#setControllerAlwaysOn(boolean)}.
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_NFC_OEM_EXTENSION)
+ public static final int ENABLE_DEFAULT = NfcAdapter.CONTROLLER_ALWAYS_ON_MODE_DEFAULT;
+
+ /**
+ * Mode Type for {@link #setControllerAlwaysOn(int)}.
+ * Enables the controller in transparent mode when NFC is disabled.
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_NFC_OEM_EXTENSION)
+ public static final int ENABLE_TRANSPARENT = 2;
+
+ /**
+ * Mode Type for {@link #setControllerAlwaysOn(int)}.
+ * Enables the controller and initializes and enables the EE subsystem when NFC is disabled.
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_NFC_OEM_EXTENSION)
+ public static final int ENABLE_EE = 3;
+
+ /**
+ * Mode Type for {@link #setControllerAlwaysOn(int)}.
+ * Disable the Controller Always On Mode.
+ * works same as {@link NfcAdapter#setControllerAlwaysOn(boolean)}.
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_NFC_OEM_EXTENSION)
+ public static final int DISABLE = NfcAdapter.CONTROLLER_ALWAYS_ON_DISABLE;
+
+ /**
+ * Possible controller modes for {@link #setControllerAlwaysOn(int)}.
+ *
+ * @hide
+ */
+ @IntDef(prefix = { "" }, value = {
+ ENABLE_DEFAULT,
+ ENABLE_TRANSPARENT,
+ ENABLE_EE,
+ DISABLE,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ControllerMode{}
+
+ /**
* Event that Host Card Emulation is activated.
*/
public static final int HCE_ACTIVATE = 1;
@@ -389,6 +441,32 @@
NfcAdapter.sService.fetchActiveNfceeList(), new ArrayList<String>());
}
+ /**
+ * Sets NFC controller always on feature.
+ * <p>This API is for the NFCC internal state management. It allows to discriminate
+ * the controller function from the NFC function by keeping the NFC controller on without
+ * any NFC RF enabled if necessary.
+ * <p>This call is asynchronous, register listener {@link NfcAdapter.ControllerAlwaysOnListener}
+ * by {@link NfcAdapter#registerControllerAlwaysOnListener} to find out when the operation is
+ * complete.
+ * @param mode one of {@link ControllerMode} modes
+ * @throws UnsupportedOperationException if
+ * <li> if FEATURE_NFC, FEATURE_NFC_HOST_CARD_EMULATION, FEATURE_NFC_HOST_CARD_EMULATION_NFCF,
+ * FEATURE_NFC_OFF_HOST_CARD_EMULATION_UICC and FEATURE_NFC_OFF_HOST_CARD_EMULATION_ESE
+ * are unavailable </li>
+ * <li> if the feature is unavailable @see NfcAdapter#isNfcControllerAlwaysOnSupported() </li>
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_NFC_OEM_EXTENSION)
+ @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON)
+ public void setControllerAlwaysOn(@ControllerMode int mode) {
+ if (!NfcAdapter.sHasNfcFeature && !NfcAdapter.sHasCeFeature) {
+ throw new UnsupportedOperationException();
+ }
+ NfcAdapter.callService(() -> NfcAdapter.sService.setControllerAlwaysOn(mode));
+ }
+
private final class NfcOemExtensionCallback extends INfcOemExtensionCallback.Stub {
@Override
diff --git a/packages/PackageInstaller/Android.bp b/packages/PackageInstaller/Android.bp
index bd84b58..a30c0c3 100644
--- a/packages/PackageInstaller/Android.bp
+++ b/packages/PackageInstaller/Android.bp
@@ -13,6 +13,7 @@
// limitations under the License.
package {
+ default_team: "trendy_team_framework_android_packages",
default_applicable_licenses: [
"frameworks_base_packages_PackageInstaller_license",
],
diff --git a/packages/PackageInstaller/TEST_MAPPING b/packages/PackageInstaller/TEST_MAPPING
index 717ec02..91882fd 100644
--- a/packages/PackageInstaller/TEST_MAPPING
+++ b/packages/PackageInstaller/TEST_MAPPING
@@ -65,6 +65,17 @@
"name": "CtsIntentSignatureTestCases"
},
{
+ "name": "CtsPackageInstallerCUJDeviceAdminTestCases",
+ "options":[
+ {
+ "exclude-annotation":"androidx.test.filters.FlakyTest"
+ },
+ {
+ "exclude-annotation":"org.junit.Ignore"
+ }
+ ]
+ },
+ {
"name": "CtsPackageInstallerCUJInstallationTestCases",
"options":[
{
@@ -76,6 +87,17 @@
]
},
{
+ "name": "CtsPackageInstallerCUJMultiUsersTestCases",
+ "options":[
+ {
+ "exclude-annotation":"androidx.test.filters.FlakyTest"
+ },
+ {
+ "exclude-annotation":"org.junit.Ignore"
+ }
+ ]
+ },
+ {
"name": "CtsPackageInstallerCUJUninstallationTestCases",
"options":[
{
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialog.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialog.kt
index 022dded..265864e 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialog.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialog.kt
@@ -17,11 +17,16 @@
package com.android.settingslib.spa.widget.dialog
import android.content.res.Configuration
+import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.AlertDialog
+import androidx.compose.material3.Button
+import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
@@ -32,9 +37,11 @@
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalConfiguration
+import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.DialogProperties
+import com.android.settingslib.spa.framework.theme.isSpaExpressiveEnabled
data class AlertDialogButton(
val text: String,
@@ -85,27 +92,41 @@
AlertDialog(
onDismissRequest = ::close,
modifier = Modifier.width(getDialogWidth()),
- confirmButton = { confirmButton?.let { Button(it) } },
- dismissButton = dismissButton?.let { { Button(it) } },
- title = title?.let { { Text(it) } },
- text = text?.let {
- {
- Column(Modifier.verticalScroll(rememberScrollState())) {
- text()
- }
- }
+ confirmButton = {
+ confirmButton?.let { if (isSpaExpressiveEnabled) ConfirmButton(it) else Button(it) }
},
+ dismissButton =
+ dismissButton?.let {
+ { if (isSpaExpressiveEnabled) DismissButton(it) else Button(it) }
+ },
+ title = title?.let { { CenterRow { Text(it) } } },
+ text =
+ text?.let {
+ { CenterRow { Column(Modifier.verticalScroll(rememberScrollState())) { text() } } }
+ },
properties = DialogProperties(usePlatformDefaultWidth = false),
)
}
@Composable
+internal fun CenterRow(content: @Composable (() -> Unit)) {
+ if (isSpaExpressiveEnabled) {
+ Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center) {
+ content()
+ }
+ } else {
+ content()
+ }
+}
+
+@Composable
fun getDialogWidth(): Dp {
val configuration = LocalConfiguration.current
- return configuration.screenWidthDp.dp * when (configuration.orientation) {
- Configuration.ORIENTATION_LANDSCAPE -> 0.65f
- else -> 0.85f
- }
+ return configuration.screenWidthDp.dp *
+ when (configuration.orientation) {
+ Configuration.ORIENTATION_LANDSCAPE -> 0.65f
+ else -> 0.85f
+ }
}
@Composable
@@ -120,3 +141,47 @@
Text(button.text)
}
}
+
+@Composable
+private fun AlertDialogPresenter.DismissButton(button: AlertDialogButton) {
+ OutlinedButton(
+ onClick = {
+ close()
+ button.onClick()
+ },
+ enabled = button.enabled,
+ ) {
+ Text(button.text)
+ }
+}
+
+@Composable
+private fun AlertDialogPresenter.ConfirmButton(button: AlertDialogButton) {
+ Button(
+ onClick = {
+ close()
+ button.onClick()
+ },
+ enabled = button.enabled,
+ ) {
+ Text(button.text)
+ }
+}
+
+@Preview
+@Composable
+private fun AlertDialogPreview() {
+ val alertDialogPresenter = remember {
+ object : AlertDialogPresenter {
+ override fun open() {}
+
+ override fun close() {}
+ }
+ }
+ alertDialogPresenter.SettingsAlertDialog(
+ confirmButton = AlertDialogButton("Ok"),
+ dismissButton = AlertDialogButton("Cancel"),
+ title = "Title",
+ text = { Text("Text") },
+ )
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialogWithIcon.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialogWithIcon.kt
index 030522d..58a83fa 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialogWithIcon.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialogWithIcon.kt
@@ -40,10 +40,7 @@
dismissButton: AlertDialogButton?,
title: String?,
icon: @Composable (() -> Unit)? = {
- Icon(
- Icons.Default.WarningAmber,
- contentDescription = null
- )
+ Icon(Icons.Default.WarningAmber, contentDescription = null)
},
text: @Composable (() -> Unit)?,
) {
@@ -52,43 +49,22 @@
icon = icon,
modifier = Modifier.width(getDialogWidth()),
confirmButton = {
- confirmButton?.let {
- Button(
- onClick = {
- it.onClick()
- },
- ) {
- Text(it.text)
+ confirmButton?.let { Button(onClick = { it.onClick() }) { Text(it.text) } }
+ },
+ dismissButton =
+ dismissButton?.let { { OutlinedButton(onClick = { it.onClick() }) { Text(it.text) } } },
+ title =
+ title?.let {
+ {
+ CenterRow {
+ Text(it, modifier = Modifier.fillMaxWidth(), textAlign = TextAlign.Center)
+ }
}
- }
- },
- dismissButton = dismissButton?.let {
- {
- OutlinedButton(
- onClick = {
- it.onClick()
- },
- ) {
- Text(it.text)
- }
- }
- },
- title = title?.let {
- {
- Text(
- it,
- modifier = Modifier.fillMaxWidth(),
- textAlign = TextAlign.Center
- )
- }
- },
- text = text?.let {
- {
- Column(Modifier.verticalScroll(rememberScrollState())) {
- text()
- }
- }
- },
+ },
+ text =
+ text?.let {
+ { CenterRow { Column(Modifier.verticalScroll(rememberScrollState())) { text() } } }
+ },
properties = DialogProperties(usePlatformDefaultWidth = false),
)
-}
\ No newline at end of file
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlterDialogContent.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlterDialogContent.kt
index bef0bca..9f2210d 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlterDialogContent.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlterDialogContent.kt
@@ -58,10 +58,7 @@
dismissButton: AlertDialogButton?,
title: String?,
icon: @Composable (() -> Unit)? = {
- Icon(
- Icons.Default.WarningAmber,
- contentDescription = null
- )
+ Icon(Icons.Default.WarningAmber, contentDescription = null)
},
text: @Composable (() -> Unit)?,
) {
@@ -69,42 +66,22 @@
buttons = {
AlertDialogFlowRow(
mainAxisSpacing = ButtonsMainAxisSpacing,
- crossAxisSpacing = ButtonsCrossAxisSpacing
+ crossAxisSpacing = ButtonsCrossAxisSpacing,
) {
- dismissButton?.let {
- OutlinedButton(onClick = it.onClick) {
- Text(it.text)
- }
- }
- confirmButton?.let {
- Button(
- onClick = {
- it.onClick()
- },
- ) {
- Text(it.text)
- }
- }
+ dismissButton?.let { OutlinedButton(onClick = it.onClick) { Text(it.text) } }
+ confirmButton?.let { Button(onClick = { it.onClick() }) { Text(it.text) } }
}
},
icon = icon,
modifier = Modifier.width(getDialogWidth()),
- title = title?.let {
- {
- Text(
- it,
- modifier = Modifier.fillMaxWidth(),
- textAlign = TextAlign.Center
- )
- }
- },
- text = text?.let {
- {
- Column(Modifier.verticalScroll(rememberScrollState())) {
- text()
- }
- }
- },
+ title =
+ title?.let {
+ { Text(it, modifier = Modifier.fillMaxWidth(), textAlign = TextAlign.Center) }
+ },
+ text =
+ text?.let {
+ { CenterRow { Column(Modifier.verticalScroll(rememberScrollState())) { text() } } }
+ },
)
}
@@ -121,18 +98,12 @@
shape = SettingsShape.CornerExtraLarge,
color = MaterialTheme.colorScheme.surfaceContainerHigh,
) {
- Column(
- modifier = Modifier.padding(DialogPadding)
- ) {
+ Column(modifier = Modifier.padding(DialogPadding)) {
icon?.let {
CompositionLocalProvider(
- LocalContentColor provides AlertDialogDefaults.iconContentColor,
+ LocalContentColor provides AlertDialogDefaults.iconContentColor
) {
- Box(
- Modifier
- .padding(IconPadding)
- .align(Alignment.CenterHorizontally)
- ) {
+ Box(Modifier.padding(IconPadding).align(Alignment.CenterHorizontally)) {
icon()
}
}
@@ -144,8 +115,7 @@
) {
Box(
// Align the title to the center when an icon is present.
- Modifier
- .padding(TitlePadding)
+ Modifier.padding(TitlePadding)
.align(
if (icon == null) {
Alignment.Start
@@ -161,11 +131,10 @@
text?.let {
ProvideContentColorTextStyle(
contentColor = AlertDialogDefaults.textContentColor,
- textStyle = MaterialTheme.typography.bodyMedium
+ textStyle = MaterialTheme.typography.bodyMedium,
) {
Box(
- Modifier
- .weight(weight = 1f, fill = false)
+ Modifier.weight(weight = 1f, fill = false)
.padding(TextPadding)
.align(Alignment.Start)
) {
@@ -177,7 +146,7 @@
ProvideContentColorTextStyle(
contentColor = MaterialTheme.colorScheme.primary,
textStyle = MaterialTheme.typography.labelLarge,
- content = buttons
+ content = buttons,
)
}
}
@@ -188,7 +157,7 @@
internal fun AlertDialogFlowRow(
mainAxisSpacing: Dp,
crossAxisSpacing: Dp,
- content: @Composable () -> Unit
+ content: @Composable () -> Unit,
) {
Layout(content) { measurables, constraints ->
val sequences = mutableListOf<List<Placeable>>()
@@ -204,8 +173,9 @@
// Return whether the placeable can be added to the current sequence.
fun canAddToCurrentSequence(placeable: Placeable) =
- currentSequence.isEmpty() || currentMainAxisSize + mainAxisSpacing.roundToPx() +
- placeable.width <= constraints.maxWidth
+ currentSequence.isEmpty() ||
+ currentMainAxisSize + mainAxisSpacing.roundToPx() + placeable.width <=
+ constraints.maxWidth
// Store current sequence information and start a new sequence.
fun startNewSequence() {
@@ -213,8 +183,7 @@
crossAxisSpace += crossAxisSpacing.roundToPx()
}
// Ensures that confirming actions appear above dismissive actions.
- @Suppress("ListIterator")
- sequences.add(0, currentSequence.toList())
+ @Suppress("ListIterator") sequences.add(0, currentSequence.toList())
crossAxisSizes += currentCrossAxisSize
crossAxisPositions += crossAxisSpace
@@ -254,23 +223,23 @@
layout(layoutWidth, layoutHeight) {
sequences.fastForEachIndexed { i, placeables ->
- val childrenMainAxisSizes = IntArray(placeables.size) { j ->
- placeables[j].width +
- if (j < placeables.lastIndex) mainAxisSpacing.roundToPx() else 0
- }
+ val childrenMainAxisSizes =
+ IntArray(placeables.size) { j ->
+ placeables[j].width +
+ if (j < placeables.lastIndex) mainAxisSpacing.roundToPx() else 0
+ }
val arrangement = Arrangement.End
val mainAxisPositions = IntArray(childrenMainAxisSizes.size) { 0 }
with(arrangement) {
arrange(
- mainAxisLayoutSize, childrenMainAxisSizes,
- layoutDirection, mainAxisPositions
+ mainAxisLayoutSize,
+ childrenMainAxisSizes,
+ layoutDirection,
+ mainAxisPositions,
)
}
placeables.fastForEachIndexed { j, placeable ->
- placeable.place(
- x = mainAxisPositions[j],
- y = crossAxisPositions[i]
- )
+ placeable.place(x = mainAxisPositions[j], y = crossAxisPositions[i])
}
}
}
@@ -289,8 +258,8 @@
/**
* ProvideContentColorTextStyle
*
- * A convenience method to provide values to both LocalContentColor and LocalTextStyle in
- * one call. This is less expensive than nesting calls to CompositionLocalProvider.
+ * A convenience method to provide values to both LocalContentColor and LocalTextStyle in one call.
+ * This is less expensive than nesting calls to CompositionLocalProvider.
*
* Text styles will be merged with the current value of LocalTextStyle.
*/
@@ -298,12 +267,12 @@
private fun ProvideContentColorTextStyle(
contentColor: Color,
textStyle: TextStyle,
- content: @Composable () -> Unit
+ content: @Composable () -> Unit,
) {
val mergedStyle = LocalTextStyle.current.merge(textStyle)
CompositionLocalProvider(
LocalContentColor provides contentColor,
LocalTextStyle provides mergedStyle,
- content = content
+ content = content,
)
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
index 6f2567b..a3f9e51 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
@@ -80,7 +80,9 @@
"com.android.settings.action.BLUETOOTH_LE_AUDIO_SHARING_STATE_CHANGE";
public static final String EXTRA_LE_AUDIO_SHARING_STATE = "BLUETOOTH_LE_AUDIO_SHARING_STATE";
public static final String EXTRA_BLUETOOTH_DEVICE = "BLUETOOTH_DEVICE";
+ public static final String EXTRA_BT_DEVICE_TO_AUTO_ADD_SOURCE = "BT_DEVICE_TO_AUTO_ADD_SOURCE";
public static final String EXTRA_START_LE_AUDIO_SHARING = "START_LE_AUDIO_SHARING";
+ public static final String EXTRA_PAIR_AND_JOIN_SHARING = "PAIR_AND_JOIN_SHARING";
public static final int BROADCAST_STATE_UNKNOWN = 0;
public static final int BROADCAST_STATE_ON = 1;
public static final int BROADCAST_STATE_OFF = 2;
@@ -1224,7 +1226,7 @@
Intent intent = new Intent(ACTION_LE_AUDIO_SHARING_STATE_CHANGE);
intent.putExtra(EXTRA_LE_AUDIO_SHARING_STATE, state);
intent.setPackage(mContext.getPackageName());
- Log.e(TAG, "notifyBroadcastStateChange for state = " + state);
+ Log.d(TAG, "notifyBroadcastStateChange for state = " + state);
mContext.sendBroadcast(intent);
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingServiceConnection.kt b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingServiceConnection.kt
index 7eae5b2..3d8ff86 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingServiceConnection.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingServiceConnection.kt
@@ -56,11 +56,13 @@
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapNotNull
import kotlinx.coroutines.flow.shareIn
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
+import kotlinx.coroutines.plus
import kotlinx.coroutines.withContext
@OptIn(ExperimentalCoroutinesApi::class)
@@ -101,8 +103,7 @@
} else if (allStatus.all { it is ServiceConnectionStatus.Connected }) {
allStatus
.filterIsInstance<
- ServiceConnectionStatus.Connected<
- IDeviceSettingsProviderService>
+ ServiceConnectionStatus.Connected<IDeviceSettingsProviderService>
>()
.all { it.service.serviceStatus?.enabled == true }
} else {
@@ -232,7 +233,7 @@
IDeviceSettingsProviderService.Stub::asInterface,
)
.stateIn(
- coroutineScope,
+ coroutineScope.plus(backgroundCoroutineContext),
SharingStarted.WhileSubscribed(),
ServiceConnectionStatus.Connecting,
)
@@ -263,21 +264,30 @@
transform: ((IBinder) -> T),
): Flow<ServiceConnectionStatus<T>> {
return callbackFlow {
- val serviceConnection =
- object : ServiceConnection {
- override fun onServiceConnected(name: ComponentName, service: IBinder) {
- launch { send(ServiceConnectionStatus.Connected(transform(service))) }
- }
+ val serviceConnection =
+ object : ServiceConnection {
+ override fun onServiceConnected(name: ComponentName, service: IBinder) {
+ launch { send(ServiceConnectionStatus.Connected(transform(service))) }
+ }
- override fun onServiceDisconnected(name: ComponentName?) {
- launch { send(ServiceConnectionStatus.Connecting) }
+ override fun onServiceDisconnected(name: ComponentName?) {
+ launch { send(ServiceConnectionStatus.Connecting) }
+ }
}
+ if (
+ !context.bindService(
+ intent,
+ Context.BIND_AUTO_CREATE,
+ { launch { it.run() } },
+ serviceConnection,
+ )
+ ) {
+ Log.w(TAG, "Fail to bind service $intent")
+ launch { send(ServiceConnectionStatus.Failed) }
}
- if (!context.bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE)) {
- launch { send(ServiceConnectionStatus.Failed) }
+ awaitClose { context.unbindService(serviceConnection) }
}
- awaitClose { context.unbindService(serviceConnection) }
- }
+ .flowOn(backgroundCoroutineContext)
}
private suspend fun tryGetEndpointFromMetadata(cachedDevice: CachedBluetoothDevice): EndPoint? =
diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/ConversationIconFactory.java b/packages/SettingsLib/src/com/android/settingslib/notification/ConversationIconFactory.java
index 251cd36..9a9a960 100644
--- a/packages/SettingsLib/src/com/android/settingslib/notification/ConversationIconFactory.java
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/ConversationIconFactory.java
@@ -87,7 +87,7 @@
* Returns the conversation info drawable
*/
public Drawable getBaseIconDrawable(ShortcutInfo shortcutInfo) {
- return mLauncherApps.getShortcutIconDrawable(shortcutInfo, mFillResIconDpi);
+ return mLauncherApps.getShortcutIconDrawable(shortcutInfo, mFullResIconDpi);
}
/**
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingRepositoryTest.kt b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingRepositoryTest.kt
index 81b5634..0cb6bc1 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingRepositoryTest.kt
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingRepositoryTest.kt
@@ -102,9 +102,9 @@
`when`(settingProviderService2.queryLocalInterface(anyString()))
.thenReturn(settingProviderService2)
- `when`(context.bindService(any(), any(), anyInt())).then { input ->
+ `when`(context.bindService(any(), anyInt(), any(), any())).then { input ->
val intent = input.getArgument<Intent?>(0)
- val connection = input.getArgument<ServiceConnection>(1)
+ val connection = input.getArgument<ServiceConnection>(3)
when (intent?.action) {
CONFIG_SERVICE_INTENT_ACTION ->
@@ -210,7 +210,7 @@
fun getDeviceSettingsConfig_bindingServiceFail_returnNull() {
testScope.runTest {
`when`(configService.getDeviceSettingsConfig(any())).thenReturn(DEVICE_SETTING_CONFIG)
- doReturn(false).`when`(context).bindService(any(), any(), anyInt())
+ doReturn(false).`when`(context).bindService(any(), anyInt(), any(), any())
val config = underTest.getDeviceSettingsConfig(cachedDevice)
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 855ebe3..c49ffb4 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -1063,6 +1063,13 @@
}
flag {
+ name: "communal_widget_resizing"
+ namespace: "systemui"
+ description: "Allow resizing of widgets on glanceable hub"
+ bug: "368053818"
+}
+
+flag {
name: "app_clips_backlinks"
namespace: "systemui"
description: "Enables Backlinks improvement feature in App Clips"
@@ -1415,3 +1422,13 @@
description: "Allow non-touchscreen devices to bypass falsing"
bug: "319809270"
}
+
+flag {
+ name: "media_projection_dialog_behind_lockscreen"
+ namespace: "systemui"
+ description: "Ensure MediaProjection Dialog appears behind the lockscreen"
+ bug: "351409536"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/data/repository/KeyboardRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/data/repository/KeyboardRepositoryTest.kt
index 361e768..8f9e238 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/data/repository/KeyboardRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/data/repository/KeyboardRepositoryTest.kt
@@ -17,11 +17,13 @@
package com.android.systemui.keyboard.data.repository
-import android.hardware.input.InputManager
+import android.hardware.input.FakeInputManager
+import android.hardware.input.InputManager.InputDeviceListener
import android.hardware.input.InputManager.KeyboardBacklightListener
import android.hardware.input.KeyboardBacklightState
+import android.hardware.input.fakeInputManager
import android.testing.TestableLooper
-import android.view.InputDevice
+import android.view.InputDevice.SOURCE_KEYBOARD
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -30,10 +32,7 @@
import com.android.systemui.coroutines.collectValues
import com.android.systemui.inputdevice.data.repository.InputDeviceRepository
import com.android.systemui.keyboard.data.model.Keyboard
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.mock
-import com.android.systemui.util.mockito.nullable
-import com.android.systemui.util.mockito.whenever
+import com.android.systemui.testKosmos
import com.android.systemui.utils.os.FakeHandler
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.CoroutineDispatcher
@@ -50,9 +49,10 @@
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
import org.mockito.Captor
-import org.mockito.Mock
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.any
+import org.mockito.kotlin.anyOrNull
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@@ -60,10 +60,9 @@
@RunWith(AndroidJUnit4::class)
class KeyboardRepositoryTest : SysuiTestCase() {
- @Captor
- private lateinit var deviceListenerCaptor: ArgumentCaptor<InputManager.InputDeviceListener>
+ @Captor private lateinit var deviceListenerCaptor: ArgumentCaptor<InputDeviceListener>
@Captor private lateinit var backlightListenerCaptor: ArgumentCaptor<KeyboardBacklightListener>
- @Mock private lateinit var inputManager: InputManager
+ private lateinit var fakeInputManager: FakeInputManager
private lateinit var underTest: KeyboardRepository
private lateinit var dispatcher: CoroutineDispatcher
@@ -73,16 +72,14 @@
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
- whenever(inputManager.inputDeviceIds).thenReturn(intArrayOf())
- whenever(inputManager.getInputDevice(any())).then { invocation ->
- val id = invocation.arguments.first()
- INPUT_DEVICES_MAP[id]
- }
+ fakeInputManager = testKosmos().fakeInputManager
dispatcher = StandardTestDispatcher()
testScope = TestScope(dispatcher)
val handler = FakeHandler(TestableLooper.get(this).looper)
- inputDeviceRepo = InputDeviceRepository(handler, testScope.backgroundScope, inputManager)
- underTest = KeyboardRepositoryImpl(dispatcher, inputManager, inputDeviceRepo)
+ inputDeviceRepo =
+ InputDeviceRepository(handler, testScope.backgroundScope, fakeInputManager.inputManager)
+ underTest =
+ KeyboardRepositoryImpl(dispatcher, fakeInputManager.inputManager, inputDeviceRepo)
}
@Test
@@ -95,7 +92,7 @@
@Test
fun emitsConnected_ifKeyboardAlreadyConnectedAtTheStart() =
testScope.runTest {
- whenever(inputManager.inputDeviceIds).thenReturn(intArrayOf(PHYSICAL_FULL_KEYBOARD_ID))
+ fakeInputManager.addPhysicalKeyboard(PHYSICAL_FULL_KEYBOARD_ID)
val initialValue = underTest.isAnyKeyboardConnected.first()
assertThat(initialValue).isTrue()
}
@@ -103,74 +100,77 @@
@Test
fun emitsConnected_whenNewPhysicalKeyboardConnects() =
testScope.runTest {
- val deviceListener = captureDeviceListener()
+ captureDeviceListener()
val isKeyboardConnected by collectLastValue(underTest.isAnyKeyboardConnected)
- deviceListener.onInputDeviceAdded(PHYSICAL_FULL_KEYBOARD_ID)
+ fakeInputManager.addPhysicalKeyboard(PHYSICAL_FULL_KEYBOARD_ID)
assertThat(isKeyboardConnected).isTrue()
}
@Test
- fun emitsDisconnected_whenDeviceWithIdDoesNotExist() =
+ fun emitsDisconnected_whenDeviceNotFound() =
testScope.runTest {
- val deviceListener = captureDeviceListener()
+ captureDeviceListener()
val isKeyboardConnected by collectLastValue(underTest.isAnyKeyboardConnected)
-
- deviceListener.onInputDeviceAdded(NULL_DEVICE_ID)
+ fakeInputManager.addDevice(NULL_DEVICE_ID, SOURCE_KEYBOARD, isNotFound = true)
assertThat(isKeyboardConnected).isFalse()
}
@Test
fun emitsDisconnected_whenKeyboardDisconnects() =
testScope.runTest {
- val deviceListener = captureDeviceListener()
+ captureDeviceListener()
val isKeyboardConnected by collectLastValue(underTest.isAnyKeyboardConnected)
- deviceListener.onInputDeviceAdded(PHYSICAL_FULL_KEYBOARD_ID)
+ fakeInputManager.addPhysicalKeyboard(PHYSICAL_FULL_KEYBOARD_ID)
assertThat(isKeyboardConnected).isTrue()
- deviceListener.onInputDeviceRemoved(PHYSICAL_FULL_KEYBOARD_ID)
+ fakeInputManager.removeDevice(PHYSICAL_FULL_KEYBOARD_ID)
assertThat(isKeyboardConnected).isFalse()
}
- private suspend fun captureDeviceListener(): InputManager.InputDeviceListener {
+ private suspend fun captureDeviceListener() {
underTest.isAnyKeyboardConnected.first()
- verify(inputManager).registerInputDeviceListener(deviceListenerCaptor.capture(), nullable())
- return deviceListenerCaptor.value
+ verify(fakeInputManager.inputManager)
+ .registerInputDeviceListener(deviceListenerCaptor.capture(), anyOrNull())
+ fakeInputManager.registerInputDeviceListener(deviceListenerCaptor.value)
}
@Test
fun emitsDisconnected_whenVirtualOrNotFullKeyboardConnects() =
testScope.runTest {
- val deviceListener = captureDeviceListener()
+ captureDeviceListener()
val isKeyboardConnected by collectLastValue(underTest.isAnyKeyboardConnected)
- deviceListener.onInputDeviceAdded(PHYSICAL_NOT_FULL_KEYBOARD_ID)
+ fakeInputManager.addPhysicalKeyboard(
+ PHYSICAL_NOT_FULL_KEYBOARD_ID,
+ isFullKeyboard = false
+ )
assertThat(isKeyboardConnected).isFalse()
- deviceListener.onInputDeviceAdded(VIRTUAL_FULL_KEYBOARD_ID)
+ fakeInputManager.addDevice(VIRTUAL_FULL_KEYBOARD_ID, SOURCE_KEYBOARD)
assertThat(isKeyboardConnected).isFalse()
}
@Test
fun emitsDisconnected_whenKeyboardDisconnectsAndWasAlreadyConnectedAtTheStart() =
testScope.runTest {
- val deviceListener = captureDeviceListener()
+ captureDeviceListener()
val isKeyboardConnected by collectLastValue(underTest.isAnyKeyboardConnected)
- deviceListener.onInputDeviceRemoved(PHYSICAL_FULL_KEYBOARD_ID)
+ fakeInputManager.removeDevice(PHYSICAL_FULL_KEYBOARD_ID)
assertThat(isKeyboardConnected).isFalse()
}
@Test
fun emitsConnected_whenAnotherDeviceDisconnects() =
testScope.runTest {
- val deviceListener = captureDeviceListener()
+ captureDeviceListener()
val isKeyboardConnected by collectLastValue(underTest.isAnyKeyboardConnected)
- deviceListener.onInputDeviceAdded(PHYSICAL_FULL_KEYBOARD_ID)
- deviceListener.onInputDeviceRemoved(VIRTUAL_FULL_KEYBOARD_ID)
+ fakeInputManager.addPhysicalKeyboard(PHYSICAL_FULL_KEYBOARD_ID)
+ fakeInputManager.removeDevice(VIRTUAL_FULL_KEYBOARD_ID)
assertThat(isKeyboardConnected).isTrue()
}
@@ -178,12 +178,12 @@
@Test
fun emitsConnected_whenOnePhysicalKeyboardDisconnectsButAnotherRemainsConnected() =
testScope.runTest {
- val deviceListener = captureDeviceListener()
+ captureDeviceListener()
val isKeyboardConnected by collectLastValue(underTest.isAnyKeyboardConnected)
- deviceListener.onInputDeviceAdded(PHYSICAL_FULL_KEYBOARD_ID)
- deviceListener.onInputDeviceAdded(ANOTHER_PHYSICAL_FULL_KEYBOARD_ID)
- deviceListener.onInputDeviceRemoved(ANOTHER_PHYSICAL_FULL_KEYBOARD_ID)
+ fakeInputManager.addPhysicalKeyboard(PHYSICAL_FULL_KEYBOARD_ID)
+ fakeInputManager.addPhysicalKeyboard(ANOTHER_PHYSICAL_FULL_KEYBOARD_ID)
+ fakeInputManager.removeDevice(ANOTHER_PHYSICAL_FULL_KEYBOARD_ID)
assertThat(isKeyboardConnected).isTrue()
}
@@ -195,7 +195,7 @@
// subscribed to and listener is actually registered in inputManager
val backlight by collectLastValueImmediately(underTest.backlight)
- verify(inputManager)
+ verify(fakeInputManager.inputManager)
.registerKeyboardBacklightListener(any(), backlightListenerCaptor.capture())
backlightListenerCaptor.value.onBacklightChanged(current = 1, max = 5)
@@ -217,7 +217,7 @@
fun keyboardBacklightValuesNotPassed_fromBacklightListener_whenNotTriggeredByKeyPress() {
testScope.runTest {
val backlight by collectLastValueImmediately(underTest.backlight)
- verify(inputManager)
+ verify(fakeInputManager.inputManager)
.registerKeyboardBacklightListener(any(), backlightListenerCaptor.capture())
backlightListenerCaptor.value.onBacklightChanged(
@@ -233,7 +233,7 @@
fun passesKeyboardBacklightValues_fromBacklightListener_whenTriggeredByKeyPress() {
testScope.runTest {
val backlight by collectLastValueImmediately(underTest.backlight)
- verify(inputManager)
+ verify(fakeInputManager.inputManager)
.registerKeyboardBacklightListener(any(), backlightListenerCaptor.capture())
backlightListenerCaptor.value.onBacklightChanged(
@@ -248,14 +248,11 @@
@Test
fun passessAllKeyboards_thatWereAlreadyConnectedOnInitialization() {
testScope.runTest {
- whenever(inputManager.inputDeviceIds)
- .thenReturn(
- intArrayOf(
- PHYSICAL_FULL_KEYBOARD_ID,
- ANOTHER_PHYSICAL_FULL_KEYBOARD_ID,
- VIRTUAL_FULL_KEYBOARD_ID // not a physical keyboard - that's why result is 2
- )
- )
+ fakeInputManager.addPhysicalKeyboard(PHYSICAL_FULL_KEYBOARD_ID)
+ fakeInputManager.addPhysicalKeyboard(ANOTHER_PHYSICAL_FULL_KEYBOARD_ID)
+ // not a physical keyboard - that's why result is 2
+ fakeInputManager.addDevice(VIRTUAL_FULL_KEYBOARD_ID, SOURCE_KEYBOARD)
+
val keyboards by collectValues(underTest.newlyConnectedKeyboard)
assertThat(keyboards).hasSize(2)
@@ -265,9 +262,9 @@
@Test
fun passesNewlyConnectedKeyboard() {
testScope.runTest {
- val deviceListener = captureDeviceListener()
+ captureDeviceListener()
- deviceListener.onInputDeviceAdded(PHYSICAL_FULL_KEYBOARD_ID)
+ fakeInputManager.addPhysicalKeyboard(PHYSICAL_FULL_KEYBOARD_ID, VENDOR_ID, PRODUCT_ID)
assertThat(underTest.newlyConnectedKeyboard.first())
.isEqualTo(Keyboard(VENDOR_ID, PRODUCT_ID))
@@ -277,13 +274,12 @@
@Test
fun emitsOnlyNewlyConnectedKeyboards() {
testScope.runTest {
- whenever(inputManager.inputDeviceIds).thenReturn(intArrayOf(PHYSICAL_FULL_KEYBOARD_ID))
+ fakeInputManager.addPhysicalKeyboard(PHYSICAL_FULL_KEYBOARD_ID)
underTest.newlyConnectedKeyboard.first()
- verify(inputManager)
- .registerInputDeviceListener(deviceListenerCaptor.capture(), nullable())
- val deviceListener = deviceListenerCaptor.value
- deviceListener.onInputDeviceAdded(ANOTHER_PHYSICAL_FULL_KEYBOARD_ID)
+ captureDeviceListener()
+
+ fakeInputManager.addPhysicalKeyboard(ANOTHER_PHYSICAL_FULL_KEYBOARD_ID)
val keyboards by collectValues(underTest.newlyConnectedKeyboard)
assertThat(keyboards).hasSize(1)
@@ -293,14 +289,11 @@
@Test
fun stillEmitsNewKeyboardEvenIfFlowWasSubscribedAfterOtherFlows() {
testScope.runTest {
- whenever(inputManager.inputDeviceIds)
- .thenReturn(
- intArrayOf(
- PHYSICAL_FULL_KEYBOARD_ID,
- ANOTHER_PHYSICAL_FULL_KEYBOARD_ID,
- VIRTUAL_FULL_KEYBOARD_ID // not a physical keyboard - that's why result is 2
- )
- )
+ fakeInputManager.addPhysicalKeyboard(PHYSICAL_FULL_KEYBOARD_ID)
+ fakeInputManager.addPhysicalKeyboard(ANOTHER_PHYSICAL_FULL_KEYBOARD_ID)
+ // not a physical keyboard - that's why result is 2
+ fakeInputManager.addDevice(VIRTUAL_FULL_KEYBOARD_ID, SOURCE_KEYBOARD)
+
collectLastValueImmediately(underTest.isAnyKeyboardConnected)
// let's pretend second flow is subscribed after some delay
@@ -314,12 +307,12 @@
@Test
fun emitsKeyboardWhenItWasReconnected() {
testScope.runTest {
- val deviceListener = captureDeviceListener()
+ captureDeviceListener()
val keyboards by collectValues(underTest.newlyConnectedKeyboard)
- deviceListener.onInputDeviceAdded(PHYSICAL_FULL_KEYBOARD_ID)
- deviceListener.onInputDeviceRemoved(PHYSICAL_FULL_KEYBOARD_ID)
- deviceListener.onInputDeviceAdded(PHYSICAL_FULL_KEYBOARD_ID)
+ fakeInputManager.addPhysicalKeyboard(PHYSICAL_FULL_KEYBOARD_ID)
+ fakeInputManager.removeDevice(PHYSICAL_FULL_KEYBOARD_ID)
+ fakeInputManager.addPhysicalKeyboard(PHYSICAL_FULL_KEYBOARD_ID)
assertThat(keyboards).hasSize(2)
}
@@ -339,30 +332,13 @@
private companion object {
private const val PHYSICAL_FULL_KEYBOARD_ID = 1
- private const val VIRTUAL_FULL_KEYBOARD_ID = 2
+ private const val VIRTUAL_FULL_KEYBOARD_ID = -2 // Virtual keyboards has id with minus value
private const val PHYSICAL_NOT_FULL_KEYBOARD_ID = 3
private const val ANOTHER_PHYSICAL_FULL_KEYBOARD_ID = 4
- private const val NULL_DEVICE_ID = 5
+ private const val NULL_DEVICE_ID = -5
private const val VENDOR_ID = 99
private const val PRODUCT_ID = 101
-
- private val INPUT_DEVICES_MAP: Map<Int, InputDevice> =
- mapOf(
- PHYSICAL_FULL_KEYBOARD_ID to inputDevice(virtual = false, fullKeyboard = true),
- VIRTUAL_FULL_KEYBOARD_ID to inputDevice(virtual = true, fullKeyboard = true),
- PHYSICAL_NOT_FULL_KEYBOARD_ID to inputDevice(virtual = false, fullKeyboard = false),
- ANOTHER_PHYSICAL_FULL_KEYBOARD_ID to
- inputDevice(virtual = false, fullKeyboard = true)
- )
-
- private fun inputDevice(virtual: Boolean, fullKeyboard: Boolean): InputDevice =
- mock<InputDevice>().also {
- whenever(it.isVirtual).thenReturn(virtual)
- whenever(it.isFullKeyboard).thenReturn(fullKeyboard)
- whenever(it.vendorId).thenReturn(VENDOR_ID)
- whenever(it.productId).thenReturn(PRODUCT_ID)
- }
}
private class TestBacklightState(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaControlInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaControlInteractorTest.kt
index d594f3a..62d0625 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaControlInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaControlInteractorTest.kt
@@ -121,7 +121,7 @@
MediaData(
userId = USER_ID,
instanceId = InstanceId.fakeInstanceId(2),
- artist = ARTIST
+ artist = ARTIST,
)
mediaDataFilter.onMediaDataLoaded(KEY, KEY, mediaData)
@@ -145,10 +145,17 @@
val clickIntent = mock<PendingIntent> { whenever(it.isActivity).thenReturn(true) }
val expandable = mock<Expandable>()
+ val activityController = mock<ActivityTransitionAnimator.Controller>()
+ whenever(expandable.activityTransitionController(any())).thenReturn(activityController)
underTest.startClickIntent(expandable, clickIntent, SMARTSPACE_CARD_CLICK_EVENT, 1)
- verify(clickIntent).send(any<Bundle>())
+ verify(activityStarter)
+ .startPendingIntentMaybeDismissingKeyguard(
+ eq(clickIntent),
+ eq(null),
+ eq(activityController),
+ )
}
@Test
@@ -174,7 +181,7 @@
mediaData.appUid,
surface = SURFACE,
cardinality = 2,
- rank = 1
+ rank = 1,
)
verify(activityStarter)
.postStartActivityDismissingKeyguard(eq(clickIntent), eq(activityController))
@@ -232,7 +239,7 @@
eq(true),
eq(dialogTransitionController),
eq(null),
- eq(null)
+ eq(null),
)
}
@@ -248,7 +255,7 @@
.createBroadcastDialogWithController(
eq(APP_NAME),
eq(PACKAGE_NAME),
- eq(dialogTransitionController)
+ eq(dialogTransitionController),
)
}
@@ -279,7 +286,7 @@
anyInt(),
anyInt(),
anyInt(),
- anyBoolean()
+ anyBoolean(),
)
verify(listener).onMediaDataRemoved(eq(KEY), eq(true))
}
@@ -307,7 +314,7 @@
mediaData.appUid,
surface = SURFACE,
cardinality = 2,
- rank = 1
+ rank = 1,
)
verify(listener).onMediaDataRemoved(eq(KEY), eq(true))
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModelTest.kt
index 9558e5d..0122028 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModelTest.kt
@@ -24,26 +24,25 @@
import android.media.session.PlaybackState
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
-import com.android.internal.logging.InstanceId
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.kosmos.testScope
-import com.android.systemui.media.controls.domain.pipeline.MediaDataFilterImpl
import com.android.systemui.media.controls.domain.pipeline.mediaDataFilter
import com.android.systemui.media.controls.shared.model.MediaData
import com.android.systemui.media.controls.shared.model.MediaDeviceData
import com.android.systemui.media.controls.util.mediaInstanceId
import com.android.systemui.statusbar.notificationLockscreenUserManager
import com.android.systemui.testKosmos
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.eq
-import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.test.runTest
+import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers
+import org.mockito.ArgumentMatchers.any
import org.mockito.Mockito
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.whenever
@SmallTest
@RunWith(AndroidJUnit4::class)
@@ -52,30 +51,31 @@
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
- private val mediaDataFilter: MediaDataFilterImpl = kosmos.mediaDataFilter
+ private val mediaDataFilter = kosmos.mediaDataFilter
private val notificationLockscreenUserManager = kosmos.notificationLockscreenUserManager
private val packageManager = kosmos.packageManager
private val drawable = context.getDrawable(R.drawable.ic_media_play)
- private val instanceId: InstanceId = kosmos.mediaInstanceId
-
+ private val instanceId = kosmos.mediaInstanceId
private val underTest: MediaControlViewModel = kosmos.mediaControlViewModel
+ @Before
+ fun setUp() {
+ whenever(packageManager.getApplicationIcon(Mockito.anyString())).thenReturn(drawable)
+ whenever(packageManager.getApplicationIcon(any(ApplicationInfo::class.java)))
+ .thenReturn(drawable)
+ whenever(packageManager.getApplicationInfo(eq(PACKAGE_NAME), ArgumentMatchers.anyInt()))
+ .thenReturn(ApplicationInfo())
+ whenever(packageManager.getApplicationLabel(any())).thenReturn(PACKAGE_NAME)
+ whenever(notificationLockscreenUserManager.isCurrentProfile(USER_ID)).thenReturn(true)
+ whenever(notificationLockscreenUserManager.isProfileAvailable(USER_ID)).thenReturn(true)
+ context.setMockPackageManager(packageManager)
+ }
+
@Test
fun addMediaControl_mediaControlViewModelIsLoaded() =
testScope.runTest {
- whenever(packageManager.getApplicationIcon(Mockito.anyString())).thenReturn(drawable)
- whenever(packageManager.getApplicationIcon(any(ApplicationInfo::class.java)))
- .thenReturn(drawable)
- whenever(packageManager.getApplicationInfo(eq(PACKAGE_NAME), ArgumentMatchers.anyInt()))
- .thenReturn(ApplicationInfo())
- whenever(packageManager.getApplicationLabel(any())).thenReturn(PACKAGE_NAME)
- whenever(notificationLockscreenUserManager.isCurrentProfile(USER_ID)).thenReturn(true)
- whenever(notificationLockscreenUserManager.isProfileAvailable(USER_ID)).thenReturn(true)
val playerModel by collectLastValue(underTest.player)
-
- context.setMockPackageManager(packageManager)
-
- val mediaData = initMediaData()
+ val mediaData = initMediaData(ARTIST, TITLE)
mediaDataFilter.onMediaDataLoaded(KEY, KEY, mediaData)
@@ -88,7 +88,51 @@
assertThat(playerModel?.playTurbulenceNoise).isFalse()
}
- private fun initMediaData(): MediaData {
+ @Test
+ fun emitDuplicateMediaControls_mediaControlIsNotBound() =
+ testScope.runTest {
+ val playerModel by collectLastValue(underTest.player)
+ val mediaData = initMediaData(ARTIST, TITLE)
+
+ mediaDataFilter.onMediaDataLoaded(KEY, KEY, mediaData)
+
+ assertThat(playerModel).isNotNull()
+ assertThat(playerModel?.titleName).isEqualTo(TITLE)
+ assertThat(playerModel?.artistName).isEqualTo(ARTIST)
+ assertThat(underTest.isNewPlayer(playerModel!!)).isTrue()
+
+ mediaDataFilter.onMediaDataLoaded(KEY, KEY, mediaData)
+
+ assertThat(playerModel).isNotNull()
+ assertThat(playerModel?.titleName).isEqualTo(TITLE)
+ assertThat(playerModel?.artistName).isEqualTo(ARTIST)
+ assertThat(underTest.isNewPlayer(playerModel!!)).isFalse()
+ }
+
+ @Test
+ fun emitDifferentMediaControls_mediaControlIsBound() =
+ testScope.runTest {
+ val playerModel by collectLastValue(underTest.player)
+ var mediaData = initMediaData(ARTIST, TITLE)
+
+ mediaDataFilter.onMediaDataLoaded(KEY, KEY, mediaData)
+
+ assertThat(playerModel).isNotNull()
+ assertThat(playerModel?.titleName).isEqualTo(TITLE)
+ assertThat(playerModel?.artistName).isEqualTo(ARTIST)
+ assertThat(underTest.isNewPlayer(playerModel!!)).isTrue()
+
+ mediaData = initMediaData(ARTIST_2, TITLE_2)
+
+ mediaDataFilter.onMediaDataLoaded(KEY, KEY, mediaData)
+
+ assertThat(playerModel).isNotNull()
+ assertThat(playerModel?.titleName).isEqualTo(TITLE_2)
+ assertThat(playerModel?.artistName).isEqualTo(ARTIST_2)
+ assertThat(underTest.isNewPlayer(playerModel!!)).isTrue()
+ }
+
+ private fun initMediaData(artist: String, title: String): MediaData {
val device = MediaDeviceData(true, null, DEVICE_NAME, null, showBroadcastButton = true)
// Create media session
@@ -111,12 +155,12 @@
return MediaData(
userId = USER_ID,
- artist = ARTIST,
- song = TITLE,
+ artist = artist,
+ song = title,
packageName = PACKAGE,
token = session.sessionToken,
device = device,
- instanceId = instanceId
+ instanceId = instanceId,
)
}
@@ -127,6 +171,8 @@
private const val PACKAGE = "PKG"
private const val ARTIST = "ARTIST"
private const val TITLE = "TITLE"
+ private const val ARTIST_2 = "ARTIST_2"
+ private const val TITLE_2 = "TITLE_2"
private const val DEVICE_NAME = "DEVICE_NAME"
private const val SESSION_KEY = "SESSION_KEY"
private const val SESSION_ARTIST = "SESSION_ARTIST"
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavBarHelperTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/NavBarHelperTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavBarHelperTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/NavBarHelperTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarRotationContextTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/NavigationBarRotationContextTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarRotationContextTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/NavigationBarRotationContextTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/TaskbarDelegateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/TaskbarDelegateTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/navigationbar/TaskbarDelegateTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/TaskbarDelegateTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/FloatingRotationButtonPositionCalculatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/gestural/FloatingRotationButtonPositionCalculatorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/FloatingRotationButtonPositionCalculatorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/gestural/FloatingRotationButtonPositionCalculatorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/views/NavigationBarInflaterViewTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/NavigationBarInflaterViewTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/navigationbar/views/NavigationBarInflaterViewTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/NavigationBarInflaterViewTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/views/NavigationBarTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/NavigationBarTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/navigationbar/views/NavigationBarTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/NavigationBarTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/views/NavigationBarTransitionsTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/NavigationBarTransitionsTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/navigationbar/views/NavigationBarTransitionsTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/NavigationBarTransitionsTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/views/buttons/KeyButtonViewTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/buttons/KeyButtonViewTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/navigationbar/views/buttons/KeyButtonViewTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/buttons/KeyButtonViewTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/views/buttons/NavigationBarContextTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/buttons/NavigationBarContextTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/navigationbar/views/buttons/NavigationBarContextTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/buttons/NavigationBarContextTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/views/buttons/NearestTouchFrameTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/buttons/NearestTouchFrameTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/navigationbar/views/buttons/NearestTouchFrameTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/buttons/NearestTouchFrameTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/FakeNoteTaskBubbleController.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/notetask/FakeNoteTaskBubbleController.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/notetask/FakeNoteTaskBubbleController.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/notetask/FakeNoteTaskBubbleController.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskBubblesServiceTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/notetask/NoteTaskBubblesServiceTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskBubblesServiceTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/notetask/NoteTaskBubblesServiceTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskEventLoggerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/notetask/NoteTaskEventLoggerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskEventLoggerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/notetask/NoteTaskEventLoggerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInfoResolverTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/notetask/NoteTaskInfoResolverTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInfoResolverTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/notetask/NoteTaskInfoResolverTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInfoTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/notetask/NoteTaskInfoTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInfoTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/notetask/NoteTaskInfoTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/people/NotificationHelperTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/people/NotificationHelperTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/people/NotificationHelperTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/people/NotificationHelperTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/people/PeopleBackupFollowUpJobTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/people/PeopleBackupFollowUpJobTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/people/PeopleBackupFollowUpJobTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/people/PeopleBackupFollowUpJobTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/people/PeopleProviderTestable.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/people/PeopleProviderTestable.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/people/PeopleProviderTestable.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/people/PeopleProviderTestable.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/people/SharedPreferencesHelperTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/people/SharedPreferencesHelperTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/people/SharedPreferencesHelperTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/people/SharedPreferencesHelperTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/people/widget/LaunchConversationActivityTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/people/widget/LaunchConversationActivityTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/people/widget/LaunchConversationActivityTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/people/widget/LaunchConversationActivityTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleBackupHelperTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/people/widget/PeopleBackupHelperTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleBackupHelperTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/people/widget/PeopleBackupHelperTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyChipBuilderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/privacy/PrivacyChipBuilderTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyChipBuilderTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/privacy/PrivacyChipBuilderTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/privacy/PrivacyDialogTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/privacy/PrivacyDialogTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/process/condition/SystemProcessConditionTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/process/condition/SystemProcessConditionTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/process/condition/SystemProcessConditionTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/process/condition/SystemProcessConditionTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/HeaderPrivacyIconsControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/HeaderPrivacyIconsControllerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/HeaderPrivacyIconsControllerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/HeaderPrivacyIconsControllerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/LeftRightArrowPressedListenerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/LeftRightArrowPressedListenerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/LeftRightArrowPressedListenerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/LeftRightArrowPressedListenerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/PagedTileLayoutTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/PagedTileLayoutTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/PagedTileLayoutTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/PagedTileLayoutTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSContainerImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QSContainerImplTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/QSContainerImplTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QSContainerImplTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSDisableFlagsLoggerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QSDisableFlagsLoggerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/QSDisableFlagsLoggerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QSDisableFlagsLoggerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseSceneContainerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QSPanelControllerBaseSceneContainerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseSceneContainerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QSPanelControllerBaseSceneContainerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QSPanelControllerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QSPanelControllerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelSwitchToParentTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QSPanelSwitchToParentTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelSwitchToParentTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QSPanelSwitchToParentTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSSquishinessControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QSSquishinessControllerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/QSSquishinessControllerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QSSquishinessControllerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QuickQSPanelTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QuickQSPanelTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/TileStateToProtoTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/TileStateToProtoTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/TileStateToProtoTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/TileStateToProtoTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/TouchAnimatorTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/TouchAnimatorTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/TouchAnimatorTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/TouchAnimatorTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/UserSettingObserverTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/UserSettingObserverTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/UserSettingObserverTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/UserSettingObserverTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileAdapterDelegateTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/customize/TileAdapterDelegateTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileAdapterDelegateTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/customize/TileAdapterDelegateTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileAdapterTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/customize/TileAdapterTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileAdapterTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/customize/TileAdapterTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/external/CustomTileTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/external/CustomTileTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileColorPickerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/external/TileColorPickerTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/external/TileColorPickerTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/external/TileColorPickerTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceManagerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/external/TileServiceManagerTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceManagerTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/external/TileServiceManagerTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/shared/QSPipelineFlagsRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/shared/QSPipelineFlagsRepositoryTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/shared/QSPipelineFlagsRepositoryTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/shared/QSPipelineFlagsRepositoryTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSFactoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tileimpl/QSFactoryImplTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSFactoryImplTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tileimpl/QSFactoryImplTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/ResourceIconTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tileimpl/ResourceIconTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/ResourceIconTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tileimpl/ResourceIconTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/AirplaneModeTileTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/AirplaneModeTileTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/tiles/AirplaneModeTileTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/AirplaneModeTileTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BatterySaverTileTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/BatterySaverTileTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BatterySaverTileTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/BatterySaverTileTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CameraToggleTileTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/CameraToggleTileTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CameraToggleTileTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/CameraToggleTileTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/CastTileTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/CastTileTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ColorCorrectionTileTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ColorCorrectionTileTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ColorCorrectionTileTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ColorCorrectionTileTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ColorInversionTileTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ColorInversionTileTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ColorInversionTileTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ColorInversionTileTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DataSaverTileTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/DataSaverTileTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DataSaverTileTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/DataSaverTileTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/FlashlightTileTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/FlashlightTileTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/tiles/FlashlightTileTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/FlashlightTileTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/FontScalingTileTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/FontScalingTileTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/tiles/FontScalingTileTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/FontScalingTileTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/InternetTileTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/InternetTileTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/tiles/InternetTileTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/InternetTileTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/LocationTileTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/LocationTileTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/tiles/LocationTileTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/LocationTileTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/MicrophoneToggleTileTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/MicrophoneToggleTileTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/tiles/MicrophoneToggleTileTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/MicrophoneToggleTileTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/NfcTileTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/NfcTileTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/tiles/NfcTileTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/NfcTileTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/NightDisplayTileTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/NightDisplayTileTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/tiles/NightDisplayTileTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/NightDisplayTileTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/OneHandedModeTileTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/OneHandedModeTileTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/tiles/OneHandedModeTileTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/OneHandedModeTileTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QRCodeScannerTileTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/QRCodeScannerTileTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QRCodeScannerTileTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/QRCodeScannerTileTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ReduceBrightColorsTileTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ReduceBrightColorsTileTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ReduceBrightColorsTileTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ReduceBrightColorsTileTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RotationLockTileTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/RotationLockTileTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RotationLockTileTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/RotationLockTileTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UiModeNightTileTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/UiModeNightTileTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UiModeNightTileTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/UiModeNightTileTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UserDetailViewAdapterTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/UserDetailViewAdapterTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UserDetailViewAdapterTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/UserDetailViewAdapterTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelImplTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelImplTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelImplTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetAdapterTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/dialog/InternetAdapterTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetAdapterTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/dialog/InternetAdapterTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/WifiStateWorkerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/dialog/WifiStateWorkerTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/WifiStateWorkerTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/dialog/WifiStateWorkerTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/impl/work/ui/WorkModeTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/work/ui/WorkModeTileMapperTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/tiles/impl/work/ui/WorkModeTileMapperTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/work/ui/WorkModeTileMapperTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/user/UserSwitchDialogControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/user/UserSwitchDialogControllerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/user/UserSwitchDialogControllerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/user/UserSwitchDialogControllerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/recordissue/CustomTraceStateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/recordissue/CustomTraceStateTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/recordissue/CustomTraceStateTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/recordissue/CustomTraceStateTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/recordissue/IssueRecordingServiceCommandHandlerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/recordissue/IssueRecordingServiceCommandHandlerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/recordissue/IssueRecordingServiceCommandHandlerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/recordissue/IssueRecordingServiceCommandHandlerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/recordissue/IssueRecordingStateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/recordissue/IssueRecordingStateTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/recordissue/IssueRecordingStateTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/recordissue/IssueRecordingStateTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/retail/data/repository/RetailModeSettingsRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/retail/data/repository/RetailModeSettingsRepositoryTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/retail/data/repository/RetailModeSettingsRepositoryTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/retail/data/repository/RetailModeSettingsRepositoryTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/retail/domain/interactor/RetailModeInteractorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/retail/domain/interactor/RetailModeInteractorImplTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/retail/domain/interactor/RetailModeInteractorImplTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/retail/domain/interactor/RetailModeInteractorImplTest.kt
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
index 8f913ff..78a8a42 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
@@ -113,6 +113,7 @@
import android.view.inputmethod.InputMethodManager;
import android.view.textclassifier.TextClassificationManager;
+import androidx.annotation.NonNull;
import androidx.asynclayoutinflater.view.AsyncLayoutInflater;
import androidx.core.app.NotificationManagerCompat;
@@ -718,6 +719,19 @@
@Provides
@Singleton
+ static ViewCaptureAwareWindowManager.Factory viewCaptureAwareWindowManagerFactory(
+ Lazy<ViewCapture> daggerLazyViewCapture) {
+ return new ViewCaptureAwareWindowManager.Factory() {
+ @NonNull
+ @Override
+ public ViewCaptureAwareWindowManager create(@NonNull WindowManager windowManager) {
+ return provideViewCaptureAwareWindowManager(windowManager, daggerLazyViewCapture);
+ }
+ };
+ }
+
+ @Provides
+ @Singleton
static PermissionManager providePermissionManager(Context context) {
PermissionManager pm = context.getSystemService(PermissionManager.class);
if (pm != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 1a0525d..ba23eb3 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -346,6 +346,7 @@
private boolean mShuttingDown;
private boolean mDozing;
private boolean mAnimatingScreenOff;
+ private boolean mIgnoreDismiss;
private final Context mContext;
private final FalsingCollector mFalsingCollector;
@@ -627,18 +628,20 @@
public void onUserSwitching(int userId) {
Log.d(TAG, String.format("onUserSwitching %d", userId));
synchronized (KeyguardViewMediator.this) {
+ mIgnoreDismiss = true;
notifyTrustedChangedLocked(mUpdateMonitor.getUserHasTrust(userId));
resetKeyguardDonePendingLocked();
- dismiss(null /* callback */, null /* message */);
+ resetStateLocked();
adjustStatusBarLocked();
}
}
@Override
public void onUserSwitchComplete(int userId) {
+ mIgnoreDismiss = false;
Log.d(TAG, String.format("onUserSwitchComplete %d", userId));
- // We are calling dismiss again and with a delay as there are race conditions
- // in some scenarios caused by async layout listeners
+ // We are calling dismiss with a delay as there are race conditions in some scenarios
+ // caused by async layout listeners
mHandler.postDelayed(() -> dismiss(null /* callback */, null /* message */), 500);
}
@@ -2442,6 +2445,10 @@
}
public void dismiss(IKeyguardDismissCallback callback, CharSequence message) {
+ if (mIgnoreDismiss) {
+ android.util.Log.i(TAG, "Ignoring request to dismiss (user switch in progress?)");
+ return;
+ }
mHandler.obtainMessage(DISMISS, new DismissMessage(callback, message)).sendToTarget();
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
index deb0b2d..b5f6b41 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
@@ -261,7 +261,10 @@
->
if (biometricMessage?.message != null) {
chipbarCoordinator!!.displayView(
- createChipbarInfo(biometricMessage.message, R.drawable.ic_lock)
+ createChipbarInfo(
+ biometricMessage.message,
+ R.drawable.ic_lock,
+ )
)
} else {
chipbarCoordinator!!.removeView(ID, "occludingAppMsgNull")
@@ -324,16 +327,12 @@
.getDimensionPixelSize(R.dimen.shelf_appear_translation)
.stateIn(this)
viewModel.isNotifIconContainerVisible.collect { isVisible ->
- if (isVisible.value) {
- blueprintViewModel.refreshBlueprint()
- } else {
- childViews[aodNotificationIconContainerId]
- ?.setAodNotifIconContainerIsVisible(
- isVisible,
- iconsAppearTranslationPx.value,
- screenOffAnimationController,
- )
- }
+ childViews[aodNotificationIconContainerId]
+ ?.setAodNotifIconContainerIsVisible(
+ isVisible,
+ iconsAppearTranslationPx.value,
+ screenOffAnimationController,
+ )
}
}
@@ -383,7 +382,7 @@
if (msdlFeedback()) {
msdlPlayer?.playToken(
MSDLToken.UNLOCK,
- authInteractionProperties,
+ authInteractionProperties
)
} else {
vibratorHelper.performHapticFeedback(
@@ -399,7 +398,7 @@
if (msdlFeedback()) {
msdlPlayer?.playToken(
MSDLToken.FAILURE,
- authInteractionProperties,
+ authInteractionProperties
)
} else {
vibratorHelper.performHapticFeedback(
@@ -426,7 +425,7 @@
blueprintViewModel,
clockViewModel,
childViews,
- burnInParams,
+ burnInParams
)
)
@@ -465,7 +464,11 @@
*/
private fun createChipbarInfo(message: String, @DrawableRes icon: Int): ChipbarInfo {
return ChipbarInfo(
- startIcon = TintedIcon(Icon.Resource(icon, null), ChipbarInfo.DEFAULT_ICON_TINT),
+ startIcon =
+ TintedIcon(
+ Icon.Resource(icon, null),
+ ChipbarInfo.DEFAULT_ICON_TINT,
+ ),
text = Text.Loaded(message),
endItem = null,
vibrationEffect = null,
@@ -496,7 +499,7 @@
oldLeft: Int,
oldTop: Int,
oldRight: Int,
- oldBottom: Int,
+ oldBottom: Int
) {
// After layout, ensure the notifications are positioned correctly
childViews[nsslPlaceholderId]?.let { notificationListPlaceholder ->
@@ -512,7 +515,7 @@
viewModel.onNotificationContainerBoundsChanged(
notificationListPlaceholder.top.toFloat(),
notificationListPlaceholder.bottom.toFloat(),
- animate = shouldAnimate,
+ animate = shouldAnimate
)
}
@@ -528,7 +531,7 @@
Int.MAX_VALUE
} else {
view.getTop()
- },
+ }
)
}
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBlueprintViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBlueprintViewModel.kt
index 4cf3c4e..c6efcfa 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBlueprintViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBlueprintViewModel.kt
@@ -25,18 +25,20 @@
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor
import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.IntraBlueprintTransition.Config
-import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.IntraBlueprintTransition.Type
import javax.inject.Inject
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
-data class TransitionData(val config: Config, val start: Long = System.currentTimeMillis())
+data class TransitionData(
+ val config: Config,
+ val start: Long = System.currentTimeMillis(),
+)
class KeyguardBlueprintViewModel
@Inject
constructor(
@Main private val handler: Handler,
- private val keyguardBlueprintInteractor: KeyguardBlueprintInteractor,
+ keyguardBlueprintInteractor: KeyguardBlueprintInteractor,
) {
val blueprint = keyguardBlueprintInteractor.blueprint
val blueprintId = keyguardBlueprintInteractor.blueprintId
@@ -74,9 +76,6 @@
}
}
- fun refreshBlueprint(type: Type = Type.NoTransition) =
- keyguardBlueprintInteractor.refreshBlueprint(type)
-
fun updateTransitions(data: TransitionData?, mutate: MutableSet<Transition>.() -> Unit) {
runningTransitions.mutate()
@@ -96,7 +95,7 @@
Log.w(
TAG,
"runTransition: skipping ${transition::class.simpleName}: " +
- "currentPriority=$currentPriority; config=$config",
+ "currentPriority=$currentPriority; config=$config"
)
}
apply()
@@ -107,7 +106,7 @@
Log.i(
TAG,
"runTransition: running ${transition::class.simpleName}: " +
- "currentPriority=$currentPriority; config=$config",
+ "currentPriority=$currentPriority; config=$config"
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaControlInteractor.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaControlInteractor.kt
index 130868d..1f339dd 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaControlInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaControlInteractor.kt
@@ -33,6 +33,7 @@
import com.android.systemui.media.controls.data.repository.MediaFilterRepository
import com.android.systemui.media.controls.domain.pipeline.MediaDataProcessor
import com.android.systemui.media.controls.domain.pipeline.getNotificationActions
+import com.android.systemui.media.controls.shared.MediaLogger
import com.android.systemui.media.controls.shared.model.MediaControlModel
import com.android.systemui.media.controls.shared.model.MediaData
import com.android.systemui.media.controls.util.MediaSmartspaceLogger
@@ -59,6 +60,7 @@
private val lockscreenUserManager: NotificationLockscreenUserManager,
private val mediaOutputDialogManager: MediaOutputDialogManager,
private val broadcastDialogController: BroadcastDialogController,
+ private val mediaLogger: MediaLogger,
) {
val mediaControl: Flow<MediaControlModel?> =
@@ -73,7 +75,7 @@
instanceId: InstanceId,
delayMs: Long,
eventId: Int,
- location: Int
+ location: Int,
): Boolean {
logSmartspaceUserEvent(eventId, location)
val dismissed =
@@ -81,7 +83,7 @@
if (!dismissed) {
Log.w(
TAG,
- "Manager failed to dismiss media of instanceId=$instanceId, Token uid=${token?.uid}"
+ "Manager failed to dismiss media of instanceId=$instanceId, Token uid=${token?.uid}",
)
}
return dismissed
@@ -120,20 +122,20 @@
expandable: Expandable,
clickIntent: PendingIntent,
eventId: Int,
- location: Int
+ location: Int,
) {
logSmartspaceUserEvent(eventId, location)
- if (!launchOverLockscreen(clickIntent)) {
+ if (!launchOverLockscreen(expandable, clickIntent)) {
activityStarter.postStartActivityDismissingKeyguard(
clickIntent,
- expandable.activityTransitionController(Cuj.CUJ_SHADE_APP_LAUNCH_FROM_MEDIA_PLAYER)
+ expandable.activityTransitionController(Cuj.CUJ_SHADE_APP_LAUNCH_FROM_MEDIA_PLAYER),
)
}
}
fun startDeviceIntent(deviceIntent: PendingIntent) {
if (deviceIntent.isActivity) {
- if (!launchOverLockscreen(deviceIntent)) {
+ if (!launchOverLockscreen(expandable = null, deviceIntent)) {
activityStarter.postStartActivityDismissingKeyguard(deviceIntent)
}
} else {
@@ -141,20 +143,33 @@
}
}
- private fun launchOverLockscreen(pendingIntent: PendingIntent): Boolean {
+ private fun launchOverLockscreen(
+ expandable: Expandable?,
+ pendingIntent: PendingIntent,
+ ): Boolean {
val showOverLockscreen =
keyguardStateController.isShowing &&
activityIntentHelper.wouldPendingShowOverLockscreen(
pendingIntent,
- lockscreenUserManager.currentUserId
+ lockscreenUserManager.currentUserId,
)
if (showOverLockscreen) {
try {
- val options = BroadcastOptions.makeBasic()
- options.isInteractive = true
- options.pendingIntentBackgroundActivityStartMode =
- ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
- pendingIntent.send(options.toBundle())
+ if (expandable != null) {
+ activityStarter.startPendingIntentMaybeDismissingKeyguard(
+ pendingIntent,
+ /* intentSentUiThreadCallback = */ null,
+ expandable.activityTransitionController(
+ Cuj.CUJ_SHADE_APP_LAUNCH_FROM_MEDIA_PLAYER
+ ),
+ )
+ } else {
+ val options = BroadcastOptions.makeBasic()
+ options.isInteractive = true
+ options.pendingIntentBackgroundActivityStartMode =
+ ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
+ pendingIntent.send(options.toBundle())
+ }
} catch (e: PendingIntent.CanceledException) {
Log.e(TAG, "pending intent of $instanceId was canceled")
}
@@ -166,7 +181,7 @@
fun startMediaOutputDialog(
expandable: Expandable,
packageName: String,
- token: MediaSession.Token? = null
+ token: MediaSession.Token? = null,
) {
mediaOutputDialogManager.createAndShowWithController(
packageName,
@@ -180,7 +195,7 @@
broadcastDialogController.createBroadcastDialogWithController(
broadcastApp,
packageName,
- expandable.dialogTransitionController()
+ expandable.dialogTransitionController(),
)
}
@@ -188,10 +203,14 @@
repository.logSmartspaceCardUserEvent(
eventId,
MediaSmartspaceLogger.getSurface(location),
- instanceId = instanceId
+ instanceId = instanceId,
)
}
+ fun logMediaControlIsBound(artistName: CharSequence, songName: CharSequence) {
+ mediaLogger.logMediaControlIsBound(instanceId, artistName, songName)
+ }
+
private fun Expandable.dialogController(): DialogTransitionAnimator.Controller? {
return dialogTransitionController(
cuj =
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/shared/MediaLogger.kt b/packages/SystemUI/src/com/android/systemui/media/controls/shared/MediaLogger.kt
index 7d20e17..88c47ba 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/shared/MediaLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/shared/MediaLogger.kt
@@ -36,7 +36,7 @@
bool1 = active
str2 = reason
},
- { "add media $str1, active: $bool1, reason: $str2" }
+ { "add media $str1, active: $bool1, reason: $str2" },
)
}
@@ -48,7 +48,7 @@
str1 = instanceId.toString()
str2 = reason
},
- { "removing media $str1, reason: $str2" }
+ { "removing media $str1, reason: $str2" },
)
}
@@ -61,7 +61,7 @@
bool1 = isActive
str2 = reason
},
- { "add recommendation $str1, active $bool1, reason: $str2" }
+ { "add recommendation $str1, active $bool1, reason: $str2" },
)
}
@@ -74,7 +74,7 @@
bool1 = immediately
str2 = reason
},
- { "removing recommendation $str1, immediate=$bool1, reason: $str2" }
+ { "removing recommendation $str1, immediate=$bool1, reason: $str2" },
)
}
@@ -83,7 +83,7 @@
TAG,
LogLevel.DEBUG,
{ str1 = instanceId.toString() },
- { "adding media card $str1 to carousel" }
+ { "adding media card $str1 to carousel" },
)
}
@@ -92,7 +92,7 @@
TAG,
LogLevel.DEBUG,
{ str1 = instanceId.toString() },
- { "removing media card $str1 from carousel" }
+ { "removing media card $str1 from carousel" },
)
}
@@ -101,7 +101,7 @@
TAG,
LogLevel.DEBUG,
{ str1 = key },
- { "adding recommendation card $str1 to carousel" }
+ { "adding recommendation card $str1 to carousel" },
)
}
@@ -110,7 +110,7 @@
TAG,
LogLevel.DEBUG,
{ str1 = key },
- { "removing recommendation card $str1 from carousel" }
+ { "removing recommendation card $str1 from carousel" },
)
}
@@ -119,7 +119,24 @@
TAG,
LogLevel.DEBUG,
{ str1 = key },
- { "duplicate media notification $str1 posted" }
+ { "duplicate media notification $str1 posted" },
+ )
+ }
+
+ fun logMediaControlIsBound(
+ instanceId: InstanceId,
+ artistName: CharSequence,
+ title: CharSequence,
+ ) {
+ buffer.log(
+ TAG,
+ LogLevel.DEBUG,
+ {
+ str1 = instanceId.toString()
+ str2 = artistName.toString()
+ str3 = title.toString()
+ },
+ { "binding media control, instance id= $str1, artist= $str2, title= $str3" },
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/MediaControlViewBinder.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/MediaControlViewBinder.kt
index 6373fed..4055818 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/MediaControlViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/MediaControlViewBinder.kt
@@ -80,16 +80,19 @@
mediaCard.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.STARTED) {
launch {
- viewModel.player.collectLatest { playerViewModel ->
- playerViewModel?.let {
- bindMediaCard(
- viewHolder,
- viewController,
- it,
- falsingManager,
- backgroundDispatcher,
- mainDispatcher,
- )
+ viewModel.player.collectLatest { player ->
+ player?.let {
+ if (viewModel.isNewPlayer(it)) {
+ bindMediaCard(
+ viewHolder,
+ viewController,
+ it,
+ falsingManager,
+ backgroundDispatcher,
+ mainDispatcher,
+ )
+ viewModel.onMediaControlIsBound(it.artistName, it.titleName)
+ }
}
}
}
@@ -143,7 +146,7 @@
viewHolder,
viewModel.outputSwitcher,
viewController,
- falsingManager
+ falsingManager,
)
bindGutsViewModel(viewHolder, viewModel, viewController, falsingManager)
bindActionButtons(viewHolder, viewModel, viewController, falsingManager)
@@ -157,7 +160,7 @@
viewController,
backgroundDispatcher,
mainDispatcher,
- isSongUpdated
+ isSongUpdated,
)
if (viewModel.playTurbulenceNoise) {
@@ -259,12 +262,12 @@
if (buttonView.id == R.id.actionPrev) {
viewController.setUpPrevButtonInfo(
buttonModel.isEnabled,
- buttonModel.notVisibleValue
+ buttonModel.notVisibleValue,
)
} else if (buttonView.id == R.id.actionNext) {
viewController.setUpNextButtonInfo(
buttonModel.isEnabled,
- buttonModel.notVisibleValue
+ buttonModel.notVisibleValue,
)
}
val animHandler = (buttonView.tag ?: AnimationBindHandler()) as AnimationBindHandler
@@ -295,7 +298,7 @@
viewController.collapsedLayout,
visible,
buttonModel.notVisibleValue,
- buttonModel.showInCollapsed
+ buttonModel.showInCollapsed,
)
}
}
@@ -350,7 +353,7 @@
createTouchRippleAnimation(
button,
viewController.colorSchemeTransition,
- multiRippleView
+ multiRippleView,
)
)
@@ -382,12 +385,12 @@
setVisibleAndAlpha(
expandedSet,
R.id.media_explicit_indicator,
- viewModel.isExplicitVisible
+ viewModel.isExplicitVisible,
)
setVisibleAndAlpha(
collapsedSet,
R.id.media_explicit_indicator,
- viewModel.isExplicitVisible
+ viewModel.isExplicitVisible,
)
// refreshState is required here to resize the text views (and prevent ellipsis)
@@ -398,7 +401,7 @@
// something is incorrectly bound, but needs to be run if other elements were
// updated while the enter animation was running
viewController.refreshState()
- }
+ },
)
}
@@ -427,7 +430,7 @@
viewModel.backgroundCover!!,
viewModel.colorScheme,
width,
- height
+ height,
)
} else {
ColorDrawable(Color.TRANSPARENT)
@@ -493,7 +496,7 @@
transitionDrawable: TransitionDrawable,
layer: Int,
targetWidth: Int,
- targetHeight: Int
+ targetHeight: Int,
) {
val drawable = transitionDrawable.getDrawable(layer) ?: return
val width = drawable.intrinsicWidth
@@ -509,7 +512,7 @@
artworkIcon: android.graphics.drawable.Icon,
mutableColorScheme: ColorScheme,
width: Int,
- height: Int
+ height: Int,
): LayerDrawable {
val albumArt = MediaArtworkHelper.getScaledBackground(context, artworkIcon, width, height)
return MediaArtworkHelper.setUpGradientColorOnDrawable(
@@ -517,7 +520,7 @@
context.getDrawable(R.drawable.qs_media_scrim)?.mutate() as GradientDrawable,
mutableColorScheme,
MEDIA_PLAYER_SCRIM_START_ALPHA,
- MEDIA_PLAYER_SCRIM_END_ALPHA
+ MEDIA_PLAYER_SCRIM_END_ALPHA,
)
}
@@ -544,7 +547,7 @@
private fun createTouchRippleAnimation(
button: ImageButton,
colorSchemeTransition: ColorSchemeTransition,
- multiRippleView: MultiRippleView
+ multiRippleView: MultiRippleView,
): RippleAnimation {
val maxSize = (multiRippleView.width * 2).toFloat()
return RippleAnimation(
@@ -562,7 +565,7 @@
baseRingFadeParams = null,
sparkleRingFadeParams = null,
centerFillFadeParams = null,
- shouldDistort = false
+ shouldDistort = false,
)
)
}
@@ -596,7 +599,7 @@
set: ConstraintSet,
resId: Int,
visible: Boolean,
- notVisibleValue: Int
+ notVisibleValue: Int,
) {
set.setVisibility(resId, if (visible) ConstraintSet.VISIBLE else notVisibleValue)
set.setAlpha(resId, if (visible) 1.0f else 0.0f)
@@ -618,7 +621,7 @@
collapsedSet: ConstraintSet,
visible: Boolean,
notVisibleValue: Int,
- showInCollapsed: Boolean
+ showInCollapsed: Boolean,
) {
if (notVisibleValue == ConstraintSet.INVISIBLE) {
// Since time views should appear instead of buttons.
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaActionViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaActionViewModel.kt
index 82099e6..3f22d54 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaActionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaActionViewModel.kt
@@ -32,4 +32,16 @@
val buttonId: Int? = null,
val isEnabled: Boolean,
val onClicked: (Int) -> Unit,
-)
+) {
+ fun contentEquals(other: MediaActionViewModel?): Boolean {
+ return other?.let {
+ contentDescription == other.contentDescription &&
+ isVisibleWhenScrubbing == other.isVisibleWhenScrubbing &&
+ notVisibleValue == other.notVisibleValue &&
+ showInCollapsed == other.showInCollapsed &&
+ rebindId == other.rebindId &&
+ buttonId == other.buttonId &&
+ isEnabled == other.isEnabled
+ } ?: false
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModel.kt
index 64820e0..104d155 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModel.kt
@@ -69,18 +69,30 @@
mediaControl?.let { toViewModel(it) }
}
}
- .distinctUntilChanged()
+ .distinctUntilChanged { old, new ->
+ (new == null && old == null) || new?.contentEquals(old) ?: false
+ }
.flowOn(backgroundDispatcher)
private var isPlaying = false
private var isAnyButtonClicked = false
private var location = -1
+ private var playerViewModel: MediaPlayerViewModel? = null
+
+ fun isNewPlayer(viewModel: MediaPlayerViewModel): Boolean {
+ val contentEquals = playerViewModel?.contentEquals(viewModel) ?: false
+ return (!contentEquals).also { playerViewModel = viewModel }
+ }
+
+ fun onMediaControlIsBound(artistName: CharSequence, titleName: CharSequence) {
+ interactor.logMediaControlIsBound(artistName, titleName)
+ }
private fun onDismissMediaData(
token: Token?,
uid: Int,
packageName: String,
- instanceId: InstanceId
+ instanceId: InstanceId,
) {
logger.logLongPressDismiss(uid, packageName, instanceId)
interactor.removeMediaControl(
@@ -88,7 +100,7 @@
instanceId,
MEDIA_PLAYER_ANIMATION_DELAY,
SMARTSPACE_CARD_DISMISS_EVENT,
- location
+ location,
)
}
@@ -99,7 +111,7 @@
applicationContext,
backgroundDispatcher,
model.artwork,
- TAG
+ TAG,
)
val scheme =
wallpaperColors?.let { ColorScheme(it, true, Style.CONTENT) }
@@ -107,7 +119,7 @@
applicationContext,
model.packageName,
TAG,
- Style.CONTENT
+ Style.CONTENT,
)
?: return null
@@ -131,7 +143,7 @@
R.string.controls_media_playing_item_description,
model.songName,
model.artistName,
- model.appName
+ model.appName,
)
}
},
@@ -157,7 +169,7 @@
expandable,
clickIntent,
SMARTSPACE_CARD_CLICK_EVENT,
- location
+ location,
)
}
},
@@ -177,7 +189,7 @@
}
}
},
- onLocationChanged = { location = it }
+ onLocationChanged = { location = it },
)
}
@@ -191,7 +203,7 @@
device?.name?.let {
TextUtils.equals(
it,
- applicationContext.getString(R.string.broadcasting_description_is_broadcasting)
+ applicationContext.getString(R.string.broadcasting_description_is_broadcasting),
)
} ?: false
val useDisabledAlpha =
@@ -236,19 +248,19 @@
logger.logOpenBroadcastDialog(
model.uid,
model.packageName,
- model.instanceId
+ model.instanceId,
)
interactor.startBroadcastDialog(
expandable,
device?.name.toString(),
- model.packageName
+ model.packageName,
)
} else {
logger.logOpenOutputSwitcher(model.uid, model.packageName, model.instanceId)
interactor.startMediaOutputDialog(
expandable,
model.packageName,
- model.token
+ model.token,
)
}
} else {
@@ -257,10 +269,10 @@
?: interactor.startMediaOutputDialog(
expandable,
model.packageName,
- model.token
+ model.token,
)
}
- }
+ },
)
}
@@ -270,7 +282,7 @@
if (model.isDismissible) {
applicationContext.getString(
R.string.controls_media_close_session,
- model.appName
+ model.appName,
)
} else {
applicationContext.getString(R.string.controls_media_active_session)
@@ -304,7 +316,7 @@
model,
mediaButton.getActionById(buttonId),
buttonId,
- isScrubbingTimeEnabled
+ isScrubbingTimeEnabled,
)
}
}
@@ -319,7 +331,7 @@
model: MediaControlModel,
mediaAction: MediaAction?,
buttonId: Int,
- canShowScrubbingTimeViews: Boolean
+ canShowScrubbingTimeViews: Boolean,
): MediaActionViewModel {
val showInCollapsed = SEMANTIC_ACTIONS_COMPACT.contains(buttonId)
val hideWhenScrubbing = SEMANTIC_ACTIONS_HIDE_WHEN_SCRUBBING.contains(buttonId)
@@ -353,7 +365,7 @@
private fun toNotifActionViewModel(
model: MediaControlModel,
mediaAction: MediaAction,
- index: Int
+ index: Int,
): MediaActionViewModel {
return MediaActionViewModel(
icon = mediaAction.icon,
@@ -375,7 +387,7 @@
uid: Int,
packageName: String,
instanceId: InstanceId,
- action: Runnable
+ action: Runnable,
) {
logger.logTapAction(id, uid, packageName, instanceId)
interactor.logSmartspaceUserEvent(SMARTSPACE_CARD_CLICK_EVENT, location)
@@ -424,7 +436,7 @@
R.id.actionPrev,
R.id.actionNext,
R.id.action0,
- R.id.action1
+ R.id.action1,
)
const val TURBULENCE_NOISE_PLAY_MS_DURATION = 7500L
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaOutputSwitcherViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaOutputSwitcherViewModel.kt
index 9df9bcc..2a47a5a 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaOutputSwitcherViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaOutputSwitcherViewModel.kt
@@ -29,4 +29,15 @@
val alpha: Float,
val isVisible: Boolean,
val onClicked: (Expandable) -> Unit,
-)
+) {
+ fun contentEquals(other: MediaOutputSwitcherViewModel?): Boolean {
+ return (other?.let {
+ isTapEnabled == other.isTapEnabled &&
+ deviceString == other.deviceString &&
+ isCurrentBroadcastApp == other.isCurrentBroadcastApp &&
+ isIntentValid == other.isIntentValid &&
+ alpha == other.alpha &&
+ isVisible == other.isVisible
+ } ?: false)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaPlayerViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaPlayerViewModel.kt
index 96e7fc7..f4b0d6e 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaPlayerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaPlayerViewModel.kt
@@ -43,4 +43,30 @@
val onSeek: () -> Unit,
val onBindSeekbar: (SeekBarViewModel) -> Unit,
val onLocationChanged: (Int) -> Unit,
-)
+) {
+ fun contentEquals(other: MediaPlayerViewModel?): Boolean {
+ return other?.let {
+ other.backgroundCover == backgroundCover &&
+ appIcon == other.appIcon &&
+ useGrayColorFilter == other.useGrayColorFilter &&
+ artistName == other.artistName &&
+ titleName == other.titleName &&
+ isExplicitVisible == other.isExplicitVisible &&
+ shouldAddGradient == other.shouldAddGradient &&
+ canShowTime == other.canShowTime &&
+ playTurbulenceNoise == other.playTurbulenceNoise &&
+ useSemanticActions == other.useSemanticActions &&
+ areActionsEqual(other.actionButtons) &&
+ outputSwitcher.contentEquals(other.outputSwitcher)
+ } ?: false
+ }
+
+ private fun areActionsEqual(other: List<MediaActionViewModel>): Boolean {
+ actionButtons.forEachIndexed { index, mediaActionViewModel ->
+ if (!mediaActionViewModel.contentEquals(other[index])) {
+ return false
+ }
+ }
+ return true
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java
index 4251b81..8351597 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java
@@ -34,6 +34,7 @@
import android.app.Activity;
import android.app.ActivityOptions.LaunchCookie;
import android.app.AlertDialog;
+import android.app.KeyguardManager;
import android.app.StatusBarManager;
import android.app.compat.CompatChanges;
import android.content.Context;
@@ -83,6 +84,7 @@
private final StatusBarManager mStatusBarManager;
private final MediaProjectionMetricsLogger mMediaProjectionMetricsLogger;
private final ScreenCaptureDisabledDialogDelegate mScreenCaptureDisabledDialogDelegate;
+ private final KeyguardManager mKeyguardManager;
private String mPackageName;
private int mUid;
@@ -101,11 +103,13 @@
FeatureFlags featureFlags,
Lazy<ScreenCaptureDevicePolicyResolver> screenCaptureDevicePolicyResolver,
StatusBarManager statusBarManager,
+ KeyguardManager keyguardManager,
MediaProjectionMetricsLogger mediaProjectionMetricsLogger,
ScreenCaptureDisabledDialogDelegate screenCaptureDisabledDialogDelegate) {
mFeatureFlags = featureFlags;
mScreenCaptureDevicePolicyResolver = screenCaptureDevicePolicyResolver;
mStatusBarManager = statusBarManager;
+ mKeyguardManager = keyguardManager;
mMediaProjectionMetricsLogger = mediaProjectionMetricsLogger;
mScreenCaptureDisabledDialogDelegate = screenCaptureDisabledDialogDelegate;
}
@@ -208,7 +212,14 @@
}
setUpDialog(mDialog);
- mDialog.show();
+
+ boolean shouldDismissKeyguard =
+ com.android.systemui.Flags.mediaProjectionDialogBehindLockscreen();
+ if (shouldDismissKeyguard && mKeyguardManager.isDeviceLocked()) {
+ requestDeviceUnlock();
+ } else {
+ mDialog.show();
+ }
if (savedInstanceState == null) {
mMediaProjectionMetricsLogger.notifyPermissionRequestDisplayed(mUid);
@@ -332,6 +343,16 @@
return false;
}
+ private void requestDeviceUnlock() {
+ mKeyguardManager.requestDismissKeyguard(this,
+ new KeyguardManager.KeyguardDismissCallback() {
+ @Override
+ public void onDismissSucceeded() {
+ mDialog.show();
+ }
+ });
+ }
+
private void grantMediaProjectionPermission(
int screenShareMode, boolean hasCastingCapabilities) {
try {
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 526c64c..55943a5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt
@@ -16,6 +16,8 @@
package com.android.systemui.statusbar.dagger
+import android.content.Context
+import com.android.app.viewcapture.ViewCaptureAwareWindowManager
import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.log.LogBuffer
@@ -63,12 +65,18 @@
@Binds abstract fun statusBarInitializer(impl: StatusBarInitializerImpl): StatusBarInitializer
- @Binds
- abstract fun statusBarWindowController(
- impl: StatusBarWindowControllerImpl
- ): StatusBarWindowController
-
companion object {
+
+ @Provides
+ @SysUISingleton
+ fun statusBarWindowController(
+ context: Context?,
+ viewCaptureAwareWindowManager: ViewCaptureAwareWindowManager?,
+ factory: StatusBarWindowControllerImpl.Factory,
+ ): StatusBarWindowController {
+ return factory.create(context, viewCaptureAwareWindowManager)
+ }
+
@Provides
@SysUISingleton
@OngoingCallLog
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowControllerImpl.java
index 1a0327c..1ee7cf3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowControllerImpl.java
@@ -28,7 +28,6 @@
import static com.android.systemui.util.leak.RotationUtils.ROTATION_UPSIDE_DOWN;
import android.content.Context;
-import android.content.res.Resources;
import android.graphics.Insets;
import android.graphics.PixelFormat;
import android.graphics.Rect;
@@ -52,23 +51,23 @@
import com.android.internal.policy.SystemBarUtils;
import com.android.systemui.animation.ActivityTransitionAnimator;
import com.android.systemui.animation.DelegateTransitionAnimatorController;
-import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.fragments.FragmentHostManager;
import com.android.systemui.fragments.FragmentService;
import com.android.systemui.res.R;
import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider;
+import com.android.systemui.statusbar.window.StatusBarWindowModule.InternalWindowViewInflater;
import com.android.systemui.unfold.UnfoldTransitionProgressProvider;
import com.android.systemui.unfold.util.JankMonitorTransitionProgressListener;
-import java.util.Optional;
+import dagger.assisted.Assisted;
+import dagger.assisted.AssistedFactory;
+import dagger.assisted.AssistedInject;
-import javax.inject.Inject;
+import java.util.Optional;
/**
* Encapsulates all logic for the status bar window state management.
*/
-@SysUISingleton
public class StatusBarWindowControllerImpl implements StatusBarWindowController {
private static final String TAG = "StatusBarWindowController";
private static final boolean DEBUG = false;
@@ -90,21 +89,20 @@
private final WindowManager.LayoutParams mLpChanged;
private final Binder mInsetsSourceOwner = new Binder();
- @Inject
+ @AssistedInject
public StatusBarWindowControllerImpl(
- Context context,
- @StatusBarWindowModule.InternalWindowView StatusBarWindowView statusBarWindowView,
- ViewCaptureAwareWindowManager viewCaptureAwareWindowManager,
+ @Assisted Context context,
+ @InternalWindowViewInflater StatusBarWindowViewInflater statusBarWindowViewInflater,
+ @Assisted ViewCaptureAwareWindowManager viewCaptureAwareWindowManager,
IWindowManager iWindowManager,
StatusBarContentInsetsProvider contentInsetsProvider,
FragmentService fragmentService,
- @Main Resources resources,
Optional<UnfoldTransitionProgressProvider> unfoldTransitionProgressProvider) {
mContext = context;
mWindowManager = viewCaptureAwareWindowManager;
mIWindowManager = iWindowManager;
mContentInsetsProvider = contentInsetsProvider;
- mStatusBarWindowView = statusBarWindowView;
+ mStatusBarWindowView = statusBarWindowViewInflater.inflate(context);
mFragmentService = fragmentService;
mLaunchAnimationContainer = mStatusBarWindowView.findViewById(
R.id.status_bar_launch_animation_container);
@@ -354,4 +352,13 @@
mLpChanged.forciblyShownTypes &= ~WindowInsets.Type.statusBars();
}
}
+
+ @AssistedFactory
+ public interface Factory {
+ /** Creates a new instance. */
+ StatusBarWindowControllerImpl create(
+ Context context,
+ ViewCaptureAwareWindowManager viewCaptureAwareWindowManager);
+ }
+
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowModule.kt
index 1c7debc..ebfbac7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowModule.kt
@@ -1,47 +1,36 @@
package com.android.systemui.statusbar.window
-import android.view.LayoutInflater
-import com.android.systemui.res.R
import com.android.systemui.dagger.SysUISingleton
+import dagger.Binds
import dagger.Module
-import dagger.Provides
import javax.inject.Qualifier
/** Module providing dependencies related to the status bar window. */
@Module
abstract class StatusBarWindowModule {
- /**
- * Provides a [StatusBarWindowView].
- *
- * Only [StatusBarWindowController] should inject the view.
- */
- @Module
- companion object {
- @JvmStatic
- @Provides
- @SysUISingleton
- @InternalWindowView
- fun providesStatusBarWindowView(layoutInflater: LayoutInflater): StatusBarWindowView {
- return layoutInflater.inflate(
- R.layout.super_status_bar,
- /* root= */null
- ) as StatusBarWindowView?
- ?: throw IllegalStateException(
- "R.layout.super_status_bar could not be properly inflated"
- )
- }
- }
/**
- * We want [StatusBarWindowView] to be provided to [StatusBarWindowController]'s constructor via
- * dagger so that we can provide a fake window view when testing the controller. However, we wan
- * want *only* the controller to be able to inject the window view.
+ * Binds a [StatusBarWindowViewInflater].
*
- * This protected qualifier annotation achieves this. [StatusBarWindowView] can only be injected
- * if it's annotated with [InternalWindowView], and only classes inside this [statusbar.window]
- * package can access the annotation.
+ * Only [StatusBarWindowControllerImpl] should inject it.
+ */
+ @Binds
+ @SysUISingleton
+ @InternalWindowViewInflater
+ abstract fun providesStatusBarWindowViewInflater(
+ inflaterImpl: StatusBarWindowViewInflaterImpl
+ ): StatusBarWindowViewInflater
+
+ /**
+ * We want [StatusBarWindowViewInflater] to be provided to [StatusBarWindowControllerImpl]'s
+ * constructor via dagger so that we can provide a fake window view when testing the controller.
+ * However, we wan want *only* the controller to be able to inject the window view.
+ *
+ * This protected qualifier annotation achieves this. [StatusBarWindowViewInflater] can only be
+ * injected if it's annotated with [InternalWindowViewInflater], and only classes inside this
+ * [statusbar.window] package can access the annotation.
*/
@Retention(AnnotationRetention.BINARY)
@Qualifier
- protected annotation class InternalWindowView
+ protected annotation class InternalWindowViewInflater
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowViewInflater.kt b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowViewInflater.kt
new file mode 100644
index 0000000..f030a4a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowViewInflater.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.window
+
+import android.content.Context
+import android.view.LayoutInflater
+import com.android.systemui.res.R
+import javax.inject.Inject
+
+/**
+ * Inflates a [StatusBarWindowView]. Exists so that it can be injected into
+ * [StatusBarWindowControllerImpl] and be swapped for a fake implementation in tests.
+ */
+interface StatusBarWindowViewInflater {
+ fun inflate(context: Context): StatusBarWindowView
+}
+
+class StatusBarWindowViewInflaterImpl @Inject constructor() : StatusBarWindowViewInflater {
+
+ override fun inflate(context: Context): StatusBarWindowView {
+ val layoutInflater = LayoutInflater.from(context)
+ return layoutInflater.inflate(R.layout.super_status_bar, /* root= */ null)
+ as StatusBarWindowView?
+ ?: throw IllegalStateException(
+ "R.layout.super_status_bar could not be properly inflated"
+ )
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/android/hardware/input/FakeInputManager.kt b/packages/SystemUI/tests/utils/src/android/hardware/input/FakeInputManager.kt
index ee36cad..de4bbec 100644
--- a/packages/SystemUI/tests/utils/src/android/hardware/input/FakeInputManager.kt
+++ b/packages/SystemUI/tests/utils/src/android/hardware/input/FakeInputManager.kt
@@ -84,7 +84,7 @@
if (devices.containsKey(deviceId)) {
return
}
- addPhysicalKeyboard(deviceId, enabled)
+ addPhysicalKeyboard(deviceId, enabled = enabled)
}
fun registerInputDeviceListener(listener: InputDeviceListener) {
@@ -92,9 +92,15 @@
inputDeviceListener = listener
}
- fun addPhysicalKeyboard(id: Int, enabled: Boolean = true) {
+ fun addPhysicalKeyboard(
+ id: Int,
+ vendorId: Int = 0,
+ productId: Int = 0,
+ isFullKeyboard: Boolean = true,
+ enabled: Boolean = true
+ ) {
check(id > 0) { "Physical keyboard ids have to be > 0" }
- addKeyboard(id, enabled)
+ addKeyboard(id, vendorId, productId, isFullKeyboard, enabled)
}
fun removeKeysFromKeyboard(deviceId: Int, vararg keyCodes: Int) {
@@ -102,20 +108,38 @@
supportedKeyCodesByDeviceId[deviceId]!!.removeAll(keyCodes.asList())
}
- private fun addKeyboard(id: Int, enabled: Boolean = true) {
- devices[id] =
+ private fun addKeyboard(
+ id: Int,
+ vendorId: Int = 0,
+ productId: Int = 0,
+ isFullKeyboard: Boolean = true,
+ enabled: Boolean = true
+ ) {
+ val keyboardType =
+ if (isFullKeyboard) InputDevice.KEYBOARD_TYPE_ALPHABETIC
+ else InputDevice.KEYBOARD_TYPE_NON_ALPHABETIC
+ // VendorId and productId are set to 0 if not specified, which is the same as the default
+ // values used in InputDevice.Builder
+ val builder =
InputDevice.Builder()
.setId(id)
- .setKeyboardType(InputDevice.KEYBOARD_TYPE_ALPHABETIC)
+ .setVendorId(vendorId)
+ .setProductId(productId)
+ .setKeyboardType(keyboardType)
.setSources(InputDevice.SOURCE_KEYBOARD)
.setEnabled(enabled)
.setKeyCharacterMap(keyCharacterMap)
- .build()
+ devices[id] = builder.build()
+ inputDeviceListener?.onInputDeviceAdded(id)
supportedKeyCodesByDeviceId[id] = allKeyCodes.toMutableSet()
}
- fun addDevice(id: Int, sources: Int) {
- devices[id] = InputDevice.Builder().setId(id).setSources(sources).build()
+ fun addDevice(id: Int, sources: Int, isNotFound: Boolean = false) {
+ // there's not way of differentiate device connection vs registry in current implementation.
+ // If the device isNotFound, it means that we connect an unregistered device.
+ if (!isNotFound) {
+ devices[id] = InputDevice.Builder().setId(id).setSources(sources).build()
+ }
inputDeviceListener?.onInputDeviceAdded(id)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaControlInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaControlInteractorKosmos.kt
index 6e650a3..77afa79 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaControlInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaControlInteractorKosmos.kt
@@ -21,6 +21,7 @@
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.media.controls.data.repository.mediaFilterRepository
import com.android.systemui.media.controls.domain.pipeline.mediaDataProcessor
+import com.android.systemui.media.controls.shared.mediaLogger
import com.android.systemui.media.controls.util.mediaInstanceId
import com.android.systemui.media.mediaOutputDialogManager
import com.android.systemui.plugins.activityStarter
@@ -39,5 +40,6 @@
lockscreenUserManager = notificationLockscreenUserManager,
mediaOutputDialogManager = mediaOutputDialogManager,
broadcastDialogController = mockBroadcastDialogController,
+ mediaLogger = mediaLogger,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/interactor/factory/MediaControlInteractorFactoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/interactor/factory/MediaControlInteractorFactoryKosmos.kt
index e490b75..9ea660f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/interactor/factory/MediaControlInteractorFactoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/interactor/factory/MediaControlInteractorFactoryKosmos.kt
@@ -23,6 +23,7 @@
import com.android.systemui.media.controls.data.repository.mediaFilterRepository
import com.android.systemui.media.controls.domain.pipeline.interactor.MediaControlInteractor
import com.android.systemui.media.controls.domain.pipeline.mediaDataProcessor
+import com.android.systemui.media.controls.shared.mediaLogger
import com.android.systemui.media.mediaOutputDialogManager
import com.android.systemui.plugins.activityStarter
import com.android.systemui.statusbar.notificationLockscreenUserManager
@@ -42,6 +43,7 @@
lockscreenUserManager = notificationLockscreenUserManager,
mediaOutputDialogManager = mediaOutputDialogManager,
broadcastDialogController = mockBroadcastDialogController,
+ mediaLogger = mediaLogger,
)
}
}
diff --git a/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncAdapter.java b/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncAdapter.java
index 8c6f50e..e29b6e4 100644
--- a/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncAdapter.java
+++ b/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncAdapter.java
@@ -145,13 +145,25 @@
ArrayMap<String, ArraySet<String>> removedFunctionsDiffMap =
getRemovedFunctionsDiffMap(staticPackageToFunctionMap, runtimePackageToFunctionMap);
- Set<AppSearchSchema> appRuntimeMetadataSchemas =
- getAllRuntimeMetadataSchemas(staticPackageToFunctionMap.keySet());
- appRuntimeMetadataSchemas.add(
- AppFunctionRuntimeMetadata.createParentAppFunctionRuntimeSchema());
+ if (!staticPackageToFunctionMap.keySet().equals(runtimePackageToFunctionMap.keySet())) {
+ // Drop removed packages from removedFunctionsDiffMap, as setSchema() deletes them
+ ArraySet<String> removedPackages =
+ getRemovedPackages(
+ staticPackageToFunctionMap.keySet(), removedFunctionsDiffMap.keySet());
+ for (String packageName : removedPackages) {
+ removedFunctionsDiffMap.remove(packageName);
+ }
+ Set<AppSearchSchema> appRuntimeMetadataSchemas =
+ getAllRuntimeMetadataSchemas(staticPackageToFunctionMap.keySet());
+ appRuntimeMetadataSchemas.add(
+ AppFunctionRuntimeMetadata.createParentAppFunctionRuntimeSchema());
+ SetSchemaRequest addSetSchemaRequest =
+ buildSetSchemaRequestForRuntimeMetadataSchemas(
+ mPackageManager, appRuntimeMetadataSchemas);
+ Objects.requireNonNull(
+ runtimeMetadataSearchSession.setSchema(addSetSchemaRequest).get());
+ }
- // Operation order matters here. i.e. remove -> setSchema -> add. Otherwise we would
- // encounter an error trying to delete a document with no existing schema.
if (!removedFunctionsDiffMap.isEmpty()) {
RemoveByDocumentIdRequest removeByDocumentIdRequest =
buildRemoveRuntimeMetadataRequest(removedFunctionsDiffMap);
@@ -164,12 +176,6 @@
}
if (!addedFunctionsDiffMap.isEmpty()) {
- // TODO(b/357551503): only set schema on package diff
- SetSchemaRequest addSetSchemaRequest =
- buildSetSchemaRequestForRuntimeMetadataSchemas(
- mPackageManager, appRuntimeMetadataSchemas);
- Objects.requireNonNull(
- runtimeMetadataSearchSession.setSchema(addSetSchemaRequest).get());
PutDocumentsRequest putDocumentsRequest =
buildPutRuntimeMetadataRequest(addedFunctionsDiffMap);
AppSearchBatchResult<String, Void> putDocumentBatchResult =
@@ -276,6 +282,30 @@
}
/**
+ * This method returns a set of packages that are in the removed function packages but not in
+ * the all existing static packages.
+ *
+ * @param allExistingStaticPackages A set of all existing static metadata packages.
+ * @param removedFunctionPackages A set of all removed function packages.
+ * @return A set of packages that are in the removed function packages but not in the all
+ * existing static packages.
+ */
+ @NonNull
+ private static ArraySet<String> getRemovedPackages(
+ @NonNull Set<String> allExistingStaticPackages,
+ @NonNull Set<String> removedFunctionPackages) {
+ ArraySet<String> removedPackages = new ArraySet<>();
+
+ for (String packageName : removedFunctionPackages) {
+ if (!allExistingStaticPackages.contains(packageName)) {
+ removedPackages.add(packageName);
+ }
+ }
+
+ return removedPackages;
+ }
+
+ /**
* This method returns a map of package names to a set of function ids that are in the static
* metadata but not in the runtime metadata.
*
diff --git a/services/core/java/com/android/server/BatteryService.java b/services/core/java/com/android/server/BatteryService.java
index 6657c1c..59dea09 100644
--- a/services/core/java/com/android/server/BatteryService.java
+++ b/services/core/java/com/android/server/BatteryService.java
@@ -160,6 +160,7 @@
private int mLastChargeCounter;
private int mLastBatteryCycleCount;
private int mLastChargingState;
+ private int mLastBatteryCapacityLevel;
/**
* The last seen charging policy. This requires the
* {@link android.Manifest.permission#BATTERY_STATS} permission and should therefore not be
@@ -609,7 +610,8 @@
|| mHealthInfo.batteryChargeCounterUah != mLastChargeCounter
|| mInvalidCharger != mLastInvalidCharger
|| mHealthInfo.batteryCycleCount != mLastBatteryCycleCount
- || mHealthInfo.chargingState != mLastChargingState)) {
+ || mHealthInfo.chargingState != mLastChargingState
+ || mHealthInfo.batteryCapacityLevel != mLastBatteryCapacityLevel)) {
if (mPlugType != mLastPlugType) {
if (mLastPlugType == BATTERY_PLUGGED_NONE) {
@@ -829,6 +831,7 @@
mLastInvalidCharger = mInvalidCharger;
mLastBatteryCycleCount = mHealthInfo.batteryCycleCount;
mLastChargingState = mHealthInfo.chargingState;
+ mLastBatteryCapacityLevel = mHealthInfo.batteryCapacityLevel;
}
}
@@ -862,6 +865,7 @@
intent.putExtra(BatteryManager.EXTRA_CHARGE_COUNTER, mHealthInfo.batteryChargeCounterUah);
intent.putExtra(BatteryManager.EXTRA_CYCLE_COUNT, mHealthInfo.batteryCycleCount);
intent.putExtra(BatteryManager.EXTRA_CHARGING_STATUS, mHealthInfo.chargingState);
+ intent.putExtra(BatteryManager.EXTRA_CAPACITY_LEVEL, mHealthInfo.batteryCapacityLevel);
if (DEBUG) {
Slog.d(TAG, "Sending ACTION_BATTERY_CHANGED. scale:" + BATTERY_SCALE
+ ", info:" + mHealthInfo.toString());
@@ -964,6 +968,7 @@
event.putLong(BatteryManager.EXTRA_EVENT_TIMESTAMP, now);
event.putInt(BatteryManager.EXTRA_CYCLE_COUNT, mHealthInfo.batteryCycleCount);
event.putInt(BatteryManager.EXTRA_CHARGING_STATUS, mHealthInfo.chargingState);
+ event.putInt(BatteryManager.EXTRA_CAPACITY_LEVEL, mHealthInfo.batteryCapacityLevel);
boolean queueWasEmpty = mBatteryLevelsEventQueue.isEmpty();
mBatteryLevelsEventQueue.add(event);
@@ -1401,6 +1406,7 @@
pw.println(" technology: " + mHealthInfo.batteryTechnology);
pw.println(" Charging state: " + mHealthInfo.chargingState);
pw.println(" Charging policy: " + mHealthInfo.chargingPolicy);
+ pw.println(" Capacity level: " + mHealthInfo.batteryCapacityLevel);
} else {
Shell shell = new Shell();
shell.exec(mBinderService, null, fd, null, args, null, new ResultReceiver(null));
diff --git a/services/core/java/com/android/server/UiModeManagerService.java b/services/core/java/com/android/server/UiModeManagerService.java
index 1c13ad5..f32031de 100644
--- a/services/core/java/com/android/server/UiModeManagerService.java
+++ b/services/core/java/com/android/server/UiModeManagerService.java
@@ -32,11 +32,11 @@
import static android.app.UiModeManager.PROJECTION_TYPE_NONE;
import static android.os.UserHandle.USER_SYSTEM;
import static android.os.UserHandle.getCallingUserId;
-import static android.os.UserManager.isVisibleBackgroundUsersEnabled;
import static android.provider.Settings.Secure.CONTRAST_LEVEL;
import static android.util.TimeUtils.isTimeBetween;
import static com.android.internal.util.FunctionalUtils.ignoreRemoteException;
+import static com.android.server.pm.UserManagerService.enforceCurrentUserIfVisibleBackgroundEnabled;
import android.annotation.IntRange;
import android.annotation.NonNull;
@@ -100,7 +100,6 @@
import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
import com.android.internal.notification.SystemNotificationChannels;
import com.android.internal.util.DumpUtils;
-import com.android.server.pm.UserManagerService;
import com.android.server.twilight.TwilightListener;
import com.android.server.twilight.TwilightManager;
import com.android.server.twilight.TwilightState;
@@ -850,7 +849,7 @@
}
final int user = UserHandle.getCallingUserId();
- enforceValidCallingUser(user);
+ enforceCurrentUserIfVisibleBackgroundEnabled(user);
final long ident = Binder.clearCallingIdentity();
try {
@@ -914,7 +913,7 @@
@AttentionModeThemeOverlayType int attentionModeThemeOverlayType) {
setAttentionModeThemeOverlay_enforcePermission();
- enforceValidCallingUser(UserHandle.getCallingUserId());
+ enforceCurrentUserIfVisibleBackgroundEnabled(UserHandle.getCallingUserId());
synchronized (mLock) {
if (mAttentionModeThemeOverlay != attentionModeThemeOverlayType) {
@@ -1005,7 +1004,7 @@
return false;
}
final int user = Binder.getCallingUserHandle().getIdentifier();
- enforceValidCallingUser(user);
+ enforceCurrentUserIfVisibleBackgroundEnabled(user);
if (user != mCurrentUser && getContext().checkCallingOrSelfPermission(
android.Manifest.permission.INTERACT_ACROSS_USERS)
@@ -1064,7 +1063,7 @@
return;
}
final int user = UserHandle.getCallingUserId();
- enforceValidCallingUser(user);
+ enforceCurrentUserIfVisibleBackgroundEnabled(user);
final long ident = Binder.clearCallingIdentity();
try {
@@ -1094,7 +1093,7 @@
return;
}
final int user = UserHandle.getCallingUserId();
- enforceValidCallingUser(user);
+ enforceCurrentUserIfVisibleBackgroundEnabled(user);
final long ident = Binder.clearCallingIdentity();
try {
@@ -1116,7 +1115,7 @@
assertLegit(callingPackage);
assertSingleProjectionType(projectionType);
enforceProjectionTypePermissions(projectionType);
- enforceValidCallingUser(getCallingUserId());
+ enforceCurrentUserIfVisibleBackgroundEnabled(getCallingUserId());
synchronized (mLock) {
if (mProjectionHolders == null) {
@@ -1162,7 +1161,7 @@
assertLegit(callingPackage);
assertSingleProjectionType(projectionType);
enforceProjectionTypePermissions(projectionType);
- enforceValidCallingUser(getCallingUserId());
+ enforceCurrentUserIfVisibleBackgroundEnabled(getCallingUserId());
return releaseProjectionUnchecked(projectionType, callingPackage);
}
@@ -1204,7 +1203,7 @@
return;
}
- enforceValidCallingUser(getCallingUserId());
+ enforceCurrentUserIfVisibleBackgroundEnabled(getCallingUserId());
synchronized (mLock) {
if (mProjectionListeners == null) {
@@ -1253,32 +1252,6 @@
}
};
- // This method validates whether calling user is valid in visible background users
- // feature. Valid user is the current user or the system or in the same profile group as
- // the current user.
- private void enforceValidCallingUser(int userId) {
- if (!isVisibleBackgroundUsersEnabled()) {
- return;
- }
- if (LOG) {
- Slog.d(TAG, "enforceValidCallingUser: userId=" + userId
- + " isSystemUser=" + (userId == USER_SYSTEM) + " current user=" + mCurrentUser
- + " callingPid=" + Binder.getCallingPid()
- + " callingUid=" + mInjector.getCallingUid());
- }
- long ident = Binder.clearCallingIdentity();
- try {
- if (userId != USER_SYSTEM && userId != mCurrentUser
- && !UserManagerService.getInstance().isSameProfileGroup(userId, mCurrentUser)) {
- throw new SecurityException(
- "Calling user is not valid for level-1 compatibility in MUMD. "
- + "callingUserId=" + userId + " currentUserId=" + mCurrentUser);
- }
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
- }
-
private void enforceProjectionTypePermissions(@UiModeManager.ProjectionType int p) {
if ((p & PROJECTION_TYPE_AUTOMOTIVE) != 0) {
getContext().enforceCallingPermission(
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 54a7410..871c320 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -714,12 +714,14 @@
/**
* Map userId to its companion app uids.
*/
+ @GuardedBy("mCompanionAppUidsMap")
private final Map<Integer, Set<Integer>> mCompanionAppUidsMap = new ArrayMap<>();
/**
* The profile owner UIDs.
*/
- private ArraySet<Integer> mProfileOwnerUids = null;
+ @GuardedBy("mProfileOwnerUids")
+ private final ArraySet<Integer> mProfileOwnerUids = new ArraySet<>();
final UserController mUserController;
@VisibleForTesting
@@ -17535,32 +17537,35 @@
@Override
public void setProfileOwnerUid(ArraySet<Integer> profileOwnerUids) {
- synchronized (ActivityManagerService.this) {
- mProfileOwnerUids = profileOwnerUids;
+ synchronized (mProfileOwnerUids) {
+ mProfileOwnerUids.clear();
+ mProfileOwnerUids.addAll(profileOwnerUids);
}
}
@Override
public boolean isProfileOwner(int uid) {
- synchronized (ActivityManagerService.this) {
- return mProfileOwnerUids != null && mProfileOwnerUids.indexOf(uid) >= 0;
+ synchronized (mProfileOwnerUids) {
+ return mProfileOwnerUids.indexOf(uid) >= 0;
}
}
@Override
public void setCompanionAppUids(int userId, Set<Integer> companionAppUids) {
- synchronized (ActivityManagerService.this) {
+ synchronized (mCompanionAppUidsMap) {
mCompanionAppUidsMap.put(userId, companionAppUids);
}
}
@Override
public boolean isAssociatedCompanionApp(int userId, int uid) {
- final Set<Integer> allUids = mCompanionAppUidsMap.get(userId);
- if (allUids == null) {
- return false;
+ synchronized (mCompanionAppUidsMap) {
+ final Set<Integer> allUids = mCompanionAppUidsMap.get(userId);
+ if (allUids == null) {
+ return false;
+ }
+ return allUids.contains(uid);
}
- return allUids.contains(uid);
}
@Override
diff --git a/services/core/java/com/android/server/pm/TEST_MAPPING b/services/core/java/com/android/server/pm/TEST_MAPPING
index c75622c..21a6df2 100644
--- a/services/core/java/com/android/server/pm/TEST_MAPPING
+++ b/services/core/java/com/android/server/pm/TEST_MAPPING
@@ -208,6 +208,22 @@
"name": "CtsUpdateOwnershipEnforcementTestCases"
},
{
+ "name": "CtsPackageInstallerCUJDeviceAdminTestCases",
+ "file_patterns": [
+ "core/java/.*Install.*",
+ "services/core/.*Install.*",
+ "services/core/java/com/android/server/pm/.*"
+ ],
+ "options":[
+ {
+ "exclude-annotation":"androidx.test.filters.FlakyTest"
+ },
+ {
+ "exclude-annotation":"org.junit.Ignore"
+ }
+ ]
+ },
+ {
"name": "CtsPackageInstallerCUJInstallationTestCases",
"file_patterns": [
"core/java/.*Install.*",
@@ -224,6 +240,22 @@
]
},
{
+ "name": "CtsPackageInstallerCUJMultiUsersTestCases",
+ "file_patterns": [
+ "core/java/.*Install.*",
+ "services/core/.*Install.*",
+ "services/core/java/com/android/server/pm/.*"
+ ],
+ "options":[
+ {
+ "exclude-annotation":"androidx.test.filters.FlakyTest"
+ },
+ {
+ "exclude-annotation":"org.junit.Ignore"
+ }
+ ]
+ },
+ {
"name": "CtsPackageInstallerCUJUninstallationTestCases",
"file_patterns": [
"core/java/.*Install.*",
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 3d5b273..6009b4a 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -6218,7 +6218,8 @@
public void onProcessRemoved(String name, int uid) {
synchronized (mGlobalLockWithoutBoost) {
final WindowProcessController proc = mProcessNames.remove(name, uid);
- if (proc != null && !mStartingProcessActivities.isEmpty()) {
+ if (proc != null && !proc.mHasEverAttached
+ && !mStartingProcessActivities.isEmpty()) {
// Use a copy in case finishIfPossible changes the list indirectly.
final ArrayList<ActivityRecord> activities =
new ArrayList<>(mStartingProcessActivities);
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 8f5612c..84072e2 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -1841,6 +1841,7 @@
}
boolean attachApplication(WindowProcessController app) throws RemoteException {
+ app.mHasEverAttached = true;
final ArrayList<ActivityRecord> activities = mService.mStartingProcessActivities;
RemoteException remoteException = null;
boolean hasActivityStarted = false;
diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index 30d6f0a..32fe303 100644
--- a/services/core/java/com/android/server/wm/WindowProcessController.java
+++ b/services/core/java/com/android/server/wm/WindowProcessController.java
@@ -204,6 +204,9 @@
// Set to true when process was launched with a wrapper attached
private volatile boolean mUsingWrapper;
+ /** Whether this process has ever completed ActivityThread#handleBindApplication. */
+ boolean mHasEverAttached;
+
/** Non-null if this process may have a window. */
@Nullable
Session mWindowSession;
diff --git a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
index 957b5e0..ae0c6e5 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
@@ -331,6 +331,7 @@
final WindowProcessController proc = mSystemServicesTestRule.addProcess(
activity.packageName, activity.processName,
6789 /* pid */, activity.info.applicationInfo.uid);
+ assertFalse(proc.mHasEverAttached);
try {
mRootWindowContainer.attachApplication(proc);
verify(mSupervisor).realStartActivityLocked(eq(topActivity), eq(proc),
@@ -338,6 +339,15 @@
} catch (RemoteException e) {
e.rethrowAsRuntimeException();
}
+
+ // Verify that onProcessRemoved won't clear the launching activities if an attached process
+ // is died. Because in real case, it should be handled from WindowProcessController's
+ // and ActivityRecord's handleAppDied to decide whether to remove the activities.
+ assertTrue(proc.mHasEverAttached);
+ assertTrue(mAtm.mStartingProcessActivities.isEmpty());
+ mAtm.mStartingProcessActivities.add(activity);
+ mAtm.mInternal.onProcessRemoved(proc.mName, proc.mUid);
+ assertFalse(mAtm.mStartingProcessActivities.isEmpty());
}
/**
diff --git a/telephony/java/android/telephony/satellite/ISatelliteModemStateCallback.aidl b/telephony/java/android/telephony/satellite/ISatelliteModemStateCallback.aidl
index 66a20ae..50e3a0e 100644
--- a/telephony/java/android/telephony/satellite/ISatelliteModemStateCallback.aidl
+++ b/telephony/java/android/telephony/satellite/ISatelliteModemStateCallback.aidl
@@ -34,4 +34,12 @@
* @param isEmergency True means satellite enabled for emergency mode, false otherwise.
*/
void onEmergencyModeChanged(in boolean isEmergency);
+
+ /**
+ * Indicates that the satellite registration failed with following failure code
+ *
+ * @param causeCode the primary failure cause code of the procedure.
+ * For LTE (EMM), cause codes are TS 24.301 Sec 9.9.3.9
+ */
+ void onRegistrationFailure(in int causeCode);
}
diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java
index 90dae3b..4eefaac 100644
--- a/telephony/java/android/telephony/satellite/SatelliteManager.java
+++ b/telephony/java/android/telephony/satellite/SatelliteManager.java
@@ -19,6 +19,7 @@
import android.Manifest;
import android.annotation.CallbackExecutor;
import android.annotation.FlaggedApi;
+import android.annotation.Hide;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -1579,6 +1580,13 @@
executor.execute(() -> Binder.withCleanCallingIdentity(() ->
callback.onEmergencyModeChanged(isEmergency)));
}
+
+ @Hide
+ @Override
+ public void onRegistrationFailure(int causeCode) {
+ executor.execute(() -> Binder.withCleanCallingIdentity(() ->
+ callback.onRegistrationFailure(causeCode)));
+ }
};
sSatelliteModemStateCallbackMap.put(callback, internalCallback);
return telephony.registerForSatelliteModemStateChanged(internalCallback);
diff --git a/telephony/java/android/telephony/satellite/SatelliteModemStateCallback.java b/telephony/java/android/telephony/satellite/SatelliteModemStateCallback.java
index 423a785..13af469 100644
--- a/telephony/java/android/telephony/satellite/SatelliteModemStateCallback.java
+++ b/telephony/java/android/telephony/satellite/SatelliteModemStateCallback.java
@@ -45,4 +45,13 @@
*/
@FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
default void onEmergencyModeChanged(boolean isEmergency) {};
+
+ /**
+ * Indicates that the satellite registration failed with following failure code
+ *
+ * @param causeCode the primary failure cause code of the procedure.
+ * For LTE (EMM), cause codes are TS 24.301 Sec 9.9.3.9
+ * @hide
+ */
+ default void onRegistrationFailure(int causeCode) {};
}
diff --git a/tests/Tracing/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java b/tests/Tracing/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java
index c882b4e..cfb2645 100644
--- a/tests/Tracing/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java
+++ b/tests/Tracing/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java
@@ -16,6 +16,8 @@
package com.android.internal.protolog;
+import static android.tools.traces.Utils.executeShellCommand;
+
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
@@ -48,6 +50,7 @@
import com.android.internal.protolog.common.LogLevel;
import com.google.common.truth.Truth;
+import com.google.protobuf.InvalidProtocolBufferException;
import org.junit.After;
import org.junit.Before;
@@ -57,12 +60,14 @@
import org.junit.runners.JUnit4;
import org.mockito.Mockito;
+import perfetto.protos.PerfettoConfig.TracingServiceState;
import perfetto.protos.Protolog;
import perfetto.protos.ProtologCommon;
import java.io.File;
import java.io.IOException;
import java.util.List;
+import java.util.Optional;
import java.util.Random;
import java.util.concurrent.atomic.AtomicInteger;
@@ -178,6 +183,8 @@
viewerConfigInputStreamProvider, sReader, () -> sCacheUpdater.run(),
TestProtoLogGroup.values(), dataSourceBuilder, sProtoLogConfigurationService);
}
+
+ waitDataSourceIsAvailable();
}
@Before
@@ -863,6 +870,54 @@
.isEqualTo("This message should also be logged 567");
}
+ private static void waitDataSourceIsAvailable() {
+ final int timeoutMs = 10000;
+ final int busyWaitIntervalMs = 100;
+
+ int elapsedMs = 0;
+
+ while (!isDataSourceAvailable()) {
+ SystemClock.sleep(busyWaitIntervalMs);
+ elapsedMs += busyWaitIntervalMs;
+ if (elapsedMs >= timeoutMs) {
+ throw new RuntimeException("Data source didn't become available."
+ + " Waited for: " + timeoutMs + " ms");
+ }
+ }
+ }
+
+ private static boolean isDataSourceAvailable() {
+ byte[] proto = executeShellCommand("perfetto --query-raw");
+
+ try {
+ TracingServiceState state = TracingServiceState.parseFrom(proto);
+
+ Optional<Integer> producerId = Optional.empty();
+
+ for (TracingServiceState.Producer producer : state.getProducersList()) {
+ if (producer.getPid() == android.os.Process.myPid()) {
+ producerId = Optional.of(producer.getId());
+ break;
+ }
+ }
+
+ if (producerId.isEmpty()) {
+ return false;
+ }
+
+ for (TracingServiceState.DataSource ds : state.getDataSourcesList()) {
+ if (ds.getDsDescriptor().getName().equals(TEST_PROTOLOG_DATASOURCE_NAME)
+ && ds.getProducerId() == producerId.get()) {
+ return true;
+ }
+ }
+ } catch (InvalidProtocolBufferException e) {
+ throw new RuntimeException(e);
+ }
+
+ return false;
+ }
+
private enum TestProtoLogGroup implements IProtoLogGroup {
TEST_GROUP(true, true, false, "TEST_TAG");
diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java b/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java
index f68ae2c..fc4a909 100644
--- a/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java
+++ b/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java
@@ -173,6 +173,10 @@
}
}
}
+
+ Executor getExecutor() {
+ return mExecutor;
+ }
}
private ISharedConnectivityService mService;
@@ -188,7 +192,7 @@
private final String mServicePackageName;
private final String mIntentAction;
private ServiceConnection mServiceConnection;
- private UserManager mUserManager;
+ private final UserManager mUserManager;
/**
* Creates a new instance of {@link SharedConnectivityManager}.
@@ -316,15 +320,19 @@
private void registerCallbackInternal(SharedConnectivityClientCallback callback,
SharedConnectivityCallbackProxy proxy) {
- try {
- mService.registerCallback(proxy);
- synchronized (mProxyDataLock) {
- mProxyMap.put(callback, proxy);
- }
- } catch (RemoteException e) {
- Log.e(TAG, "Exception in registerCallback", e);
- callback.onRegisterCallbackFailed(e);
- }
+ proxy.getExecutor().execute(
+ () -> {
+ try {
+ mService.registerCallback(proxy);
+ synchronized (mProxyDataLock) {
+ mProxyMap.put(callback, proxy);
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "Exception in registerCallback", e);
+ callback.onRegisterCallbackFailed(e);
+ }
+ }
+ );
}
/**