summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--AconfigFlags.bp15
-rw-r--r--core/api/current.txt15
-rw-r--r--core/java/android/app/ActivityOptions.java7
-rw-r--r--core/java/android/app/appfunctions/AppFunctionService.java54
-rw-r--r--core/java/android/app/appfunctions/IAppFunctionService.aidl2
-rw-r--r--core/java/android/app/assist/AssistStructure.java11
-rw-r--r--core/java/android/app/assist/flags.aconfig13
-rw-r--r--core/java/android/os/OWNERS7
-rw-r--r--core/java/android/os/StatsBootstrapAtomValue.aidl1
-rw-r--r--core/java/android/view/accessibility/AccessibilityEvent.java14
-rw-r--r--core/java/android/view/accessibility/AccessibilityNodeInfo.java122
-rw-r--r--core/java/android/view/accessibility/flags/accessibility_flags.aconfig9
-rw-r--r--core/jni/android_view_WindowManagerGlobal.cpp6
-rw-r--r--core/jni/platform/host/HostRuntime.cpp2
-rw-r--r--core/tests/coretests/src/android/app/assist/AssistStructureTest.java14
-rw-r--r--core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt6
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt55
-rw-r--r--libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/OpenTransparentActivityTest.kt5
-rw-r--r--libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/QuickSwitchLauncherToLetterboxAppTest.kt4
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt75
-rw-r--r--libs/appfunctions/api/current.txt3
-rw-r--r--libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionService.java50
-rw-r--r--libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionResponse.java13
-rw-r--r--packages/SettingsLib/SettingsTheme/res/layout-v33/settingslib_preference.xml48
-rw-r--r--packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt4
-rw-r--r--packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/dialog/DialogMainPageProvider.kt99
-rw-r--r--packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/CheckBoxPreferencePageProvider.kt99
-rw-r--r--packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferenceMainPageProvider.kt2
-rw-r--r--packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/TwoTargetButtonPreferencePageProvider.kt80
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt2
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialogWithIcon.kt8
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/BaseLayout.kt5
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/MainSwitchPreference.kt46
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/ZeroStatePreference.kt2
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java2
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java17
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java124
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastAssistantCallbackExt.kt7
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/IDeviceSettingsConfigProviderService.aidl2
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingServiceConnection.kt55
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/notification/data/repository/FakeZenModeRepository.kt4
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothEventManagerTest.java145
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CsipDeviceManagerTest.java36
-rw-r--r--packages/Shell/AndroidManifest.xml6
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayCallbackControllerTest.kt14
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt3
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt117
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModelTest.kt174
-rw-r--r--packages/SystemUI/res/values/strings.xml5
-rw-r--r--packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractor.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayCallbackController.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java36
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java14
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractor.kt22
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt115
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModelKosmos.kt2
-rw-r--r--services/appfunctions/java/com/android/server/appfunctions/AppFunctionDumpHelper.java20
-rw-r--r--services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java2
-rw-r--r--services/appfunctions/java/com/android/server/appfunctions/FutureSearchResults.java6
-rw-r--r--services/appfunctions/java/com/android/server/appfunctions/FutureSearchResultsImpl.java5
-rw-r--r--services/appfunctions/java/com/android/server/appfunctions/MetadataSyncAdapter.java31
-rw-r--r--services/appfunctions/java/com/android/server/appfunctions/RunAppFunctionServiceCallback.java22
-rw-r--r--services/core/java/com/android/server/display/DisplayDeviceConfig.java14
-rw-r--r--services/core/java/com/android/server/display/brightness/clamper/BrightnessPowerClamper.java20
-rw-r--r--services/core/java/com/android/server/pm/ComputerEngine.java23
-rw-r--r--services/core/java/com/android/server/stats/bootstrap/StatsBootstrapAtomService.java3
-rw-r--r--services/core/java/com/android/server/wm/ActivityStartController.java5
-rw-r--r--services/core/java/com/android/server/wm/WindowOrganizerController.java4
-rw-r--r--services/core/xsd/display-device-config/display-device-config.xsd2
-rw-r--r--services/core/xsd/display-device-config/schema/current.txt4
-rw-r--r--services/tests/appfunctions/src/com/android/server/appfunctions/MetadataSyncAdapterTest.kt4
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java4
-rw-r--r--services/tests/servicestests/AndroidManifest.xml1
-rw-r--r--services/tests/servicestests/src/com/android/server/appop/AppOpsActiveWatcherTest.java1
-rw-r--r--services/tests/servicestests/src/com/android/server/appop/AppOpsDeviceAwareServiceTest.java1
-rw-r--r--services/tests/servicestests/src/com/android/server/appop/AppOpsNotedWatcherTest.java25
-rw-r--r--services/tests/servicestests/src/com/android/server/appop/AppOpsStartedWatcherTest.java26
-rw-r--r--services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java81
-rw-r--r--services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java9
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java68
-rw-r--r--tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/LetterboxAppHelper.kt4
-rw-r--r--tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml14
-rw-r--r--tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java6
-rw-r--r--tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/NonResizeableFixedAspectRatioPortraitActivity.java28
87 files changed, 1756 insertions, 495 deletions
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index fbe4905d9754..d582cb79fba1 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -24,6 +24,7 @@ aconfig_declarations_group {
"android-sdk-flags-java",
"android.adaptiveauth.flags-aconfig-java",
"android.app.appfunctions.flags-aconfig-java",
+ "android.app.assist.flags-aconfig-java",
"android.app.contextualsearch.flags-aconfig-java",
"android.app.flags-aconfig-java",
"android.app.jank.flags-aconfig-java",
@@ -1230,6 +1231,20 @@ java_aconfig_library {
defaults: ["framework-minus-apex-aconfig-java-defaults"],
}
+// Assist
+aconfig_declarations {
+ name: "android.app.assist.flags-aconfig",
+ package: "android.app.assist.flags",
+ container: "system",
+ srcs: ["core/java/android/app/assist/flags.aconfig"],
+}
+
+java_aconfig_library {
+ name: "android.app.assist.flags-aconfig-java",
+ aconfig_declarations: "android.app.assist.flags-aconfig",
+ defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
// Smartspace
aconfig_declarations {
name: "android.app.smartspace.flags-aconfig",
diff --git a/core/api/current.txt b/core/api/current.txt
index 65e4f270121b..a131ea7d29a7 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -4950,7 +4950,7 @@ package android.app {
field @Deprecated @FlaggedApi("com.android.window.flags.bal_additional_start_modes") public static final int MODE_BACKGROUND_ACTIVITY_START_ALLOWED = 1; // 0x1
field @FlaggedApi("com.android.window.flags.bal_additional_start_modes") public static final int MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS = 3; // 0x3
field @FlaggedApi("com.android.window.flags.bal_additional_start_modes") public static final int MODE_BACKGROUND_ACTIVITY_START_ALLOW_IF_VISIBLE = 4; // 0x4
- field @FlaggedApi("com.android.window.flags.bal_additional_start_modes") public static final int MODE_BACKGROUND_ACTIVITY_START_DENIED = 2; // 0x2
+ field public static final int MODE_BACKGROUND_ACTIVITY_START_DENIED = 2; // 0x2
field public static final int MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED = 0; // 0x0
}
@@ -8792,7 +8792,8 @@ package android.app.appfunctions {
ctor public AppFunctionService();
method @NonNull public final android.os.IBinder onBind(@Nullable android.content.Intent);
method @Deprecated @MainThread public void onExecuteFunction(@NonNull android.app.appfunctions.ExecuteAppFunctionRequest, @NonNull java.util.function.Consumer<android.app.appfunctions.ExecuteAppFunctionResponse>);
- method @MainThread public void onExecuteFunction(@NonNull android.app.appfunctions.ExecuteAppFunctionRequest, @NonNull android.os.CancellationSignal, @NonNull java.util.function.Consumer<android.app.appfunctions.ExecuteAppFunctionResponse>);
+ method @Deprecated @MainThread public void onExecuteFunction(@NonNull android.app.appfunctions.ExecuteAppFunctionRequest, @NonNull android.os.CancellationSignal, @NonNull java.util.function.Consumer<android.app.appfunctions.ExecuteAppFunctionResponse>);
+ method @MainThread public void onExecuteFunction(@NonNull android.app.appfunctions.ExecuteAppFunctionRequest, @NonNull String, @NonNull android.os.CancellationSignal, @NonNull java.util.function.Consumer<android.app.appfunctions.ExecuteAppFunctionResponse>);
field @NonNull public static final String SERVICE_INTERFACE = "android.app.appfunctions.AppFunctionService";
}
@@ -54921,6 +54922,7 @@ package android.view.accessibility {
method public void setPackageName(CharSequence);
method public void setSpeechStateChangeTypes(int);
method public void writeToParcel(android.os.Parcel, int);
+ field @FlaggedApi("android.view.accessibility.tri_state_checked") public static final int CONTENT_CHANGE_TYPE_CHECKED = 8192; // 0x2000
field public static final int CONTENT_CHANGE_TYPE_CONTENT_DESCRIPTION = 4; // 0x4
field public static final int CONTENT_CHANGE_TYPE_CONTENT_INVALID = 1024; // 0x400
field public static final int CONTENT_CHANGE_TYPE_DRAG_CANCELLED = 512; // 0x200
@@ -55066,6 +55068,7 @@ package android.view.accessibility {
method @Deprecated public void getBoundsInParent(android.graphics.Rect);
method public void getBoundsInScreen(android.graphics.Rect);
method public void getBoundsInWindow(@NonNull android.graphics.Rect);
+ method @FlaggedApi("android.view.accessibility.tri_state_checked") public int getChecked();
method public android.view.accessibility.AccessibilityNodeInfo getChild(int);
method @Nullable public android.view.accessibility.AccessibilityNodeInfo getChild(int, int);
method public int getChildCount();
@@ -55108,7 +55111,7 @@ package android.view.accessibility {
method public boolean isAccessibilityDataSensitive();
method public boolean isAccessibilityFocused();
method public boolean isCheckable();
- method public boolean isChecked();
+ method @Deprecated @FlaggedApi("android.view.accessibility.tri_state_checked") public boolean isChecked();
method public boolean isClickable();
method public boolean isContentInvalid();
method public boolean isContextClickable();
@@ -55153,7 +55156,8 @@ package android.view.accessibility {
method public void setBoundsInWindow(@NonNull android.graphics.Rect);
method public void setCanOpenPopup(boolean);
method public void setCheckable(boolean);
- method public void setChecked(boolean);
+ method @Deprecated @FlaggedApi("android.view.accessibility.tri_state_checked") public void setChecked(boolean);
+ method @FlaggedApi("android.view.accessibility.tri_state_checked") public void setChecked(int);
method public void setClassName(CharSequence);
method public void setClickable(boolean);
method public void setCollectionInfo(android.view.accessibility.AccessibilityNodeInfo.CollectionInfo);
@@ -55249,6 +55253,9 @@ package android.view.accessibility {
field public static final int ACTION_SELECT = 4; // 0x4
field public static final int ACTION_SET_SELECTION = 131072; // 0x20000
field public static final int ACTION_SET_TEXT = 2097152; // 0x200000
+ field @FlaggedApi("android.view.accessibility.tri_state_checked") public static final int CHECKED_STATE_FALSE = 0; // 0x0
+ field @FlaggedApi("android.view.accessibility.tri_state_checked") public static final int CHECKED_STATE_PARTIAL = 2; // 0x2
+ field @FlaggedApi("android.view.accessibility.tri_state_checked") public static final int CHECKED_STATE_TRUE = 1; // 0x1
field @NonNull public static final android.os.Parcelable.Creator<android.view.accessibility.AccessibilityNodeInfo> CREATOR;
field public static final String EXTRA_DATA_RENDERING_INFO_KEY = "android.view.accessibility.extra.DATA_RENDERING_INFO_KEY";
field public static final String EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_LENGTH = "android.view.accessibility.extra.DATA_TEXT_CHARACTER_LOCATION_ARG_LENGTH";
diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java
index 6ab39b028032..832c88a795e5 100644
--- a/core/java/android/app/ActivityOptions.java
+++ b/core/java/android/app/ActivityOptions.java
@@ -120,7 +120,7 @@ public class ActivityOptions extends ComponentOptions {
/**
* Grants the {@link PendingIntent} background activity start privileges.
*
- * This behaves the same as {@link #MODE_BACKGROUND_ACTIVITY_START_ALLOWED_ALWAYS}, except it
+ * This behaves the same as {@link #MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS}, except it
* does not grant background activity launch permissions based on the privileged permission
* <code>START_ACTIVITIES_FROM_BACKGROUND</code>.
*
@@ -136,7 +136,6 @@ public class ActivityOptions extends ComponentOptions {
/**
* Denies the {@link PendingIntent} any background activity start privileges.
*/
- @FlaggedApi(Flags.FLAG_BAL_ADDITIONAL_START_MODES)
public static final int MODE_BACKGROUND_ACTIVITY_START_DENIED = 2;
/**
* Grants the {@link PendingIntent} all background activity start privileges, including
@@ -146,12 +145,12 @@ public class ActivityOptions extends ComponentOptions {
* <p><b>Caution:</b> This mode should be used sparingly. Most apps should use
* {@link #MODE_BACKGROUND_ACTIVITY_START_ALLOW_IF_VISIBLE} instead, relying on notifications
* or foreground services for background interactions to minimize user disruption. However,
- * this mode is necessary for specific use cases, such as companion apps responding to
+ * this mode is necessary for specific use cases, such as companion apps responding to
* prompts from a connected device.
*
* <p>For more information on background activity start restrictions, see:
* <a href="https://developer.android.com/guide/components/activities/background-starts">
- * Restrictions on starting activities from the background</a>
+ * Restrictions on starting activities from the background</a>
*/
@FlaggedApi(Flags.FLAG_BAL_ADDITIONAL_START_MODES)
public static final int MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS = 3;
diff --git a/core/java/android/app/appfunctions/AppFunctionService.java b/core/java/android/app/appfunctions/AppFunctionService.java
index 7a68a656564b..ceca850a1037 100644
--- a/core/java/android/app/appfunctions/AppFunctionService.java
+++ b/core/java/android/app/appfunctions/AppFunctionService.java
@@ -29,11 +29,9 @@ import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.Binder;
-import android.os.Bundle;
+import android.os.CancellationSignal;
import android.os.IBinder;
import android.os.ICancellationSignal;
-import android.os.CancellationSignal;
-import android.os.RemoteCallback;
import android.os.RemoteException;
import android.util.Log;
@@ -80,6 +78,7 @@ public abstract class AppFunctionService extends Service {
*/
void perform(
@NonNull ExecuteAppFunctionRequest request,
+ @NonNull String callingPackage,
@NonNull CancellationSignal cancellationSignal,
@NonNull Consumer<ExecuteAppFunctionResponse> callback);
}
@@ -92,6 +91,7 @@ public abstract class AppFunctionService extends Service {
@Override
public void executeAppFunction(
@NonNull ExecuteAppFunctionRequest request,
+ @NonNull String callingPackage,
@NonNull ICancellationCallback cancellationCallback,
@NonNull IExecuteAppFunctionCallback callback) {
if (context.checkCallingPermission(BIND_APP_FUNCTION_SERVICE)
@@ -103,6 +103,7 @@ public abstract class AppFunctionService extends Service {
try {
onExecuteFunction.perform(
request,
+ callingPackage,
buildCancellationSignal(cancellationCallback),
safeCallback::onResult);
} catch (Exception ex) {
@@ -128,12 +129,11 @@ public abstract class AppFunctionService extends Service {
throw e.rethrowFromSystemServer();
}
- return cancellationSignal ;
+ return cancellationSignal;
}
- private final Binder mBinder = createBinder(
- AppFunctionService.this,
- AppFunctionService.this::onExecuteFunction);
+ private final Binder mBinder =
+ createBinder(AppFunctionService.this, AppFunctionService.this::onExecuteFunction);
@NonNull
@Override
@@ -141,7 +141,6 @@ public abstract class AppFunctionService extends Service {
return mBinder;
}
-
/**
* Called by the system to execute a specific app function.
*
@@ -161,7 +160,6 @@ public abstract class AppFunctionService extends Service {
*
* @param request The function execution request.
* @param callback A callback to report back the result.
- *
* @deprecated Use {@link #onExecuteFunction(ExecuteAppFunctionRequest, CancellationSignal,
* Consumer)} instead. This method will be removed once usage references are updated.
*/
@@ -198,12 +196,50 @@ public abstract class AppFunctionService extends Service {
* @param request The function execution request.
* @param cancellationSignal A signal to cancel the execution.
* @param callback A callback to report back the result.
+ * @deprecated Use {@link #onExecuteFunction(ExecuteAppFunctionRequest, String,
+ * CancellationSignal, Consumer)} instead. This method will be removed once usage references
+ * are updated.
*/
@MainThread
+ @Deprecated
public void onExecuteFunction(
@NonNull ExecuteAppFunctionRequest request,
@NonNull CancellationSignal cancellationSignal,
@NonNull Consumer<ExecuteAppFunctionResponse> callback) {
onExecuteFunction(request, callback);
}
+
+ /**
+ * 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.
+ *
+ * <p>This method also accepts a {@link CancellationSignal} that the app should listen to cancel
+ * the execution of function if requested by the system.
+ *
+ * @param request The function execution request.
+ * @param callingPackage The package name of the app that is requesting the execution.
+ * @param cancellationSignal A signal to cancel the execution.
+ * @param callback A callback to report back the result.
+ */
+ @MainThread
+ public void onExecuteFunction(
+ @NonNull ExecuteAppFunctionRequest request,
+ @NonNull String callingPackage,
+ @NonNull CancellationSignal cancellationSignal,
+ @NonNull Consumer<ExecuteAppFunctionResponse> callback) {
+ onExecuteFunction(request, cancellationSignal, callback);
+ }
}
diff --git a/core/java/android/app/appfunctions/IAppFunctionService.aidl b/core/java/android/app/appfunctions/IAppFunctionService.aidl
index 291f33ccb1b8..bf935d2a102b 100644
--- a/core/java/android/app/appfunctions/IAppFunctionService.aidl
+++ b/core/java/android/app/appfunctions/IAppFunctionService.aidl
@@ -34,11 +34,13 @@ oneway interface IAppFunctionService {
* Called by the system to execute a specific app function.
*
* @param request the function execution request.
+ * @param callingPackage The package name of the app that is requesting the execution.
* @param cancellationCallback a callback to send back the cancellation transport.
* @param callback a callback to report back the result.
*/
void executeAppFunction(
in ExecuteAppFunctionRequest request,
+ in String callingPackage,
in ICancellationCallback cancellationCallback,
in IExecuteAppFunctionCallback callback
);
diff --git a/core/java/android/app/assist/AssistStructure.java b/core/java/android/app/assist/AssistStructure.java
index 508077ed43cc..1af2437a5d6a 100644
--- a/core/java/android/app/assist/AssistStructure.java
+++ b/core/java/android/app/assist/AssistStructure.java
@@ -1,5 +1,6 @@
package android.app.assist;
+import static android.app.assist.flags.Flags.addPlaceholderViewForNullChild;
import static android.credentials.Constants.FAILURE_CREDMAN_SELECTOR;
import static android.credentials.Constants.SUCCESS_CREDMAN_SELECTOR;
import static android.service.autofill.Flags.FLAG_AUTOFILL_CREDMAN_DEV_INTEGRATION;
@@ -284,12 +285,18 @@ public class AssistStructure implements Parcelable {
mCurViewStackEntry = entry;
}
- void writeView(ViewNode child, Parcel out, PooledStringWriter pwriter, int levelAdj) {
+ void writeView(@Nullable ViewNode child, Parcel out, PooledStringWriter pwriter,
+ int levelAdj) {
if (DEBUG_PARCEL) Log.d(TAG, "write view: at " + out.dataPosition()
+ ", windows=" + mNumWrittenWindows
+ ", views=" + mNumWrittenViews
+ ", level=" + (mCurViewStackPos+levelAdj));
out.writeInt(VALIDATE_VIEW_TOKEN);
+ if (addPlaceholderViewForNullChild() && child == null) {
+ if (DEBUG_PARCEL_TREE) Log.d(TAG, "Detected an empty child"
+ + "; writing a placeholder for the child.");
+ child = new ViewNode();
+ }
int flags = child.writeSelfToParcel(out, pwriter, mSanitizeOnWrite,
mTmpMatrix, /*willWriteChildren=*/true);
mNumWrittenViews++;
@@ -2545,7 +2552,7 @@ public class AssistStructure implements Parcelable {
ensureData();
}
Log.i(TAG, "Task id: " + mTaskId);
- Log.i(TAG, "Activity: " + (mActivityComponent != null
+ Log.i(TAG, "Activity: " + (mActivityComponent != null
? mActivityComponent.flattenToShortString()
: null));
Log.i(TAG, "Sanitize on write: " + mSanitizeOnWrite);
diff --git a/core/java/android/app/assist/flags.aconfig b/core/java/android/app/assist/flags.aconfig
new file mode 100644
index 000000000000..bf0aeacbc7f3
--- /dev/null
+++ b/core/java/android/app/assist/flags.aconfig
@@ -0,0 +1,13 @@
+package: "android.app.assist.flags"
+container: "system"
+
+flag {
+ name: "add_placeholder_view_for_null_child"
+ namespace: "machine_learning"
+ description: "Flag to add a placeholder view when a child view is null."
+ bug: "369503426"
+ is_fixed_read_only: true
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/core/java/android/os/OWNERS b/core/java/android/os/OWNERS
index 7d3076d6611f..a1b75034442e 100644
--- a/core/java/android/os/OWNERS
+++ b/core/java/android/os/OWNERS
@@ -115,3 +115,10 @@ per-file OomKillRecord.java = file:/MEMORY_OWNERS
# MessageQueue
per-file MessageQueue.java = mfasheh@google.com, shayba@google.com
per-file Message.java = mfasheh@google.com, shayba@google.com
+
+# Stats
+per-file IStatsBootstrapAtomService.aidl = file:/services/core/java/com/android/server/stats/OWNERS
+per-file StatsBootstrapAtom.aidl = file:/services/core/java/com/android/server/stats/OWNERS
+per-file StatsBootstrapAtomValue.aidl = file:/services/core/java/com/android/server/stats/OWNERS
+per-file StatsBootstrapAtomService.java = file:/services/core/java/com/android/server/stats/OWNERS
+per-file StatsServiceManager.java = file:/services/core/java/com/android/server/stats/OWNERS
diff --git a/core/java/android/os/StatsBootstrapAtomValue.aidl b/core/java/android/os/StatsBootstrapAtomValue.aidl
index a90dfa404ee9..b59bc062648f 100644
--- a/core/java/android/os/StatsBootstrapAtomValue.aidl
+++ b/core/java/android/os/StatsBootstrapAtomValue.aidl
@@ -26,4 +26,5 @@ union StatsBootstrapAtomValue {
float floatValue;
String stringValue;
byte[] bytesValue;
+ String[] stringArrayValue;
} \ No newline at end of file
diff --git a/core/java/android/view/accessibility/AccessibilityEvent.java b/core/java/android/view/accessibility/AccessibilityEvent.java
index a43906f30ff7..dfac244fcc0d 100644
--- a/core/java/android/view/accessibility/AccessibilityEvent.java
+++ b/core/java/android/view/accessibility/AccessibilityEvent.java
@@ -16,6 +16,7 @@
package android.view.accessibility;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.compat.annotation.UnsupportedAppUsage;
@@ -784,6 +785,19 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par
*/
public static final int CONTENT_CHANGE_TYPE_ENABLED = 1 << 12;
+ /**
+ * Change type for {@link #TYPE_WINDOW_CONTENT_CHANGED} event:
+ * The source node changed its checked state, which is returned by
+ * {@link AccessibilityNodeInfo#getChecked()}.
+ * The view changing its checked state should call
+ * {@link AccessibilityNodeInfo#setChecked(int)} and then send this event.
+ *
+ * @see AccessibilityNodeInfo#getChecked()
+ * @see AccessibilityNodeInfo#setChecked(int)
+ */
+ @FlaggedApi(Flags.FLAG_TRI_STATE_CHECKED)
+ public static final int CONTENT_CHANGE_TYPE_CHECKED = 1 << 13;
+
// Speech state change types.
/** Change type for {@link #TYPE_SPEECH_STATE_CHANGE} event: another service is speaking. */
diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
index fe6aafbd7e16..c5ca059d1cea 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
@@ -860,6 +860,37 @@ public class AccessibilityNodeInfo implements Parcelable {
public static final String EXTRA_DATA_REQUESTED_KEY =
"android.view.accessibility.AccessibilityNodeInfo.extra_data_requested";
+ // Tri-state checked states.
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = { "CHECKED_STATE" }, value = {
+ CHECKED_STATE_FALSE,
+ CHECKED_STATE_TRUE,
+ CHECKED_STATE_PARTIAL
+ })
+ public @interface CheckedState {}
+
+ /**
+ * This node is not checked.
+ */
+ @FlaggedApi(Flags.FLAG_TRI_STATE_CHECKED)
+ public static final int CHECKED_STATE_FALSE = 0;
+
+ /**
+ * This node is checked.
+ */
+ @FlaggedApi(Flags.FLAG_TRI_STATE_CHECKED)
+ public static final int CHECKED_STATE_TRUE = 1;
+
+ /**
+ * This node is partially checked. For example,
+ * when a checkbox owns a number of sub-options and they have
+ * different states, then the main checkbox is in a partially-checked state.
+ */
+ @FlaggedApi(Flags.FLAG_TRI_STATE_CHECKED)
+ public static final int CHECKED_STATE_PARTIAL = 2;
+
// Boolean attributes.
private static final int BOOLEAN_PROPERTY_CHECKABLE = 1 /* << 0 */;
@@ -1038,6 +1069,10 @@ public class AccessibilityNodeInfo implements Parcelable {
private IBinder mLeashedParent;
private long mLeashedParentNodeId = UNDEFINED_NODE_ID;
+ // TODO(b/369951517) Initialize mChecked explicitly with
+ // the CHECKED_FALSE state when flagging is removed.
+ private int mChecked;
+
/**
* Creates a new {@link AccessibilityNodeInfo}.
*/
@@ -2319,28 +2354,100 @@ public class AccessibilityNodeInfo implements Parcelable {
}
/**
- * Gets whether this node is checked.
+ * Gets whether this node is checked. This is only meaningful
+ * when {@link #isCheckable()} returns {@code true}.
+ *
+ * @deprecated Use {@link #getChecked()} instead.
*
* @return True if the node is checked.
*/
+ @FlaggedApi(Flags.FLAG_TRI_STATE_CHECKED)
+ @Deprecated
public boolean isChecked() {
return getBooleanProperty(BOOLEAN_PROPERTY_CHECKED);
}
/**
- * Sets whether this node is checked.
+ * Sets whether this node is checked. This is only meaningful
+ * when {@link #isCheckable()} returns {@code true}.
* <p>
* <strong>Note:</strong> Cannot be called from an
* {@link android.accessibilityservice.AccessibilityService}.
* This class is made immutable before being delivered to an AccessibilityService.
* </p>
*
+ * @deprecated Use {@link #setChecked(int)} instead.
+ *
* @param checked True if the node is checked.
*
* @throws IllegalStateException If called from an AccessibilityService.
*/
+ @FlaggedApi(Flags.FLAG_TRI_STATE_CHECKED)
+ @Deprecated
public void setChecked(boolean checked) {
setBooleanProperty(BOOLEAN_PROPERTY_CHECKED, checked);
+ if (Flags.triStateChecked()) {
+ mChecked = checked ? CHECKED_STATE_TRUE : CHECKED_STATE_FALSE;
+ }
+ }
+
+ /**
+ * Gets the checked state of this node. This is only meaningful
+ * when {@link #isCheckable()} returns {@code true}.
+ *
+ * @see #setCheckable(boolean)
+ * @see #isCheckable()
+ * @see #setChecked(int)
+ *
+ * @return The checked state, one of:
+ * <ul>
+ * <li>{@link #CHECKED_STATE_FALSE}
+ * <li>{@link #CHECKED_STATE_TRUE}
+ * <li>{@link #CHECKED_STATE_PARTIAL}
+ * </ul>
+ */
+ @FlaggedApi(Flags.FLAG_TRI_STATE_CHECKED)
+ public @CheckedState int getChecked() {
+ return mChecked;
+ }
+
+ /**
+ * Sets the checked state of this node. This is only meaningful
+ * when {@link #isCheckable()} returns {@code true}.
+ * <p>
+ * <strong>Note:</strong> Cannot be called from an
+ * {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ *
+ * @see #setCheckable(boolean)
+ * @see #isCheckable()
+ * @see #getChecked()
+ *
+ * @param checked The checked state. One of
+ * <ul>
+ * <li>{@link #CHECKED_STATE_FALSE}
+ * <li>{@link #CHECKED_STATE_TRUE}
+ * <li>{@link #CHECKED_STATE_PARTIAL}
+ * </ul>
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ * @throws IllegalArgumentException if checked is not one of {@link #CHECKED_STATE_FALSE},
+ * {@link #CHECKED_STATE_TRUE}, or {@link #CHECKED_STATE_PARTIAL}.
+ */
+ @FlaggedApi(Flags.FLAG_TRI_STATE_CHECKED)
+ public void setChecked(@CheckedState int checked) {
+ enforceNotSealed();
+ switch (checked) {
+ case CHECKED_STATE_FALSE:
+ case CHECKED_STATE_TRUE:
+ case CHECKED_STATE_PARTIAL:
+ mChecked = checked;
+ break;
+ default:
+ throw new IllegalArgumentException("Unknown checked argument: " + checked);
+ }
+ setBooleanProperty(BOOLEAN_PROPERTY_CHECKED, checked == CHECKED_STATE_TRUE);
}
/**
@@ -4515,6 +4622,10 @@ public class AccessibilityNodeInfo implements Parcelable {
if (mLeashedParentNodeId != DEFAULT.mLeashedParentNodeId) {
nonDefaultFields |= bitAt(fieldIndex);
}
+ fieldIndex++;
+ if (mChecked != DEFAULT.mChecked) {
+ nonDefaultFields |= bitAt(fieldIndex);
+ }
int totalFields = fieldIndex;
parcel.writeLong(nonDefaultFields);
@@ -4683,6 +4794,9 @@ public class AccessibilityNodeInfo implements Parcelable {
if (isBitSet(nonDefaultFields, fieldIndex++)) {
parcel.writeLong(mLeashedParentNodeId);
}
+ if (isBitSet(nonDefaultFields, fieldIndex++)) {
+ parcel.writeInt(mChecked);
+ }
if (DEBUG) {
fieldIndex--;
@@ -4771,6 +4885,7 @@ public class AccessibilityNodeInfo implements Parcelable {
mLeashedChild = other.mLeashedChild;
mLeashedParent = other.mLeashedParent;
mLeashedParentNodeId = other.mLeashedParentNodeId;
+ mChecked = other.mChecked;
}
private void initCopyInfos(AccessibilityNodeInfo other) {
@@ -4960,6 +5075,9 @@ public class AccessibilityNodeInfo implements Parcelable {
if (isBitSet(nonDefaultFields, fieldIndex++)) {
mLeashedParentNodeId = parcel.readLong();
}
+ if (isBitSet(nonDefaultFields, fieldIndex++)) {
+ mChecked = parcel.readInt();
+ }
mSealed = sealed;
}
diff --git a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
index 69cbb9b2bfb8..8ffae845de1f 100644
--- a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
+++ b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
@@ -219,6 +219,13 @@ flag {
}
flag {
+ name: "tri_state_checked"
+ namespace: "accessibility"
+ description: "Feature flag for adding tri-state checked api"
+ bug: "333784774"
+}
+
+flag {
name: "warning_use_default_dialog_type"
namespace: "accessibility"
description: "Uses the default type for the A11yService warning dialog, instead of SYSTEM_ALERT_DIALOG"
@@ -226,4 +233,4 @@ flag {
metadata {
purpose: PURPOSE_BUGFIX
}
-}
+ }
diff --git a/core/jni/android_view_WindowManagerGlobal.cpp b/core/jni/android_view_WindowManagerGlobal.cpp
index abc621d8dc90..4202de39adb0 100644
--- a/core/jni/android_view_WindowManagerGlobal.cpp
+++ b/core/jni/android_view_WindowManagerGlobal.cpp
@@ -69,8 +69,8 @@ void removeInputChannel(const sp<IBinder>& clientToken) {
JNIEnv* env = AndroidRuntime::getJNIEnv();
ScopedLocalRef<jobject> clientTokenObj(env, javaObjectForIBinder(env, clientToken));
- env->CallStaticObjectMethod(gWindowManagerGlobal.clazz, gWindowManagerGlobal.removeInputChannel,
- clientTokenObj.get());
+ env->CallStaticVoidMethod(gWindowManagerGlobal.clazz, gWindowManagerGlobal.removeInputChannel,
+ clientTokenObj.get());
}
int register_android_view_WindowManagerGlobal(JNIEnv* env) {
@@ -88,4 +88,4 @@ int register_android_view_WindowManagerGlobal(JNIEnv* env) {
return NO_ERROR;
}
-} // namespace android \ No newline at end of file
+} // namespace android
diff --git a/core/jni/platform/host/HostRuntime.cpp b/core/jni/platform/host/HostRuntime.cpp
index 88b3e1c1ed9d..27417c0c9929 100644
--- a/core/jni/platform/host/HostRuntime.cpp
+++ b/core/jni/platform/host/HostRuntime.cpp
@@ -115,9 +115,9 @@ static const std::unordered_map<std::string, RegJNIRec> gRegJNIMap = {
#ifdef __linux__
{"android.content.res.ApkAssets", REG_JNI(register_android_content_res_ApkAssets)},
{"android.content.res.AssetManager", REG_JNI(register_android_content_AssetManager)},
+#endif
{"android.content.res.StringBlock", REG_JNI(register_android_content_StringBlock)},
{"android.content.res.XmlBlock", REG_JNI(register_android_content_XmlBlock)},
-#endif
{"android.database.CursorWindow", REG_JNI(register_android_database_CursorWindow)},
{"android.database.sqlite.SQLiteConnection",
REG_JNI(register_android_database_SQLiteConnection)},
diff --git a/core/tests/coretests/src/android/app/assist/AssistStructureTest.java b/core/tests/coretests/src/android/app/assist/AssistStructureTest.java
index a28b2f66aaa8..51e79e7ac4e1 100644
--- a/core/tests/coretests/src/android/app/assist/AssistStructureTest.java
+++ b/core/tests/coretests/src/android/app/assist/AssistStructureTest.java
@@ -24,6 +24,7 @@ import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
import android.app.assist.AssistStructure.ViewNode;
import android.app.assist.AssistStructure.ViewNodeBuilder;
@@ -37,6 +38,7 @@ import android.os.Bundle;
import android.os.LocaleList;
import android.os.OutcomeReceiver;
import android.os.Parcel;
+import android.os.PooledStringWriter;
import android.os.SystemClock;
import android.text.InputFilter;
import android.util.Log;
@@ -355,6 +357,18 @@ public class AssistStructureTest {
}
+ @Test
+ public void testParcelTransferWriter_writeNull() {
+ AssistStructure structure = new AssistStructure(mActivity, FOR_AUTOFILL, NO_FLAGS);
+ Parcel parcel = Parcel.obtain();
+ AssistStructure.ParcelTransferWriter writer =
+ new AssistStructure.ParcelTransferWriter(structure, parcel);
+ writer.writeView(null, parcel, new PooledStringWriter(parcel), 0);
+
+ // No throw any exception.
+ assertTrue(true);
+ }
+
private EditText newSmallView() {
EditText view = new EditText(mContext);
view.setText("I AM GROOT");
diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java
index 6e563ff44478..da202b63d0f1 100644
--- a/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java
+++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java
@@ -46,7 +46,7 @@ public class AccessibilityNodeInfoTest {
// The number of fields tested in the corresponding CTS AccessibilityNodeInfoTest:
// See fullyPopulateAccessibilityNodeInfo, assertEqualsAccessibilityNodeInfo,
// and assertAccessibilityNodeInfoCleared in that class.
- private static final int NUM_MARSHALLED_PROPERTIES = 44;
+ private static final int NUM_MARSHALLED_PROPERTIES = 45;
/**
* The number of properties that are purposely not marshalled
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 3f6dc94d6a3f..92535f37094a 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
@@ -465,6 +465,12 @@ class DesktopTasksController(
removeWallpaperActivity(wct)
}
taskRepository.addClosingTask(displayId, taskId)
+ taskbarDesktopTaskListener?.onTaskbarCornerRoundingUpdate(
+ doesAnyTaskRequireTaskbarRounding(
+ displayId,
+ taskId
+ )
+ )
}
fun minimizeTask(taskInfo: RunningTaskInfo) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt
index a4bc2fe9460b..0b1bb8f36fa8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt
@@ -21,6 +21,7 @@ import android.content.Context
import android.os.IBinder
import android.view.SurfaceControl
import android.view.WindowManager
+import android.view.WindowManager.TRANSIT_CLOSE
import android.view.WindowManager.TRANSIT_TO_BACK
import android.window.TransitionInfo
import android.window.WindowContainerTransaction
@@ -36,8 +37,8 @@ import com.android.wm.shell.transition.Transitions
/**
* A [Transitions.TransitionObserver] that observes shell transitions and updates the
- * [DesktopRepository] state TODO: b/332682201 This observes transitions related to desktop
- * mode and other transitions that originate both within and outside shell.
+ * [DesktopRepository] state TODO: b/332682201 This observes transitions related to desktop mode and
+ * other transitions that originate both within and outside shell.
*/
class DesktopTasksTransitionObserver(
private val context: Context,
@@ -47,6 +48,8 @@ class DesktopTasksTransitionObserver(
shellInit: ShellInit
) : Transitions.TransitionObserver {
+ private var transitionToCloseWallpaper: IBinder? = null
+
init {
if (DesktopModeStatus.canEnterDesktopMode(context)) {
shellInit.addInitCallback(::onInit, this)
@@ -70,6 +73,7 @@ class DesktopTasksTransitionObserver(
handleBackNavigation(info)
removeTaskIfNeeded(info)
}
+ removeWallpaperOnLastTaskClosingIfNeeded(transition, info)
}
private fun removeTaskIfNeeded(info: TransitionInfo) {
@@ -81,13 +85,9 @@ class DesktopTasksTransitionObserver(
val taskInfo = change.taskInfo
if (taskInfo == null || taskInfo.taskId == -1) continue
- if (desktopRepository.isActiveTask(taskInfo.taskId)
- && taskInfo.windowingMode != WINDOWING_MODE_FREEFORM
- ) {
- desktopRepository.removeFreeformTask(
- taskInfo.displayId,
- taskInfo.taskId
- )
+ if (desktopRepository.isActiveTask(taskInfo.taskId) &&
+ taskInfo.windowingMode != WINDOWING_MODE_FREEFORM) {
+ desktopRepository.removeFreeformTask(taskInfo.displayId, taskInfo.taskId)
}
}
}
@@ -104,14 +104,32 @@ class DesktopTasksTransitionObserver(
if (desktopRepository.getVisibleTaskCount(taskInfo.displayId) > 0 &&
change.mode == TRANSIT_TO_BACK &&
- taskInfo.windowingMode == WINDOWING_MODE_FREEFORM
- ) {
+ taskInfo.windowingMode == WINDOWING_MODE_FREEFORM) {
desktopRepository.minimizeTask(taskInfo.displayId, taskInfo.taskId)
}
}
}
}
+ private fun removeWallpaperOnLastTaskClosingIfNeeded(
+ transition: IBinder,
+ info: TransitionInfo
+ ) {
+ for (change in info.changes) {
+ val taskInfo = change.taskInfo
+ if (taskInfo == null || taskInfo.taskId == -1) {
+ continue
+ }
+
+ if (desktopRepository.getVisibleTaskCount(taskInfo.displayId) == 1 &&
+ change.mode == TRANSIT_CLOSE &&
+ taskInfo.windowingMode == WINDOWING_MODE_FREEFORM &&
+ desktopRepository.wallpaperActivityToken != null) {
+ transitionToCloseWallpaper = transition
+ }
+ }
+ }
+
override fun onTransitionStarting(transition: IBinder) {
// TODO: b/332682201 Update repository state
}
@@ -122,6 +140,16 @@ class DesktopTasksTransitionObserver(
override fun onTransitionFinished(transition: IBinder, aborted: Boolean) {
// TODO: b/332682201 Update repository state
+ if (transitionToCloseWallpaper == transition) {
+ // TODO: b/362469671 - Handle merging the animation when desktop is also closing.
+ desktopRepository.wallpaperActivityToken?.let { wallpaperActivityToken ->
+ transitions.startTransition(
+ TRANSIT_CLOSE,
+ WindowContainerTransaction().removeTask(wallpaperActivityToken),
+ null)
+ }
+ transitionToCloseWallpaper = null
+ }
}
private fun updateWallpaperToken(info: TransitionInfo) {
@@ -139,10 +167,9 @@ class DesktopTasksTransitionObserver(
// task.
shellTaskOrganizer.applyTransaction(
WindowContainerTransaction()
- .setTaskTrimmableFromRecents(taskInfo.token, false)
- )
+ .setTaskTrimmableFromRecents(taskInfo.token, false))
}
- WindowManager.TRANSIT_CLOSE ->
+ TRANSIT_CLOSE ->
desktopRepository.wallpaperActivityToken = null
else -> {}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/OpenTransparentActivityTest.kt b/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/OpenTransparentActivityTest.kt
index 2980d5113bba..e176f47d4094 100644
--- a/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/OpenTransparentActivityTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/OpenTransparentActivityTest.kt
@@ -17,7 +17,6 @@
package com.android.wm.shell.flicker.appcompat
import android.platform.test.annotations.Postsubmit
-import android.tools.Rotation
import android.tools.flicker.assertions.FlickerTest
import android.tools.flicker.junit.FlickerParametersRunnerFactory
import android.tools.flicker.legacy.FlickerBuilder
@@ -109,9 +108,7 @@ class OpenTransparentActivityTest(flicker: LegacyFlickerTest) : TransparentBaseA
@Parameterized.Parameters(name = "{0}")
@JvmStatic
fun getParams(): Collection<FlickerTest> {
- return LegacyFlickerTestFactory.nonRotationTests(
- supportedRotations = listOf(Rotation.ROTATION_90)
- )
+ return LegacyFlickerTestFactory.nonRotationTests()
}
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/QuickSwitchLauncherToLetterboxAppTest.kt b/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/QuickSwitchLauncherToLetterboxAppTest.kt
index 2484f675b236..9b8c949a1705 100644
--- a/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/QuickSwitchLauncherToLetterboxAppTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/QuickSwitchLauncherToLetterboxAppTest.kt
@@ -20,7 +20,6 @@ import android.graphics.Rect
import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.RequiresDevice
import android.tools.NavBar
-import android.tools.Rotation
import android.tools.flicker.assertions.FlickerTest
import android.tools.flicker.junit.FlickerParametersRunnerFactory
import android.tools.flicker.legacy.FlickerBuilder
@@ -266,8 +265,7 @@ class QuickSwitchLauncherToLetterboxAppTest(flicker: LegacyFlickerTest) : BaseAp
@JvmStatic
fun getParams(): Collection<FlickerTest> {
return LegacyFlickerTestFactory.nonRotationTests(
- supportedNavigationModes = listOf(NavBar.MODE_GESTURAL),
- supportedRotations = listOf(Rotation.ROTATION_90)
+ supportedNavigationModes = listOf(NavBar.MODE_GESTURAL)
)
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt
index 598df34a310d..fe87aa88a8db 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt
@@ -22,26 +22,38 @@ import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
import android.content.ComponentName
import android.content.Context
import android.content.Intent
+import android.os.IBinder
import android.platform.test.annotations.EnableFlags
import android.view.Display.DEFAULT_DISPLAY
+import android.view.WindowManager
+import android.view.WindowManager.TRANSIT_CLOSE
import android.view.WindowManager.TRANSIT_OPEN
import android.view.WindowManager.TRANSIT_TO_BACK
import android.window.IWindowContainerToken
import android.window.TransitionInfo
import android.window.TransitionInfo.Change
import android.window.WindowContainerToken
+import android.window.WindowContainerTransaction
+import android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REMOVE_TASK
import com.android.modules.utils.testing.ExtendedMockitoRule
import com.android.window.flags.Flags
+import com.android.wm.shell.MockToken
import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.common.ShellExecutor
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
import com.android.wm.shell.sysui.ShellInit
import com.android.wm.shell.transition.Transitions
+import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
import org.junit.Before
import org.junit.Rule
import org.junit.Test
+import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.ArgumentMatchers.isA
import org.mockito.Mockito
import org.mockito.kotlin.any
+import org.mockito.kotlin.isNull
import org.mockito.kotlin.mock
import org.mockito.kotlin.never
import org.mockito.kotlin.spy
@@ -130,6 +142,27 @@ class DesktopTasksTransitionObserverTest {
verify(taskRepository).removeFreeformTask(task.displayId, task.taskId)
}
+ @Test
+ fun closeLastTask_wallpaperTokenExists_wallpaperIsRemoved() {
+ val mockTransition = Mockito.mock(IBinder::class.java)
+ val task = createTaskInfo(1, WINDOWING_MODE_FREEFORM)
+ val wallpaperToken = MockToken().token()
+ whenever(taskRepository.getVisibleTaskCount(task.displayId)).thenReturn(1)
+ whenever(taskRepository.wallpaperActivityToken).thenReturn(wallpaperToken)
+
+ transitionObserver.onTransitionReady(
+ transition = mockTransition,
+ info = createCloseTransition(task),
+ startTransaction = mock(),
+ finishTransaction = mock(),
+ )
+ transitionObserver.onTransitionFinished(mockTransition, false)
+
+ val wct = getLatestWct(type = TRANSIT_CLOSE)
+ assertThat(wct.hierarchyOps).hasSize(1)
+ wct.assertRemoveAt(index = 0, wallpaperToken)
+ }
+
private fun createBackNavigationTransition(
task: RunningTaskInfo?
): TransitionInfo {
@@ -160,6 +193,48 @@ class DesktopTasksTransitionObserverTest {
}
}
+ private fun createCloseTransition(
+ task: RunningTaskInfo?
+ ): TransitionInfo {
+ return TransitionInfo(TRANSIT_CLOSE, 0 /* flags */).apply {
+ addChange(
+ Change(mock(), mock()).apply {
+ mode = TRANSIT_CLOSE
+ parent = null
+ taskInfo = task
+ flags = flags
+ }
+ )
+ }
+ }
+
+ private fun getLatestWct(
+ @WindowManager.TransitionType type: Int = TRANSIT_OPEN,
+ handlerClass: Class<out Transitions.TransitionHandler>? = null
+ ): WindowContainerTransaction {
+ val arg = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
+ if (handlerClass == null) {
+ Mockito.verify(transitions).startTransition(eq(type), arg.capture(), isNull())
+ } else {
+ Mockito.verify(transitions)
+ .startTransition(eq(type), arg.capture(), isA(handlerClass))
+ }
+ return arg.value
+ }
+
+ private fun WindowContainerTransaction.assertRemoveAt(index: Int, token: WindowContainerToken) {
+ assertIndexInBounds(index)
+ val op = hierarchyOps[index]
+ assertThat(op.type).isEqualTo(HIERARCHY_OP_TYPE_REMOVE_TASK)
+ assertThat(op.container).isEqualTo(token.asBinder())
+ }
+
+ private fun WindowContainerTransaction.assertIndexInBounds(index: Int) {
+ assertWithMessage("WCT does not have a hierarchy operation at index $index")
+ .that(hierarchyOps.size)
+ .isGreaterThan(index)
+ }
+
private fun createTaskInfo(id: Int, windowingMode: Int = WINDOWING_MODE_FREEFORM) =
RunningTaskInfo().apply {
taskId = id
diff --git a/libs/appfunctions/api/current.txt b/libs/appfunctions/api/current.txt
index bc269fedddfe..e9845c1d9f13 100644
--- a/libs/appfunctions/api/current.txt
+++ b/libs/appfunctions/api/current.txt
@@ -15,7 +15,8 @@ package com.google.android.appfunctions.sidecar {
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 void onExecuteFunction(@NonNull com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest, @NonNull android.os.CancellationSignal, @NonNull java.util.function.Consumer<com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse>);
+ method @MainThread public void onExecuteFunction(@NonNull com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest, @NonNull String, @NonNull android.os.CancellationSignal, @NonNull java.util.function.Consumer<com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse>);
+ method @Deprecated @MainThread public void onExecuteFunction(@NonNull com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest, @NonNull android.os.CancellationSignal, @NonNull java.util.function.Consumer<com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse>);
method @Deprecated @MainThread public 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";
diff --git a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionService.java b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionService.java
index 6e91de6bbcf2..2a168e871713 100644
--- a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionService.java
+++ b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionService.java
@@ -24,8 +24,8 @@ import android.annotation.Nullable;
import android.app.Service;
import android.content.Intent;
import android.os.Binder;
-import android.os.IBinder;
import android.os.CancellationSignal;
+import android.os.IBinder;
import android.util.Log;
import java.util.function.Consumer;
@@ -71,18 +71,21 @@ public abstract class AppFunctionService extends Service {
private final Binder mBinder =
android.app.appfunctions.AppFunctionService.createBinder(
/* context= */ this,
- /* onExecuteFunction= */ (platformRequest, cancellationSignal, callback) -> {
+ /* onExecuteFunction= */ (platformRequest,
+ callingPackage,
+ cancellationSignal,
+ callback) -> {
AppFunctionService.this.onExecuteFunction(
SidecarConverter.getSidecarExecuteAppFunctionRequest(
platformRequest),
+ callingPackage,
cancellationSignal,
(sidecarResponse) -> {
callback.accept(
SidecarConverter.getPlatformExecuteAppFunctionResponse(
sidecarResponse));
});
- }
- );
+ });
@NonNull
@Override
@@ -107,11 +110,49 @@ public abstract class AppFunctionService extends Service {
* 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.
*
+ * <p>This method also accepts a {@link CancellationSignal} that the app should listen to cancel
+ * the execution of function if requested by the system.
+ *
+ * @param request The function execution request.
+ * @param callingPackage The package name of the app that is requesting the execution.
+ * @param cancellationSignal A signal to cancel the execution.
+ * @param callback A callback to report back the result.
+ */
+ @MainThread
+ public void onExecuteFunction(
+ @NonNull ExecuteAppFunctionRequest request,
+ @NonNull String callingPackage,
+ @NonNull CancellationSignal cancellationSignal,
+ @NonNull Consumer<ExecuteAppFunctionResponse> callback) {
+ onExecuteFunction(request, cancellationSignal, callback);
+ }
+
+ /**
+ * 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 cancellationSignal A {@link CancellationSignal} to cancel the request.
* @param callback A callback to report back the result.
+ * @deprecated Use {@link #onExecuteFunction(ExecuteAppFunctionRequest, String,
+ * CancellationSignal, Consumer)} instead. This method will be removed once usage references
+ * are updated.
*/
@MainThread
+ @Deprecated
public void onExecuteFunction(
@NonNull ExecuteAppFunctionRequest request,
@NonNull CancellationSignal cancellationSignal,
@@ -138,7 +179,6 @@ public abstract class AppFunctionService extends Service {
*
* @param request The function execution request.
* @param callback A callback to report back the result.
- *
* @deprecated Use {@link #onExecuteFunction(ExecuteAppFunctionRequest, CancellationSignal,
* Consumer)} instead. This method will be removed once usage references are updated.
*/
diff --git a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionResponse.java b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionResponse.java
index d87fec7985e9..969e5d58b909 100644
--- a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionResponse.java
+++ b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionResponse.java
@@ -234,12 +234,13 @@ public final class ExecuteAppFunctionResponse {
@IntDef(
prefix = {"RESULT_"},
value = {
- RESULT_OK,
- RESULT_DENIED,
- RESULT_APP_UNKNOWN_ERROR,
- RESULT_INTERNAL_ERROR,
- RESULT_INVALID_ARGUMENT,
- RESULT_DISABLED
+ RESULT_OK,
+ RESULT_DENIED,
+ RESULT_APP_UNKNOWN_ERROR,
+ RESULT_INTERNAL_ERROR,
+ RESULT_INVALID_ARGUMENT,
+ RESULT_DISABLED,
+ RESULT_CANCELLED
})
@Retention(RetentionPolicy.SOURCE)
public @interface ResultCode {}
diff --git a/packages/SettingsLib/SettingsTheme/res/layout-v33/settingslib_preference.xml b/packages/SettingsLib/SettingsTheme/res/layout-v33/settingslib_preference.xml
new file mode 100644
index 000000000000..952562e3d8ea
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/layout-v33/settingslib_preference.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2021 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:minHeight="?android:attr/listPreferredItemHeightSmall"
+ android:gravity="center_vertical"
+ android:paddingLeft="?android:attr/listPreferredItemPaddingLeft"
+ android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+ android:paddingRight="?android:attr/listPreferredItemPaddingRight"
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+ android:background="?android:attr/selectableItemBackground"
+ android:clipToPadding="false"
+ android:baselineAligned="false">
+
+ <include layout="@layout/settingslib_icon_frame"/>
+
+ <include layout="@layout/settingslib_preference_frame"/>
+
+ <!-- Preference should place its actual preference widget here. -->
+ <LinearLayout
+ android:id="@android:id/widget_frame"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:gravity="end|center_vertical"
+ android:paddingLeft="16dp"
+ android:paddingStart="16dp"
+ android:paddingRight="0dp"
+ android:paddingEnd="0dp"
+ android:orientation="vertical"/>
+
+</LinearLayout> \ No newline at end of file
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
index 2a251a59e1d8..dfd296fe006f 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
@@ -39,6 +39,7 @@ import com.android.settingslib.spa.gallery.page.LoadingBarPageProvider
import com.android.settingslib.spa.gallery.page.ProgressBarPageProvider
import com.android.settingslib.spa.gallery.scaffold.NonScrollablePagerPageProvider
import com.android.settingslib.spa.gallery.page.SliderPageProvider
+import com.android.settingslib.spa.gallery.preference.CheckBoxPreferencePageProvider
import com.android.settingslib.spa.gallery.preference.IntroPreferencePageProvider
import com.android.settingslib.spa.gallery.preference.ListPreferencePageProvider
import com.android.settingslib.spa.gallery.preference.MainSwitchPreferencePageProvider
@@ -46,6 +47,7 @@ import com.android.settingslib.spa.gallery.preference.PreferenceMainPageProvider
import com.android.settingslib.spa.gallery.preference.PreferencePageProvider
import com.android.settingslib.spa.gallery.preference.SwitchPreferencePageProvider
import com.android.settingslib.spa.gallery.preference.TopIntroPreferencePageProvider
+import com.android.settingslib.spa.gallery.preference.TwoTargetButtonPreferencePageProvider
import com.android.settingslib.spa.gallery.preference.TwoTargetSwitchPreferencePageProvider
import com.android.settingslib.spa.gallery.preference.ZeroStatePreferencePageProvider
import com.android.settingslib.spa.gallery.scaffold.PagerMainPageProvider
@@ -105,6 +107,8 @@ class GallerySpaEnvironment(context: Context) : SpaEnvironment(context) {
CopyablePageProvider,
IntroPreferencePageProvider,
TopIntroPreferencePageProvider,
+ CheckBoxPreferencePageProvider,
+ TwoTargetButtonPreferencePageProvider,
),
rootPages = listOf(
HomePageProvider.createSettingsPage(),
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/dialog/DialogMainPageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/dialog/DialogMainPageProvider.kt
index c9c81aac01c3..cb055049e6c4 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/dialog/DialogMainPageProvider.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/dialog/DialogMainPageProvider.kt
@@ -19,50 +19,93 @@ package com.android.settingslib.spa.gallery.dialog
import android.os.Bundle
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
-import com.android.settingslib.spa.framework.common.SettingsEntry
-import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.runtime.setValue
import com.android.settingslib.spa.framework.common.SettingsPageProvider
-import com.android.settingslib.spa.framework.common.createSettingsPage
import com.android.settingslib.spa.framework.compose.navigator
import com.android.settingslib.spa.widget.dialog.AlertDialogButton
+import com.android.settingslib.spa.widget.dialog.SettingsAlertDialogWithIcon
import com.android.settingslib.spa.widget.dialog.rememberAlertDialogPresenter
import com.android.settingslib.spa.widget.preference.Preference
import com.android.settingslib.spa.widget.preference.PreferenceModel
+import com.android.settingslib.spa.widget.scaffold.RegularScaffold
+import com.android.settingslib.spa.widget.ui.Category
private const val TITLE = "Category: Dialog"
object DialogMainPageProvider : SettingsPageProvider {
override val name = "DialogMain"
- private val owner = createSettingsPage()
- override fun buildEntry(arguments: Bundle?): List<SettingsEntry> = listOf(
- SettingsEntryBuilder.create("AlertDialog", owner).setUiLayoutFn {
- val alertDialogPresenter = rememberAlertDialogPresenter(
- confirmButton = AlertDialogButton("Ok"),
- dismissButton = AlertDialogButton("Cancel"),
- title = "Title",
- text = { Text("Text") },
- )
- Preference(object : PreferenceModel {
- override val title = "Show AlertDialog"
- override val onClick = alertDialogPresenter::open
- })
- }.build(),
- SettingsEntryBuilder.create("NavDialog", owner).setUiLayoutFn {
- Preference(object : PreferenceModel {
- override val title = "Navigate to Dialog"
- override val onClick = navigator(route = NavDialogProvider.name)
- })
- }.build(),
- )
+ @Composable
+ override fun Page(arguments: Bundle?) {
+ RegularScaffold(TITLE) {
+ Category {
+ AlertDialog()
+ AlertDialogWithIcon()
+ NavDialog()
+ }
+ }
+ }
@Composable
fun Entry() {
- Preference(object : PreferenceModel {
- override val title = TITLE
- override val onClick = navigator(name)
- })
+ Preference(
+ object : PreferenceModel {
+ override val title = TITLE
+ override val onClick = navigator(name)
+ }
+ )
}
override fun getTitle(arguments: Bundle?) = TITLE
}
+
+@Composable
+private fun AlertDialog() {
+ val alertDialogPresenter =
+ rememberAlertDialogPresenter(
+ confirmButton = AlertDialogButton("Ok"),
+ dismissButton = AlertDialogButton("Cancel"),
+ title = "Title",
+ text = { Text("Text") },
+ )
+ Preference(
+ object : PreferenceModel {
+ override val title = "Show AlertDialog"
+ override val onClick = alertDialogPresenter::open
+ }
+ )
+}
+
+@Composable
+private fun AlertDialogWithIcon() {
+ var openDialog by rememberSaveable { mutableStateOf(false) }
+ val close = { openDialog = false }
+ val open = { openDialog = true }
+ if (openDialog) {
+ SettingsAlertDialogWithIcon(
+ title = "Title",
+ onDismissRequest = close,
+ confirmButton = AlertDialogButton("OK", onClick = close),
+ dismissButton = AlertDialogButton("Dismiss", onClick = close),
+ ) {}
+ }
+ Preference(
+ object : PreferenceModel {
+ override val title = "Show AlertDialogWithIcon"
+ override val onClick = open
+ }
+ )
+}
+
+@Composable
+private fun NavDialog() {
+ Preference(
+ object : PreferenceModel {
+ override val title = "Navigate to Dialog"
+ override val onClick = navigator(route = NavDialogProvider.name)
+ }
+ )
+}
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/CheckBoxPreferencePageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/CheckBoxPreferencePageProvider.kt
new file mode 100644
index 000000000000..c2b67cbfe56f
--- /dev/null
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/CheckBoxPreferencePageProvider.kt
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.gallery.preference
+
+import android.os.Bundle
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.outlined.AirplanemodeActive
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.runtime.setValue
+import com.android.settingslib.spa.framework.common.SettingsPageProvider
+import com.android.settingslib.spa.framework.compose.navigator
+import com.android.settingslib.spa.widget.preference.CheckboxPreference
+import com.android.settingslib.spa.widget.preference.CheckboxPreferenceModel
+import com.android.settingslib.spa.widget.preference.Preference
+import com.android.settingslib.spa.widget.preference.PreferenceModel
+import com.android.settingslib.spa.widget.scaffold.RegularScaffold
+import com.android.settingslib.spa.widget.ui.Category
+import com.android.settingslib.spa.widget.ui.SettingsIcon
+
+private const val TITLE = "Sample CheckBoxPreference"
+
+object CheckBoxPreferencePageProvider : SettingsPageProvider {
+ override val name = "CheckBoxPreference"
+
+ @Composable
+ override fun Page(arguments: Bundle?) {
+ RegularScaffold(TITLE) {
+ Category {
+ var checked1 by rememberSaveable { mutableStateOf(true) }
+ CheckboxPreference(
+ object : CheckboxPreferenceModel {
+ override val title = "Use Dark theme"
+ override val checked = { checked1 }
+ override val onCheckedChange = { newChecked: Boolean ->
+ checked1 = newChecked
+ }
+ }
+ )
+ var checked2 by rememberSaveable { mutableStateOf(false) }
+ CheckboxPreference(
+ object : CheckboxPreferenceModel {
+ override val title = "Use Dark theme"
+ override val summary = { "Summary" }
+ override val checked = { checked2 }
+ override val onCheckedChange = { newChecked: Boolean ->
+ checked2 = newChecked
+ }
+ }
+ )
+ var checked3 by rememberSaveable { mutableStateOf(true) }
+ CheckboxPreference(
+ object : CheckboxPreferenceModel {
+ override val title = "Use Dark theme"
+ override val summary = { "Summary" }
+ override val checked = { checked3 }
+ override val onCheckedChange = { newChecked: Boolean ->
+ checked3 = newChecked
+ }
+ override val icon =
+ @Composable {
+ SettingsIcon(imageVector = Icons.Outlined.AirplanemodeActive)
+ }
+ }
+ )
+ }
+ }
+ }
+
+ @Composable
+ fun Entry() {
+ Preference(
+ object : PreferenceModel {
+ override val title = TITLE
+ override val onClick = navigator(name)
+ }
+ )
+ }
+
+ override fun getTitle(arguments: Bundle?): String {
+ return TITLE
+ }
+}
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferenceMainPageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferenceMainPageProvider.kt
index 831b43942e98..3cfb5368e24a 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferenceMainPageProvider.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferenceMainPageProvider.kt
@@ -36,11 +36,13 @@ object PreferenceMainPageProvider : SettingsPageProvider {
Category {
PreferencePageProvider.Entry()
ListPreferencePageProvider.Entry()
+ CheckBoxPreferencePageProvider.Entry()
}
Category {
SwitchPreferencePageProvider.Entry()
MainSwitchPreferencePageProvider.Entry()
TwoTargetSwitchPreferencePageProvider.Entry()
+ TwoTargetButtonPreferencePageProvider.Entry()
}
Category {
ZeroStatePreferencePageProvider.Entry()
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/TwoTargetButtonPreferencePageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/TwoTargetButtonPreferencePageProvider.kt
new file mode 100644
index 000000000000..c6e834a3fa7b
--- /dev/null
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/TwoTargetButtonPreferencePageProvider.kt
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.gallery.preference
+
+import android.os.Bundle
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.outlined.Add
+import androidx.compose.material.icons.outlined.Info
+import androidx.compose.runtime.Composable
+import com.android.settingslib.spa.framework.common.SettingsPageProvider
+import com.android.settingslib.spa.framework.compose.navigator
+import com.android.settingslib.spa.widget.preference.Preference
+import com.android.settingslib.spa.widget.preference.PreferenceModel
+import com.android.settingslib.spa.widget.preference.TwoTargetButtonPreference
+import com.android.settingslib.spa.widget.scaffold.RegularScaffold
+import com.android.settingslib.spa.widget.ui.Category
+
+private const val TITLE = "Sample TwoTargetButtonPreference"
+
+object TwoTargetButtonPreferencePageProvider : SettingsPageProvider {
+ override val name = "TwoTargetButtonPreference"
+
+ @Composable
+ override fun Page(arguments: Bundle?) {
+ RegularScaffold(TITLE) {
+ Category {
+ SampleTwoTargetButtonPreference()
+ SampleTwoTargetButtonPreferenceWithSummary()
+ }
+ }
+ }
+
+ @Composable
+ fun Entry() {
+ Preference(
+ object : PreferenceModel {
+ override val title = TITLE
+ override val onClick = navigator(name)
+ }
+ )
+ }
+}
+
+@Composable
+private fun SampleTwoTargetButtonPreference() {
+ TwoTargetButtonPreference(
+ title = "TwoTargetButton",
+ summary = { "" },
+ buttonIcon = Icons.Outlined.Info,
+ buttonIconDescription = "info",
+ onClick = {},
+ onButtonClick = {},
+ )
+}
+
+@Composable
+private fun SampleTwoTargetButtonPreferenceWithSummary() {
+ TwoTargetButtonPreference(
+ title = "TwoTargetButton",
+ summary = { "summary" },
+ buttonIcon = Icons.Outlined.Add,
+ buttonIconDescription = "info",
+ onClick = {},
+ onButtonClick = {},
+ )
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt
index ab95162fb142..5dca63724faf 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt
@@ -85,4 +85,6 @@ object SettingsDimension {
val illustrationMaxHeight = 300.dp
val illustrationPadding = paddingLarge
val illustrationCornerRadius = 28.dp
+
+ val preferenceMinHeight = 72.dp
}
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 58a83fa72532..4cf270dca2bb 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
@@ -17,7 +17,6 @@
package com.android.settingslib.spa.widget.dialog
import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
@@ -26,12 +25,13 @@ import androidx.compose.material.icons.filled.WarningAmber
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button
import androidx.compose.material3.Icon
+import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
-import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.window.DialogProperties
+import com.android.settingslib.spa.framework.theme.isSpaExpressiveEnabled
@Composable
fun SettingsAlertDialogWithIcon(
@@ -57,7 +57,9 @@ fun SettingsAlertDialogWithIcon(
title?.let {
{
CenterRow {
- Text(it, modifier = Modifier.fillMaxWidth(), textAlign = TextAlign.Center)
+ if (isSpaExpressiveEnabled)
+ Text(it, style = MaterialTheme.typography.bodyLarge)
+ else Text(it)
}
}
},
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/BaseLayout.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/BaseLayout.kt
index c68ec78b1ba6..acb96be64a34 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/BaseLayout.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/BaseLayout.kt
@@ -22,6 +22,7 @@ import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
@@ -35,6 +36,7 @@ import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.min
import com.android.settingslib.spa.framework.theme.SettingsDimension
import com.android.settingslib.spa.framework.theme.SettingsOpacity.alphaForEnabled
import com.android.settingslib.spa.framework.theme.SettingsShape
@@ -62,7 +64,8 @@ internal fun BaseLayout(
.semantics(mergeDescendants = true) {}
.then(
if (isSpaExpressiveEnabled)
- Modifier.clip(SettingsShape.CornerExtraSmall)
+ Modifier.heightIn(min = SettingsDimension.preferenceMinHeight)
+ .clip(SettingsShape.CornerExtraSmall)
.background(MaterialTheme.colorScheme.surfaceBright)
else Modifier
)
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/MainSwitchPreference.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/MainSwitchPreference.kt
index b28e88eb8af8..e6a23662e9f0 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/MainSwitchPreference.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/MainSwitchPreference.kt
@@ -17,6 +17,7 @@
package com.android.settingslib.spa.widget.preference
import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.MaterialTheme
@@ -25,6 +26,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.min
import com.android.settingslib.spa.framework.theme.SettingsDimension
import com.android.settingslib.spa.framework.theme.SettingsShape
import com.android.settingslib.spa.framework.theme.SettingsTheme
@@ -35,13 +37,19 @@ import com.android.settingslib.spa.framework.util.EntryHighlight
fun MainSwitchPreference(model: SwitchPreferenceModel) {
EntryHighlight {
Surface(
- modifier = Modifier.padding(SettingsDimension.itemPaddingEnd),
- color = when (model.checked()) {
- true -> MaterialTheme.colorScheme.primaryContainer
- else -> MaterialTheme.colorScheme.secondaryContainer
- },
- shape = if (isSpaExpressiveEnabled) CircleShape
- else SettingsShape.CornerExtraLarge,
+ modifier =
+ Modifier.padding(SettingsDimension.itemPaddingEnd)
+ .then(
+ if (isSpaExpressiveEnabled)
+ Modifier.heightIn(min = SettingsDimension.preferenceMinHeight)
+ else Modifier
+ ),
+ color =
+ when (model.checked()) {
+ true -> MaterialTheme.colorScheme.primaryContainer
+ else -> MaterialTheme.colorScheme.secondaryContainer
+ },
+ shape = if (isSpaExpressiveEnabled) CircleShape else SettingsShape.CornerExtraLarge,
) {
InternalSwitchPreference(
title = model.title,
@@ -61,16 +69,20 @@ fun MainSwitchPreference(model: SwitchPreferenceModel) {
private fun MainSwitchPreferencePreview() {
SettingsTheme {
Column {
- MainSwitchPreference(object : SwitchPreferenceModel {
- override val title = "Use Dark theme"
- override val checked = { true }
- override val onCheckedChange: (Boolean) -> Unit = {}
- })
- MainSwitchPreference(object : SwitchPreferenceModel {
- override val title = "Use Dark theme"
- override val checked = { false }
- override val onCheckedChange: (Boolean) -> Unit = {}
- })
+ MainSwitchPreference(
+ object : SwitchPreferenceModel {
+ override val title = "Use Dark theme"
+ override val checked = { true }
+ override val onCheckedChange: (Boolean) -> Unit = {}
+ }
+ )
+ MainSwitchPreference(
+ object : SwitchPreferenceModel {
+ override val title = "Use Dark theme"
+ override val checked = { false }
+ override val onCheckedChange: (Boolean) -> Unit = {}
+ }
+ )
}
}
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/ZeroStatePreference.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/ZeroStatePreference.kt
index b771f367e697..541922335387 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/ZeroStatePreference.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/ZeroStatePreference.kt
@@ -54,7 +54,7 @@ fun ZeroStatePreference(icon: ImageVector, text: String? = null, description: St
val zeroStateShape = remember {
RoundedPolygon.star(
numVerticesPerRadius = 6,
- innerRadius = 0.75f,
+ innerRadius = 0.8f,
rounding = CornerRounding(0.3f)
)
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
index 8b6351e3c289..7fdb32cb63e9 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
@@ -60,7 +60,7 @@ public class CachedBluetoothDeviceManager {
mBtManager = localBtManager;
mHearingAidDeviceManager = new HearingAidDeviceManager(context, localBtManager,
mCachedDevices);
- mCsipDeviceManager = new CsipDeviceManager(localBtManager, mCachedDevices);
+ mCsipDeviceManager = new CsipDeviceManager(context, localBtManager, mCachedDevices);
}
public synchronized Collection<CachedBluetoothDevice> getCachedDevicesCopy() {
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java
index 6dab22454baf..b9f16edf6e77 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java
@@ -21,8 +21,10 @@ import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothLeBroadcastMetadata;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.BluetoothUuid;
+import android.content.Context;
import android.os.Build;
import android.os.ParcelUuid;
+import android.os.UserManager;
import android.util.Log;
import androidx.annotation.ChecksSdkIntAtLeast;
@@ -45,9 +47,11 @@ public class CsipDeviceManager {
private final LocalBluetoothManager mBtManager;
private final List<CachedBluetoothDevice> mCachedDevices;
+ private final Context mContext;
- CsipDeviceManager(LocalBluetoothManager localBtManager,
+ CsipDeviceManager(Context context, LocalBluetoothManager localBtManager,
List<CachedBluetoothDevice> cachedDevices) {
+ mContext = context;
mBtManager = localBtManager;
mCachedDevices = cachedDevices;
}
@@ -379,7 +383,11 @@ public class CsipDeviceManager {
preferredMainDevice.refresh();
hasChanged = true;
}
- syncAudioSharingSourceIfNeeded(preferredMainDevice);
+ if (isWorkProfile()) {
+ log("addMemberDevicesIntoMainDevice: skip sync source for work profile");
+ } else {
+ syncAudioSharingSourceIfNeeded(preferredMainDevice);
+ }
}
if (hasChanged) {
log("addMemberDevicesIntoMainDevice: After changed, CachedBluetoothDevice list: "
@@ -388,6 +396,11 @@ public class CsipDeviceManager {
return hasChanged;
}
+ private boolean isWorkProfile() {
+ UserManager userManager = mContext.getSystemService(UserManager.class);
+ return userManager != null && userManager.isManagedProfile();
+ }
+
private void syncAudioSharingSourceIfNeeded(CachedBluetoothDevice mainDevice) {
boolean isAudioSharingEnabled = BluetoothUtils.isAudioSharingEnabled();
if (isAudioSharingEnabled) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
index 364e95c61ca8..6a9d5687370e 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
@@ -18,6 +18,8 @@ package com.android.settingslib.bluetooth;
import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+import static java.util.stream.Collectors.toList;
+
import android.annotation.CallbackExecutor;
import android.annotation.IntDef;
import android.bluetooth.BluetoothAdapter;
@@ -64,6 +66,7 @@ import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;
@@ -84,6 +87,8 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile {
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 String BLUETOOTH_LE_BROADCAST_PRIMARY_DEVICE_GROUP_ID =
+ "bluetooth_le_broadcast_primary_device_group_id";
public static final int BROADCAST_STATE_UNKNOWN = 0;
public static final int BROADCAST_STATE_ON = 1;
public static final int BROADCAST_STATE_OFF = 2;
@@ -1121,6 +1126,10 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile {
/** Update fallback active device if needed. */
public void updateFallbackActiveDeviceIfNeeded() {
+ if (isWorkProfile(mContext)) {
+ Log.d(TAG, "Skip updateFallbackActiveDeviceIfNeeded for work profile.");
+ return;
+ }
if (mServiceBroadcast == null) {
Log.d(TAG, "Skip updateFallbackActiveDeviceIfNeeded due to broadcast profile is null");
return;
@@ -1135,71 +1144,114 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile {
Log.d(TAG, "Skip updateFallbackActiveDeviceIfNeeded due to assistant profile is null");
return;
}
- List<BluetoothDevice> devicesInBroadcast = getDevicesInBroadcast();
- if (devicesInBroadcast.isEmpty()) {
+ Map<Integer, List<BluetoothDevice>> deviceGroupsInBroadcast = getDeviceGroupsInBroadcast();
+ if (deviceGroupsInBroadcast.isEmpty()) {
Log.d(TAG, "Skip updateFallbackActiveDeviceIfNeeded due to no sinks in broadcast");
return;
}
- List<BluetoothDevice> devices =
- BluetoothAdapter.getDefaultAdapter().getMostRecentlyConnectedDevices();
- BluetoothDevice targetDevice = null;
- // Find the earliest connected device in sharing session.
- int targetDeviceIdx = -1;
- for (BluetoothDevice device : devicesInBroadcast) {
- if (devices.contains(device)) {
- int idx = devices.indexOf(device);
- if (idx > targetDeviceIdx) {
- targetDeviceIdx = idx;
- targetDevice = device;
+ int targetGroupId = BluetoothCsipSetCoordinator.GROUP_ID_INVALID;
+ int fallbackActiveGroupId = BluetoothUtils.getPrimaryGroupIdForBroadcast(
+ mContext.getContentResolver());
+ if (Flags.audioSharingHysteresisModeFix()) {
+ int userPreferredPrimaryGroupId = getUserPreferredPrimaryGroupId();
+ if (userPreferredPrimaryGroupId != BluetoothCsipSetCoordinator.GROUP_ID_INVALID
+ && deviceGroupsInBroadcast.containsKey(userPreferredPrimaryGroupId)) {
+ if (userPreferredPrimaryGroupId == fallbackActiveGroupId) {
+ Log.d(TAG, "Skip updateFallbackActiveDeviceIfNeeded, already user preferred");
+ return;
+ } else {
+ targetGroupId = userPreferredPrimaryGroupId;
}
}
- }
- if (targetDevice == null) {
- Log.d(TAG, "Skip updateFallbackActiveDeviceIfNeeded, target is null");
+ if (targetGroupId == BluetoothCsipSetCoordinator.GROUP_ID_INVALID) {
+ // If there is no user preferred primary device, set the earliest connected
+ // device in sharing session as the fallback.
+ targetGroupId = getEarliestConnectedDeviceGroup(deviceGroupsInBroadcast);
+ }
+ } else {
+ // Set the earliest connected device in sharing session as the fallback.
+ targetGroupId = getEarliestConnectedDeviceGroup(deviceGroupsInBroadcast);
+ }
+ Log.d(TAG, "updateFallbackActiveDeviceIfNeeded, target group id = " + targetGroupId);
+ if (targetGroupId == BluetoothCsipSetCoordinator.GROUP_ID_INVALID) return;
+ if (targetGroupId == fallbackActiveGroupId) {
+ Log.d(TAG, "Skip updateFallbackActiveDeviceIfNeeded, already is fallback");
return;
}
- CachedBluetoothDevice targetCachedDevice = mDeviceManager.findDevice(targetDevice);
+ CachedBluetoothDevice targetCachedDevice = getMainDevice(
+ deviceGroupsInBroadcast.get(targetGroupId));
if (targetCachedDevice == null) {
- Log.d(TAG, "Skip updateFallbackActiveDeviceIfNeeded, fail to find cached bt device");
- return;
- }
- int fallbackActiveGroupId = getFallbackActiveGroupId();
- if (fallbackActiveGroupId != BluetoothCsipSetCoordinator.GROUP_ID_INVALID
- && BluetoothUtils.getGroupId(targetCachedDevice) == fallbackActiveGroupId) {
- Log.d(
- TAG,
- "Skip updateFallbackActiveDeviceIfNeeded, already is fallback: "
- + fallbackActiveGroupId);
+ Log.d(TAG, "Skip updateFallbackActiveDeviceIfNeeded, fail to find main device");
return;
}
Log.d(
TAG,
"updateFallbackActiveDeviceIfNeeded, set active device: "
- + targetDevice.getAnonymizedAddress());
+ + targetCachedDevice.getDevice());
targetCachedDevice.setActive();
}
- private List<BluetoothDevice> getDevicesInBroadcast() {
+ @NonNull
+ private Map<Integer, List<BluetoothDevice>> getDeviceGroupsInBroadcast() {
boolean hysteresisModeFixEnabled = Flags.audioSharingHysteresisModeFix();
List<BluetoothDevice> connectedDevices = mServiceBroadcastAssistant.getConnectedDevices();
return connectedDevices.stream()
.filter(
- bluetoothDevice -> {
+ device -> {
List<BluetoothLeBroadcastReceiveState> sourceList =
- mServiceBroadcastAssistant.getAllSources(
- bluetoothDevice);
+ mServiceBroadcastAssistant.getAllSources(device);
return !sourceList.isEmpty() && sourceList.stream().anyMatch(
source -> hysteresisModeFixEnabled
? BluetoothUtils.isSourceMatched(source, mBroadcastId)
: BluetoothUtils.isConnected(source));
})
- .collect(Collectors.toList());
+ .collect(Collectors.groupingBy(
+ device -> BluetoothUtils.getGroupId(mDeviceManager.findDevice(device))));
+ }
+
+ private int getEarliestConnectedDeviceGroup(
+ @NonNull Map<Integer, List<BluetoothDevice>> deviceGroups) {
+ List<BluetoothDevice> devices =
+ BluetoothAdapter.getDefaultAdapter().getMostRecentlyConnectedDevices();
+ // Find the earliest connected device in sharing session.
+ int targetDeviceIdx = -1;
+ int targetGroupId = BluetoothCsipSetCoordinator.GROUP_ID_INVALID;
+ for (Map.Entry<Integer, List<BluetoothDevice>> entry : deviceGroups.entrySet()) {
+ for (BluetoothDevice device : entry.getValue()) {
+ if (devices.contains(device)) {
+ int idx = devices.indexOf(device);
+ if (idx > targetDeviceIdx) {
+ targetDeviceIdx = idx;
+ targetGroupId = entry.getKey();
+ }
+ }
+ }
+ }
+ return targetGroupId;
+ }
+
+ @Nullable
+ private CachedBluetoothDevice getMainDevice(@Nullable List<BluetoothDevice> devices) {
+ if (devices == null || devices.size() == 1) return null;
+ List<CachedBluetoothDevice> cachedDevices =
+ devices.stream()
+ .map(device -> mDeviceManager.findDevice(device))
+ .filter(Objects::nonNull)
+ .collect(toList());
+ for (CachedBluetoothDevice cachedDevice : cachedDevices) {
+ if (!cachedDevice.getMemberDevice().isEmpty()) {
+ return cachedDevice;
+ }
+ }
+ CachedBluetoothDevice mainDevice = cachedDevices.isEmpty() ? null : cachedDevices.get(0);
+ return mainDevice;
}
- private int getFallbackActiveGroupId() {
+ private int getUserPreferredPrimaryGroupId() {
+ // TODO: use real key name in SettingsProvider
return Settings.Secure.getInt(
- mContext.getContentResolver(),
- "bluetooth_le_broadcast_fallback_active_group_id",
+ mContentResolver,
+ BLUETOOTH_LE_BROADCAST_PRIMARY_DEVICE_GROUP_ID,
BluetoothCsipSetCoordinator.GROUP_ID_INVALID);
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastAssistantCallbackExt.kt b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastAssistantCallbackExt.kt
index 24815fabfdf2..91a99aed6db5 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastAssistantCallbackExt.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastAssistantCallbackExt.kt
@@ -16,7 +16,6 @@
package com.android.settingslib.bluetooth
-import android.bluetooth.BluetoothAdapter
import android.bluetooth.BluetoothDevice
import android.bluetooth.BluetoothLeBroadcastAssistant
import android.bluetooth.BluetoothLeBroadcastMetadata
@@ -82,9 +81,5 @@ val LocalBluetoothLeBroadcastAssistant.onSourceConnectedOrRemoved: Flow<Unit>
ConcurrentUtils.DIRECT_EXECUTOR,
callback,
)
- awaitClose {
- if (BluetoothAdapter.getDefaultAdapter()?.isEnabled == true) {
- unregisterServiceCallBack(callback)
- }
- }
+ awaitClose { unregisterServiceCallBack(callback) }
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/IDeviceSettingsConfigProviderService.aidl b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/IDeviceSettingsConfigProviderService.aidl
index 9cf49070a62c..c85756ed067a 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/IDeviceSettingsConfigProviderService.aidl
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/IDeviceSettingsConfigProviderService.aidl
@@ -20,5 +20,5 @@ import com.android.settingslib.bluetooth.devicesettings.DeviceInfo;
import com.android.settingslib.bluetooth.devicesettings.IGetDeviceSettingsConfigCallback;
interface IDeviceSettingsConfigProviderService {
- oneway void getDeviceSettingsConfig(in DeviceInfo device, in IGetDeviceSettingsConfigCallback callback);
+ void getDeviceSettingsConfig(in DeviceInfo device, in IGetDeviceSettingsConfigCallback callback);
} \ No newline at end of file
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 4af0504bd73a..a33fcc6747b4 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
@@ -23,6 +23,7 @@ import android.content.Intent
import android.content.ServiceConnection
import android.os.IBinder
import android.os.IInterface
+import android.os.RemoteException
import android.text.TextUtils
import android.util.Log
import com.android.settingslib.bluetooth.BluetoothUtils
@@ -55,6 +56,7 @@ import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.emitAll
import kotlinx.coroutines.flow.filterIsInstance
import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.flow.flatMapConcat
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flow
@@ -100,6 +102,9 @@ class DeviceSettingServiceConnection(
private var isServiceEnabled =
coroutineScope.async(backgroundCoroutineContext, start = CoroutineStart.LAZY) {
val states = getSettingsProviderServices()?.values ?: return@async false
+ if (states.isEmpty()) {
+ return@async true
+ }
combine(states) { it.toList() }
.mapNotNull { allStatus ->
if (allStatus.any { it is ServiceConnectionStatus.Failed }) {
@@ -114,7 +119,7 @@ class DeviceSettingServiceConnection(
null
}
}
- .first()
+ .firstOrNull() ?: false
}
private var config =
@@ -131,9 +136,15 @@ class DeviceSettingServiceConnection(
is ServiceConnectionStatus.Connected ->
flowOf(
getDeviceSettingsConfigFromService(
- deviceInfo { setBluetoothAddress(cachedDevice.address) },
- it.service,
- )
+ deviceInfo { setBluetoothAddress(cachedDevice.address) },
+ it.service,
+ )
+ .also { config ->
+ Log.i(
+ TAG,
+ "device setting config for $cachedDevice is $config",
+ )
+ }
)
ServiceConnectionStatus.Connecting -> flowOf()
ServiceConnectionStatus.Failed -> flowOf(null)
@@ -146,21 +157,26 @@ class DeviceSettingServiceConnection(
deviceInfo: DeviceInfo,
service: IDeviceSettingsConfigProviderService,
): DeviceSettingsConfig? = suspendCancellableCoroutine { continuation ->
- service.getDeviceSettingsConfig(
- deviceInfo,
- object : IGetDeviceSettingsConfigCallback.Stub() {
- override fun onResult(
- status: DeviceSettingsConfigServiceStatus,
- config: DeviceSettingsConfig?,
- ) {
- if (!status.success) {
- continuation.resume(null)
- } else {
- continuation.resume(config)
+ try {
+ service.getDeviceSettingsConfig(
+ deviceInfo,
+ object : IGetDeviceSettingsConfigCallback.Stub() {
+ override fun onResult(
+ status: DeviceSettingsConfigServiceStatus,
+ config: DeviceSettingsConfig?,
+ ) {
+ if (!status.success) {
+ continuation.resume(null)
+ } else {
+ continuation.resume(config)
+ }
}
- }
- },
- )
+ },
+ )
+ } catch (e: RemoteException) {
+ Log.i(TAG, "Fail to get config")
+ continuation.resume(null)
+ }
}
private val settingIdToItemMapping =
@@ -298,13 +314,16 @@ class DeviceSettingServiceConnection(
val serviceConnection =
object : ServiceConnection {
override fun onServiceConnected(name: ComponentName, service: IBinder) {
+ Log.i(TAG, "Service connected for $intent")
launch { send(ServiceConnectionStatus.Connected(transform(service))) }
}
override fun onServiceDisconnected(name: ComponentName?) {
+ Log.i(TAG, "Service disconnected for $intent")
launch { send(ServiceConnectionStatus.Connecting) }
}
}
+ Log.i(TAG, "Try to bind service for $intent")
if (
!context.bindService(
intent,
diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/data/repository/FakeZenModeRepository.kt b/packages/SettingsLib/src/com/android/settingslib/notification/data/repository/FakeZenModeRepository.kt
index 43d79466d6ca..23be7baa6496 100644
--- a/packages/SettingsLib/src/com/android/settingslib/notification/data/repository/FakeZenModeRepository.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/data/repository/FakeZenModeRepository.kt
@@ -78,6 +78,10 @@ class FakeZenModeRepository : ZenModeRepository {
mutableModesFlow.value = (mutableModesFlow.value.filter { it.id != modeId }) + mode
}
+ fun clearModes() {
+ mutableModesFlow.value = listOf()
+ }
+
fun getMode(id: String): ZenMode? {
return mutableModesFlow.value.find { it.id == id }
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothEventManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothEventManagerTest.java
index b1489be943e6..22b315084f3f 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothEventManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothEventManagerTest.java
@@ -22,6 +22,7 @@ import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -36,6 +37,7 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.UserHandle;
+import android.os.UserManager;
import android.platform.test.flag.junit.SetFlagsRule;
import android.telephony.TelephonyManager;
@@ -96,6 +98,8 @@ public class BluetoothEventManagerTest {
private LocalBluetoothProfileManager mLocalProfileManager;
@Mock
private BluetoothUtils.ErrorListener mErrorListener;
+ @Mock
+ private LocalBluetoothLeBroadcast mBroadcast;
private Context mContext;
private Intent mIntent;
@@ -107,7 +111,7 @@ public class BluetoothEventManagerTest {
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
- mContext = RuntimeEnvironment.application;
+ mContext = spy(RuntimeEnvironment.application);
mBluetoothEventManager =
new BluetoothEventManager(
@@ -208,28 +212,14 @@ public class BluetoothEventManagerTest {
*/
@Test
public void dispatchProfileConnectionStateChanged_flagOff_noUpdateFallbackDevice() {
- ShadowBluetoothAdapter shadowBluetoothAdapter =
- Shadow.extract(BluetoothAdapter.getDefaultAdapter());
- shadowBluetoothAdapter.setIsLeAudioBroadcastSourceSupported(
- BluetoothStatusCodes.FEATURE_SUPPORTED);
- shadowBluetoothAdapter.setIsLeAudioBroadcastAssistantSupported(
- BluetoothStatusCodes.FEATURE_SUPPORTED);
- mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
- LocalBluetoothLeBroadcast broadcast = mock(LocalBluetoothLeBroadcast.class);
- when(broadcast.isProfileReady()).thenReturn(true);
- LocalBluetoothLeBroadcastAssistant assistant =
- mock(LocalBluetoothLeBroadcastAssistant.class);
- when(assistant.isProfileReady()).thenReturn(true);
- LocalBluetoothProfileManager profileManager = mock(LocalBluetoothProfileManager.class);
- when(profileManager.getLeAudioBroadcastProfile()).thenReturn(broadcast);
- when(profileManager.getLeAudioBroadcastAssistantProfile()).thenReturn(assistant);
- when(mBtManager.getProfileManager()).thenReturn(profileManager);
+ setUpAudioSharing(/* enableFlag= */ false, /* enableFeature= */ true, /* enableProfile= */
+ true, /* workProfile= */ false);
mBluetoothEventManager.dispatchProfileConnectionStateChanged(
mCachedBluetoothDevice,
BluetoothProfile.STATE_DISCONNECTED,
BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT);
- verify(broadcast, times(0)).updateFallbackActiveDeviceIfNeeded();
+ verify(mBroadcast, times(0)).updateFallbackActiveDeviceIfNeeded();
}
/**
@@ -239,28 +229,14 @@ public class BluetoothEventManagerTest {
*/
@Test
public void dispatchProfileConnectionStateChanged_notSupport_noUpdateFallbackDevice() {
- ShadowBluetoothAdapter shadowBluetoothAdapter =
- Shadow.extract(BluetoothAdapter.getDefaultAdapter());
- shadowBluetoothAdapter.setIsLeAudioBroadcastSourceSupported(
- BluetoothStatusCodes.FEATURE_NOT_SUPPORTED);
- shadowBluetoothAdapter.setIsLeAudioBroadcastAssistantSupported(
- BluetoothStatusCodes.FEATURE_SUPPORTED);
- mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
- LocalBluetoothLeBroadcast broadcast = mock(LocalBluetoothLeBroadcast.class);
- when(broadcast.isProfileReady()).thenReturn(true);
- LocalBluetoothLeBroadcastAssistant assistant =
- mock(LocalBluetoothLeBroadcastAssistant.class);
- when(assistant.isProfileReady()).thenReturn(true);
- LocalBluetoothProfileManager profileManager = mock(LocalBluetoothProfileManager.class);
- when(profileManager.getLeAudioBroadcastProfile()).thenReturn(broadcast);
- when(profileManager.getLeAudioBroadcastAssistantProfile()).thenReturn(assistant);
- when(mBtManager.getProfileManager()).thenReturn(profileManager);
+ setUpAudioSharing(/* enableFlag= */ true, /* enableFeature= */ false, /* enableProfile= */
+ true, /* workProfile= */ false);
mBluetoothEventManager.dispatchProfileConnectionStateChanged(
mCachedBluetoothDevice,
BluetoothProfile.STATE_DISCONNECTED,
BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT);
- verify(broadcast, times(0)).updateFallbackActiveDeviceIfNeeded();
+ verify(mBroadcast, times(0)).updateFallbackActiveDeviceIfNeeded();
}
/**
@@ -270,28 +246,14 @@ public class BluetoothEventManagerTest {
*/
@Test
public void dispatchProfileConnectionStateChanged_profileNotReady_noUpdateFallbackDevice() {
- ShadowBluetoothAdapter shadowBluetoothAdapter =
- Shadow.extract(BluetoothAdapter.getDefaultAdapter());
- shadowBluetoothAdapter.setIsLeAudioBroadcastSourceSupported(
- BluetoothStatusCodes.FEATURE_SUPPORTED);
- shadowBluetoothAdapter.setIsLeAudioBroadcastAssistantSupported(
- BluetoothStatusCodes.FEATURE_SUPPORTED);
- mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
- LocalBluetoothLeBroadcast broadcast = mock(LocalBluetoothLeBroadcast.class);
- when(broadcast.isProfileReady()).thenReturn(false);
- LocalBluetoothLeBroadcastAssistant assistant =
- mock(LocalBluetoothLeBroadcastAssistant.class);
- when(assistant.isProfileReady()).thenReturn(true);
- LocalBluetoothProfileManager profileManager = mock(LocalBluetoothProfileManager.class);
- when(profileManager.getLeAudioBroadcastProfile()).thenReturn(broadcast);
- when(profileManager.getLeAudioBroadcastAssistantProfile()).thenReturn(assistant);
- when(mBtManager.getProfileManager()).thenReturn(profileManager);
+ setUpAudioSharing(/* enableFlag= */ true, /* enableFeature= */ true, /* enableProfile= */
+ false, /* workProfile= */ false);
mBluetoothEventManager.dispatchProfileConnectionStateChanged(
mCachedBluetoothDevice,
BluetoothProfile.STATE_DISCONNECTED,
BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT);
- verify(broadcast, times(0)).updateFallbackActiveDeviceIfNeeded();
+ verify(mBroadcast, times(0)).updateFallbackActiveDeviceIfNeeded();
}
/**
@@ -301,28 +263,31 @@ public class BluetoothEventManagerTest {
*/
@Test
public void dispatchProfileConnectionStateChanged_notAssistantProfile_noUpdateFallbackDevice() {
- ShadowBluetoothAdapter shadowBluetoothAdapter =
- Shadow.extract(BluetoothAdapter.getDefaultAdapter());
- shadowBluetoothAdapter.setIsLeAudioBroadcastSourceSupported(
- BluetoothStatusCodes.FEATURE_SUPPORTED);
- shadowBluetoothAdapter.setIsLeAudioBroadcastAssistantSupported(
- BluetoothStatusCodes.FEATURE_SUPPORTED);
- mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
- LocalBluetoothLeBroadcast broadcast = mock(LocalBluetoothLeBroadcast.class);
- when(broadcast.isProfileReady()).thenReturn(true);
- LocalBluetoothLeBroadcastAssistant assistant =
- mock(LocalBluetoothLeBroadcastAssistant.class);
- when(assistant.isProfileReady()).thenReturn(true);
- LocalBluetoothProfileManager profileManager = mock(LocalBluetoothProfileManager.class);
- when(profileManager.getLeAudioBroadcastProfile()).thenReturn(broadcast);
- when(profileManager.getLeAudioBroadcastAssistantProfile()).thenReturn(assistant);
- when(mBtManager.getProfileManager()).thenReturn(profileManager);
+ setUpAudioSharing(/* enableFlag= */ true, /* enableFeature= */ true, /* enableProfile= */
+ true, /* workProfile= */ false);
mBluetoothEventManager.dispatchProfileConnectionStateChanged(
mCachedBluetoothDevice,
BluetoothProfile.STATE_DISCONNECTED,
BluetoothProfile.LE_AUDIO);
- verify(broadcast, times(0)).updateFallbackActiveDeviceIfNeeded();
+ verify(mBroadcast, times(0)).updateFallbackActiveDeviceIfNeeded();
+ }
+
+ /**
+ * dispatchProfileConnectionStateChanged should not call {@link
+ * LocalBluetoothLeBroadcast}#updateFallbackActiveDeviceIfNeeded when triggered for
+ * work profile.
+ */
+ @Test
+ public void dispatchProfileConnectionStateChanged_workProfile_noUpdateFallbackDevice() {
+ setUpAudioSharing(/* enableFlag= */ true, /* enableFeature= */ true, /* enableProfile= */
+ true, /* workProfile= */ true);
+ mBluetoothEventManager.dispatchProfileConnectionStateChanged(
+ mCachedBluetoothDevice,
+ BluetoothProfile.STATE_DISCONNECTED,
+ BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT);
+
+ verify(mBroadcast).updateFallbackActiveDeviceIfNeeded();
}
/**
@@ -332,28 +297,40 @@ public class BluetoothEventManagerTest {
*/
@Test
public void dispatchProfileConnectionStateChanged_audioSharing_updateFallbackDevice() {
+ setUpAudioSharing(/* enableFlag= */ true, /* enableFeature= */ true, /* enableProfile= */
+ true, /* workProfile= */ false);
+ mBluetoothEventManager.dispatchProfileConnectionStateChanged(
+ mCachedBluetoothDevice,
+ BluetoothProfile.STATE_DISCONNECTED,
+ BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT);
+
+ verify(mBroadcast).updateFallbackActiveDeviceIfNeeded();
+ }
+
+ private void setUpAudioSharing(boolean enableFlag, boolean enableFeature,
+ boolean enableProfile, boolean workProfile) {
+ if (enableFlag) {
+ mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
+ } else {
+ mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
+ }
ShadowBluetoothAdapter shadowBluetoothAdapter =
Shadow.extract(BluetoothAdapter.getDefaultAdapter());
- shadowBluetoothAdapter.setIsLeAudioBroadcastSourceSupported(
- BluetoothStatusCodes.FEATURE_SUPPORTED);
- shadowBluetoothAdapter.setIsLeAudioBroadcastAssistantSupported(
- BluetoothStatusCodes.FEATURE_SUPPORTED);
- mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
- LocalBluetoothLeBroadcast broadcast = mock(LocalBluetoothLeBroadcast.class);
- when(broadcast.isProfileReady()).thenReturn(true);
+ int code = enableFeature ? BluetoothStatusCodes.FEATURE_SUPPORTED
+ : BluetoothStatusCodes.FEATURE_NOT_SUPPORTED;
+ shadowBluetoothAdapter.setIsLeAudioBroadcastSourceSupported(code);
+ shadowBluetoothAdapter.setIsLeAudioBroadcastAssistantSupported(code);
+ when(mBroadcast.isProfileReady()).thenReturn(enableProfile);
LocalBluetoothLeBroadcastAssistant assistant =
mock(LocalBluetoothLeBroadcastAssistant.class);
- when(assistant.isProfileReady()).thenReturn(true);
+ when(assistant.isProfileReady()).thenReturn(enableProfile);
LocalBluetoothProfileManager profileManager = mock(LocalBluetoothProfileManager.class);
- when(profileManager.getLeAudioBroadcastProfile()).thenReturn(broadcast);
+ when(profileManager.getLeAudioBroadcastProfile()).thenReturn(mBroadcast);
when(profileManager.getLeAudioBroadcastAssistantProfile()).thenReturn(assistant);
when(mBtManager.getProfileManager()).thenReturn(profileManager);
- mBluetoothEventManager.dispatchProfileConnectionStateChanged(
- mCachedBluetoothDevice,
- BluetoothProfile.STATE_DISCONNECTED,
- BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT);
-
- verify(broadcast).updateFallbackActiveDeviceIfNeeded();
+ UserManager userManager = mock(UserManager.class);
+ when(mContext.getSystemService(UserManager.class)).thenReturn(userManager);
+ when(userManager.isManagedProfile()).thenReturn(workProfile);
}
@Test
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CsipDeviceManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CsipDeviceManagerTest.java
index b180b69f1e07..fd14d1ff6786 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CsipDeviceManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CsipDeviceManagerTest.java
@@ -39,6 +39,7 @@ import android.bluetooth.BluetoothStatusCodes;
import android.content.Context;
import android.os.Looper;
import android.os.Parcel;
+import android.os.UserManager;
import android.platform.test.flag.junit.SetFlagsRule;
import com.android.settingslib.flags.Flags;
@@ -104,6 +105,8 @@ public class CsipDeviceManagerTest {
private LocalBluetoothLeBroadcast mBroadcast;
@Mock
private LocalBluetoothLeBroadcastAssistant mAssistant;
+ @Mock
+ private UserManager mUserManager;
private ShadowBluetoothAdapter mShadowBluetoothAdapter;
private CachedBluetoothDevice mCachedDevice1;
@@ -128,7 +131,7 @@ public class CsipDeviceManagerTest {
public void setUp() {
MockitoAnnotations.initMocks(this);
- mContext = RuntimeEnvironment.application;
+ mContext = spy(RuntimeEnvironment.application);
mShadowBluetoothAdapter = Shadow.extract(BluetoothAdapter.getDefaultAdapter());
mShadowBluetoothAdapter.setEnabled(true);
mShadowBluetoothAdapter.setIsLeAudioBroadcastSourceSupported(
@@ -356,6 +359,37 @@ public class CsipDeviceManagerTest {
}
@Test
+ public void
+ addMemberDevicesIntoMainDevice_preferredDeviceIsMainAndTwoMain_workProfile_doNothing() {
+ // Condition: The preferredDevice is main and there is another main device in top list
+ // Expected Result: return true and there is the preferredDevice in top list
+ CachedBluetoothDevice preferredDevice = mCachedDevice1;
+ mCachedDevice1.getMemberDevice().clear();
+ mCachedDevices.clear();
+ mCachedDevices.add(preferredDevice);
+ mCachedDevices.add(mCachedDevice2);
+ mCachedDevices.add(mCachedDevice3);
+ mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
+ when(mBroadcast.isEnabled(null)).thenReturn(true);
+ BluetoothLeBroadcastMetadata metadata = Mockito.mock(BluetoothLeBroadcastMetadata.class);
+ when(mBroadcast.getLatestBluetoothLeBroadcastMetadata()).thenReturn(metadata);
+ BluetoothLeBroadcastReceiveState state = Mockito.mock(
+ BluetoothLeBroadcastReceiveState.class);
+ when(state.getBisSyncState()).thenReturn(ImmutableList.of(1L));
+ when(mAssistant.getAllSources(mDevice2)).thenReturn(ImmutableList.of(state));
+ when(mContext.getSystemService(UserManager.class)).thenReturn(mUserManager);
+ when(mUserManager.isManagedProfile()).thenReturn(true);
+
+ assertThat(mCsipDeviceManager.addMemberDevicesIntoMainDevice(GROUP1, preferredDevice))
+ .isTrue();
+ assertThat(mCachedDevices.contains(preferredDevice)).isTrue();
+ assertThat(mCachedDevices.contains(mCachedDevice2)).isFalse();
+ assertThat(mCachedDevices.contains(mCachedDevice3)).isTrue();
+ assertThat(preferredDevice.getMemberDevice()).contains(mCachedDevice2);
+ verify(mAssistant, never()).addSource(mDevice1, metadata, /* isGroupOp= */ false);
+ }
+
+ @Test
public void addMemberDevicesIntoMainDevice_preferredDeviceIsMainAndTwoMain_syncSource() {
// Condition: The preferredDevice is main and there is another main device in top list
// Expected Result: return true and there is the preferredDevice in top list
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 456fedf912ff..408ed1e861c3 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -743,12 +743,6 @@
<uses-permission android:name="android.permission.READ_SAFETY_CENTER_STATUS" />
<uses-permission android:name="android.permission.MANAGE_SAFETY_CENTER" />
- <!-- Permissions required for CTS test - CtsVirtualDevicesTestCases -->
- <uses-permission android:name="android.permission.CREATE_VIRTUAL_DEVICE" />
- <uses-permission android:name="android.permission.ADD_TRUSTED_DISPLAY" />
- <uses-permission android:name="android.permission.ADD_ALWAYS_UNLOCKED_DISPLAY" />
-
-
<!-- Permission required for CTS test - Notification test suite -->
<uses-permission android:name="android.permission.REVOKE_POST_NOTIFICATIONS_WITHOUT_KILL" />
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayCallbackControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayCallbackControllerTest.kt
index d9dcfdc7becb..9c6fd4bf71b4 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayCallbackControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayCallbackControllerTest.kt
@@ -56,6 +56,7 @@ class DreamOverlayCallbackControllerTest : SysuiTestCase() {
// Adding twice should not invoke twice
reset(callback)
+ underTest.onStartDream()
underTest.addCallback(callback)
underTest.onWakeUp()
verify(callback, times(1)).onWakeUp()
@@ -68,6 +69,19 @@ class DreamOverlayCallbackControllerTest : SysuiTestCase() {
}
@Test
+ fun onWakeUp_multipleCalls() {
+ underTest.onStartDream()
+ assertThat(underTest.isDreaming).isEqualTo(true)
+
+ underTest.addCallback(callback)
+ underTest.onWakeUp()
+ underTest.onWakeUp()
+ underTest.onWakeUp()
+ verify(callback, times(1)).onWakeUp()
+ assertThat(underTest.isDreaming).isEqualTo(false)
+ }
+
+ @Test
fun onStartDreamInvokesCallback() {
underTest.addCallback(callback)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt
index a3314e8900ce..f5d2d42902d8 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt
@@ -712,6 +712,9 @@ class DreamOverlayServiceTest : SysuiTestCase() {
// Verify DreamOverlayContainerViewController is destroyed.
verify(mDreamOverlayContainerViewController).destroy()
+
+ // DreamOverlay callback receives onWakeUp.
+ verify(mDreamOverlayCallbackController).onWakeUp()
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt
index c5ccf9e6a1d1..74d4178891b9 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt
@@ -18,6 +18,8 @@ package com.android.systemui.statusbar.policy.domain.interactor
import android.app.AutomaticZenRule
import android.app.Flags
+import android.app.NotificationManager.INTERRUPTION_FILTER_NONE
+import android.app.NotificationManager.INTERRUPTION_FILTER_PRIORITY
import android.app.NotificationManager.Policy
import android.platform.test.annotations.EnableFlags
import android.provider.Settings
@@ -25,6 +27,7 @@ import android.provider.Settings.Secure.ZEN_DURATION
import android.provider.Settings.Secure.ZEN_DURATION_FOREVER
import android.provider.Settings.Secure.ZEN_DURATION_PROMPT
import android.service.notification.SystemZenRules
+import android.service.notification.ZenPolicy
import android.service.notification.ZenPolicy.VISUAL_EFFECT_NOTIFICATION_LIST
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
@@ -383,6 +386,120 @@ class ZenModeInteractorTest : SysuiTestCase() {
}
@Test
+ @EnableFlags(Flags.FLAG_MODES_UI)
+ fun activeModesBlockingEverything_hasModesWithFilterNone() =
+ testScope.runTest {
+ val blockingEverything by collectLastValue(underTest.activeModesBlockingEverything)
+
+ zenModeRepository.addModes(
+ listOf(
+ TestModeBuilder()
+ .setName("Filter=None, Not active")
+ .setInterruptionFilter(INTERRUPTION_FILTER_NONE)
+ .setActive(false)
+ .build(),
+ TestModeBuilder()
+ .setName("Filter=Priority, Active")
+ .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
+ .setActive(true)
+ .build(),
+ TestModeBuilder()
+ .setName("Filter=None, Active")
+ .setInterruptionFilter(INTERRUPTION_FILTER_NONE)
+ .setActive(true)
+ .build(),
+ TestModeBuilder()
+ .setName("Filter=None, Active Too")
+ .setInterruptionFilter(INTERRUPTION_FILTER_NONE)
+ .setActive(true)
+ .build(),
+ )
+ )
+ runCurrent()
+
+ assertThat(blockingEverything!!.mainMode!!.name).isEqualTo("Filter=None, Active")
+ assertThat(blockingEverything!!.modeNames)
+ .containsExactly("Filter=None, Active", "Filter=None, Active Too")
+ .inOrder()
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_MODES_UI)
+ fun activeModesBlockingMedia_hasModesWithPolicyBlockingMedia() =
+ testScope.runTest {
+ val blockingMedia by collectLastValue(underTest.activeModesBlockingMedia)
+
+ zenModeRepository.addModes(
+ listOf(
+ TestModeBuilder()
+ .setName("Blocks media, Not active")
+ .setZenPolicy(ZenPolicy.Builder().allowMedia(false).build())
+ .setActive(false)
+ .build(),
+ TestModeBuilder()
+ .setName("Allows media, Active")
+ .setZenPolicy(ZenPolicy.Builder().allowMedia(true).build())
+ .setActive(true)
+ .build(),
+ TestModeBuilder()
+ .setName("Blocks media, Active")
+ .setZenPolicy(ZenPolicy.Builder().allowMedia(false).build())
+ .setActive(true)
+ .build(),
+ TestModeBuilder()
+ .setName("Blocks media, Active Too")
+ .setZenPolicy(ZenPolicy.Builder().allowMedia(false).build())
+ .setActive(true)
+ .build(),
+ )
+ )
+ runCurrent()
+
+ assertThat(blockingMedia!!.mainMode!!.name).isEqualTo("Blocks media, Active")
+ assertThat(blockingMedia!!.modeNames)
+ .containsExactly("Blocks media, Active", "Blocks media, Active Too")
+ .inOrder()
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_MODES_UI)
+ fun activeModesBlockingAlarms_hasModesWithPolicyBlockingAlarms() =
+ testScope.runTest {
+ val blockingAlarms by collectLastValue(underTest.activeModesBlockingAlarms)
+
+ zenModeRepository.addModes(
+ listOf(
+ TestModeBuilder()
+ .setName("Blocks alarms, Not active")
+ .setZenPolicy(ZenPolicy.Builder().allowAlarms(false).build())
+ .setActive(false)
+ .build(),
+ TestModeBuilder()
+ .setName("Allows alarms, Active")
+ .setZenPolicy(ZenPolicy.Builder().allowAlarms(true).build())
+ .setActive(true)
+ .build(),
+ TestModeBuilder()
+ .setName("Blocks alarms, Active")
+ .setZenPolicy(ZenPolicy.Builder().allowAlarms(false).build())
+ .setActive(true)
+ .build(),
+ TestModeBuilder()
+ .setName("Blocks alarms, Active Too")
+ .setZenPolicy(ZenPolicy.Builder().allowAlarms(false).build())
+ .setActive(true)
+ .build(),
+ )
+ )
+ runCurrent()
+
+ assertThat(blockingAlarms!!.mainMode!!.name).isEqualTo("Blocks alarms, Active")
+ assertThat(blockingAlarms!!.modeNames)
+ .containsExactly("Blocks alarms, Active", "Blocks alarms, Active Too")
+ .inOrder()
+ }
+
+ @Test
@EnableFlags(ModesEmptyShadeFix.FLAG_NAME, Flags.FLAG_MODES_UI, Flags.FLAG_MODES_API)
fun modesHidingNotifications_onlyIncludesModesWithNotifListSuppression() =
testScope.runTest {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModelTest.kt
new file mode 100644
index 000000000000..f80b36a10dc2
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModelTest.kt
@@ -0,0 +1,174 @@
+/*
+ * 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.volume.panel.component.volume.slider.ui.viewmodel
+
+import android.app.Flags
+import android.app.NotificationManager.INTERRUPTION_FILTER_NONE
+import android.media.AudioManager
+import android.platform.test.annotations.EnableFlags
+import android.service.notification.ZenPolicy
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.internal.logging.uiEventLogger
+import com.android.settingslib.notification.modes.TestModeBuilder
+import com.android.settingslib.volume.shared.model.AudioStream
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.statusbar.policy.data.repository.fakeZenModeRepository
+import com.android.systemui.statusbar.policy.domain.interactor.zenModeInteractor
+import com.android.systemui.testKosmos
+import com.android.systemui.volume.domain.interactor.audioVolumeInteractor
+import com.android.systemui.volume.shared.volumePanelLogger
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class AudioStreamSliderViewModelTest : SysuiTestCase() {
+
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val zenModeRepository = kosmos.fakeZenModeRepository
+
+ private lateinit var mediaStream: AudioStreamSliderViewModel
+ private lateinit var alarmsStream: AudioStreamSliderViewModel
+ private lateinit var notificationStream: AudioStreamSliderViewModel
+ private lateinit var otherStream: AudioStreamSliderViewModel
+
+ @Before
+ fun setUp() {
+ mediaStream = audioStreamSliderViewModel(AudioManager.STREAM_MUSIC)
+ alarmsStream = audioStreamSliderViewModel(AudioManager.STREAM_ALARM)
+ notificationStream = audioStreamSliderViewModel(AudioManager.STREAM_NOTIFICATION)
+ otherStream = audioStreamSliderViewModel(AudioManager.STREAM_VOICE_CALL)
+ }
+
+ private fun audioStreamSliderViewModel(stream: Int): AudioStreamSliderViewModel {
+ return AudioStreamSliderViewModel(
+ AudioStreamSliderViewModel.FactoryAudioStreamWrapper(AudioStream(stream)),
+ testScope.backgroundScope,
+ context,
+ kosmos.audioVolumeInteractor,
+ kosmos.zenModeInteractor,
+ kosmos.uiEventLogger,
+ kosmos.volumePanelLogger,
+ )
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_MODES_UI, Flags.FLAG_MODES_UI_ICONS)
+ fun slider_media_hasDisabledByModesText() =
+ testScope.runTest {
+ val mediaSlider by collectLastValue(mediaStream.slider)
+
+ zenModeRepository.addMode(
+ TestModeBuilder()
+ .setName("Media is ok")
+ .setZenPolicy(ZenPolicy.Builder().allowAllSounds().build())
+ .setActive(true)
+ .build()
+ )
+ zenModeRepository.addMode(
+ TestModeBuilder()
+ .setName("No media plz")
+ .setZenPolicy(ZenPolicy.Builder().allowMedia(false).build())
+ .setActive(true)
+ .build()
+ )
+ runCurrent()
+
+ assertThat(mediaSlider!!.disabledMessage)
+ .isEqualTo("Unavailable because No media plz is on")
+
+ zenModeRepository.clearModes()
+ runCurrent()
+
+ assertThat(mediaSlider!!.disabledMessage).isEqualTo("Unavailable")
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_MODES_UI, Flags.FLAG_MODES_UI_ICONS)
+ fun slider_alarms_hasDisabledByModesText() =
+ testScope.runTest {
+ val alarmsSlider by collectLastValue(alarmsStream.slider)
+
+ zenModeRepository.addMode(
+ TestModeBuilder()
+ .setName("Alarms are ok")
+ .setZenPolicy(ZenPolicy.Builder().allowAllSounds().build())
+ .setActive(true)
+ .build()
+ )
+ zenModeRepository.addMode(
+ TestModeBuilder()
+ .setName("Zzzzz")
+ .setZenPolicy(ZenPolicy.Builder().allowAlarms(false).build())
+ .setActive(true)
+ .build()
+ )
+ runCurrent()
+
+ assertThat(alarmsSlider!!.disabledMessage).isEqualTo("Unavailable because Zzzzz is on")
+
+ zenModeRepository.clearModes()
+ runCurrent()
+
+ assertThat(alarmsSlider!!.disabledMessage).isEqualTo("Unavailable")
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_MODES_UI, Flags.FLAG_MODES_UI_ICONS)
+ fun slider_other_hasDisabledByModesText() =
+ testScope.runTest {
+ val otherSlider by collectLastValue(otherStream.slider)
+
+ zenModeRepository.addMode(
+ TestModeBuilder()
+ .setName("Everything blocked")
+ .setInterruptionFilter(INTERRUPTION_FILTER_NONE)
+ .setActive(true)
+ .build()
+ )
+ runCurrent()
+
+ assertThat(otherSlider!!.disabledMessage)
+ .isEqualTo("Unavailable because Everything blocked is on")
+
+ zenModeRepository.clearModes()
+ runCurrent()
+
+ assertThat(otherSlider!!.disabledMessage).isEqualTo("Unavailable")
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_MODES_UI, Flags.FLAG_MODES_UI_ICONS)
+ fun slider_notification_hasSpecialDisabledText() =
+ testScope.runTest {
+ val notificationSlider by collectLastValue(notificationStream.slider)
+ runCurrent()
+
+ assertThat(notificationSlider!!.disabledMessage)
+ .isEqualTo("Unavailable because ring is muted")
+ }
+}
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 96a85d78e2b5..72250610d768 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1746,6 +1746,11 @@
<!-- A message shown when the media volume changing is disabled because of the don't disturb mode [CHAR_LIMIT=50]-->
<string name="stream_media_unavailable">Unavailable because Do Not Disturb is on</string>
+ <!-- A message shown when a specific volume (e.g. Alarms, Media, etc) is disabled because an active mode is muting that audio stream altogether [CHAR_LIMIT=50]-->
+ <string name="stream_unavailable_by_modes">Unavailable because <xliff:g id="mode" example="Bedtime">%s</xliff:g> is on</string>
+ <!-- A message shown when a specific volume (e.g. Alarms, Media, etc) is disabled but we don't know which mode (or anything else) is responsible. [CHAR_LIMIT=50]-->
+ <string name="stream_unavailable_by_unknown">Unavailable</string>
+
<!-- Shown in the header of quick settings to indicate to the user that their phone ringer is on vibrate. [CHAR_LIMIT=NONE] -->
<!-- Shown in the header of quick settings to indicate to the user that their phone ringer is on silent (muted). [CHAR_LIMIT=NONE] -->
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractor.kt
index 373671d01395..0949ea4d7797 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractor.kt
@@ -16,6 +16,7 @@
package com.android.systemui.bouncer.domain.interactor
+import android.util.Log
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.biometrics.data.repository.FingerprintPropertyRepository
import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepository
@@ -46,6 +47,7 @@ import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.stateIn
/** Encapsulates business logic for interacting with the lock-screen alternate bouncer. */
@@ -137,6 +139,8 @@ constructor(
flowOf(false)
}
}
+ .distinctUntilChanged()
+ .onEach { Log.d(TAG, "canShowAlternateBouncer changed to $it") }
.stateIn(
scope = scope,
started = WhileSubscribed(),
@@ -234,5 +238,7 @@ constructor(
companion object {
private const val MIN_VISIBILITY_DURATION_UNTIL_TOUCHES_DISMISS_ALTERNATE_BOUNCER_MS = 200L
+
+ private const val TAG = "AlternateBouncerInteractor"
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayCallbackController.kt b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayCallbackController.kt
index d5ff8f21abb2..2b617525fbcc 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayCallbackController.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayCallbackController.kt
@@ -39,8 +39,10 @@ class DreamOverlayCallbackController @Inject constructor() :
}
fun onWakeUp() {
- isDreaming = false
- callbacks.forEach { it.onWakeUp() }
+ if (isDreaming) {
+ isDreaming = false
+ callbacks.forEach { it.onWakeUp() }
+ }
}
fun onStartDream() {
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
index 83f86a718029..7a6ca0859a09 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
@@ -297,6 +297,8 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ
mStateController.setLowLightActive(false);
mStateController.setEntryAnimationsFinished(false);
+ mDreamOverlayCallbackController.onWakeUp();
+
if (mDreamOverlayContainerViewController != null) {
mDreamOverlayContainerViewController.destroy();
mDreamOverlayContainerViewController = null;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
index 7b6a2cb62b14..560028cb5640 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
@@ -444,9 +444,11 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
if (onFinishedRunnable != null) {
onFinishedRunnable.run();
}
+ if (mRunWithoutInterruptions) {
+ enableAppearDrawing(false);
+ }
// We need to reset the View state, even if the animation was cancelled
- enableAppearDrawing(false);
onAppearAnimationFinished(isAppearing);
if (mRunWithoutInterruptions) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index 0474344ee390..7e5b45543e9e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -33,7 +33,6 @@ import static com.android.systemui.Flags.relockWithPowerButtonImmediately;
import static com.android.systemui.Flags.statusBarSignalPolicyRefactor;
import static com.android.systemui.charging.WirelessChargingAnimation.UNKNOWN_BATTERY_LEVEL;
import static com.android.systemui.flags.Flags.SHORTCUT_LIST_SEARCH_LAYOUT;
-import static com.android.systemui.statusbar.NotificationLockscreenUserManager.PERMISSION_SELF;
import static com.android.systemui.statusbar.StatusBarState.SHADE;
import android.annotation.Nullable;
@@ -41,7 +40,6 @@ import android.app.ActivityOptions;
import android.app.IWallpaperManager;
import android.app.KeyguardManager;
import android.app.Notification;
-import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.StatusBarManager;
import android.app.TaskInfo;
@@ -275,11 +273,6 @@ import javax.inject.Provider;
@SysUISingleton
public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
- private static final String BANNER_ACTION_CANCEL =
- "com.android.systemui.statusbar.banner_action_cancel";
- private static final String BANNER_ACTION_SETUP =
- "com.android.systemui.statusbar.banner_action_setup";
-
private static final int MSG_LAUNCH_TRANSITION_TIMEOUT = 1003;
// 1020-1040 reserved for BaseStatusBar
@@ -963,12 +956,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
}
}
- IntentFilter internalFilter = new IntentFilter();
- internalFilter.addAction(BANNER_ACTION_CANCEL);
- internalFilter.addAction(BANNER_ACTION_SETUP);
- mContext.registerReceiver(mBannerActionBroadcastReceiver, internalFilter, PERMISSION_SELF,
- null, Context.RECEIVER_EXPORTED_UNAUDITED);
-
if (mWallpaperSupported) {
IWallpaperManager wallpaperManager = IWallpaperManager.Stub.asInterface(
ServiceManager.getService(Context.WALLPAPER_SERVICE));
@@ -2948,29 +2935,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
return mDeviceInteractive;
}
- private final BroadcastReceiver mBannerActionBroadcastReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- String action = intent.getAction();
- if (BANNER_ACTION_CANCEL.equals(action) || BANNER_ACTION_SETUP.equals(action)) {
- NotificationManager noMan = (NotificationManager)
- mContext.getSystemService(Context.NOTIFICATION_SERVICE);
- noMan.cancel(com.android.internal.messages.nano.SystemMessageProto.SystemMessage.
- NOTE_HIDDEN_NOTIFICATIONS);
-
- Settings.Secure.putInt(mContext.getContentResolver(),
- Settings.Secure.SHOW_NOTE_ABOUT_NOTIFICATION_HIDING, 0);
- if (BANNER_ACTION_SETUP.equals(action)) {
- mShadeController.animateCollapseShadeForced();
- mContext.startActivity(new Intent(Settings.ACTION_APP_NOTIFICATION_REDACTION)
- .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
-
- );
- }
- }
- }
- };
-
@Override
public void handleExternalShadeWindowTouch(MotionEvent event) {
getNotificationShadeWindowViewController().handleExternalTouch(event);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index 479ffb728eb2..17bd53869ee5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -530,6 +530,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
this::consumeKeyguardAuthenticatedBiometricsHandled
);
} else {
+ // Collector that keeps the AlternateBouncerInteractor#canShowAlternateBouncer flow hot.
mListenForCanShowAlternateBouncer = mJavaAdapter.alwaysCollectFlow(
mAlternateBouncerInteractor.getCanShowAlternateBouncer(),
this::consumeCanShowAlternateBouncer
@@ -578,8 +579,17 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
}
private void consumeCanShowAlternateBouncer(boolean canShow) {
- // do nothing, we only are registering for the flow to ensure that there's at least
- // one subscriber that will update AlternateBouncerInteractor.canShowAlternateBouncer.value
+ // Hack: this is required to fix issues where
+ // KeyguardBouncerRepository#alternateBouncerVisible state is incorrectly set and then never
+ // reset. This is caused by usages of show()/forceShow() that only read this flow to set the
+ // alternate bouncer visible state, if there is a race condition between when that flow
+ // changes to false and when the read happens, the flow will be set to an incorrect value
+ // and not reset on time.
+ if (!canShow) {
+ Log.d(TAG, "canShowAlternateBouncer turned false, maybe try hiding the alternate "
+ + "bouncer if it is already visible");
+ mAlternateBouncerInteractor.maybeHide();
+ }
}
/** Register a callback, to be invoked by the Predictive Back system. */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractor.kt
index daba1099c49d..9839f9d76537 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractor.kt
@@ -16,10 +16,12 @@
package com.android.systemui.statusbar.policy.domain.interactor
+import android.app.NotificationManager.INTERRUPTION_FILTER_NONE
import android.content.Context
import android.provider.Settings
import android.provider.Settings.Secure.ZEN_DURATION_FOREVER
import android.provider.Settings.Secure.ZEN_DURATION_PROMPT
+import android.service.notification.ZenPolicy.STATE_DISALLOW
import android.service.notification.ZenPolicy.VISUAL_EFFECT_NOTIFICATION_LIST
import android.util.Log
import androidx.concurrent.futures.await
@@ -115,6 +117,26 @@ constructor(
.flowOn(bgDispatcher)
.distinctUntilChanged()
+ val activeModesBlockingEverything: Flow<ActiveZenModes> = getFilteredActiveModesFlow { mode ->
+ mode.interruptionFilter == INTERRUPTION_FILTER_NONE
+ }
+
+ val activeModesBlockingMedia: Flow<ActiveZenModes> = getFilteredActiveModesFlow { mode ->
+ mode.policy.priorityCategoryMedia == STATE_DISALLOW
+ }
+
+ val activeModesBlockingAlarms: Flow<ActiveZenModes> = getFilteredActiveModesFlow { mode ->
+ mode.policy.priorityCategoryAlarms == STATE_DISALLOW
+ }
+
+ private fun getFilteredActiveModesFlow(predicate: (ZenMode) -> Boolean): Flow<ActiveZenModes> {
+ return modes
+ .map { modes -> modes.filter { mode -> predicate(mode) } }
+ .map { modes -> buildActiveZenModes(modes) }
+ .flowOn(bgDispatcher)
+ .distinctUntilChanged()
+ }
+
suspend fun getActiveModes() = buildActiveZenModes(zenModeRepository.getModes())
private suspend fun buildActiveZenModes(modes: List<ZenMode>): ActiveZenModes {
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt
index ffb1f11c4970..2aa1ac99a400 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt
@@ -18,6 +18,9 @@ package com.android.systemui.volume.panel.component.volume.slider.ui.viewmodel
import android.content.Context
import android.media.AudioManager
+import android.media.AudioManager.STREAM_ALARM
+import android.media.AudioManager.STREAM_MUSIC
+import android.media.AudioManager.STREAM_NOTIFICATION
import android.util.Log
import com.android.internal.logging.UiEventLogger
import com.android.settingslib.volume.domain.interactor.AudioVolumeInteractor
@@ -25,7 +28,11 @@ import com.android.settingslib.volume.shared.model.AudioStream
import com.android.settingslib.volume.shared.model.AudioStreamModel
import com.android.settingslib.volume.shared.model.RingerMode
import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.modes.shared.ModesUiIcons
import com.android.systemui.res.R
+import com.android.systemui.statusbar.policy.domain.interactor.ZenModeInteractor
+import com.android.systemui.statusbar.policy.domain.model.ActiveZenModes
+import com.android.systemui.util.kotlin.combine
import com.android.systemui.volume.panel.shared.VolumePanelLogger
import com.android.systemui.volume.panel.ui.VolumePanelUiEvent
import dagger.assisted.Assisted
@@ -51,16 +58,14 @@ constructor(
@Assisted private val coroutineScope: CoroutineScope,
private val context: Context,
private val audioVolumeInteractor: AudioVolumeInteractor,
+ private val zenModeInteractor: ZenModeInteractor,
private val uiEventLogger: UiEventLogger,
private val volumePanelLogger: VolumePanelLogger,
) : SliderViewModel {
private val volumeChanges = MutableStateFlow<Int?>(null)
private val streamsAffectedByRing =
- setOf(
- AudioManager.STREAM_RING,
- AudioManager.STREAM_NOTIFICATION,
- )
+ setOf(AudioManager.STREAM_RING, AudioManager.STREAM_NOTIFICATION)
private val audioStream = audioStreamWrapper.audioStream
private val iconsByStream =
mapOf(
@@ -78,11 +83,6 @@ constructor(
AudioStream(AudioManager.STREAM_NOTIFICATION) to R.string.stream_notification,
AudioStream(AudioManager.STREAM_ALARM) to R.string.stream_alarm,
)
- private val disabledTextByStream =
- mapOf(
- AudioStream(AudioManager.STREAM_NOTIFICATION) to
- R.string.stream_notification_unavailable,
- )
private val uiEventByStream =
mapOf(
AudioStream(AudioManager.STREAM_MUSIC) to
@@ -98,15 +98,48 @@ constructor(
)
override val slider: StateFlow<SliderState> =
- combine(
- audioVolumeInteractor.getAudioStream(audioStream),
- audioVolumeInteractor.canChangeVolume(audioStream),
- audioVolumeInteractor.ringerMode,
- ) { model, isEnabled, ringerMode ->
- volumePanelLogger.onVolumeUpdateReceived(audioStream, model.volume)
- model.toState(isEnabled, ringerMode)
- }
- .stateIn(coroutineScope, SharingStarted.Eagerly, SliderState.Empty)
+ if (ModesUiIcons.isEnabled) {
+ combine(
+ audioVolumeInteractor.getAudioStream(audioStream),
+ audioVolumeInteractor.canChangeVolume(audioStream),
+ audioVolumeInteractor.ringerMode,
+ zenModeInteractor.activeModesBlockingEverything,
+ zenModeInteractor.activeModesBlockingAlarms,
+ zenModeInteractor.activeModesBlockingMedia,
+ ) {
+ model,
+ isEnabled,
+ ringerMode,
+ modesBlockingEverything,
+ modesBlockingAlarms,
+ modesBlockingMedia ->
+ volumePanelLogger.onVolumeUpdateReceived(audioStream, model.volume)
+ model.toState(
+ isEnabled,
+ ringerMode,
+ getStreamDisabledMessage(
+ modesBlockingEverything,
+ modesBlockingAlarms,
+ modesBlockingMedia,
+ ),
+ )
+ }
+ .stateIn(coroutineScope, SharingStarted.Eagerly, SliderState.Empty)
+ } else {
+ combine(
+ audioVolumeInteractor.getAudioStream(audioStream),
+ audioVolumeInteractor.canChangeVolume(audioStream),
+ audioVolumeInteractor.ringerMode,
+ ) { model, isEnabled, ringerMode ->
+ volumePanelLogger.onVolumeUpdateReceived(audioStream, model.volume)
+ model.toState(
+ isEnabled,
+ ringerMode,
+ getStreamDisabledMessageWithoutModes(audioStream),
+ )
+ }
+ .stateIn(coroutineScope, SharingStarted.Eagerly, SliderState.Empty)
+ }
init {
volumeChanges
@@ -139,6 +172,7 @@ constructor(
private fun AudioStreamModel.toState(
isEnabled: Boolean,
ringerMode: RingerMode,
+ disabledMessage: String?,
): State {
val label =
labelsByStream[audioStream]?.let(context::getString)
@@ -148,13 +182,7 @@ constructor(
valueRange = volumeRange.first.toFloat()..volumeRange.last.toFloat(),
icon = getIcon(ringerMode),
label = label,
- disabledMessage =
- context.getString(
- disabledTextByStream.getOrDefault(
- audioStream,
- R.string.stream_alarm_unavailable,
- )
- ),
+ disabledMessage = disabledMessage,
isEnabled = isEnabled,
a11yStep = volumeRange.step,
a11yClickDescription =
@@ -191,6 +219,43 @@ constructor(
)
}
+ private fun getStreamDisabledMessage(
+ blockingEverything: ActiveZenModes,
+ blockingAlarms: ActiveZenModes,
+ blockingMedia: ActiveZenModes,
+ ): String {
+ // TODO: b/372213356 - Figure out the correct messages for VOICE_CALL and RING.
+ // In fact, VOICE_CALL should not be affected by interruption filtering at all.
+ return if (audioStream.value == STREAM_NOTIFICATION) {
+ context.getString(R.string.stream_notification_unavailable)
+ } else {
+ val blockingModeName =
+ when {
+ blockingEverything.mainMode != null -> blockingEverything.mainMode.name
+ audioStream.value == STREAM_ALARM -> blockingAlarms.mainMode?.name
+ audioStream.value == STREAM_MUSIC -> blockingMedia.mainMode?.name
+ else -> null
+ }
+
+ if (blockingModeName != null) {
+ context.getString(R.string.stream_unavailable_by_modes, blockingModeName)
+ } else {
+ // Should not actually be visible, but as a catch-all.
+ context.getString(R.string.stream_unavailable_by_unknown)
+ }
+ }
+ }
+
+ private fun getStreamDisabledMessageWithoutModes(audioStream: AudioStream): String {
+ // TODO: b/372213356 - Figure out the correct messages for VOICE_CALL and RING.
+ // In fact, VOICE_CALL should not be affected by interruption filtering at all.
+ return if (audioStream.value == STREAM_NOTIFICATION) {
+ context.getString(R.string.stream_notification_unavailable)
+ } else {
+ context.getString(R.string.stream_alarm_unavailable)
+ }
+ }
+
private fun AudioStreamModel.getIcon(ringerMode: RingerMode): Icon {
val iconRes =
if (isAffectedByMute && isMuted) {
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModelKosmos.kt
index e6b52f0c52e0..55f0a28d0135 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModelKosmos.kt
@@ -19,6 +19,7 @@ package com.android.systemui.volume.panel.component.volume.slider.ui.viewmodel
import android.content.applicationContext
import com.android.internal.logging.uiEventLogger
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.statusbar.policy.domain.interactor.zenModeInteractor
import com.android.systemui.volume.domain.interactor.audioVolumeInteractor
import com.android.systemui.volume.shared.volumePanelLogger
import kotlinx.coroutines.CoroutineScope
@@ -36,6 +37,7 @@ val Kosmos.audioStreamSliderViewModelFactory by
coroutineScope,
applicationContext,
audioVolumeInteractor,
+ zenModeInteractor,
uiEventLogger,
volumePanelLogger,
)
diff --git a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionDumpHelper.java b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionDumpHelper.java
index 9fc413f6224b..a832545475bf 100644
--- a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionDumpHelper.java
+++ b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionDumpHelper.java
@@ -84,15 +84,17 @@ public final class AppFunctionDumpHelper {
new FutureGlobalSearchSession(appSearchManager, Runnable::run)) {
pw.println();
- FutureSearchResults futureSearchResults =
- searchSession.search("", buildAppFunctionMetadataSearchSpec()).get();
- List<SearchResult> searchResultsList;
- do {
- searchResultsList = futureSearchResults.getNextPage().get();
- for (SearchResult searchResult : searchResultsList) {
- dumpAppFunctionMetadata(pw, searchResult);
- }
- } while (!searchResultsList.isEmpty());
+ try (FutureSearchResults futureSearchResults =
+ searchSession.search("", buildAppFunctionMetadataSearchSpec()).get(); ) {
+ List<SearchResult> searchResultsList;
+ do {
+ searchResultsList = futureSearchResults.getNextPage().get();
+ for (SearchResult searchResult : searchResultsList) {
+ dumpAppFunctionMetadata(pw, searchResult);
+ }
+ } while (!searchResultsList.isEmpty());
+ }
+
} catch (Exception e) {
pw.println("Failed to dump AppFunction state: " + e);
}
diff --git a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java
index 6d350e6b2a4a..c5fef191c52c 100644
--- a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java
+++ b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java
@@ -441,7 +441,7 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub {
targetUser,
mServiceConfig.getExecuteAppFunctionCancellationTimeoutMillis(),
cancellationSignal,
- RunAppFunctionServiceCallback.create(
+ new RunAppFunctionServiceCallback(
requestInternal,
cancellationCallback,
safeExecuteAppFunctionCallback),
diff --git a/services/appfunctions/java/com/android/server/appfunctions/FutureSearchResults.java b/services/appfunctions/java/com/android/server/appfunctions/FutureSearchResults.java
index 45cbdb4021e5..c38ff143178f 100644
--- a/services/appfunctions/java/com/android/server/appfunctions/FutureSearchResults.java
+++ b/services/appfunctions/java/com/android/server/appfunctions/FutureSearchResults.java
@@ -24,11 +24,12 @@ import android.app.appsearch.SearchResults;
import com.android.internal.infra.AndroidFuture;
+import java.io.Closeable;
import java.io.IOException;
import java.util.List;
/** A future API wrapper of {@link android.app.appsearch.SearchResults}. */
-public interface FutureSearchResults {
+public interface FutureSearchResults extends Closeable {
/** Converts a failed app search result codes into an exception. */
@NonNull
@@ -52,4 +53,7 @@ public interface FutureSearchResults {
* there are no more results.
*/
AndroidFuture<List<SearchResult>> getNextPage();
+
+ @Override
+ void close();
}
diff --git a/services/appfunctions/java/com/android/server/appfunctions/FutureSearchResultsImpl.java b/services/appfunctions/java/com/android/server/appfunctions/FutureSearchResultsImpl.java
index c3be342043ba..c8bc538f7226 100644
--- a/services/appfunctions/java/com/android/server/appfunctions/FutureSearchResultsImpl.java
+++ b/services/appfunctions/java/com/android/server/appfunctions/FutureSearchResultsImpl.java
@@ -54,4 +54,9 @@ public class FutureSearchResultsImpl implements FutureSearchResults {
}
});
}
+
+ @Override
+ public void close() {
+ mSearchResults.close();
+ }
}
diff --git a/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncAdapter.java b/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncAdapter.java
index bbf6c0beb163..96be76975e9d 100644
--- a/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncAdapter.java
+++ b/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncAdapter.java
@@ -420,26 +420,29 @@ public class MetadataSyncAdapter {
Objects.requireNonNull(propertyPackageName);
ArrayMap<String, ArraySet<String>> packageToFunctionIds = new ArrayMap<>();
- FutureSearchResults futureSearchResults =
+ try (FutureSearchResults futureSearchResults =
searchSession
.search(
"",
buildMetadataSearchSpec(
schemaType, propertyFunctionId, propertyPackageName))
- .get();
- List<SearchResult> searchResultsList = futureSearchResults.getNextPage().get();
- // TODO(b/357551503): This could be expensive if we have more functions
- while (!searchResultsList.isEmpty()) {
- for (SearchResult searchResult : searchResultsList) {
- String packageName =
- searchResult.getGenericDocument().getPropertyString(propertyPackageName);
- String functionId =
- searchResult.getGenericDocument().getPropertyString(propertyFunctionId);
- packageToFunctionIds
- .computeIfAbsent(packageName, k -> new ArraySet<>())
- .add(functionId);
+ .get(); ) {
+ List<SearchResult> searchResultsList = futureSearchResults.getNextPage().get();
+ // TODO(b/357551503): This could be expensive if we have more functions
+ while (!searchResultsList.isEmpty()) {
+ for (SearchResult searchResult : searchResultsList) {
+ String packageName =
+ searchResult
+ .getGenericDocument()
+ .getPropertyString(propertyPackageName);
+ String functionId =
+ searchResult.getGenericDocument().getPropertyString(propertyFunctionId);
+ packageToFunctionIds
+ .computeIfAbsent(packageName, k -> new ArraySet<>())
+ .add(functionId);
+ }
+ searchResultsList = futureSearchResults.getNextPage().get();
}
- searchResultsList = futureSearchResults.getNextPage().get();
}
return packageToFunctionIds;
}
diff --git a/services/appfunctions/java/com/android/server/appfunctions/RunAppFunctionServiceCallback.java b/services/appfunctions/java/com/android/server/appfunctions/RunAppFunctionServiceCallback.java
index 7820390dd544..129be65f3153 100644
--- a/services/appfunctions/java/com/android/server/appfunctions/RunAppFunctionServiceCallback.java
+++ b/services/appfunctions/java/com/android/server/appfunctions/RunAppFunctionServiceCallback.java
@@ -27,17 +27,17 @@ import android.util.Slog;
import com.android.server.appfunctions.RemoteServiceCaller.RunServiceCallCallback;
import com.android.server.appfunctions.RemoteServiceCaller.ServiceUsageCompleteListener;
-
/**
* A callback to forward a request to the {@link IAppFunctionService} and report back the result.
*/
public class RunAppFunctionServiceCallback implements RunServiceCallCallback<IAppFunctionService> {
+ private static final String TAG = RunAppFunctionServiceCallback.class.getSimpleName();
private final ExecuteAppFunctionAidlRequest mRequestInternal;
private final SafeOneTimeExecuteAppFunctionCallback mSafeExecuteAppFunctionCallback;
private final ICancellationCallback mCancellationCallback;
- private RunAppFunctionServiceCallback(
+ public RunAppFunctionServiceCallback(
ExecuteAppFunctionAidlRequest requestInternal,
ICancellationCallback cancellationCallback,
SafeOneTimeExecuteAppFunctionCallback safeExecuteAppFunctionCallback) {
@@ -46,21 +46,6 @@ public class RunAppFunctionServiceCallback implements RunServiceCallCallback<IAp
this.mCancellationCallback = cancellationCallback;
}
- /**
- * Creates a new instance of {@link RunAppFunctionServiceCallback}.
- *
- * @param requestInternal a request to send to the service.
- * @param cancellationCallback a callback to forward cancellation signal to the service.
- * @param safeExecuteAppFunctionCallback a callback to report back the result of the operation.
- */
- public static RunAppFunctionServiceCallback create(
- ExecuteAppFunctionAidlRequest requestInternal,
- ICancellationCallback cancellationCallback,
- SafeOneTimeExecuteAppFunctionCallback safeExecuteAppFunctionCallback) {
- return new RunAppFunctionServiceCallback(
- requestInternal, cancellationCallback, safeExecuteAppFunctionCallback);
- }
-
@Override
public void onServiceConnected(
@NonNull IAppFunctionService service,
@@ -68,6 +53,7 @@ public class RunAppFunctionServiceCallback implements RunServiceCallCallback<IAp
try {
service.executeAppFunction(
mRequestInternal.getClientRequest(),
+ mRequestInternal.getCallingPackage(),
mCancellationCallback,
new IExecuteAppFunctionCallback.Stub() {
@Override
@@ -88,7 +74,7 @@ public class RunAppFunctionServiceCallback implements RunServiceCallCallback<IAp
@Override
public void onFailedToConnect() {
- Slog.e("AppFunctionManagerServiceImpl", "Failed to connect to service");
+ Slog.e(TAG, "Failed to connect to service");
mSafeExecuteAppFunctionCallback.onResult(
ExecuteAppFunctionResponse.newFailure(
ExecuteAppFunctionResponse.RESULT_APP_UNKNOWN_ERROR,
diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
index 8d96ba93530b..c4e10360a7cb 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
@@ -150,7 +150,7 @@ import javax.xml.datatype.DatatypeConfigurationException;
* <screenBrightnessDefault>0.65</screenBrightnessDefault>
* <powerThrottlingConfig>
* <brightnessLowestCapAllowed>0.1</brightnessLowestCapAllowed>
- * <customAnimationRateSec>0.004</customAnimationRateSec>
+ * <customAnimationRate>0.004</customAnimationRate>
* <pollingWindowMaxMillis>30000</pollingWindowMaxMillis>
* <pollingWindowMinMillis>10000</pollingWindowMinMillis>
* <powerThrottlingMap>
@@ -2193,11 +2193,11 @@ public class DisplayDeviceConfig {
return;
}
float lowestBrightnessCap = powerThrottlingCfg.getBrightnessLowestCapAllowed().floatValue();
- float customAnimationRateSec = powerThrottlingCfg.getCustomAnimationRateSec().floatValue();
+ float customAnimationRate = powerThrottlingCfg.getCustomAnimationRate().floatValue();
int pollingWindowMaxMillis = powerThrottlingCfg.getPollingWindowMaxMillis().intValue();
int pollingWindowMinMillis = powerThrottlingCfg.getPollingWindowMinMillis().intValue();
mPowerThrottlingConfigData = new PowerThrottlingConfigData(lowestBrightnessCap,
- customAnimationRateSec,
+ customAnimationRate,
pollingWindowMaxMillis,
pollingWindowMinMillis);
}
@@ -3012,16 +3012,16 @@ public class DisplayDeviceConfig {
/** Lowest brightness cap allowed for this device. */
public final float brightnessLowestCapAllowed;
/** Time take to animate brightness in seconds. */
- public final float customAnimationRateSec;
+ public final float customAnimationRate;
/** Time window for maximum polling power in milliseconds. */
public final int pollingWindowMaxMillis;
/** Time window for minimum polling power in milliseconds. */
public final int pollingWindowMinMillis;
public PowerThrottlingConfigData(float brightnessLowestCapAllowed,
- float customAnimationRateSec, int pollingWindowMaxMillis,
+ float customAnimationRate, int pollingWindowMaxMillis,
int pollingWindowMinMillis) {
this.brightnessLowestCapAllowed = brightnessLowestCapAllowed;
- this.customAnimationRateSec = customAnimationRateSec;
+ this.customAnimationRate = customAnimationRate;
this.pollingWindowMaxMillis = pollingWindowMaxMillis;
this.pollingWindowMinMillis = pollingWindowMinMillis;
}
@@ -3031,7 +3031,7 @@ public class DisplayDeviceConfig {
return "PowerThrottlingConfigData{"
+ "brightnessLowestCapAllowed: "
+ brightnessLowestCapAllowed
- + ", customAnimationRateSec: " + customAnimationRateSec
+ + ", customAnimationRate: " + customAnimationRate
+ ", pollingWindowMaxMillis: " + pollingWindowMaxMillis
+ ", pollingWindowMinMillis: " + pollingWindowMinMillis
+ "} ";
diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessPowerClamper.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessPowerClamper.java
index 85e81f989845..1a18b004a3e2 100644
--- a/services/core/java/com/android/server/display/brightness/clamper/BrightnessPowerClamper.java
+++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessPowerClamper.java
@@ -85,7 +85,7 @@ class BrightnessPowerClamper extends
private String mDataId = null;
private float mCurrentBrightness = PowerManager.BRIGHTNESS_INVALID;
private float mCustomAnimationRateSec = DisplayBrightnessState.CUSTOM_ANIMATION_RATE_NOT_SET;
- private float mCustomAnimationRateSecDeviceConfig =
+ private float mCustomAnimationRateDeviceConfig =
DisplayBrightnessState.CUSTOM_ANIMATION_RATE_NOT_SET;
private final BiFunction<String, String, ThrottlingLevel> mDataPointMapper = (key, value) -> {
try {
@@ -117,7 +117,7 @@ class BrightnessPowerClamper extends
};
mPowerThrottlingConfigData = powerData.getPowerThrottlingConfigData();
if (mPowerThrottlingConfigData != null) {
- mCustomAnimationRateSecDeviceConfig = mPowerThrottlingConfigData.customAnimationRateSec;
+ mCustomAnimationRateDeviceConfig = mPowerThrottlingConfigData.customAnimationRate;
}
mThermalLevelListener = new ThermalLevelListener(handler);
mPmicMonitor =
@@ -228,10 +228,6 @@ class BrightnessPowerClamper extends
}
mPowerThrottlingConfigData = data.getPowerThrottlingConfigData();
- if (mPowerThrottlingConfigData == null) {
- Slog.d(TAG,
- "Power throttling data is missing for configuration data.");
- }
}
private void recalculateBrightnessCap() {
@@ -282,13 +278,13 @@ class BrightnessPowerClamper extends
mIsActive = isActive;
Slog.i(TAG, "Power clamper changing current brightness cap mBrightnessCap: "
+ mBrightnessCap + " to target brightness cap:" + targetBrightnessCap
- + " for current screen brightness: " + mCurrentBrightness);
- mBrightnessCap = targetBrightnessCap;
- Slog.i(TAG, "Power clamper changed state: thermalStatus:" + mCurrentThermalLevel
+ + " for current screen brightness: " + mCurrentBrightness + "\n"
+ + "Power clamper changed state: thermalStatus:" + mCurrentThermalLevel
+ " mCurrentThermalLevelChanged:" + mCurrentThermalLevelChanged
+ " mCurrentAvgPowerConsumed:" + mCurrentAvgPowerConsumed
- + " mCustomAnimationRateSec:" + mCustomAnimationRateSecDeviceConfig);
- mCustomAnimationRateSec = mCustomAnimationRateSecDeviceConfig;
+ + " mCustomAnimationRateSec:" + mCustomAnimationRateDeviceConfig);
+ mBrightnessCap = targetBrightnessCap;
+ mCustomAnimationRateSec = mCustomAnimationRateDeviceConfig;
mChangeListener.onChanged();
} else {
mCustomAnimationRateSec = DisplayBrightnessState.CUSTOM_ANIMATION_RATE_NOT_SET;
@@ -344,7 +340,7 @@ class BrightnessPowerClamper extends
+ mPowerThrottlingConfigData.pollingWindowMinMillis + " msec.");
return;
}
- mCustomAnimationRateSecDeviceConfig = mPowerThrottlingConfigData.customAnimationRateSec;
+ mCustomAnimationRateDeviceConfig = mPowerThrottlingConfigData.customAnimationRate;
mThermalLevelListener.start();
}
diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java
index 4665a72b0b06..b228bb9a89c3 100644
--- a/services/core/java/com/android/server/pm/ComputerEngine.java
+++ b/services/core/java/com/android/server/pm/ComputerEngine.java
@@ -5144,10 +5144,28 @@ public class ComputerEngine implements Computer {
}
updateOwnerPackageName = installSource.mUpdateOwnerPackageName;
+
+ if (DEBUG_INSTALL) {
+ Log.d(TAG, "ComputerEngine getInstallSourceInfo updateOwnerPackageName = "
+ + updateOwnerPackageName + ", callingUid = " + callingUid + ", packageName = "
+ + packageName + ", userId = " + userId);
+ }
+
if (updateOwnerPackageName != null) {
final PackageStateInternal ps = mSettings.getPackage(updateOwnerPackageName);
final boolean isCallerSystemOrUpdateOwner = callingUid == Process.SYSTEM_UID
|| isCallerSameApp(updateOwnerPackageName, callingUid);
+
+ if (DEBUG_INSTALL) {
+ Log.d(TAG, "ComputerEngine getInstallSourceInfo ps = "
+ + ps + ", isCallerSystemOrUpdateOwner =" + isCallerSystemOrUpdateOwner
+ + ", isCallerSameApp = "
+ + isCallerSameApp(updateOwnerPackageName, callingUid) + ", filter = "
+ + shouldFilterApplicationIncludingUninstalled(ps, callingUid, userId)
+ + ", FromManagedUserOrProfile = "
+ + isCallerFromManagedUserOrProfile(userId));
+ }
+
// Except for package visibility filtering, we also hide update owner if the installer
// is in the managed user or profile. As we don't enforce the update ownership for the
// managed user and profile, knowing there's an update owner is meaningless in that
@@ -5159,6 +5177,11 @@ public class ComputerEngine implements Computer {
}
}
+ if (DEBUG_INSTALL) {
+ Log.d(TAG, "ComputerEngine getInstallSourceInfo updateOwnerPackageName = "
+ + updateOwnerPackageName);
+ }
+
if (installSource.mIsInitiatingPackageUninstalled) {
// We can't check visibility in the usual way, since the initiating package is no
// longer present. So we apply simpler rules to whether to expose the info:
diff --git a/services/core/java/com/android/server/stats/bootstrap/StatsBootstrapAtomService.java b/services/core/java/com/android/server/stats/bootstrap/StatsBootstrapAtomService.java
index 0d420a535415..dcb47a7b60b6 100644
--- a/services/core/java/com/android/server/stats/bootstrap/StatsBootstrapAtomService.java
+++ b/services/core/java/com/android/server/stats/bootstrap/StatsBootstrapAtomService.java
@@ -62,6 +62,9 @@ public class StatsBootstrapAtomService extends IStatsBootstrapAtomService.Stub {
case StatsBootstrapAtomValue.bytesValue:
builder.writeByteArray(value.getBytesValue());
break;
+ case StatsBootstrapAtomValue.stringArrayValue:
+ builder.writeStringArray(value.getStringArrayValue());
+ break;
default:
Slog.e(TAG, "Unexpected value type " + value.getTag()
+ " when logging atom " + atom.atomId);
diff --git a/services/core/java/com/android/server/wm/ActivityStartController.java b/services/core/java/com/android/server/wm/ActivityStartController.java
index 35ec5adf54b0..0580d4a5a4a3 100644
--- a/services/core/java/com/android/server/wm/ActivityStartController.java
+++ b/services/core/java/com/android/server/wm/ActivityStartController.java
@@ -43,7 +43,6 @@ import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.os.Binder;
-import android.os.Bundle;
import android.os.IBinder;
import android.os.Trace;
import android.os.UserHandle;
@@ -550,14 +549,14 @@ public class ActivityStartController {
* Starts an activity in the TaskFragment.
* @param taskFragment TaskFragment {@link TaskFragment} to start the activity in.
* @param activityIntent intent to start the activity.
- * @param activityOptions ActivityOptions to start the activity with.
+ * @param activityOptions SafeActivityOptions to start the activity with.
* @param resultTo the caller activity
* @param callingUid the caller uid
* @param callingPid the caller pid
* @return the start result.
*/
int startActivityInTaskFragment(@NonNull TaskFragment taskFragment,
- @NonNull Intent activityIntent, @Nullable Bundle activityOptions,
+ @NonNull Intent activityIntent, @Nullable SafeActivityOptions activityOptions,
@Nullable IBinder resultTo, int callingUid, int callingPid,
@Nullable IBinder errorCallbackToken) {
final ActivityRecord caller =
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 2229807f5db1..82c7a9350eca 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -1523,8 +1523,10 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
final IBinder callerActivityToken = operation.getActivityToken();
final Intent activityIntent = operation.getActivityIntent();
final Bundle activityOptions = operation.getBundle();
+ final SafeActivityOptions safeOptions =
+ SafeActivityOptions.fromBundle(activityOptions, caller.mPid, caller.mUid);
final int result = waitAsyncStart(() -> mService.getActivityStartController()
- .startActivityInTaskFragment(taskFragment, activityIntent, activityOptions,
+ .startActivityInTaskFragment(taskFragment, activityIntent, safeOptions,
callerActivityToken, caller.mUid, caller.mPid,
errorCallbackToken));
if (!isStartResultSuccessful(result)) {
diff --git a/services/core/xsd/display-device-config/display-device-config.xsd b/services/core/xsd/display-device-config/display-device-config.xsd
index 776de2e52061..20c69ac93f63 100644
--- a/services/core/xsd/display-device-config/display-device-config.xsd
+++ b/services/core/xsd/display-device-config/display-device-config.xsd
@@ -464,7 +464,7 @@
<xs:annotation name="nonnull"/>
<xs:annotation name="final"/>
</xs:element>
- <xs:element name="customAnimationRateSec" type="nonNegativeDecimal" minOccurs="0" maxOccurs="1">
+ <xs:element name="customAnimationRate" type="nonNegativeDecimal" minOccurs="0" maxOccurs="1">
<xs:annotation name="nonnull"/>
<xs:annotation name="final"/>
</xs:element>
diff --git a/services/core/xsd/display-device-config/schema/current.txt b/services/core/xsd/display-device-config/schema/current.txt
index 110a5a20da6a..a8f18b3d2eee 100644
--- a/services/core/xsd/display-device-config/schema/current.txt
+++ b/services/core/xsd/display-device-config/schema/current.txt
@@ -345,12 +345,12 @@ package com.android.server.display.config {
public class PowerThrottlingConfig {
ctor public PowerThrottlingConfig();
method @NonNull public final java.math.BigDecimal getBrightnessLowestCapAllowed();
- method @NonNull public final java.math.BigDecimal getCustomAnimationRateSec();
+ method @NonNull public final java.math.BigDecimal getCustomAnimationRate();
method @NonNull public final java.math.BigInteger getPollingWindowMaxMillis();
method @NonNull public final java.math.BigInteger getPollingWindowMinMillis();
method public final java.util.List<com.android.server.display.config.PowerThrottlingMap> getPowerThrottlingMap();
method public final void setBrightnessLowestCapAllowed(@NonNull java.math.BigDecimal);
- method public final void setCustomAnimationRateSec(@NonNull java.math.BigDecimal);
+ method public final void setCustomAnimationRate(@NonNull java.math.BigDecimal);
method public final void setPollingWindowMaxMillis(@NonNull java.math.BigInteger);
method public final void setPollingWindowMinMillis(@NonNull java.math.BigInteger);
}
diff --git a/services/tests/appfunctions/src/com/android/server/appfunctions/MetadataSyncAdapterTest.kt b/services/tests/appfunctions/src/com/android/server/appfunctions/MetadataSyncAdapterTest.kt
index 5758da858423..96fb4535b992 100644
--- a/services/tests/appfunctions/src/com/android/server/appfunctions/MetadataSyncAdapterTest.kt
+++ b/services/tests/appfunctions/src/com/android/server/appfunctions/MetadataSyncAdapterTest.kt
@@ -391,6 +391,10 @@ class MetadataSyncAdapterTest {
return AndroidFuture.completedFuture(mutableListOf())
}
}
+
+ override fun close() {
+ Log.d("FakeRuntimeMetadataSearchSession", "Closing session")
+ }
}
return AndroidFuture.completedFuture(futureSearchResults)
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
index 3976ea4fc86e..2220f439f6c8 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
@@ -265,7 +265,7 @@ public final class DisplayDeviceConfigTest {
mDisplayDeviceConfig.getPowerThrottlingConfigData();
assertNotNull(powerThrottlingConfigData);
assertEquals(0.1f, powerThrottlingConfigData.brightnessLowestCapAllowed, SMALL_DELTA);
- assertEquals(15f, powerThrottlingConfigData.customAnimationRateSec, SMALL_DELTA);
+ assertEquals(15f, powerThrottlingConfigData.customAnimationRate, SMALL_DELTA);
assertEquals(20000, powerThrottlingConfigData.pollingWindowMaxMillis);
assertEquals(10000, powerThrottlingConfigData.pollingWindowMinMillis);
}
@@ -1299,7 +1299,7 @@ public final class DisplayDeviceConfigTest {
private String getPowerThrottlingConfig() {
return "<powerThrottlingConfig >\n"
+ "<brightnessLowestCapAllowed>0.1</brightnessLowestCapAllowed>\n"
- + "<customAnimationRateSec>15</customAnimationRateSec>\n"
+ + "<customAnimationRate>15</customAnimationRate>\n"
+ "<pollingWindowMaxMillis>20000</pollingWindowMaxMillis>\n"
+ "<pollingWindowMinMillis>10000</pollingWindowMinMillis>\n"
+ "<powerThrottlingMap>\n"
diff --git a/services/tests/servicestests/AndroidManifest.xml b/services/tests/servicestests/AndroidManifest.xml
index 2724149d859f..c645c0852f1b 100644
--- a/services/tests/servicestests/AndroidManifest.xml
+++ b/services/tests/servicestests/AndroidManifest.xml
@@ -113,6 +113,7 @@
<uses-permission android:name="android.permission.MANAGE_ROLE_HOLDERS" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.CAMERA" />
+ <uses-permission android:name="android.permission.CREATE_VIRTUAL_DEVICE" />
<queries>
<package android:name="com.android.servicestests.apps.suspendtestapp" />
diff --git a/services/tests/servicestests/src/com/android/server/appop/AppOpsActiveWatcherTest.java b/services/tests/servicestests/src/com/android/server/appop/AppOpsActiveWatcherTest.java
index c970a3e34d12..840e5c58078b 100644
--- a/services/tests/servicestests/src/com/android/server/appop/AppOpsActiveWatcherTest.java
+++ b/services/tests/servicestests/src/com/android/server/appop/AppOpsActiveWatcherTest.java
@@ -65,7 +65,6 @@ public class AppOpsActiveWatcherTest {
VirtualDeviceRule.withAdditionalPermissions(
Manifest.permission.GRANT_RUNTIME_PERMISSIONS,
Manifest.permission.REVOKE_RUNTIME_PERMISSIONS,
- Manifest.permission.CREATE_VIRTUAL_DEVICE,
Manifest.permission.GET_APP_OPS_STATS
);
private static final long NOTIFICATION_TIMEOUT_MILLIS = 5000;
diff --git a/services/tests/servicestests/src/com/android/server/appop/AppOpsDeviceAwareServiceTest.java b/services/tests/servicestests/src/com/android/server/appop/AppOpsDeviceAwareServiceTest.java
index 7f2327aa4f24..e3eca6d5fd83 100644
--- a/services/tests/servicestests/src/com/android/server/appop/AppOpsDeviceAwareServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/appop/AppOpsDeviceAwareServiceTest.java
@@ -58,7 +58,6 @@ public class AppOpsDeviceAwareServiceTest {
VirtualDeviceRule.withAdditionalPermissions(
Manifest.permission.GRANT_RUNTIME_PERMISSIONS,
Manifest.permission.REVOKE_RUNTIME_PERMISSIONS,
- Manifest.permission.CREATE_VIRTUAL_DEVICE,
Manifest.permission.GET_APP_OPS_STATS);
private static final String ATTRIBUTION_TAG_1 = "attributionTag1";
diff --git a/services/tests/servicestests/src/com/android/server/appop/AppOpsNotedWatcherTest.java b/services/tests/servicestests/src/com/android/server/appop/AppOpsNotedWatcherTest.java
index 1abd4eb6157f..b0846f62628c 100644
--- a/services/tests/servicestests/src/com/android/server/appop/AppOpsNotedWatcherTest.java
+++ b/services/tests/servicestests/src/com/android/server/appop/AppOpsNotedWatcherTest.java
@@ -22,16 +22,14 @@ import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
import android.app.AppOpsManager;
import android.app.AppOpsManager.OnOpNotedListener;
import android.companion.virtual.VirtualDeviceManager;
-import android.companion.virtual.VirtualDeviceParams;
import android.content.AttributionSource;
import android.content.Context;
import android.os.Process;
-import android.virtualdevice.cts.common.FakeAssociationRule;
+import android.virtualdevice.cts.common.VirtualDeviceRule;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
@@ -42,8 +40,6 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InOrder;
-import java.util.concurrent.atomic.AtomicInteger;
-
/**
* Tests watching noted ops.
*/
@@ -51,7 +47,7 @@ import java.util.concurrent.atomic.AtomicInteger;
@RunWith(AndroidJUnit4.class)
public class AppOpsNotedWatcherTest {
@Rule
- public FakeAssociationRule mFakeAssociationRule = new FakeAssociationRule();
+ public VirtualDeviceRule mVirtualDeviceRule = VirtualDeviceRule.createDefault();
private static final long NOTIFICATION_TIMEOUT_MILLIS = 5000;
@Test
@@ -119,19 +115,12 @@ public class AppOpsNotedWatcherTest {
public void testWatchNotedOpsForExternalDevice() {
final AppOpsManager.OnOpNotedListener listener = mock(
AppOpsManager.OnOpNotedListener.class);
- final VirtualDeviceManager virtualDeviceManager = getContext().getSystemService(
- VirtualDeviceManager.class);
- AtomicInteger virtualDeviceId = new AtomicInteger();
- runWithShellPermissionIdentity(() -> {
- final VirtualDeviceManager.VirtualDevice virtualDevice =
- virtualDeviceManager.createVirtualDevice(
- mFakeAssociationRule.getAssociationInfo().getId(),
- new VirtualDeviceParams.Builder().setName("virtual_device").build());
- virtualDeviceId.set(virtualDevice.getDeviceId());
- });
+ final VirtualDeviceManager.VirtualDevice virtualDevice =
+ mVirtualDeviceRule.createManagedVirtualDevice();
+ final int virtualDeviceId = virtualDevice.getDeviceId();
AttributionSource attributionSource = new AttributionSource(Process.myUid(),
getContext().getOpPackageName(), getContext().getAttributionTag(),
- virtualDeviceId.get());
+ virtualDeviceId);
final AppOpsManager appOpsManager = getContext().getSystemService(AppOpsManager.class);
appOpsManager.startWatchingNoted(new int[]{AppOpsManager.OP_FINE_LOCATION,
@@ -142,7 +131,7 @@ public class AppOpsNotedWatcherTest {
verify(listener, timeout(NOTIFICATION_TIMEOUT_MILLIS)
.times(1)).onOpNoted(eq(AppOpsManager.OPSTR_FINE_LOCATION),
eq(Process.myUid()), eq(getContext().getOpPackageName()),
- eq(getContext().getAttributionTag()), eq(virtualDeviceId.get()),
+ eq(getContext().getAttributionTag()), eq(virtualDeviceId),
eq(AppOpsManager.OP_FLAG_SELF), eq(AppOpsManager.MODE_ALLOWED));
appOpsManager.finishOp(getContext().getAttributionSource().getToken(),
diff --git a/services/tests/servicestests/src/com/android/server/appop/AppOpsStartedWatcherTest.java b/services/tests/servicestests/src/com/android/server/appop/AppOpsStartedWatcherTest.java
index 8a6ba4d484f7..d46fb90f40d6 100644
--- a/services/tests/servicestests/src/com/android/server/appop/AppOpsStartedWatcherTest.java
+++ b/services/tests/servicestests/src/com/android/server/appop/AppOpsStartedWatcherTest.java
@@ -16,8 +16,6 @@
package com.android.server.appop;
-import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
-
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
@@ -28,11 +26,10 @@ import static org.mockito.Mockito.verifyNoMoreInteractions;
import android.app.AppOpsManager;
import android.app.AppOpsManager.OnOpStartedListener;
import android.companion.virtual.VirtualDeviceManager;
-import android.companion.virtual.VirtualDeviceParams;
import android.content.AttributionSource;
import android.content.Context;
import android.os.Process;
-import android.virtualdevice.cts.common.FakeAssociationRule;
+import android.virtualdevice.cts.common.VirtualDeviceRule;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
@@ -43,15 +40,13 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InOrder;
-import java.util.concurrent.atomic.AtomicInteger;
-
/** Tests watching started ops. */
@SmallTest
@RunWith(AndroidJUnit4.class)
public class AppOpsStartedWatcherTest {
@Rule
- public FakeAssociationRule mFakeAssociationRule = new FakeAssociationRule();
+ public VirtualDeviceRule mVirtualDeviceRule = VirtualDeviceRule.createDefault();
private static final long NOTIFICATION_TIMEOUT_MILLIS = 5000;
@Test
@@ -124,20 +119,13 @@ public class AppOpsStartedWatcherTest {
@Test
public void testWatchStartedOpsForExternalDevice() {
- final VirtualDeviceManager virtualDeviceManager = getContext().getSystemService(
- VirtualDeviceManager.class);
- AtomicInteger virtualDeviceId = new AtomicInteger();
- runWithShellPermissionIdentity(() -> {
- final VirtualDeviceManager.VirtualDevice virtualDevice =
- virtualDeviceManager.createVirtualDevice(
- mFakeAssociationRule.getAssociationInfo().getId(),
- new VirtualDeviceParams.Builder().setName("virtual_device").build());
- virtualDeviceId.set(virtualDevice.getDeviceId());
- });
+ final VirtualDeviceManager.VirtualDevice virtualDevice =
+ mVirtualDeviceRule.createManagedVirtualDevice();
+ final int virtualDeviceId = virtualDevice.getDeviceId();
final OnOpStartedListener listener = mock(OnOpStartedListener.class);
AttributionSource attributionSource = new AttributionSource(Process.myUid(),
getContext().getOpPackageName(), getContext().getAttributionTag(),
- virtualDeviceId.get());
+ virtualDeviceId);
final AppOpsManager appOpsManager = getContext().getSystemService(AppOpsManager.class);
appOpsManager.startWatchingStarted(new int[]{AppOpsManager.OP_FINE_LOCATION,
@@ -150,7 +138,7 @@ public class AppOpsStartedWatcherTest {
verify(listener, timeout(NOTIFICATION_TIMEOUT_MILLIS)
.times(1)).onOpStarted(eq(AppOpsManager.OP_FINE_LOCATION),
eq(Process.myUid()), eq(getContext().getOpPackageName()),
- eq(getContext().getAttributionTag()), eq(virtualDeviceId.get()),
+ eq(getContext().getAttributionTag()), eq(virtualDeviceId),
eq(AppOpsManager.OP_FLAG_SELF),
eq(AppOpsManager.MODE_ALLOWED), eq(OnOpStartedListener.START_TYPE_STARTED),
eq(AppOpsManager.ATTRIBUTION_FLAGS_NONE),
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
index 62f5edce4e36..dad45b3048b9 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
@@ -50,7 +50,6 @@ import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import static org.testng.Assert.assertThrows;
-import android.Manifest;
import android.app.WindowConfiguration;
import android.app.admin.DevicePolicyManager;
import android.companion.AssociationInfo;
@@ -113,10 +112,11 @@ import android.view.Display;
import android.view.DisplayInfo;
import android.view.KeyEvent;
import android.view.WindowManager;
+import android.virtualdevice.cts.common.VirtualDeviceRule;
import androidx.test.platform.app.InstrumentationRegistry;
-import com.android.compatibility.common.util.AdoptShellPermissionsRule;
+import com.android.compatibility.common.util.SystemUtil;
import com.android.internal.app.BlockedAppStreamingActivity;
import com.android.internal.os.BackgroundThread;
import com.android.server.LocalServices;
@@ -224,9 +224,7 @@ public class VirtualDeviceManagerServiceTest {
public SetFlagsRule mSetFlagsRule = new SetFlagsRule();
@Rule
- public AdoptShellPermissionsRule mAdoptShellPermissionsRule = new AdoptShellPermissionsRule(
- InstrumentationRegistry.getInstrumentation().getUiAutomation(),
- Manifest.permission.CREATE_VIRTUAL_DEVICE);
+ public VirtualDeviceRule mVirtualDeviceRule = VirtualDeviceRule.createDefault();
private Context mContext;
private InputManagerMockHelper mInputManagerMockHelper;
@@ -1069,64 +1067,65 @@ public class VirtualDeviceManagerServiceTest {
@Test
public void createVirtualDpad_noPermission_failsSecurityException() {
addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
- try (DropShellPermissionsTemporarily drop = new DropShellPermissionsTemporarily()) {
- assertThrows(SecurityException.class,
- () -> mDeviceImpl.createVirtualDpad(DPAD_CONFIG, BINDER));
- }
+ // Shell doesn't have CREATE_VIRTUAL_DEVICE permission.
+ SystemUtil.runWithShellPermissionIdentity(() ->
+ assertThrows(SecurityException.class,
+ () -> mDeviceImpl.createVirtualDpad(DPAD_CONFIG, BINDER)));
}
@Test
public void createVirtualKeyboard_noPermission_failsSecurityException() {
addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
- try (DropShellPermissionsTemporarily drop = new DropShellPermissionsTemporarily()) {
- assertThrows(SecurityException.class,
- () -> mDeviceImpl.createVirtualKeyboard(KEYBOARD_CONFIG, BINDER));
- }
+ // Shell doesn't have CREATE_VIRTUAL_DEVICE permission.
+ SystemUtil.runWithShellPermissionIdentity(() ->
+ assertThrows(SecurityException.class,
+ () -> mDeviceImpl.createVirtualKeyboard(KEYBOARD_CONFIG, BINDER)));
}
@Test
public void createVirtualMouse_noPermission_failsSecurityException() {
addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
- try (DropShellPermissionsTemporarily drop = new DropShellPermissionsTemporarily()) {
- assertThrows(SecurityException.class,
- () -> mDeviceImpl.createVirtualMouse(MOUSE_CONFIG, BINDER));
- }
+ // Shell doesn't have CREATE_VIRTUAL_DEVICE permission.
+ SystemUtil.runWithShellPermissionIdentity(() ->
+ assertThrows(SecurityException.class,
+ () -> mDeviceImpl.createVirtualMouse(MOUSE_CONFIG, BINDER)));
}
@Test
public void createVirtualTouchscreen_noPermission_failsSecurityException() {
addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
- try (DropShellPermissionsTemporarily drop = new DropShellPermissionsTemporarily()) {
- assertThrows(SecurityException.class,
- () -> mDeviceImpl.createVirtualTouchscreen(TOUCHSCREEN_CONFIG, BINDER));
- }
+ // Shell doesn't have CREATE_VIRTUAL_DEVICE permission.
+ SystemUtil.runWithShellPermissionIdentity(() ->
+ assertThrows(SecurityException.class,
+ () -> mDeviceImpl.createVirtualTouchscreen(TOUCHSCREEN_CONFIG, BINDER)));
}
@Test
public void createVirtualNavigationTouchpad_noPermission_failsSecurityException() {
addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
- try (DropShellPermissionsTemporarily drop = new DropShellPermissionsTemporarily()) {
- assertThrows(SecurityException.class,
- () -> mDeviceImpl.createVirtualNavigationTouchpad(NAVIGATION_TOUCHPAD_CONFIG,
- BINDER));
- }
+ // Shell doesn't have CREATE_VIRTUAL_DEVICE permission.
+ SystemUtil.runWithShellPermissionIdentity(() ->
+ assertThrows(SecurityException.class,
+ () -> mDeviceImpl.createVirtualNavigationTouchpad(
+ NAVIGATION_TOUCHPAD_CONFIG,
+ BINDER)));
}
@Test
public void onAudioSessionStarting_noPermission_failsSecurityException() {
addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
- try (DropShellPermissionsTemporarily drop = new DropShellPermissionsTemporarily()) {
- assertThrows(SecurityException.class,
- () -> mDeviceImpl.onAudioSessionStarting(
- DISPLAY_ID_1, mRoutingCallback, mConfigChangedCallback));
- }
+ // Shell doesn't have CREATE_VIRTUAL_DEVICE permission.
+ SystemUtil.runWithShellPermissionIdentity(() ->
+ assertThrows(SecurityException.class,
+ () -> mDeviceImpl.onAudioSessionStarting(
+ DISPLAY_ID_1, mRoutingCallback, mConfigChangedCallback)));
}
@Test
public void onAudioSessionEnded_noPermission_failsSecurityException() {
- try (DropShellPermissionsTemporarily drop = new DropShellPermissionsTemporarily()) {
- assertThrows(SecurityException.class, () -> mDeviceImpl.onAudioSessionEnded());
- }
+ // Shell doesn't have CREATE_VIRTUAL_DEVICE permission.
+ SystemUtil.runWithShellPermissionIdentity(() ->
+ assertThrows(SecurityException.class, () -> mDeviceImpl.onAudioSessionEnded()));
}
@Test
@@ -2002,18 +2001,4 @@ public class VirtualDeviceManagerServiceTest {
/* timeApprovedMs= */0, /* lastTimeConnectedMs= */0,
/* systemDataSyncFlags= */ -1, /* deviceIcon= */ null);
}
-
- /** Helper class to drop permissions temporarily and restore them at the end of a test. */
- static final class DropShellPermissionsTemporarily implements AutoCloseable {
- DropShellPermissionsTemporarily() {
- InstrumentationRegistry.getInstrumentation().getUiAutomation()
- .dropShellPermissionIdentity();
- }
-
- @Override
- public void close() {
- InstrumentationRegistry.getInstrumentation().getUiAutomation()
- .adoptShellPermissionIdentity();
- }
- }
}
diff --git a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java
index 425bb158f997..7e22d74c64e1 100644
--- a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java
@@ -1256,7 +1256,8 @@ public class MediaProjectionManagerServiceTest {
Manifest.permission.BYPASS_ROLE_QUALIFICATION);
roleManager.setBypassingRoleQualification(true);
- roleManager.addRoleHolderAsUser(role, packageName, /* flags = */ 0, user,
+ roleManager.addRoleHolderAsUser(role, packageName,
+ /* flags= */ RoleManager.MANAGE_HOLDERS_FLAG_DONT_KILL_APP, user,
mContext.getMainExecutor(), success -> {
if (success) {
latch.countDown();
@@ -1271,9 +1272,9 @@ public class MediaProjectionManagerServiceTest {
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
- roleManager.removeRoleHolderAsUser(role, packageName, 0, user,
- mContext.getMainExecutor(), (aBool) -> {
- });
+ roleManager.removeRoleHolderAsUser(role, packageName,
+ /* flags= */ RoleManager.MANAGE_HOLDERS_FLAG_DONT_KILL_APP, user,
+ mContext.getMainExecutor(), (aBool) -> {});
roleManager.setBypassingRoleQualification(false);
instrumentation.getUiAutomation()
.dropShellPermissionIdentity();
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
index f56825faab73..42e31de295d6 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
@@ -16,6 +16,7 @@
package com.android.server.wm;
+import static android.Manifest.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS;
import static android.app.ActivityManager.START_CANCELED;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
@@ -28,6 +29,8 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+import static android.content.pm.PackageManager.PERMISSION_DENIED;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.content.res.Configuration.SCREEN_HEIGHT_DP_UNDEFINED;
import static android.content.res.Configuration.SCREEN_WIDTH_DP_UNDEFINED;
import static android.view.InsetsSource.FLAG_FORCE_CONSUMING;
@@ -37,6 +40,7 @@ import static android.window.DisplayAreaOrganizer.FEATURE_VENDOR_FIRST;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.never;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;
@@ -61,6 +65,7 @@ import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.quality.Strictness.LENIENT;
import android.annotation.NonNull;
import android.app.ActivityManager;
@@ -69,6 +74,7 @@ import android.app.ActivityOptions;
import android.app.ActivityTaskManager.RootTaskInfo;
import android.app.IRequestFinishCallback;
import android.app.PictureInPictureParams;
+import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.ParceledListSlice;
import android.content.res.Configuration;
@@ -87,6 +93,7 @@ import android.view.WindowInsets;
import android.window.ITaskFragmentOrganizer;
import android.window.ITaskOrganizer;
import android.window.IWindowContainerTransactionCallback;
+import android.window.RemoteTransition;
import android.window.StartingWindowInfo;
import android.window.StartingWindowRemovalInfo;
import android.window.TaskAppearedInfo;
@@ -102,6 +109,7 @@ import com.android.window.flags.Flags;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
+import org.mockito.MockitoSession;
import java.util.ArrayList;
import java.util.HashSet;
@@ -638,6 +646,66 @@ public class WindowOrganizerTests extends WindowTestsBase {
}
@Test
+ public void testStartActivityInTaskFragment_checkCallerPermission() {
+ final ActivityStartController activityStartController =
+ mWm.mAtmService.getActivityStartController();
+ spyOn(activityStartController);
+ final ArgumentCaptor<SafeActivityOptions> activityOptionsCaptor =
+ ArgumentCaptor.forClass(SafeActivityOptions.class);
+
+ final int uid = Binder.getCallingUid();
+ final Task rootTask = new TaskBuilder(mSupervisor).setCreateActivity(true)
+ .setWindowingMode(WINDOWING_MODE_FULLSCREEN).build();
+ final WindowContainerTransaction t = new WindowContainerTransaction();
+ final TaskFragmentOrganizer organizer =
+ createTaskFragmentOrganizer(t, true /* isSystemOrganizer */);
+ final IBinder token = new Binder();
+ final TaskFragment taskFragment = new TaskFragmentBuilder(mAtm)
+ .setParentTask(rootTask)
+ .setFragmentToken(token)
+ .setOrganizer(organizer)
+ .createActivityCount(1)
+ .build();
+ mWm.mAtmService.mWindowOrganizerController.mLaunchTaskFragments.put(token, taskFragment);
+ final ActivityRecord ownerActivity = taskFragment.getTopMostActivity();
+
+ // Start Activity in TaskFragment with remote transition.
+ final RemoteTransition transition = mock(RemoteTransition.class);
+ final ActivityOptions options = ActivityOptions.makeRemoteTransition(transition);
+ final Intent intent = new Intent();
+ t.startActivityInTaskFragment(token, ownerActivity.token, intent, options.toBundle());
+ mWm.mAtmService.mWindowOrganizerController.applyTaskFragmentTransactionLocked(
+ t, TaskFragmentOrganizer.TASK_FRAGMENT_TRANSIT_OPEN,
+ false /* shouldApplyIndependently */, null /* remoteTransition */);
+
+ // Get the ActivityOptions.
+ verify(activityStartController).startActivityInTaskFragment(
+ eq(taskFragment), eq(intent), activityOptionsCaptor.capture(),
+ eq(ownerActivity.token), eq(uid), anyInt(), any());
+ final SafeActivityOptions safeActivityOptions = activityOptionsCaptor.getValue();
+
+ final MockitoSession session =
+ mockitoSession().strictness(LENIENT).spyStatic(ActivityTaskManagerService.class)
+ .startMocking();
+ try {
+ // Without the CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS permission, start activity with
+ // remote transition is not allowed.
+ doReturn(PERMISSION_DENIED).when(() -> ActivityTaskManagerService.checkPermission(
+ eq(CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS), anyInt(), eq(uid)));
+ assertThrows(SecurityException.class,
+ () -> safeActivityOptions.getOptions(mWm.mAtmService.mTaskSupervisor));
+
+ // With the CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS permission, start activity with
+ // remote transition is allowed.
+ doReturn(PERMISSION_GRANTED).when(() -> ActivityTaskManagerService.checkPermission(
+ eq(CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS), anyInt(), eq(uid)));
+ safeActivityOptions.getOptions(mWm.mAtmService.mTaskSupervisor);
+ } finally {
+ session.finishMocking();
+ }
+ }
+
+ @Test
public void testTaskFragmentChangeHidden_throwsWhenNotSystemOrganizer() {
// Non-system organizers are not allow to update the hidden state.
testTaskFragmentChangesWithoutSystemOrganizerThrowException(
diff --git a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/LetterboxAppHelper.kt b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/LetterboxAppHelper.kt
index 634b6eedd7e6..8d27c1d1dfd1 100644
--- a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/LetterboxAppHelper.kt
+++ b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/LetterboxAppHelper.kt
@@ -33,9 +33,9 @@ class LetterboxAppHelper
@JvmOverloads
constructor(
instr: Instrumentation,
- launcherName: String = ActivityOptions.NonResizeablePortraitActivity.LABEL,
+ launcherName: String = ActivityOptions.NonResizeableFixedAspectRatioPortraitActivity.LABEL,
component: ComponentNameMatcher =
- ActivityOptions.NonResizeablePortraitActivity.COMPONENT.toFlickerComponent()
+ ActivityOptions.NonResizeableFixedAspectRatioPortraitActivity.COMPONENT.toFlickerComponent()
) : StandardAppHelper(instr, launcherName, component) {
private val gestureHelper: GestureHelper = GestureHelper(instrumentation)
diff --git a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
index f891606f0066..f2e34257ef01 100644
--- a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
+++ b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
@@ -115,6 +115,19 @@
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
+ <activity android:name=".NonResizeableFixedAspectRatioPortraitActivity"
+ android:theme="@style/CutoutNever"
+ android:resizeableActivity="false"
+ android:screenOrientation="portrait"
+ android:minAspectRatio="1.77"
+ android:taskAffinity="com.android.server.wm.flicker.testapp.NonResizeableFixedAspectRatioPortraitActivity"
+ android:label="NonResizeableFixedAspectRatioPortraitActivity"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.LAUNCHER"/>
+ </intent-filter>
+ </activity>
<activity android:name=".StartMediaProjectionActivity"
android:theme="@style/CutoutNever"
android:resizeableActivity="false"
@@ -143,6 +156,7 @@
<activity android:name=".LaunchTransparentActivity"
android:resizeableActivity="false"
android:screenOrientation="portrait"
+ android:minAspectRatio="1.77"
android:theme="@style/OptOutEdgeToEdge"
android:taskAffinity="com.android.server.wm.flicker.testapp.LaunchTransparentActivity"
android:label="LaunchTransparentActivity"
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java
index e4de2c574553..73625da9dfa5 100644
--- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java
@@ -85,6 +85,12 @@ public class ActivityOptions {
FLICKER_APP_PACKAGE + ".NonResizeablePortraitActivity");
}
+ public static class NonResizeableFixedAspectRatioPortraitActivity {
+ public static final String LABEL = "NonResizeableFixedAspectRatioPortraitActivity";
+ public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE,
+ FLICKER_APP_PACKAGE + ".NonResizeableFixedAspectRatioPortraitActivity");
+ }
+
public static class StartMediaProjectionActivity {
public static final String LABEL = "StartMediaProjectionActivity";
public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE,
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/NonResizeableFixedAspectRatioPortraitActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/NonResizeableFixedAspectRatioPortraitActivity.java
new file mode 100644
index 000000000000..be38c259d00d
--- /dev/null
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/NonResizeableFixedAspectRatioPortraitActivity.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.testapp;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+public class NonResizeableFixedAspectRatioPortraitActivity extends Activity {
+ @Override
+ protected void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+ setContentView(R.layout.activity_non_resizeable);
+ }
+}