summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/controllers/BackgroundJobsController.java5
-rw-r--r--core/api/current.txt7
-rw-r--r--core/api/system-current.txt4
-rw-r--r--core/java/android/app/ActivityThread.java2
-rw-r--r--core/java/android/app/ApplicationStartInfo.java11
-rw-r--r--core/java/android/content/pm/ILauncherApps.aidl1
-rw-r--r--core/java/android/content/pm/LauncherApps.java23
-rw-r--r--core/java/android/content/pm/PackageInfo.java2
-rw-r--r--core/java/android/hardware/camera2/CameraMetadata.java2
-rw-r--r--core/java/android/hardware/camera2/CaptureRequest.java4
-rw-r--r--core/java/android/hardware/camera2/CaptureResult.java4
-rw-r--r--core/java/android/os/flags.aconfig7
-rw-r--r--core/java/android/service/notification/ZenModeConfig.java1
-rw-r--r--core/java/android/view/ISurfaceControlViewHostParent.aidl3
-rw-r--r--core/java/android/view/SurfaceControlViewHost.java1
-rw-r--r--core/java/android/view/SurfaceView.java38
-rw-r--r--core/java/android/view/ViewRootImpl.java98
-rw-r--r--core/java/android/view/WindowlessWindowManager.java14
-rw-r--r--core/java/android/view/flags/refresh_rate_flags.aconfig8
-rw-r--r--core/java/com/android/internal/widget/LockPatternUtils.java15
-rw-r--r--core/java/com/android/internal/widget/LockscreenCredential.java10
-rw-r--r--core/jni/com_android_internal_os_ZygoteCommandBuffer.cpp14
-rw-r--r--core/res/res/values/strings.xml2
-rw-r--r--core/res/res/values/symbols.xml1
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java26
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java26
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitWindowManager.java12
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java15
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/DividerViewTest.java2
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitWindowManagerTests.java2
-rw-r--r--media/java/android/media/AudioHalVersionInfo.java3
-rw-r--r--packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java17
-rw-r--r--packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/common/BroadcastReceiverFlow.kt6
-rw-r--r--packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/framework/common/BroadcastReceiverFlowTest.kt14
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java388
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastAssistant.java231
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateSharedAsState.kt6
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt12
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt5
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt4
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/ObservableTransitionState.kt18
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt149
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt7
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt33
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt74
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt16
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/preview/KeyguardRemotePreviewManagerTest.kt40
-rw-r--r--packages/SystemUI/res/drawable/arrow_pointing_down.xml26
-rw-r--r--packages/SystemUI/res/layout/record_issue_dialog.xml81
-rw-r--r--packages/SystemUI/res/values/strings.xml14
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt45
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardRemotePreviewManager.kt103
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/ColorSchemeTransition.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt48
-rw-r--r--packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt88
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java35
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/kotlin/Log.kt40
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt1
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RecordIssueTileTest.kt37
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt93
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsActivityTest.java2
-rw-r--r--services/core/java/android/content/pm/PackageManagerInternal.java11
-rw-r--r--services/core/java/com/android/server/am/ActiveServices.java7
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerService.java85
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerShellCommand.java2
-rw-r--r--services/core/java/com/android/server/am/AppStartInfoTracker.java122
-rw-r--r--services/core/java/com/android/server/am/ProcessList.java7
-rw-r--r--services/core/java/com/android/server/content/SyncManager.java4
-rw-r--r--services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java7
-rw-r--r--services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java19
-rw-r--r--services/core/java/com/android/server/inputmethod/InputMethodManagerService.java3
-rw-r--r--services/core/java/com/android/server/locksettings/LockSettingsService.java63
-rw-r--r--services/core/java/com/android/server/locksettings/LockSettingsStorage.java4
-rw-r--r--services/core/java/com/android/server/locksettings/UnifiedProfilePasswordCache.java (renamed from services/core/java/com/android/server/locksettings/ManagedProfilePasswordCache.java)34
-rw-r--r--services/core/java/com/android/server/notification/ZenModeHelper.java4
-rw-r--r--services/core/java/com/android/server/pdb/PersistentDataBlockService.java4
-rw-r--r--services/core/java/com/android/server/pm/Computer.java9
-rw-r--r--services/core/java/com/android/server/pm/ComputerEngine.java21
-rw-r--r--services/core/java/com/android/server/pm/IPackageManagerBase.java18
-rw-r--r--services/core/java/com/android/server/pm/InstallPackageHelper.java6
-rw-r--r--services/core/java/com/android/server/pm/LauncherAppsService.java24
-rw-r--r--services/core/java/com/android/server/pm/PackageArchiver.java59
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerInternalBase.java7
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerService.java16
-rw-r--r--services/core/java/com/android/server/pm/SuspendPackageHelper.java15
-rw-r--r--services/core/java/com/android/server/pm/UserManagerService.java10
-rw-r--r--services/core/java/com/android/server/policy/PhoneWindowManager.java131
-rw-r--r--services/core/java/com/android/server/policy/TalkbackShortcutController.java118
-rw-r--r--services/core/java/com/android/server/trust/TrustManagerService.java14
-rw-r--r--services/core/java/com/android/server/wm/ActivityMetricsLaunchObserver.java9
-rw-r--r--services/core/java/com/android/server/wm/ActivityMetricsLogger.java6
-rw-r--r--services/core/java/com/android/server/wm/ActivityTaskManagerService.java3
-rw-r--r--services/core/java/com/android/server/wm/BackgroundActivityStartController.java28
-rw-r--r--services/core/java/com/android/server/wm/ClientLifecycleManager.java107
-rw-r--r--services/core/java/com/android/server/wm/LaunchObserverRegistryImpl.java18
-rw-r--r--services/core/java/com/android/server/wm/RootWindowContainer.java3
-rw-r--r--services/core/java/com/android/server/wm/TaskFragment.java43
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerInternal.java5
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerService.java6
-rw-r--r--services/core/java/com/android/server/wm/WindowSurfacePlacer.java4
-rw-r--r--services/core/jni/com_android_server_pm_PackageManagerShellCommandDataLoader.cpp2
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/job/controllers/BackgroundJobsControllerTest.java9
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java20
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/pm/PackageHelperTestBase.kt1
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt66
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java62
-rw-r--r--services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java4
-rw-r--r--services/tests/uiservicestests/Android.bp1
-rwxr-xr-xservices/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java8
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java13
-rw-r--r--services/tests/wmtests/src/com/android/server/policy/ModifierShortcutTests.java11
-rw-r--r--services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java19
-rw-r--r--services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java43
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java4
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java28
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ClientLifecycleManagerTests.java117
-rw-r--r--telephony/java/android/telephony/satellite/NtnSignalStrength.java3
-rw-r--r--telephony/java/android/telephony/satellite/SatelliteManager.java21
-rw-r--r--telephony/java/com/android/internal/telephony/ITelephony.aidl15
121 files changed, 2626 insertions, 885 deletions
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/BackgroundJobsController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/BackgroundJobsController.java
index 03891bbec56a..e3ba50dc635b 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/BackgroundJobsController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/BackgroundJobsController.java
@@ -26,6 +26,7 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.os.SystemClock;
import android.os.UserHandle;
@@ -319,8 +320,8 @@ public final class BackgroundJobsController extends StateController {
final boolean isStopped = mPackageManagerInternal.isPackageStopped(packageName, uid);
mPackageStoppedState.add(uid, packageName, isStopped);
return isStopped;
- } catch (IllegalArgumentException e) {
- Slog.d(TAG, "Couldn't determine stopped state for unknown package: " + packageName);
+ } catch (PackageManager.NameNotFoundException e) {
+ Slog.e(TAG, "Couldn't determine stopped state for unknown package: " + packageName);
return false;
}
}
diff --git a/core/api/current.txt b/core/api/current.txt
index 83e3fabe475b..7f261d450b42 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -5285,9 +5285,10 @@ package android.app {
field public static final int START_TIMESTAMP_RESERVED_RANGE_DEVELOPER_START = 21; // 0x15
field public static final int START_TIMESTAMP_RESERVED_RANGE_SYSTEM = 20; // 0x14
field public static final int START_TIMESTAMP_SURFACEFLINGER_COMPOSITION_COMPLETE = 7; // 0x7
- field public static final int START_TYPE_COLD = 0; // 0x0
- field public static final int START_TYPE_HOT = 2; // 0x2
- field public static final int START_TYPE_WARM = 1; // 0x1
+ field public static final int START_TYPE_COLD = 1; // 0x1
+ field public static final int START_TYPE_HOT = 3; // 0x3
+ field public static final int START_TYPE_UNSET = 0; // 0x0
+ field public static final int START_TYPE_WARM = 2; // 0x2
}
public final class AsyncNotedAppOp implements android.os.Parcelable {
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 5ecc0b6a6568..7237af3f1df5 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -16987,7 +16987,7 @@ package android.telephony.satellite {
method @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") @NonNull @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public java.util.Set<java.lang.Integer> getSatelliteAttachRestrictionReasonsForCarrier(int);
method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void pollPendingSatelliteDatagrams(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void provisionSatelliteService(@NonNull String, @NonNull byte[], @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
- method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public int registerForNtnSignalStrengthChanged(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.NtnSignalStrengthCallback);
+ method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void registerForNtnSignalStrengthChanged(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.NtnSignalStrengthCallback) throws android.telephony.satellite.SatelliteManager.SatelliteException;
method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public int registerForSatelliteCapabilitiesChanged(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.SatelliteCapabilitiesCallback);
method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public int registerForSatelliteDatagram(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.SatelliteDatagramCallback);
method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public int registerForSatelliteModemStateChanged(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.SatelliteStateCallback);
@@ -16999,7 +16999,7 @@ package android.telephony.satellite {
method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestIsSatelliteEnabled(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,android.telephony.satellite.SatelliteManager.SatelliteException>);
method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestIsSatelliteProvisioned(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,android.telephony.satellite.SatelliteManager.SatelliteException>);
method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public void requestIsSatelliteSupported(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,android.telephony.satellite.SatelliteManager.SatelliteException>);
- method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @NonNull @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestNtnSignalStrength(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.telephony.satellite.NtnSignalStrength,android.telephony.satellite.SatelliteManager.SatelliteException>);
+ method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestNtnSignalStrength(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.telephony.satellite.NtnSignalStrength,android.telephony.satellite.SatelliteManager.SatelliteException>);
method @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestSatelliteAttachEnabledForCarrier(int, boolean, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestSatelliteCapabilities(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.telephony.satellite.SatelliteCapabilities,android.telephony.satellite.SatelliteManager.SatelliteException>);
method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestSatelliteEnabled(boolean, boolean, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 8b39ed6fb411..6ddb36a72aa9 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -4077,7 +4077,7 @@ public final class ActivityThread extends ClientTransactionHandler
final LoadedApk sdkApk = getPackageInfo(
contextInfo.getSdkApplicationInfo(),
r.packageInfo.getCompatibilityInfo(),
- ActivityContextInfo.CONTEXT_FLAGS);
+ contextInfo.getContextFlags());
final ContextImpl activityContext = ContextImpl.createActivityContext(
this, sdkApk, r.activityInfo, r.token, displayId, r.overrideConfig);
diff --git a/core/java/android/app/ApplicationStartInfo.java b/core/java/android/app/ApplicationStartInfo.java
index c8317c8faad5..656feb0401d6 100644
--- a/core/java/android/app/ApplicationStartInfo.java
+++ b/core/java/android/app/ApplicationStartInfo.java
@@ -104,14 +104,17 @@ public final class ApplicationStartInfo implements Parcelable {
/** Process started due to Activity started for any reason not explicitly listed. */
public static final int START_REASON_START_ACTIVITY = 11;
+ /** Start type not yet set. */
+ public static final int START_TYPE_UNSET = 0;
+
/** Process started from scratch. */
- public static final int START_TYPE_COLD = 0;
+ public static final int START_TYPE_COLD = 1;
/** Process retained minimally SavedInstanceState. */
- public static final int START_TYPE_WARM = 1;
+ public static final int START_TYPE_WARM = 2;
/** Process brought back to foreground. */
- public static final int START_TYPE_HOT = 2;
+ public static final int START_TYPE_HOT = 3;
/**
* Default. The system always creates a new instance of the activity in the target task and
@@ -277,6 +280,7 @@ public final class ApplicationStartInfo implements Parcelable {
@IntDef(
prefix = {"START_TYPE_"},
value = {
+ START_TYPE_UNSET,
START_TYPE_COLD,
START_TYPE_WARM,
START_TYPE_HOT,
@@ -769,6 +773,7 @@ public final class ApplicationStartInfo implements Parcelable {
private static String startTypeToString(@StartType int startType) {
return switch (startType) {
+ case START_TYPE_UNSET -> "UNSET";
case START_TYPE_COLD -> "COLD";
case START_TYPE_WARM -> "WARM";
case START_TYPE_HOT -> "HOT";
diff --git a/core/java/android/content/pm/ILauncherApps.aidl b/core/java/android/content/pm/ILauncherApps.aidl
index e9f419e9a8ce..6f7299aa8e31 100644
--- a/core/java/android/content/pm/ILauncherApps.aidl
+++ b/core/java/android/content/pm/ILauncherApps.aidl
@@ -64,6 +64,7 @@ interface ILauncherApps {
PendingIntent getActivityLaunchIntent(String callingPackage, in ComponentName component,
in UserHandle user);
LauncherUserInfo getLauncherUserInfo(in UserHandle user);
+ List<String> getPreInstalledSystemPackages(in UserHandle user);
void showAppDetailsAsUser(in IApplicationThread caller, String callingPackage,
String callingFeatureId, in ComponentName component, in Rect sourceBounds,
in Bundle opts, in UserHandle user);
diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java
index 0cd4358b2c91..ccc8f0956865 100644
--- a/core/java/android/content/pm/LauncherApps.java
+++ b/core/java/android/content/pm/LauncherApps.java
@@ -800,6 +800,29 @@ public class LauncherApps {
}
/**
+ * Returns the list of the system packages that are installed at user creation.
+ *
+ * <p>An empty list denotes that all system packages are installed for that user at creation.
+ * This behaviour is inherited from the underlining UserManager API.
+ *
+ * @param userHandle the user for which installed system packages are required.
+ * @return {@link List} of {@link String}, representing the package name of the installed
+ * package. Can be empty but not null.
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_ALLOW_PRIVATE_PROFILE)
+ public List<String> getPreInstalledSystemPackages(@NonNull UserHandle userHandle) {
+ if (DEBUG) {
+ Log.i(TAG, "getPreInstalledSystemPackages for user: " + userHandle);
+ }
+ try {
+ return mService.getPreInstalledSystemPackages(userHandle);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Returns the activity info for a given intent and user handle, if it resolves. Otherwise it
* returns null.
*
diff --git a/core/java/android/content/pm/PackageInfo.java b/core/java/android/content/pm/PackageInfo.java
index 5dee65b62201..4f61613b9c6e 100644
--- a/core/java/android/content/pm/PackageInfo.java
+++ b/core/java/android/content/pm/PackageInfo.java
@@ -231,7 +231,7 @@ public class PackageInfo implements Parcelable {
* or null if there were none. This is only filled in if the flag
* {@link PackageManager#GET_PERMISSIONS} was set. Each value matches
* the corresponding entry in {@link #requestedPermissions}, and will have
- * the flags {@link #REQUESTED_PERMISSION_GRANTED} and
+ * the flags {@link #REQUESTED_PERMISSION_GRANTED}, {@link #REQUESTED_PERMISSION_IMPLICIT}, and
* {@link #REQUESTED_PERMISSION_NEVER_FOR_LOCATION} set as appropriate.
*/
@Nullable
diff --git a/core/java/android/hardware/camera2/CameraMetadata.java b/core/java/android/hardware/camera2/CameraMetadata.java
index 93fbe8aee8d4..7cf10d89004f 100644
--- a/core/java/android/hardware/camera2/CameraMetadata.java
+++ b/core/java/android/hardware/camera2/CameraMetadata.java
@@ -1216,7 +1216,7 @@ public abstract class CameraMetadata<TKey> {
* <ul>
* <li>Profile {@link android.hardware.camera2.params.DynamicRangeProfiles#HLG10 }</li>
* <li>All mandatory stream combinations for this specific capability as per
- * <a href="CameraDevice#10-bit-output-additional-guaranteed-configurations">documentation</a></li>
+ * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#10-bit-output-additional-guaranteed-configurations">documentation</a></li>
* <li>In case the device is not able to capture some combination of supported
* standard 8-bit and/or 10-bit dynamic range profiles within the same capture request,
* then those constraints must be listed in
diff --git a/core/java/android/hardware/camera2/CaptureRequest.java b/core/java/android/hardware/camera2/CaptureRequest.java
index 06397c9a1598..ded96a23e11e 100644
--- a/core/java/android/hardware/camera2/CaptureRequest.java
+++ b/core/java/android/hardware/camera2/CaptureRequest.java
@@ -1484,7 +1484,7 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>>
* <p>To start a CaptureSession with a target FPS range different from the
* capture request template's default value, the application
* is strongly recommended to call
- * {@link SessionConfiguration#setSessionParameters }
+ * {@link android.hardware.camera2.params.SessionConfiguration#setSessionParameters }
* with the target fps range before creating the capture session. The aeTargetFpsRange is
* typically a session parameter. Specifying it at session creation time helps avoid
* session reconfiguration delays in cases like 60fps or high speed recording.</p>
@@ -2161,7 +2161,7 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>>
* OFF if the recording output is not stabilized, or if there are no output
* Surface types that can be stabilized.</p>
* <p>The application is strongly recommended to call
- * {@link SessionConfiguration#setSessionParameters }
+ * {@link android.hardware.camera2.params.SessionConfiguration#setSessionParameters }
* with the desired video stabilization mode before creating the capture session.
* Video stabilization mode is a session parameter on many devices. Specifying
* it at session creation time helps avoid reconfiguration delay caused by difference
diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java
index ab4406c37c8e..1d26d69a58b9 100644
--- a/core/java/android/hardware/camera2/CaptureResult.java
+++ b/core/java/android/hardware/camera2/CaptureResult.java
@@ -899,7 +899,7 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
* <p>To start a CaptureSession with a target FPS range different from the
* capture request template's default value, the application
* is strongly recommended to call
- * {@link SessionConfiguration#setSessionParameters }
+ * {@link android.hardware.camera2.params.SessionConfiguration#setSessionParameters }
* with the target fps range before creating the capture session. The aeTargetFpsRange is
* typically a session parameter. Specifying it at session creation time helps avoid
* session reconfiguration delays in cases like 60fps or high speed recording.</p>
@@ -2382,7 +2382,7 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
* OFF if the recording output is not stabilized, or if there are no output
* Surface types that can be stabilized.</p>
* <p>The application is strongly recommended to call
- * {@link SessionConfiguration#setSessionParameters }
+ * {@link android.hardware.camera2.params.SessionConfiguration#setSessionParameters }
* with the desired video stabilization mode before creating the capture session.
* Video stabilization mode is a session parameter on many devices. Specifying
* it at session creation time helps avoid reconfiguration delay caused by difference
diff --git a/core/java/android/os/flags.aconfig b/core/java/android/os/flags.aconfig
index 145981c92283..83d237d6e53b 100644
--- a/core/java/android/os/flags.aconfig
+++ b/core/java/android/os/flags.aconfig
@@ -78,6 +78,13 @@ flag {
}
flag {
+ name: "adpf_use_fmq_channel"
+ namespace: "game"
+ description: "Guards use of the FMQ channel for ADPF"
+ bug: "315894228"
+}
+
+flag {
name: "battery_service_support_current_adb_command"
namespace: "backstage_power"
description: "Whether or not BatteryService supports adb commands for Current values."
diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java
index a5b087c05dfa..fcdc5fe71e4e 100644
--- a/core/java/android/service/notification/ZenModeConfig.java
+++ b/core/java/android/service/notification/ZenModeConfig.java
@@ -1980,6 +1980,7 @@ public class ZenModeConfig implements Parcelable {
@Nullable public ZenDeviceEffects zenDeviceEffects;
public boolean modified; // rule has been modified from initial creation
public String pkg;
+ @AutomaticZenRule.Type
public int type = AutomaticZenRule.TYPE_UNKNOWN;
public String triggerDescription;
public String iconResName;
diff --git a/core/java/android/view/ISurfaceControlViewHostParent.aidl b/core/java/android/view/ISurfaceControlViewHostParent.aidl
index f42e00148587..559c20ee4825 100644
--- a/core/java/android/view/ISurfaceControlViewHostParent.aidl
+++ b/core/java/android/view/ISurfaceControlViewHostParent.aidl
@@ -16,6 +16,7 @@
package android.view;
+import android.view.KeyEvent;
import android.view.WindowManager;
/**
@@ -24,4 +25,6 @@ import android.view.WindowManager;
*/
oneway interface ISurfaceControlViewHostParent {
void updateParams(in WindowManager.LayoutParams[] childAttrs);
+ // To forward the back key event from embedded to host app.
+ void forwardBackKeyToParent(in KeyEvent keyEvent);
}
diff --git a/core/java/android/view/SurfaceControlViewHost.java b/core/java/android/view/SurfaceControlViewHost.java
index 405653123f79..4840f003da3e 100644
--- a/core/java/android/view/SurfaceControlViewHost.java
+++ b/core/java/android/view/SurfaceControlViewHost.java
@@ -447,6 +447,7 @@ public class SurfaceControlViewHost {
addWindowToken(attrs);
view.setLayoutParams(attrs);
mViewRoot.setView(view, attrs, null);
+ mViewRoot.setBackKeyCallbackForWindowlessWindow(mWm::forwardBackKeyToParent);
}
/**
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index a44a95a1677f..108de281a411 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -37,6 +37,7 @@ import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.Region;
import android.graphics.RenderNode;
+import android.hardware.input.InputManager;
import android.os.Build;
import android.os.Handler;
import android.os.IBinder;
@@ -159,6 +160,8 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall
private static final boolean DEBUG = false;
private static final boolean DEBUG_POSITION = false;
+ private static final long FORWARD_BACK_KEY_TOLERANCE_MS = 100;
+
@UnsupportedAppUsage(
maxTargetSdk = Build.VERSION_CODES.TIRAMISU,
publicAlternatives = "Track {@link SurfaceHolder#addCallback} instead")
@@ -326,6 +329,41 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall
});
}
}
+
+ @Override
+ public void forwardBackKeyToParent(@NonNull KeyEvent keyEvent) {
+ runOnUiThread(() -> {
+ if (!isAttachedToWindow() || keyEvent.getKeyCode() != KeyEvent.KEYCODE_BACK) {
+ return;
+ }
+ final ViewRootImpl vri = getViewRootImpl();
+ if (vri == null) {
+ return;
+ }
+ final InputManager inputManager = mContext.getSystemService(InputManager.class);
+ if (inputManager == null) {
+ return;
+ }
+ // Check that the event was created recently.
+ final long timeDiff = SystemClock.uptimeMillis() - keyEvent.getEventTime();
+ if (timeDiff > FORWARD_BACK_KEY_TOLERANCE_MS) {
+ Log.e(TAG, "Ignore the input event that exceed the tolerance time, "
+ + "exceed " + timeDiff + "ms");
+ return;
+ }
+ if (inputManager.verifyInputEvent(keyEvent) == null) {
+ Log.e(TAG, "Received invalid input event");
+ return;
+ }
+ try {
+ vri.processingBackKey(true);
+ vri.enqueueInputEvent(keyEvent, null /* receiver */, 0 /* flags */,
+ true /* processImmediately */);
+ } finally {
+ vri.processingBackKey(false);
+ }
+ });
+ }
};
private final boolean mRtDrivenClipping = Flags.clipSurfaceviews();
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 40382fdcf4b8..8339d3372565 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -91,6 +91,7 @@ import static android.view.accessibility.Flags.reduceWindowContentChangedEventTh
import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.IME_FOCUS_CONTROLLER;
import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.INSETS_CONTROLLER;
import static android.view.flags.Flags.toolkitSetFrameRateReadOnly;
+import static android.view.flags.Flags.toolkitMetricsForFrameRateDecision;
import static com.android.input.flags.Flags.enablePointerChoreographer;
@@ -250,7 +251,7 @@ import java.util.OptionalInt;
import java.util.Queue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
-import java.util.function.Consumer;
+import java.util.function.Predicate;
/**
* The top of a view hierarchy, implementing the needed protocol between View
@@ -370,6 +371,8 @@ public final class ViewRootImpl implements ViewParent,
*/
private static final int KEEP_CLEAR_AREA_REPORT_RATE_MILLIS = 100;
+ private static final long NANOS_PER_SEC = 1000000000;
+
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
static final ThreadLocal<HandlerActionQueue> sRunQueues = new ThreadLocal<HandlerActionQueue>();
@@ -615,6 +618,13 @@ public final class ViewRootImpl implements ViewParent,
boolean mUpcomingWindowFocus;
@GuardedBy("this")
boolean mUpcomingInTouchMode;
+ // While set, allow this VRI to handle back key without drop it.
+ private boolean mProcessingBackKey;
+ /**
+ * Compatibility {@link OnBackInvokedCallback} for windowless window, to forward the back
+ * key event host app.
+ */
+ private Predicate<KeyEvent> mWindowlessBackKeyCallback;
public boolean mTraversalScheduled;
int mTraversalBarrier;
@@ -817,6 +827,8 @@ public final class ViewRootImpl implements ViewParent,
private boolean mInsetsAnimationRunning;
+ private long mPreviousFrameDrawnTime = -1;
+
/**
* The resolved pointer icon type requested by this window.
* A null value indicates the resolved pointer icon has not yet been calculated.
@@ -1054,11 +1066,14 @@ public final class ViewRootImpl implements ViewParent,
private boolean mChildBoundingInsetsChanged = false;
private String mTag = TAG;
+ private String mFpsTraceName;
private static boolean sToolkitSetFrameRateReadOnlyFlagValue;
+ private static boolean sToolkitMetricsForFrameRateDecisionFlagValue;
static {
sToolkitSetFrameRateReadOnlyFlagValue = toolkitSetFrameRateReadOnly();
+ sToolkitMetricsForFrameRateDecisionFlagValue = toolkitMetricsForFrameRateDecision();
}
// The latest input event from the gesture that was used to resolve the pointer icon.
@@ -1302,6 +1317,7 @@ public final class ViewRootImpl implements ViewParent,
attrs = mWindowAttributes;
setTag();
+ mFpsTraceName = "FPS of " + getTitle();
if (DEBUG_KEEP_SCREEN_ON && (mClientWindowLayoutFlags
& WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) != 0
@@ -3189,7 +3205,11 @@ public final class ViewRootImpl implements ViewParent,
host.dispatchAttachedToWindow(mAttachInfo, 0);
mAttachInfo.mTreeObserver.dispatchOnWindowAttachedChange(true);
dispatchApplyInsets(host);
- if (!mOnBackInvokedDispatcher.isOnBackInvokedCallbackEnabled()) {
+ if (!mOnBackInvokedDispatcher.isOnBackInvokedCallbackEnabled()
+ // Don't register compat OnBackInvokedCallback for windowless window.
+ // The onBackInvoked event by default should forward to host app, so the
+ // host app can decide the behavior.
+ && mWindowlessBackKeyCallback == null) {
// For apps requesting legacy back behavior, we add a compat callback that
// dispatches {@link KeyEvent#KEYCODE_BACK} to their root views.
// This way from system point of view, these apps are providing custom
@@ -4721,6 +4741,31 @@ public final class ViewRootImpl implements ViewParent,
}
}
+ /**
+ * Called from draw() to collect metrics for frame rate decision.
+ */
+ private void collectFrameRateDecisionMetrics() {
+ if (!Trace.isEnabled()) {
+ if (mPreviousFrameDrawnTime > 0) mPreviousFrameDrawnTime = -1;
+ return;
+ }
+
+ if (mPreviousFrameDrawnTime < 0) {
+ mPreviousFrameDrawnTime = mChoreographer.getExpectedPresentationTimeNanos();
+ return;
+ }
+
+ long expectedDrawnTime = mChoreographer.getExpectedPresentationTimeNanos();
+ long timeDiff = expectedDrawnTime - mPreviousFrameDrawnTime;
+ if (timeDiff <= 0) {
+ return;
+ }
+
+ long fps = NANOS_PER_SEC / timeDiff;
+ Trace.setCounter(mFpsTraceName, fps);
+ mPreviousFrameDrawnTime = expectedDrawnTime;
+ }
+
private void reportDrawFinished(@Nullable Transaction t, int seqId) {
if (DEBUG_BLAST) {
Log.d(mTag, "reportDrawFinished");
@@ -5039,6 +5084,9 @@ public final class ViewRootImpl implements ViewParent,
if (DEBUG_FPS) {
trackFPS();
}
+ if (sToolkitMetricsForFrameRateDecisionFlagValue) {
+ collectFrameRateDecisionMetrics();
+ }
if (!sFirstDrawComplete) {
synchronized (sFirstDrawHandlers) {
@@ -6655,7 +6703,8 @@ public final class ViewRootImpl implements ViewParent,
// Find a reason for dropping or canceling the event.
final String reason;
- if (!mAttachInfo.mHasWindowFocus
+ // The embedded window is focused, allow this VRI to handle back key.
+ if (!mAttachInfo.mHasWindowFocus && !(mProcessingBackKey && isBack(q.mEvent))
&& !q.mEvent.isFromSource(InputDevice.SOURCE_CLASS_POINTER)
&& !isAutofillUiShowing()) {
// This is a non-pointer event and the window doesn't currently have input focus
@@ -6878,10 +6927,20 @@ public final class ViewRootImpl implements ViewParent,
// If the new back dispatch is enabled, intercept KEYCODE_BACK before it reaches the
// view tree or IME, and invoke the appropriate {@link OnBackInvokedCallback}.
- if (isBack(keyEvent)
- && mContext != null
- && mOnBackInvokedDispatcher.isOnBackInvokedCallbackEnabled()) {
- return doOnBackKeyEvent(keyEvent);
+ if (isBack(keyEvent)) {
+ if (mWindowlessBackKeyCallback != null) {
+ if (mWindowlessBackKeyCallback.test(keyEvent)) {
+ return keyEvent.getAction() == KeyEvent.ACTION_UP
+ && !keyEvent.isCanceled()
+ ? FINISH_HANDLED : FINISH_NOT_HANDLED;
+ } else {
+ // Unable to forward the back key to host, forward to next stage.
+ return FORWARD;
+ }
+ } else if (mContext != null
+ && mOnBackInvokedDispatcher.isOnBackInvokedCallbackEnabled()) {
+ return doOnBackKeyEvent(keyEvent);
+ }
}
if (mInputQueue != null) {
@@ -10524,6 +10583,11 @@ public final class ViewRootImpl implements ViewParent,
mHandler.obtainMessage(MSG_REQUEST_SCROLL_CAPTURE, listener).sendToTarget();
}
+ // Make this VRI able to process back key without drop it.
+ void processingBackKey(boolean processing) {
+ mProcessingBackKey = processing;
+ }
+
/**
* Collect and include any ScrollCaptureCallback instances registered with the window.
*
@@ -11748,13 +11812,18 @@ public final class ViewRootImpl implements ViewParent,
KeyCharacterMap.VIRTUAL_KEYBOARD, 0 /* scancode */,
KeyEvent.FLAG_FROM_SYSTEM | KeyEvent.FLAG_VIRTUAL_HARD_KEY,
InputDevice.SOURCE_KEYBOARD);
- enqueueInputEvent(ev);
+ enqueueInputEvent(ev, null /* receiver */, 0 /* flags */, true /* processImmediately */);
}
private void registerCompatOnBackInvokedCallback() {
mCompatOnBackInvokedCallback = () -> {
- sendBackKeyEvent(KeyEvent.ACTION_DOWN);
- sendBackKeyEvent(KeyEvent.ACTION_UP);
+ try {
+ processingBackKey(true);
+ sendBackKeyEvent(KeyEvent.ACTION_DOWN);
+ sendBackKeyEvent(KeyEvent.ACTION_UP);
+ } finally {
+ processingBackKey(false);
+ }
};
if (mOnBackInvokedDispatcher.hasImeOnBackInvokedDispatcher()) {
Log.d(TAG, "Skip registering CompatOnBackInvokedCallback on IME dispatcher");
@@ -12189,4 +12258,13 @@ public final class ViewRootImpl implements ViewParent,
}
return false;
}
+
+ /**
+ * Set the default back key callback for windowless window, to forward the back key event
+ * to host app.
+ * MUST NOT call this method for normal window.
+ */
+ void setBackKeyCallbackForWindowlessWindow(@NonNull Predicate<KeyEvent> callback) {
+ mWindowlessBackKeyCallback = callback;
+ }
}
diff --git a/core/java/android/view/WindowlessWindowManager.java b/core/java/android/view/WindowlessWindowManager.java
index d817e6f51f55..393d2565fc85 100644
--- a/core/java/android/view/WindowlessWindowManager.java
+++ b/core/java/android/view/WindowlessWindowManager.java
@@ -16,6 +16,7 @@
package android.view;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.WindowConfiguration;
import android.content.res.Configuration;
@@ -703,4 +704,17 @@ public class WindowlessWindowManager implements IWindowSession {
}
}
}
+
+ boolean forwardBackKeyToParent(@NonNull KeyEvent keyEvent) {
+ if (mParentInterface == null) {
+ return false;
+ }
+ try {
+ mParentInterface.forwardBackKeyToParent(keyEvent);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to forward back key To Parent: ", e);
+ return false;
+ }
+ return true;
+ }
}
diff --git a/core/java/android/view/flags/refresh_rate_flags.aconfig b/core/java/android/view/flags/refresh_rate_flags.aconfig
index a467afe5d06a..0aa516e08697 100644
--- a/core/java/android/view/flags/refresh_rate_flags.aconfig
+++ b/core/java/android/view/flags/refresh_rate_flags.aconfig
@@ -42,4 +42,12 @@ flag {
namespace: "core_graphics"
description: "Enable the `setFrameRate` callback"
bug: "299946220"
+}
+
+flag {
+ name: "toolkit_metrics_for_frame_rate_decision"
+ namespace: "toolkit"
+ description: "Feature flag for toolkit metrics collecting for frame rate decision"
+ bug: "301343249"
+ is_fixed_read_only: true
} \ No newline at end of file
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index 28fd2b488426..bf8e6135fd01 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -897,13 +897,26 @@ public class LockPatternUtils {
}
/**
- * Returns true if {@code userHandle} is a managed profile with separate challenge.
+ * Returns true if {@code userHandle} is a profile with separate challenge.
+ * <p>
+ * Returns false if {@code userHandle} is a profile with unified challenge, a profile whose
+ * credential is not shareable with its parent, or a non-profile user.
*/
public boolean isSeparateProfileChallengeEnabled(int userHandle) {
return isCredentialSharableWithParent(userHandle) && hasSeparateChallenge(userHandle);
}
/**
+ * Returns true if {@code userHandle} is a profile with unified challenge.
+ * <p>
+ * Returns false if {@code userHandle} is a profile with separate challenge, a profile whose
+ * credential is not shareable with its parent, or a non-profile user.
+ */
+ public boolean isProfileWithUnifiedChallenge(int userHandle) {
+ return isCredentialSharableWithParent(userHandle) && !hasSeparateChallenge(userHandle);
+ }
+
+ /**
* Returns true if {@code userHandle} is a managed profile with unified challenge.
*/
public boolean isManagedProfileWithUnifiedChallenge(int userHandle) {
diff --git a/core/java/com/android/internal/widget/LockscreenCredential.java b/core/java/com/android/internal/widget/LockscreenCredential.java
index c88763ce6c97..18d5f6db6ac9 100644
--- a/core/java/com/android/internal/widget/LockscreenCredential.java
+++ b/core/java/com/android/internal/widget/LockscreenCredential.java
@@ -134,12 +134,12 @@ public class LockscreenCredential implements Parcelable, AutoCloseable {
}
/**
- * Creates a LockscreenCredential object representing a managed password for profile with
- * unified challenge. This credentiall will have type {@code CREDENTIAL_TYPE_PASSWORD} for now.
- * TODO: consider add a new credential type for this. This can then supersede the
- * isLockTiedToParent argument in various places in LSS.
+ * Creates a LockscreenCredential object representing the system-generated, system-managed
+ * password for a profile with unified challenge. This credential has type {@code
+ * CREDENTIAL_TYPE_PASSWORD} for now. TODO: consider add a new credential type for this. This
+ * can then supersede the isLockTiedToParent argument in various places in LSS.
*/
- public static LockscreenCredential createManagedPassword(@NonNull byte[] password) {
+ public static LockscreenCredential createUnifiedProfilePassword(@NonNull byte[] password) {
return new LockscreenCredential(CREDENTIAL_TYPE_PASSWORD,
Arrays.copyOf(password, password.length), /* hasInvalidChars= */ false);
}
diff --git a/core/jni/com_android_internal_os_ZygoteCommandBuffer.cpp b/core/jni/com_android_internal_os_ZygoteCommandBuffer.cpp
index 87ab4969040e..54c4cd50a902 100644
--- a/core/jni/com_android_internal_os_ZygoteCommandBuffer.cpp
+++ b/core/jni/com_android_internal_os_ZygoteCommandBuffer.cpp
@@ -63,6 +63,7 @@ class NativeCommandBuffer {
std::optional<std::pair<char*, char*>> readLine(FailFn fail_fn) {
char* result = mBuffer + mNext;
while (true) {
+ // We have scanned up to, but not including mNext for this line's newline.
if (mNext == mEnd) {
if (mEnd == MAX_COMMAND_BYTES) {
return {};
@@ -89,7 +90,7 @@ class NativeCommandBuffer {
} else {
mNext = nl - mBuffer + 1;
if (--mLinesLeft < 0) {
- fail_fn("ZygoteCommandBuffer.readLine attempted to read past mEnd of command");
+ fail_fn("ZygoteCommandBuffer.readLine attempted to read past end of command");
}
return std::make_pair(result, nl);
}
@@ -125,8 +126,8 @@ class NativeCommandBuffer {
mEnd += lineLen + 1;
}
- // Clear mBuffer, start reading new command, return the number of arguments, leaving mBuffer
- // positioned at the beginning of first argument. Return 0 on EOF.
+ // Start reading new command, return the number of arguments, leaving mBuffer positioned at the
+ // beginning of first argument. Return 0 on EOF.
template<class FailFn>
int getCount(FailFn fail_fn) {
mLinesLeft = 1;
@@ -451,11 +452,14 @@ jboolean com_android_internal_os_ZygoteCommandBuffer_nativeForkRepeatedly(
(CREATE_ERROR("Write unexpectedly returned short: %d < 5", res));
}
}
- // Clear buffer and get count from next command.
- n_buffer->clear();
for (;;) {
+ // Clear buffer and get count from next command.
+ n_buffer->clear();
// Poll isn't strictly necessary for now. But without it, disconnect is hard to detect.
int poll_res = TEMP_FAILURE_RETRY(poll(fd_structs, 2, -1 /* infinite timeout */));
+ if (poll_res < 0) {
+ fail_fn_z(CREATE_ERROR("Poll failed: %d: %s", errno, strerror(errno)));
+ }
if ((fd_structs[SESSION_IDX].revents & POLLIN) != 0) {
if (n_buffer->getCount(fail_fn_z) != 0) {
break;
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 1f6ac80a133c..4596ca74bf8f 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -5252,6 +5252,8 @@
<!-- Zen mode - name of default automatic calendar time-based rule that is triggered every night (when sleeping). [CHAR LIMIT=40] -->
<string name="zen_mode_default_every_night_name">Sleeping</string>
+ <!-- Zen mode - Trigger description of the rule, indicating which app owns it. [CHAR_LIMIT=100] -->
+ <string name="zen_mode_implicit_trigger_description">Managed by <xliff:g id="app_name">%1$s</xliff:g></string>
<!-- Zen mode - Condition summary when a rule is activated due to a call to setInterruptionFilter(). [CHAR_LIMIT=NONE] -->
<string name="zen_mode_implicit_activated">On</string>
<!-- Zen mode - Condition summary when a rule is deactivated due to a call to setInterruptionFilter(). [CHAR_LIMIT=NONE] -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 5791ddb8addf..fd6158d02b8f 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2586,6 +2586,7 @@
<java-symbol type="string" name="zen_mode_default_weekends_name" />
<java-symbol type="string" name="zen_mode_default_events_name" />
<java-symbol type="string" name="zen_mode_default_every_night_name" />
+ <java-symbol type="string" name="zen_mode_implicit_trigger_description" />
<java-symbol type="string" name="zen_mode_implicit_activated" />
<java-symbol type="string" name="zen_mode_implicit_deactivated" />
<java-symbol type="string" name="display_rotation_camera_compat_toast_after_rotation" />
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java
index 15d14e87fcf6..b315f94b5d00 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java
@@ -241,7 +241,7 @@ public class WindowAreaComponentImpl implements WindowAreaComponent,
for (int i = 0; i < displays.length; i++) {
DisplayAddress.Physical address =
(DisplayAddress.Physical) displays[i].getAddress();
- if (mRearDisplayAddress == address.getPhysicalDisplayId()) {
+ if (address != null && mRearDisplayAddress == address.getPhysicalDisplayId()) {
rearDisplayMetrics = new DisplayMetrics();
final Display rearDisplay = displays[i];
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java
index 0f0fbd9cc12f..f801b0d01084 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java
@@ -83,6 +83,7 @@ public class DividerView extends FrameLayout implements View.OnTouchListener {
private int mStartPos;
private GestureDetector mDoubleTapDetector;
private boolean mInteractive;
+ private boolean mHideHandle;
private boolean mSetTouchRegion = true;
private int mLastDraggingPosition;
private int mHandleRegionWidth;
@@ -211,11 +212,8 @@ public class DividerView extends FrameLayout implements View.OnTouchListener {
}
/** Sets up essential dependencies of the divider bar. */
- public void setup(
- SplitLayout layout,
- SplitWindowManager splitWindowManager,
- SurfaceControlViewHost viewHost,
- InsetsState insetsState) {
+ public void setup(SplitLayout layout, SplitWindowManager splitWindowManager,
+ SurfaceControlViewHost viewHost, InsetsState insetsState) {
mSplitLayout = layout;
mSplitWindowManager = splitWindowManager;
mViewHost = viewHost;
@@ -277,6 +275,7 @@ public class DividerView extends FrameLayout implements View.OnTouchListener {
R.dimen.docked_stack_divider_lift_elevation);
mDoubleTapDetector = new GestureDetector(getContext(), new DoubleTapListener());
mInteractive = true;
+ mHideHandle = false;
setOnTouchListener(this);
mHandle.setAccessibilityDelegate(mHandleDelegate);
setWillNotDraw(false);
@@ -469,10 +468,11 @@ public class DividerView extends FrameLayout implements View.OnTouchListener {
void setInteractive(boolean interactive, boolean hideHandle, String from) {
if (interactive == mInteractive) return;
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
- "Set divider bar %s from %s", interactive ? "interactive" : "non-interactive",
- from);
+ "Set divider bar %s hide handle=%b from %s",
+ interactive ? "interactive" : "non-interactive", hideHandle, from);
mInteractive = interactive;
- if (!mInteractive && hideHandle && mMoving) {
+ mHideHandle = hideHandle;
+ if (!mInteractive && mHideHandle && mMoving) {
final int position = mSplitLayout.getDividePosition();
mSplitLayout.flingDividePosition(
mLastDraggingPosition,
@@ -482,7 +482,15 @@ public class DividerView extends FrameLayout implements View.OnTouchListener {
mMoving = false;
}
releaseTouching();
- mHandle.setVisibility(!mInteractive && hideHandle ? View.INVISIBLE : View.VISIBLE);
+ mHandle.setVisibility(!mInteractive && mHideHandle ? View.INVISIBLE : View.VISIBLE);
+ }
+
+ boolean isInteractive() {
+ return mInteractive;
+ }
+
+ boolean isHandleHidden() {
+ return mHideHandle;
}
private class DoubleTapListener extends GestureDetector.SimpleOnGestureListener {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
index b699533374df..53caddb52f23 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
@@ -59,6 +59,7 @@ import android.window.WindowContainerTransaction;
import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.R;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.animation.Interpolators;
@@ -70,6 +71,7 @@ import com.android.wm.shell.common.InteractionJankMonitorUtils;
import com.android.wm.shell.common.split.SplitScreenConstants.PersistentSnapPosition;
import com.android.wm.shell.common.split.SplitScreenConstants.SnapPosition;
import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition;
+import com.android.wm.shell.protolog.ShellProtoLogGroup;
import java.io.PrintWriter;
import java.util.function.Consumer;
@@ -420,7 +422,7 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
public void init() {
if (mInitialized) return;
mInitialized = true;
- mSplitWindowManager.init(this, mInsetsState);
+ mSplitWindowManager.init(this, mInsetsState, false /* isRestoring */);
mDisplayImeController.addPositionProcessor(mImePositionProcessor);
}
@@ -442,14 +444,19 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
}
/** Releases and re-inflates {@link DividerView} on the root surface. */
- public void update(SurfaceControl.Transaction t) {
+ public void update(SurfaceControl.Transaction t, boolean resetImePosition) {
if (!mInitialized) {
init();
return;
}
mSplitWindowManager.release(t);
- mImePositionProcessor.reset();
- mSplitWindowManager.init(this, mInsetsState);
+ if (resetImePosition) {
+ mImePositionProcessor.reset();
+ }
+ mSplitWindowManager.init(this, mInsetsState, true /* isRestoring */);
+ // Update the surface positions again after recreating the divider in case nothing else
+ // triggers it
+ mSplitLayoutHandler.onLayoutPositionChanging(SplitLayout.this);
}
@Override
@@ -868,6 +875,9 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
pw.println(prefix + TAG + ":");
pw.println(innerPrefix + "mAllowLeftRightSplitInPortrait=" + mAllowLeftRightSplitInPortrait);
pw.println(innerPrefix + "mIsLeftRightSplit=" + mIsLeftRightSplit);
+ pw.println(innerPrefix + "mFreezeDividerWindow=" + mFreezeDividerWindow);
+ pw.println(innerPrefix + "mDimNonImeSide=" + mDimNonImeSide);
+ pw.println(innerPrefix + "mDividerPosition=" + mDividerPosition);
pw.println(innerPrefix + "bounds1=" + mBounds1.toShortString());
pw.println(innerPrefix + "dividerBounds=" + mDividerBounds.toShortString());
pw.println(innerPrefix + "bounds2=" + mBounds2.toShortString());
@@ -1151,14 +1161,16 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
mTargetYOffset = needOffset ? getTargetYOffset() : 0;
if (mTargetYOffset != mLastYOffset) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
+ "Split IME animation starting, fromY=%d toY=%d",
+ mLastYOffset, mTargetYOffset);
// Freeze the configuration size with offset to prevent app get a configuration
// changed or relaunch. This is required to make sure client apps will calculate
// insets properly after layout shifted.
if (mTargetYOffset == 0) {
mSplitLayoutHandler.setLayoutOffsetTarget(0, 0, SplitLayout.this);
} else {
- mSplitLayoutHandler.setLayoutOffsetTarget(0, mTargetYOffset - mLastYOffset,
- SplitLayout.this);
+ mSplitLayoutHandler.setLayoutOffsetTarget(0, mTargetYOffset, SplitLayout.this);
}
}
@@ -1183,6 +1195,8 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
public void onImeEndPositioning(int displayId, boolean cancel,
SurfaceControl.Transaction t) {
if (displayId != mDisplayId || !mHasImeFocus || cancel) return;
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
+ "Split IME animation ending, canceled=%b", cancel);
onProgress(1.0f);
mSplitLayoutHandler.onLayoutPositionChanging(SplitLayout.this);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitWindowManager.java
index 00361d9dd9cf..8fb9bda539a0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitWindowManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitWindowManager.java
@@ -62,6 +62,10 @@ public final class SplitWindowManager extends WindowlessWindowManager {
// Used to "pass" a transaction to WWM.remove so that view removal can be synchronized.
private SurfaceControl.Transaction mSyncTransaction = null;
+ // For saving/restoring state
+ private boolean mLastDividerInteractive = true;
+ private boolean mLastDividerHandleHidden;
+
public interface ParentContainerCallbacks {
void attachToParentSurface(SurfaceControl.Builder b);
void onLeashReady(SurfaceControl leash);
@@ -107,7 +111,7 @@ public final class SplitWindowManager extends WindowlessWindowManager {
}
/** Inflates {@link DividerView} on to the root surface. */
- void init(SplitLayout splitLayout, InsetsState insetsState) {
+ void init(SplitLayout splitLayout, InsetsState insetsState, boolean isRestoring) {
if (mDividerView != null || mViewHost != null) {
throw new UnsupportedOperationException(
"Try to inflate divider view again without release first");
@@ -130,6 +134,10 @@ public final class SplitWindowManager extends WindowlessWindowManager {
lp.accessibilityTitle = mContext.getResources().getString(R.string.accessibility_divider);
mViewHost.setView(mDividerView, lp);
mDividerView.setup(splitLayout, this, mViewHost, insetsState);
+ if (isRestoring) {
+ mDividerView.setInteractive(mLastDividerInteractive, mLastDividerHandleHidden,
+ "restore_setup");
+ }
}
/**
@@ -138,6 +146,8 @@ public final class SplitWindowManager extends WindowlessWindowManager {
*/
void release(@Nullable SurfaceControl.Transaction t) {
if (mDividerView != null) {
+ mLastDividerInteractive = mDividerView.isInteractive();
+ mLastDividerHandleHidden = mDividerView.isHandleHidden();
mDividerView = null;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index 449bef5608c9..77427d999aaf 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -1666,7 +1666,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
}
void finishEnterSplitScreen(SurfaceControl.Transaction finishT) {
- mSplitLayout.update(finishT);
+ mSplitLayout.update(finishT, true /* resetImePosition */);
mMainStage.getSplitDecorManager().inflate(mContext, mMainStage.mRootLeash,
getMainStageBounds());
mSideStage.getSplitDecorManager().inflate(mContext, mSideStage.mRootLeash,
@@ -1860,9 +1860,10 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
&& mSplitLayout.updateConfiguration(mRootTaskInfo.configuration)
&& mMainStage.isActive()) {
// Clear the divider remote animating flag as the divider will be re-rendered to apply
- // the new rotation config.
+ // the new rotation config. Don't reset the IME state since those updates are not in
+ // sync with task info changes.
mIsDividerRemoteAnimating = false;
- mSplitLayout.update(null /* t */);
+ mSplitLayout.update(null /* t */, false /* resetImePosition */);
onLayoutSizeChanged(mSplitLayout);
}
}
@@ -2325,7 +2326,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
*/
public void updateSurfaces(SurfaceControl.Transaction transaction) {
updateSurfaceBounds(mSplitLayout, transaction, /* applyResizingOffset */ false);
- mSplitLayout.update(transaction);
+ mSplitLayout.update(transaction, true /* resetImePosition */);
}
private void onDisplayChange(int displayId, int fromRotation, int toRotation,
@@ -2598,7 +2599,9 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
final TransitionInfo.Change change = info.getChanges().get(iC);
if (change.getMode() == TRANSIT_CHANGE
&& (change.getFlags() & FLAG_IS_DISPLAY) != 0) {
- mSplitLayout.update(startTransaction);
+ // Don't reset the IME state since those updates are not in sync with the
+ // display change transition
+ mSplitLayout.update(startTransaction, false /* resetImePosition */);
}
if (mMixedHandler.isEnteringPip(change, transitType)) {
@@ -2699,7 +2702,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
startTransaction, finishTransaction, finishCallback)) {
if (mSplitTransitions.isPendingResize(transition)) {
// Only need to update in resize because divider exist before transition.
- mSplitLayout.update(startTransaction);
+ mSplitLayout.update(startTransaction, true /* resetImePosition */);
startTransaction.apply();
}
return true;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/DividerViewTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/DividerViewTest.java
index 145c8f0ab8af..636c6326d213 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/DividerViewTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/DividerViewTest.java
@@ -69,7 +69,7 @@ public class DividerViewTest extends ShellTestCase {
SplitWindowManager splitWindowManager = new SplitWindowManager("TestSplitWindowManager",
mContext,
configuration, mCallbacks);
- splitWindowManager.init(mSplitLayout, new InsetsState());
+ splitWindowManager.init(mSplitLayout, new InsetsState(), false /* isRestoring */);
mDividerView = spy((DividerView) splitWindowManager.getDividerView());
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitWindowManagerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitWindowManagerTests.java
index 2e5078d86a8b..150aa13f2d00 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitWindowManagerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitWindowManagerTests.java
@@ -59,7 +59,7 @@ public class SplitWindowManagerTests extends ShellTestCase {
@Test
@UiThreadTest
public void testInitRelease() {
- mSplitWindowManager.init(mSplitLayout, new InsetsState());
+ mSplitWindowManager.init(mSplitLayout, new InsetsState(), false /* isRestoring */);
assertThat(mSplitWindowManager.getSurfaceControl()).isNotNull();
mSplitWindowManager.release(null /* t */);
assertThat(mSplitWindowManager.getSurfaceControl()).isNull();
diff --git a/media/java/android/media/AudioHalVersionInfo.java b/media/java/android/media/AudioHalVersionInfo.java
index c881a03c6732..0f48abeb6882 100644
--- a/media/java/android/media/AudioHalVersionInfo.java
+++ b/media/java/android/media/AudioHalVersionInfo.java
@@ -80,8 +80,9 @@ public final class AudioHalVersionInfo implements Parcelable, Comparable<AudioHa
* List of all valid Audio HAL versions. This list need to be in sync with sAudioHALVersions
* defined in frameworks/av/media/libaudiohal/FactoryHalHidl.cpp.
*/
+ // TODO: add AIDL_1_0 with sAudioHALVersions.
public static final @NonNull List<AudioHalVersionInfo> VERSIONS =
- List.of(AIDL_1_0, HIDL_7_1, HIDL_7_0, HIDL_6_0, HIDL_5_0, HIDL_4_0);
+ List.of(HIDL_7_1, HIDL_7_0, HIDL_6_0, HIDL_5_0, HIDL_4_0);
private static final String TAG = "AudioHalVersionInfo";
private AudioHalVersion mHalVersion = new AudioHalVersion();
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java b/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java
index e07e9425808e..2da8c8c69ff8 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java
@@ -156,10 +156,9 @@ public class UninstallAlertDialogFragment extends DialogFragment implements
if (customUserManager.isUserOfType(USER_TYPE_PROFILE_MANAGED)
&& customUserManager.isSameProfileGroup(dialogInfo.user, myUserHandle)) {
messageBuilder.append(isArchive
- ? getString(R.string.archive_application_text_current_user_work_profile,
- userName) : getString(
- R.string.uninstall_application_text_current_user_work_profile,
- userName));
+ ? getString(R.string.archive_application_text_current_user_work_profile)
+ : getString(
+ R.string.uninstall_application_text_current_user_work_profile));
} else if (customUserManager.isUserOfType(USER_TYPE_PROFILE_CLONE)
&& customUserManager.isSameProfileGroup(dialogInfo.user, myUserHandle)) {
mIsClonedApp = true;
@@ -168,11 +167,11 @@ public class UninstallAlertDialogFragment extends DialogFragment implements
} else if (Flags.allowPrivateProfile()
&& customUserManager.isPrivateProfile()
&& customUserManager.isSameProfileGroup(dialogInfo.user, myUserHandle)) {
- messageBuilder.append(isArchive ? getString(
- R.string.archive_application_text_current_user_private_profile,
- userName) : getString(
- R.string.uninstall_application_text_current_user_private_profile,
- userName));
+ messageBuilder.append(
+ isArchive ? getString(
+ R.string.archive_application_text_current_user_private_profile)
+ : getString(
+ R.string.uninstall_application_text_current_user_private_profile));
} else if (isArchive) {
messageBuilder.append(
getString(R.string.archive_application_text_user, userName));
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/common/BroadcastReceiverFlow.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/common/BroadcastReceiverFlow.kt
index d0d2dc0083a6..e099f1124bf1 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/common/BroadcastReceiverFlow.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/common/BroadcastReceiverFlow.kt
@@ -20,13 +20,17 @@ import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
+import android.util.Log
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.conflate
import kotlinx.coroutines.flow.flowOn
+private const val TAG = "BroadcastReceiverFlow"
+
/**
* A [BroadcastReceiver] flow for the given [intentFilter].
*/
@@ -39,4 +43,6 @@ fun Context.broadcastReceiverFlow(intentFilter: IntentFilter): Flow<Intent> = ca
registerReceiver(broadcastReceiver, intentFilter, Context.RECEIVER_NOT_EXPORTED)
awaitClose { unregisterReceiver(broadcastReceiver) }
+}.catch { e ->
+ Log.e(TAG, "Error while broadcastReceiverFlow", e)
}.conflate().flowOn(Dispatchers.Default)
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/framework/common/BroadcastReceiverFlowTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/framework/common/BroadcastReceiverFlowTest.kt
index dfaf3c66ff8d..eef5225aef42 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/framework/common/BroadcastReceiverFlowTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/framework/common/BroadcastReceiverFlowTest.kt
@@ -31,8 +31,10 @@ import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.any
import org.mockito.kotlin.doAnswer
+import org.mockito.kotlin.doThrow
import org.mockito.kotlin.eq
import org.mockito.kotlin.mock
+import org.mockito.kotlin.stub
@RunWith(AndroidJUnit4::class)
class BroadcastReceiverFlowTest {
@@ -74,6 +76,18 @@ class BroadcastReceiverFlowTest {
assertThat(onReceiveIsCalled).isTrue()
}
+ @Test
+ fun broadcastReceiverFlow_unregisterReceiverThrowException_noCrash() = runBlocking {
+ context.stub {
+ on { unregisterReceiver(any()) } doThrow IllegalArgumentException()
+ }
+ val flow = context.broadcastReceiverFlow(INTENT_FILTER)
+
+ flow.firstWithTimeoutOrNull()
+
+ assertThat(registeredBroadcastReceiver).isNotNull()
+ }
+
private companion object {
val INTENT_FILTER = IntentFilter()
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
index 69b61c74625e..2cb44ec39a23 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
@@ -31,7 +31,6 @@ import android.bluetooth.BluetoothLeBroadcastReceiveState;
import android.bluetooth.BluetoothLeBroadcastSubgroup;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.BluetoothProfile.ServiceListener;
-import android.bluetooth.BluetoothStatusCodes;
import android.content.ContentResolver;
import android.content.Context;
import android.database.ContentObserver;
@@ -42,7 +41,6 @@ import android.os.Looper;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.Log;
-import android.util.Pair;
import androidx.annotation.RequiresApi;
@@ -53,15 +51,17 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
+import java.util.Map;
import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadLocalRandom;
/**
- * LocalBluetoothLeBroadcast provides an interface between the Settings app
- * and the functionality of the local {@link BluetoothLeBroadcast}.
- * Use the {@link BluetoothLeBroadcast.Callback} to get the result callback.
+ * LocalBluetoothLeBroadcast provides an interface between the Settings app and the functionality of
+ * the local {@link BluetoothLeBroadcast}. Use the {@link BluetoothLeBroadcast.Callback} to get the
+ * result callback.
*/
public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile {
private static final String TAG = "LocalBluetoothLeBroadcast";
@@ -74,11 +74,12 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile {
// Order of this profile in device profiles list
private static final int ORDINAL = 1;
private static final int UNKNOWN_VALUE_PLACEHOLDER = -1;
- private static final Uri[] SETTINGS_URIS = new Uri[]{
- Settings.Secure.getUriFor(Settings.Secure.BLUETOOTH_LE_BROADCAST_PROGRAM_INFO),
- Settings.Secure.getUriFor(Settings.Secure.BLUETOOTH_LE_BROADCAST_CODE),
- Settings.Secure.getUriFor(Settings.Secure.BLUETOOTH_LE_BROADCAST_APP_SOURCE_NAME),
- };
+ private static final Uri[] SETTINGS_URIS =
+ new Uri[] {
+ Settings.Secure.getUriFor(Settings.Secure.BLUETOOTH_LE_BROADCAST_PROGRAM_INFO),
+ Settings.Secure.getUriFor(Settings.Secure.BLUETOOTH_LE_BROADCAST_CODE),
+ Settings.Secure.getUriFor(Settings.Secure.BLUETOOTH_LE_BROADCAST_APP_SOURCE_NAME),
+ };
private BluetoothLeBroadcast mServiceBroadcast;
private BluetoothLeBroadcastAssistant mServiceBroadcastAssistant;
@@ -95,62 +96,82 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile {
private Executor mExecutor;
private ContentResolver mContentResolver;
private ContentObserver mSettingsObserver;
+ // Cached broadcast callbacks being register before service is connected.
+ private Map<BluetoothLeBroadcast.Callback, Executor> mCachedBroadcastCallbackExecutorMap =
+ new ConcurrentHashMap<>();
- private final ServiceListener mServiceListener = new ServiceListener() {
- @Override
- public void onServiceConnected(int profile, BluetoothProfile proxy) {
- if (DEBUG) {
- Log.d(TAG, "Bluetooth service connected: " + profile);
- }
- if ((profile == BluetoothProfile.LE_AUDIO_BROADCAST) && !mIsBroadcastProfileReady) {
- mServiceBroadcast = (BluetoothLeBroadcast) proxy;
- mIsBroadcastProfileReady = true;
- registerServiceCallBack(mExecutor, mBroadcastCallback);
- List<BluetoothLeBroadcastMetadata> metadata = getAllBroadcastMetadata();
- if (!metadata.isEmpty()) {
- updateBroadcastInfoFromBroadcastMetadata(metadata.get(0));
+ private final ServiceListener mServiceListener =
+ new ServiceListener() {
+ @Override
+ public void onServiceConnected(int profile, BluetoothProfile proxy) {
+ if (DEBUG) {
+ Log.d(TAG, "Bluetooth service connected: " + profile);
+ }
+ if ((profile == BluetoothProfile.LE_AUDIO_BROADCAST)
+ && !mIsBroadcastProfileReady) {
+ mServiceBroadcast = (BluetoothLeBroadcast) proxy;
+ mIsBroadcastProfileReady = true;
+ registerServiceCallBack(mExecutor, mBroadcastCallback);
+ List<BluetoothLeBroadcastMetadata> metadata = getAllBroadcastMetadata();
+ if (!metadata.isEmpty()) {
+ updateBroadcastInfoFromBroadcastMetadata(metadata.get(0));
+ }
+ registerContentObserver();
+ if (DEBUG) {
+ Log.d(
+ TAG,
+ "onServiceConnected: register "
+ + "mCachedBroadcastCallbackExecutorMap = "
+ + mCachedBroadcastCallbackExecutorMap);
+ }
+ mCachedBroadcastCallbackExecutorMap.forEach(
+ (callback, executor) ->
+ registerServiceCallBack(executor, callback));
+ } else if ((profile == BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT)
+ && !mIsBroadcastAssistantProfileReady) {
+ mIsBroadcastAssistantProfileReady = true;
+ mServiceBroadcastAssistant = (BluetoothLeBroadcastAssistant) proxy;
+ registerBroadcastAssistantCallback(mExecutor, mBroadcastAssistantCallback);
+ }
}
- registerContentObserver();
- } else if ((profile == BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT)
- && !mIsBroadcastAssistantProfileReady) {
- mIsBroadcastAssistantProfileReady = true;
- mServiceBroadcastAssistant = (BluetoothLeBroadcastAssistant) proxy;
- registerBroadcastAssistantCallback(mExecutor, mBroadcastAssistantCallback);
- }
- }
- @Override
- public void onServiceDisconnected(int profile) {
- if (DEBUG) {
- Log.d(TAG, "Bluetooth service disconnected");
- }
- if ((profile == BluetoothProfile.LE_AUDIO_BROADCAST) && mIsBroadcastProfileReady) {
- mIsBroadcastProfileReady = false;
- unregisterServiceCallBack(mBroadcastCallback);
- }
- if ((profile == BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT)
- && mIsBroadcastAssistantProfileReady) {
- mIsBroadcastAssistantProfileReady = false;
- unregisterBroadcastAssistantCallback(mBroadcastAssistantCallback);
- }
+ @Override
+ public void onServiceDisconnected(int profile) {
+ if (DEBUG) {
+ Log.d(TAG, "Bluetooth service disconnected: " + profile);
+ }
+ if ((profile == BluetoothProfile.LE_AUDIO_BROADCAST)
+ && mIsBroadcastProfileReady) {
+ mIsBroadcastProfileReady = false;
+ unregisterServiceCallBack(mBroadcastCallback);
+ mCachedBroadcastCallbackExecutorMap.clear();
+ }
+ if ((profile == BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT)
+ && mIsBroadcastAssistantProfileReady) {
+ mIsBroadcastAssistantProfileReady = false;
+ unregisterBroadcastAssistantCallback(mBroadcastAssistantCallback);
+ }
- if (!mIsBroadcastAssistantProfileReady && !mIsBroadcastProfileReady) {
- unregisterContentObserver();
- }
- }
- };
+ if (!mIsBroadcastAssistantProfileReady && !mIsBroadcastProfileReady) {
+ unregisterContentObserver();
+ }
+ }
+ };
private final BluetoothLeBroadcast.Callback mBroadcastCallback =
new BluetoothLeBroadcast.Callback() {
@Override
public void onBroadcastStarted(int reason, int broadcastId) {
if (DEBUG) {
- Log.d(TAG,
- "onBroadcastStarted(), reason = " + reason + ", broadcastId = "
+ Log.d(
+ TAG,
+ "onBroadcastStarted(), reason = "
+ + reason
+ + ", broadcastId = "
+ broadcastId);
}
setLatestBroadcastId(broadcastId);
- setAppSourceName(mNewAppSourceName, /*updateContentResolver=*/ true);
+ setAppSourceName(mNewAppSourceName, /* updateContentResolver= */ true);
}
@Override
@@ -161,8 +182,8 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile {
}
@Override
- public void onBroadcastMetadataChanged(int broadcastId,
- @NonNull BluetoothLeBroadcastMetadata metadata) {
+ public void onBroadcastMetadataChanged(
+ int broadcastId, @NonNull BluetoothLeBroadcastMetadata metadata) {
if (DEBUG) {
Log.d(TAG, "onBroadcastMetadataChanged(), broadcastId = " + broadcastId);
}
@@ -172,8 +193,11 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile {
@Override
public void onBroadcastStopped(int reason, int broadcastId) {
if (DEBUG) {
- Log.d(TAG,
- "onBroadcastStopped(), reason = " + reason + ", broadcastId = "
+ Log.d(
+ TAG,
+ "onBroadcastStopped(), reason = "
+ + reason
+ + ", broadcastId = "
+ broadcastId);
}
@@ -191,37 +215,42 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile {
@Override
public void onBroadcastUpdated(int reason, int broadcastId) {
if (DEBUG) {
- Log.d(TAG,
- "onBroadcastUpdated(), reason = " + reason + ", broadcastId = "
+ Log.d(
+ TAG,
+ "onBroadcastUpdated(), reason = "
+ + reason
+ + ", broadcastId = "
+ broadcastId);
}
setLatestBroadcastId(broadcastId);
- setAppSourceName(mNewAppSourceName, /*updateContentResolver=*/ true);
+ setAppSourceName(mNewAppSourceName, /* updateContentResolver= */ true);
}
@Override
public void onBroadcastUpdateFailed(int reason, int broadcastId) {
if (DEBUG) {
- Log.d(TAG,
- "onBroadcastUpdateFailed(), reason = " + reason + ", broadcastId = "
+ Log.d(
+ TAG,
+ "onBroadcastUpdateFailed(), reason = "
+ + reason
+ + ", broadcastId = "
+ broadcastId);
}
}
@Override
- public void onPlaybackStarted(int reason, int broadcastId) {
- }
+ public void onPlaybackStarted(int reason, int broadcastId) {}
@Override
- public void onPlaybackStopped(int reason, int broadcastId) {
- }
+ public void onPlaybackStopped(int reason, int broadcastId) {}
};
private final BluetoothLeBroadcastAssistant.Callback mBroadcastAssistantCallback =
new BluetoothLeBroadcastAssistant.Callback() {
@Override
- public void onSourceAdded(@NonNull BluetoothDevice sink, int sourceId,
- int reason) {}
+ public void onSourceAdded(
+ @NonNull BluetoothDevice sink, int sourceId, int reason) {}
+
@Override
public void onSearchStarted(int reason) {}
@@ -238,38 +267,65 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile {
public void onSourceFound(@NonNull BluetoothLeBroadcastMetadata source) {}
@Override
- public void onSourceAddFailed(@NonNull BluetoothDevice sink,
- @NonNull BluetoothLeBroadcastMetadata source, int reason) {}
+ public void onSourceAddFailed(
+ @NonNull BluetoothDevice sink,
+ @NonNull BluetoothLeBroadcastMetadata source,
+ int reason) {}
@Override
- public void onSourceModified(@NonNull BluetoothDevice sink, int sourceId,
- int reason) {}
+ public void onSourceModified(
+ @NonNull BluetoothDevice sink, int sourceId, int reason) {}
@Override
- public void onSourceModifyFailed(@NonNull BluetoothDevice sink, int sourceId,
- int reason) {}
+ public void onSourceModifyFailed(
+ @NonNull BluetoothDevice sink, int sourceId, int reason) {}
@Override
- public void onSourceRemoved(@NonNull BluetoothDevice sink, int sourceId,
- int reason) {
+ public void onSourceRemoved(
+ @NonNull BluetoothDevice sink, int sourceId, int reason) {
if (DEBUG) {
- Log.d(TAG, "onSourceRemoved(), sink = " + sink + ", reason = "
- + reason + ", sourceId = " + sourceId);
+ Log.d(
+ TAG,
+ "onSourceRemoved(), sink = "
+ + sink
+ + ", reason = "
+ + reason
+ + ", sourceId = "
+ + sourceId);
}
}
@Override
- public void onSourceRemoveFailed(@NonNull BluetoothDevice sink, int sourceId,
- int reason) {
+ public void onSourceRemoveFailed(
+ @NonNull BluetoothDevice sink, int sourceId, int reason) {
if (DEBUG) {
- Log.d(TAG, "onSourceRemoveFailed(), sink = " + sink + ", reason = "
- + reason + ", sourceId = " + sourceId);
+ Log.d(
+ TAG,
+ "onSourceRemoveFailed(), sink = "
+ + sink
+ + ", reason = "
+ + reason
+ + ", sourceId = "
+ + sourceId);
}
}
@Override
- public void onReceiveStateChanged(@NonNull BluetoothDevice sink, int sourceId,
- @NonNull BluetoothLeBroadcastReceiveState state) {}
+ public void onReceiveStateChanged(
+ @NonNull BluetoothDevice sink,
+ int sourceId,
+ @NonNull BluetoothLeBroadcastReceiveState state) {
+ if (DEBUG) {
+ Log.d(
+ TAG,
+ "onReceiveStateChanged(), sink = "
+ + sink
+ + ", sourceId = "
+ + sourceId
+ + ", state = "
+ + state);
+ }
+ }
};
private class BroadcastSettingsObserver extends ContentObserver {
@@ -296,8 +352,8 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile {
BluetoothAdapter.getDefaultAdapter()
.getProfileProxy(context, mServiceListener, BluetoothProfile.LE_AUDIO_BROADCAST);
BluetoothAdapter.getDefaultAdapter()
- .getProfileProxy(context, mServiceListener,
- BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT);
+ .getProfileProxy(
+ context, mServiceListener, BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT);
}
/**
@@ -312,11 +368,11 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile {
}
String programInfo = getProgramInfo();
if (DEBUG) {
- Log.d(TAG,
- "startBroadcast: language = " + language + " ,programInfo = " + programInfo);
+ Log.d(TAG, "startBroadcast: language = " + language + " ,programInfo = " + programInfo);
}
buildContentMetadata(language, programInfo);
- mServiceBroadcast.startBroadcast(mBluetoothLeAudioContentMetadata,
+ mServiceBroadcast.startBroadcast(
+ mBluetoothLeAudioContentMetadata,
(mBroadcastCode != null && mBroadcastCode.length > 0) ? mBroadcastCode : null);
}
@@ -325,7 +381,7 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile {
}
public void setProgramInfo(String programInfo) {
- setProgramInfo(programInfo, /*updateContentResolver=*/ true);
+ setProgramInfo(programInfo, /* updateContentResolver= */ true);
}
private void setProgramInfo(String programInfo, boolean updateContentResolver) {
@@ -344,8 +400,10 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile {
Log.d(TAG, "mContentResolver is null");
return;
}
- Settings.Secure.putString(mContentResolver,
- Settings.Secure.BLUETOOTH_LE_BROADCAST_PROGRAM_INFO, programInfo);
+ Settings.Secure.putString(
+ mContentResolver,
+ Settings.Secure.BLUETOOTH_LE_BROADCAST_PROGRAM_INFO,
+ programInfo);
}
}
@@ -354,7 +412,7 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile {
}
public void setBroadcastCode(byte[] broadcastCode) {
- setBroadcastCode(broadcastCode, /*updateContentResolver=*/ true);
+ setBroadcastCode(broadcastCode, /* updateContentResolver= */ true);
}
private void setBroadcastCode(byte[] broadcastCode, boolean updateContentResolver) {
@@ -372,7 +430,9 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile {
Log.d(TAG, "mContentResolver is null");
return;
}
- Settings.Secure.putString(mContentResolver, Settings.Secure.BLUETOOTH_LE_BROADCAST_CODE,
+ Settings.Secure.putString(
+ mContentResolver,
+ Settings.Secure.BLUETOOTH_LE_BROADCAST_CODE,
new String(broadcastCode, StandardCharsets.UTF_8));
}
}
@@ -401,8 +461,10 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile {
Log.d(TAG, "mContentResolver is null");
return;
}
- Settings.Secure.putString(mContentResolver,
- Settings.Secure.BLUETOOTH_LE_BROADCAST_APP_SOURCE_NAME, mAppSourceName);
+ Settings.Secure.putString(
+ mContentResolver,
+ Settings.Secure.BLUETOOTH_LE_BROADCAST_APP_SOURCE_NAME,
+ mAppSourceName);
}
}
@@ -427,10 +489,11 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile {
if (mBluetoothLeBroadcastMetadata == null) {
final List<BluetoothLeBroadcastMetadata> metadataList =
mServiceBroadcast.getAllBroadcastMetadata();
- mBluetoothLeBroadcastMetadata = metadataList.stream()
- .filter(i -> i.getBroadcastId() == mBroadcastId)
- .findFirst()
- .orElse(null);
+ mBluetoothLeBroadcastMetadata =
+ metadataList.stream()
+ .filter(i -> i.getBroadcastId() == mBroadcastId)
+ .findFirst()
+ .orElse(null);
}
return mBluetoothLeBroadcastMetadata;
}
@@ -440,22 +503,27 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile {
Log.d(TAG, "updateBroadcastInfoFromContentProvider: mContentResolver is null");
return;
}
- String programInfo = Settings.Secure.getString(mContentResolver,
- Settings.Secure.BLUETOOTH_LE_BROADCAST_PROGRAM_INFO);
+ String programInfo =
+ Settings.Secure.getString(
+ mContentResolver, Settings.Secure.BLUETOOTH_LE_BROADCAST_PROGRAM_INFO);
if (programInfo == null) {
programInfo = getDefaultValueOfProgramInfo();
}
- setProgramInfo(programInfo, /*updateContentResolver=*/ false);
+ setProgramInfo(programInfo, /* updateContentResolver= */ false);
- String prefBroadcastCode = Settings.Secure.getString(mContentResolver,
- Settings.Secure.BLUETOOTH_LE_BROADCAST_CODE);
- byte[] broadcastCode = (prefBroadcastCode == null) ? getDefaultValueOfBroadcastCode()
- : prefBroadcastCode.getBytes(StandardCharsets.UTF_8);
- setBroadcastCode(broadcastCode, /*updateContentResolver=*/ false);
+ String prefBroadcastCode =
+ Settings.Secure.getString(
+ mContentResolver, Settings.Secure.BLUETOOTH_LE_BROADCAST_CODE);
+ byte[] broadcastCode =
+ (prefBroadcastCode == null)
+ ? getDefaultValueOfBroadcastCode()
+ : prefBroadcastCode.getBytes(StandardCharsets.UTF_8);
+ setBroadcastCode(broadcastCode, /* updateContentResolver= */ false);
- String appSourceName = Settings.Secure.getString(mContentResolver,
- Settings.Secure.BLUETOOTH_LE_BROADCAST_APP_SOURCE_NAME);
- setAppSourceName(appSourceName, /*updateContentResolver=*/ false);
+ String appSourceName =
+ Settings.Secure.getString(
+ mContentResolver, Settings.Secure.BLUETOOTH_LE_BROADCAST_APP_SOURCE_NAME);
+ setAppSourceName(appSourceName, /* updateContentResolver= */ false);
}
private void updateBroadcastInfoFromBroadcastMetadata(
@@ -474,12 +542,12 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile {
}
BluetoothLeAudioContentMetadata contentMetadata = subgroup.get(0).getContentMetadata();
setProgramInfo(contentMetadata.getProgramInfo());
- setAppSourceName(getAppSourceName(), /*updateContentResolver=*/ true);
+ setAppSourceName(getAppSourceName(), /* updateContentResolver= */ true);
}
/**
- * Stop the latest LE Broadcast. If the system stopped the LE Broadcast, then the system
- * calls the corresponding callback {@link BluetoothLeBroadcast.Callback}.
+ * Stop the latest LE Broadcast. If the system stopped the LE Broadcast, then the system calls
+ * the corresponding callback {@link BluetoothLeBroadcast.Callback}.
*/
public void stopLatestBroadcast() {
stopBroadcast(mBroadcastId);
@@ -511,7 +579,8 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile {
}
String programInfo = getProgramInfo();
if (DEBUG) {
- Log.d(TAG,
+ Log.d(
+ TAG,
"updateBroadcast: language = " + language + " ,programInfo = " + programInfo);
}
mNewAppSourceName = appSourceName;
@@ -519,50 +588,79 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile {
mServiceBroadcast.updateBroadcast(mBroadcastId, mBluetoothLeAudioContentMetadata);
}
- public void registerServiceCallBack(@NonNull @CallbackExecutor Executor executor,
+ /**
+ * Register Broadcast Callbacks to track its state and receivers
+ *
+ * @param executor Executor object for callback
+ * @param callback Callback object to be registered
+ */
+ public void registerServiceCallBack(
+ @NonNull @CallbackExecutor Executor executor,
@NonNull BluetoothLeBroadcast.Callback callback) {
if (mServiceBroadcast == null) {
- Log.d(TAG, "The BluetoothLeBroadcast is null.");
+ Log.d(TAG, "registerServiceCallBack failed, the BluetoothLeBroadcast is null.");
+ mCachedBroadcastCallbackExecutorMap.putIfAbsent(callback, executor);
return;
}
- mServiceBroadcast.registerCallback(executor, callback);
+ try {
+ mServiceBroadcast.registerCallback(executor, callback);
+ } catch (IllegalArgumentException e) {
+ Log.w(TAG, "registerServiceCallBack failed. " + e.getMessage());
+ }
}
/**
- * Register Broadcast Assistant Callbacks to track it's state and receivers
+ * Register Broadcast Assistant Callbacks to track its state and receivers
*
* @param executor Executor object for callback
* @param callback Callback object to be registered
*/
- public void registerBroadcastAssistantCallback(@NonNull @CallbackExecutor Executor executor,
+ private void registerBroadcastAssistantCallback(
+ @NonNull @CallbackExecutor Executor executor,
@NonNull BluetoothLeBroadcastAssistant.Callback callback) {
if (mServiceBroadcastAssistant == null) {
- Log.d(TAG, "The BluetoothLeBroadcastAssisntant is null.");
+ Log.d(
+ TAG,
+ "registerBroadcastAssistantCallback failed, "
+ + "the BluetoothLeBroadcastAssistant is null.");
return;
}
mServiceBroadcastAssistant.registerCallback(executor, callback);
}
+ /**
+ * Unregister previously registered Broadcast Callbacks
+ *
+ * @param callback Callback object to be unregistered
+ */
public void unregisterServiceCallBack(@NonNull BluetoothLeBroadcast.Callback callback) {
+ mCachedBroadcastCallbackExecutorMap.remove(callback);
if (mServiceBroadcast == null) {
- Log.d(TAG, "The BluetoothLeBroadcast is null.");
+ Log.d(TAG, "unregisterServiceCallBack failed, the BluetoothLeBroadcast is null.");
return;
}
- mServiceBroadcast.unregisterCallback(callback);
+ try {
+ mServiceBroadcast.unregisterCallback(callback);
+ } catch (IllegalArgumentException e) {
+ Log.w(TAG, "unregisterServiceCallBack failed. " + e.getMessage());
+ }
}
/**
- * Unregister previousely registered Broadcast Assistant Callbacks
+ * Unregister previously registered Broadcast Assistant Callbacks
*
* @param callback Callback object to be unregistered
*/
- public void unregisterBroadcastAssistantCallback(
+ private void unregisterBroadcastAssistantCallback(
@NonNull BluetoothLeBroadcastAssistant.Callback callback) {
if (mServiceBroadcastAssistant == null) {
- Log.d(TAG, "The BluetoothLeBroadcastAssisntant is null.");
+ Log.d(
+ TAG,
+ "unregisterBroadcastAssistantCallback, "
+ + "the BluetoothLeBroadcastAssistant is null.");
return;
}
@@ -570,8 +668,8 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile {
}
private void buildContentMetadata(String language, String programInfo) {
- mBluetoothLeAudioContentMetadata = mBuilder.setLanguage(language).setProgramInfo(
- programInfo).build();
+ mBluetoothLeAudioContentMetadata =
+ mBuilder.setLanguage(language).setProgramInfo(programInfo).build();
}
public LocalBluetoothLeBroadcastMetadata getLocalBluetoothLeBroadcastMetaData() {
@@ -600,9 +698,7 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile {
return true;
}
- /**
- * Not supported since LE Audio Broadcasts do not establish a connection.
- */
+ /** Not supported since LE Audio Broadcasts do not establish a connection. */
public int getConnectionStatus(BluetoothDevice device) {
if (mServiceBroadcast == null) {
return BluetoothProfile.STATE_DISCONNECTED;
@@ -611,9 +707,7 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile {
return mServiceBroadcast.getConnectionState(device);
}
- /**
- * Not supported since LE Audio Broadcasts do not establish a connection.
- */
+ /** Not supported since LE Audio Broadcasts do not establish a connection. */
public List<BluetoothDevice> getConnectedDevices() {
if (mServiceBroadcast == null) {
return new ArrayList<BluetoothDevice>(0);
@@ -622,8 +716,8 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile {
return mServiceBroadcast.getConnectedDevices();
}
- public @NonNull
- List<BluetoothLeBroadcastMetadata> getAllBroadcastMetadata() {
+ /** Get all broadcast metadata. */
+ public @NonNull List<BluetoothLeBroadcastMetadata> getAllBroadcastMetadata() {
if (mServiceBroadcast == null) {
Log.d(TAG, "The BluetoothLeBroadcast is null.");
return Collections.emptyList();
@@ -640,16 +734,14 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile {
return !mServiceBroadcast.getAllBroadcastMetadata().isEmpty();
}
- /**
- * Service does not provide method to get/set policy.
- */
+ /** Service does not provide method to get/set policy. */
public int getConnectionPolicy(BluetoothDevice device) {
return CONNECTION_POLICY_FORBIDDEN;
}
/**
- * Service does not provide "setEnabled" method. Please use {@link #startBroadcast},
- * {@link #stopBroadcast()} or {@link #updateBroadcast(String, String)}
+ * Service does not provide "setEnabled" method. Please use {@link #startBroadcast}, {@link
+ * #stopBroadcast()} or {@link #updateBroadcast(String, String)}
*/
public boolean setEnabled(BluetoothDevice device, boolean enabled) {
return false;
@@ -683,9 +775,8 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile {
}
if (mServiceBroadcast != null) {
try {
- BluetoothAdapter.getDefaultAdapter().closeProfileProxy(
- BluetoothProfile.LE_AUDIO_BROADCAST,
- mServiceBroadcast);
+ BluetoothAdapter.getDefaultAdapter()
+ .closeProfileProxy(BluetoothProfile.LE_AUDIO_BROADCAST, mServiceBroadcast);
mServiceBroadcast = null;
} catch (Throwable t) {
Log.w(TAG, "Error cleaning up LeAudio proxy", t);
@@ -694,13 +785,13 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile {
}
private String getDefaultValueOfProgramInfo() {
- //set the default value;
+ // set the default value;
int postfix = ThreadLocalRandom.current().nextInt(DEFAULT_CODE_MIN, DEFAULT_CODE_MAX);
return BluetoothAdapter.getDefaultAdapter().getName() + UNDERLINE + postfix;
}
private byte[] getDefaultValueOfBroadcastCode() {
- //set the default value;
+ // set the default value;
return generateRandomPassword().getBytes(StandardCharsets.UTF_8);
}
@@ -708,14 +799,14 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile {
if (DEBUG) {
Log.d(TAG, "resetCacheInfo:");
}
- setAppSourceName("", /*updateContentResolver=*/ true);
+ setAppSourceName("", /* updateContentResolver= */ true);
mBluetoothLeBroadcastMetadata = null;
mBroadcastId = UNKNOWN_VALUE_PLACEHOLDER;
}
private String generateRandomPassword() {
String randomUUID = UUID.randomUUID().toString();
- //first 12 chars from xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx
+ // first 12 chars from xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx
return randomUUID.substring(0, 8) + randomUUID.substring(9, 13);
}
@@ -752,5 +843,4 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile {
}
}
}
-
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastAssistant.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastAssistant.java
index bb103b8896fd..34008ac56042 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastAssistant.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastAssistant.java
@@ -39,13 +39,14 @@ import com.android.settingslib.R;
import java.util.ArrayList;
import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;
-
/**
- * LocalBluetoothLeBroadcastAssistant provides an interface between the Settings app
- * and the functionality of the local {@link BluetoothLeBroadcastAssistant}.
- * Use the {@link BluetoothLeBroadcastAssistant.Callback} to get the result callback.
+ * LocalBluetoothLeBroadcastAssistant provides an interface between the Settings app and the
+ * functionality of the local {@link BluetoothLeBroadcastAssistant}. Use the {@link
+ * BluetoothLeBroadcastAssistant.Callback} to get the result callback.
*/
public class LocalBluetoothLeBroadcastAssistant implements LocalBluetoothProfile {
private static final String TAG = "LocalBluetoothLeBroadcastAssistant";
@@ -62,58 +63,76 @@ public class LocalBluetoothLeBroadcastAssistant implements LocalBluetoothProfile
private BluetoothLeBroadcastMetadata mBluetoothLeBroadcastMetadata;
private BluetoothLeBroadcastMetadata.Builder mBuilder;
private boolean mIsProfileReady;
+ // Cached assistant callbacks being register before service is connected.
+ private final Map<BluetoothLeBroadcastAssistant.Callback, Executor> mCachedCallbackExecutorMap =
+ new ConcurrentHashMap<>();
+
+ private final ServiceListener mServiceListener =
+ new ServiceListener() {
+ @Override
+ public void onServiceConnected(int profile, BluetoothProfile proxy) {
+ if (DEBUG) {
+ Log.d(TAG, "Bluetooth service connected");
+ }
+ mService = (BluetoothLeBroadcastAssistant) proxy;
+ // We just bound to the service, so refresh the UI for any connected LeAudio
+ // devices.
+ List<BluetoothDevice> deviceList = mService.getConnectedDevices();
+ while (!deviceList.isEmpty()) {
+ BluetoothDevice nextDevice = deviceList.remove(0);
+ CachedBluetoothDevice device = mDeviceManager.findDevice(nextDevice);
+ // we may add a new device here, but generally this should not happen
+ if (device == null) {
+ if (DEBUG) {
+ Log.d(
+ TAG,
+ "LocalBluetoothLeBroadcastAssistant found new device: "
+ + nextDevice);
+ }
+ device = mDeviceManager.addDevice(nextDevice);
+ }
+ device.onProfileStateChanged(
+ LocalBluetoothLeBroadcastAssistant.this,
+ BluetoothProfile.STATE_CONNECTED);
+ device.refresh();
+ }
- private final ServiceListener mServiceListener = new ServiceListener() {
- @Override
- public void onServiceConnected(int profile, BluetoothProfile proxy) {
- if (DEBUG) {
- Log.d(TAG, "Bluetooth service connected");
- }
- mService = (BluetoothLeBroadcastAssistant) proxy;
- // We just bound to the service, so refresh the UI for any connected LeAudio devices.
- List<BluetoothDevice> deviceList = mService.getConnectedDevices();
- while (!deviceList.isEmpty()) {
- BluetoothDevice nextDevice = deviceList.remove(0);
- CachedBluetoothDevice device = mDeviceManager.findDevice(nextDevice);
- // we may add a new device here, but generally this should not happen
- if (device == null) {
+ mProfileManager.callServiceConnectedListeners();
+ mIsProfileReady = true;
if (DEBUG) {
- Log.d(TAG, "LocalBluetoothLeBroadcastAssistant found new device: "
- + nextDevice);
+ Log.d(
+ TAG,
+ "onServiceConnected, register mCachedCallbackExecutorMap = "
+ + mCachedCallbackExecutorMap);
}
- device = mDeviceManager.addDevice(nextDevice);
+ mCachedCallbackExecutorMap.forEach(
+ (callback, executor) -> registerServiceCallBack(executor, callback));
}
- device.onProfileStateChanged(LocalBluetoothLeBroadcastAssistant.this,
- BluetoothProfile.STATE_CONNECTED);
- device.refresh();
- }
- mProfileManager.callServiceConnectedListeners();
- mIsProfileReady = true;
- }
-
- @Override
- public void onServiceDisconnected(int profile) {
- if (profile != BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT) {
- Log.d(TAG, "The profile is not LE_AUDIO_BROADCAST_ASSISTANT");
- return;
- }
- if (DEBUG) {
- Log.d(TAG, "Bluetooth service disconnected");
- }
- mProfileManager.callServiceDisconnectedListeners();
- mIsProfileReady = false;
- }
- };
+ @Override
+ public void onServiceDisconnected(int profile) {
+ if (profile != BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT) {
+ Log.d(TAG, "The profile is not LE_AUDIO_BROADCAST_ASSISTANT");
+ return;
+ }
+ if (DEBUG) {
+ Log.d(TAG, "Bluetooth service disconnected");
+ }
+ mProfileManager.callServiceDisconnectedListeners();
+ mIsProfileReady = false;
+ mCachedCallbackExecutorMap.clear();
+ }
+ };
- public LocalBluetoothLeBroadcastAssistant(Context context,
+ public LocalBluetoothLeBroadcastAssistant(
+ Context context,
CachedBluetoothDeviceManager deviceManager,
LocalBluetoothProfileManager profileManager) {
mProfileManager = profileManager;
mDeviceManager = deviceManager;
- BluetoothAdapter.getDefaultAdapter().
- getProfileProxy(context, mServiceListener,
- BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT);
+ BluetoothAdapter.getDefaultAdapter()
+ .getProfileProxy(
+ context, mServiceListener, BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT);
mBuilder = new BluetoothLeBroadcastMetadata.Builder();
}
@@ -123,11 +142,11 @@ public class LocalBluetoothLeBroadcastAssistant implements LocalBluetoothProfile
* @param sink Broadcast Sink to which the Broadcast Source should be added
* @param metadata Broadcast Source metadata to be added to the Broadcast Sink
* @param isGroupOp {@code true} if Application wants to perform this operation for all
- * coordinated set members throughout this session. Otherwise, caller
- * would have to add, modify, and remove individual set members.
+ * coordinated set members throughout this session. Otherwise, caller would have to add,
+ * modify, and remove individual set members.
*/
- public void addSource(BluetoothDevice sink, BluetoothLeBroadcastMetadata metadata,
- boolean isGroupOp) {
+ public void addSource(
+ BluetoothDevice sink, BluetoothLeBroadcastMetadata metadata, boolean isGroupOp) {
if (mService == null) {
Log.d(TAG, "The BluetoothLeBroadcastAssistant is null");
return;
@@ -140,36 +159,55 @@ public class LocalBluetoothLeBroadcastAssistant implements LocalBluetoothProfile
* the qr code string.
*
* @param sink Broadcast Sink to which the Broadcast Source should be added
- * @param sourceAddressType hardware MAC Address of the device. See
- * {@link BluetoothDevice.AddressType}.
+ * @param sourceAddressType hardware MAC Address of the device. See {@link
+ * BluetoothDevice.AddressType}.
* @param presentationDelayMicros presentation delay of this Broadcast Source in microseconds.
* @param sourceAdvertisingSid 1-byte long Advertising_SID of the Broadcast Source.
* @param broadcastId 3-byte long Broadcast_ID of the Broadcast Source.
- * @param paSyncInterval Periodic Advertising Sync interval of the broadcast Source,
- * {@link BluetoothLeBroadcastMetadata#PA_SYNC_INTERVAL_UNKNOWN} if
- * unknown.
+ * @param paSyncInterval Periodic Advertising Sync interval of the broadcast Source, {@link
+ * BluetoothLeBroadcastMetadata#PA_SYNC_INTERVAL_UNKNOWN} if unknown.
* @param isEncrypted whether the Broadcast Source is encrypted.
* @param broadcastCode Broadcast Code for this Broadcast Source, null if code is not required.
* @param sourceDevice source advertiser address.
* @param isGroupOp {@code true} if Application wants to perform this operation for all
- * coordinated set members throughout this session. Otherwise, caller
- * would have to add, modify, and remove individual set members.
+ * coordinated set members throughout this session. Otherwise, caller would have to add,
+ * modify, and remove individual set members.
*/
- public void addSource(@NonNull BluetoothDevice sink, int sourceAddressType,
- int presentationDelayMicros, int sourceAdvertisingSid, int broadcastId,
- int paSyncInterval, boolean isEncrypted, byte[] broadcastCode,
- BluetoothDevice sourceDevice, boolean isGroupOp) {
+ public void addSource(
+ @NonNull BluetoothDevice sink,
+ int sourceAddressType,
+ int presentationDelayMicros,
+ int sourceAdvertisingSid,
+ int broadcastId,
+ int paSyncInterval,
+ boolean isEncrypted,
+ byte[] broadcastCode,
+ BluetoothDevice sourceDevice,
+ boolean isGroupOp) {
if (DEBUG) {
Log.d(TAG, "addSource()");
}
- buildMetadata(sourceAddressType, presentationDelayMicros, sourceAdvertisingSid, broadcastId,
- paSyncInterval, isEncrypted, broadcastCode, sourceDevice);
+ buildMetadata(
+ sourceAddressType,
+ presentationDelayMicros,
+ sourceAdvertisingSid,
+ broadcastId,
+ paSyncInterval,
+ isEncrypted,
+ broadcastCode,
+ sourceDevice);
addSource(sink, mBluetoothLeBroadcastMetadata, isGroupOp);
}
- private void buildMetadata(int sourceAddressType, int presentationDelayMicros,
- int sourceAdvertisingSid, int broadcastId, int paSyncInterval, boolean isEncrypted,
- byte[] broadcastCode, BluetoothDevice sourceDevice) {
+ private void buildMetadata(
+ int sourceAddressType,
+ int presentationDelayMicros,
+ int sourceAdvertisingSid,
+ int broadcastId,
+ int paSyncInterval,
+ boolean isEncrypted,
+ byte[] broadcastCode,
+ BluetoothDevice sourceDevice) {
mBluetoothLeBroadcastMetadata =
mBuilder.setSourceDevice(sourceDevice, sourceAddressType)
.setSourceAdvertisingSid(sourceAdvertisingSid)
@@ -223,10 +261,10 @@ public class LocalBluetoothLeBroadcastAssistant implements LocalBluetoothProfile
/**
* Stops an ongoing search for nearby Broadcast Sources.
*
- * On success, {@link BluetoothLeBroadcastAssistant.Callback#onSearchStopped(int)} will be
- * called with reason code {@link BluetoothStatusCodes#REASON_LOCAL_APP_REQUEST}.
- * On failure, {@link BluetoothLeBroadcastAssistant.Callback#onSearchStopFailed(int)} will be
- * called with reason code
+ * <p>On success, {@link BluetoothLeBroadcastAssistant.Callback#onSearchStopped(int)} will be
+ * called with reason code {@link BluetoothStatusCodes#REASON_LOCAL_APP_REQUEST}. On failure,
+ * {@link BluetoothLeBroadcastAssistant.Callback#onSearchStopFailed(int)} will be called with
+ * reason code
*
* @throws IllegalStateException if callback was not registered
*/
@@ -245,8 +283,8 @@ public class LocalBluetoothLeBroadcastAssistant implements LocalBluetoothProfile
* Get information about all Broadcast Sources that a Broadcast Sink knows about.
*
* @param sink Broadcast Sink from which to get all Broadcast Sources
- * @return the list of Broadcast Receive State {@link BluetoothLeBroadcastReceiveState}
- * stored in the Broadcast Sink
+ * @return the list of Broadcast Receive State {@link BluetoothLeBroadcastReceiveState} stored
+ * in the Broadcast Sink
* @throws NullPointerException when <var>sink</var> is null
*/
public @NonNull List<BluetoothLeBroadcastReceiveState> getAllSources(
@@ -261,24 +299,50 @@ public class LocalBluetoothLeBroadcastAssistant implements LocalBluetoothProfile
return mService.getAllSources(sink);
}
- public void registerServiceCallBack(@NonNull @CallbackExecutor Executor executor,
+ /**
+ * Register Broadcast Assistant Callbacks to track its state and receivers
+ *
+ * @param executor Executor object for callback
+ * @param callback Callback object to be registered
+ */
+ public void registerServiceCallBack(
+ @NonNull @CallbackExecutor Executor executor,
@NonNull BluetoothLeBroadcastAssistant.Callback callback) {
if (mService == null) {
- Log.d(TAG, "The BluetoothLeBroadcast is null.");
+ Log.d(
+ TAG,
+ "registerServiceCallBack failed, the BluetoothLeBroadcastAssistant is null.");
+ mCachedCallbackExecutorMap.putIfAbsent(callback, executor);
return;
}
- mService.registerCallback(executor, callback);
+ try {
+ mService.registerCallback(executor, callback);
+ } catch (IllegalArgumentException e) {
+ Log.w(TAG, "registerServiceCallBack failed. " + e.getMessage());
+ }
}
+ /**
+ * Unregister previously registered Broadcast Assistant Callbacks
+ *
+ * @param callback Callback object to be unregistered
+ */
public void unregisterServiceCallBack(
@NonNull BluetoothLeBroadcastAssistant.Callback callback) {
+ mCachedCallbackExecutorMap.remove(callback);
if (mService == null) {
- Log.d(TAG, "The BluetoothLeBroadcast is null.");
+ Log.d(
+ TAG,
+ "unregisterServiceCallBack failed, the BluetoothLeBroadcastAssistant is null.");
return;
}
- mService.unregisterCallback(callback);
+ try {
+ mService.unregisterCallback(callback);
+ } catch (IllegalArgumentException e) {
+ Log.w(TAG, "unregisterServiceCallBack failed. " + e.getMessage());
+ }
}
public boolean isProfileReady() {
@@ -310,9 +374,11 @@ public class LocalBluetoothLeBroadcastAssistant implements LocalBluetoothProfile
return new ArrayList<BluetoothDevice>(0);
}
return mService.getDevicesMatchingConnectionStates(
- new int[]{BluetoothProfile.STATE_CONNECTED,
- BluetoothProfile.STATE_CONNECTING,
- BluetoothProfile.STATE_DISCONNECTING});
+ new int[] {
+ BluetoothProfile.STATE_CONNECTED,
+ BluetoothProfile.STATE_CONNECTING,
+ BluetoothProfile.STATE_DISCONNECTING
+ });
}
public boolean isEnabled(BluetoothDevice device) {
@@ -373,9 +439,8 @@ public class LocalBluetoothLeBroadcastAssistant implements LocalBluetoothProfile
}
if (mService != null) {
try {
- BluetoothAdapter.getDefaultAdapter().closeProfileProxy(
- BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT,
- mService);
+ BluetoothAdapter.getDefaultAdapter()
+ .closeProfileProxy(BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT, mService);
mService = null;
} catch (Throwable t) {
Log.w(TAG, "Error cleaning up LeAudio proxy", t);
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateSharedAsState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateSharedAsState.kt
index 009f8bb38e61..e538e093e60f 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateSharedAsState.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateSharedAsState.kt
@@ -175,11 +175,7 @@ private fun <T> computeValue(
canOverflow: Boolean,
): T {
val state = layoutImpl.state.transitionState
- if (
- state !is TransitionState.Transition ||
- state.fromScene == state.toScene ||
- !layoutImpl.isTransitionReady(state)
- ) {
+ if (state !is TransitionState.Transition || !layoutImpl.isTransitionReady(state)) {
return sharedValue.value
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt
index 199832bc4ab6..de69c37d4630 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt
@@ -47,12 +47,6 @@ internal fun CoroutineScope.animateToScene(
when (state) {
is TransitionState.Idle -> animate(layoutImpl, target)
is TransitionState.Transition -> {
- if (state.toScene == state.fromScene) {
- // Same as idle.
- animate(layoutImpl, target)
- return
- }
-
// A transition is currently running: first check whether `transition.toScene` or
// `transition.fromScene` is the same as our target scene, in which case the transition
// can be accelerated or reversed to end up in the target state.
@@ -153,13 +147,13 @@ private fun CoroutineScope.animate(
}
private class OneOffTransition(
- override val fromScene: SceneKey,
- override val toScene: SceneKey,
+ fromScene: SceneKey,
+ toScene: SceneKey,
override val currentScene: SceneKey,
override val isInitiatedByUserInput: Boolean,
override val isUserInputOngoing: Boolean,
private val animatable: Animatable<Float, AnimationVector1D>,
-) : TransitionState.Transition {
+) : TransitionState.Transition(fromScene, toScene) {
override val progress: Float
get() = animatable.value
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
index 31604a6817f0..431a8aef6d3d 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
@@ -288,7 +288,6 @@ private fun shouldDrawElement(
// Always draw the element if there is no ongoing transition or if the element is not shared.
if (
state !is TransitionState.Transition ||
- state.fromScene == state.toScene ||
!layoutImpl.isTransitionReady(state) ||
state.fromScene !in element.sceneValues ||
state.toScene !in element.sceneValues
@@ -374,7 +373,7 @@ private fun isElementOpaque(
): Boolean {
val state = layoutImpl.state.transitionState
- if (state !is TransitionState.Transition || state.fromScene == state.toScene) {
+ if (state !is TransitionState.Transition) {
return true
}
@@ -611,7 +610,7 @@ private inline fun <T> computeValue(
val state = layoutImpl.state.transitionState
// There is no ongoing transition.
- if (state !is TransitionState.Transition || state.fromScene == state.toScene) {
+ if (state !is TransitionState.Transition) {
// Even if this element SceneTransitionLayout is not animated, the layout itself might be
// animated (e.g. by another parent SceneTransitionLayout), in which case this element still
// need to participate in the layout phase.
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt
index fa385d014ccb..7029da2edb0d 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt
@@ -131,10 +131,6 @@ private fun shouldComposeMovableElement(
val fromScene = (transitionState as TransitionState.Transition).fromScene
val toScene = transitionState.toScene
- if (fromScene == toScene) {
- check(fromScene == scene)
- return true
- }
val fromReady = layoutImpl.isSceneReady(fromScene)
val toReady = layoutImpl.isSceneReady(toScene)
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/ObservableTransitionState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/ObservableTransitionState.kt
index 1b79dbdee510..983cff83ed57 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/ObservableTransitionState.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/ObservableTransitionState.kt
@@ -73,17 +73,13 @@ fun SceneTransitionLayoutState.observableTransitionState(): Flow<ObservableTrans
when (val state = transitionState) {
is TransitionState.Idle -> ObservableTransitionState.Idle(state.currentScene)
is TransitionState.Transition -> {
- if (state.fromScene == state.toScene) {
- ObservableTransitionState.Idle(state.currentScene)
- } else {
- ObservableTransitionState.Transition(
- fromScene = state.fromScene,
- toScene = state.toScene,
- progress = snapshotFlow { state.progress },
- isInitiatedByUserInput = state.isInitiatedByUserInput,
- isUserInputOngoing = snapshotFlow { state.isUserInputOngoing },
- )
- }
+ ObservableTransitionState.Transition(
+ fromScene = state.fromScene,
+ toScene = state.toScene,
+ progress = snapshotFlow { state.progress },
+ isInitiatedByUserInput = state.isInitiatedByUserInput,
+ isUserInputOngoing = snapshotFlow { state.isUserInputOngoing },
+ )
}
}
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt
index 03f37d0c9bda..91decf4d8b7e 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt
@@ -49,8 +49,12 @@ internal class SceneGestureHandler(
layoutImpl.state.transitionState = value
}
- internal var swipeTransition: SwipeTransition = SwipeTransition(currentScene, currentScene, 1f)
- private set
+ private var _swipeTransition: SwipeTransition? = null
+ internal var swipeTransition: SwipeTransition
+ get() = _swipeTransition ?: error("SwipeTransition needs to be initialized")
+ set(value) {
+ _swipeTransition = value
+ }
private fun updateTransition(newTransition: SwipeTransition, force: Boolean = false) {
if (isDrivingTransition || force) transitionState = newTransition
@@ -61,7 +65,7 @@ internal class SceneGestureHandler(
get() = layoutImpl.scene(transitionState.currentScene)
internal val isDrivingTransition
- get() = transitionState == swipeTransition
+ get() = transitionState == _swipeTransition
/**
* The velocity threshold at which the intent of the user is to swipe up or down. It is the same
@@ -82,12 +86,15 @@ internal class SceneGestureHandler(
private var actionDownOrRight: UserAction? = null
private var actionUpOrLeftNoEdge: UserAction? = null
private var actionDownOrRightNoEdge: UserAction? = null
+ private var upOrLeftScene: SceneKey? = null
+ private var downOrRightScene: SceneKey? = null
internal fun onDragStarted(pointersDown: Int, startedPosition: Offset?, overSlop: Float) {
if (isDrivingTransition) {
// This [transition] was already driving the animation: simply take over it.
// Stop animating and start from where the current offset.
swipeTransition.cancelOffsetAnimation()
+ updateTargetScenes(swipeTransition._fromScene)
return
}
@@ -105,11 +112,8 @@ internal class SceneGestureHandler(
val fromScene = currentScene
setCurrentActions(fromScene, startedPosition, pointersDown)
- if (fromScene.upOrLeft() == null && fromScene.downOrRight() == null) {
- return
- }
-
- val (targetScene, distance) = fromScene.findTargetSceneAndDistance(overSlop)
+ val (targetScene, distance) =
+ findTargetSceneAndDistance(fromScene, overSlop, updateScenes = true) ?: return
updateTransition(SwipeTransition(fromScene, targetScene, distance), force = true)
}
@@ -179,16 +183,21 @@ internal class SceneGestureHandler(
val (fromScene, acceleratedOffset) =
computeFromSceneConsideringAcceleratedSwipe(swipeTransition)
- swipeTransition.dragOffset += acceleratedOffset
- // Compute the target scene depending on the current offset.
+ val isNewFromScene = fromScene.key != swipeTransition.fromScene
val (targetScene, distance) =
- fromScene.findTargetSceneAndDistance(swipeTransition.dragOffset)
+ findTargetSceneAndDistance(
+ fromScene,
+ swipeTransition.dragOffset,
+ updateScenes = isNewFromScene,
+ )
+ ?: run {
+ onDragStopped(delta, true)
+ return
+ }
+ swipeTransition.dragOffset += acceleratedOffset
- // TODO(b/290184746): support long scroll A => B => C? especially for non fullscreen scenes
- if (
- fromScene.key != swipeTransition.fromScene || targetScene.key != swipeTransition.toScene
- ) {
+ if (isNewFromScene || targetScene.key != swipeTransition.toScene) {
updateTransition(
SwipeTransition(fromScene, targetScene, distance).apply {
this.dragOffset = swipeTransition.dragOffset
@@ -197,6 +206,11 @@ internal class SceneGestureHandler(
}
}
+ private fun updateTargetScenes(fromScene: Scene) {
+ upOrLeftScene = fromScene.upOrLeft()
+ downOrRightScene = fromScene.downOrRight()
+ }
+
/**
* Change fromScene in the case where the user quickly swiped multiple times in the same
* direction to accelerate the transition from A => B then B => C.
@@ -214,37 +228,71 @@ internal class SceneGestureHandler(
val absoluteDistance = swipeTransition.distance.absoluteValue
// If the swipe was not committed, don't do anything.
- if (fromScene == toScene || swipeTransition._currentScene != toScene) {
+ if (swipeTransition._currentScene != toScene) {
return Pair(fromScene, 0f)
}
// If the offset is past the distance then let's change fromScene so that the user can swipe
// to the next screen or go back to the previous one.
val offset = swipeTransition.dragOffset
- return if (offset <= -absoluteDistance && fromScene.upOrLeft() == toScene.key) {
+ return if (offset <= -absoluteDistance && upOrLeftScene == toScene.key) {
Pair(toScene, absoluteDistance)
- } else if (offset >= absoluteDistance && fromScene.downOrRight() == toScene.key) {
+ } else if (offset >= absoluteDistance && downOrRightScene == toScene.key) {
Pair(toScene, -absoluteDistance)
} else {
Pair(fromScene, 0f)
}
}
- // TODO(b/290184746): there are two bugs here:
- // 1. if both upOrLeft and downOrRight become `null` during a transition this will crash
- // 2. if one of them changes during a transition, the transition will jump cut to the new target
- private inline fun Scene.findTargetSceneAndDistance(
- directionOffset: Float
- ): Pair<Scene, Float> {
- val upOrLeft = upOrLeft()
- val downOrRight = downOrRight()
- val absoluteDistance = getAbsoluteDistance()
+ /**
+ * Returns the target scene and distance from [fromScene] in the direction [directionOffset].
+ *
+ * @param fromScene the scene from which we look for the target
+ * @param directionOffset signed float that indicates the direction. Positive is down or right
+ * negative is up or left.
+ * @param updateScenes whether the target scenes should be updated to the current values held in
+ * the Scenes map. Usually we don't want to update them while doing a drag, because this could
+ * change the target scene (jump cutting) to a different scene, when some system state changed
+ * the targets the background. However, an update is needed any time we calculate the targets
+ * for a new fromScene.
+ * @return null when there are no targets in either direction. If one direction is null and you
+ * drag into the null direction this function will return the opposite direction, assuming
+ * that the users intention is to start the drag into the other direction eventually. If
+ * [directionOffset] is 0f and both direction are available, it will default to
+ * [upOrLeftScene].
+ */
+ private inline fun findTargetSceneAndDistance(
+ fromScene: Scene,
+ directionOffset: Float,
+ updateScenes: Boolean,
+ ): Pair<Scene, Float>? {
+ if (updateScenes) updateTargetScenes(fromScene)
+ val absoluteDistance = fromScene.getAbsoluteDistance()
// Compute the target scene depending on the current offset.
- return if ((directionOffset < 0f && upOrLeft != null) || downOrRight == null) {
- Pair(layoutImpl.scene(upOrLeft!!), -absoluteDistance)
- } else {
- Pair(layoutImpl.scene(downOrRight), absoluteDistance)
+ return when {
+ upOrLeftScene == null && downOrRightScene == null -> null
+ (directionOffset < 0f && upOrLeftScene != null) || downOrRightScene == null ->
+ Pair(layoutImpl.scene(upOrLeftScene!!), -absoluteDistance)
+ else -> Pair(layoutImpl.scene(downOrRightScene!!), absoluteDistance)
+ }
+ }
+
+ /**
+ * A strict version of [findTargetSceneAndDistance] that will return null when there is no Scene
+ * in [directionOffset] direction
+ */
+ private inline fun findTargetSceneAndDistanceStrict(
+ fromScene: Scene,
+ directionOffset: Float,
+ ): Pair<Scene, Float>? {
+ val absoluteDistance = fromScene.getAbsoluteDistance()
+ return when {
+ directionOffset > 0f ->
+ upOrLeftScene?.let { Pair(layoutImpl.scene(it), -absoluteDistance) }
+ directionOffset < 0f ->
+ downOrRightScene?.let { Pair(layoutImpl.scene(it), absoluteDistance) }
+ else -> null
}
}
@@ -311,20 +359,21 @@ internal class SceneGestureHandler(
val startFromIdlePosition = swipeTransition.dragOffset == 0f
if (startFromIdlePosition) {
- // If there is a next scene, we start the overscroll animation.
- val (targetScene, distance) = fromScene.findTargetSceneAndDistance(velocity)
- val isValidTarget = distance != 0f && targetScene.key != fromScene.key
- if (isValidTarget) {
- updateTransition(
- SwipeTransition(fromScene, targetScene, distance).apply {
- _currentScene = swipeTransition._currentScene
+ // If there is a target scene, we start the overscroll animation.
+ val (targetScene, distance) =
+ findTargetSceneAndDistanceStrict(fromScene, velocity)
+ ?: run {
+ // We will not animate
+ transitionState = TransitionState.Idle(fromScene.key)
+ return
}
- )
- animateTo(targetScene = fromScene, targetOffset = 0f)
- } else {
- // We will not animate
- transitionState = TransitionState.Idle(fromScene.key)
- }
+
+ updateTransition(
+ SwipeTransition(fromScene, targetScene, distance).apply {
+ _currentScene = swipeTransition._currentScene
+ }
+ )
+ animateTo(targetScene = fromScene, targetOffset = 0f)
} else {
// We were between two scenes: animate to the initial scene.
animateTo(targetScene = fromScene, targetOffset = 0f)
@@ -410,15 +459,11 @@ internal class SceneGestureHandler(
* above or to the left of [toScene].
*/
val distance: Float
- ) : TransitionState.Transition {
+ ) : TransitionState.Transition(_fromScene.key, _toScene.key) {
var _currentScene by mutableStateOf(_fromScene)
override val currentScene: SceneKey
get() = _currentScene.key
- override val fromScene: SceneKey = _fromScene.key
-
- override val toScene: SceneKey = _toScene.key
-
override val progress: Float
get() {
val offset = if (isAnimatingOffset) offsetAnimatable.value else dragOffset
@@ -494,9 +539,9 @@ private class SceneDraggableHandler(
}
internal class SceneNestedScrollHandler(
- private val gestureHandler: SceneGestureHandler,
- private val topOrLeftBehavior: NestedScrollBehavior,
- private val bottomOrRightBehavior: NestedScrollBehavior,
+ private val gestureHandler: SceneGestureHandler,
+ private val topOrLeftBehavior: NestedScrollBehavior,
+ private val bottomOrRightBehavior: NestedScrollBehavior,
) : NestedScrollHandler {
override val connection: PriorityNestedScrollConnection = nestedScrollConnection()
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
index 02ddccbc051b..00e33e24c41e 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
@@ -172,7 +172,7 @@ internal class SceneTransitionLayoutImpl(
val width: Int
val height: Int
val state = state.transitionState
- if (state !is TransitionState.Transition || state.fromScene == state.toScene) {
+ if (state !is TransitionState.Transition) {
width = placeable.width
height = placeable.height
} else {
@@ -232,10 +232,7 @@ internal class SceneTransitionLayoutImpl(
is TransitionState.Idle -> drawContent()
is TransitionState.Transition -> {
// Don't draw scenes that are not ready yet.
- if (
- readyScenes.containsKey(key) ||
- state.fromScene == state.toScene
- ) {
+ if (readyScenes.containsKey(key)) {
drawContent()
}
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
index f48e9147eef4..623725582a9d 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
@@ -39,11 +39,6 @@ class SceneTransitionLayoutState(initialScene: SceneKey) {
fun isTransitioning(from: SceneKey? = null, to: SceneKey? = null): Boolean {
val transition = transitionState as? TransitionState.Transition ?: return false
- // TODO(b/310915136): Remove this check.
- if (transition.fromScene == transition.toScene) {
- return false
- }
-
return (from == null || transition.fromScene == from) &&
(to == null || transition.toScene == to)
}
@@ -71,32 +66,30 @@ sealed interface TransitionState {
/** No transition/animation is currently running. */
data class Idle(override val currentScene: SceneKey) : TransitionState
- /**
- * There is a transition animating between two scenes.
- *
- * Important note: [fromScene] and [toScene] might be the same, in which case this [Transition]
- * should be treated the same as [Idle]. This is designed on purpose so that a [Transition] can
- * be started without knowing in advance where it is transitioning to, making the logic of
- * [swipeToScene] easier to reason about.
- */
- interface Transition : TransitionState {
- /** The scene this transition is starting from. */
- val fromScene: SceneKey
+ /** There is a transition animating between two scenes. */
+ abstract class Transition(
+ /** The scene this transition is starting from. Can't be the same as toScene */
+ val fromScene: SceneKey,
- /** The scene this transition is going to. */
+ /** The scene this transition is going to. Can't be the same as fromScene */
val toScene: SceneKey
+ ) : TransitionState {
+
+ init {
+ check(fromScene != toScene)
+ }
/**
* The progress of the transition. This is usually in the `[0; 1]` range, but it can also be
* less than `0` or greater than `1` when using transitions with a spring AnimationSpec or
* when flinging quickly during a swipe gesture.
*/
- val progress: Float
+ abstract val progress: Float
/** Whether the transition was triggered by user input rather than being programmatic. */
- val isInitiatedByUserInput: Boolean
+ abstract val isInitiatedByUserInput: Boolean
/** Whether user input is currently driving the transition. */
- val isUserInputOngoing: Boolean
+ abstract val isUserInputOngoing: Boolean
}
}
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt
index 34afc4c91d4c..e6224df649ca 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt
@@ -58,16 +58,22 @@ class SceneGestureHandlerTest {
private val layoutState: SceneTransitionLayoutState =
SceneTransitionLayoutState(internalCurrentScene)
+ val mutableUserActionsA: MutableMap<UserAction, SceneKey> =
+ mutableMapOf(Swipe.Up to SceneB, Swipe.Down to SceneC)
+
+ val mutableUserActionsB: MutableMap<UserAction, SceneKey> =
+ mutableMapOf(Swipe.Up to SceneC, Swipe.Down to SceneA)
+
private val scenesBuilder: SceneTransitionLayoutScope.() -> Unit = {
scene(
key = SceneA,
- userActions = mapOf(Swipe.Up to SceneB, Swipe.Down to SceneC),
+ userActions = mutableUserActionsA,
) {
Text("SceneA")
}
scene(
key = SceneB,
- userActions = mapOf(Swipe.Up to SceneC, Swipe.Down to SceneA),
+ userActions = mutableUserActionsB,
) {
Text("SceneB")
}
@@ -412,6 +418,70 @@ class SceneGestureHandlerTest {
}
@Test
+ fun onAccelaratedScrollBothTargetsBecomeNull_settlesToIdle() = runGestureTest {
+ draggable.onDragStarted()
+ draggable.onDelta(up(0.2f))
+
+ draggable.onDelta(up(0.2f))
+ draggable.onDragStopped(velocity = -velocityThreshold)
+ assertTransition(currentScene = SceneB, fromScene = SceneA, toScene = SceneB)
+
+ mutableUserActionsA.remove(Swipe.Up)
+ mutableUserActionsA.remove(Swipe.Down)
+ mutableUserActionsB.remove(Swipe.Up)
+ mutableUserActionsB.remove(Swipe.Down)
+
+ // start accelaratedScroll and scroll over to B -> null
+ draggable.onDragStarted()
+ draggable.onDelta(up(0.5f))
+ draggable.onDelta(up(0.5f))
+
+ // here onDragStopped is already triggered, but subsequent onDelta/onDragStopped calls may
+ // still be called. Make sure that they don't crash or change the scene
+ draggable.onDelta(up(0.5f))
+ draggable.onDragStopped(0f)
+
+ advanceUntilIdle()
+ assertIdle(SceneB)
+
+ // These events can still come in after the animation has settled
+ draggable.onDelta(up(0.5f))
+ draggable.onDragStopped(0f)
+ assertIdle(SceneB)
+ }
+
+ @Test
+ fun onDragTargetsChanged_targetStaysTheSame() = runGestureTest {
+ draggable.onDragStarted(up(0.1f))
+ assertTransition(fromScene = SceneA, toScene = SceneB, progress = 0.1f)
+
+ mutableUserActionsA[Swipe.Up] = SceneC
+ draggable.onDelta(up(0.1f))
+ // target stays B even though UserActions changed
+ assertTransition(fromScene = SceneA, toScene = SceneB, progress = 0.2f)
+ draggable.onDragStopped(down(0.1f))
+ advanceUntilIdle()
+
+ // now target changed to C for new drag
+ draggable.onDragStarted(up(0.1f))
+ assertTransition(fromScene = SceneA, toScene = SceneC, progress = 0.1f)
+ }
+
+ @Test
+ fun onDragTargetsChanged_targetsChangeWhenStartingNewDrag() = runGestureTest {
+ draggable.onDragStarted(up(0.1f))
+ assertTransition(fromScene = SceneA, toScene = SceneB, progress = 0.1f)
+
+ mutableUserActionsA[Swipe.Up] = SceneC
+ draggable.onDelta(up(0.1f))
+ draggable.onDragStopped(down(0.1f))
+
+ // now target changed to C for new drag that started before previous drag settled to Idle
+ draggable.onDragStarted(up(0.1f))
+ assertTransition(fromScene = SceneA, toScene = SceneC, progress = 0.3f)
+ }
+
+ @Test
fun startGestureDuringAnimatingOffset_shouldImmediatelyStopTheAnimation() = runGestureTest {
draggable.onDragStarted()
assertTransition(currentScene = SceneA)
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt
index 94c51ca50667..eeda8d46adfa 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt
@@ -39,18 +39,6 @@ class SceneTransitionLayoutStateTest {
}
@Test
- fun isTransitioningTo_fromSceneEqualToToScene() {
- val state = SceneTransitionLayoutState(TestScenes.SceneA)
- state.transitionState = transition(from = TestScenes.SceneA, to = TestScenes.SceneA)
-
- assertThat(state.isTransitioning()).isFalse()
- assertThat(state.isTransitioning(from = TestScenes.SceneA)).isFalse()
- assertThat(state.isTransitioning(to = TestScenes.SceneB)).isFalse()
- assertThat(state.isTransitioning(from = TestScenes.SceneA, to = TestScenes.SceneB))
- .isFalse()
- }
-
- @Test
fun isTransitioningTo_transition() {
val state = SceneTransitionLayoutState(TestScenes.SceneA)
state.transitionState = transition(from = TestScenes.SceneA, to = TestScenes.SceneB)
@@ -64,10 +52,8 @@ class SceneTransitionLayoutStateTest {
}
private fun transition(from: SceneKey, to: SceneKey): TransitionState.Transition {
- return object : TransitionState.Transition {
+ return object : TransitionState.Transition(from, to) {
override val currentScene: SceneKey = from
- override val fromScene: SceneKey = from
- override val toScene: SceneKey = to
override val progress: Float = 0f
override val isInitiatedByUserInput: Boolean = false
override val isUserInputOngoing: Boolean = false
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/preview/KeyguardRemotePreviewManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/preview/KeyguardRemotePreviewManagerTest.kt
new file mode 100644
index 000000000000..e8504563b4ae
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/preview/KeyguardRemotePreviewManagerTest.kt
@@ -0,0 +1,40 @@
+package com.android.systemui.keyguard.ui.preview
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class KeyguardRemotePreviewManagerTest : SysuiTestCase() {
+
+ private val testDispatcher = StandardTestDispatcher()
+ private val testScope = TestScope(testDispatcher)
+
+ @Test
+ fun onDestroy_clearsReferencesToRenderer() =
+ testScope.runTest {
+ val renderer = mock<KeyguardPreviewRenderer>()
+ val onDestroy: (PreviewLifecycleObserver) -> Unit = {}
+
+ val observer = PreviewLifecycleObserver(this, testDispatcher, renderer, onDestroy)
+
+ // Precondition check.
+ assertThat(observer.renderer).isNotNull()
+ assertThat(observer.onDestroy).isNotNull()
+
+ observer.onDestroy()
+
+ // The verification checks renderer/requestDestruction lambda because they-re
+ // non-singletons which can't leak KeyguardPreviewRenderer.
+ assertThat(observer.renderer).isNull()
+ assertThat(observer.onDestroy).isNull()
+ }
+}
diff --git a/packages/SystemUI/res/drawable/arrow_pointing_down.xml b/packages/SystemUI/res/drawable/arrow_pointing_down.xml
new file mode 100644
index 000000000000..be39683cd78d
--- /dev/null
+++ b/packages/SystemUI/res/drawable/arrow_pointing_down.xml
@@ -0,0 +1,26 @@
+<!--
+ ~ 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.
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M5.41,7.59L4,9l8,8 8,-8 -1.41,-1.41L12,14.17" />
+</vector> \ No newline at end of file
diff --git a/packages/SystemUI/res/layout/record_issue_dialog.xml b/packages/SystemUI/res/layout/record_issue_dialog.xml
new file mode 100644
index 000000000000..53ad9f157a2e
--- /dev/null
+++ b/packages/SystemUI/res/layout/record_issue_dialog.xml
@@ -0,0 +1,81 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:orientation="vertical" >
+
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:textAppearance="@style/TextAppearance.Dialog.Body.Message"
+ android:text="@string/qs_record_issue_dropdown_header" />
+
+ <Button
+ android:id="@+id/issue_type_button"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/qs_record_issue_dropdown_prompt"
+ android:lines="1"
+ android:drawableRight="@drawable/arrow_pointing_down"
+ android:layout_marginTop="@dimen/qqs_layout_margin_top"
+ android:focusable="false"
+ android:clickable="true" />
+
+ <!-- Screen Record Switch -->
+ <LinearLayout
+ android:id="@+id/screenrecord_switch_container"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="@dimen/qqs_layout_margin_top"
+ android:orientation="horizontal">
+
+ <ImageView
+ android:layout_width="@dimen/screenrecord_option_icon_size"
+ android:layout_height="@dimen/screenrecord_option_icon_size"
+ android:layout_weight="0"
+ android:src="@drawable/ic_screenrecord"
+ app:tint="?androidprv:attr/materialColorOnSurface"
+ android:layout_gravity="center"
+ android:layout_marginEnd="@dimen/screenrecord_option_padding" />
+
+ <TextView
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:minHeight="@dimen/screenrecord_option_icon_size"
+ android:layout_weight="1"
+ android:layout_gravity="fill_vertical"
+ android:gravity="center"
+ android:text="@string/quick_settings_screen_record_label"
+ android:textAppearance="@style/TextAppearance.Dialog.Body.Message"
+ android:importantForAccessibility="no"/>
+
+ <Switch
+ android:id="@+id/screenrecord_switch"
+ android:layout_width="wrap_content"
+ android:minHeight="@dimen/screenrecord_option_icon_size"
+ android:layout_height="wrap_content"
+ android:gravity="center"
+ android:layout_gravity="fill_vertical"
+ android:layout_weight="0"
+ android:contentDescription="@string/quick_settings_screen_record_label" />
+ </LinearLayout>
+</LinearLayout> \ No newline at end of file
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 13d2feae1d9b..e10925d551e2 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -831,6 +831,20 @@
<!-- QuickSettings: Text to prompt the user to stop an ongoing recording [CHAR LIMIT=20] -->
<string name="qs_record_issue_stop">Stop</string>
+ <!-- QuickSettings: Issue Type Drop down options in Record Issue Start Dialog [CHAR LIMIT=50] -->
+ <string name="qs_record_issue_dropdown_header">What part of your device experience was affected?</string>
+ <!-- QuickSettings: Issue Type Drop down prompt in Record Issue Start Dialog [CHAR LIMIT=30] -->
+ <string name="qs_record_issue_dropdown_prompt">Select issue type</string>
+ <!-- QuickSettings: Screen record switch label in Record Issue Start Dialog [CHAR LIMIT=20] -->
+ <string name="qs_record_issue_dropdown_screenrecord">Screen record</string>
+
+ <!-- QuickSettings: Issue Type Drop down choices list in Record Issue Start Dialog [CHAR LIMIT=30] -->
+ <string-array name="qs_record_issue_types">
+ <item>Performance</item>
+ <item>User Interface</item>
+ <item>Battery</item>
+ </string-array>
+
<!-- QuickSettings: Label for the toggle that controls whether One-handed mode is enabled. [CHAR LIMIT=NONE] -->
<string name="quick_settings_onehanded_label">One-handed mode</string>
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt
index eceaf6c3c4fd..c090688dea73 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt
@@ -187,7 +187,8 @@ constructor(
faceManager?.sensorPropertiesInternal?.firstOrNull()?.supportsFaceDetection ?: false
private val _isAuthRunning = MutableStateFlow(false)
- override val isAuthRunning: StateFlow<Boolean> = _isAuthRunning
+ override val isAuthRunning: StateFlow<Boolean>
+ get() = _isAuthRunning
private val keyguardSessionId: InstanceId?
get() = sessionTracker.getSessionId(StatusBarManager.SESSION_KEYGUARD)
@@ -253,13 +254,6 @@ constructor(
)
.andAllFlows("canFaceAuthRun", faceAuthLog)
.flowOn(backgroundDispatcher)
- .onEach {
- faceAuthLogger.canFaceAuthRunChanged(it)
- if (!it) {
- // Cancel currently running auth if any of the gating checks are false.
- cancel()
- }
- }
.stateIn(applicationScope, SharingStarted.Eagerly, false)
// Face detection can run only when lockscreen bypass is enabled
@@ -287,12 +281,9 @@ constructor(
)
.andAllFlows("canFaceDetectRun", faceDetectLog)
.flowOn(backgroundDispatcher)
- .onEach {
- if (!it) {
- cancelDetection()
- }
- }
.stateIn(applicationScope, SharingStarted.Eagerly, false)
+ observeFaceAuthGatingChecks()
+ observeFaceDetectGatingChecks()
observeFaceAuthResettingConditions()
listenForSchedulingWatchdog()
processPendingAuthRequests()
@@ -347,6 +338,17 @@ constructor(
pendingAuthenticateRequest.value = null
}
+ private fun observeFaceDetectGatingChecks() {
+ canRunDetection
+ .onEach {
+ if (!it) {
+ cancelDetection()
+ }
+ }
+ .flowOn(mainDispatcher)
+ .launchIn(applicationScope)
+ }
+
private fun isUdfps() =
deviceEntryFingerprintAuthRepository.availableFpSensorType.map {
it == BiometricType.UNDER_DISPLAY_FINGERPRINT
@@ -405,6 +407,20 @@ constructor(
)
}
+ private fun observeFaceAuthGatingChecks() {
+ canRunFaceAuth
+ .onEach {
+ faceAuthLogger.canFaceAuthRunChanged(it)
+ if (!it) {
+ // Cancel currently running auth if any of the gating checks are false.
+ faceAuthLogger.cancellingFaceAuth()
+ cancel()
+ }
+ }
+ .flowOn(mainDispatcher)
+ .launchIn(applicationScope)
+ }
+
private val faceAuthCallback =
object : FaceManager.AuthenticationCallback() {
override fun onAuthenticationFailed() {
@@ -539,7 +555,7 @@ constructor(
authenticate(it.uiEvent, it.fallbackToDetection)
}
}
- .flowOn(backgroundDispatcher)
+ .flowOn(mainDispatcher)
.launchIn(applicationScope)
}
@@ -635,7 +651,6 @@ constructor(
override fun cancel() {
if (authCancellationSignal == null) return
- faceAuthLogger.cancellingFaceAuth()
authCancellationSignal?.cancel()
cancelNotReceivedHandlerJob?.cancel()
cancelNotReceivedHandlerJob =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt
index fb20000471a2..e3f47397eca3 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt
@@ -31,7 +31,6 @@ import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
import com.android.systemui.keyguard.data.repository.DeviceEntryFaceAuthRepository
@@ -72,7 +71,6 @@ constructor(
private val context: Context,
@Application private val applicationScope: CoroutineScope,
@Main private val mainDispatcher: CoroutineDispatcher,
- @Background private val backgroundDispatcher: CoroutineDispatcher,
private val repository: DeviceEntryFaceAuthRepository,
private val primaryBouncerInteractor: Lazy<PrimaryBouncerInteractor>,
private val alternateBouncerInteractor: AlternateBouncerInteractor,
@@ -109,7 +107,6 @@ constructor(
fallbackToDetect = false
)
}
- .flowOn(backgroundDispatcher)
.launchIn(applicationScope)
alternateBouncerInteractor.isVisible
@@ -121,7 +118,6 @@ constructor(
fallbackToDetect = false
)
}
- .flowOn(backgroundDispatcher)
.launchIn(applicationScope)
merge(
@@ -150,7 +146,6 @@ constructor(
fallbackToDetect = true
)
}
- .flowOn(backgroundDispatcher)
.launchIn(applicationScope)
deviceEntryFingerprintAuthRepository.isLockedOut
@@ -163,7 +158,6 @@ constructor(
}
}
}
- .flowOn(backgroundDispatcher)
.launchIn(applicationScope)
// User switching should stop face auth and then when it is complete we should trigger face
@@ -187,7 +181,6 @@ constructor(
)
}
}
- .flowOn(backgroundDispatcher)
.launchIn(applicationScope)
}
@@ -302,7 +295,6 @@ constructor(
trustManager.clearAllBiometricRecognized(BiometricSourceType.FACE, userInfo.id)
}
}
- .flowOn(backgroundDispatcher)
.onEach { (isAuthenticated, _) ->
listeners.forEach { it.onAuthenticatedChanged(isAuthenticated) }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardRemotePreviewManager.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardRemotePreviewManager.kt
index acfd3b0bcf57..24240dfe7402 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardRemotePreviewManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardRemotePreviewManager.kt
@@ -30,6 +30,7 @@ import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.shared.quickaffordance.shared.model.KeyguardPreviewConstants
+import com.android.systemui.util.kotlin.logD
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
@@ -58,11 +59,14 @@ constructor(
observer =
PreviewLifecycleObserver(
- renderer,
applicationScope,
mainDispatcher,
+ renderer,
::destroyObserver,
)
+
+ logD(TAG) { "Created observer $observer" }
+
// Destroy any previous renderer associated with this token.
activePreviews[renderer.id]?.let { destroyObserver(it) }
activePreviews[renderer.id] = observer
@@ -80,6 +84,8 @@ constructor(
observer,
)
)
+ // NOTE: The process on the other side can retain messenger indefinitely.
+ // (e.g. GC might not trigger and cleanup the reference)
val msg = Message.obtain()
msg.replyTo = messenger
result.putParcelable(KEY_PREVIEW_CALLBACK, msg)
@@ -99,57 +105,84 @@ constructor(
}
}
- private class PreviewLifecycleObserver(
- private val renderer: KeyguardPreviewRenderer,
- private val scope: CoroutineScope,
- private val mainDispatcher: CoroutineDispatcher,
- private val requestDestruction: (PreviewLifecycleObserver) -> Unit,
- ) : Handler.Callback, IBinder.DeathRecipient {
+ companion object {
+ internal const val TAG = "KeyguardRemotePreviewManager"
+ @VisibleForTesting const val KEY_PREVIEW_SURFACE_PACKAGE = "surface_package"
+ @VisibleForTesting const val KEY_PREVIEW_CALLBACK = "callback"
+ }
+}
- private var isDestroyedOrDestroying = false
+/**
+ * Handles messages from the other process and handles cleanup.
+ *
+ * NOTE: The other process might hold on to reference of this class indefinitely. It's entirely
+ * possible that GC won't trigger and we'll leak this for all times even if [onDestroy] was called.
+ * This helps make sure no non-Singleton objects are retained beyond destruction to prevent leaks.
+ */
+@VisibleForTesting(VisibleForTesting.PRIVATE)
+class PreviewLifecycleObserver(
+ private val scope: CoroutineScope,
+ private val mainDispatcher: CoroutineDispatcher,
+ renderer: KeyguardPreviewRenderer,
+ onDestroy: (PreviewLifecycleObserver) -> Unit,
+) : Handler.Callback, IBinder.DeathRecipient {
+
+ private var isDestroyedOrDestroying = false
+ // These two are null after destruction
+ @VisibleForTesting var renderer: KeyguardPreviewRenderer?
+ @VisibleForTesting var onDestroy: ((PreviewLifecycleObserver) -> Unit)?
+
+ init {
+ this.renderer = renderer
+ this.onDestroy = onDestroy
+ }
- override fun handleMessage(message: Message): Boolean {
- if (isDestroyedOrDestroying) {
- return true
- }
+ override fun handleMessage(message: Message): Boolean {
+ if (isDestroyedOrDestroying) {
+ return true
+ }
- when (message.what) {
- KeyguardPreviewConstants.MESSAGE_ID_SLOT_SELECTED -> {
- message.data.getString(KeyguardPreviewConstants.KEY_SLOT_ID)?.let { slotId ->
- renderer.onSlotSelected(slotId = slotId)
- }
+ when (message.what) {
+ KeyguardPreviewConstants.MESSAGE_ID_SLOT_SELECTED -> {
+ message.data.getString(KeyguardPreviewConstants.KEY_SLOT_ID)?.let { slotId ->
+ checkNotNull(renderer).onSlotSelected(slotId = slotId)
}
- KeyguardPreviewConstants.MESSAGE_ID_HIDE_SMART_SPACE -> {
- renderer.hideSmartspace(
+ }
+ KeyguardPreviewConstants.MESSAGE_ID_HIDE_SMART_SPACE -> {
+ checkNotNull(renderer)
+ .hideSmartspace(
message.data.getBoolean(KeyguardPreviewConstants.KEY_HIDE_SMART_SPACE)
)
- }
- else -> requestDestruction(this)
}
-
- return true
+ else -> checkNotNull(onDestroy).invoke(this)
}
- override fun binderDied() {
- requestDestruction(this)
+ return true
+ }
+
+ override fun binderDied() {
+ onDestroy?.invoke(this)
+ }
+
+ fun onDestroy(): Pair<IBinder?, Int>? {
+ if (isDestroyedOrDestroying) {
+ return null
}
- fun onDestroy(): Pair<IBinder?, Int>? {
- if (isDestroyedOrDestroying) {
- return null
- }
+ logD(TAG) { "Destroying $this" }
- isDestroyedOrDestroying = true
- val hostToken = renderer.hostToken
+ isDestroyedOrDestroying = true
+ return renderer?.let { rendererToDestroy ->
+ this.renderer = null
+ this.onDestroy = null
+ val hostToken = rendererToDestroy.hostToken
hostToken?.unlinkToDeath(this, 0)
- scope.launch(mainDispatcher) { renderer.destroy() }
- return renderer.id
+ scope.launch(mainDispatcher) { rendererToDestroy.destroy() }
+ rendererToDestroy.id
}
}
companion object {
private const val TAG = "KeyguardRemotePreviewManager"
- @VisibleForTesting const val KEY_PREVIEW_SURFACE_PACKAGE = "surface_package"
- @VisibleForTesting const val KEY_PREVIEW_CALLBACK = "callback"
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/ColorSchemeTransition.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/ColorSchemeTransition.kt
index 14d4b6800b0a..c87fd14a943d 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/ColorSchemeTransition.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/ColorSchemeTransition.kt
@@ -119,7 +119,7 @@ internal constructor(
::AnimatingColorTransition
)
- val bgColor = context.getColor(com.google.android.material.R.color.material_dynamic_secondary95)
+ val bgColor = context.getColor(com.google.android.material.R.color.material_dynamic_neutral20)
val surfaceColor =
animatingColorTransitionFactory(bgColor, ::surfaceFromScheme) { surfaceColor ->
val colorList = ColorStateList.valueOf(surfaceColor)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt
index a4088f81f062..0434b2d89e32 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt
@@ -16,6 +16,7 @@
package com.android.systemui.qs.tiles
+import android.app.AlertDialog
import android.content.Intent
import android.os.Handler
import android.os.Looper
@@ -24,8 +25,11 @@ import android.text.TextUtils
import android.view.View
import android.widget.Switch
import androidx.annotation.VisibleForTesting
+import com.android.internal.jank.InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN
import com.android.internal.logging.MetricsLogger
import com.android.systemui.Flags.recordIssueQsTile
+import com.android.systemui.animation.DialogCuj
+import com.android.systemui.animation.DialogLaunchAnimator
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.plugins.ActivityStarter
@@ -36,7 +40,11 @@ import com.android.systemui.qs.QSHost
import com.android.systemui.qs.QsEventLogger
import com.android.systemui.qs.logging.QSLogger
import com.android.systemui.qs.tileimpl.QSTileImpl
+import com.android.systemui.recordissue.RecordIssueDialogDelegate
import com.android.systemui.res.R
+import com.android.systemui.statusbar.phone.KeyguardDismissUtil
+import com.android.systemui.statusbar.phone.SystemUIDialog
+import com.android.systemui.statusbar.policy.KeyguardStateController
import javax.inject.Inject
class RecordIssueTile
@@ -50,7 +58,11 @@ constructor(
metricsLogger: MetricsLogger,
statusBarStateController: StatusBarStateController,
activityStarter: ActivityStarter,
- qsLogger: QSLogger
+ qsLogger: QSLogger,
+ private val keyguardDismissUtil: KeyguardDismissUtil,
+ private val keyguardStateController: KeyguardStateController,
+ private val dialogLaunchAnimator: DialogLaunchAnimator,
+ private val sysuiDialogFactory: SystemUIDialog.Factory,
) :
QSTileImpl<QSTile.BooleanState>(
host,
@@ -76,11 +88,41 @@ constructor(
handlesLongClick = false
}
- override fun handleClick(view: View?) {
- isRecording = !isRecording
+ @VisibleForTesting
+ public override fun handleClick(view: View?) {
+ if (isRecording) {
+ isRecording = false
+ } else {
+ mUiHandler.post { showPrompt(view) }
+ }
refreshState()
}
+ private fun showPrompt(view: View?) {
+ val dialog: AlertDialog =
+ RecordIssueDialogDelegate(sysuiDialogFactory) {
+ isRecording = true
+ refreshState()
+ }
+ .createDialog()
+ val dismissAction =
+ ActivityStarter.OnDismissAction {
+ // We animate from the touched view only if we are not on the keyguard, given
+ // that if we are we will dismiss it which will also collapse the shade.
+ if (view != null && !keyguardStateController.isShowing) {
+ dialogLaunchAnimator.showFromView(
+ dialog,
+ view,
+ DialogCuj(CUJ_SHADE_DIALOG_OPEN, TILE_SPEC)
+ )
+ } else {
+ dialog.show()
+ }
+ false
+ }
+ keyguardDismissUtil.executeWhenUnlocked(dismissAction, false, true)
+ }
+
override fun getLongClickIntent(): Intent? = null
@VisibleForTesting
diff --git a/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt
new file mode 100644
index 000000000000..8221c635741b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.recordissue
+
+import android.annotation.SuppressLint
+import android.content.Context
+import android.content.res.ColorStateList
+import android.graphics.Color
+import android.os.Bundle
+import android.view.Gravity
+import android.view.LayoutInflater
+import android.view.WindowManager
+import android.widget.Button
+import android.widget.PopupMenu
+import android.widget.Switch
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.phone.SystemUIDialog
+
+class RecordIssueDialogDelegate(
+ private val factory: SystemUIDialog.Factory,
+ private val onStarted: Runnable
+) : SystemUIDialog.Delegate {
+
+ @SuppressLint("UseSwitchCompatOrMaterialCode") private lateinit var screenRecordSwitch: Switch
+ private lateinit var issueTypeButton: Button
+
+ override fun beforeCreate(dialog: SystemUIDialog, savedInstanceState: Bundle?) {
+ dialog.apply {
+ setView(LayoutInflater.from(context).inflate(R.layout.record_issue_dialog, null))
+ setTitle(context.getString(R.string.qs_record_issue_label))
+ setIcon(R.drawable.qs_record_issue_icon_off)
+ setNegativeButton(R.string.cancel) { _, _ -> dismiss() }
+ setPositiveButton(R.string.qs_record_issue_start) { _, _ ->
+ onStarted.run()
+ dismiss()
+ }
+ }
+ }
+
+ override fun createDialog(): SystemUIDialog = factory.create(this)
+
+ override fun onCreate(dialog: SystemUIDialog, savedInstanceState: Bundle?) {
+ dialog.apply {
+ window?.addPrivateFlags(WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS)
+ window?.setGravity(Gravity.CENTER)
+
+ screenRecordSwitch = requireViewById(R.id.screenrecord_switch)
+ issueTypeButton = requireViewById(R.id.issue_type_button)
+ issueTypeButton.setOnClickListener { onIssueTypeClicked(context) }
+ }
+ }
+
+ private fun onIssueTypeClicked(context: Context) {
+ val selectedCategory = issueTypeButton.text.toString()
+ val popupMenu = PopupMenu(context, issueTypeButton)
+
+ context.resources.getStringArray(R.array.qs_record_issue_types).forEachIndexed { i, cat ->
+ popupMenu.menu.add(0, 0, i, cat).apply {
+ setIcon(R.drawable.arrow_pointing_down)
+ if (selectedCategory != cat) {
+ iconTintList = ColorStateList.valueOf(Color.TRANSPARENT)
+ }
+ }
+ }
+ popupMenu.apply {
+ setOnMenuItemClickListener {
+ issueTypeButton.text = it.title
+ true
+ }
+ setForceShowIcon(true)
+ show()
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
index 756c440eca89..0c5472f0ecfb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
@@ -41,6 +41,7 @@ import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
+import com.android.systemui.log.core.LogLevel;
import com.android.systemui.res.R;
import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
@@ -49,6 +50,7 @@ import dagger.Lazy;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Objects;
+import java.util.function.Consumer;
import javax.inject.Inject;
@@ -210,23 +212,33 @@ public class KeyguardStateControllerImpl implements KeyguardStateController, Dum
private void notifyKeyguardChanged() {
Trace.beginSection("KeyguardStateController#notifyKeyguardChanged");
// Copy the list to allow removal during callback.
- new ArrayList<>(mCallbacks).forEach(Callback::onKeyguardShowingChanged);
+ invokeForEachCallback(Callback::onKeyguardShowingChanged);
Trace.endSection();
}
private void notifyKeyguardFaceAuthEnabledChanged() {
+ invokeForEachCallback(Callback::onFaceEnrolledChanged);
+ }
+
+ private void invokeForEachCallback(Consumer<Callback> consumer) {
// Copy the list to allow removal during callback.
- new ArrayList<>(mCallbacks).forEach(callback -> {
+ ArrayList<Callback> copyOfCallbacks = new ArrayList<>(mCallbacks);
+ for (int i = 0; i < copyOfCallbacks.size(); i++) {
+ Callback callback = copyOfCallbacks.get(i);
+ // Temporary fix for b/315731775, callback is null even though only non-null callbacks
+ // are added to the list by addCallback
if (callback != null) {
- callback.onFaceEnrolledChanged();
+ consumer.accept(callback);
+ } else {
+ mLogger.log("KeyguardStateController callback is null", LogLevel.DEBUG);
}
- });
+ }
}
private void notifyUnlockedChanged() {
Trace.beginSection("KeyguardStateController#notifyUnlockedChanged");
// Copy the list to allow removal during callback.
- new ArrayList<>(mCallbacks).forEach(Callback::onUnlockedChanged);
+ invokeForEachCallback(Callback::onUnlockedChanged);
Trace.endSection();
}
@@ -242,10 +254,7 @@ public class KeyguardStateControllerImpl implements KeyguardStateController, Dum
Trace.traceCounter(Trace.TRACE_TAG_APP, "keyguardFadingAway",
keyguardFadingAway ? 1 : 0);
mKeyguardFadingAway = keyguardFadingAway;
- ArrayList<Callback> callbacks = new ArrayList<>(mCallbacks);
- for (int i = 0; i < callbacks.size(); i++) {
- callbacks.get(i).onKeyguardFadingAwayChanged();
- }
+ invokeForEachCallback(Callback::onKeyguardFadingAwayChanged);
}
}
@@ -359,7 +368,7 @@ public class KeyguardStateControllerImpl implements KeyguardStateController, Dum
Trace.traceCounter(Trace.TRACE_TAG_APP, "keyguardGoingAway",
keyguardGoingAway ? 1 : 0);
mKeyguardGoingAway = keyguardGoingAway;
- new ArrayList<>(mCallbacks).forEach(Callback::onKeyguardGoingAwayChanged);
+ invokeForEachCallback(Callback::onKeyguardGoingAwayChanged);
}
}
@@ -368,7 +377,7 @@ public class KeyguardStateControllerImpl implements KeyguardStateController, Dum
if (mPrimaryBouncerShowing != showing) {
mPrimaryBouncerShowing = showing;
- new ArrayList<>(mCallbacks).forEach(Callback::onPrimaryBouncerShowingChanged);
+ invokeForEachCallback(Callback::onPrimaryBouncerShowingChanged);
}
}
@@ -392,13 +401,13 @@ public class KeyguardStateControllerImpl implements KeyguardStateController, Dum
boolean dismissingFromTouch) {
mDismissAmount = dismissAmount;
mDismissingFromTouch = dismissingFromTouch;
- new ArrayList<>(mCallbacks).forEach(Callback::onKeyguardDismissAmountChanged);
+ invokeForEachCallback(Callback::onKeyguardDismissAmountChanged);
}
@Override
public void setLaunchTransitionFadingAway(boolean fadingAway) {
mLaunchTransitionFadingAway = fadingAway;
- new ArrayList<>(mCallbacks).forEach(Callback::onLaunchTransitionFadingAwayChanged);
+ invokeForEachCallback(Callback::onLaunchTransitionFadingAwayChanged);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/Log.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/Log.kt
new file mode 100644
index 000000000000..2f6c450e924c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/Log.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.util.kotlin
+
+import android.util.Log
+
+/** Logs message at [Log.DEBUG] level. Won't call the lambda if [DEBUG] is not loggable. */
+inline fun logD(tag: String, messageLambda: () -> String) {
+ if (Log.isLoggable(tag, Log.DEBUG)) {
+ Log.d(tag, messageLambda.invoke())
+ }
+}
+
+/** Logs message at [Log.VERBOSE] level. Won't call the lambda if [VERBOSE] is not loggable. */
+inline fun logV(tag: String, messageLambda: () -> String) {
+ if (Log.isLoggable(tag, Log.VERBOSE)) {
+ Log.v(tag, messageLambda.invoke())
+ }
+}
+
+/** Logs message at [Log.INFO] level. Won't call the lambda if [INFO] is not loggable. */
+inline fun logI(tag: String, messageLambda: () -> String) {
+ if (Log.isLoggable(tag, Log.INFO)) {
+ Log.i(tag, messageLambda.invoke())
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt
index 4ab8e28bc232..6eb95bddaf53 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt
@@ -137,7 +137,6 @@ class KeyguardFaceAuthInteractorTest : SysuiTestCase() {
mContext,
testScope.backgroundScope,
dispatcher,
- dispatcher,
faceAuthRepository,
{
PrimaryBouncerInteractor(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RecordIssueTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RecordIssueTileTest.kt
index d8199c527cac..e9714dc524ee 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RecordIssueTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RecordIssueTileTest.kt
@@ -23,6 +23,7 @@ import android.testing.TestableLooper
import androidx.test.filters.SmallTest
import com.android.internal.logging.MetricsLogger
import com.android.systemui.SysuiTestCase
+import com.android.systemui.animation.DialogLaunchAnimator
import com.android.systemui.classifier.FalsingManagerFake
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.statusbar.StatusBarStateController
@@ -30,12 +31,19 @@ import com.android.systemui.qs.QSHost
import com.android.systemui.qs.QsEventLogger
import com.android.systemui.qs.logging.QSLogger
import com.android.systemui.res.R
+import com.android.systemui.statusbar.phone.KeyguardDismissUtil
+import com.android.systemui.statusbar.phone.SystemUIDialog
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.ArgumentMatchers.isA
import org.mockito.Mock
+import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
/**
@@ -53,15 +61,22 @@ class RecordIssueTileTest : SysuiTestCase() {
@Mock private lateinit var statusBarStateController: StatusBarStateController
@Mock private lateinit var activityStarter: ActivityStarter
@Mock private lateinit var qsLogger: QSLogger
+ @Mock private lateinit var keyguardDismissUtil: KeyguardDismissUtil
+ @Mock private lateinit var keyguardStateController: KeyguardStateController
+ @Mock private lateinit var dialogLauncherAnimator: DialogLaunchAnimator
+ @Mock private lateinit var dialogFactory: SystemUIDialog.Factory
+ @Mock private lateinit var dialog: SystemUIDialog
+ private lateinit var testableLooper: TestableLooper
private lateinit var tile: RecordIssueTile
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
whenever(host.context).thenReturn(mContext)
+ whenever(dialogFactory.create(any())).thenReturn(dialog)
- val testableLooper = TestableLooper.get(this)
+ testableLooper = TestableLooper.get(this)
tile =
RecordIssueTile(
host,
@@ -72,7 +87,11 @@ class RecordIssueTileTest : SysuiTestCase() {
metricsLogger,
statusBarStateController,
activityStarter,
- qsLogger
+ qsLogger,
+ keyguardDismissUtil,
+ keyguardStateController,
+ dialogLauncherAnimator,
+ dialogFactory
)
}
@@ -119,4 +138,18 @@ class RecordIssueTileTest : SysuiTestCase() {
assertThat(testState.state).isEqualTo(Tile.STATE_ACTIVE)
}
+
+ @Test
+ fun showPrompt_shouldUseKeyguardDismissUtil_ToShowDialog() {
+ tile.isRecording = false
+ tile.handleClick(null)
+ testableLooper.processAllMessages()
+
+ verify(keyguardDismissUtil)
+ .executeWhenUnlocked(
+ isA(ActivityStarter.OnDismissAction::class.java),
+ eq(false),
+ eq(true)
+ )
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt
new file mode 100644
index 000000000000..bbc59d03ac2b
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.recordissue
+
+import android.app.Dialog
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.widget.Button
+import android.widget.Switch
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.animation.DialogLaunchAnimator
+import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.model.SysUiState
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.phone.SystemUIDialog
+import com.android.systemui.statusbar.phone.SystemUIDialogManager
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.TimeUnit
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.ArgumentMatchers.anyInt
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+class RecordIssueDialogDelegateTest : SysuiTestCase() {
+
+ private lateinit var dialog: SystemUIDialog
+ private lateinit var latch: CountDownLatch
+
+ @Before
+ fun setup() {
+ val dialogFactory =
+ SystemUIDialog.Factory(
+ context,
+ mock<FeatureFlags>(),
+ mock<SystemUIDialogManager>(),
+ mock<SysUiState>().apply {
+ whenever(setFlag(anyInt(), anyBoolean())).thenReturn(this)
+ },
+ mock<BroadcastDispatcher>(),
+ mock<DialogLaunchAnimator>()
+ )
+
+ latch = CountDownLatch(1)
+ dialog = RecordIssueDialogDelegate(dialogFactory) { latch.countDown() }.createDialog()
+ dialog.show()
+ }
+
+ @After
+ fun teardown() {
+ dialog.dismiss()
+ }
+
+ @Test
+ fun dialog_hasCorrectUiElements_afterCreation() {
+ dialog.requireViewById<Switch>(R.id.screenrecord_switch)
+ dialog.requireViewById<Button>(R.id.issue_type_button)
+
+ assertThat(dialog.getButton(Dialog.BUTTON_POSITIVE).text)
+ .isEqualTo(context.getString(R.string.qs_record_issue_start))
+ assertThat(dialog.getButton(Dialog.BUTTON_NEGATIVE).text)
+ .isEqualTo(context.getString(R.string.cancel))
+ }
+
+ @Test
+ fun onStarted_isCalled_afterStartButtonIsClicked() {
+ dialog.getButton(Dialog.BUTTON_POSITIVE).callOnClick()
+ latch.await(1L, TimeUnit.MILLISECONDS)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsActivityTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsActivityTest.java
index 0f33aaf20195..2c7b60660165 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsActivityTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsActivityTest.java
@@ -56,6 +56,7 @@ import com.google.common.util.concurrent.Futures;
import org.junit.After;
import org.junit.Before;
+import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -138,6 +139,7 @@ public final class AppClipsActivityTest extends SysuiTestCase {
}
@Test
+ @Ignore("b/315848285")
public void screenshotDisplayed_userConsented_screenshotExportedSuccessfully() {
ResultReceiver resultReceiver = createResultReceiver((resultCode, data) -> {
assertThat(resultCode).isEqualTo(RESULT_OK);
diff --git a/services/core/java/android/content/pm/PackageManagerInternal.java b/services/core/java/android/content/pm/PackageManagerInternal.java
index 09e79860b45c..136692eb90d5 100644
--- a/services/core/java/android/content/pm/PackageManagerInternal.java
+++ b/services/core/java/android/content/pm/PackageManagerInternal.java
@@ -1426,14 +1426,19 @@ public abstract class PackageManagerInternal {
/**
* Checks if package is quarantined for a specific user.
+ *
+ * @throws PackageManager.NameNotFoundException if the package is not found
*/
- public abstract boolean isPackageQuarantined(@NonNull String packageName,
- @UserIdInt int userId);
+ public abstract boolean isPackageQuarantined(@NonNull String packageName, @UserIdInt int userId)
+ throws PackageManager.NameNotFoundException;
/**
* Checks if package is stopped for a specific user.
+ *
+ * @throws PackageManager.NameNotFoundException if the package is not found
*/
- public abstract boolean isPackageStopped(@NonNull String packageName, @UserIdInt int userId);
+ public abstract boolean isPackageStopped(@NonNull String packageName, @UserIdInt int userId)
+ throws PackageManager.NameNotFoundException;
/**
* Sends the PACKAGE_RESTARTED broadcast.
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 71916843fe0b..df8f17ac9d7c 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -5175,6 +5175,8 @@ public final class ActiveServices {
return null;
}
+ final long startTimeNs = SystemClock.elapsedRealtimeNanos();
+
if (DEBUG_SERVICE) {
Slog.v(TAG_SERVICE, "Bringing up " + r + " " + r.intent + " fg=" + r.fgRequired);
}
@@ -5333,9 +5335,14 @@ public final class ActiveServices {
bringDownServiceLocked(r, enqueueOomAdj);
return msg;
}
+ mAm.mProcessList.getAppStartInfoTracker().handleProcessServiceStart(startTimeNs, app, r,
+ hostingRecord, true);
if (isolated) {
r.isolationHostProc = app;
}
+ } else {
+ mAm.mProcessList.getAppStartInfoTracker().handleProcessServiceStart(startTimeNs, app, r,
+ hostingRecord, false);
}
if (r.fgRequired) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index ac173f3b5b7a..21b2d32cea05 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -1103,9 +1103,51 @@ public class ActivityManagerService extends IActivityManager.Stub
private final ActivityMetricsLaunchObserver mActivityLaunchObserver =
new ActivityMetricsLaunchObserver() {
+
+ @Override
+ public void onIntentStarted(@NonNull Intent intent, long timestampNanos) {
+ synchronized (this) {
+ mProcessList.getAppStartInfoTracker().onIntentStarted(intent, timestampNanos);
+ }
+ }
+
@Override
- public void onActivityLaunched(long id, ComponentName name, int temperature) {
+ public void onIntentFailed(long id) {
+ mProcessList.getAppStartInfoTracker().onIntentFailed(id);
+ }
+
+ @Override
+ public void onActivityLaunched(long id, ComponentName name, int temperature, int userId) {
mAppProfiler.onActivityLaunched();
+ synchronized (ActivityManagerService.this) {
+ ProcessRecord record = null;
+ try {
+ record = getProcessRecordLocked(name.getPackageName(), mContext
+ .getPackageManager().getPackageUidAsUser(name.getPackageName(), 0,
+ userId));
+ } catch (NameNotFoundException nnfe) {
+ // Ignore, record will be lost.
+ }
+ mProcessList.getAppStartInfoTracker().onActivityLaunched(id, name, temperature,
+ record);
+ }
+ }
+
+ @Override
+ public void onActivityLaunchCancelled(long id) {
+ mProcessList.getAppStartInfoTracker().onActivityLaunchCancelled(id);
+ }
+
+ @Override
+ public void onActivityLaunchFinished(long id, ComponentName name, long timestampNanos,
+ int launchMode) {
+ mProcessList.getAppStartInfoTracker().onActivityLaunchFinished(id, name,
+ timestampNanos, launchMode);
+ }
+
+ @Override
+ public void onReportFullyDrawn(long id, long timestampNanos) {
+ mProcessList.getAppStartInfoTracker().onReportFullyDrawn(id, timestampNanos);
}
};
@@ -4488,13 +4530,13 @@ public class ActivityManagerService extends IActivityManager.Stub
@GuardedBy("this")
private void attachApplicationLocked(@NonNull IApplicationThread thread,
int pid, int callingUid, long startSeq) {
-
// Find the application record that is being attached... either via
// the pid if we are running in multiple processes, or just pull the
// next app record if we are emulating process with anonymous threads.
ProcessRecord app;
long startTime = SystemClock.uptimeMillis();
long bindApplicationTimeMillis;
+ long bindApplicationTimeNanos;
if (pid != MY_PID && pid >= 0) {
synchronized (mPidsSelfLocked) {
app = mPidsSelfLocked.get(pid);
@@ -4698,6 +4740,7 @@ public class ActivityManagerService extends IActivityManager.Stub
checkTime(startTime, "attachApplicationLocked: immediately before bindApplication");
bindApplicationTimeMillis = SystemClock.uptimeMillis();
+ bindApplicationTimeNanos = SystemClock.elapsedRealtimeNanos();
mAtmInternal.preBindApplication(app.getWindowProcessController());
final ActiveInstrumentation instr2 = app.getActiveInstrumentation();
if (mPlatformCompat != null) {
@@ -4754,6 +4797,8 @@ public class ActivityManagerService extends IActivityManager.Stub
}
app.setBindApplicationTime(bindApplicationTimeMillis);
+ mProcessList.getAppStartInfoTracker()
+ .reportBindApplicationTimeNanos(app, bindApplicationTimeNanos);
// Make app active after binding application or client may be running requests (e.g
// starting activities) before it is ready.
@@ -9799,12 +9844,12 @@ public class ActivityManagerService extends IActivityManager.Stub
final int uid = enforceDumpPermissionForPackage(packageName, userId, callingUid,
"getHistoricalProcessStartReasons");
if (uid != INVALID_UID) {
- mProcessList.mAppStartInfoTracker.getStartInfo(
+ mProcessList.getAppStartInfoTracker().getStartInfo(
packageName, userId, callingPid, maxNum, results);
}
} else {
// If no package name is given, use the caller's uid as the filter uid.
- mProcessList.mAppStartInfoTracker.getStartInfo(
+ mProcessList.getAppStartInfoTracker().getStartInfo(
packageName, callingUid, callingPid, maxNum, results);
}
return new ParceledListSlice<ApplicationStartInfo>(results);
@@ -9822,7 +9867,7 @@ public class ActivityManagerService extends IActivityManager.Stub
}
final int callingUid = Binder.getCallingUid();
- mProcessList.mAppStartInfoTracker.addStartInfoCompleteListener(listener, callingUid);
+ mProcessList.getAppStartInfoTracker().addStartInfoCompleteListener(listener, callingUid);
}
@@ -9836,7 +9881,7 @@ public class ActivityManagerService extends IActivityManager.Stub
}
final int callingUid = Binder.getCallingUid();
- mProcessList.mAppStartInfoTracker.clearStartInfoCompleteListener(callingUid, true);
+ mProcessList.getAppStartInfoTracker().clearStartInfoCompleteListener(callingUid, true);
}
@Override
@@ -10138,7 +10183,7 @@ public class ActivityManagerService extends IActivityManager.Stub
pw.println();
if (dumpAll) {
pw.println("-------------------------------------------------------------------------------");
- mProcessList.mAppStartInfoTracker.dumpHistoryProcessStartInfo(pw, dumpPackage);
+ mProcessList.getAppStartInfoTracker().dumpHistoryProcessStartInfo(pw, dumpPackage);
pw.println("-------------------------------------------------------------------------------");
mProcessList.mAppExitInfoTracker.dumpHistoryProcessExitInfo(pw, dumpPackage);
}
@@ -10541,7 +10586,7 @@ public class ActivityManagerService extends IActivityManager.Stub
dumpPackage = args[opti];
opti++;
}
- mProcessList.mAppStartInfoTracker.dumpHistoryProcessStartInfo(pw, dumpPackage);
+ mProcessList.getAppStartInfoTracker().dumpHistoryProcessStartInfo(pw, dumpPackage);
} else if ("exit-info".equals(cmd)) {
if (opti < args.length) {
dumpPackage = args[opti];
@@ -13831,6 +13876,7 @@ public class ActivityManagerService extends IActivityManager.Stub
// activity manager to announce its creation.
public boolean bindBackupAgent(String packageName, int backupMode, int targetUserId,
@BackupDestination int backupDestination) {
+ long startTimeNs = SystemClock.elapsedRealtimeNanos();
if (DEBUG_BACKUP) {
Slog.v(TAG, "bindBackupAgent: app=" + packageName + " mode=" + backupMode
+ " targetUserId=" + targetUserId + " callingUid = " + Binder.getCallingUid()
@@ -13906,15 +13952,20 @@ public class ActivityManagerService extends IActivityManager.Stub
? new ComponentName(app.packageName, app.backupAgentName)
: new ComponentName("android", "FullBackupAgent");
- // startProcessLocked() returns existing proc's record if it's already running
- ProcessRecord proc = startProcessLocked(app.processName, app,
- false, 0,
- new HostingRecord(HostingRecord.HOSTING_TYPE_BACKUP, hostingName),
- ZYGOTE_POLICY_FLAG_SYSTEM_PROCESS, false, false);
+ ProcessRecord proc = getProcessRecordLocked(app.processName, app.uid);
+ boolean isProcessStarted = proc != null;
+ if (!isProcessStarted) {
+ proc = startProcessLocked(app.processName, app,
+ false, 0,
+ new HostingRecord(HostingRecord.HOSTING_TYPE_BACKUP, hostingName),
+ ZYGOTE_POLICY_FLAG_SYSTEM_PROCESS, false, false);
+ }
if (proc == null) {
Slog.e(TAG, "Unable to start backup agent process " + r);
return false;
}
+ mProcessList.getAppStartInfoTracker().handleProcessBackupStart(startTimeNs, proc, r,
+ !isProcessStarted);
// If the app is a regular app (uid >= 10000) and not the system server or phone
// process, etc, then mark it as being in full backup so that certain calls to the
@@ -18741,8 +18792,12 @@ public class ActivityManagerService extends IActivityManager.Stub
// If the process is known as top app, set a hint so when the process is
// started, the top priority can be applied immediately to avoid cpu being
// preempted by other processes before attaching the process of top app.
- startProcessLocked(processName, info, knownToBeDead, 0 /* intentFlags */,
- new HostingRecord(hostingType, hostingName, isTop),
+ final long startTimeNs = SystemClock.elapsedRealtimeNanos();
+ HostingRecord hostingRecord =
+ new HostingRecord(hostingType, hostingName, isTop);
+ ProcessRecord rec = getProcessRecordLocked(processName, info.uid);
+ ProcessRecord app = startProcessLocked(processName, info, knownToBeDead,
+ 0 /* intentFlags */, hostingRecord,
ZYGOTE_POLICY_FLAG_LATENCY_SENSITIVE, false /* allowWhileBooting */,
false /* isolated */);
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index f3b2ef34ad6d..ae0cd65b2770 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -1362,7 +1362,7 @@ final class ActivityManagerShellCommand extends ShellCommand {
}
userId = user.id;
}
- mInternal.mProcessList.mAppStartInfoTracker
+ mInternal.mProcessList.getAppStartInfoTracker()
.clearHistoryProcessStartInfo(packageName, userId);
return 0;
}
diff --git a/services/core/java/com/android/server/am/AppStartInfoTracker.java b/services/core/java/com/android/server/am/AppStartInfoTracker.java
index edca74fae0e4..82e554e67b7e 100644
--- a/services/core/java/com/android/server/am/AppStartInfoTracker.java
+++ b/services/core/java/com/android/server/am/AppStartInfoTracker.java
@@ -22,11 +22,12 @@ import static android.os.Process.THREAD_PRIORITY_BACKGROUND;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
-import android.app.ActivityOptions;
+import android.annotation.NonNull;
import android.app.ApplicationStartInfo;
import android.app.Flags;
import android.app.IApplicationStartInfoCompleteListener;
import android.content.BroadcastReceiver;
+import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
@@ -138,6 +139,15 @@ public final class AppStartInfoTracker {
/** The path to the historical proc start info file, persisted in the storage. */
@VisibleForTesting File mProcStartInfoFile;
+
+ /**
+ * Temporary list of records that have not been completed.
+ *
+ * Key is timestamp of launch from {@link #ActivityMetricsLaunchObserver}.
+ */
+ @GuardedBy("mLock")
+ private ArrayMap<Long, ApplicationStartInfo> mInProgRecords = new ArrayMap<>();
+
AppStartInfoTracker() {
mCallbacks = new SparseArray<>();
mData = new ProcessMap<AppStartInfoContainer>();
@@ -174,68 +184,99 @@ public final class AppStartInfoTracker {
});
}
- void handleProcessColdStarted(long startTimeNs, HostingRecord hostingRecord,
- ProcessRecord app) {
+ void onIntentStarted(@NonNull Intent intent, long timestampNanos) {
synchronized (mLock) {
if (!mEnabled) {
return;
}
ApplicationStartInfo start = new ApplicationStartInfo();
- addBaseFieldsFromProcessRecord(start, app);
start.setStartupState(ApplicationStartInfo.STARTUP_STATE_STARTED);
- start.addStartupTimestamp(
- ApplicationStartInfo.START_TIMESTAMP_LAUNCH, startTimeNs);
- start.addStartupTimestamp(
- ApplicationStartInfo.START_TIMESTAMP_FORK, app.getStartElapsedTime());
- start.setStartType(ApplicationStartInfo.START_TYPE_COLD);
- start.setReason(ApplicationStartInfo.START_REASON_OTHER);
- addStartInfoLocked(start);
+ start.setIntent(intent);
+ start.setStartType(ApplicationStartInfo.START_TYPE_UNSET);
+ start.addStartupTimestamp(ApplicationStartInfo.START_TIMESTAMP_LAUNCH, timestampNanos);
+ if (intent != null && intent.getCategories() != null
+ && intent.getCategories().contains(Intent.CATEGORY_LAUNCHER)) {
+ start.setReason(ApplicationStartInfo.START_REASON_LAUNCHER);
+ } else {
+ start.setReason(ApplicationStartInfo.START_REASON_START_ACTIVITY);
+ }
+ mInProgRecords.put(timestampNanos, start);
}
}
- public void handleProcessActivityWarmOrHotStarted(long startTimeNs,
- ActivityOptions activityOptions, Intent intent) {
+ void onIntentFailed(long id) {
synchronized (mLock) {
if (!mEnabled) {
return;
}
- ApplicationStartInfo start = new ApplicationStartInfo();
- start.setStartupState(ApplicationStartInfo.STARTUP_STATE_STARTED);
- start.addStartupTimestamp(
- ApplicationStartInfo.START_TIMESTAMP_LAUNCH, startTimeNs);
- start.setIntent(intent);
- start.setReason(ApplicationStartInfo.START_REASON_LAUNCHER);
- if (activityOptions != null) {
- start.setProcessName(activityOptions.getPackageName());
+ if (!mInProgRecords.containsKey(id)) {
+ return;
}
- start.setStartType(ApplicationStartInfo.START_TYPE_WARM);
- if (intent != null && intent.getCategories() != null
- && intent.getCategories().contains(Intent.CATEGORY_LAUNCHER)) {
- start.setReason(ApplicationStartInfo.START_REASON_LAUNCHER);
+ mInProgRecords.get(id).setStartupState(ApplicationStartInfo.STARTUP_STATE_ERROR);
+ mInProgRecords.remove(id);
+ }
+ }
+
+ void onActivityLaunched(long id, ComponentName name, long temperature, ProcessRecord app) {
+ synchronized (mLock) {
+ if (!mEnabled) {
+ return;
+ }
+ if (!mInProgRecords.containsKey(id)) {
+ return;
+ }
+ if (app != null) {
+ ApplicationStartInfo info = mInProgRecords.get(id);
+ info.setStartType((int) temperature);
+ addBaseFieldsFromProcessRecord(info, app);
+ addStartInfoLocked(info);
} else {
- start.setReason(ApplicationStartInfo.START_REASON_START_ACTIVITY);
+ mInProgRecords.remove(id);
}
- addStartInfoLocked(start);
}
}
- public void handleProcessActivityStartedFromRecents(long startTimeNs,
- ActivityOptions activityOptions) {
+ void onActivityLaunchCancelled(long id) {
synchronized (mLock) {
if (!mEnabled) {
return;
}
- ApplicationStartInfo start = new ApplicationStartInfo();
- start.setStartupState(ApplicationStartInfo.STARTUP_STATE_STARTED);
- start.addStartupTimestamp(
- ApplicationStartInfo.START_TIMESTAMP_LAUNCH, startTimeNs);
- if (activityOptions != null) {
- start.setIntent(activityOptions.getResultData());
- start.setProcessName(activityOptions.getPackageName());
+ if (!mInProgRecords.containsKey(id)) {
+ return;
}
- start.setReason(ApplicationStartInfo.START_REASON_LAUNCHER_RECENTS);
- start.setStartType(ApplicationStartInfo.START_TYPE_WARM);
- addStartInfoLocked(start);
+ ApplicationStartInfo info = mInProgRecords.get(id);
+ info.setStartupState(ApplicationStartInfo.STARTUP_STATE_ERROR);
+ mInProgRecords.remove(id);
+ }
+ }
+
+ void onActivityLaunchFinished(long id, ComponentName name, long timestampNanos,
+ int launchMode) {
+ synchronized (mLock) {
+ if (!mEnabled) {
+ return;
+ }
+ if (!mInProgRecords.containsKey(id)) {
+ return;
+ }
+ ApplicationStartInfo info = mInProgRecords.get(id);
+ info.setStartupState(ApplicationStartInfo.STARTUP_STATE_FIRST_FRAME_DRAWN);
+ info.setLaunchMode(launchMode);
+ }
+ }
+
+ void onReportFullyDrawn(long id, long timestampNanos) {
+ synchronized (mLock) {
+ if (!mEnabled) {
+ return;
+ }
+ if (!mInProgRecords.containsKey(id)) {
+ return;
+ }
+ ApplicationStartInfo info = mInProgRecords.get(id);
+ info.addStartupTimestamp(ApplicationStartInfo.START_TIMESTAMP_FULLY_DRAWN,
+ timestampNanos);
+ mInProgRecords.remove(id);
}
}
@@ -347,7 +388,8 @@ public final class AppStartInfoTracker {
ApplicationStartInfo.START_TIMESTAMP_APPLICATION_ONCREATE);
}
- void reportBindApplicationTimeNanos(ProcessRecord app, long timeNs) {
+ /** Report a bind application timestamp to add to {@link ApplicationStartInfo}. */
+ public void reportBindApplicationTimeNanos(ProcessRecord app, long timeNs) {
addTimestampToStart(app, timeNs,
ApplicationStartInfo.START_TIMESTAMP_BIND_APPLICATION);
}
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 4ff34b1d7faa..3156e9da0ae9 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -498,7 +498,7 @@ public final class ProcessList {
/** Manages the {@link android.app.ApplicationStartInfo} records. */
@GuardedBy("mAppStartInfoTracker")
- final AppStartInfoTracker mAppStartInfoTracker = new AppStartInfoTracker();
+ private final AppStartInfoTracker mAppStartInfoTracker = new AppStartInfoTracker();
/**
* The currently running SDK sandbox processes for a uid.
@@ -1523,6 +1523,10 @@ public final class ProcessList {
return mCachedRestoreLevel;
}
+ AppStartInfoTracker getAppStartInfoTracker() {
+ return mAppStartInfoTracker;
+ }
+
/**
* Set the out-of-memory badness adjustment for a process.
* If {@code pid <= 0}, this method will be a no-op.
@@ -2572,6 +2576,7 @@ public final class ProcessList {
boolean isSdkSandbox, int sdkSandboxUid, String sdkSandboxClientAppPackage,
String abiOverride, String entryPoint, String[] entryPointArgs, Runnable crashHandler) {
long startTime = SystemClock.uptimeMillis();
+ final long startTimeNs = SystemClock.elapsedRealtimeNanos();
ProcessRecord app;
if (!isolated) {
app = getProcessRecordLocked(processName, info.uid);
diff --git a/services/core/java/com/android/server/content/SyncManager.java b/services/core/java/com/android/server/content/SyncManager.java
index 1e5e147749a2..df179a9b6d65 100644
--- a/services/core/java/com/android/server/content/SyncManager.java
+++ b/services/core/java/com/android/server/content/SyncManager.java
@@ -1294,8 +1294,8 @@ public class SyncManager {
if (android.content.pm.Flags.stayStopped()) {
try {
return mPackageManagerInternal.isPackageStopped(packageName, userId);
- } catch (IllegalArgumentException e) {
- Log.d(TAG, "Couldn't determine stopped state for unknown package: " + packageName);
+ } catch (NameNotFoundException e) {
+ Log.e(TAG, "Couldn't determine stopped state for unknown package: " + packageName);
}
}
return false;
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
index 168715713f8d..5831b29437dc 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
@@ -194,10 +194,14 @@ public final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
addAndStartAction(new RequestActiveSourceAction(this, new IHdmiControlCallback.Stub() {
@Override
public void onComplete(int result) {
- if (result != HdmiControlManager.RESULT_SUCCESS) {
+ if (!mService.getLocalActiveSource().isValid()
+ && result != HdmiControlManager.RESULT_SUCCESS) {
mService.sendCecCommand(HdmiCecMessageBuilder.buildActiveSource(
getDeviceInfo().getLogicalAddress(),
getDeviceInfo().getPhysicalAddress()));
+ updateActiveSource(getDeviceInfo().getLogicalAddress(),
+ getDeviceInfo().getPhysicalAddress(),
+ "RequestActiveSourceAction#finishWithCallback()");
}
}
}));
@@ -257,6 +261,7 @@ public final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
if (isAlreadyActiveSource(targetDevice, targetAddress, callback)) {
return;
}
+ removeAction(RequestActiveSourceAction.class);
if (targetAddress == Constants.ADDR_INTERNAL) {
handleSelectInternalSource();
// Switching to internal source is always successful even when CEC control is disabled.
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
index f526dbe9c66d..4089a81dfc20 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
@@ -215,10 +215,20 @@ public abstract class InputMethodManagerInternal {
/**
* Switch the keyboard layout in response to a keyboard shortcut.
*
- * @param direction {@code 1} to switch to the next subtype, {@code -1} to switch to the
- * previous subtype
+ * @param direction {@code 1} to switch to the next subtype, {@code -1} to switch to the
+ * previous subtype
+ * @param displayId the display to which the keyboard layout switch shortcut is
+ * dispatched. Note that there is no guarantee that an IME is
+ * associated with this display. This is more or less than a hint for
+ * cases when no IME is running for the given targetWindowToken. There
+ * is a longstanding discussion whether we should allow users to
+ * rotate keyboard layout even when there is no edit field, and this
+ * displayID would be helpful for such a situation.
+ * @param targetWindowToken the window token to which other keys are being sent while handling
+ * this shortcut.
*/
- public abstract void switchKeyboardLayout(int direction);
+ public abstract void onSwitchKeyboardLayoutShortcut(int direction, int displayId,
+ IBinder targetWindowToken);
/**
* Returns true if any InputConnection is currently active.
@@ -314,7 +324,8 @@ public abstract class InputMethodManagerInternal {
}
@Override
- public void switchKeyboardLayout(int direction) {
+ public void onSwitchKeyboardLayoutShortcut(int direction, int displayId,
+ IBinder targetWindowToken) {
}
@Override
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 30e9f5bd2a14..c440a6461de2 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -5763,7 +5763,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
}
@Override
- public void switchKeyboardLayout(int direction) {
+ public void onSwitchKeyboardLayoutShortcut(int direction, int displayId,
+ IBinder targetWindowToken) {
synchronized (ImfLock.class) {
switchKeyboardLayoutLocked(direction);
}
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index 42c2548264d4..0c2eee5aebbe 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -209,7 +209,7 @@ import javax.crypto.spec.GCMParameterSpec;
* <li>Protect each user's data using their SP. For example, use the SP to encrypt/decrypt the
* user's credential-encrypted (CE) key for file-based encryption (FBE).</li>
*
- * <li>Generate, protect, and use profile passwords for managed profiles.</li>
+ * <li>Generate, protect, and use unified profile passwords.</li>
*
* <li>Support unlocking the SP by alternative means: resume-on-reboot (reboot escrow) for easier
* OTA updates, and escrow tokens when set up by the Device Policy Controller (DPC).</li>
@@ -287,7 +287,7 @@ public class LockSettingsService extends ILockSettings.Stub {
private final java.security.KeyStore mJavaKeyStore;
private final RecoverableKeyStoreManager mRecoverableKeyStoreManager;
- private ManagedProfilePasswordCache mManagedProfilePasswordCache;
+ private final UnifiedProfilePasswordCache mUnifiedProfilePasswordCache;
private final RebootEscrowManager mRebootEscrowManager;
@@ -404,7 +404,8 @@ public class LockSettingsService extends ILockSettings.Stub {
for (int i = 0; i < newPasswordChars.length; i++) {
newPassword[i] = (byte) newPasswordChars[i];
}
- LockscreenCredential credential = LockscreenCredential.createManagedPassword(newPassword);
+ LockscreenCredential credential =
+ LockscreenCredential.createUnifiedProfilePassword(newPassword);
Arrays.fill(newPasswordChars, '\u0000');
Arrays.fill(newPassword, (byte) 0);
Arrays.fill(randomLockSeed, (byte) 0);
@@ -424,7 +425,7 @@ public class LockSettingsService extends ILockSettings.Stub {
if (!isCredentialSharableWithParent(profileUserId)) {
return;
}
- // Do not tie profile when work challenge is enabled
+ // Do not tie profile when separate challenge is enabled
if (getSeparateProfileChallengeEnabledInternal(profileUserId)) {
return;
}
@@ -462,7 +463,7 @@ public class LockSettingsService extends ILockSettings.Stub {
setLockCredentialInternal(unifiedProfilePassword, profileUserPassword, profileUserId,
/* isLockTiedToParent= */ true);
tieProfileLockToParent(profileUserId, parent.id, unifiedProfilePassword);
- mManagedProfilePasswordCache.storePassword(profileUserId, unifiedProfilePassword,
+ mUnifiedProfilePasswordCache.storePassword(profileUserId, unifiedProfilePassword,
parentSid);
}
}
@@ -620,9 +621,9 @@ public class LockSettingsService extends ILockSettings.Stub {
}
}
- public @NonNull ManagedProfilePasswordCache getManagedProfilePasswordCache(
+ public @NonNull UnifiedProfilePasswordCache getUnifiedProfilePasswordCache(
java.security.KeyStore ks) {
- return new ManagedProfilePasswordCache(ks);
+ return new UnifiedProfilePasswordCache(ks);
}
public boolean isHeadlessSystemUserMode() {
@@ -665,7 +666,7 @@ public class LockSettingsService extends ILockSettings.Stub {
mGatekeeperPasswords = new LongSparseArray<>();
mSpManager = injector.getSyntheticPasswordManager(mStorage);
- mManagedProfilePasswordCache = injector.getManagedProfilePasswordCache(mJavaKeyStore);
+ mUnifiedProfilePasswordCache = injector.getUnifiedProfilePasswordCache(mJavaKeyStore);
mBiometricDeferredQueue = new BiometricDeferredQueue(mSpManager, mHandler);
mRebootEscrowManager = injector.getRebootEscrowManager(new RebootEscrowCallbacks(),
@@ -689,8 +690,8 @@ public class LockSettingsService extends ILockSettings.Stub {
}
/**
- * If the account is credential-encrypted, show notification requesting the user to unlock the
- * device.
+ * If the user is a managed profile whose credential-encrypted storage is locked, show a
+ * notification requesting the user to unlock the device.
*/
private void maybeShowEncryptionNotificationForUser(@UserIdInt int userId, String reason) {
final UserInfo user = mUserManager.getUserInfo(userId);
@@ -846,7 +847,7 @@ public class LockSettingsService extends ILockSettings.Stub {
mHandler.post(new Runnable() {
@Override
public void run() {
- // Hide notification first, as tie managed profile lock takes time
+ // Hide notification first, as tie profile lock takes time
hideEncryptionNotification(new UserHandle(userId));
if (isCredentialSharableWithParent(userId)) {
@@ -1458,13 +1459,13 @@ public class LockSettingsService extends ILockSettings.Stub {
cipher.init(Cipher.DECRYPT_MODE, decryptionKey, new GCMParameterSpec(128, iv));
decryptionResult = cipher.doFinal(encryptedPassword);
- LockscreenCredential credential = LockscreenCredential.createManagedPassword(
+ LockscreenCredential credential = LockscreenCredential.createUnifiedProfilePassword(
decryptionResult);
Arrays.fill(decryptionResult, (byte) 0);
try {
long parentSid = getGateKeeperService().getSecureUserId(
mUserManager.getProfileParent(userId).id);
- mManagedProfilePasswordCache.storePassword(userId, credential, parentSid);
+ mUnifiedProfilePasswordCache.storePassword(userId, credential, parentSid);
} catch (RemoteException e) {
Slogf.w(TAG, "Failed to talk to GateKeeper service", e);
}
@@ -1550,7 +1551,7 @@ public class LockSettingsService extends ILockSettings.Stub {
// so it goes into the cache
getDecryptedPasswordForTiedProfile(profile.id);
} catch (GeneralSecurityException | IOException e) {
- Slog.d(TAG, "Cache work profile password failed", e);
+ Slog.d(TAG, "Cache unified profile password failed", e);
}
}
}
@@ -1604,19 +1605,19 @@ public class LockSettingsService extends ILockSettings.Stub {
}
/**
- * Synchronize all profile's work challenge of the given user if it's unified: tie or clear them
+ * Synchronize all profile's challenge of the given user if it's unified: tie or clear them
* depending on the parent user's secure state.
*
- * When clearing tied work challenges, a pre-computed password table for profiles are required,
- * since changing password for profiles requires existing password, and existing passwords can
- * only be computed before the parent user's password is cleared.
+ * When clearing tied challenges, a pre-computed password table for profiles are required, since
+ * changing password for profiles requires existing password, and existing passwords can only be
+ * computed before the parent user's password is cleared.
*
* Strictly this is a recursive function, since setLockCredentialInternal ends up calling this
* method again on profiles. However the recursion is guaranteed to terminate as this method
* terminates when the user is a profile that shares lock credentials with parent.
* (e.g. managed and clone profile).
*/
- private void synchronizeUnifiedWorkChallengeForProfiles(int userId,
+ private void synchronizeUnifiedChallengeForProfiles(int userId,
Map<Integer, LockscreenCredential> profilePasswordMap) {
if (isCredentialSharableWithParent(userId)) {
return;
@@ -1635,7 +1636,7 @@ public class LockSettingsService extends ILockSettings.Stub {
tieProfileLockIfNecessary(profileUserId,
LockscreenCredential.createNone());
} else {
- // We use cached work profile password computed before clearing the parent's
+ // We use cached profile password computed before clearing the parent's
// credential, otherwise they get lost
if (profilePasswordMap != null
&& profilePasswordMap.containsKey(profileUserId)) {
@@ -1777,7 +1778,7 @@ public class LockSettingsService extends ILockSettings.Stub {
notifyPasswordChanged(credential, userId);
}
if (isCredentialSharableWithParent(userId)) {
- // Make sure the profile doesn't get locked straight after setting work challenge.
+ // Make sure the profile doesn't get locked straight after setting challenge.
setDeviceUnlockedForUser(userId);
}
notifySeparateProfileChallengeChanged(userId);
@@ -2368,7 +2369,7 @@ public class LockSettingsService extends ILockSettings.Stub {
}
try {
- // Unlock work profile, and work profile with unified lock must use password only
+ // Unlock profile with unified lock
return doVerifyCredential(getDecryptedPasswordForTiedProfile(userId),
userId, null /* progressCallback */, flags);
} catch (UnrecoverableKeyException | InvalidKeyException | KeyStoreException
@@ -2492,7 +2493,7 @@ public class LockSettingsService extends ILockSettings.Stub {
mStrongAuth.removeUser(userId);
AndroidKeyStoreMaintenance.onUserRemoved(userId);
- mManagedProfilePasswordCache.removePassword(userId);
+ mUnifiedProfilePasswordCache.removePassword(userId);
gateKeeperClearSecureUserId(userId);
removeKeystoreProfileKey(userId);
@@ -2982,7 +2983,7 @@ public class LockSettingsService extends ILockSettings.Stub {
credential, sp, userId);
final Map<Integer, LockscreenCredential> profilePasswords;
if (!credential.isNone()) {
- // not needed by synchronizeUnifiedWorkChallengeForProfiles()
+ // not needed by synchronizeUnifiedChallengeForProfiles()
profilePasswords = null;
if (!mSpManager.hasSidForUser(userId)) {
@@ -2993,8 +2994,8 @@ public class LockSettingsService extends ILockSettings.Stub {
}
}
} else {
- // Cache all profile password if they use unified work challenge. This will later be
- // used to clear the profile's password in synchronizeUnifiedWorkChallengeForProfiles()
+ // Cache all profile password if they use unified challenge. This will later be used to
+ // clear the profile's password in synchronizeUnifiedChallengeForProfiles().
profilePasswords = getDecryptedPasswordsForAllTiedProfiles(userId);
mSpManager.clearSidForUser(userId);
@@ -3010,10 +3011,10 @@ public class LockSettingsService extends ILockSettings.Stub {
}
setCurrentLskfBasedProtectorId(newProtectorId, userId);
LockPatternUtils.invalidateCredentialTypeCache();
- synchronizeUnifiedWorkChallengeForProfiles(userId, profilePasswords);
+ synchronizeUnifiedChallengeForProfiles(userId, profilePasswords);
setUserPasswordMetrics(credential, userId);
- mManagedProfilePasswordCache.removePassword(userId);
+ mUnifiedProfilePasswordCache.removePassword(userId);
if (savedCredentialType != CREDENTIAL_TYPE_NONE) {
mSpManager.destroyAllWeakTokenBasedProtectors(userId);
}
@@ -3114,7 +3115,7 @@ public class LockSettingsService extends ILockSettings.Stub {
try {
currentCredential = getDecryptedPasswordForTiedProfile(userId);
} catch (Exception e) {
- Slog.e(TAG, "Failed to get work profile credential", e);
+ Slog.e(TAG, "Failed to get unified profile password", e);
return null;
}
}
@@ -3284,7 +3285,7 @@ public class LockSettingsService extends ILockSettings.Stub {
@Override
public boolean tryUnlockWithCachedUnifiedChallenge(int userId) {
checkPasswordReadPermission();
- try (LockscreenCredential cred = mManagedProfilePasswordCache.retrievePassword(userId)) {
+ try (LockscreenCredential cred = mUnifiedProfilePasswordCache.retrievePassword(userId)) {
if (cred == null) {
return false;
}
@@ -3296,7 +3297,7 @@ public class LockSettingsService extends ILockSettings.Stub {
@Override
public void removeCachedUnifiedChallenge(int userId) {
checkWritePermission();
- mManagedProfilePasswordCache.removePassword(userId);
+ mUnifiedProfilePasswordCache.removePassword(userId);
}
static String timestampToString(long timestamp) {
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsStorage.java b/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
index 1e8b387fc189..6d123ccebc7c 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
@@ -501,10 +501,10 @@ class LockSettingsStorage {
final UserInfo parentInfo = um.getProfileParent(userId);
if (parentInfo == null) {
- // This user owns its lock settings files - safe to delete them
+ // Delete files specific to non-profile users.
deleteFile(getRebootEscrowFile(userId));
} else {
- // Managed profile
+ // Delete files specific to profile users.
removeChildProfileLock(userId);
}
diff --git a/services/core/java/com/android/server/locksettings/ManagedProfilePasswordCache.java b/services/core/java/com/android/server/locksettings/UnifiedProfilePasswordCache.java
index 1298fe8f07a4..21caf76d30d0 100644
--- a/services/core/java/com/android/server/locksettings/ManagedProfilePasswordCache.java
+++ b/services/core/java/com/android/server/locksettings/UnifiedProfilePasswordCache.java
@@ -43,30 +43,31 @@ import javax.crypto.SecretKey;
import javax.crypto.spec.GCMParameterSpec;
/**
- * Caches *unified* work challenge for managed profiles. The cached credential is encrypted using
- * a keystore key auth-bound to the parent user's lockscreen credential, similar to how unified
- * work challenge is normally secured.
- *
- * <p> The cache is filled whenever the managed profile's unified challenge is created or derived
- * (as part of the parent user's credential verification flow). It's removed when the profile is
- * deleted or a (separate) lockscreen credential is explicitly set on the profile. There is also
- * an ADB command to evict the cache "cmd lock_settings remove-cache --user X", to assist
- * development and testing.
-
- * <p> The encrypted credential is stored in-memory only so the cache does not persist across
- * reboots.
+ * An in-memory cache for unified profile passwords. A "unified profile password" is the random
+ * password that the system automatically generates and manages for each profile that uses a unified
+ * challenge and where the parent user has a secure lock screen.
+ * <p>
+ * Each password in this cache is encrypted by a Keystore key that is auth-bound to the parent user.
+ * This is very similar to how the password is protected on-disk, but the in-memory cache uses a
+ * much longer timeout on the keys: 7 days instead of 30 seconds. This enables use cases like
+ * unpausing work apps without requiring authentication as frequently.
+ * <p>
+ * Unified profile passwords are cached when they are created, or when they are decrypted as part of
+ * the parent user's LSKF verification flow. They are removed when the profile is deleted or when a
+ * separate challenge is explicitly set on the profile. There is also an ADB command to evict a
+ * cached password, "locksettings remove-cache --user X", to assist development and testing.
*/
@VisibleForTesting // public visibility is needed for Mockito
-public class ManagedProfilePasswordCache {
+public class UnifiedProfilePasswordCache {
- private static final String TAG = "ManagedProfilePasswordCache";
+ private static final String TAG = "UnifiedProfilePasswordCache";
private static final int KEY_LENGTH = 256;
private static final int CACHE_TIMEOUT_SECONDS = (int) TimeUnit.DAYS.toSeconds(7);
private final SparseArray<byte[]> mEncryptedPasswords = new SparseArray<>();
private final KeyStore mKeyStore;
- public ManagedProfilePasswordCache(KeyStore keyStore) {
+ public UnifiedProfilePasswordCache(KeyStore keyStore) {
mKeyStore = keyStore;
}
@@ -151,7 +152,8 @@ public class ManagedProfilePasswordCache {
Slog.d(TAG, "Cannot decrypt", e);
return null;
}
- LockscreenCredential result = LockscreenCredential.createManagedPassword(credential);
+ LockscreenCredential result =
+ LockscreenCredential.createUnifiedProfilePassword(credential);
Arrays.fill(credential, (byte) 0);
return result;
}
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index 7ec94c315798..3f8b5952a1bc 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -627,6 +627,7 @@ public class ZenModeHelper {
try {
ApplicationInfo applicationInfo = mPm.getApplicationInfo(pkg, 0);
rule.name = applicationInfo.loadLabel(mPm).toString();
+ rule.iconResName = drawableResIdToResName(pkg, applicationInfo.icon);
} catch (PackageManager.NameNotFoundException e) {
// Should not happen, since it's the app calling us (?)
Log.w(TAG, "Package not found for creating implicit zen rule");
@@ -634,6 +635,9 @@ public class ZenModeHelper {
}
});
+ rule.type = AutomaticZenRule.TYPE_OTHER;
+ rule.triggerDescription = mContext.getString(R.string.zen_mode_implicit_trigger_description,
+ rule.name);
rule.condition = null;
rule.conditionId = new Uri.Builder()
.scheme(Condition.SCHEME)
diff --git a/services/core/java/com/android/server/pdb/PersistentDataBlockService.java b/services/core/java/com/android/server/pdb/PersistentDataBlockService.java
index f985b5b64e21..b9b09fb0e84c 100644
--- a/services/core/java/com/android/server/pdb/PersistentDataBlockService.java
+++ b/services/core/java/com/android/server/pdb/PersistentDataBlockService.java
@@ -754,9 +754,9 @@ public class PersistentDataBlockService extends SystemService {
}
};
- private PersistentDataBlockManagerInternal mInternalService =
- new PersistentDataBlockManagerInternal() {
+ private InternalService mInternalService = new InternalService();
+ private class InternalService implements PersistentDataBlockManagerInternal {
@Override
public void setFrpCredentialHandle(byte[] handle) {
writeInternal(handle, getFrpCredentialDataOffset(), MAX_FRP_CREDENTIAL_HANDLE_SIZE);
diff --git a/services/core/java/com/android/server/pm/Computer.java b/services/core/java/com/android/server/pm/Computer.java
index bd725ed49f3c..27f4e11c53ad 100644
--- a/services/core/java/com/android/server/pm/Computer.java
+++ b/services/core/java/com/android/server/pm/Computer.java
@@ -508,12 +508,15 @@ public interface Computer extends PackageDataSnapshot {
boolean getApplicationHiddenSettingAsUser(@NonNull String packageName, @UserIdInt int userId);
- boolean isPackageSuspendedForUser(@NonNull String packageName, @UserIdInt int userId);
+ boolean isPackageSuspendedForUser(@NonNull String packageName, @UserIdInt int userId)
+ throws PackageManager.NameNotFoundException;
- boolean isPackageQuarantinedForUser(@NonNull String packageName, @UserIdInt int userId);
+ boolean isPackageQuarantinedForUser(@NonNull String packageName, @UserIdInt int userId)
+ throws PackageManager.NameNotFoundException;
/** Check if the package is in a stopped state for a given user. */
- boolean isPackageStoppedForUser(@NonNull String packageName, @UserIdInt int userId);
+ boolean isPackageStoppedForUser(@NonNull String packageName, @UserIdInt int userId)
+ throws PackageManager.NameNotFoundException;
boolean isSuspendingAnyPackages(@NonNull String suspendingPackage, @UserIdInt int userId);
diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java
index 2dc3fb7464fd..abfd5715810e 100644
--- a/services/core/java/com/android/server/pm/ComputerEngine.java
+++ b/services/core/java/com/android/server/pm/ComputerEngine.java
@@ -4980,31 +4980,34 @@ public class ComputerEngine implements Computer {
}
}
- private PackageUserStateInternal getUserStageOrDefaultForUser(@NonNull String packageName,
- int userId) {
+ private PackageUserStateInternal getUserStateOrDefaultForUser(@NonNull String packageName,
+ int userId) throws PackageManager.NameNotFoundException {
final int callingUid = Binder.getCallingUid();
enforceCrossUserPermission(callingUid, userId, true /* requireFullPermission */,
false /* checkShell */, "when asking about packages for user " + userId);
final PackageStateInternal ps = mSettings.getPackage(packageName);
if (ps == null || shouldFilterApplicationIncludingUninstalled(ps, callingUid, userId)) {
- throw new IllegalArgumentException("Unknown target package: " + packageName);
+ throw new PackageManager.NameNotFoundException(packageName);
}
return ps.getUserStateOrDefault(userId);
}
@Override
- public boolean isPackageSuspendedForUser(@NonNull String packageName, int userId) {
- return getUserStageOrDefaultForUser(packageName, userId).isSuspended();
+ public boolean isPackageSuspendedForUser(@NonNull String packageName, int userId)
+ throws PackageManager.NameNotFoundException {
+ return getUserStateOrDefaultForUser(packageName, userId).isSuspended();
}
@Override
- public boolean isPackageQuarantinedForUser(@NonNull String packageName, @UserIdInt int userId) {
- return getUserStageOrDefaultForUser(packageName, userId).isQuarantined();
+ public boolean isPackageQuarantinedForUser(@NonNull String packageName, @UserIdInt int userId)
+ throws PackageManager.NameNotFoundException {
+ return getUserStateOrDefaultForUser(packageName, userId).isQuarantined();
}
@Override
- public boolean isPackageStoppedForUser(@NonNull String packageName, @UserIdInt int userId) {
- return getUserStageOrDefaultForUser(packageName, userId).isStopped();
+ public boolean isPackageStoppedForUser(@NonNull String packageName, @UserIdInt int userId)
+ throws PackageManager.NameNotFoundException {
+ return getUserStateOrDefaultForUser(packageName, userId).isStopped();
}
@Override
diff --git a/services/core/java/com/android/server/pm/IPackageManagerBase.java b/services/core/java/com/android/server/pm/IPackageManagerBase.java
index 24a33f1949fa..e3bbd2d8d17e 100644
--- a/services/core/java/com/android/server/pm/IPackageManagerBase.java
+++ b/services/core/java/com/android/server/pm/IPackageManagerBase.java
@@ -951,20 +951,32 @@ public abstract class IPackageManagerBase extends IPackageManager.Stub {
@Deprecated
public final boolean isPackageSuspendedForUser(@NonNull String packageName,
@UserIdInt int userId) {
- return snapshot().isPackageSuspendedForUser(packageName, userId);
+ try {
+ return snapshot().isPackageSuspendedForUser(packageName, userId);
+ } catch (PackageManager.NameNotFoundException e) {
+ throw new IllegalArgumentException("Unknown target package: " + packageName);
+ }
}
@Override
@Deprecated
public final boolean isPackageQuarantinedForUser(@NonNull String packageName,
@UserIdInt int userId) {
- return snapshot().isPackageQuarantinedForUser(packageName, userId);
+ try {
+ return snapshot().isPackageQuarantinedForUser(packageName, userId);
+ } catch (PackageManager.NameNotFoundException e) {
+ throw new IllegalArgumentException("Unknown target package: " + packageName);
+ }
}
@Override
public final boolean isPackageStoppedForUser(@NonNull String packageName,
@UserIdInt int userId) {
- return snapshot().isPackageStoppedForUser(packageName, userId);
+ try {
+ return snapshot().isPackageStoppedForUser(packageName, userId);
+ } catch (PackageManager.NameNotFoundException e) {
+ throw new IllegalArgumentException("Unknown target package: " + packageName);
+ }
}
@Override
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index f1c062763183..9b8ee741476e 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -2450,9 +2450,9 @@ final class InstallPackageHelper {
// Hardcode previousAppId to 0 to disable any data migration (http://b/221088088)
mAppDataHelper.prepareAppDataPostCommitLIF(ps, 0, installRequest.getNewUsers());
if (installRequest.isClearCodeCache()) {
- mAppDataHelper.clearAppDataLeafLIF(packageName, ps.getVolumeUuid(),
- UserHandle.USER_ALL, FLAG_STORAGE_DE | FLAG_STORAGE_CE
- | FLAG_STORAGE_EXTERNAL | Installer.FLAG_CLEAR_CODE_CACHE_ONLY);
+ mAppDataHelper.clearAppDataLIF(ps.getPkg(), UserHandle.USER_ALL,
+ FLAG_STORAGE_DE | FLAG_STORAGE_CE | FLAG_STORAGE_EXTERNAL
+ | Installer.FLAG_CLEAR_CODE_CACHE_ONLY);
}
if (installRequest.isInstallReplace() && pkg != null) {
mDexManager.notifyPackageUpdated(packageName,
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index 58c1c0564f95..ba66377beb8a 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -1575,6 +1575,30 @@ public class LauncherAppsService extends SystemService {
}
@Override
+ public List<String> getPreInstalledSystemPackages(UserHandle user) {
+ // Only system launchers, which have access to recents should have access to this API.
+ // TODO(b/303803157): Update access control for this API to default Launcher app.
+ if (!mActivityTaskManagerInternal.isCallerRecents(Binder.getCallingUid())) {
+ throw new SecurityException("Caller is not the recents app");
+ }
+ if (!canAccessProfile(user.getIdentifier(),
+ "Can't access preinstalled packages for another user")) {
+ return null;
+ }
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ String userType = mUm.getUserInfo(user.getIdentifier()).userType;
+ Set<String> preInstalledPackages = mUm.getPreInstallableSystemPackages(userType);
+ if (preInstalledPackages == null) {
+ return new ArrayList<>();
+ }
+ return List.copyOf(preInstalledPackages);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
public void startActivityAsUser(IApplicationThread caller, String callingPackage,
String callingFeatureId, ComponentName component, Rect sourceBounds,
Bundle opts, UserHandle user) throws RemoteException {
diff --git a/services/core/java/com/android/server/pm/PackageArchiver.java b/services/core/java/com/android/server/pm/PackageArchiver.java
index e3bab3f243c3..6b05edf7c25a 100644
--- a/services/core/java/com/android/server/pm/PackageArchiver.java
+++ b/services/core/java/com/android/server/pm/PackageArchiver.java
@@ -323,6 +323,7 @@ public class PackageArchiver {
PackageStateInternal ps = getPackageState(packageName, snapshot,
Binder.getCallingUid(), userId);
verifyNotSystemApp(ps.getFlags());
+ verifyInstalled(ps, userId);
String responsibleInstallerPackage = getResponsibleInstallerPackage(ps);
verifyInstaller(responsibleInstallerPackage, userId);
ApplicationInfo installerInfo = snapshot.getApplicationInfo(
@@ -476,6 +477,14 @@ public class PackageArchiver {
}
}
+ private void verifyInstalled(PackageStateInternal ps, int userId)
+ throws PackageManager.NameNotFoundException {
+ if (!ps.getUserStateOrDefault(userId).isInstalled()) {
+ throw new PackageManager.NameNotFoundException(
+ TextUtils.formatSimple("%s is not installed.", ps.getPackageName()));
+ }
+ }
+
/**
* Returns true if the app is archivable.
*/
@@ -519,11 +528,11 @@ public class PackageArchiver {
/**
* Returns true if user has opted the app out of archiving through system settings.
*/
- // TODO(b/304256918) Switch this to a separate OP code for archiving.
private boolean isAppOptedOutOfArchiving(String packageName, int uid) {
return Binder.withCleanCallingIdentity(() ->
- getAppOpsManager().checkOp(AppOpsManager.OP_AUTO_REVOKE_PERMISSIONS_IF_UNUSED,
- uid, packageName) == MODE_IGNORED);
+ getAppOpsManager().checkOpNoThrow(
+ AppOpsManager.OP_AUTO_REVOKE_PERMISSIONS_IF_UNUSED, uid, packageName)
+ == MODE_IGNORED);
}
private void verifyOptOutStatus(String packageName, int uid)
@@ -676,23 +685,51 @@ public class PackageArchiver {
PackageStateInternal ps;
try {
ps = getPackageState(packageName, snapshot, callingUid, userId);
- snapshot.enforceCrossUserPermission(callingUid, userId, true, false,
- "getArchivedAppIcon");
- verifyArchived(ps, userId);
} catch (PackageManager.NameNotFoundException e) {
- throw new ParcelableException(e);
+ Slog.e(TAG, TextUtils.formatSimple("Package %s couldn't be found.", packageName), e);
+ return null;
}
- List<ArchiveActivityInfo> activityInfos = ps.getUserStateOrDefault(
- userId).getArchiveState().getActivityInfos();
- if (activityInfos.size() == 0) {
+ ArchiveState archiveState = getAnyArchiveState(ps, userId);
+ if (archiveState == null || archiveState.getActivityInfos().size() == 0) {
return null;
}
// TODO(b/298452477) Handle monochrome icons.
// In the rare case the archived app defined more than two launcher activities, we choose
// the first one arbitrarily.
- return includeCloudOverlay(decodeIcon(activityInfos.get(0)));
+ return includeCloudOverlay(decodeIcon(archiveState.getActivityInfos().get(0)));
+ }
+
+ /**
+ * This method first checks the ArchiveState for the provided userId and then tries to fallback
+ * to other users if the current user is not archived.
+ *
+ * <p> This fallback behaviour is required for archived apps to fit into the multi-user world
+ * where APKs are shared across users. E.g. current ways of fetching icons for apps that are
+ * only installed on the work profile also work when executed on the personal profile if you're
+ * using {@link PackageManager#MATCH_UNINSTALLED_PACKAGES}. Resource fetching from APKs is for
+ * the most part userId-agnostic, which we need to mimic here in order for existing methods
+ * like {@link PackageManager#getApplicationIcon} to continue working.
+ *
+ * @return {@link ArchiveState} for {@code userId} if present. If not present, false back to an
+ * arbitrary userId. If no user is archived, returns null.
+ */
+ @Nullable
+ private ArchiveState getAnyArchiveState(PackageStateInternal ps, int userId) {
+ PackageUserStateInternal userState = ps.getUserStateOrDefault(userId);
+ if (isArchived(userState)) {
+ return userState.getArchiveState();
+ }
+
+ for (int i = 0; i < ps.getUserStates().size(); i++) {
+ userState = ps.getUserStates().valueAt(i);
+ if (isArchived(userState)) {
+ return userState.getArchiveState();
+ }
+ }
+
+ return null;
}
@VisibleForTesting
diff --git a/services/core/java/com/android/server/pm/PackageManagerInternalBase.java b/services/core/java/com/android/server/pm/PackageManagerInternalBase.java
index 1e7d0437d99e..c737b45ae885 100644
--- a/services/core/java/com/android/server/pm/PackageManagerInternalBase.java
+++ b/services/core/java/com/android/server/pm/PackageManagerInternalBase.java
@@ -762,13 +762,14 @@ abstract class PackageManagerInternalBase extends PackageManagerInternal {
}
@Override
- public boolean isPackageQuarantined(@NonNull String packageName,
- @UserIdInt int userId) {
+ public boolean isPackageQuarantined(@NonNull String packageName, @UserIdInt int userId)
+ throws PackageManager.NameNotFoundException {
return snapshot().isPackageQuarantinedForUser(packageName, userId);
}
@Override
- public boolean isPackageStopped(@NonNull String packageName, @UserIdInt int userId) {
+ public boolean isPackageStopped(@NonNull String packageName, @UserIdInt int userId)
+ throws PackageManager.NameNotFoundException {
return snapshot().isPackageStoppedForUser(packageName, userId);
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index c5b006c4c77d..d36965832186 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -5242,14 +5242,18 @@ public class PackageManagerService implements PackageSender, TestUtilityService
@Override
public String getSuspendingPackage(String packageName, int userId) {
- final int callingUid = Binder.getCallingUid();
- final Computer snapshot = snapshot();
- // This will do visibility checks as well.
- if (!snapshot.isPackageSuspendedForUser(packageName, userId)) {
+ try {
+ final int callingUid = Binder.getCallingUid();
+ final Computer snapshot = snapshot();
+ // This will do visibility checks as well.
+ if (!snapshot.isPackageSuspendedForUser(packageName, userId)) {
+ return null;
+ }
+ return mSuspendPackageHelper.getSuspendingPackage(snapshot, packageName, userId,
+ callingUid);
+ } catch (PackageManager.NameNotFoundException e) {
return null;
}
- return mSuspendPackageHelper.getSuspendingPackage(snapshot, packageName, userId,
- callingUid);
}
@Override
diff --git a/services/core/java/com/android/server/pm/SuspendPackageHelper.java b/services/core/java/com/android/server/pm/SuspendPackageHelper.java
index fe8c12c8e232..c2a960a95394 100644
--- a/services/core/java/com/android/server/pm/SuspendPackageHelper.java
+++ b/services/core/java/com/android/server/pm/SuspendPackageHelper.java
@@ -418,11 +418,24 @@ public final class SuspendPackageHelper {
}
String suspendingPackage = null;
+ String suspendedBySystem = null;
+ String qasPackage = null;
for (int i = 0; i < userState.getSuspendParams().size(); i++) {
suspendingPackage = userState.getSuspendParams().keyAt(i);
+ var suspendParams = userState.getSuspendParams().valueAt(i);
if (PLATFORM_PACKAGE_NAME.equals(suspendingPackage)) {
- return suspendingPackage;
+ suspendedBySystem = suspendingPackage;
}
+ if (suspendParams.isQuarantined() && qasPackage == null) {
+ qasPackage = suspendingPackage;
+ }
+ }
+ // Precedence: quarantined, then system, then suspending.
+ if (qasPackage != null) {
+ return qasPackage;
+ }
+ if (suspendedBySystem != null) {
+ return suspendedBySystem;
}
return suspendingPackage;
}
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index a7b52f4e7a58..c48eccf2aac5 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -532,9 +532,10 @@ public class UserManagerService extends IUserManager.Stub {
}
final IntentSender target = intent.getParcelableExtra(Intent.EXTRA_INTENT, android.content.IntentSender.class);
final int userId = intent.getIntExtra(Intent.EXTRA_USER_ID, UserHandle.USER_NULL);
+ final String callingPackage = intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME);
// Call setQuietModeEnabled on bg thread to avoid ANR
BackgroundThread.getHandler().post(() ->
- setQuietModeEnabled(userId, false, target, /* callingPackage */ null));
+ setQuietModeEnabled(userId, false, target, callingPackage));
}
};
@@ -1410,7 +1411,7 @@ public class UserManagerService extends IUserManager.Stub {
if (onlyIfCredentialNotRequired) {
return false;
}
- showConfirmCredentialToDisableQuietMode(userId, target);
+ showConfirmCredentialToDisableQuietMode(userId, target, callingPackage);
return false;
}
}
@@ -1434,7 +1435,7 @@ public class UserManagerService extends IUserManager.Stub {
if (onlyIfCredentialNotRequired) {
return false;
}
- showConfirmCredentialToDisableQuietMode(userId, target);
+ showConfirmCredentialToDisableQuietMode(userId, target, callingPackage);
return false;
}
setQuietModeEnabled(userId, false /* enableQuietMode */, target, callingPackage);
@@ -1604,7 +1605,7 @@ public class UserManagerService extends IUserManager.Stub {
* Show confirm credential screen to unlock user in order to turn off quiet mode.
*/
private void showConfirmCredentialToDisableQuietMode(
- @UserIdInt int userId, @Nullable IntentSender target) {
+ @UserIdInt int userId, @Nullable IntentSender target, @Nullable String callingPackage) {
if (android.app.admin.flags.Flags.quietModeCredentialBugFix()) {
// TODO (b/308121702) It may be brittle to rely on user states to check profile state
int state;
@@ -1635,6 +1636,7 @@ public class UserManagerService extends IUserManager.Stub {
}
callBackIntent.putExtra(Intent.EXTRA_USER_ID, userId);
callBackIntent.setPackage(mContext.getPackageName());
+ callBackIntent.putExtra(Intent.EXTRA_PACKAGE_NAME, callingPackage);
callBackIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
final PendingIntent pendingIntent = PendingIntent.getBroadcast(
mContext,
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 4e5dc1dd76fa..938ed2329ffd 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -76,7 +76,6 @@ import static android.view.WindowManagerGlobal.ADD_PERMISSION_DENIED;
import static android.view.contentprotection.flags.Flags.createAccessibilityOverlayAppOpEnabled;
import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.SCREENSHOT_KEYCHORD_DELAY;
-import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__A11Y_WEAR_TRIPLE_PRESS_GESTURE;
import static com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs.CAMERA_LENS_COVERED;
import static com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs.CAMERA_LENS_COVER_ABSENT;
import static com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs.CAMERA_LENS_UNCOVERED;
@@ -97,7 +96,6 @@ import static com.android.server.wm.WindowManagerPolicyProto.SCREEN_ON_FULLY;
import static com.android.server.wm.WindowManagerPolicyProto.WINDOW_MANAGER_DRAW_COMPLETE;
import android.accessibilityservice.AccessibilityService;
-import android.accessibilityservice.AccessibilityServiceInfo;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SuppressLint;
@@ -124,7 +122,6 @@ import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
-import android.content.pm.ServiceInfo;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.database.ContentObserver;
@@ -204,8 +201,6 @@ import android.widget.Toast;
import com.android.internal.R;
import com.android.internal.accessibility.AccessibilityShortcutController;
-import com.android.internal.accessibility.util.AccessibilityStatsLogUtils;
-import com.android.internal.accessibility.util.AccessibilityUtils;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.AssistUtils;
import com.android.internal.display.BrightnessUtils;
@@ -385,8 +380,6 @@ public class PhoneWindowManager implements WindowManagerPolicy {
public static final String TRACE_WAIT_FOR_ALL_WINDOWS_DRAWN_METHOD = "waitForAllWindowsDrawn";
- private static final String TALKBACK_LABEL = "TalkBack";
-
private static final int POWER_BUTTON_SUPPRESSION_DELAY_DEFAULT_MILLIS = 800;
/**
@@ -477,6 +470,8 @@ public class PhoneWindowManager implements WindowManagerPolicy {
/** Controller that supports enabling an AccessibilityService by holding down the volume keys */
private AccessibilityShortcutController mAccessibilityShortcutController;
+ private TalkbackShortcutController mTalkbackShortcutController;
+
boolean mSafeMode;
// Whether to allow dock apps with METADATA_DOCK_HOME to temporarily take over the Home key.
@@ -813,7 +808,10 @@ public class PhoneWindowManager implements WindowManagerPolicy {
handleScreenShot(msg.arg1);
break;
case MSG_SWITCH_KEYBOARD_LAYOUT:
- handleSwitchKeyboardLayout(msg.arg1, msg.arg2);
+ SwitchKeyboardLayoutMessageObject object =
+ (SwitchKeyboardLayoutMessageObject) msg.obj;
+ handleSwitchKeyboardLayout(object.keyEvent, object.direction,
+ object.focusedToken);
break;
case MSG_LOG_KEYBOARD_SYSTEM_EVENT:
handleKeyboardSystemEvent(KeyboardLogEvent.from(msg.arg1), (KeyEvent) msg.obj);
@@ -934,6 +932,10 @@ public class PhoneWindowManager implements WindowManagerPolicy {
}
}
+ private record SwitchKeyboardLayoutMessageObject(KeyEvent keyEvent, IBinder focusedToken,
+ int direction) {
+ }
+
final IPersistentVrStateCallbacks mPersistentVrModeListener =
new IPersistentVrStateCallbacks.Stub() {
@Override
@@ -1602,19 +1604,11 @@ public class PhoneWindowManager implements WindowManagerPolicy {
if (DEBUG_INPUT) {
Slog.d(TAG, "Executing stem primary triple press action behavior.");
}
-
- if (Settings.System.getIntForUser(mContext.getContentResolver(),
- Settings.System.WEAR_ACCESSIBILITY_GESTURE_ENABLED,
- /* def= */ 0, UserHandle.USER_CURRENT) == 1) {
- /** Toggle talkback begin */
- ComponentName componentName = getTalkbackComponent();
- if (componentName != null && toggleTalkBack(componentName)) {
- /** log stem triple press telemetry if it's a talkback enabled event */
- logStemTriplePressAccessibilityTelemetry(componentName);
- }
- performHapticFeedback(HapticFeedbackConstants.CONFIRM, /* always = */ false,
- /* reason = */ "Stem primary - Triple Press - Toggle Accessibility");
- /** Toggle talkback end */
+ mTalkbackShortcutController.toggleTalkback(mCurrentUserId);
+ if (mTalkbackShortcutController.isTalkBackShortcutGestureEnabled()) {
+ performHapticFeedback(HapticFeedbackConstants.CONFIRM, /* always = */
+ false, /* reason = */
+ "Stem primary - Triple Press - Toggle Accessibility");
}
break;
}
@@ -1640,61 +1634,6 @@ public class PhoneWindowManager implements WindowManagerPolicy {
}
/**
- * A function that toggles talkback service
- *
- * @return {@code true} if talkback is enabled, {@code false} if talkback is disabled
- */
- private boolean toggleTalkBack(ComponentName componentName) {
- final Set<ComponentName> enabledServices =
- AccessibilityUtils.getEnabledServicesFromSettings(mContext, mCurrentUserId);
-
- boolean isTalkbackAlreadyEnabled = enabledServices.contains(componentName);
- AccessibilityUtils.setAccessibilityServiceState(mContext, componentName,
- !isTalkbackAlreadyEnabled);
- /** if isTalkbackAlreadyEnabled is true, then it's a disabled event so return false
- * and if isTalkbackAlreadyEnabled is false, return true as it's an enabled event */
- return !isTalkbackAlreadyEnabled;
- }
-
- /**
- * A function that logs stem triple press accessibility telemetry
- * If the user setup (Oobe) is not completed, set the
- * WEAR_ACCESSIBILITY_GESTURE_ENABLED_DURING_OOBE
- * setting which will be later logged via Settings Snapshot
- * else, log ACCESSIBILITY_SHORTCUT_REPORTED atom
- */
- private void logStemTriplePressAccessibilityTelemetry(ComponentName componentName) {
- if (!AccessibilityUtils.isUserSetupCompleted(mContext)) {
- Settings.Secure.putInt(mContext.getContentResolver(),
- Settings.System.WEAR_ACCESSIBILITY_GESTURE_ENABLED_DURING_OOBE, 1);
- } else {
- AccessibilityStatsLogUtils.logAccessibilityShortcutActivated(mContext, componentName,
- ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__A11Y_WEAR_TRIPLE_PRESS_GESTURE,
- /* serviceEnabled= */ true);
- }
- }
-
- private ComponentName getTalkbackComponent() {
- AccessibilityManager accessibilityManager = mContext.getSystemService(
- AccessibilityManager.class);
- List<AccessibilityServiceInfo> serviceInfos =
- accessibilityManager.getInstalledAccessibilityServiceList();
-
- for (AccessibilityServiceInfo service : serviceInfos) {
- final ServiceInfo serviceInfo = service.getResolveInfo().serviceInfo;
- if (isTalkback(serviceInfo)) {
- return new ComponentName(serviceInfo.packageName, serviceInfo.name);
- }
- }
- return null;
- }
-
- private boolean isTalkback(ServiceInfo info) {
- String label = info.loadLabel(mPackageManager).toString();
- return label.equals(TALKBACK_LABEL);
- }
-
- /**
* Load most recent task (expect current task) and bring it to the front.
*/
void performStemPrimaryDoublePressSwitchToRecentTask() {
@@ -1731,12 +1670,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
case TRIPLE_PRESS_PRIMARY_NOTHING:
break;
case TRIPLE_PRESS_PRIMARY_TOGGLE_ACCESSIBILITY:
- if (Settings.System.getIntForUser(
- mContext.getContentResolver(),
- Settings.System.WEAR_ACCESSIBILITY_GESTURE_ENABLED,
- /* def= */ 0,
- UserHandle.USER_CURRENT)
- == 1) {
+ if (mTalkbackShortcutController.isTalkBackShortcutGestureEnabled()) {
return 3;
}
break;
@@ -2252,6 +2186,10 @@ public class PhoneWindowManager implements WindowManagerPolicy {
ButtonOverridePermissionChecker getButtonOverridePermissionChecker() {
return new ButtonOverridePermissionChecker();
}
+
+ TalkbackShortcutController getTalkbackShortcutController() {
+ return new TalkbackShortcutController(mContext);
+ }
}
/** {@inheritDoc} */
@@ -2515,6 +2453,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
mKeyguardDrawnTimeout = mContext.getResources().getInteger(
com.android.internal.R.integer.config_keyguardDrawnTimeout);
mKeyguardDelegate = injector.getKeyguardServiceDelegate();
+ mTalkbackShortcutController = injector.getTalkbackShortcutController();
initKeyCombinationRules();
initSingleKeyGestureRules(injector.getLooper());
mButtonOverridePermissionChecker = injector.getButtonOverridePermissionChecker();
@@ -3709,7 +3648,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
case KeyEvent.KEYCODE_LANGUAGE_SWITCH:
if (firstDown) {
int direction = (metaState & KeyEvent.META_SHIFT_MASK) != 0 ? -1 : 1;
- sendSwitchKeyboardLayout(event, direction);
+ sendSwitchKeyboardLayout(event, focusedToken, direction);
logKeyboardSystemsEvent(event, KeyboardLogEvent.LANGUAGE_SWITCH);
return true;
}
@@ -3978,7 +3917,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
+ ", policyFlags=" + policyFlags);
}
- if (interceptUnhandledKey(event)) {
+ if (interceptUnhandledKey(event, focusedToken)) {
return null;
}
@@ -4036,7 +3975,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
return fallbackEvent;
}
- private boolean interceptUnhandledKey(KeyEvent event) {
+ private boolean interceptUnhandledKey(KeyEvent event, IBinder focusedToken) {
final int keyCode = event.getKeyCode();
final int repeatCount = event.getRepeatCount();
final boolean down = event.getAction() == KeyEvent.ACTION_DOWN;
@@ -4049,7 +3988,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
if (KeyEvent.metaStateHasModifiers(metaState & ~KeyEvent.META_SHIFT_MASK,
KeyEvent.META_CTRL_ON)) {
int direction = (metaState & KeyEvent.META_SHIFT_MASK) != 0 ? -1 : 1;
- sendSwitchKeyboardLayout(event, direction);
+ sendSwitchKeyboardLayout(event, focusedToken, direction);
return true;
}
}
@@ -4105,16 +4044,22 @@ public class PhoneWindowManager implements WindowManagerPolicy {
}
}
- private void sendSwitchKeyboardLayout(@NonNull KeyEvent event, int direction) {
- mHandler.obtainMessage(MSG_SWITCH_KEYBOARD_LAYOUT, event.getDeviceId(),
- direction).sendToTarget();
+ private void sendSwitchKeyboardLayout(@NonNull KeyEvent event,
+ @Nullable IBinder focusedToken, int direction) {
+ SwitchKeyboardLayoutMessageObject object =
+ new SwitchKeyboardLayoutMessageObject(event, focusedToken, direction);
+ mHandler.obtainMessage(MSG_SWITCH_KEYBOARD_LAYOUT, object).sendToTarget();
}
- private void handleSwitchKeyboardLayout(int deviceId, int direction) {
+ private void handleSwitchKeyboardLayout(@NonNull KeyEvent event, int direction,
+ IBinder focusedToken) {
if (FeatureFlagUtils.isEnabled(mContext, FeatureFlagUtils.SETTINGS_NEW_KEYBOARD_UI)) {
- InputMethodManagerInternal.get().switchKeyboardLayout(direction);
+ IBinder targetWindowToken =
+ mWindowManagerInternal.getTargetWindowTokenFromInputToken(focusedToken);
+ InputMethodManagerInternal.get().onSwitchKeyboardLayoutShortcut(direction,
+ event.getDisplayId(), targetWindowToken);
} else {
- mWindowManagerFuncs.switchKeyboardLayout(deviceId, direction);
+ mWindowManagerFuncs.switchKeyboardLayout(event.getDeviceId(), direction);
}
}
@@ -4124,7 +4069,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
if ((actions & ACTION_PASS_TO_USER) != 0) {
long delayMillis = interceptKeyBeforeDispatching(
focusedToken, fallbackEvent, policyFlags);
- if (delayMillis == 0 && !interceptUnhandledKey(fallbackEvent)) {
+ if (delayMillis == 0 && !interceptUnhandledKey(fallbackEvent, focusedToken)) {
return true;
}
}
diff --git a/services/core/java/com/android/server/policy/TalkbackShortcutController.java b/services/core/java/com/android/server/policy/TalkbackShortcutController.java
new file mode 100644
index 000000000000..906da2f4cdf5
--- /dev/null
+++ b/services/core/java/com/android/server/policy/TalkbackShortcutController.java
@@ -0,0 +1,118 @@
+/*
+ * 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.server.policy;
+
+import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__A11Y_WEAR_TRIPLE_PRESS_GESTURE;
+
+import android.accessibilityservice.AccessibilityServiceInfo;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.pm.ServiceInfo;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.view.accessibility.AccessibilityManager;
+
+import com.android.internal.accessibility.util.AccessibilityStatsLogUtils;
+import com.android.internal.accessibility.util.AccessibilityUtils;
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.List;
+import java.util.Set;
+
+/**
+ * This class controls talkback shortcut related operations such as toggling, quering and
+ * logging.
+ */
+@VisibleForTesting
+class TalkbackShortcutController {
+ private static final String TALKBACK_LABEL = "TalkBack";
+ private final Context mContext;
+ private final PackageManager mPackageManager;
+
+ TalkbackShortcutController(Context context) {
+ mContext = context;
+ mPackageManager = mContext.getPackageManager();
+ }
+
+ /**
+ * A function that toggles talkback service.
+ *
+ * @return talkback state after toggle. {@code true} if talkback is enabled, {@code false} if
+ * talkback is disabled
+ */
+ boolean toggleTalkback(int userId) {
+ final Set<ComponentName> enabledServices =
+ AccessibilityUtils.getEnabledServicesFromSettings(mContext, userId);
+ ComponentName componentName = getTalkbackComponent();
+ boolean isTalkbackAlreadyEnabled = enabledServices.contains(componentName);
+
+ if (isTalkBackShortcutGestureEnabled()) {
+ isTalkbackAlreadyEnabled = !isTalkbackAlreadyEnabled;
+ AccessibilityUtils.setAccessibilityServiceState(mContext, componentName,
+ isTalkbackAlreadyEnabled);
+
+ // log stem triple press telemetry if it's a talkback enabled event.
+ if (componentName != null && isTalkbackAlreadyEnabled) {
+ logStemTriplePressAccessibilityTelemetry(componentName);
+ }
+ }
+ return isTalkbackAlreadyEnabled;
+ }
+
+ private ComponentName getTalkbackComponent() {
+ AccessibilityManager accessibilityManager = mContext.getSystemService(
+ AccessibilityManager.class);
+ List<AccessibilityServiceInfo> serviceInfos =
+ accessibilityManager.getInstalledAccessibilityServiceList();
+
+ for (AccessibilityServiceInfo service : serviceInfos) {
+ final ServiceInfo serviceInfo = service.getResolveInfo().serviceInfo;
+ if (isTalkback(serviceInfo)) {
+ return new ComponentName(serviceInfo.packageName, serviceInfo.name);
+ }
+ }
+ return null;
+ }
+
+ boolean isTalkBackShortcutGestureEnabled() {
+ return Settings.System.getIntForUser(mContext.getContentResolver(),
+ Settings.System.WEAR_ACCESSIBILITY_GESTURE_ENABLED,
+ /* def= */ 0, UserHandle.USER_CURRENT) == 1;
+ }
+
+ /**
+ * A function that logs stem triple press accessibility telemetry. If the user setup (Oobe)
+ * is not completed, set the WEAR_ACCESSIBILITY_GESTURE_ENABLED_DURING_OOBE setting which
+ * will be later logged via Settings Snapshot else, log ACCESSIBILITY_SHORTCUT_REPORTED atom
+ */
+ private void logStemTriplePressAccessibilityTelemetry(ComponentName componentName) {
+ if (!AccessibilityUtils.isUserSetupCompleted(mContext)) {
+ Settings.Secure.putInt(mContext.getContentResolver(),
+ Settings.System.WEAR_ACCESSIBILITY_GESTURE_ENABLED_DURING_OOBE, 1);
+ return;
+ }
+ AccessibilityStatsLogUtils.logAccessibilityShortcutActivated(mContext,
+ componentName,
+ ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__A11Y_WEAR_TRIPLE_PRESS_GESTURE,
+ /* serviceEnabled= */ true);
+ }
+
+ private boolean isTalkback(ServiceInfo info) {
+ return TALKBACK_LABEL.equals(info.loadLabel(mPackageManager).toString());
+ }
+}
diff --git a/services/core/java/com/android/server/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java
index eac4fc00b667..9a85c42e1a10 100644
--- a/services/core/java/com/android/server/trust/TrustManagerService.java
+++ b/services/core/java/com/android/server/trust/TrustManagerService.java
@@ -1608,14 +1608,12 @@ public class TrustManagerService extends SystemService {
user.name, user.id, user.flags);
if (!user.supportsSwitchToByUser()) {
final boolean locked;
- if (user.isProfile()) {
- if (mLockPatternUtils.isSeparateProfileChallengeEnabled(user.id)) {
- fout.print(" (profile with separate challenge)");
- locked = isDeviceLockedInner(user.id);
- } else {
- fout.print(" (profile with unified challenge)");
- locked = isDeviceLockedInner(resolveProfileParent(user.id));
- }
+ if (mLockPatternUtils.isProfileWithUnifiedChallenge(user.id)) {
+ fout.print(" (profile with unified challenge)");
+ locked = isDeviceLockedInner(resolveProfileParent(user.id));
+ } else if (mLockPatternUtils.isSeparateProfileChallengeEnabled(user.id)) {
+ fout.print(" (profile with separate challenge)");
+ locked = isDeviceLockedInner(user.id);
} else {
fout.println(" (user that cannot be switched to)");
locked = isDeviceLockedInner(user.id);
diff --git a/services/core/java/com/android/server/wm/ActivityMetricsLaunchObserver.java b/services/core/java/com/android/server/wm/ActivityMetricsLaunchObserver.java
index 81e5fbd564e0..769f01c34e17 100644
--- a/services/core/java/com/android/server/wm/ActivityMetricsLaunchObserver.java
+++ b/services/core/java/com/android/server/wm/ActivityMetricsLaunchObserver.java
@@ -142,8 +142,11 @@ public class ActivityMetricsLaunchObserver {
* if the launching activity is started from an existing launch sequence (trampoline)
* but cannot coalesce to the existing one, e.g. to a different display.
* @param name The launching activity name.
+ * @param temperature The temperature at which a launch sequence had started.
+ * @param userId The id of the user the activity is being launched for.
*/
- public void onActivityLaunched(long id, ComponentName name, @Temperature int temperature) {
+ public void onActivityLaunched(long id, ComponentName name, @Temperature int temperature,
+ int userId) {
}
/**
@@ -177,13 +180,15 @@ public class ActivityMetricsLaunchObserver {
* @param timestampNanos the timestamp of ActivityLaunchFinished event in nanoseconds.
* To compute the TotalTime duration, deduct the timestamp {@link #onIntentStarted}
* from {@code timestampNanos}.
+ * @param launchMode The activity launch mode.
*
* @apiNote The finishing activity isn't necessarily the same as the starting activity;
* in the case of a trampoline, multiple activities could've been started
* and only the latest activity that was top-most during first-frame drawn
* is reported here.
*/
- public void onActivityLaunchFinished(long id, ComponentName name, long timestampNanos) {
+ public void onActivityLaunchFinished(long id, ComponentName name, long timestampNanos,
+ int launchMode) {
}
/**
diff --git a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
index 7b20529ce5e1..78f501ad9fed 100644
--- a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
+++ b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
@@ -1737,7 +1737,8 @@ class ActivityMetricsLogger {
// Beginning a launch is timing sensitive and so should be observed as soon as possible.
mLaunchObserver.onActivityLaunched(info.mLaunchingState.mStartUptimeNs,
- info.mLastLaunchedActivity.mActivityComponent, temperature);
+ info.mLastLaunchedActivity.mActivityComponent, temperature,
+ info.mLastLaunchedActivity.mUserId);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
}
@@ -1774,7 +1775,8 @@ class ActivityMetricsLogger {
"MetricsLogger:launchObserverNotifyActivityLaunchFinished");
mLaunchObserver.onActivityLaunchFinished(info.mLaunchingState.mStartUptimeNs,
- info.mLastLaunchedActivity.mActivityComponent, timestampNs);
+ info.mLastLaunchedActivity.mActivityComponent, timestampNs,
+ info.mLastLaunchedActivity.launchMode);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
}
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 73edb4bed6ca..869bcc0f9b8f 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -1053,6 +1053,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
mWindowManager = wm;
mRootWindowContainer = wm.mRoot;
mWindowOrganizerController.mTransitionController.setWindowManager(wm);
+ mLifecycleManager.setWindowManager(wm);
mTempConfig.setToDefaults();
mTempConfig.setLocales(LocaleList.getDefault());
mConfigurationSeq = mTempConfig.seq = 1;
@@ -1274,7 +1275,6 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
@Nullable String callingFeatureId, Intent intent, String resolvedType,
IBinder resultTo, String resultWho, int requestCode, int startFlags,
ProfilerInfo profilerInfo, Bundle bOptions, int userId, boolean validateIncomingUser) {
-
final SafeActivityOptions opts = SafeActivityOptions.fromBundle(bOptions);
assertPackageMatchesCallingUid(callingPackage);
@@ -1315,7 +1315,6 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
.setActivityOptions(opts)
.setUserId(userId)
.execute();
-
}
@Override
diff --git a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
index 92665af1075b..4929df8061b2 100644
--- a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
+++ b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
@@ -20,6 +20,7 @@ import static android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND;
import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE;
import static android.os.Process.SYSTEM_UID;
import static android.provider.DeviceConfig.NAMESPACE_WINDOW_MANAGER;
@@ -49,7 +50,6 @@ import android.compat.annotation.EnabledAfter;
import android.content.ComponentName;
import android.content.Intent;
import android.content.pm.PackageManager;
-import android.os.Build;
import android.os.Process;
import android.os.UserHandle;
import android.provider.DeviceConfig;
@@ -86,7 +86,7 @@ public class BackgroundActivityStartController {
private static final int NO_PROCESS_UID = -1;
/** If enabled the creator will not allow BAL on its behalf by default. */
@ChangeId
- @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ @EnabledAfter(targetSdkVersion = UPSIDE_DOWN_CAKE)
private static final long DEFAULT_RESCIND_BAL_PRIVILEGES_FROM_PENDING_INTENT_CREATOR =
296478951;
public static final ActivityOptions ACTIVITY_OPTIONS_SYSTEM_DEFINED =
@@ -228,6 +228,7 @@ public class BackgroundActivityStartController {
private final Intent mIntent;
private final WindowProcessController mCallerApp;
private final WindowProcessController mRealCallerApp;
+ private final boolean mIsCallForResult;
private BalState(int callingUid, int callingPid, final String callingPackage,
int realCallingUid, int realCallingPid,
@@ -247,8 +248,10 @@ public class BackgroundActivityStartController {
mOriginatingPendingIntent = originatingPendingIntent;
mIntent = intent;
mRealCallingPackage = mService.getPackageNameIfUnique(realCallingUid, realCallingPid);
- if (originatingPendingIntent == null // not a PendingIntent
- || resultRecord != null // sent for result
+ mIsCallForResult = resultRecord != null;
+ if (balRequireOptInByPendingIntentCreator() // auto-opt in introduced with this feature
+ && (originatingPendingIntent == null // not a PendingIntent
+ || mIsCallForResult) // sent for result
) {
// grant BAL privileges unless explicitly opted out
mBalAllowedByPiCreatorWithHardening = mBalAllowedByPiCreator =
@@ -351,6 +354,19 @@ public class BackgroundActivityStartController {
return name + "[debugOnly]";
}
+ /** @return valid targetSdk or <code>-1</code> */
+ private int getTargetSdk(String packageName) {
+ if (packageName == null) {
+ return -1;
+ }
+ try {
+ PackageManager pm = mService.mContext.getPackageManager();
+ return pm.getTargetSdkVersion(packageName);
+ } catch (Exception e) {
+ return -1;
+ }
+ }
+
private boolean hasRealCaller() {
return mRealCallingUid != NO_PROCESS_UID;
}
@@ -368,6 +384,7 @@ public class BackgroundActivityStartController {
StringBuilder sb = new StringBuilder(2048);
sb.append("[callingPackage: ")
.append(getDebugPackageName(mCallingPackage, mCallingUid));
+ sb.append("; callingPackageTargetSdk: ").append(getTargetSdk(mCallingPackage));
sb.append("; callingUid: ").append(mCallingUid);
sb.append("; callingPid: ").append(mCallingPid);
sb.append("; appSwitchState: ").append(mAppSwitchState);
@@ -387,10 +404,13 @@ public class BackgroundActivityStartController {
.append(mBalAllowedByPiCreatorWithHardening);
sb.append("; resultIfPiCreatorAllowsBal: ").append(resultIfPiCreatorAllowsBal);
sb.append("; hasRealCaller: ").append(hasRealCaller());
+ sb.append("; isCallForResult: ").append(mIsCallForResult);
sb.append("; isPendingIntent: ").append(isPendingIntent());
if (hasRealCaller()) {
sb.append("; realCallingPackage: ")
.append(getDebugPackageName(mRealCallingPackage, mRealCallingUid));
+ sb.append("; realCallingPackageTargetSdk: ")
+ .append(getTargetSdk(mRealCallingPackage));
sb.append("; realCallingUid: ").append(mRealCallingUid);
sb.append("; realCallingPid: ").append(mRealCallingPid);
sb.append("; realCallingUidHasAnyVisibleWindow: ")
diff --git a/services/core/java/com/android/server/wm/ClientLifecycleManager.java b/services/core/java/com/android/server/wm/ClientLifecycleManager.java
index 8b282dd3e7a8..6b6388b10ce7 100644
--- a/services/core/java/com/android/server/wm/ClientLifecycleManager.java
+++ b/services/core/java/com/android/server/wm/ClientLifecycleManager.java
@@ -22,7 +22,13 @@ import android.app.servertransaction.ActivityLifecycleItem;
import android.app.servertransaction.ClientTransaction;
import android.app.servertransaction.ClientTransactionItem;
import android.os.Binder;
+import android.os.IBinder;
import android.os.RemoteException;
+import android.util.ArrayMap;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.window.flags.Flags;
/**
* Class that is able to combine multiple client lifecycle transition requests and/or callbacks,
@@ -31,8 +37,18 @@ import android.os.RemoteException;
* @see ClientTransaction
*/
class ClientLifecycleManager {
- // TODO(lifecycler): Implement building transactions or global transaction.
- // TODO(lifecycler): Use object pools for transactions and transaction items.
+
+ private static final String TAG = "ClientLifecycleManager";
+
+ /** Mapping from client process binder to its pending transaction. */
+ @VisibleForTesting
+ final ArrayMap<IBinder, ClientTransaction> mPendingTransactions = new ArrayMap<>();
+
+ private WindowManagerService mWms;
+
+ void setWindowManager(@NonNull WindowManagerService wms) {
+ mWms = wms;
+ }
/**
* Schedules a transaction, which may consist of multiple callbacks and a lifecycle request.
@@ -82,14 +98,24 @@ class ClientLifecycleManager {
*/
void scheduleTransactionItem(@NonNull IApplicationThread client,
@NonNull ClientTransactionItem transactionItem) throws RemoteException {
- // TODO(b/260873529): queue the transaction items.
- final ClientTransaction clientTransaction = ClientTransaction.obtain(client);
- if (transactionItem.isActivityLifecycleItem()) {
- clientTransaction.setLifecycleStateRequest((ActivityLifecycleItem) transactionItem);
+ // The behavior is different depending on the flag.
+ // When flag is on, we wait until RootWindowContainer#performSurfacePlacementNoTrace to
+ // dispatch all pending transactions at once.
+ if (Flags.bundleClientTransactionFlag()) {
+ final ClientTransaction clientTransaction = getOrCreatePendingTransaction(client);
+ clientTransaction.addTransactionItem(transactionItem);
+
+ onClientTransactionItemScheduledLocked(clientTransaction);
} else {
- clientTransaction.addCallback(transactionItem);
+ // TODO(b/260873529): cleanup after launch.
+ final ClientTransaction clientTransaction = ClientTransaction.obtain(client);
+ if (transactionItem.isActivityLifecycleItem()) {
+ clientTransaction.setLifecycleStateRequest((ActivityLifecycleItem) transactionItem);
+ } else {
+ clientTransaction.addCallback(transactionItem);
+ }
+ scheduleTransaction(clientTransaction);
}
- scheduleTransaction(clientTransaction);
}
/**
@@ -100,10 +126,67 @@ class ClientLifecycleManager {
void scheduleTransactionAndLifecycleItems(@NonNull IApplicationThread client,
@NonNull ClientTransactionItem transactionItem,
@NonNull ActivityLifecycleItem lifecycleItem) throws RemoteException {
- // TODO(b/260873529): replace with #scheduleTransactionItem after launch for cleanup.
- final ClientTransaction clientTransaction = ClientTransaction.obtain(client);
- clientTransaction.addCallback(transactionItem);
- clientTransaction.setLifecycleStateRequest(lifecycleItem);
+ // The behavior is different depending on the flag.
+ // When flag is on, we wait until RootWindowContainer#performSurfacePlacementNoTrace to
+ // dispatch all pending transactions at once.
+ if (Flags.bundleClientTransactionFlag()) {
+ final ClientTransaction clientTransaction = getOrCreatePendingTransaction(client);
+ clientTransaction.addTransactionItem(transactionItem);
+ clientTransaction.addTransactionItem(lifecycleItem);
+
+ onClientTransactionItemScheduledLocked(clientTransaction);
+ } else {
+ // TODO(b/260873529): cleanup after launch.
+ final ClientTransaction clientTransaction = ClientTransaction.obtain(client);
+ clientTransaction.addCallback(transactionItem);
+ clientTransaction.setLifecycleStateRequest(lifecycleItem);
+ scheduleTransaction(clientTransaction);
+ }
+ }
+
+ /** Executes all the pending transactions. */
+ void dispatchPendingTransactions() {
+ final int size = mPendingTransactions.size();
+ for (int i = 0; i < size; i++) {
+ final ClientTransaction transaction = mPendingTransactions.valueAt(i);
+ try {
+ scheduleTransaction(transaction);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to deliver transaction for " + transaction.getClient());
+ }
+ }
+ mPendingTransactions.clear();
+ }
+
+ @NonNull
+ private ClientTransaction getOrCreatePendingTransaction(@NonNull IApplicationThread client) {
+ final IBinder clientBinder = client.asBinder();
+ final ClientTransaction pendingTransaction = mPendingTransactions.get(clientBinder);
+ if (pendingTransaction != null) {
+ return pendingTransaction;
+ }
+
+ // Create new transaction if there is no existing.
+ final ClientTransaction transaction = ClientTransaction.obtain(client);
+ mPendingTransactions.put(clientBinder, transaction);
+ return transaction;
+ }
+
+ /** Must only be called with WM lock. */
+ private void onClientTransactionItemScheduledLocked(
+ @NonNull ClientTransaction clientTransaction) throws RemoteException {
+ // TODO(b/260873529): make sure WindowSurfacePlacer#requestTraversal is called before
+ // ClientTransaction scheduled when needed.
+
+ if (mWms != null && (mWms.mWindowPlacerLocked.isInLayout()
+ || mWms.mWindowPlacerLocked.isTraversalScheduled())) {
+ // The pending transactions will be dispatched when
+ // RootWindowContainer#performSurfacePlacementNoTrace.
+ return;
+ }
+
+ // Dispatch the pending transaction immediately.
+ mPendingTransactions.remove(clientTransaction.getClient().asBinder());
scheduleTransaction(clientTransaction);
}
}
diff --git a/services/core/java/com/android/server/wm/LaunchObserverRegistryImpl.java b/services/core/java/com/android/server/wm/LaunchObserverRegistryImpl.java
index 9cbc1bdcbeeb..4c73a415d3f3 100644
--- a/services/core/java/com/android/server/wm/LaunchObserverRegistryImpl.java
+++ b/services/core/java/com/android/server/wm/LaunchObserverRegistryImpl.java
@@ -84,10 +84,10 @@ class LaunchObserverRegistryImpl extends ActivityMetricsLaunchObserver implement
}
@Override
- public void onActivityLaunched(long id, ComponentName name, int temperature) {
+ public void onActivityLaunched(long id, ComponentName name, int temperature, int userId) {
mHandler.sendMessage(PooledLambda.obtainMessage(
LaunchObserverRegistryImpl::handleOnActivityLaunched,
- this, id, name, temperature));
+ this, id, name, temperature, userId));
}
@Override
@@ -97,10 +97,11 @@ class LaunchObserverRegistryImpl extends ActivityMetricsLaunchObserver implement
}
@Override
- public void onActivityLaunchFinished(long id, ComponentName name, long timestampNs) {
+ public void onActivityLaunchFinished(long id, ComponentName name, long timestampNs,
+ int launchMode) {
mHandler.sendMessage(PooledLambda.obtainMessage(
LaunchObserverRegistryImpl::handleOnActivityLaunchFinished,
- this, id, name, timestampNs));
+ this, id, name, timestampNs, launchMode));
}
@Override
@@ -137,10 +138,10 @@ class LaunchObserverRegistryImpl extends ActivityMetricsLaunchObserver implement
}
private void handleOnActivityLaunched(long id, ComponentName name,
- @Temperature int temperature) {
+ @Temperature int temperature, int userId) {
// Traverse start-to-end to meet the registerLaunchObserver multi-cast order guarantee.
for (int i = 0; i < mList.size(); i++) {
- mList.get(i).onActivityLaunched(id, name, temperature);
+ mList.get(i).onActivityLaunched(id, name, temperature, userId);
}
}
@@ -151,10 +152,11 @@ class LaunchObserverRegistryImpl extends ActivityMetricsLaunchObserver implement
}
}
- private void handleOnActivityLaunchFinished(long id, ComponentName name, long timestampNs) {
+ private void handleOnActivityLaunchFinished(long id, ComponentName name, long timestampNs,
+ int launchMode) {
// Traverse start-to-end to meet the registerLaunchObserver multi-cast order guarantee.
for (int i = 0; i < mList.size(); i++) {
- mList.get(i).onActivityLaunchFinished(id, name, timestampNs);
+ mList.get(i).onActivityLaunchFinished(id, name, timestampNs, launchMode);
}
}
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index d5aa2760d41a..9a75dae3569e 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -842,6 +842,9 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
handleResizingWindows();
clearFrameChangingWindows();
+ // Called after #handleResizingWindows to include WindowStateResizeItem if any.
+ mWmService.mAtmService.getLifecycleManager().dispatchPendingTransactions();
+
if (mWmService.mDisplayFrozen) {
ProtoLog.v(WM_DEBUG_ORIENTATION,
"With display frozen, orientationChangeComplete=%b",
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index fc92755c6550..5d019122d52e 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -73,6 +73,7 @@ import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityOptions;
+import android.app.IApplicationThread;
import android.app.ResultInfo;
import android.app.WindowConfiguration;
import android.app.servertransaction.ActivityResultItem;
@@ -103,6 +104,7 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.common.ProtoLog;
import com.android.server.am.HostingRecord;
import com.android.server.pm.pkg.AndroidPackage;
+import com.android.window.flags.Flags;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -1509,23 +1511,38 @@ class TaskFragment extends WindowContainer<WindowContainer> {
}
try {
- final ClientTransaction transaction = ClientTransaction.obtain(
- next.app.getThread());
+ final IApplicationThread appThread = next.app.getThread();
+ final ClientTransaction transaction = Flags.bundleClientTransactionFlag()
+ ? null
+ : ClientTransaction.obtain(appThread);
// Deliver all pending results.
- ArrayList<ResultInfo> a = next.results;
+ final ArrayList<ResultInfo> a = next.results;
if (a != null) {
final int size = a.size();
if (!next.finishing && size > 0) {
if (DEBUG_RESULTS) {
Slog.v(TAG_RESULTS, "Delivering results to " + next + ": " + a);
}
- transaction.addCallback(ActivityResultItem.obtain(next.token, a));
+ final ActivityResultItem activityResultItem = ActivityResultItem.obtain(
+ next.token, a);
+ if (transaction == null) {
+ mAtmService.getLifecycleManager().scheduleTransactionItem(
+ appThread, activityResultItem);
+ } else {
+ transaction.addCallback(activityResultItem);
+ }
}
}
if (next.newIntents != null) {
- transaction.addCallback(
- NewIntentItem.obtain(next.token, next.newIntents, true /* resume */));
+ final NewIntentItem newIntentItem = NewIntentItem.obtain(
+ next.token, next.newIntents, true /* resume */);
+ if (transaction == null) {
+ mAtmService.getLifecycleManager().scheduleTransactionItem(
+ appThread, newIntentItem);
+ } else {
+ transaction.addCallback(newIntentItem);
+ }
}
// Well the app will no longer be stopped.
@@ -1539,10 +1556,16 @@ class TaskFragment extends WindowContainer<WindowContainer> {
final int topProcessState = mAtmService.mTopProcessState;
next.app.setPendingUiCleanAndForceProcessStateUpTo(topProcessState);
next.abortAndClearOptionsAnimation();
- transaction.setLifecycleStateRequest(
- ResumeActivityItem.obtain(next.token, topProcessState,
- dc.isNextTransitionForward(), next.shouldSendCompatFakeFocus()));
- mAtmService.getLifecycleManager().scheduleTransaction(transaction);
+ final ResumeActivityItem resumeActivityItem = ResumeActivityItem.obtain(
+ next.token, topProcessState, dc.isNextTransitionForward(),
+ next.shouldSendCompatFakeFocus());
+ if (transaction == null) {
+ mAtmService.getLifecycleManager().scheduleTransactionItem(
+ appThread, resumeActivityItem);
+ } else {
+ transaction.setLifecycleStateRequest(resumeActivityItem);
+ mAtmService.getLifecycleManager().scheduleTransaction(transaction);
+ }
ProtoLog.d(WM_DEBUG_STATES, "resumeTopActivity: Resumed %s", next);
} catch (Exception e) {
diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java
index 808a11d4adae..516d37c0136a 100644
--- a/services/core/java/com/android/server/wm/WindowManagerInternal.java
+++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java
@@ -866,6 +866,11 @@ public abstract class WindowManagerInternal {
public abstract ImeTargetInfo onToggleImeRequested(boolean show,
@NonNull IBinder focusedToken, @NonNull IBinder requestToken, int displayId);
+ /**
+ * Returns the token to identify the target window that the IME is associated with.
+ */
+ public abstract @Nullable IBinder getTargetWindowTokenFromInputToken(IBinder inputToken);
+
/** The information of input method target when IME is requested to show or hide. */
public static class ImeTargetInfo {
public final String focusedWindowName;
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 10dd334ed50c..2125c63eed34 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -8552,6 +8552,12 @@ public class WindowManagerService extends IWindowManager.Stub
fromOrientations, toOrientations);
}
}
+
+ @Override
+ public @Nullable IBinder getTargetWindowTokenFromInputToken(IBinder inputToken) {
+ InputTarget inputTarget = WindowManagerService.this.getInputTargetFromToken(inputToken);
+ return inputTarget == null ? null : inputTarget.getWindowToken();
+ }
}
private final class ImeTargetVisibilityPolicyImpl extends ImeTargetVisibilityPolicy {
diff --git a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
index aa589024fdc6..dff718a4b7d5 100644
--- a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
+++ b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
@@ -210,6 +210,10 @@ class WindowSurfacePlacer {
return mInLayout;
}
+ boolean isTraversalScheduled() {
+ return mTraversalScheduled;
+ }
+
void requestTraversal() {
if (mTraversalScheduled) {
return;
diff --git a/services/core/jni/com_android_server_pm_PackageManagerShellCommandDataLoader.cpp b/services/core/jni/com_android_server_pm_PackageManagerShellCommandDataLoader.cpp
index 3d4f866948af..d66b9b956071 100644
--- a/services/core/jni/com_android_server_pm_PackageManagerShellCommandDataLoader.cpp
+++ b/services/core/jni/com_android_server_pm_PackageManagerShellCommandDataLoader.cpp
@@ -182,7 +182,7 @@ static inline IncFsSize verityTreeSizeForFile(IncFsSize fileSize) {
auto block_count = 1 + (fileSize - 1) / INCFS_DATA_FILE_BLOCK_SIZE;
auto hash_block_count = block_count;
- for (auto i = 0; hash_block_count > 1; i++) {
+ while (hash_block_count > 1) {
hash_block_count = (hash_block_count + hash_per_block - 1) / hash_per_block;
total_tree_block_count += hash_block_count;
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/BackgroundJobsControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/BackgroundJobsControllerTest.java
index 23886a17c890..00fe3d92413c 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/BackgroundJobsControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/BackgroundJobsControllerTest.java
@@ -25,6 +25,7 @@ import static com.android.server.job.controllers.JobStatus.CONSTRAINT_BACKGROUND
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -167,8 +168,12 @@ public class BackgroundJobsControllerTest {
}
private void setStoppedState(int uid, String pkgName, boolean stopped) {
- doReturn(stopped).when(mPackageManagerInternal).isPackageStopped(pkgName, uid);
- sendPackageStoppedBroadcast(uid, pkgName, stopped);
+ try {
+ doReturn(stopped).when(mPackageManagerInternal).isPackageStopped(pkgName, uid);
+ sendPackageStoppedBroadcast(uid, pkgName, stopped);
+ } catch (PackageManager.NameNotFoundException e) {
+ fail("Unable to set stopped state for unknown package: " + pkgName);
+ }
}
private void sendPackageStoppedBroadcast(int uid, String pkgName, boolean stopped) {
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java
index 2332988cc832..e5ecdc478df7 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java
@@ -62,6 +62,7 @@ import android.os.ParcelableException;
import android.os.UserHandle;
import android.platform.test.annotations.Presubmit;
import android.text.TextUtils;
+import android.util.SparseArray;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
@@ -174,6 +175,7 @@ public class PackageArchiverTest {
mUserState = new PackageUserStateImpl().setInstalled(true);
mPackageSetting.setUserState(mUserId, mUserState);
when(mPackageState.getUserStateOrDefault(eq(mUserId))).thenReturn(mUserState);
+ when(mPackageState.getUserStates()).thenReturn(new SparseArray<>());
when(mContext.getSystemService(LauncherApps.class)).thenReturn(mLauncherApps);
when(mContext.getSystemService(AppOpsManager.class)).thenReturn(
@@ -343,7 +345,7 @@ public class PackageArchiverTest {
@Test
public void archiveApp_appOptedOutOfArchiving() {
- when(mAppOpsManager.checkOp(
+ when(mAppOpsManager.checkOpNoThrow(
eq(AppOpsManager.OP_AUTO_REVOKE_PERMISSIONS_IF_UNUSED),
anyInt(), eq(PACKAGE))).thenReturn(MODE_IGNORED);
@@ -430,7 +432,7 @@ public class PackageArchiverTest {
@Test
public void isAppArchivable_appOptedOutOfArchiving()
throws PackageManager.NameNotFoundException {
- when(mAppOpsManager.checkOp(
+ when(mAppOpsManager.checkOpNoThrow(
eq(AppOpsManager.OP_AUTO_REVOKE_PERMISSIONS_IF_UNUSED),
anyInt(), eq(PACKAGE))).thenReturn(MODE_IGNORED);
@@ -551,22 +553,12 @@ public class PackageArchiverTest {
when(mComputer.getPackageStateFiltered(eq(PACKAGE), anyInt(), anyInt())).thenReturn(
null);
- Exception e = assertThrows(
- ParcelableException.class,
- () -> mArchiveManager.getArchivedAppIcon(PACKAGE, UserHandle.CURRENT));
- assertThat(e.getCause()).isInstanceOf(PackageManager.NameNotFoundException.class);
- assertThat(e.getCause()).hasMessageThat().isEqualTo(
- String.format("Package %s not found.", PACKAGE));
+ assertThat(mArchiveManager.getArchivedAppIcon(PACKAGE, UserHandle.CURRENT)).isNull();
}
@Test
public void getArchivedAppIcon_notArchived() {
- Exception e = assertThrows(
- ParcelableException.class,
- () -> mArchiveManager.getArchivedAppIcon(PACKAGE, UserHandle.CURRENT));
- assertThat(e.getCause()).isInstanceOf(PackageManager.NameNotFoundException.class);
- assertThat(e.getCause()).hasMessageThat().isEqualTo(
- String.format("Package %s is not currently archived.", PACKAGE));
+ assertThat(mArchiveManager.getArchivedAppIcon(PACKAGE, UserHandle.CURRENT)).isNull();
}
@Test
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/PackageHelperTestBase.kt b/services/tests/mockingservicestests/src/com/android/server/pm/PackageHelperTestBase.kt
index a6ba5d4c3032..7b80aea80035 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/PackageHelperTestBase.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/PackageHelperTestBase.kt
@@ -36,6 +36,7 @@ import org.mockito.MockitoAnnotations
open class PackageHelperTestBase {
companion object {
+ const val PLATFORM_PACKAGE_NAME = "android"
const val TEST_PACKAGE_1 = "com.android.test.package1"
const val TEST_PACKAGE_2 = "com.android.test.package2"
const val DEVICE_OWNER_PACKAGE = "com.android.test.owner"
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt b/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt
index 147303363200..ae53e707a7cc 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt
@@ -283,6 +283,72 @@ class SuspendPackageHelperTest : PackageHelperTestBase() {
}
@Test
+ fun getSuspendingPackagePrecedence() {
+ val launcherExtras = PersistableBundle()
+ launcherExtras.putString(TEST_PACKAGE_2, TEST_PACKAGE_2)
+ val targetPackages = arrayOf(TEST_PACKAGE_2)
+ // Suspend.
+ var failedNames = suspendPackageHelper.setPackagesSuspended(pms.snapshotComputer(),
+ targetPackages, true /* suspended */, null /* appExtras */, launcherExtras,
+ null /* dialogInfo */, DEVICE_OWNER_PACKAGE, TEST_USER_ID, deviceOwnerUid,
+ false /* quarantined */)
+ assertThat(failedNames).isEmpty()
+ testHandler.flush()
+
+ assertThat(suspendPackageHelper.getSuspendingPackage(pms.snapshotComputer(),
+ TEST_PACKAGE_2, TEST_USER_ID, deviceOwnerUid)).isEqualTo(DEVICE_OWNER_PACKAGE)
+
+ // Suspend by system.
+ failedNames = suspendPackageHelper.setPackagesSuspended(pms.snapshotComputer(),
+ targetPackages, true /* suspended */, null /* appExtras */, launcherExtras,
+ null /* dialogInfo */, PLATFORM_PACKAGE_NAME, TEST_USER_ID, deviceOwnerUid,
+ false /* quarantined */)
+ assertThat(failedNames).isEmpty()
+ testHandler.flush()
+
+ assertThat(suspendPackageHelper.getSuspendingPackage(pms.snapshotComputer(),
+ TEST_PACKAGE_2, TEST_USER_ID, deviceOwnerUid)).isEqualTo(PLATFORM_PACKAGE_NAME)
+
+ // QAS by package1.
+ failedNames = suspendPackageHelper.setPackagesSuspended(pms.snapshotComputer(),
+ targetPackages, true /* suspended */, null /* appExtras */, launcherExtras,
+ null /* dialogInfo */, TEST_PACKAGE_1, TEST_USER_ID, deviceOwnerUid,
+ true /* quarantined */)
+ assertThat(failedNames).isEmpty()
+ testHandler.flush()
+
+ assertThat(suspendPackageHelper.getSuspendingPackage(pms.snapshotComputer(),
+ TEST_PACKAGE_2, TEST_USER_ID, deviceOwnerUid)).isEqualTo(TEST_PACKAGE_1)
+
+ // Un-QAS by package1.
+ suspendPackageHelper.removeSuspensionsBySuspendingPackage(pms.snapshotComputer(),
+ targetPackages, { suspendingPackage -> suspendingPackage == TEST_PACKAGE_1 },
+ TEST_USER_ID)
+ testHandler.flush()
+
+ assertThat(suspendPackageHelper.getSuspendingPackage(pms.snapshotComputer(),
+ TEST_PACKAGE_2, TEST_USER_ID, deviceOwnerUid)).isEqualTo(PLATFORM_PACKAGE_NAME)
+
+ // Un-suspend by system.
+ suspendPackageHelper.removeSuspensionsBySuspendingPackage(pms.snapshotComputer(),
+ targetPackages, { suspendingPackage -> suspendingPackage == PLATFORM_PACKAGE_NAME },
+ TEST_USER_ID)
+ testHandler.flush()
+
+ assertThat(suspendPackageHelper.getSuspendingPackage(pms.snapshotComputer(),
+ TEST_PACKAGE_2, TEST_USER_ID, deviceOwnerUid)).isEqualTo(DEVICE_OWNER_PACKAGE)
+
+ // Unsuspend.
+ suspendPackageHelper.removeSuspensionsBySuspendingPackage(pms.snapshotComputer(),
+ targetPackages, { suspendingPackage -> suspendingPackage == DEVICE_OWNER_PACKAGE },
+ TEST_USER_ID)
+ testHandler.flush()
+
+ assertThat(suspendPackageHelper.getSuspendingPackage(pms.snapshotComputer(),
+ TEST_PACKAGE_2, TEST_USER_ID, deviceOwnerUid)).isNull()
+ }
+
+ @Test
fun getSuspendedDialogInfo() {
val dialogInfo = SuspendDialogInfo.Builder()
.setTitle(TEST_PACKAGE_1).build()
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
index a2a8424881d4..0973d46283ed 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
@@ -23,6 +23,7 @@ import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_1;
import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_2;
import static com.android.server.hdmi.Constants.ADDR_RECORDER_1;
import static com.android.server.hdmi.Constants.ADDR_TV;
+import static com.android.server.hdmi.HdmiCecLocalDevice.ActiveSource;
import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC;
import static com.google.common.truth.Truth.assertThat;
@@ -1712,13 +1713,14 @@ public class HdmiCecLocalDeviceTvTest {
HdmiCecMessageBuilder.buildRequestActiveSource(ADDR_TV);
HdmiCecMessage activeSourceFromTv =
HdmiCecMessageBuilder.buildActiveSource(ADDR_TV, 0x0000);
-
mHdmiControlService.getHdmiCecNetwork().clearLocalDevices();
mNativeWrapper.setPollAddressResponse(ADDR_PLAYBACK_1, SendMessageResult.SUCCESS);
mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
mTestLooper.dispatchAll();
assertThat(mNativeWrapper.getResultMessages()).contains(requestActiveSource);
+ assertThat(mHdmiControlService.getLocalActiveSource()).isEqualTo(
+ new ActiveSource(Constants.ADDR_INVALID, Constants.INVALID_PHYSICAL_ADDRESS));
mNativeWrapper.clearResultMessages();
mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS);
mTestLooper.dispatchAll();
@@ -1728,8 +1730,64 @@ public class HdmiCecLocalDeviceTvTest {
mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS);
mTestLooper.dispatchAll();
assertThat(mNativeWrapper.getResultMessages()).contains(activeSourceFromTv);
+ assertThat(mHdmiControlService.getLocalActiveSource()).isEqualTo(
+ new ActiveSource(mTvLogicalAddress, mTvPhysicalAddress));
+ }
+
+ @Test
+ public void requestActiveSourceActionComplete_validLocalActiveSource_doNotSendActiveSource() {
+ HdmiCecMessage requestActiveSource =
+ HdmiCecMessageBuilder.buildRequestActiveSource(ADDR_TV);
+ HdmiCecMessage activeSourceFromTv =
+ HdmiCecMessageBuilder.buildActiveSource(ADDR_TV, 0x0000);
+ mHdmiControlService.getHdmiCecNetwork().clearLocalDevices();
+ mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
+ mTestLooper.dispatchAll();
+
+ assertThat(mNativeWrapper.getResultMessages()).contains(requestActiveSource);
+ mHdmiControlService.setActiveSource(mTvLogicalAddress, mTvPhysicalAddress,
+ "HdmiCecLocalDeviceTvTest");
+ mNativeWrapper.clearResultMessages();
+ mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS * 2);
+ mTestLooper.dispatchAll();
+
+ assertThat(mNativeWrapper.getResultMessages()).doesNotContain(activeSourceFromTv);
+ }
+
+ @Test
+ public void onAddressAllocated_startRequestActiveSourceAction_cancelOnDeviceSelect() {
+ HdmiCecMessage requestActiveSource =
+ HdmiCecMessageBuilder.buildRequestActiveSource(ADDR_TV);
+ HdmiCecMessage activeSourceFromTv =
+ HdmiCecMessageBuilder.buildActiveSource(ADDR_TV, 0x0000);
+ mHdmiControlService.getHdmiCecNetwork().clearLocalDevices();
+ mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
+ HdmiDeviceInfo playbackDevice = HdmiDeviceInfo.cecDeviceBuilder()
+ .setLogicalAddress(ADDR_PLAYBACK_1)
+ .setPhysicalAddress(0x1000)
+ .setPortId(PORT_1)
+ .setDeviceType(HdmiDeviceInfo.DEVICE_PLAYBACK)
+ .setVendorId(0x1234)
+ .setDisplayName("Playback 1")
+ .setDevicePowerStatus(HdmiControlManager.POWER_STATUS_ON)
+ .setCecVersion(HdmiControlManager.HDMI_CEC_VERSION_1_4_B)
+ .build();
+ mHdmiControlService.getHdmiCecNetwork().addCecDevice(playbackDevice);
+ mTestLooper.dispatchAll();
+
+ assertThat(mNativeWrapper.getResultMessages()).contains(requestActiveSource);
+ mNativeWrapper.clearResultMessages();
+ mHdmiCecLocalDeviceTv.deviceSelect(playbackDevice.getId(), null);
+ mTestLooper.dispatchAll();
+
+ mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS * 2);
+ mTestLooper.dispatchAll();
+
+ // RequestActiveSourceAction should be cancelled and TV will not broadcast <Active Source>.
+ assertThat(mNativeWrapper.getResultMessages()).doesNotContain(activeSourceFromTv);
}
+
@Test
public void newDeviceConnectedIfOnlyOneGiveOsdNameSent() {
mHdmiControlService.getHdmiCecNetwork().clearDeviceList();
@@ -1737,7 +1795,7 @@ public class HdmiCecLocalDeviceTvTest {
.isEmpty();
HdmiCecMessage reportPhysicalAddress =
HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
- ADDR_PLAYBACK_2, 0x1000, HdmiDeviceInfo.DEVICE_PLAYBACK);
+ ADDR_PLAYBACK_2, 0x1000, HdmiDeviceInfo.DEVICE_PLAYBACK);
HdmiCecMessage giveOsdName = HdmiCecMessageBuilder.buildGiveOsdNameCommand(
ADDR_TV, ADDR_PLAYBACK_2);
mNativeWrapper.onCecMessage(reportPhysicalAddress);
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java
index 18961c0feef9..ee076c6bcf4b 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java
@@ -140,9 +140,9 @@ public class LockSettingsServiceTestable extends LockSettingsService {
}
@Override
- public ManagedProfilePasswordCache getManagedProfilePasswordCache(
+ public UnifiedProfilePasswordCache getUnifiedProfilePasswordCache(
java.security.KeyStore ks) {
- return mock(ManagedProfilePasswordCache.class);
+ return mock(UnifiedProfilePasswordCache.class);
}
@Override
diff --git a/services/tests/uiservicestests/Android.bp b/services/tests/uiservicestests/Android.bp
index 6335b3bfade2..4e1c72af2727 100644
--- a/services/tests/uiservicestests/Android.bp
+++ b/services/tests/uiservicestests/Android.bp
@@ -46,6 +46,7 @@ android_test {
"testng",
"flag-junit",
"notification_flags_lib",
+ "platform-test-rules",
],
libs: [
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 36d55a48346e..14b551ae0b22 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -211,6 +211,9 @@ import android.os.WorkSource;
import android.permission.PermissionManager;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.SetFlagsRule;
+import android.platform.test.rule.DeniedDevices;
+import android.platform.test.rule.DeviceProduct;
+import android.platform.test.rule.LimitDevicesRule;
import android.provider.DeviceConfig;
import android.provider.MediaStore;
import android.provider.Settings;
@@ -281,6 +284,7 @@ import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
+import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestRule;
@@ -313,6 +317,7 @@ import java.util.function.Consumer;
@RunWith(AndroidTestingRunner.class)
@SuppressLint("GuardedBy") // It's ok for this test to access guarded methods from the service.
@RunWithLooper
+@DeniedDevices(denied = {DeviceProduct.CF_AUTO})
public class NotificationManagerServiceTest extends UiServiceTestCase {
private static final String TEST_CHANNEL_ID = "NotificationManagerServiceTestChannelId";
private static final String TEST_PACKAGE = "The.name.is.Package.Test.Package";
@@ -331,6 +336,9 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
private final int mUid = Binder.getCallingUid();
private final @UserIdInt int mUserId = UserHandle.getUserId(mUid);
+ @ClassRule
+ public static final LimitDevicesRule sLimitDevicesRule = new LimitDevicesRule();
+
@Rule
public TestRule compatChangeRule = new PlatformCompatChangeRule();
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
index 646ee3f6d206..1aea56cd8f9f 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
@@ -80,6 +80,7 @@ import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.notNull;
import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
@@ -265,10 +266,12 @@ public class ZenModeHelperTest extends UiServiceTestCase {
.thenReturn(CUSTOM_PKG_UID);
when(mPackageManager.getPackagesForUid(anyInt())).thenReturn(
new String[] {pkg});
- ApplicationInfo mockAppInfo = mock(ApplicationInfo.class);
- when(mockAppInfo.loadLabel(any())).thenReturn(CUSTOM_APP_LABEL);
+
+ ApplicationInfo appInfoSpy = spy(new ApplicationInfo());
+ appInfoSpy.icon = ICON_RES_ID;
+ when(appInfoSpy.loadLabel(any())).thenReturn(CUSTOM_APP_LABEL);
when(mPackageManager.getApplicationInfo(eq(CUSTOM_PKG_NAME), anyInt()))
- .thenReturn(mockAppInfo);
+ .thenReturn(appInfoSpy);
mZenModeHelper.mPm = mPackageManager;
mZenModeEventLogger.reset();
@@ -3753,6 +3756,10 @@ public class ZenModeHelperTest extends UiServiceTestCase {
rule.zenPolicy = policy;
rule.pkg = ownerPkg;
rule.name = CUSTOM_APP_LABEL;
+ rule.iconResName = ICON_RES_NAME;
+ rule.triggerDescription = mContext.getString(R.string.zen_mode_implicit_trigger_description,
+ CUSTOM_APP_LABEL);
+ rule.type = AutomaticZenRule.TYPE_OTHER;
rule.enabled = true;
return rule;
}
diff --git a/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutTests.java b/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutTests.java
index 8d236eda5dc5..0382ca0d9fec 100644
--- a/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutTests.java
@@ -61,6 +61,7 @@ public class ModifierShortcutTests extends ShortcutKeyTestBase {
META_SHORTCUTS.append(KEYCODE_P, Intent.CATEGORY_APP_MUSIC);
META_SHORTCUTS.append(KEYCODE_S, Intent.CATEGORY_APP_MESSAGING);
}
+ private static final int ANY_DISPLAY_ID = 123;
@Before
public void setUp() {
@@ -96,8 +97,9 @@ public class ModifierShortcutTests extends ShortcutKeyTestBase {
*/
@Test
public void testCtrlSpace() {
- sendKeyCombination(new int[]{KEYCODE_CTRL_LEFT, KEYCODE_SPACE}, 0);
- mPhoneWindowManager.assertSwitchKeyboardLayout(1);
+ sendKeyCombination(new int[]{KEYCODE_CTRL_LEFT, KEYCODE_SPACE}, /* duration= */ 0,
+ ANY_DISPLAY_ID);
+ mPhoneWindowManager.assertSwitchKeyboardLayout(/* direction= */ 1, ANY_DISPLAY_ID);
}
/**
@@ -105,8 +107,9 @@ public class ModifierShortcutTests extends ShortcutKeyTestBase {
*/
@Test
public void testCtrlShiftSpace() {
- sendKeyCombination(new int[]{KEYCODE_CTRL_LEFT, KEYCODE_SHIFT_LEFT, KEYCODE_SPACE}, 0);
- mPhoneWindowManager.assertSwitchKeyboardLayout(-1);
+ sendKeyCombination(new int[]{KEYCODE_CTRL_LEFT, KEYCODE_SHIFT_LEFT, KEYCODE_SPACE},
+ /* duration= */ 0, ANY_DISPLAY_ID);
+ mPhoneWindowManager.assertSwitchKeyboardLayout(/* direction= */ -1, ANY_DISPLAY_ID);
}
/**
diff --git a/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java b/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java
index 9cdec2588501..157d1627d993 100644
--- a/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java
+++ b/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java
@@ -114,7 +114,7 @@ class ShortcutKeyTestBase {
}
}
- void sendKeyCombination(int[] keyCodes, long durationMillis, boolean longPress) {
+ void sendKeyCombination(int[] keyCodes, long durationMillis, boolean longPress, int displayId) {
final long downTime = mPhoneWindowManager.getCurrentTime();
final int count = keyCodes.length;
int metaState = 0;
@@ -124,7 +124,7 @@ class ShortcutKeyTestBase {
final KeyEvent event = new KeyEvent(downTime, downTime, KeyEvent.ACTION_DOWN, keyCode,
0 /*repeat*/, metaState, KeyCharacterMap.VIRTUAL_KEYBOARD, 0 /*scancode*/,
0 /*flags*/, InputDevice.SOURCE_KEYBOARD);
- event.setDisplayId(DEFAULT_DISPLAY);
+ event.setDisplayId(displayId);
interceptKey(event);
// The order is important here, metaState could be updated and applied to the next key.
metaState |= MODIFIER.getOrDefault(keyCode, 0);
@@ -142,7 +142,7 @@ class ShortcutKeyTestBase {
KeyEvent.ACTION_DOWN, keyCode, 1 /*repeat*/, metaState,
KeyCharacterMap.VIRTUAL_KEYBOARD, 0 /*scancode*/,
KeyEvent.FLAG_LONG_PRESS /*flags*/, InputDevice.SOURCE_KEYBOARD);
- nextDownEvent.setDisplayId(DEFAULT_DISPLAY);
+ nextDownEvent.setDisplayId(displayId);
interceptKey(nextDownEvent);
}
}
@@ -153,18 +153,23 @@ class ShortcutKeyTestBase {
final KeyEvent upEvent = new KeyEvent(downTime, eventTime, KeyEvent.ACTION_UP, keyCode,
0, metaState, KeyCharacterMap.VIRTUAL_KEYBOARD, 0 /*scancode*/, 0 /*flags*/,
InputDevice.SOURCE_KEYBOARD);
- upEvent.setDisplayId(DEFAULT_DISPLAY);
+ upEvent.setDisplayId(displayId);
interceptKey(upEvent);
metaState &= ~MODIFIER.getOrDefault(keyCode, 0);
}
}
void sendKeyCombination(int[] keyCodes, long durationMillis) {
- sendKeyCombination(keyCodes, durationMillis, false /* longPress */);
+ sendKeyCombination(keyCodes, durationMillis, false /* longPress */, DEFAULT_DISPLAY);
+ }
+
+ void sendKeyCombination(int[] keyCodes, long durationMillis, int displayId) {
+ sendKeyCombination(keyCodes, durationMillis, false /* longPress */, displayId);
}
void sendLongPressKeyCombination(int[] keyCodes) {
- sendKeyCombination(keyCodes, ViewConfiguration.getLongPressTimeout(), true /* longPress */);
+ sendKeyCombination(keyCodes, ViewConfiguration.getLongPressTimeout(), true /* longPress */,
+ DEFAULT_DISPLAY);
}
void sendKey(int keyCode) {
@@ -172,7 +177,7 @@ class ShortcutKeyTestBase {
}
void sendKey(int keyCode, boolean longPress) {
- sendKeyCombination(new int[]{keyCode}, 0 /*durationMillis*/, longPress);
+ sendKeyCombination(new int[]{keyCode}, 0 /*durationMillis*/, longPress, DEFAULT_DISPLAY);
}
private void interceptKey(KeyEvent keyEvent) {
diff --git a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
index d057226836a3..0678210e1d2f 100644
--- a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
+++ b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
@@ -166,12 +166,34 @@ class TestPhoneWindowManager {
@Mock
private PhoneWindowManager.ButtonOverridePermissionChecker mButtonOverridePermissionChecker;
+ @Mock private IBinder mInputToken;
+ @Mock private IBinder mImeTargetWindowToken;
+
private StaticMockitoSession mMockitoSession;
private OffsettableClock mClock = new OffsettableClock();
private TestLooper mTestLooper = new TestLooper(() -> mClock.now());
private HandlerThread mHandlerThread;
private Handler mHandler;
+ private boolean mIsTalkBackEnabled;
+
+ class TestTalkbackShortcutController extends TalkbackShortcutController {
+ TestTalkbackShortcutController(Context context) {
+ super(context);
+ }
+
+ @Override
+ boolean toggleTalkback(int currentUserId) {
+ mIsTalkBackEnabled = !mIsTalkBackEnabled;
+ return mIsTalkBackEnabled;
+ }
+
+ @Override
+ boolean isTalkBackShortcutGestureEnabled() {
+ return true;
+ }
+ }
+
private class TestInjector extends PhoneWindowManager.Injector {
TestInjector(Context context, WindowManagerPolicy.WindowManagerFuncs funcs) {
super(context, funcs, mTestLooper.getLooper());
@@ -197,6 +219,10 @@ class TestPhoneWindowManager {
PhoneWindowManager.ButtonOverridePermissionChecker getButtonOverridePermissionChecker() {
return mButtonOverridePermissionChecker;
}
+
+ TalkbackShortcutController getTalkbackShortcutController() {
+ return new TestTalkbackShortcutController(mContext);
+ }
}
TestPhoneWindowManager(Context context, boolean supportSettingsUpdate) {
@@ -304,6 +330,9 @@ class TestPhoneWindowManager {
doNothing().when(mPhoneWindowManager).finishedWakingUp(anyInt(), anyInt());
doNothing().when(mPhoneWindowManager).lockNow(any());
+ doReturn(mImeTargetWindowToken)
+ .when(mWindowManagerInternal).getTargetWindowTokenFromInputToken(mInputToken);
+
mPhoneWindowManager.init(new TestInjector(mContext, mWindowManagerFuncsImpl));
mPhoneWindowManager.systemReady();
mPhoneWindowManager.systemBooted();
@@ -342,12 +371,12 @@ class TestPhoneWindowManager {
}
long interceptKeyBeforeDispatching(KeyEvent event) {
- return mPhoneWindowManager.interceptKeyBeforeDispatching(null /*focusedToken*/,
- event, FLAG_INTERACTIVE);
+ return mPhoneWindowManager.interceptKeyBeforeDispatching(mInputToken, event,
+ FLAG_INTERACTIVE);
}
void dispatchUnhandledKey(KeyEvent event) {
- mPhoneWindowManager.dispatchUnhandledKey(null /*focusedToken*/, event, FLAG_INTERACTIVE);
+ mPhoneWindowManager.dispatchUnhandledKey(mInputToken, event, FLAG_INTERACTIVE);
}
long getCurrentTime() {
@@ -623,14 +652,16 @@ class TestPhoneWindowManager {
verify(mStatusBarManagerInternal).startAssist(any());
}
- void assertSwitchKeyboardLayout(int direction) {
+ void assertSwitchKeyboardLayout(int direction, int displayId) {
mTestLooper.dispatchAll();
if (FeatureFlagUtils.isEnabled(mContext, FeatureFlagUtils.SETTINGS_NEW_KEYBOARD_UI)) {
- verify(mInputMethodManagerInternal).switchKeyboardLayout(eq(direction));
+ verify(mInputMethodManagerInternal).onSwitchKeyboardLayoutShortcut(eq(direction),
+ eq(displayId), eq(mImeTargetWindowToken));
verify(mWindowManagerFuncsImpl, never()).switchKeyboardLayout(anyInt(), anyInt());
} else {
verify(mWindowManagerFuncsImpl).switchKeyboardLayout(anyInt(), eq(direction));
- verify(mInputMethodManagerInternal, never()).switchKeyboardLayout(anyInt());
+ verify(mInputMethodManagerInternal, never())
+ .onSwitchKeyboardLayoutShortcut(anyInt(), anyInt(), any());
}
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java
index 65e77dcd4ca9..d4ba3b25178d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java
@@ -124,7 +124,7 @@ public class ActivityMetricsLaunchObserverTests extends WindowTestsBase {
private void verifyOnActivityLaunched(ActivityRecord activity) {
final ArgumentCaptor<Long> idCaptor = ArgumentCaptor.forClass(Long.class);
verifyAsync(mLaunchObserver).onActivityLaunched(idCaptor.capture(),
- eq(activity.mActivityComponent), anyInt());
+ eq(activity.mActivityComponent), anyInt(), anyInt());
final long id = idCaptor.getValue();
setExpectedStartedId(id, activity);
mLastLaunchedIds.put(activity.mActivityComponent, id);
@@ -132,7 +132,7 @@ public class ActivityMetricsLaunchObserverTests extends WindowTestsBase {
private void verifyOnActivityLaunchFinished(ActivityRecord activity) {
verifyAsync(mLaunchObserver).onActivityLaunchFinished(eq(mExpectedStartedId),
- eq(activity.mActivityComponent), anyLong());
+ eq(activity.mActivityComponent), anyLong(), anyInt());
}
private void setExpectedStartedId(long id, Object reason) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index 7aa46a62b0f1..85c6f9e9b2fe 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -123,6 +123,7 @@ import android.app.ICompatCameraControlCallback;
import android.app.PictureInPictureParams;
import android.app.servertransaction.ActivityConfigurationChangeItem;
import android.app.servertransaction.ClientTransaction;
+import android.app.servertransaction.ClientTransactionItem;
import android.app.servertransaction.DestroyActivityItem;
import android.app.servertransaction.PauseActivityItem;
import android.app.servertransaction.WindowStateResizeItem;
@@ -169,6 +170,7 @@ import org.mockito.ArgumentCaptor;
import org.mockito.invocation.InvocationOnMock;
import java.util.ArrayList;
+import java.util.List;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
@@ -260,8 +262,18 @@ public class ActivityRecordTests extends WindowTestsBase {
final MutableBoolean pauseFound = new MutableBoolean(false);
doAnswer((InvocationOnMock invocationOnMock) -> {
final ClientTransaction transaction = invocationOnMock.getArgument(0);
- if (transaction.getLifecycleStateRequest() instanceof PauseActivityItem) {
- pauseFound.value = true;
+ final List<ClientTransactionItem> items = transaction.getTransactionItems();
+ if (items != null) {
+ for (ClientTransactionItem item : items) {
+ if (item instanceof PauseActivityItem) {
+ pauseFound.value = true;
+ break;
+ }
+ }
+ } else {
+ if (transaction.getLifecycleStateRequest() instanceof PauseActivityItem) {
+ pauseFound.value = true;
+ }
}
return null;
}).when(mClientLifecycleManager).scheduleTransaction(any());
@@ -279,6 +291,8 @@ public class ActivityRecordTests extends WindowTestsBase {
// If the activity is not focusable, it should move to paused.
activity.makeVisibleIfNeeded(null /* starting */, true /* reportToClient */);
+ mClientLifecycleManager.dispatchPendingTransactions();
+
assertTrue(activity.isState(PAUSING));
assertTrue(pauseFound.value);
@@ -1239,7 +1253,7 @@ public class ActivityRecordTests extends WindowTestsBase {
}
@Test
- public void testFinishActivityIfPossible_sendResultImmediately() {
+ public void testFinishActivityIfPossible_sendResultImmediately() throws RemoteException {
// Create activity representing the source of the activity result.
final ComponentName sourceComponent = ComponentName.createRelative(
DEFAULT_COMPONENT_PACKAGE_NAME, ".SourceActivity");
@@ -1270,12 +1284,8 @@ public class ActivityRecordTests extends WindowTestsBase {
targetActivity.finishIfPossible(0, new Intent(), null, "test", false /* oomAdj */);
- try {
- verify(mClientLifecycleManager, atLeastOnce()).scheduleTransaction(
- any(ClientTransaction.class));
- } catch (RemoteException ignored) {
- }
-
+ verify(mClientLifecycleManager, atLeastOnce()).scheduleTransactionItem(
+ any(), any(ClientTransactionItem.class));
assertNull(targetActivity.results);
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/ClientLifecycleManagerTests.java b/services/tests/wmtests/src/com/android/server/wm/ClientLifecycleManagerTests.java
index 04aa9815e698..7fdc5fc2cff6 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ClientLifecycleManagerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ClientLifecycleManagerTests.java
@@ -17,13 +17,15 @@
package com.android.server.wm;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+import static com.android.window.flags.Flags.FLAG_BUNDLE_CLIENT_TRANSACTION_FLAG;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.clearInvocations;
-import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.never;
@@ -31,12 +33,15 @@ import android.app.IApplicationThread;
import android.app.servertransaction.ActivityLifecycleItem;
import android.app.servertransaction.ClientTransaction;
import android.app.servertransaction.ClientTransactionItem;
+import android.os.IBinder;
import android.os.RemoteException;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
import androidx.test.filters.SmallTest;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
@@ -47,30 +52,47 @@ import org.mockito.MockitoAnnotations;
* Build/Install/Run:
* atest WmTests:ClientLifecycleManagerTests
*/
+// Suppress GuardedBy warning on unit tests
+@SuppressWarnings("GuardedBy")
@SmallTest
@Presubmit
public class ClientLifecycleManagerTests {
+ @Rule(order = 0)
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
+ @Rule(order = 1)
+ public final SystemServicesTestRule mSystemServices = new SystemServicesTestRule();
+
+ @Mock
+ private IBinder mClientBinder;
@Mock
private IApplicationThread mClient;
@Mock
private IApplicationThread.Stub mNonBinderClient;
@Mock
+ private ClientTransaction mTransaction;
+ @Mock
private ClientTransactionItem mTransactionItem;
@Mock
private ActivityLifecycleItem mLifecycleItem;
@Captor
private ArgumentCaptor<ClientTransaction> mTransactionCaptor;
+ private WindowManagerService mWms;
private ClientLifecycleManager mLifecycleManager;
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
+ mWms = mSystemServices.getWindowManagerService();
mLifecycleManager = spy(new ClientLifecycleManager());
+ mLifecycleManager.setWindowManager(mWms);
doReturn(true).when(mLifecycleItem).isActivityLifecycleItem();
+ doReturn(mClientBinder).when(mClient).asBinder();
+ doReturn(mNonBinderClient).when(mNonBinderClient).asBinder();
}
@Test
@@ -92,9 +114,11 @@ public class ClientLifecycleManagerTests {
}
@Test
- public void testScheduleTransactionItem() throws RemoteException {
- doNothing().when(mLifecycleManager).scheduleTransaction(any());
- mLifecycleManager.scheduleTransactionItem(mClient, mTransactionItem);
+ public void testScheduleTransactionItem_notBundle() throws RemoteException {
+ mSetFlagsRule.disableFlags(FLAG_BUNDLE_CLIENT_TRANSACTION_FLAG);
+
+ // Use non binder client to get non-recycled ClientTransaction.
+ mLifecycleManager.scheduleTransactionItem(mNonBinderClient, mTransactionItem);
verify(mLifecycleManager).scheduleTransaction(mTransactionCaptor.capture());
ClientTransaction transaction = mTransactionCaptor.getValue();
@@ -104,7 +128,7 @@ public class ClientLifecycleManagerTests {
assertNull(transaction.getTransactionItems());
clearInvocations(mLifecycleManager);
- mLifecycleManager.scheduleTransactionItem(mClient, mLifecycleItem);
+ mLifecycleManager.scheduleTransactionItem(mNonBinderClient, mLifecycleItem);
verify(mLifecycleManager).scheduleTransaction(mTransactionCaptor.capture());
transaction = mTransactionCaptor.getValue();
@@ -113,9 +137,54 @@ public class ClientLifecycleManagerTests {
}
@Test
- public void testScheduleTransactionAndLifecycleItems() throws RemoteException {
- doNothing().when(mLifecycleManager).scheduleTransaction(any());
- mLifecycleManager.scheduleTransactionAndLifecycleItems(mClient, mTransactionItem,
+ public void testScheduleTransactionItem() throws RemoteException {
+ mSetFlagsRule.enableFlags(FLAG_BUNDLE_CLIENT_TRANSACTION_FLAG);
+ spyOn(mWms.mWindowPlacerLocked);
+ doReturn(true).when(mWms.mWindowPlacerLocked).isTraversalScheduled();
+
+ // Use non binder client to get non-recycled ClientTransaction.
+ mLifecycleManager.scheduleTransactionItem(mNonBinderClient, mTransactionItem);
+
+ // When there is traversal scheduled, add transaction items to pending.
+ assertEquals(1, mLifecycleManager.mPendingTransactions.size());
+ ClientTransaction transaction =
+ mLifecycleManager.mPendingTransactions.get(mNonBinderClient);
+ assertEquals(1, transaction.getTransactionItems().size());
+ assertEquals(mTransactionItem, transaction.getTransactionItems().get(0));
+ assertNull(transaction.getCallbacks());
+ assertNull(transaction.getLifecycleStateRequest());
+ verify(mLifecycleManager, never()).scheduleTransaction(any());
+
+ // Add new transaction item to the existing pending.
+ clearInvocations(mLifecycleManager);
+ mLifecycleManager.scheduleTransactionItem(mNonBinderClient, mLifecycleItem);
+
+ assertEquals(1, mLifecycleManager.mPendingTransactions.size());
+ transaction = mLifecycleManager.mPendingTransactions.get(mNonBinderClient);
+ assertEquals(2, transaction.getTransactionItems().size());
+ assertEquals(mTransactionItem, transaction.getTransactionItems().get(0));
+ assertEquals(mLifecycleItem, transaction.getTransactionItems().get(1));
+ assertNull(transaction.getCallbacks());
+ assertNull(transaction.getLifecycleStateRequest());
+ verify(mLifecycleManager, never()).scheduleTransaction(any());
+ }
+
+ @Test
+ public void testScheduleTransactionItemUnlocked() throws RemoteException {
+ // Use non binder client to get non-recycled ClientTransaction.
+ mLifecycleManager.scheduleTransactionItemUnlocked(mNonBinderClient, mTransactionItem);
+
+ // Dispatch immediately.
+ assertTrue(mLifecycleManager.mPendingTransactions.isEmpty());
+ verify(mLifecycleManager).scheduleTransaction(any());
+ }
+
+ @Test
+ public void testScheduleTransactionAndLifecycleItems_notBundle() throws RemoteException {
+ mSetFlagsRule.disableFlags(FLAG_BUNDLE_CLIENT_TRANSACTION_FLAG);
+
+ // Use non binder client to get non-recycled ClientTransaction.
+ mLifecycleManager.scheduleTransactionAndLifecycleItems(mNonBinderClient, mTransactionItem,
mLifecycleItem);
verify(mLifecycleManager).scheduleTransaction(mTransactionCaptor.capture());
@@ -124,4 +193,36 @@ public class ClientLifecycleManagerTests {
assertEquals(mTransactionItem, transaction.getCallbacks().get(0));
assertEquals(mLifecycleItem, transaction.getLifecycleStateRequest());
}
+
+ @Test
+ public void testScheduleTransactionAndLifecycleItems() throws RemoteException {
+ mSetFlagsRule.enableFlags(FLAG_BUNDLE_CLIENT_TRANSACTION_FLAG);
+ spyOn(mWms.mWindowPlacerLocked);
+ doReturn(true).when(mWms.mWindowPlacerLocked).isTraversalScheduled();
+
+ // Use non binder client to get non-recycled ClientTransaction.
+ mLifecycleManager.scheduleTransactionAndLifecycleItems(mNonBinderClient, mTransactionItem,
+ mLifecycleItem);
+
+ assertEquals(1, mLifecycleManager.mPendingTransactions.size());
+ final ClientTransaction transaction =
+ mLifecycleManager.mPendingTransactions.get(mNonBinderClient);
+ assertEquals(2, transaction.getTransactionItems().size());
+ assertEquals(mTransactionItem, transaction.getTransactionItems().get(0));
+ assertEquals(mLifecycleItem, transaction.getTransactionItems().get(1));
+ assertNull(transaction.getCallbacks());
+ assertNull(transaction.getLifecycleStateRequest());
+ verify(mLifecycleManager, never()).scheduleTransaction(any());
+ }
+
+ @Test
+ public void testDispatchPendingTransactions() throws RemoteException {
+ mLifecycleManager.mPendingTransactions.put(mClientBinder, mTransaction);
+
+ mLifecycleManager.dispatchPendingTransactions();
+
+ assertTrue(mLifecycleManager.mPendingTransactions.isEmpty());
+ verify(mTransaction).schedule();
+ verify(mTransaction).recycle();
+ }
}
diff --git a/telephony/java/android/telephony/satellite/NtnSignalStrength.java b/telephony/java/android/telephony/satellite/NtnSignalStrength.java
index 16d765455d21..2fec423d1d65 100644
--- a/telephony/java/android/telephony/satellite/NtnSignalStrength.java
+++ b/telephony/java/android/telephony/satellite/NtnSignalStrength.java
@@ -86,6 +86,9 @@ public final class NtnSignalStrength implements Parcelable {
readFromParcel(in);
}
+ /**
+ * Returns notified non-terrestrial network signal strength level.
+ */
@FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
@NtnSignalStrengthLevel public int getLevel() {
return mLevel;
diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java
index 71786b308937..e09bd201f93e 100644
--- a/telephony/java/android/telephony/satellite/SatelliteManager.java
+++ b/telephony/java/android/telephony/satellite/SatelliteManager.java
@@ -34,6 +34,7 @@ import android.os.ICancellationSignal;
import android.os.OutcomeReceiver;
import android.os.RemoteException;
import android.os.ResultReceiver;
+import android.os.ServiceSpecificException;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyCallback;
import android.telephony.TelephonyFrameworkInitializer;
@@ -1919,7 +1920,6 @@ public final class SatelliteManager {
*/
@RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
@FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
- @NonNull
public void requestNtnSignalStrength(@NonNull @CallbackExecutor Executor executor,
@NonNull OutcomeReceiver<NtnSignalStrength, SatelliteException> callback) {
Objects.requireNonNull(executor);
@@ -1962,6 +1962,8 @@ public final class SatelliteManager {
/**
* Registers for NTN signal strength changed from satellite modem.
+ * If the registration operation is not successful, a {@link SatelliteException} that contains
+ * {@link SatelliteResult} will be thrown.
*
* <p>
* Note: This API is specifically designed for OEM enabled satellite connectivity only.
@@ -1973,16 +1975,14 @@ public final class SatelliteManager {
* @param executor The executor on which the callback will be called.
* @param callback The callback to handle the NTN signal strength changed event.
*
- * @return The {@link SatelliteResult} result of the operation.
- *
* @throws SecurityException if the caller doesn't have required permission.
* @throws IllegalStateException if the Telephony process is not currently available.
+ * @throws SatelliteException if the callback registration operation fails.
*/
@RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
@FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
- @SatelliteResult public int registerForNtnSignalStrengthChanged(
- @NonNull @CallbackExecutor Executor executor,
- @NonNull NtnSignalStrengthCallback callback) {
+ public void registerForNtnSignalStrengthChanged(@NonNull @CallbackExecutor Executor executor,
+ @NonNull NtnSignalStrengthCallback callback) throws SatelliteException {
Objects.requireNonNull(executor);
Objects.requireNonNull(callback);
@@ -1999,16 +1999,18 @@ public final class SatelliteManager {
ntnSignalStrength)));
}
};
+ telephony.registerForNtnSignalStrengthChanged(mSubId, internalCallback);
sNtnSignalStrengthCallbackMap.put(callback, internalCallback);
- return telephony.registerForNtnSignalStrengthChanged(mSubId, internalCallback);
} else {
throw new IllegalStateException("Telephony service is null.");
}
+ } catch (ServiceSpecificException ex) {
+ logd("registerForNtnSignalStrengthChanged() registration fails: " + ex.errorCode);
+ throw new SatelliteException(ex.errorCode);
} catch (RemoteException ex) {
loge("registerForNtnSignalStrengthChanged() RemoteException: " + ex);
ex.rethrowFromSystemServer();
}
- return SATELLITE_RESULT_REQUEST_FAILED;
}
/**
@@ -2025,6 +2027,8 @@ public final class SatelliteManager {
* {@link #registerForNtnSignalStrengthChanged(Executor, NtnSignalStrengthCallback)}.
*
* @throws SecurityException if the caller doesn't have required permission.
+ * @throws IllegalArgumentException if the callback is not valid or has already been
+ * unregistered.
* @throws IllegalStateException if the Telephony process is not currently available.
*/
@RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
@@ -2041,6 +2045,7 @@ public final class SatelliteManager {
telephony.unregisterForNtnSignalStrengthChanged(mSubId, internalCallback);
} else {
loge("unregisterForNtnSignalStrengthChanged: No internal callback.");
+ throw new IllegalArgumentException("callback is not valid");
}
} else {
throw new IllegalStateException("Telephony service is null.");
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index d7d28a17e7f2..397fb2d6db96 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -3101,16 +3101,21 @@ interface ITelephony {
void requestNtnSignalStrength(int subId, in ResultReceiver receiver);
/**
- * Registers for NTN signal strength changed from satellite modem.
+ * Registers for NTN signal strength changed from satellite modem. If the registration operation
+ * is not successful, a {@link SatelliteException} that contains {@link SatelliteResult} will be
+ * thrown.
*
* @param subId The subId of the subscription to request for.
- * @param callback The callback to handle the NTN signal strength changed event.
- *
- * @return The {@link SatelliteResult} result of the operation.
+ * @param callback The callback to handle the NTN signal strength changed event. If the
+ * operation is successful, {@link NtnSignalStrengthCallback#onNtnSignalStrengthChanged(
+ * NtnSignalStrength)} will return an instance of {@link NtnSignalStrength} with a value of
+ * {@link NtnSignalStrength.NtnSignalStrengthLevel} when the signal strength of non-terrestrial
+ * network has changed.
*/
@JavaPassthrough(annotation="@android.annotation.RequiresPermission("
+ "android.Manifest.permission.SATELLITE_COMMUNICATION)")
- int registerForNtnSignalStrengthChanged(int subId, in INtnSignalStrengthCallback callback);
+ void registerForNtnSignalStrengthChanged(int subId,
+ in INtnSignalStrengthCallback callback);
/**
* Unregisters for NTN signal strength changed from satellite modem.