summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java3
-rw-r--r--core/api/test-current.txt8
-rw-r--r--core/java/android/accessibilityservice/AccessibilityService.java4
-rw-r--r--core/java/android/accessibilityservice/AccessibilityServiceInfo.java2
-rw-r--r--core/java/android/app/LoadedApk.java10
-rw-r--r--core/java/android/content/ContentProvider.java15
-rw-r--r--core/java/android/content/ContentResolver.java18
-rw-r--r--core/java/android/content/Context.java2
-rw-r--r--core/java/android/hardware/face/FaceManager.java13
-rw-r--r--core/java/android/hardware/fingerprint/FingerprintManager.java9
-rw-r--r--core/java/android/hardware/usb/UsbPortStatus.java8
-rw-r--r--core/java/android/provider/Settings.java17
-rw-r--r--core/java/android/view/Display.java10
-rw-r--r--core/java/android/view/ViewRootImpl.java66
-rw-r--r--core/java/android/view/WindowManager.java2
-rw-r--r--core/java/android/window/SurfaceSyncGroup.java37
-rw-r--r--core/java/android/window/TaskConstants.java21
-rw-r--r--core/java/android/window/TransitionInfo.java35
-rw-r--r--core/proto/android/server/windowmanagertransitiontrace.proto2
-rw-r--r--core/res/AndroidManifest.xml8
-rw-r--r--core/tests/coretests/src/android/app/activity/ActivityThreadTest.java4
-rw-r--r--core/tests/coretests/src/android/hardware/face/FaceManagerTest.java13
-rw-r--r--core/tests/coretests/src/android/hardware/fingerprint/FingerprintManagerTest.java17
-rw-r--r--core/tests/mockingcoretests/src/android/view/DisplayTest.java30
-rw-r--r--data/etc/services.core.protolog.json6
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/common/DeviceStateManagerFoldingFeatureProducer.java12
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/common/RawFoldingFeatureProducer.java9
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/area/RearDisplayPresentation.java4
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java9
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/util/BaseDataProducer.java21
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java25
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java8
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/compatui/LetterboxEduWindowManager.java9
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/compatui/ReachabilityEduWindowManager.java8
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/compatui/RestartDialogWindowManager.java17
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java11
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java66
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java9
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java93
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java1
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java8
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java320
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestShellExecutor.java5
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTest.java49
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTransitionsTest.java182
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java245
-rw-r--r--libs/hwui/DamageAccumulator.cpp43
-rw-r--r--libs/hwui/DamageAccumulator.h4
-rw-r--r--libs/hwui/jni/android_graphics_RenderNode.cpp74
-rw-r--r--media/java/android/media/ImageUtils.java18
-rw-r--r--media/java/android/media/VolumeProvider.java41
-rw-r--r--media/java/android/media/session/MediaController.java59
-rw-r--r--media/java/android/media/soundtrigger/SoundTriggerManager.java103
-rw-r--r--packages/CompanionDeviceManager/res/layout/list_item_permission.xml3
-rw-r--r--packages/CompanionDeviceManager/res/values/strings.xml12
-rw-r--r--packages/CompanionDeviceManager/src/com/android/companiondevicemanager/PermissionListAdapter.java25
-rw-r--r--packages/PackageInstaller/src/com/android/packageinstaller/UninstallUninstalling.java4
-rw-r--r--[-rwxr-xr-x]packages/PackageInstaller/src/com/android/packageinstaller/UninstallerActivity.java8
-rw-r--r--packages/Shell/AndroidManifest.xml2
-rw-r--r--packages/SystemUI/Android.bp4
-rw-r--r--packages/SystemUI/AndroidManifest.xml3
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/FontInterpolator.kt16
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt12
-rw-r--r--packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt7
-rw-r--r--packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java6
-rw-r--r--packages/SystemUI/res/layout/status_bar.xml2
-rw-r--r--packages/SystemUI/res/values/strings.xml29
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/BiometricNotificationBroadcastReceiver.java72
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/BiometricNotificationDialogFactory.java177
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/BiometricNotificationService.java206
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/data/repository/KeyboardRepository.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt113
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgePanelParams.kt23
-rw-r--r--packages/SystemUI/src/com/android/systemui/smartspace/dagger/SmartspaceViewComponent.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt15
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewbinder/NotificationShelfViewBinder.kt65
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java13
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java20
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt51
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt45
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/model/SignalIconModel.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt58
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/wallet/controller/WalletContextualLocationsService.kt93
-rw-r--r--packages/SystemUI/src/com/android/systemui/wallet/controller/WalletContextualSuggestionsController.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/wallet/dagger/WalletModule.java8
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/animation/FontInterpolatorTest.kt25
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricNotificationDialogFactoryTest.java174
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricNotificationServiceTest.java152
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyboard/data/repository/KeyboardRepositoryTest.kt11
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt14
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/BackPanelControllerTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java7
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt29
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/wallet/controller/WalletContextualLocationsServiceTest.kt128
-rw-r--r--services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java23
-rw-r--r--services/accessibility/java/com/android/server/accessibility/magnification/AlwaysOnMagnificationFeatureFlag.java65
-rw-r--r--services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java104
-rw-r--r--services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java6
-rw-r--r--services/accessibility/java/com/android/server/accessibility/magnification/MagnificationFeatureFlagBase.java115
-rw-r--r--services/accessibility/java/com/android/server/accessibility/magnification/MagnificationThumbnail.java92
-rw-r--r--services/accessibility/java/com/android/server/accessibility/magnification/MagnificationThumbnailFeatureFlag.java46
-rw-r--r--services/companion/java/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncConnectionService.java77
-rw-r--r--services/companion/java/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncData.java188
-rw-r--r--services/companion/java/com/android/server/companion/datatransfer/contextsync/CrossDeviceCall.java44
-rw-r--r--services/companion/java/com/android/server/companion/virtual/InputController.java60
-rw-r--r--services/core/java/android/os/BatteryStatsInternal.java24
-rw-r--r--services/core/java/com/android/server/PackageWatchdog.java2
-rw-r--r--services/core/java/com/android/server/am/BatteryStatsService.java43
-rw-r--r--services/core/java/com/android/server/audio/AudioDeviceBroker.java13
-rw-r--r--services/core/java/com/android/server/audio/AudioService.java49
-rw-r--r--services/core/java/com/android/server/audio/AudioServiceEvents.java37
-rw-r--r--services/core/java/com/android/server/audio/AudioSystemAdapter.java11
-rw-r--r--services/core/java/com/android/server/audio/SpatializerHelper.java3
-rw-r--r--services/core/java/com/android/server/biometrics/OWNERS4
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/AuthSessionCoordinator.java8
-rw-r--r--services/core/java/com/android/server/display/AutomaticBrightnessController.java32
-rw-r--r--services/core/java/com/android/server/display/BrightnessMappingStrategy.java48
-rw-r--r--services/core/java/com/android/server/display/DisplayManagerService.java38
-rw-r--r--services/core/java/com/android/server/display/DisplayPowerController.java32
-rw-r--r--services/core/java/com/android/server/display/DisplayPowerController2.java27
-rw-r--r--services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java24
-rw-r--r--services/core/java/com/android/server/notification/ManagedServices.java111
-rw-r--r--services/core/java/com/android/server/pm/BroadcastHelper.java2
-rw-r--r--services/core/java/com/android/server/policy/PhoneWindowManager.java5
-rw-r--r--services/core/java/com/android/server/power/stats/wakeups/CpuWakeupStats.java17
-rw-r--r--services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java2
-rw-r--r--services/core/java/com/android/server/stats/pull/StatsPullAtomService.java62
-rw-r--r--services/core/java/com/android/server/wallpaper/WallpaperManagerService.java5
-rw-r--r--services/core/java/com/android/server/wm/ActivityTaskSupervisor.java35
-rw-r--r--services/core/java/com/android/server/wm/BLASTSyncEngine.java30
-rw-r--r--services/core/java/com/android/server/wm/DisplayContent.java3
-rw-r--r--services/core/java/com/android/server/wm/Letterbox.java4
-rw-r--r--services/core/java/com/android/server/wm/Task.java2
-rw-r--r--services/core/java/com/android/server/wm/TaskFragment.java31
-rw-r--r--services/core/java/com/android/server/wm/Transition.java7
-rw-r--r--services/core/java/com/android/server/wm/TransitionController.java112
-rw-r--r--services/core/java/com/android/server/wm/TransitionTracer.java3
-rw-r--r--services/core/java/com/android/server/wm/WallpaperWindowToken.java22
-rw-r--r--services/core/java/com/android/server/wm/WindowOrganizerController.java31
-rw-r--r--services/core/jni/com_android_server_companion_virtual_InputController.cpp42
-rw-r--r--services/core/jni/com_android_server_input_InputManagerService.cpp40
-rw-r--r--services/credentials/java/com/android/server/credentials/MetricUtilities.java21
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java195
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java1
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java74
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerControllerTest.java88
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/rollback/OWNERS3
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/rollback/RollbackPackageHealthObserverTest.java2
-rw-r--r--services/tests/servicestests/res/xml/irq_device_map_3.xml6
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java9
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationThumbnailTest.java24
-rw-r--r--services/tests/servicestests/src/com/android/server/audio/AudioDeviceBrokerTest.java2
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/sensors/AuthSessionCoordinatorTest.java10
-rw-r--r--services/tests/servicestests/src/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncConnectionServiceTest.java61
-rw-r--r--services/tests/servicestests/src/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncDataTest.java64
-rw-r--r--services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java34
-rw-r--r--services/tests/servicestests/src/com/android/server/display/BrightnessMappingStrategyTest.java30
-rw-r--r--services/tests/servicestests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java11
-rw-r--r--services/tests/servicestests/src/com/android/server/power/hint/HintManagerServiceTest.java53
-rw-r--r--services/tests/servicestests/src/com/android/server/power/stats/wakeups/CpuWakeupStatsTest.java81
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java47
-rw-r--r--services/tests/wmtests/AndroidManifest.xml6
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/SurfaceSyncGroupTests.java187
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TransitionTests.java131
-rw-r--r--telecomm/java/android/telecom/CallStreamingService.java26
-rw-r--r--telephony/java/android/telephony/AnomalyReporter.java2
-rw-r--r--telephony/java/android/telephony/TelephonyScanManager.java5
174 files changed, 5235 insertions, 1222 deletions
diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
index 3772960e8ac4..df1b66612ea2 100644
--- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
@@ -4873,8 +4873,7 @@ public class AlarmManagerService extends SystemService {
}
}
if (wakeupUids.size() > 0 && mBatteryStatsInternal != null) {
- mBatteryStatsInternal.noteCpuWakingActivity(
- BatteryStatsInternal.CPU_WAKEUP_SUBSYSTEM_ALARM, nowELAPSED,
+ mBatteryStatsInternal.noteWakingAlarmBatch(nowELAPSED,
wakeupUids.toArray());
}
deliverAlarmsLocked(triggerList, nowELAPSED);
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 9bbc4a67a79a..ae63816945e1 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -2075,6 +2075,14 @@ package android.media.soundtrigger {
public final class SoundTriggerManager {
method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public static android.media.soundtrigger.SoundTriggerInstrumentation attachInstrumentation(@NonNull java.util.concurrent.Executor, @NonNull android.media.soundtrigger.SoundTriggerInstrumentation.GlobalCallback);
+ method @NonNull public android.media.soundtrigger.SoundTriggerManager createManagerForModule(@NonNull android.hardware.soundtrigger.SoundTrigger.ModuleProperties);
+ method @NonNull public android.media.soundtrigger.SoundTriggerManager createManagerForTestModule();
+ method @NonNull public static java.util.List<android.hardware.soundtrigger.SoundTrigger.ModuleProperties> listModuleProperties();
+ method @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public int loadSoundModel(@NonNull android.hardware.soundtrigger.SoundTrigger.SoundModel);
+ }
+
+ public static class SoundTriggerManager.Model {
+ method @NonNull public android.hardware.soundtrigger.SoundTrigger.SoundModel getSoundModel();
}
}
diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java
index 08a1af47ee64..3d4b6bf019cb 100644
--- a/core/java/android/accessibilityservice/AccessibilityService.java
+++ b/core/java/android/accessibilityservice/AccessibilityService.java
@@ -209,10 +209,10 @@ import java.util.function.Consumer;
* The overlay will maintain the same relative position within the window bounds as the window
* moves. The overlay will also maintain the same relative position within the window bounds if
* the window is resized.
- * To attach an overlay to a window, use {@link attachAccessibilityOverlayToWindow}.
+ * To attach an overlay to a window, use {@link #attachAccessibilityOverlayToWindow}.
* Attaching an overlay to the display means that the overlay is independent of the active
* windows on that display.
- * To attach an overlay to a display, use {@link attachAccessibilityOverlayToDisplay}. </p>
+ * To attach an overlay to a display, use {@link #attachAccessibilityOverlayToDisplay}. </p>
* <p> When positioning an overlay that is attached to a window, the service must use window
* coordinates. In order to position an overlay on top of an existing UI element it is necessary
* to know the bounds of that element in window coordinates. To find the bounds in window
diff --git a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
index 808f25eb7cd9..d4a96b40bb4c 100644
--- a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
+++ b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
@@ -1018,7 +1018,7 @@ public class AccessibilityServiceInfo implements Parcelable {
*
* @param motionEventSources A bit mask of {@link android.view.InputDevice} sources.
* @see AccessibilityService#onMotionEvent
- * @see MotionEventSources
+ * @see #MotionEventSources
*/
public void setMotionEventSources(@MotionEventSources int motionEventSources) {
mMotionEventSources = motionEventSources;
diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java
index bf695311e89e..b5efb73225d6 100644
--- a/core/java/android/app/LoadedApk.java
+++ b/core/java/android/app/LoadedApk.java
@@ -1668,10 +1668,13 @@ public final class LoadedApk {
static final class ReceiverDispatcher {
final static class InnerReceiver extends IIntentReceiver.Stub {
+ final IApplicationThread mApplicationThread;
final WeakReference<LoadedApk.ReceiverDispatcher> mDispatcher;
final LoadedApk.ReceiverDispatcher mStrongRef;
- InnerReceiver(LoadedApk.ReceiverDispatcher rd, boolean strong) {
+ InnerReceiver(IApplicationThread thread, LoadedApk.ReceiverDispatcher rd,
+ boolean strong) {
+ mApplicationThread = thread;
mDispatcher = new WeakReference<LoadedApk.ReceiverDispatcher>(rd);
mStrongRef = strong ? rd : null;
}
@@ -1718,7 +1721,8 @@ public final class LoadedApk {
if (extras != null) {
extras.setAllowFds(false);
}
- mgr.finishReceiver(this, resultCode, data, extras, false, intent.getFlags());
+ mgr.finishReceiver(mApplicationThread.asBinder(), resultCode, data,
+ extras, false, intent.getFlags());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1825,7 +1829,7 @@ public final class LoadedApk {
}
mAppThread = appThread;
- mIIntentReceiver = new InnerReceiver(this, !registered);
+ mIIntentReceiver = new InnerReceiver(mAppThread, this, !registered);
mReceiver = receiver;
mContext = context;
mActivityThread = activityThread;
diff --git a/core/java/android/content/ContentProvider.java b/core/java/android/content/ContentProvider.java
index 31c02b886686..fa99b59888f5 100644
--- a/core/java/android/content/ContentProvider.java
+++ b/core/java/android/content/ContentProvider.java
@@ -2085,7 +2085,8 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall
*
* @param uri The URI whose file is to be opened.
* @param mode The string representation of the file mode. Can be "r", "w", "wt", "wa", "rw"
- * or "rwt". See{@link ParcelFileDescriptor#parseMode} for more details.
+ * or "rwt". Please note the exact implementation of these may differ for each
+ * Provider implementation - for example, "w" may or may not truncate.
*
* @return Returns a new ParcelFileDescriptor which you can use to access
* the file.
@@ -2147,7 +2148,8 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall
*
* @param uri The URI whose file is to be opened.
* @param mode The string representation of the file mode. Can be "r", "w", "wt", "wa", "rw"
- * or "rwt". See{@link ParcelFileDescriptor#parseMode} for more details.
+ * or "rwt". Please note the exact implementation of these may differ for each
+ * Provider implementation - for example, "w" may or may not truncate.
* @param signal A signal to cancel the operation in progress, or
* {@code null} if none. For example, if you are downloading a
* file from the network to service a "rw" mode request, you
@@ -2208,7 +2210,8 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall
*
* @param uri The URI whose file is to be opened.
* @param mode The string representation of the file mode. Can be "r", "w", "wt", "wa", "rw"
- * or "rwt". See{@link ParcelFileDescriptor#parseMode} for more details.
+ * or "rwt". Please note the exact implementation of these may differ for each
+ * Provider implementation - for example, "w" may or may not truncate.
*
* @return Returns a new AssetFileDescriptor which you can use to access
* the file.
@@ -2262,7 +2265,8 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall
*
* @param uri The URI whose file is to be opened.
* @param mode The string representation of the file mode. Can be "r", "w", "wt", "wa", "rw"
- * or "rwt". See{@link ParcelFileDescriptor#parseMode} for more details.
+ * or "rwt". Please note the exact implementation of these may differ for each
+ * Provider implementation - for example, "w" may or may not truncate.
* @param signal A signal to cancel the operation in progress, or
* {@code null} if none. For example, if you are downloading a
* file from the network to service a "rw" mode request, you
@@ -2294,7 +2298,8 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall
*
* @param uri The URI to be opened.
* @param mode The string representation of the file mode. Can be "r", "w", "wt", "wa", "rw"
- * or "rwt". See{@link ParcelFileDescriptor#parseMode} for more details.
+ * or "rwt". Please note the exact implementation of these may differ for each
+ * Provider implementation - for example, "w" may or may not truncate.
*
* @return Returns a new ParcelFileDescriptor that can be used by the
* client to access the file.
diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java
index feca7a022934..b2cd7e90f291 100644
--- a/core/java/android/content/ContentResolver.java
+++ b/core/java/android/content/ContentResolver.java
@@ -1536,7 +1536,8 @@ public abstract class ContentResolver implements ContentInterface {
/**
* Synonym for {@link #openOutputStream(Uri, String)
- * openOutputStream(uri, "w")}.
+ * openOutputStream(uri, "w")}. Please note the implementation of "w" is up to each
+ * Provider implementation and it may or may not truncate.
*
* @param uri The desired URI.
* @return an OutputStream or {@code null} if the provider recently crashed.
@@ -1562,7 +1563,8 @@ public abstract class ContentResolver implements ContentInterface {
*
* @param uri The desired URI.
* @param mode The string representation of the file mode. Can be "r", "w", "wt", "wa", "rw"
- * or "rwt". See{@link ParcelFileDescriptor#parseMode} for more details.
+ * or "rwt". Please note the exact implementation of these may differ for each
+ * Provider implementation - for example, "w" may or may not truncate.
* @return an OutputStream or {@code null} if the provider recently crashed.
* @throws FileNotFoundException if the provided URI could not be opened.
* @see #openAssetFileDescriptor(Uri, String)
@@ -1619,7 +1621,8 @@ public abstract class ContentResolver implements ContentInterface {
*
* @param uri The desired URI to open.
* @param mode The string representation of the file mode. Can be "r", "w", "wt", "wa", "rw"
- * or "rwt". See{@link ParcelFileDescriptor#parseMode} for more details.
+ * or "rwt". Please note the exact implementation of these may differ for each
+ * Provider implementation - for example, "w" may or may not truncate.
* @return Returns a new ParcelFileDescriptor pointing to the file or {@code null} if the
* provider recently crashed. You own this descriptor and are responsible for closing it
* when done.
@@ -1662,7 +1665,8 @@ public abstract class ContentResolver implements ContentInterface {
*
* @param uri The desired URI to open.
* @param mode The string representation of the file mode. Can be "r", "w", "wt", "wa", "rw"
- * or "rwt". See{@link ParcelFileDescriptor#parseMode} for more details.
+ * or "rwt". Please note the exact implementation of these may differ for each
+ * Provider implementation - for example, "w" may or may not truncate.
* @param cancellationSignal A signal to cancel the operation in progress,
* or null if none. If the operation is canceled, then
* {@link OperationCanceledException} will be thrown.
@@ -1756,7 +1760,8 @@ public abstract class ContentResolver implements ContentInterface {
*
* @param uri The desired URI to open.
* @param mode The string representation of the file mode. Can be "r", "w", "wt", "wa", "rw"
- * or "rwt". See{@link ParcelFileDescriptor#parseMode} for more details.
+ * or "rwt". Please note the exact implementation of these may differ for each
+ * Provider implementation - for example, "w" may or may not truncate.
* @return Returns a new ParcelFileDescriptor pointing to the file or {@code null} if the
* provider recently crashed. You own this descriptor and are responsible for closing it
* when done.
@@ -1810,7 +1815,8 @@ public abstract class ContentResolver implements ContentInterface {
*
* @param uri The desired URI to open.
* @param mode The string representation of the file mode. Can be "r", "w", "wt", "wa", "rw"
- * or "rwt". See{@link ParcelFileDescriptor#parseMode} for more details.
+ * or "rwt". Please note "w" is write only and "wt" is write and truncate.
+ * See{@link ParcelFileDescriptor#parseMode} for more details.
* @param cancellationSignal A signal to cancel the operation in progress, or null if
* none. If the operation is canceled, then
* {@link OperationCanceledException} will be thrown.
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 2b73afcd740f..c221d724c5a2 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -7613,7 +7613,7 @@ public abstract class Context {
* the device association is changed by the system.
* <p>
* The callback can be called when an app is moved to a different device and the {@code Context}
- * is not explicily associated with a specific device.
+ * is not explicitly associated with a specific device.
* </p>
* <p> When an application receives a device id update callback, this Context is guaranteed to
* also have an updated display ID(if any) and {@link Configuration}.
diff --git a/core/java/android/hardware/face/FaceManager.java b/core/java/android/hardware/face/FaceManager.java
index e0af9137db25..02212968cdb0 100644
--- a/core/java/android/hardware/face/FaceManager.java
+++ b/core/java/android/hardware/face/FaceManager.java
@@ -44,6 +44,7 @@ import android.os.PowerManager;
import android.os.RemoteException;
import android.os.Trace;
import android.os.UserHandle;
+import android.provider.Settings;
import android.util.Slog;
import android.view.Surface;
@@ -127,6 +128,11 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan
@Override // binder call
public void onRemoved(Face face, int remaining) {
mHandler.obtainMessage(MSG_REMOVED, remaining, 0, face).sendToTarget();
+ if (remaining == 0) {
+ Settings.Secure.putIntForUser(mContext.getContentResolver(),
+ Settings.Secure.FACE_UNLOCK_RE_ENROLL, 0,
+ UserHandle.USER_CURRENT);
+ }
}
@Override
@@ -342,6 +348,13 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan
return;
}
+ if (hardwareAuthToken == null) {
+ callback.onEnrollmentError(FACE_ERROR_UNABLE_TO_PROCESS,
+ getErrorString(mContext, FACE_ERROR_UNABLE_TO_PROCESS,
+ 0 /* vendorCode */));
+ return;
+ }
+
if (getEnrolledFaces(userId).size()
>= mContext.getResources().getInteger(R.integer.config_faceMaxTemplatesPerUser)) {
callback.onEnrollmentError(FACE_ERROR_HW_UNAVAILABLE,
diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java
index eb8136e39d29..01977f6195ff 100644
--- a/core/java/android/hardware/fingerprint/FingerprintManager.java
+++ b/core/java/android/hardware/fingerprint/FingerprintManager.java
@@ -712,6 +712,13 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing
return;
}
+ if (hardwareAuthToken == null) {
+ callback.onEnrollmentError(FINGERPRINT_ERROR_UNABLE_TO_PROCESS,
+ getErrorString(mContext, FINGERPRINT_ERROR_UNABLE_TO_PROCESS,
+ 0 /* vendorCode */));
+ return;
+ }
+
if (mService != null) {
try {
mEnrollmentCallback = callback;
@@ -832,7 +839,7 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing
}
/**
- * Removes all face templates for the given user.
+ * Removes all fingerprint templates for the given user.
* @hide
*/
@RequiresPermission(MANAGE_FINGERPRINT)
diff --git a/core/java/android/hardware/usb/UsbPortStatus.java b/core/java/android/hardware/usb/UsbPortStatus.java
index e1662b81b017..b4fe3a2a249c 100644
--- a/core/java/android/hardware/usb/UsbPortStatus.java
+++ b/core/java/android/hardware/usb/UsbPortStatus.java
@@ -588,10 +588,10 @@ public final class UsbPortStatus implements Parcelable {
* Returns non compliant reasons, if any, for the connected
* charger/cable/accessory/USB port.
*
- * @return array including {@link #NON_COMPLIANT_REASON_DEBUG_ACCESSORY},
- * {@link #NON_COMPLIANT_REASON_BC12},
- * {@link #NON_COMPLIANT_REASON_MISSING_RP},
- * or {@link #NON_COMPLIANT_REASON_TYPEC}
+ * @return array including {@link #COMPLIANCE_WARNING_OTHER},
+ * {@link #COMPLIANCE_WARNING_DEBUG_ACCESSORY},
+ * {@link #COMPLIANCE_WARNING_BC_1_2},
+ * or {@link #COMPLIANCE_WARNING_MISSING_RP}
*/
@CheckResult
@NonNull
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 561f7982c1de..2efb26593780 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -3037,14 +3037,23 @@ public final class Settings {
public void destroy() {
try {
// If this process is the system server process, mArray is the same object as
- // the memory int array kept inside SetingsProvider, so skipping the close()
- if (!Settings.isInSystemServer()) {
+ // the memory int array kept inside SettingsProvider, so skipping the close()
+ if (!Settings.isInSystemServer() && !mArray.isClosed()) {
mArray.close();
}
} catch (IOException e) {
Log.e(TAG, "Error closing backing array", e);
}
}
+
+ @Override
+ protected void finalize() throws Throwable {
+ try {
+ destroy();
+ } finally {
+ super.finalize();
+ }
+ }
}
private static final class ContentProviderHolder {
@@ -10193,9 +10202,7 @@ public final class Settings {
*
* Face unlock re enroll.
* 0 = No re enrollment.
- * 1 = Re enrollment is suggested.
- * 2 = Re enrollment is required after a set time period.
- * 3 = Re enrollment is required immediately.
+ * 1 = Re enrollment is required.
*
* @hide
*/
diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java
index baefd853876b..60529c72afae 100644
--- a/core/java/android/view/Display.java
+++ b/core/java/android/view/Display.java
@@ -2188,7 +2188,7 @@ public final class Display {
*/
@NonNull
public float[] getAlternativeRefreshRates() {
- return mAlternativeRefreshRates;
+ return Arrays.copyOf(mAlternativeRefreshRates, mAlternativeRefreshRates.length);
}
/**
@@ -2197,7 +2197,7 @@ public final class Display {
@NonNull
@HdrCapabilities.HdrType
public int[] getSupportedHdrTypes() {
- return mSupportedHdrTypes;
+ return Arrays.copyOf(mSupportedHdrTypes, mSupportedHdrTypes.length);
}
/**
@@ -2497,8 +2497,10 @@ public final class Display {
* @deprecated use {@link Display#getMode()}
* and {@link Mode#getSupportedHdrTypes()} instead
*/
- public @HdrType int[] getSupportedHdrTypes() {
- return mSupportedHdrTypes;
+ @Deprecated
+ @HdrType
+ public int[] getSupportedHdrTypes() {
+ return Arrays.copyOf(mSupportedHdrTypes, mSupportedHdrTypes.length);
}
/**
* Returns the desired content max luminance data in cd/m2 for this display.
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 2b29e7878a5d..6bd9538a9f65 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -885,6 +885,16 @@ public final class ViewRootImpl implements ViewParent,
*/
private SurfaceSyncGroup mActiveSurfaceSyncGroup;
+
+ private final Object mPreviousSyncSafeguardLock = new Object();
+
+ /**
+ * Wraps the TransactionCommitted callback for the previous SSG so it can be added to the next
+ * SSG if started before previous has completed.
+ */
+ @GuardedBy("mPreviousSyncSafeguardLock")
+ private SurfaceSyncGroup mPreviousSyncSafeguard;
+
private static final Object sSyncProgressLock = new Object();
// The count needs to be static since it's used to enable or disable RT animations which is
// done at a global level per process. If any VRI syncs are in progress, we can't enable RT
@@ -11329,6 +11339,61 @@ public final class ViewRootImpl implements ViewParent,
});
}
+ /**
+ * This code will ensure that if multiple SurfaceSyncGroups are created for the same
+ * ViewRootImpl the SurfaceSyncGroups will maintain an order. The scenario that could occur
+ * is the following:
+ * <p>
+ * 1. SSG1 is created that includes the target VRI. There could be other VRIs in SSG1
+ * 2. The target VRI draws its frame and marks its own active SSG as ready, but SSG1 is still
+ * waiting on other things in the SSG
+ * 3. Another SSG2 is created for the target VRI. The second frame renders and marks its own
+ * second SSG as complete. SSG2 has nothing else to wait on, so it will apply at this point,
+ * even though SSG1 has not finished.
+ * 4. Frame2 will get to SF first and Frame1 will later get to SF when SSG1 completes.
+ * <p>
+ * The code below ensures the SSGs that contains the VRI maintain an order. We create a new SSG
+ * that's a safeguard SSG. Its only job is to prevent the next active SSG from completing.
+ * The current active SSG for VRI will add a transaction committed callback and when that's
+ * invoked, it will mark the safeguard SSG as ready. If a new request to create a SSG comes
+ * in and the safeguard SSG is not null, it's added as part of the new active SSG. A new
+ * safeguard SSG is created to correspond to the new active SSG. This creates a chain to
+ * ensure the latter SSG always waits for the former SSG's transaction to get to SF.
+ */
+ private void safeguardOverlappingSyncs(SurfaceSyncGroup activeSurfaceSyncGroup) {
+ SurfaceSyncGroup safeguardSsg = new SurfaceSyncGroup("VRI-Safeguard");
+ // Always disable timeout on the safeguard sync
+ safeguardSsg.toggleTimeout(false /* enable */);
+ synchronized (mPreviousSyncSafeguardLock) {
+ if (mPreviousSyncSafeguard != null) {
+ activeSurfaceSyncGroup.add(mPreviousSyncSafeguard, null /* runnable */);
+ // Temporarily disable the timeout on the SSG that will contain the buffer. This
+ // is to ensure we don't timeout the active SSG before the previous one completes to
+ // ensure the order is maintained. The previous SSG has a timeout on its own SSG
+ // so it's guaranteed to complete.
+ activeSurfaceSyncGroup.toggleTimeout(false /* enable */);
+ mPreviousSyncSafeguard.addSyncCompleteCallback(mSimpleExecutor, () -> {
+ // Once we receive that the previous sync guard has been invoked, we can re-add
+ // the timeout on the active sync to ensure we eventually complete so it's not
+ // stuck permanently.
+ activeSurfaceSyncGroup.toggleTimeout(true /*enable */);
+ });
+ }
+ mPreviousSyncSafeguard = safeguardSsg;
+ }
+
+ Transaction t = new Transaction();
+ t.addTransactionCommittedListener(mSimpleExecutor, () -> {
+ safeguardSsg.markSyncReady();
+ synchronized (mPreviousSyncSafeguardLock) {
+ if (mPreviousSyncSafeguard == safeguardSsg) {
+ mPreviousSyncSafeguard = null;
+ }
+ }
+ });
+ activeSurfaceSyncGroup.addTransaction(t);
+ }
+
@Override
public SurfaceSyncGroup getOrCreateSurfaceSyncGroup() {
boolean newSyncGroup = false;
@@ -11355,6 +11420,7 @@ public final class ViewRootImpl implements ViewParent,
mHandler.post(runnable);
}
});
+ safeguardOverlappingSyncs(mActiveSurfaceSyncGroup);
updateSyncInProgressCount(mActiveSurfaceSyncGroup);
newSyncGroup = true;
}
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index ddcb431ee06d..5b6df1cb754e 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -467,7 +467,7 @@ public interface WindowManager extends ViewManager {
* implementation.
* @hide
*/
- int TRANSIT_FIRST_CUSTOM = 13;
+ int TRANSIT_FIRST_CUSTOM = 1000;
/**
* @hide
diff --git a/core/java/android/window/SurfaceSyncGroup.java b/core/java/android/window/SurfaceSyncGroup.java
index b3d512488239..18405676dbd8 100644
--- a/core/java/android/window/SurfaceSyncGroup.java
+++ b/core/java/android/window/SurfaceSyncGroup.java
@@ -38,6 +38,7 @@ import android.view.SurfaceView;
import android.view.WindowManagerGlobal;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicInteger;
@@ -63,7 +64,12 @@ public final class SurfaceSyncGroup {
private static final int MAX_COUNT = 100;
private static final AtomicInteger sCounter = new AtomicInteger(0);
- private static final int TRANSACTION_READY_TIMEOUT = 1000 * Build.HW_TIMEOUT_MULTIPLIER;
+
+ /**
+ * @hide
+ */
+ @VisibleForTesting
+ public static final int TRANSACTION_READY_TIMEOUT = 1000 * Build.HW_TIMEOUT_MULTIPLIER;
private static Supplier<Transaction> sTransactionFactory = Transaction::new;
@@ -123,6 +129,14 @@ public final class SurfaceSyncGroup {
@GuardedBy("mLock")
private boolean mTimeoutAdded;
+ /**
+ * Disable the timeout for this SSG so it will never be set until there's an explicit call to
+ * add a timeout.
+ */
+ @GuardedBy("mLock")
+ private boolean mTimeoutDisabled;
+
+
private static boolean isLocalBinder(IBinder binder) {
return !(binder instanceof BinderProxy);
}
@@ -223,6 +237,10 @@ public final class SurfaceSyncGroup {
*/
public void addSyncCompleteCallback(Executor executor, Runnable runnable) {
synchronized (mLock) {
+ if (mFinished) {
+ executor.execute(runnable);
+ return;
+ }
mSyncCompleteCallbacks.add(new Pair<>(executor, runnable));
}
}
@@ -768,6 +786,21 @@ public final class SurfaceSyncGroup {
}
}
+ /**
+ * @hide
+ */
+ public void toggleTimeout(boolean enable) {
+ synchronized (mLock) {
+ mTimeoutDisabled = !enable;
+ if (mTimeoutAdded && !enable) {
+ mHandler.removeCallbacksAndMessages(this);
+ mTimeoutAdded = false;
+ } else if (!mTimeoutAdded && enable) {
+ addTimeout();
+ }
+ }
+ }
+
private void addTimeout() {
synchronized (sHandlerThreadLock) {
if (sHandlerThread == null) {
@@ -777,7 +810,7 @@ public final class SurfaceSyncGroup {
}
synchronized (mLock) {
- if (mTimeoutAdded) {
+ if (mTimeoutAdded || mTimeoutDisabled) {
// We only need one timeout for the entire SurfaceSyncGroup since we just want to
// ensure it doesn't stay stuck forever.
return;
diff --git a/core/java/android/window/TaskConstants.java b/core/java/android/window/TaskConstants.java
index 3a04198a3add..e18fd50dc38e 100644
--- a/core/java/android/window/TaskConstants.java
+++ b/core/java/android/window/TaskConstants.java
@@ -47,37 +47,31 @@ public class TaskConstants {
-2 * TASK_CHILD_LAYER_REGION_SIZE;
/**
- * When a unresizable app is moved in the different configuration, a restart button appears
- * allowing to adapt (~resize) app to the new configuration mocks.
+ * Compat UI components: reachability education, size compat restart
+ * button, letterbox education, restart dialog.
* @hide
*/
- public static final int TASK_CHILD_LAYER_SIZE_COMPAT_RESTART_BUTTON =
- TASK_CHILD_LAYER_REGION_SIZE;
+ public static final int TASK_CHILD_LAYER_COMPAT_UI = TASK_CHILD_LAYER_REGION_SIZE;
- /**
- * Shown the first time an app is opened in size compat mode in landscape.
- * @hide
- */
- public static final int TASK_CHILD_LAYER_LETTERBOX_EDUCATION = 2 * TASK_CHILD_LAYER_REGION_SIZE;
/**
* Captions, window frames and resize handlers around task windows.
* @hide
*/
- public static final int TASK_CHILD_LAYER_WINDOW_DECORATIONS = 3 * TASK_CHILD_LAYER_REGION_SIZE;
+ public static final int TASK_CHILD_LAYER_WINDOW_DECORATIONS = 2 * TASK_CHILD_LAYER_REGION_SIZE;
/**
* Overlays the task when going into PIP w/ gesture navigation.
* @hide
*/
public static final int TASK_CHILD_LAYER_RECENTS_ANIMATION_PIP_OVERLAY =
- 4 * TASK_CHILD_LAYER_REGION_SIZE;
+ 3 * TASK_CHILD_LAYER_REGION_SIZE;
/**
* Allows other apps to add overlays on the task (i.e. game dashboard)
* @hide
*/
- public static final int TASK_CHILD_LAYER_TASK_OVERLAY = 5 * TASK_CHILD_LAYER_REGION_SIZE;
+ public static final int TASK_CHILD_LAYER_TASK_OVERLAY = 4 * TASK_CHILD_LAYER_REGION_SIZE;
/**
* Z-orders of task child layers other than activities, task fragments and layers interleaved
@@ -87,8 +81,7 @@ public class TaskConstants {
@IntDef({
TASK_CHILD_LAYER_TASK_BACKGROUND,
TASK_CHILD_LAYER_LETTERBOX_BACKGROUND,
- TASK_CHILD_LAYER_SIZE_COMPAT_RESTART_BUTTON,
- TASK_CHILD_LAYER_LETTERBOX_EDUCATION,
+ TASK_CHILD_LAYER_COMPAT_UI,
TASK_CHILD_LAYER_WINDOW_DECORATIONS,
TASK_CHILD_LAYER_RECENTS_ANIMATION_PIP_OVERLAY,
TASK_CHILD_LAYER_TASK_OVERLAY
diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java
index 0f3eef7a3289..628fc3140ee8 100644
--- a/core/java/android/window/TransitionInfo.java
+++ b/core/java/android/window/TransitionInfo.java
@@ -152,8 +152,14 @@ public final class TransitionInfo implements Parcelable {
/** The task became the top-most task even if it didn't change visibility. */
public static final int FLAG_MOVED_TO_TOP = 1 << 20;
+ /**
+ * This transition must be the only transition when it starts (ie. it must wait for all other
+ * transition animations to finish).
+ */
+ public static final int FLAG_SYNC = 1 << 21;
+
/** The first unused bit. This can be used by remotes to attach custom flags to this change. */
- public static final int FLAG_FIRST_CUSTOM = 1 << 21;
+ public static final int FLAG_FIRST_CUSTOM = 1 << 22;
/** The change belongs to a window that won't contain activities. */
public static final int FLAGS_IS_NON_APP_WINDOW =
@@ -183,12 +189,14 @@ public final class TransitionInfo implements Parcelable {
FLAG_NO_ANIMATION,
FLAG_TASK_LAUNCHING_BEHIND,
FLAG_MOVED_TO_TOP,
+ FLAG_SYNC,
FLAG_FIRST_CUSTOM
})
public @interface ChangeFlags {}
private final @TransitionType int mType;
- private final @TransitionFlags int mFlags;
+ private @TransitionFlags int mFlags;
+ private int mTrack = 0;
private final ArrayList<Change> mChanges = new ArrayList<>();
private final ArrayList<Root> mRoots = new ArrayList<>();
@@ -210,6 +218,7 @@ public final class TransitionInfo implements Parcelable {
in.readTypedList(mRoots, Root.CREATOR);
mOptions = in.readTypedObject(AnimationOptions.CREATOR);
mDebugId = in.readInt();
+ mTrack = in.readInt();
}
@Override
@@ -221,6 +230,7 @@ public final class TransitionInfo implements Parcelable {
dest.writeTypedList(mRoots, flags);
dest.writeTypedObject(mOptions, flags);
dest.writeInt(mDebugId);
+ dest.writeInt(mTrack);
}
@NonNull
@@ -262,6 +272,10 @@ public final class TransitionInfo implements Parcelable {
return mType;
}
+ public void setFlags(int flags) {
+ mFlags = flags;
+ }
+
public int getFlags() {
return mFlags;
}
@@ -356,6 +370,16 @@ public final class TransitionInfo implements Parcelable {
return (mFlags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY) != 0;
}
+ /** Gets which animation track this transition should run on. */
+ public int getTrack() {
+ return mTrack;
+ }
+
+ /** Sets which animation track this transition should run on. */
+ public void setTrack(int track) {
+ mTrack = track;
+ }
+
/**
* Set an arbitrary "debug" id for this info. This id will not be used for any "real work",
* it is just for debugging and logging.
@@ -373,7 +397,8 @@ public final class TransitionInfo implements Parcelable {
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("{id=").append(mDebugId).append(" t=").append(transitTypeToString(mType))
- .append(" f=0x").append(Integer.toHexString(mFlags)).append(" r=[");
+ .append(" f=0x").append(Integer.toHexString(mFlags)).append(" trk=").append(mTrack)
+ .append(" r=[");
for (int i = 0; i < mRoots.size(); ++i) {
if (i > 0) {
sb.append(',');
@@ -461,6 +486,9 @@ public final class TransitionInfo implements Parcelable {
if ((flags & FLAG_TASK_LAUNCHING_BEHIND) != 0) {
sb.append((sb.length() == 0 ? "" : "|") + "TASK_LAUNCHING_BEHIND");
}
+ if ((flags & FLAG_SYNC) != 0) {
+ sb.append((sb.length() == 0 ? "" : "|") + "SYNC");
+ }
if ((flags & FLAG_FIRST_CUSTOM) != 0) {
sb.append(sb.length() == 0 ? "" : "|").append("FIRST_CUSTOM");
}
@@ -532,6 +560,7 @@ public final class TransitionInfo implements Parcelable {
*/
public TransitionInfo localRemoteCopy() {
final TransitionInfo out = new TransitionInfo(mType, mFlags);
+ out.mTrack = mTrack;
out.mDebugId = mDebugId;
for (int i = 0; i < mChanges.size(); ++i) {
out.mChanges.add(mChanges.get(i).localRemoteCopy());
diff --git a/core/proto/android/server/windowmanagertransitiontrace.proto b/core/proto/android/server/windowmanagertransitiontrace.proto
index 25985ebc551a..c3cbc9102b27 100644
--- a/core/proto/android/server/windowmanagertransitiontrace.proto
+++ b/core/proto/android/server/windowmanagertransitiontrace.proto
@@ -55,12 +55,14 @@ message Transition {
optional int64 finish_time_ns = 6; // consider aborted if not provided
required int32 type = 7;
repeated Target targets = 8;
+ optional int32 flags = 9;
}
message Target {
required int32 mode = 1;
required int32 layer_id = 2;
optional int32 window_id = 3; // Not dumped in always on tracing
+ optional int32 flags = 4;
}
message TransitionState {
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index f6c9fabb7896..e216f88d3c4d 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -8217,6 +8217,14 @@
</intent-filter>
</service>
+ <service android:name="com.android.server.companion.datatransfer.contextsync.CallMetadataSyncConnectionService"
+ android:permission="android.permission.BIND_CONNECTION_SERVICE"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.telecom.ConnectionService"/>
+ </intent-filter>
+ </service>
+
<provider
android:name="com.android.server.textclassifier.IconsContentProvider"
android:authorities="com.android.textclassifier.icons"
diff --git a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
index 4f91e7a3545a..fb0f3d419002 100644
--- a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
+++ b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
@@ -71,6 +71,7 @@ import androidx.test.runner.AndroidJUnit4;
import com.android.internal.content.ReferrerIntent;
import org.junit.After;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -95,7 +96,8 @@ public class ActivityThreadTest {
// few sequence numbers the framework used to launch the test activity.
private static final int BASE_SEQ = 10000;
- private final ActivityTestRule<TestActivity> mActivityTestRule =
+ @Rule
+ public final ActivityTestRule<TestActivity> mActivityTestRule =
new ActivityTestRule<>(TestActivity.class, true /* initialTouchMode */,
false /* launchActivity */);
diff --git a/core/tests/coretests/src/android/hardware/face/FaceManagerTest.java b/core/tests/coretests/src/android/hardware/face/FaceManagerTest.java
index 1075d44b4017..07dec5d9e222 100644
--- a/core/tests/coretests/src/android/hardware/face/FaceManagerTest.java
+++ b/core/tests/coretests/src/android/hardware/face/FaceManagerTest.java
@@ -17,6 +17,7 @@
package android.hardware.face;
import static android.hardware.biometrics.BiometricFaceConstants.FACE_ERROR_HW_UNAVAILABLE;
+import static android.hardware.biometrics.BiometricFaceConstants.FACE_ERROR_UNABLE_TO_PROCESS;
import static com.google.common.truth.Truth.assertThat;
@@ -178,6 +179,18 @@ public class FaceManagerTest {
verify(mEnrollmentCallback).onEnrollmentError(eq(FACE_ERROR_HW_UNAVAILABLE), anyString());
}
+ @Test
+ public void enrollment_errorWhenHardwareAuthTokenIsNull() throws RemoteException {
+ initializeProperties();
+ mFaceManager.enroll(USER_ID, null,
+ new CancellationSignal(), mEnrollmentCallback, null /* disabledFeatures */);
+
+ verify(mEnrollmentCallback).onEnrollmentError(eq(FACE_ERROR_UNABLE_TO_PROCESS),
+ anyString());
+ verify(mService, never()).enroll(eq(USER_ID), any(), any(),
+ any(), anyString(), any(), any(), anyBoolean());
+ }
+
private void initializeProperties() throws RemoteException {
verify(mService).addAuthenticatorsRegisteredCallback(mCaptor.capture());
diff --git a/core/tests/coretests/src/android/hardware/fingerprint/FingerprintManagerTest.java b/core/tests/coretests/src/android/hardware/fingerprint/FingerprintManagerTest.java
index 5058065710be..625e2e3723a7 100644
--- a/core/tests/coretests/src/android/hardware/fingerprint/FingerprintManagerTest.java
+++ b/core/tests/coretests/src/android/hardware/fingerprint/FingerprintManagerTest.java
@@ -17,12 +17,14 @@
package android.hardware.fingerprint;
import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ERROR_HW_UNAVAILABLE;
+import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ERROR_UNABLE_TO_PROCESS;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
@@ -70,6 +72,8 @@ public class FingerprintManagerTest {
private IFingerprintService mService;
@Mock
private FingerprintManager.AuthenticationCallback mAuthCallback;
+ @Mock
+ private FingerprintManager.EnrollmentCallback mEnrollCallback;
@Captor
private ArgumentCaptor<IFingerprintAuthenticatorsRegisteredCallback> mCaptor;
@@ -149,4 +153,17 @@ public class FingerprintManagerTest {
verify(mAuthCallback).onAuthenticationError(eq(FINGERPRINT_ERROR_HW_UNAVAILABLE), any());
}
+
+ @Test
+ public void enrollment_errorWhenHardwareAuthTokenIsNull() throws RemoteException {
+ verify(mService).addAuthenticatorsRegisteredCallback(mCaptor.capture());
+
+ mCaptor.getValue().onAllAuthenticatorsRegistered(mProps);
+ mFingerprintManager.enroll(null, new CancellationSignal(), USER_ID,
+ mEnrollCallback, FingerprintManager.ENROLL_ENROLL);
+
+ verify(mEnrollCallback).onEnrollmentError(eq(FINGERPRINT_ERROR_UNABLE_TO_PROCESS),
+ anyString());
+ verify(mService, never()).enroll(any(), any(), anyInt(), any(), anyString(), anyInt());
+ }
}
diff --git a/core/tests/mockingcoretests/src/android/view/DisplayTest.java b/core/tests/mockingcoretests/src/android/view/DisplayTest.java
index 4a12bb374cb2..5e617fd59946 100644
--- a/core/tests/mockingcoretests/src/android/view/DisplayTest.java
+++ b/core/tests/mockingcoretests/src/android/view/DisplayTest.java
@@ -460,6 +460,36 @@ public class DisplayTest {
assertArrayEquals(sortedHdrTypes, displayMode.getSupportedHdrTypes());
}
+ @Test
+ public void testGetSupportedHdrTypesReturnsCopy() {
+ int[] hdrTypes = new int[]{1, 2, 3};
+ Display.Mode displayMode = new Display.Mode(0, 0, 0, 0, new float[0], hdrTypes);
+
+ int[] hdrTypesCopy = displayMode.getSupportedHdrTypes();
+ hdrTypesCopy[0] = 0;
+ assertArrayEquals(hdrTypes, displayMode.getSupportedHdrTypes());
+ }
+
+ @Test
+ public void testGetAlternativeRefreshRatesReturnsCopy() {
+ float[] alternativeRates = new float[]{1.0f, 2.0f};
+ Display.Mode displayMode = new Display.Mode(0, 0, 0, 0, alternativeRates, new int[0]);
+
+ float[] alternativeRatesCopy = displayMode.getAlternativeRefreshRates();
+ alternativeRatesCopy[0] = 0.0f;
+ assertArrayEquals(alternativeRates, displayMode.getAlternativeRefreshRates(), 0.0f);
+ }
+
+ @Test
+ public void testHdrCapabilitiesGetSupportedHdrTypesReturnsCopy() {
+ int[] hdrTypes = new int[]{1, 2, 3};
+ Display.HdrCapabilities hdrCapabilities = new Display.HdrCapabilities(hdrTypes, 0, 0, 0);
+
+ int[] hdrTypesCopy = hdrCapabilities.getSupportedHdrTypes();
+ hdrTypesCopy[0] = 0;
+ assertArrayEquals(hdrTypes, hdrCapabilities.getSupportedHdrTypes());
+ }
+
// Given rotated display dimensions, calculate the letterboxed app bounds.
private static Rect buildAppBounds(int displayWidth, int displayHeight) {
final int midWidth = displayWidth / 2;
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index 7434cb02cc7c..549ac5858d1d 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -3805,6 +3805,12 @@
"group": "WM_DEBUG_APP_TRANSITIONS_ANIM",
"at": "com\/android\/server\/wm\/AppTransitionController.java"
},
+ "1463355909": {
+ "message": "Queueing legacy sync-set: %s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WINDOW_TRANSITIONS_MIN",
+ "at": "com\/android\/server\/wm\/TransitionController.java"
+ },
"1469310004": {
"message": " SKIP: common mode mismatch. was %s",
"level": "VERBOSE",
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/common/DeviceStateManagerFoldingFeatureProducer.java b/libs/WindowManager/Jetpack/src/androidx/window/common/DeviceStateManagerFoldingFeatureProducer.java
index 66f27f517ab3..a184dff5005b 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/common/DeviceStateManagerFoldingFeatureProducer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/common/DeviceStateManagerFoldingFeatureProducer.java
@@ -39,7 +39,6 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
-import java.util.Set;
import java.util.function.Consumer;
/**
@@ -167,14 +166,13 @@ public final class DeviceStateManagerFoldingFeatureProducer
}
@Override
- protected void onListenersChanged(
- @NonNull Set<Consumer<List<CommonFoldingFeature>>> callbacks) {
- super.onListenersChanged(callbacks);
- if (callbacks.isEmpty()) {
+ protected void onListenersChanged() {
+ super.onListenersChanged();
+ if (hasListeners()) {
+ mRawFoldSupplier.addDataChangedCallback(this::notifyFoldingFeatureChange);
+ } else {
mCurrentDeviceState = INVALID_DEVICE_STATE;
mRawFoldSupplier.removeDataChangedCallback(this::notifyFoldingFeatureChange);
- } else {
- mRawFoldSupplier.addDataChangedCallback(this::notifyFoldingFeatureChange);
}
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/common/RawFoldingFeatureProducer.java b/libs/WindowManager/Jetpack/src/androidx/window/common/RawFoldingFeatureProducer.java
index 7906342d445d..8906e6d3d02e 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/common/RawFoldingFeatureProducer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/common/RawFoldingFeatureProducer.java
@@ -31,7 +31,6 @@ import androidx.window.util.BaseDataProducer;
import com.android.internal.R;
import java.util.Optional;
-import java.util.Set;
import java.util.function.Consumer;
/**
@@ -86,11 +85,11 @@ public final class RawFoldingFeatureProducer extends BaseDataProducer<String> {
}
@Override
- protected void onListenersChanged(Set<Consumer<String>> callbacks) {
- if (callbacks.isEmpty()) {
- unregisterObserversIfNeeded();
- } else {
+ protected void onListenersChanged() {
+ if (hasListeners()) {
registerObserversIfNeeded();
+ } else {
+ unregisterObserversIfNeeded();
}
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/RearDisplayPresentation.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/RearDisplayPresentation.java
index 1ff169433b9d..849b500f36e4 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/RearDisplayPresentation.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/RearDisplayPresentation.java
@@ -42,14 +42,14 @@ class RearDisplayPresentation extends Presentation implements ExtensionWindowAre
/**
* {@code mStateConsumer} is notified that their content is now visible when the
* {@link Presentation} object is started. There is no comparable callback for
- * {@link WindowAreaComponent#SESSION_STATE_INVISIBLE} in {@link #onStop()} due to the
+ * {@link WindowAreaComponent#SESSION_STATE_CONTENT_INVISIBLE} in {@link #onStop()} due to the
* timing of when a {@link android.hardware.devicestate.DeviceStateRequest} is cancelled
* ending rear display presentation mode happening before the {@link Presentation} is stopped.
*/
@Override
protected void onStart() {
super.onStart();
- mStateConsumer.accept(WindowAreaComponent.SESSION_STATE_VISIBLE);
+ mStateConsumer.accept(WindowAreaComponent.SESSION_STATE_CONTENT_VISIBLE);
}
@NonNull
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 cc46a4bc4ea3..abf230117032 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java
@@ -328,8 +328,8 @@ public class WindowAreaComponentImpl implements WindowAreaComponent,
// due to not having a good mechanism to know when
// the content is no longer visible before it's fully removed
if (getLastReportedRearDisplayPresentationStatus()
- == SESSION_STATE_VISIBLE) {
- consumer.accept(SESSION_STATE_INVISIBLE);
+ == SESSION_STATE_CONTENT_VISIBLE) {
+ consumer.accept(SESSION_STATE_CONTENT_INVISIBLE);
}
mRearDisplayPresentationController = null;
}
@@ -414,8 +414,7 @@ public class WindowAreaComponentImpl implements WindowAreaComponent,
return WindowAreaComponent.STATUS_UNAVAILABLE;
}
- if (mRearDisplaySessionStatus == WindowAreaComponent.SESSION_STATE_ACTIVE
- || isRearDisplayActive()) {
+ if (isRearDisplayActive()) {
return WindowAreaComponent.STATUS_ACTIVE;
}
@@ -537,7 +536,6 @@ public class WindowAreaComponentImpl implements WindowAreaComponent,
if (request.equals(mRearDisplayStateRequest)) {
mRearDisplaySessionStatus = WindowAreaComponent.SESSION_STATE_ACTIVE;
mRearDisplaySessionCallback.accept(mRearDisplaySessionStatus);
- updateRearDisplayStatusListeners(getCurrentRearDisplayModeStatus());
}
}
}
@@ -550,7 +548,6 @@ public class WindowAreaComponentImpl implements WindowAreaComponent,
}
mRearDisplaySessionStatus = WindowAreaComponent.SESSION_STATE_INACTIVE;
mRearDisplaySessionCallback.accept(mRearDisplaySessionStatus);
- updateRearDisplayStatusListeners(getCurrentRearDisplayModeStatus());
}
}
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/util/BaseDataProducer.java b/libs/WindowManager/Jetpack/src/androidx/window/util/BaseDataProducer.java
index 46c925aaf8a2..de52f0969fa8 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/util/BaseDataProducer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/util/BaseDataProducer.java
@@ -51,10 +51,10 @@ public abstract class BaseDataProducer<T> implements DataProducer<T>,
public final void addDataChangedCallback(@NonNull Consumer<T> callback) {
synchronized (mLock) {
mCallbacks.add(callback);
- Optional<T> currentData = getCurrentData();
- currentData.ifPresent(callback);
- onListenersChanged(mCallbacks);
}
+ Optional<T> currentData = getCurrentData();
+ currentData.ifPresent(callback);
+ onListenersChanged();
}
/**
@@ -67,11 +67,22 @@ public abstract class BaseDataProducer<T> implements DataProducer<T>,
public final void removeDataChangedCallback(@NonNull Consumer<T> callback) {
synchronized (mLock) {
mCallbacks.remove(callback);
- onListenersChanged(mCallbacks);
}
+ onListenersChanged();
}
- protected void onListenersChanged(Set<Consumer<T>> callbacks) {}
+ /**
+ * Returns {@code true} if there are any registered callbacks {@code false} if there are no
+ * registered callbacks.
+ */
+ // TODO(b/278132889) Improve the structure of BaseDataProdcuer while avoiding known issues.
+ public final boolean hasListeners() {
+ synchronized (mLock) {
+ return !mCallbacks.isEmpty();
+ }
+ }
+
+ protected void onListenersChanged() {}
/**
* @return the current data if available and {@code Optional.empty()} otherwise.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
index 1b20f67e42ab..60111aadd6af 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
@@ -2922,14 +2922,15 @@ public class BubbleStackView extends FrameLayout
final float targetX = isLtr
? mTempRect.left - margin
: mTempRect.right + margin - mManageMenu.getWidth();
- final float targetY = mTempRect.bottom - mManageMenu.getHeight();
+ final float menuHeight = getVisibleManageMenuHeight();
+ final float targetY = mTempRect.bottom - menuHeight;
final float xOffsetForAnimation = (isLtr ? 1 : -1) * mManageMenu.getWidth() / 4f;
if (show) {
mManageMenu.setScaleX(0.5f);
mManageMenu.setScaleY(0.5f);
mManageMenu.setTranslationX(targetX - xOffsetForAnimation);
- mManageMenu.setTranslationY(targetY + mManageMenu.getHeight() / 4f);
+ mManageMenu.setTranslationY(targetY + menuHeight / 4f);
mManageMenu.setAlpha(0f);
PhysicsAnimator.getInstance(mManageMenu)
@@ -2955,7 +2956,7 @@ public class BubbleStackView extends FrameLayout
.spring(DynamicAnimation.SCALE_X, 0.5f)
.spring(DynamicAnimation.SCALE_Y, 0.5f)
.spring(DynamicAnimation.TRANSLATION_X, targetX - xOffsetForAnimation)
- .spring(DynamicAnimation.TRANSLATION_Y, targetY + mManageMenu.getHeight() / 4f)
+ .spring(DynamicAnimation.TRANSLATION_Y, targetY + menuHeight / 4f)
.withEndActions(() -> {
mManageMenu.setVisibility(View.INVISIBLE);
if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) {
@@ -3272,6 +3273,24 @@ public class BubbleStackView extends FrameLayout
}
/**
+ * Menu height calculated for animation
+ * It takes into account view visibility to get the correct total height
+ */
+ private float getVisibleManageMenuHeight() {
+ float menuHeight = 0;
+
+ for (int i = 0; i < mManageMenu.getChildCount(); i++) {
+ View subview = mManageMenu.getChildAt(i);
+
+ if (subview.getVisibility() == VISIBLE) {
+ menuHeight += subview.getHeight();
+ }
+ }
+
+ return menuHeight;
+ }
+
+ /**
* @return the normalized x-axis position of the bubble stack rounded to 4 decimal places.
*/
public float getNormalizedXPosition() {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java
index 170c0ee91b40..659229228a57 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java
@@ -20,6 +20,7 @@ import static android.app.TaskInfo.CAMERA_COMPAT_CONTROL_DISMISSED;
import static android.app.TaskInfo.CAMERA_COMPAT_CONTROL_HIDDEN;
import static android.app.TaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED;
import static android.app.TaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED;
+import static android.window.TaskConstants.TASK_CHILD_LAYER_COMPAT_UI;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -46,11 +47,6 @@ import java.util.function.Consumer;
*/
class CompatUIWindowManager extends CompatUIWindowManagerAbstract {
- /**
- * The Compat UI should be below the Letterbox Education.
- */
- private static final int Z_ORDER = LetterboxEduWindowManager.Z_ORDER - 1;
-
private final CompatUICallback mCallback;
private final CompatUIConfiguration mCompatUIConfiguration;
@@ -92,7 +88,7 @@ class CompatUIWindowManager extends CompatUIWindowManagerAbstract {
@Override
protected int getZOrder() {
- return Z_ORDER;
+ return TASK_CHILD_LAYER_COMPAT_UI + 1;
}
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/LetterboxEduWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/LetterboxEduWindowManager.java
index 0c21c8ccd686..959c50d5c640 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/LetterboxEduWindowManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/LetterboxEduWindowManager.java
@@ -17,6 +17,7 @@
package com.android.wm.shell.compatui;
import static android.provider.Settings.Secure.LAUNCHER_TASKBAR_EDUCATION_SHOWING;
+import static android.window.TaskConstants.TASK_CHILD_LAYER_COMPAT_UI;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -46,12 +47,6 @@ import java.util.function.Consumer;
*/
class LetterboxEduWindowManager extends CompatUIWindowManagerAbstract {
- /**
- * The Letterbox Education should be the topmost child of the Task in case there can be more
- * than one child.
- */
- public static final int Z_ORDER = Integer.MAX_VALUE;
-
private final DialogAnimationController<LetterboxEduDialogLayout> mAnimationController;
private final Transitions mTransitions;
@@ -118,7 +113,7 @@ class LetterboxEduWindowManager extends CompatUIWindowManagerAbstract {
@Override
protected int getZOrder() {
- return Z_ORDER;
+ return TASK_CHILD_LAYER_COMPAT_UI + 2;
}
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/ReachabilityEduWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/ReachabilityEduWindowManager.java
index b6e396d12512..a18ab9154e01 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/ReachabilityEduWindowManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/ReachabilityEduWindowManager.java
@@ -18,6 +18,7 @@ package com.android.wm.shell.compatui;
import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
+import static android.window.TaskConstants.TASK_CHILD_LAYER_COMPAT_UI;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -41,11 +42,6 @@ import com.android.wm.shell.common.SyncTransactionQueue;
*/
class ReachabilityEduWindowManager extends CompatUIWindowManagerAbstract {
- /**
- * The Compat UI should be below the Letterbox Education.
- */
- private static final int Z_ORDER = LetterboxEduWindowManager.Z_ORDER - 1;
-
// The time to wait before hiding the education
private static final long DISAPPEAR_DELAY_MS = 4000L;
@@ -102,7 +98,7 @@ class ReachabilityEduWindowManager extends CompatUIWindowManagerAbstract {
@Override
protected int getZOrder() {
- return Z_ORDER;
+ return TASK_CHILD_LAYER_COMPAT_UI + 1;
}
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/RestartDialogWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/RestartDialogWindowManager.java
index aab123a843ea..51e5141a28af 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/RestartDialogWindowManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/RestartDialogWindowManager.java
@@ -17,6 +17,7 @@
package com.android.wm.shell.compatui;
import static android.provider.Settings.Secure.LAUNCHER_TASKBAR_EDUCATION_SHOWING;
+import static android.window.TaskConstants.TASK_CHILD_LAYER_COMPAT_UI;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -47,12 +48,6 @@ import java.util.function.Consumer;
*/
class RestartDialogWindowManager extends CompatUIWindowManagerAbstract {
- /**
- * The restart dialog should be the topmost child of the Task in case there can be more
- * than one child.
- */
- private static final int Z_ORDER = Integer.MAX_VALUE;
-
private final DialogAnimationController<RestartDialogLayout> mAnimationController;
private final Transitions mTransitions;
@@ -112,7 +107,7 @@ class RestartDialogWindowManager extends CompatUIWindowManagerAbstract {
@Override
protected int getZOrder() {
- return Z_ORDER;
+ return TASK_CHILD_LAYER_COMPAT_UI + 2;
}
@Override
@@ -170,10 +165,10 @@ class RestartDialogWindowManager extends CompatUIWindowManagerAbstract {
final Rect taskBounds = getTaskBounds();
final Rect taskStableBounds = getTaskStableBounds();
-
- marginParams.topMargin = taskStableBounds.top - taskBounds.top + mDialogVerticalMargin;
- marginParams.bottomMargin =
- taskBounds.bottom - taskStableBounds.bottom + mDialogVerticalMargin;
+ // only update margins based on taskbar insets
+ marginParams.topMargin = mDialogVerticalMargin;
+ marginParams.bottomMargin = taskBounds.bottom - taskStableBounds.bottom
+ + mDialogVerticalMargin;
dialogContainer.setLayoutParams(marginParams);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index b6216b340b38..566c130c7573 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -1690,8 +1690,17 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
// Similar to auto-enter-pip transition, we use content overlay when there is no
// source rect hint to enter PiP use bounds animation.
if (sourceHintRect == null) {
+ // We use content overlay when there is no source rect hint to enter PiP use bounds
+ // animation.
+ // TODO(b/272819817): cleanup the null-check and extra logging.
+ final boolean hasTopActivityInfo = mTaskInfo.topActivityInfo != null;
+ if (!hasTopActivityInfo) {
+ ProtoLog.w(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
+ "%s: TaskInfo.topActivityInfo is null", TAG);
+ }
if (SystemProperties.getBoolean(
- "persist.wm.debug.enable_pip_app_icon_overlay", true)) {
+ "persist.wm.debug.enable_pip_app_icon_overlay", true)
+ && hasTopActivityInfo) {
animator.setAppIconContentOverlay(
mContext, currentBounds, mTaskInfo.topActivityInfo,
mPipBoundsState.getLauncherState().getAppIconSizePx());
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java
index c4a0e9cf5a74..ef5e501917f8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java
@@ -33,7 +33,7 @@ public enum ShellProtoLogGroup implements IProtoLogGroup {
WM_SHELL_TRANSITIONS(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, true,
Consts.TAG_WM_SHELL),
WM_SHELL_RECENTS_TRANSITION(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, true,
- Consts.TAG_WM_SHELL),
+ "ShellRecents"),
WM_SHELL_DRAG_AND_DROP(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, true,
Consts.TAG_WM_SHELL),
WM_SHELL_STARTING_WINDOW(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
index b55487258220..4f362d4ed0d8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
@@ -100,6 +100,7 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler {
IApplicationThread appThread, IRecentsAnimationRunner listener) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
"RecentsTransitionHandler.startRecentsTransition");
+
// only care about latest one.
mAnimApp = appThread;
WindowContainerTransaction wct = new WindowContainerTransaction();
@@ -169,7 +170,11 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler {
SurfaceControl.Transaction t, IBinder mergeTarget,
Transitions.TransitionFinishCallback finishCallback) {
final int targetIdx = findController(mergeTarget);
- if (targetIdx < 0) return;
+ if (targetIdx < 0) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
+ "RecentsTransitionHandler.mergeAnimation: no controller found");
+ return;
+ }
final RecentsController controller = mControllers.get(targetIdx);
controller.merge(info, t, finishCallback);
}
@@ -177,13 +182,20 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler {
@Override
public void onTransitionConsumed(IBinder transition, boolean aborted,
SurfaceControl.Transaction finishTransaction) {
- final int idx = findController(transition);
- if (idx < 0) return;
- mControllers.get(idx).cancel("onTransitionConsumed");
+ // Only one recents transition can be handled at a time, but currently the first transition
+ // will trigger a no-op in the second transition which holds the active recents animation
+ // runner on the launcher side. For now, cancel all existing animations to ensure we
+ // don't get into a broken state with an orphaned animation runner, and later we can try to
+ // merge the latest transition into the currently running one
+ for (int i = mControllers.size() - 1; i >= 0; i--) {
+ mControllers.get(i).cancel("onTransitionConsumed");
+ }
}
/** There is only one of these and it gets reset on finish. */
private class RecentsController extends IRecentsAnimationController.Stub {
+ private final int mInstanceId;
+
private IRecentsAnimationRunner mListener;
private IBinder.DeathRecipient mDeathHandler;
private Transitions.TransitionFinishCallback mFinishCB = null;
@@ -223,10 +235,11 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler {
private int mState = STATE_NORMAL;
RecentsController(IRecentsAnimationRunner listener) {
+ mInstanceId = System.identityHashCode(this);
mListener = listener;
mDeathHandler = () -> {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
- "RecentsController.DeathRecipient: binder died");
+ "[%d] RecentsController.DeathRecipient: binder died", mInstanceId);
finish(mWillFinishToHome, false /* leaveHint */);
};
try {
@@ -239,7 +252,7 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler {
void setTransition(IBinder transition) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
- "RecentsController.setTransition: id=%s", transition);
+ "[%d] RecentsController.setTransition: id=%s", mInstanceId, transition);
mTransition = transition;
}
@@ -251,11 +264,13 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler {
void cancel(boolean toHome, String reason) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
- "RecentsController.cancel: toHome=%b reason=%s", toHome, reason);
+ "[%d] RecentsController.cancel: toHome=%b reason=%s",
+ mInstanceId, toHome, reason);
if (mListener != null) {
try {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
- "RecentsController.cancel: calling onAnimationCanceled");
+ "[%d] RecentsController.cancel: calling onAnimationCanceled",
+ mInstanceId);
mListener.onAnimationCanceled(null, null);
} catch (RemoteException e) {
Slog.e(TAG, "Error canceling recents animation", e);
@@ -290,7 +305,8 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler {
}
try {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
- "RecentsController.cancel: calling onAnimationCanceled with snapshots");
+ "[%d] RecentsController.cancel: calling onAnimationCanceled with snapshots",
+ mInstanceId);
mListener.onAnimationCanceled(taskIds, snapshots);
} catch (RemoteException e) {
Slog.e(TAG, "Error canceling recents animation", e);
@@ -300,7 +316,8 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler {
}
void cleanUp() {
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, "RecentsController.cleanup");
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
+ "[%d] RecentsController.cleanup", mInstanceId);
if (mListener != null && mDeathHandler != null) {
mListener.asBinder().unlinkToDeath(mDeathHandler, 0 /* flags */);
mDeathHandler = null;
@@ -324,7 +341,8 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler {
boolean start(TransitionInfo info, SurfaceControl.Transaction t,
SurfaceControl.Transaction finishT, Transitions.TransitionFinishCallback finishCB) {
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, "RecentsController.start");
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
+ "[%d] RecentsController.start", mInstanceId);
if (mListener == null || mTransition == null) {
cleanUp();
return false;
@@ -409,7 +427,7 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler {
t.apply();
try {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
- "RecentsController.start: calling onAnimationStart");
+ "[%d] RecentsController.start: calling onAnimationStart", mInstanceId);
mListener.onAnimationStart(this,
apps.toArray(new RemoteAnimationTarget[apps.size()]),
wallpapers.toArray(new RemoteAnimationTarget[wallpapers.size()]),
@@ -426,18 +444,20 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler {
Transitions.TransitionFinishCallback finishCallback) {
if (mFinishCB == null) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
- "RecentsController.merge: skip, no finish callback");
+ "[%d] RecentsController.merge: skip, no finish callback",
+ mInstanceId);
// This was no-op'd (likely a repeated start) and we've already sent finish.
return;
}
if (info.getType() == TRANSIT_SLEEP) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
- "RecentsController.merge: transit_sleep");
+ "[%d] RecentsController.merge: transit_sleep", mInstanceId);
// A sleep event means we need to stop animations immediately, so cancel here.
cancel("transit_sleep");
return;
}
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, "RecentsController.merge");
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
+ "[%d] RecentsController.merge", mInstanceId);
ArrayList<TransitionInfo.Change> openingTasks = null;
ArrayList<TransitionInfo.Change> closingTasks = null;
mOpeningSeparateHome = false;
@@ -595,7 +615,7 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler {
if (appearedTargets == null) return;
try {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
- "RecentsController.merge: calling onTasksAppeared");
+ "[%d] RecentsController.merge: calling onTasksAppeared", mInstanceId);
mListener.onTasksAppeared(appearedTargets);
} catch (RemoteException e) {
Slog.e(TAG, "Error sending appeared tasks to recents animation", e);
@@ -620,7 +640,7 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler {
public TaskSnapshot screenshotTask(int taskId) {
try {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
- "RecentsController.screenshotTask: taskId=%d", taskId);
+ "[%d] RecentsController.screenshotTask: taskId=%d", mInstanceId, taskId);
return ActivityTaskManager.getService().takeTaskSnapshot(taskId);
} catch (RemoteException e) {
Slog.e(TAG, "Failed to screenshot task", e);
@@ -643,7 +663,8 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler {
// animation finished.
try {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
- "RecentsController.setInputConsumerEnabled: set focus to recents");
+ "[%d] RecentsController.setInputConsumerEnabled: set focus to recents",
+ mInstanceId);
ActivityTaskManager.getService().setFocusedTask(mRecentsTaskId);
} catch (RemoteException e) {
Slog.e(TAG, "Failed to set focused task", e);
@@ -659,7 +680,8 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler {
public void setFinishTaskTransaction(int taskId,
PictureInPictureSurfaceTransaction finishTransaction, SurfaceControl overlay) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
- "RecentsController.setFinishTaskTransaction: taskId=%d", taskId);
+ "[%d] RecentsController.setFinishTaskTransaction: taskId=%d",
+ mInstanceId, taskId);
mExecutor.execute(() -> {
if (mFinishCB == null) return;
mPipTransaction = finishTransaction;
@@ -678,8 +700,8 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler {
return;
}
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
- "RecentsController.finishInner: toHome=%b userLeaveHint=%b willFinishToHome=%b",
- toHome, sendUserLeaveHint, mWillFinishToHome);
+ "[%d] RecentsController.finishInner: toHome=%b userLeave=%b willFinishToHome=%b",
+ mInstanceId, toHome, sendUserLeaveHint, mWillFinishToHome);
final Transitions.TransitionFinishCallback finishCB = mFinishCB;
mFinishCB = null;
@@ -781,7 +803,7 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler {
@Override
public void detachNavigationBarFromApp(boolean moveHomeToTop) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
- "RecentsController.detachNavigationBarFromApp");
+ "[%d] RecentsController.detachNavigationBarFromApp", mInstanceId);
mExecutor.execute(() -> {
if (mTransition == null) return;
try {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java
index 36c9077a197b..7991c529aa49 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java
@@ -388,11 +388,6 @@ public class TaskViewTaskController implements ShellTaskOrganizer.TaskListener {
}
// Sync Transactions can't operate simultaneously with shell transition collection.
if (isUsingShellTransitions()) {
- if (mTaskViewTransitions.hasPending()) {
- // There is already a transition in-flight. The window bounds will be synced
- // once it is complete.
- return;
- }
mTaskViewTransitions.setTaskBounds(this, boundsOnScreen);
return;
}
@@ -489,12 +484,14 @@ public class TaskViewTaskController implements ShellTaskOrganizer.TaskListener {
finishTransaction.reparent(mTaskLeash, mSurfaceControl)
.setPosition(mTaskLeash, 0, 0)
.apply();
-
+ mTaskViewTransitions.updateBoundsState(this, mTaskViewBase.getCurrentBoundsOnScreen());
+ mTaskViewTransitions.updateVisibilityState(this, true /* visible */);
wct.setBounds(mTaskToken, mTaskViewBase.getCurrentBoundsOnScreen());
} else {
// The surface has already been destroyed before the task has appeared,
// so go ahead and hide the task entirely
wct.setHidden(mTaskToken, true /* hidden */);
+ mTaskViewTransitions.updateVisibilityState(this, false /* visible */);
// listener callback is below
}
if (newTask) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java
index 3b1ce49ebdc7..81d69a4fa611 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java
@@ -26,6 +26,7 @@ import android.annotation.Nullable;
import android.app.ActivityManager;
import android.graphics.Rect;
import android.os.IBinder;
+import android.util.ArrayMap;
import android.util.Slog;
import android.view.SurfaceControl;
import android.view.WindowManager;
@@ -33,10 +34,13 @@ import android.window.TransitionInfo;
import android.window.TransitionRequestInfo;
import android.window.WindowContainerTransaction;
+import androidx.annotation.VisibleForTesting;
+
import com.android.wm.shell.transition.Transitions;
import com.android.wm.shell.util.TransitionUtil;
import java.util.ArrayList;
+import java.util.Objects;
/**
* Handles Shell Transitions that involve TaskView tasks.
@@ -44,7 +48,8 @@ import java.util.ArrayList;
public class TaskViewTransitions implements Transitions.TransitionHandler {
static final String TAG = "TaskViewTransitions";
- private final ArrayList<TaskViewTaskController> mTaskViews = new ArrayList<>();
+ private final ArrayMap<TaskViewTaskController, TaskViewRequestedState> mTaskViews =
+ new ArrayMap<>();
private final ArrayList<PendingTransition> mPending = new ArrayList<>();
private final Transitions mTransitions;
private final boolean[] mRegistered = new boolean[]{ false };
@@ -54,7 +59,8 @@ public class TaskViewTransitions implements Transitions.TransitionHandler {
* in-flight (collecting) at a time (because otherwise, the operations could get merged into
* a single transition). So, keep a queue here until we add a queue in server-side.
*/
- private static class PendingTransition {
+ @VisibleForTesting
+ static class PendingTransition {
final @WindowManager.TransitionType int mType;
final WindowContainerTransaction mWct;
final @NonNull TaskViewTaskController mTaskView;
@@ -78,6 +84,14 @@ public class TaskViewTransitions implements Transitions.TransitionHandler {
}
}
+ /**
+ * Visibility and bounds state that has been requested for a {@link TaskViewTaskController}.
+ */
+ private static class TaskViewRequestedState {
+ boolean mVisible;
+ Rect mBounds = new Rect();
+ }
+
public TaskViewTransitions(Transitions transitions) {
mTransitions = transitions;
// Defer registration until the first TaskView because we want this to be the "first" in
@@ -92,7 +106,7 @@ public class TaskViewTransitions implements Transitions.TransitionHandler {
mTransitions.addHandler(this);
}
}
- mTaskViews.add(tv);
+ mTaskViews.put(tv, new TaskViewRequestedState());
}
void removeTaskView(TaskViewTaskController tv) {
@@ -105,24 +119,30 @@ public class TaskViewTransitions implements Transitions.TransitionHandler {
}
/**
- * Looks through the pending transitions for one matching `taskView`.
+ * Looks through the pending transitions for a closing transaction that matches the provided
+ * `taskView`.
* @param taskView the pending transition should be for this.
- * @param closing When true, this only returns a pending transition of the close/hide type.
- * Otherwise it selects open/show.
- * @param latest When true, this will only check the most-recent pending transition for the
- * specified taskView. If it doesn't match `closing`, this will return null even
- * if there is a match earlier. The idea behind this is to check the state of
- * the taskviews "as if all transitions already happened".
*/
- private PendingTransition findPending(TaskViewTaskController taskView, boolean closing,
- boolean latest) {
+ private PendingTransition findPendingCloseTransition(TaskViewTaskController taskView) {
for (int i = mPending.size() - 1; i >= 0; --i) {
if (mPending.get(i).mTaskView != taskView) continue;
- if (TransitionUtil.isClosingType(mPending.get(i).mType) == closing) {
+ if (TransitionUtil.isClosingType(mPending.get(i).mType)) {
return mPending.get(i);
}
- if (latest) {
- return null;
+ }
+ return null;
+ }
+
+ /**
+ * Looks through the pending transitions for one matching `taskView`.
+ * @param taskView the pending transition should be for this.
+ * @param type the type of transition it's looking for
+ */
+ PendingTransition findPending(TaskViewTaskController taskView, int type) {
+ for (int i = mPending.size() - 1; i >= 0; --i) {
+ if (mPending.get(i).mTaskView != taskView) continue;
+ if (mPending.get(i).mType == type) {
+ return mPending.get(i);
}
}
return null;
@@ -152,7 +172,7 @@ public class TaskViewTransitions implements Transitions.TransitionHandler {
if (taskView == null) return null;
// Opening types should all be initiated by shell
if (!TransitionUtil.isClosingType(request.getType())) return null;
- PendingTransition pending = findPending(taskView, true /* closing */, false /* latest */);
+ PendingTransition pending = findPendingCloseTransition(taskView);
if (pending == null) {
pending = new PendingTransition(request.getType(), null, taskView, null /* cookie */);
}
@@ -166,9 +186,9 @@ public class TaskViewTransitions implements Transitions.TransitionHandler {
private TaskViewTaskController findTaskView(ActivityManager.RunningTaskInfo taskInfo) {
for (int i = 0; i < mTaskViews.size(); ++i) {
- if (mTaskViews.get(i).getTaskInfo() == null) continue;
- if (taskInfo.token.equals(mTaskViews.get(i).getTaskInfo().token)) {
- return mTaskViews.get(i);
+ if (mTaskViews.keyAt(i).getTaskInfo() == null) continue;
+ if (taskInfo.token.equals(mTaskViews.keyAt(i).getTaskInfo().token)) {
+ return mTaskViews.keyAt(i);
}
}
return null;
@@ -176,30 +196,53 @@ public class TaskViewTransitions implements Transitions.TransitionHandler {
void startTaskView(@NonNull WindowContainerTransaction wct,
@NonNull TaskViewTaskController taskView, @NonNull IBinder launchCookie) {
+ updateVisibilityState(taskView, true /* visible */);
mPending.add(new PendingTransition(TRANSIT_OPEN, wct, taskView, launchCookie));
startNextTransition();
}
void setTaskViewVisible(TaskViewTaskController taskView, boolean visible) {
- PendingTransition pending = findPending(taskView, !visible, true /* latest */);
- if (pending != null) {
- // Already opening or creating a task, so no need to do anything here.
- return;
- }
+ if (mTaskViews.get(taskView).mVisible == visible) return;
if (taskView.getTaskInfo() == null) {
// Nothing to update, task is not yet available
return;
}
+ mTaskViews.get(taskView).mVisible = visible;
final WindowContainerTransaction wct = new WindowContainerTransaction();
wct.setHidden(taskView.getTaskInfo().token, !visible /* hidden */);
- pending = new PendingTransition(
+ wct.setBounds(taskView.getTaskInfo().token, mTaskViews.get(taskView).mBounds);
+ PendingTransition pending = new PendingTransition(
visible ? TRANSIT_TO_FRONT : TRANSIT_TO_BACK, wct, taskView, null /* cookie */);
mPending.add(pending);
startNextTransition();
// visibility is reported in transition.
}
+ void updateBoundsState(TaskViewTaskController taskView, Rect boundsOnScreen) {
+ TaskViewRequestedState state = mTaskViews.get(taskView);
+ state.mBounds.set(boundsOnScreen);
+ }
+
+ void updateVisibilityState(TaskViewTaskController taskView, boolean visible) {
+ TaskViewRequestedState state = mTaskViews.get(taskView);
+ state.mVisible = visible;
+ }
+
void setTaskBounds(TaskViewTaskController taskView, Rect boundsOnScreen) {
+ TaskViewRequestedState state = mTaskViews.get(taskView);
+ if (Objects.equals(boundsOnScreen, state.mBounds)) {
+ return;
+ }
+ state.mBounds.set(boundsOnScreen);
+ if (!state.mVisible) {
+ // Task view isn't visible, the bounds will next visibility update.
+ return;
+ }
+ if (hasPending()) {
+ // There is already a transition in-flight, the window bounds will be set in
+ // prepareOpenAnimation.
+ return;
+ }
WindowContainerTransaction wct = new WindowContainerTransaction();
wct.setBounds(taskView.getTaskInfo().token, boundsOnScreen);
mPending.add(new PendingTransition(TRANSIT_CHANGE, wct, taskView, null /* cookie */));
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
index 5a92f7830194..7c729a46b679 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
@@ -235,6 +235,7 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler,
private TransitionInfo subCopy(@NonNull TransitionInfo info,
@WindowManager.TransitionType int newType, boolean withChanges) {
final TransitionInfo out = new TransitionInfo(newType, withChanges ? info.getFlags() : 0);
+ out.setTrack(info.getTrack());
out.setDebugId(info.getDebugId());
if (withChanges) {
for (int i = 0; i < info.getChanges().size(); ++i) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java
index bcc37baa5b00..0f4645c0fdab 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java
@@ -122,14 +122,14 @@ public class TransitionAnimationHelper {
? R.styleable.WindowAnimation_taskToFrontEnterAnimation
: R.styleable.WindowAnimation_taskToFrontExitAnimation;
} else if (type == TRANSIT_CLOSE) {
- if (isTask) {
+ if ((changeFlags & FLAG_TRANSLUCENT) != 0 && !enter) {
+ translucent = true;
+ }
+ if (isTask && !translucent) {
animAttr = enter
? R.styleable.WindowAnimation_taskCloseEnterAnimation
: R.styleable.WindowAnimation_taskCloseExitAnimation;
} else {
- if ((changeFlags & FLAG_TRANSLUCENT) != 0 && !enter) {
- translucent = true;
- }
animAttr = enter
? R.styleable.WindowAnimation_activityCloseEnterAnimation
: R.styleable.WindowAnimation_activityCloseExitAnimation;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
index 08b0bf74f413..5c8791effe18 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
@@ -90,14 +90,21 @@ import java.util.Arrays;
* Basically: --start--> PENDING --onTransitionReady--> READY --play--> ACTIVE --finish--> |
* --merge--> MERGED --^
*
- * At the moment, only one transition can be animating at a time. While a transition is animating,
- * transitions will be queued in the "ready" state for their turn. At the same time, whenever a
- * transition makes it to the head of the "ready" queue, it will attempt to merge to with the
- * "active" transition. If the merge succeeds, it will be moved to the "active" transition's
- * "merged" and then the next "ready" transition can attempt to merge.
+ * The READY and beyond lifecycle is managed per "track". Within a track, all the animations are
+ * serialized as described; however, multiple tracks can play simultaneously. This implies that,
+ * within a track, only one transition can be animating ("active") at a time.
*
- * Once the "active" transition animation is finished, it will be removed from the "active" list
- * and then the next "ready" transition can play.
+ * While a transition is animating in a track, transitions dispatched to the track will be queued
+ * in the "ready" state for their turn. At the same time, whenever a transition makes it to the
+ * head of the "ready" queue, it will attempt to merge to with the "active" transition. If the
+ * merge succeeds, it will be moved to the "active" transition's "merged" list and then the next
+ * "ready" transition can attempt to merge. Once the "active" transition animation is finished,
+ * the next "ready" transition can play.
+ *
+ * Track assignments are expected to be provided by WMCore and this generally tries to maintain
+ * the same assignments. If, however, WMCore decides that a transition conflicts with >1 active
+ * track, it will be marked as SYNC. This means that all currently active tracks must be flushed
+ * before the SYNC transition can play.
*/
public class Transitions implements RemoteCallable<Transitions> {
static final String TAG = "ShellTransitions";
@@ -172,12 +179,15 @@ public class Transitions implements RemoteCallable<Transitions> {
private float mTransitionAnimationScaleSetting = 1.0f;
/**
- * How much time we allow for an animation to finish itself on sleep. If it takes longer, we
+ * How much time we allow for an animation to finish itself on sync. If it takes longer, we
* will force-finish it (on this end) which may leave it in a bad state but won't hang the
* device. This needs to be pretty small because it is an allowance for each queued animation,
* however it can't be too small since there is some potential IPC involved.
*/
- private static final int SLEEP_ALLOWANCE_MS = 120;
+ private static final int SYNC_ALLOWANCE_MS = 120;
+
+ /** For testing only. Disables the force-finish timeout on sync. */
+ private boolean mDisableForceSync = false;
private static final class ActiveTransition {
IBinder mToken;
@@ -190,23 +200,45 @@ public class Transitions implements RemoteCallable<Transitions> {
/** Ordered list of transitions which have been merged into this one. */
private ArrayList<ActiveTransition> mMerged;
+ boolean isSync() {
+ return (mInfo.getFlags() & TransitionInfo.FLAG_SYNC) != 0;
+ }
+
+ int getTrack() {
+ return mInfo != null ? mInfo.getTrack() : -1;
+ }
+
@Override
public String toString() {
if (mInfo != null && mInfo.getDebugId() >= 0) {
- return "(#" + mInfo.getDebugId() + ")" + mToken;
+ return "(#" + mInfo.getDebugId() + ")" + mToken + "@" + getTrack();
}
- return mToken.toString();
+ return mToken.toString() + "@" + getTrack();
+ }
+ }
+
+ private static class Track {
+ /** Keeps track of transitions which are ready to play but still waiting for their turn. */
+ final ArrayList<ActiveTransition> mReadyTransitions = new ArrayList<>();
+
+ /** The currently playing transition in this track. */
+ ActiveTransition mActiveTransition = null;
+
+ boolean isIdle() {
+ return mActiveTransition == null && mReadyTransitions.isEmpty();
}
}
/** Keeps track of transitions which have been started, but aren't ready yet. */
private final ArrayList<ActiveTransition> mPendingTransitions = new ArrayList<>();
- /** Keeps track of transitions which are ready to play but still waiting for their turn. */
- private final ArrayList<ActiveTransition> mReadyTransitions = new ArrayList<>();
+ /**
+ * Transitions which are ready to play, but haven't been sent to a track yet because a sync
+ * is ongoing.
+ */
+ private final ArrayList<ActiveTransition> mReadyDuringSync = new ArrayList<>();
- /** Keeps track of currently playing transitions. For now, there can only be 1 max. */
- private final ArrayList<ActiveTransition> mActiveTransitions = new ArrayList<>();
+ private final ArrayList<Track> mTracks = new ArrayList<>();
public Transitions(@NonNull Context context,
@NonNull ShellInit shellInit,
@@ -374,14 +406,17 @@ public class Transitions implements RemoteCallable<Transitions> {
* will be executed when the last active transition is finished.
*/
public void runOnIdle(Runnable runnable) {
- if (mActiveTransitions.isEmpty() && mPendingTransitions.isEmpty()
- && mReadyTransitions.isEmpty()) {
+ if (isIdle()) {
runnable.run();
} else {
mRunWhenIdleQueue.add(runnable);
}
}
+ void setDisableForceSyncForTest(boolean disable) {
+ mDisableForceSync = disable;
+ }
+
/**
* Sets up visibility/alpha/transforms to resemble the starting state of an animation.
*/
@@ -542,6 +577,13 @@ public class Transitions implements RemoteCallable<Transitions> {
return true;
}
+ private Track getOrCreateTrack(int trackId) {
+ while (trackId >= mTracks.size()) {
+ mTracks.add(new Track());
+ }
+ return mTracks.get(trackId);
+ }
+
@VisibleForTesting
void onTransitionReady(@NonNull IBinder transitionToken, @NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction t, @NonNull SurfaceControl.Transaction finishT) {
@@ -554,29 +596,58 @@ public class Transitions implements RemoteCallable<Transitions> {
+ Arrays.toString(mPendingTransitions.stream().map(
activeTransition -> activeTransition.mToken).toArray()));
}
- if (activeIdx > 0) {
- Log.e(TAG, "Transition became ready out-of-order " + mPendingTransitions.get(activeIdx)
- + ". Expected order: " + Arrays.toString(mPendingTransitions.stream().map(
- activeTransition -> activeTransition.mToken).toArray()));
- }
// Move from pending to ready
final ActiveTransition active = mPendingTransitions.remove(activeIdx);
- mReadyTransitions.add(active);
active.mInfo = info;
active.mStartT = t;
active.mFinishT = finishT;
-
- for (int i = 0; i < mObservers.size(); ++i) {
- mObservers.get(i).onTransitionReady(transitionToken, info, t, finishT);
+ if (activeIdx > 0) {
+ Log.i(TAG, "Transition might be ready out-of-order " + activeIdx + " for " + active
+ + ". This is ok if it's on a different track.");
}
+ if (!mReadyDuringSync.isEmpty()) {
+ mReadyDuringSync.add(active);
+ } else {
+ dispatchReady(active);
+ }
+ }
- if (info.getType() == TRANSIT_SLEEP) {
- if (activeIdx > 0 || !mActiveTransitions.isEmpty() || mReadyTransitions.size() > 1) {
+ /**
+ * Returns true if dispatching succeeded, otherwise false. Dispatching can fail if it is
+ * blocked by a sync or sleep.
+ */
+ boolean dispatchReady(ActiveTransition active) {
+ final TransitionInfo info = active.mInfo;
+
+ if (info.getType() == TRANSIT_SLEEP || active.isSync()) {
+ // Adding to *front*! If we are here, it means that it was pulled off the front
+ // so we are just putting it back; or, it is the first one so it doesn't matter.
+ mReadyDuringSync.add(0, active);
+ boolean hadPreceding = false;
+ // Now flush all the tracks.
+ for (int i = 0; i < mTracks.size(); ++i) {
+ final Track tr = mTracks.get(i);
+ if (tr.isIdle()) continue;
+ hadPreceding = true;
// Sleep starts a process of forcing all prior transitions to finish immediately
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Start finish-for-sleep");
- finishForSleep(null /* forceFinish */);
- return;
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
+ "Start finish-for-sync track %d", i);
+ finishForSync(i, null /* forceFinish */);
}
+ if (hadPreceding) {
+ return false;
+ }
+ // Actually able to process the sleep now, so re-remove it from the queue and continue
+ // the normal flow.
+ mReadyDuringSync.remove(active);
+ }
+
+ final Track track = getOrCreateTrack(info.getTrack());
+ track.mReadyTransitions.add(active);
+
+ for (int i = 0; i < mObservers.size(); ++i) {
+ mObservers.get(i).onTransitionReady(
+ active.mToken, info, active.mStartT, active.mFinishT);
}
if (info.getRootCount() == 0 && !alwaysReportToKeyguard(info)) {
@@ -585,7 +656,7 @@ public class Transitions implements RemoteCallable<Transitions> {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "No transition roots in %s so"
+ " abort", active);
onAbort(active);
- return;
+ return true;
}
final int changeSize = info.getChanges().size();
@@ -615,16 +686,17 @@ public class Transitions implements RemoteCallable<Transitions> {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
"Non-visible anim so abort: %s", active);
onAbort(active);
- return;
+ return true;
}
setupStartState(active.mInfo, active.mStartT, active.mFinishT);
- if (mReadyTransitions.size() > 1) {
+ if (track.mReadyTransitions.size() > 1) {
// There are already transitions waiting in the queue, so just return.
- return;
+ return true;
}
- processReadyQueue();
+ processReadyQueue(track);
+ return true;
}
/**
@@ -644,25 +716,53 @@ public class Transitions implements RemoteCallable<Transitions> {
return false;
}
- void processReadyQueue() {
- if (mReadyTransitions.isEmpty()) {
- // Check if idle.
- if (mActiveTransitions.isEmpty() && mPendingTransitions.isEmpty()) {
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "All active transition "
- + "animations finished");
- // Run all runnables from the run-when-idle queue.
- for (int i = 0; i < mRunWhenIdleQueue.size(); i++) {
- mRunWhenIdleQueue.get(i).run();
+ private boolean areTracksIdle() {
+ for (int i = 0; i < mTracks.size(); ++i) {
+ if (!mTracks.get(i).isIdle()) return false;
+ }
+ return true;
+ }
+
+ private boolean isAnimating() {
+ return !mReadyDuringSync.isEmpty() || !areTracksIdle();
+ }
+
+ private boolean isIdle() {
+ return mPendingTransitions.isEmpty() && !isAnimating();
+ }
+
+ void processReadyQueue(Track track) {
+ if (track.mReadyTransitions.isEmpty()) {
+ if (track.mActiveTransition == null) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Track %d became idle",
+ mTracks.indexOf(track));
+ if (areTracksIdle()) {
+ if (!mReadyDuringSync.isEmpty()) {
+ // Dispatch everything unless we hit another sync
+ while (!mReadyDuringSync.isEmpty()) {
+ ActiveTransition next = mReadyDuringSync.remove(0);
+ boolean success = dispatchReady(next);
+ // Hit a sync or sleep, so stop dispatching.
+ if (!success) break;
+ }
+ } else if (mPendingTransitions.isEmpty()) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "All active transition "
+ + "animations finished");
+ // Run all runnables from the run-when-idle queue.
+ for (int i = 0; i < mRunWhenIdleQueue.size(); i++) {
+ mRunWhenIdleQueue.get(i).run();
+ }
+ mRunWhenIdleQueue.clear();
+ }
}
- mRunWhenIdleQueue.clear();
}
return;
}
- final ActiveTransition ready = mReadyTransitions.get(0);
- if (mActiveTransitions.isEmpty()) {
- // The normal case, just play it (currently we only support 1 active transition).
- mReadyTransitions.remove(0);
- mActiveTransitions.add(ready);
+ final ActiveTransition ready = track.mReadyTransitions.get(0);
+ if (track.mActiveTransition == null) {
+ // The normal case, just play it.
+ track.mReadyTransitions.remove(0);
+ track.mActiveTransition = ready;
if (ready.mAborted) {
// finish now since there's nothing to animate. Calls back into processReadyQueue
onFinish(ready, null, null);
@@ -670,11 +770,11 @@ public class Transitions implements RemoteCallable<Transitions> {
}
playTransition(ready);
// Attempt to merge any more queued-up transitions.
- processReadyQueue();
+ processReadyQueue(track);
return;
}
// An existing animation is playing, so see if we can merge.
- final ActiveTransition playing = mActiveTransitions.get(0);
+ final ActiveTransition playing = track.mActiveTransition;
if (ready.mAborted) {
// record as merged since it is no-op. Calls back into processReadyQueue
onMerged(playing, ready);
@@ -688,18 +788,23 @@ public class Transitions implements RemoteCallable<Transitions> {
}
private void onMerged(@NonNull ActiveTransition playing, @NonNull ActiveTransition merged) {
+ if (playing.getTrack() != merged.getTrack()) {
+ throw new IllegalStateException("Can't merge across tracks: " + merged + " into "
+ + playing);
+ }
+ final Track track = mTracks.get(playing.getTrack());
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition was merged: %s into %s",
merged, playing);
int readyIdx = 0;
- if (mReadyTransitions.isEmpty() || mReadyTransitions.get(0) != merged) {
+ if (track.mReadyTransitions.isEmpty() || track.mReadyTransitions.get(0) != merged) {
Log.e(TAG, "Merged transition out-of-order? " + merged);
- readyIdx = mReadyTransitions.indexOf(merged);
+ readyIdx = track.mReadyTransitions.indexOf(merged);
if (readyIdx < 0) {
Log.e(TAG, "Merged a transition that is no-longer queued? " + merged);
return;
}
}
- mReadyTransitions.remove(readyIdx);
+ track.mReadyTransitions.remove(readyIdx);
if (playing.mMerged == null) {
playing.mMerged = new ArrayList<>();
}
@@ -712,7 +817,7 @@ public class Transitions implements RemoteCallable<Transitions> {
mObservers.get(i).onTransitionMerged(merged.mToken, playing.mToken);
}
// See if we should merge another transition.
- processReadyQueue();
+ processReadyQueue(track);
}
private void playTransition(@NonNull ActiveTransition active) {
@@ -781,6 +886,7 @@ public class Transitions implements RemoteCallable<Transitions> {
/** Aborts a transition. This will still queue it up to maintain order. */
private void onAbort(ActiveTransition transition) {
+ final Track track = mTracks.get(transition.getTrack());
// apply immediately since they may be "parallel" operations: We currently we use abort for
// thing which are independent to other transitions (like starting-window transfer).
transition.mStartT.apply();
@@ -796,11 +902,11 @@ public class Transitions implements RemoteCallable<Transitions> {
releaseSurfaces(transition.mInfo);
// This still went into the queue (to maintain the correct finish ordering).
- if (mReadyTransitions.size() > 1) {
+ if (track.mReadyTransitions.size() > 1) {
// There are already transitions waiting in the queue, so just return.
return;
}
- processReadyQueue();
+ processReadyQueue(track);
}
/**
@@ -815,17 +921,14 @@ public class Transitions implements RemoteCallable<Transitions> {
private void onFinish(ActiveTransition active,
@Nullable WindowContainerTransaction wct,
@Nullable WindowContainerTransactionCallback wctCB) {
- int activeIdx = mActiveTransitions.indexOf(active);
- if (activeIdx < 0) {
+ final Track track = mTracks.get(active.getTrack());
+ if (track.mActiveTransition != active) {
Log.e(TAG, "Trying to finish a non-running transition. Either remote crashed or "
+ " a handler didn't properly deal with a merge. " + active,
new RuntimeException());
return;
- } else if (activeIdx != 0) {
- // Relevant right now since we only allow 1 active transition at a time.
- Log.e(TAG, "Finishing a transition out of order. " + active);
}
- mActiveTransitions.remove(activeIdx);
+ track.mActiveTransition = null;
for (int i = 0; i < mObservers.size(); ++i) {
mObservers.get(i).onTransitionFinished(active.mToken, active.mAborted);
@@ -877,18 +980,20 @@ public class Transitions implements RemoteCallable<Transitions> {
}
// Now that this is done, check the ready queue for more work.
- processReadyQueue();
+ processReadyQueue(track);
}
private boolean isTransitionKnown(IBinder token) {
for (int i = 0; i < mPendingTransitions.size(); ++i) {
if (mPendingTransitions.get(i).mToken == token) return true;
}
- for (int i = 0; i < mReadyTransitions.size(); ++i) {
- if (mReadyTransitions.get(i).mToken == token) return true;
- }
- for (int i = 0; i < mActiveTransitions.size(); ++i) {
- final ActiveTransition active = mActiveTransitions.get(i);
+ for (int t = 0; t < mTracks.size(); ++t) {
+ final Track tr = mTracks.get(t);
+ for (int i = 0; i < tr.mReadyTransitions.size(); ++i) {
+ if (tr.mReadyTransitions.get(i).mToken == token) return true;
+ }
+ final ActiveTransition active = tr.mActiveTransition;
+ if (active == null) continue;
if (active.mToken == token) return true;
if (active.mMerged == null) continue;
for (int m = 0; m < active.mMerged.size(); ++m) {
@@ -963,7 +1068,7 @@ public class Transitions implements RemoteCallable<Transitions> {
*
* This works by "merging" the sleep transition into the currently-playing transition (even if
* its out-of-order) -- turning SLEEP into a signal. If the playing transition doesn't finish
- * within `SLEEP_ALLOWANCE_MS` from this merge attempt, this will then finish it directly (and
+ * within `SYNC_ALLOWANCE_MS` from this merge attempt, this will then finish it directly (and
* send an abort/consumed message).
*
* This is then repeated until there are no more pending sleep transitions.
@@ -971,48 +1076,53 @@ public class Transitions implements RemoteCallable<Transitions> {
* @param forceFinish When non-null, this is the transition that we last sent the SLEEP merge
* signal to -- so it will be force-finished if it's still running.
*/
- private void finishForSleep(@Nullable ActiveTransition forceFinish) {
- if ((mActiveTransitions.isEmpty() && mReadyTransitions.isEmpty())
- || mSleepHandler.mSleepTransitions.isEmpty()) {
+ private void finishForSync(int trackIdx, @Nullable ActiveTransition forceFinish) {
+ final Track track = mTracks.get(trackIdx);
+ if (forceFinish != null) {
+ final Track trk = mTracks.get(forceFinish.getTrack());
+ if (trk != track) {
+ Log.e(TAG, "finishForSleep: mismatched Tracks between forceFinish and logic "
+ + forceFinish.getTrack() + " vs " + trackIdx);
+ }
+ if (trk.mActiveTransition == forceFinish) {
+ Log.e(TAG, "Forcing transition to finish due to sync timeout: " + forceFinish);
+ forceFinish.mAborted = true;
+ // Last notify of it being consumed. Note: mHandler should never be null,
+ // but check just to be safe.
+ if (forceFinish.mHandler != null) {
+ forceFinish.mHandler.onTransitionConsumed(
+ forceFinish.mToken, true /* aborted */, null /* finishTransaction */);
+ }
+ onFinish(forceFinish, null, null);
+ }
+ }
+ if (track.isIdle() || mReadyDuringSync.isEmpty()) {
// Done finishing things.
- // Prevent any weird leaks... shouldn't happen though.
- mSleepHandler.mSleepTransitions.clear();
return;
}
- if (forceFinish != null && mActiveTransitions.contains(forceFinish)) {
- Log.e(TAG, "Forcing transition to finish due to sleep timeout: " + forceFinish);
- forceFinish.mAborted = true;
- // Last notify of it being consumed. Note: mHandler should never be null,
- // but check just to be safe.
- if (forceFinish.mHandler != null) {
- forceFinish.mHandler.onTransitionConsumed(
- forceFinish.mToken, true /* aborted */, null /* finishTransaction */);
- }
- onFinish(forceFinish, null, null);
- }
final SurfaceControl.Transaction dummyT = new SurfaceControl.Transaction();
final TransitionInfo dummyInfo = new TransitionInfo(TRANSIT_SLEEP, 0 /* flags */);
- while (!mActiveTransitions.isEmpty() && !mSleepHandler.mSleepTransitions.isEmpty()) {
- final ActiveTransition playing = mActiveTransitions.get(0);
- int sleepIdx = findByToken(mReadyTransitions, mSleepHandler.mSleepTransitions.get(0));
- if (sleepIdx >= 0) {
- // Try to signal that we are sleeping by attempting to merge the sleep transition
- // into the playing one.
- final ActiveTransition nextSleep = mReadyTransitions.get(sleepIdx);
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Attempt to merge SLEEP %s"
- + " into %s", nextSleep, playing);
- playing.mHandler.mergeAnimation(nextSleep.mToken, dummyInfo, dummyT,
- playing.mToken, (wct, cb) -> {});
- } else {
- Log.e(TAG, "Couldn't find sleep transition in ready list: "
- + mSleepHandler.mSleepTransitions.get(0));
+ while (track.mActiveTransition != null && !mReadyDuringSync.isEmpty()) {
+ final ActiveTransition playing = track.mActiveTransition;
+ final ActiveTransition nextSync = mReadyDuringSync.get(0);
+ if (!nextSync.isSync()) {
+ Log.e(TAG, "Somehow blocked on a non-sync transition? " + nextSync);
}
+ // Attempt to merge a SLEEP info to signal that the playing transition needs to
+ // fast-forward.
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Attempt to merge sync %s"
+ + " into %s via a SLEEP proxy", nextSync, playing);
+ playing.mHandler.mergeAnimation(nextSync.mToken, dummyInfo, dummyT,
+ playing.mToken, (wct, cb) -> {});
// it's possible to complete immediately. If that happens, just repeat the signal
// loop until we either finish everything or start playing an animation that isn't
// finishing immediately.
- if (!mActiveTransitions.isEmpty() && mActiveTransitions.get(0) == playing) {
- // Give it a (very) short amount of time to process it before forcing.
- mMainExecutor.executeDelayed(() -> finishForSleep(playing), SLEEP_ALLOWANCE_MS);
+ if (track.mActiveTransition == playing) {
+ if (!mDisableForceSync) {
+ // Give it a short amount of time to process it before forcing.
+ mMainExecutor.executeDelayed(() -> finishForSync(trackIdx, playing),
+ SYNC_ALLOWANCE_MS);
+ }
break;
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestShellExecutor.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestShellExecutor.java
index da95c77d2b89..499870220190 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestShellExecutor.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestShellExecutor.java
@@ -48,9 +48,8 @@ public class TestShellExecutor implements ShellExecutor {
}
public void flushAll() {
- for (Runnable r : mRunnables) {
- r.run();
+ while (!mRunnables.isEmpty()) {
+ mRunnables.remove(0).run();
}
- mRunnables.clear();
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTest.java
index b6d7ff3cd5cf..0a515cdea465 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTest.java
@@ -471,4 +471,53 @@ public class TaskViewTest extends ShellTestCase {
assertThat(insetsInfo.touchableRegion.contains(20, 20)).isFalse();
assertThat(insetsInfo.touchableRegion.contains(30, 30)).isFalse();
}
+
+ @Test
+ public void testTaskViewPrepareOpenAnimationSetsBoundsAndVisibility() {
+ assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS);
+
+ TaskViewBase taskViewBase = mock(TaskViewBase.class);
+ Rect bounds = new Rect(0, 0, 100, 100);
+ when(taskViewBase.getCurrentBoundsOnScreen()).thenReturn(bounds);
+ mTaskViewTaskController.setTaskViewBase(taskViewBase);
+
+ // Surface created, but task not available so bounds / visibility isn't set
+ mTaskView.surfaceCreated(mock(SurfaceHolder.class));
+ verify(mTaskViewTransitions, never()).updateVisibilityState(
+ eq(mTaskViewTaskController), eq(true));
+
+ // Make the task available / start prepareOpen
+ WindowContainerTransaction wct = mock(WindowContainerTransaction.class);
+ mTaskViewTaskController.prepareOpenAnimation(true /* newTask */,
+ new SurfaceControl.Transaction(), new SurfaceControl.Transaction(), mTaskInfo,
+ mLeash, wct);
+
+ // Bounds got set
+ verify(wct).setBounds(any(WindowContainerToken.class), eq(bounds));
+ // Visibility & bounds state got set
+ verify(mTaskViewTransitions).updateVisibilityState(eq(mTaskViewTaskController), eq(true));
+ verify(mTaskViewTransitions).updateBoundsState(eq(mTaskViewTaskController), eq(bounds));
+ }
+
+ @Test
+ public void testTaskViewPrepareOpenAnimationSetsVisibilityFalse() {
+ assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS);
+
+ TaskViewBase taskViewBase = mock(TaskViewBase.class);
+ Rect bounds = new Rect(0, 0, 100, 100);
+ when(taskViewBase.getCurrentBoundsOnScreen()).thenReturn(bounds);
+ mTaskViewTaskController.setTaskViewBase(taskViewBase);
+
+ // Task is available, but the surface was never created
+ WindowContainerTransaction wct = mock(WindowContainerTransaction.class);
+ mTaskViewTaskController.prepareOpenAnimation(true /* newTask */,
+ new SurfaceControl.Transaction(), new SurfaceControl.Transaction(), mTaskInfo,
+ mLeash, wct);
+
+ // Bounds do not get set as there is no surface
+ verify(wct, never()).setBounds(any(WindowContainerToken.class), any());
+ // Visibility is set to false, bounds aren't set
+ verify(mTaskViewTransitions).updateVisibilityState(eq(mTaskViewTaskController), eq(false));
+ verify(mTaskViewTransitions, never()).updateBoundsState(eq(mTaskViewTaskController), any());
+ }
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTransitionsTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTransitionsTest.java
new file mode 100644
index 000000000000..45590951dd1d
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTransitionsTest.java
@@ -0,0 +1,182 @@
+/*
+ * 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.wm.shell.taskview;
+
+import static android.view.WindowManager.TRANSIT_CHANGE;
+import static android.view.WindowManager.TRANSIT_TO_FRONT;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assume.assumeTrue;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+import android.app.ActivityManager;
+import android.graphics.Rect;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.view.SurfaceControl;
+import android.window.TransitionInfo;
+import android.window.WindowContainerToken;
+
+import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.transition.Transitions;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+public class TaskViewTransitionsTest extends ShellTestCase {
+
+ @Mock
+ Transitions mTransitions;
+ @Mock
+ TaskViewTaskController mTaskViewTaskController;
+ @Mock
+ ActivityManager.RunningTaskInfo mTaskInfo;
+ @Mock
+ WindowContainerToken mToken;
+
+ TaskViewTransitions mTaskViewTransitions;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+ doReturn(true).when(mTransitions).isRegistered();
+ }
+
+ mTaskInfo = new ActivityManager.RunningTaskInfo();
+ mTaskInfo.token = mToken;
+ mTaskInfo.taskId = 314;
+ mTaskInfo.taskDescription = mock(ActivityManager.TaskDescription.class);
+
+ mTaskViewTransitions = spy(new TaskViewTransitions(mTransitions));
+ mTaskViewTransitions.addTaskView(mTaskViewTaskController);
+ when(mTaskViewTaskController.getTaskInfo()).thenReturn(mTaskInfo);
+ }
+
+ @Test
+ public void testSetTaskBounds_taskNotVisible_noTransaction() {
+ assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS);
+
+ mTaskViewTransitions.setTaskViewVisible(mTaskViewTaskController, false);
+ mTaskViewTransitions.setTaskBounds(mTaskViewTaskController,
+ new Rect(0, 0, 100, 100));
+
+ assertThat(mTaskViewTransitions.findPending(mTaskViewTaskController, TRANSIT_CHANGE))
+ .isNull();
+ }
+
+ @Test
+ public void testSetTaskBounds_taskVisible_boundsChangeTransaction() {
+ assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS);
+
+ mTaskViewTransitions.setTaskViewVisible(mTaskViewTaskController, true);
+
+ // Consume the pending transaction from visibility change
+ TaskViewTransitions.PendingTransition pending =
+ mTaskViewTransitions.findPending(mTaskViewTaskController, TRANSIT_TO_FRONT);
+ assertThat(pending).isNotNull();
+ mTaskViewTransitions.startAnimation(pending.mClaimed,
+ mock(TransitionInfo.class),
+ new SurfaceControl.Transaction(),
+ new SurfaceControl.Transaction(),
+ mock(Transitions.TransitionFinishCallback.class));
+ // Verify it was consumed
+ TaskViewTransitions.PendingTransition pending2 =
+ mTaskViewTransitions.findPending(mTaskViewTaskController, TRANSIT_TO_FRONT);
+ assertThat(pending2).isNull();
+
+ // Test that set bounds creates a new transaction
+ mTaskViewTransitions.setTaskBounds(mTaskViewTaskController,
+ new Rect(0, 0, 100, 100));
+ assertThat(mTaskViewTransitions.findPending(mTaskViewTaskController, TRANSIT_CHANGE))
+ .isNotNull();
+ }
+
+ @Test
+ public void testSetTaskBounds_taskVisibleWithPending_noTransaction() {
+ assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS);
+
+ mTaskViewTransitions.setTaskViewVisible(mTaskViewTaskController, true);
+
+ TaskViewTransitions.PendingTransition pending =
+ mTaskViewTransitions.findPending(mTaskViewTaskController, TRANSIT_TO_FRONT);
+ assertThat(pending).isNotNull();
+
+ mTaskViewTransitions.setTaskBounds(mTaskViewTaskController,
+ new Rect(0, 0, 100, 100));
+ assertThat(mTaskViewTransitions.findPending(mTaskViewTaskController, TRANSIT_CHANGE))
+ .isNull();
+ }
+
+ @Test
+ public void testSetTaskBounds_sameBounds_noTransaction() {
+ assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS);
+
+ mTaskViewTransitions.setTaskViewVisible(mTaskViewTaskController, true);
+
+ // Consume the pending transaction from visibility change
+ TaskViewTransitions.PendingTransition pending =
+ mTaskViewTransitions.findPending(mTaskViewTaskController, TRANSIT_TO_FRONT);
+ assertThat(pending).isNotNull();
+ mTaskViewTransitions.startAnimation(pending.mClaimed,
+ mock(TransitionInfo.class),
+ new SurfaceControl.Transaction(),
+ new SurfaceControl.Transaction(),
+ mock(Transitions.TransitionFinishCallback.class));
+ // Verify it was consumed
+ TaskViewTransitions.PendingTransition pending2 =
+ mTaskViewTransitions.findPending(mTaskViewTaskController, TRANSIT_TO_FRONT);
+ assertThat(pending2).isNull();
+
+ // Test that set bounds creates a new transaction
+ mTaskViewTransitions.setTaskBounds(mTaskViewTaskController,
+ new Rect(0, 0, 100, 100));
+ TaskViewTransitions.PendingTransition pendingBounds =
+ mTaskViewTransitions.findPending(mTaskViewTaskController, TRANSIT_CHANGE);
+ assertThat(pendingBounds).isNotNull();
+
+ // Consume the pending bounds transaction
+ mTaskViewTransitions.startAnimation(pendingBounds.mClaimed,
+ mock(TransitionInfo.class),
+ new SurfaceControl.Transaction(),
+ new SurfaceControl.Transaction(),
+ mock(Transitions.TransitionFinishCallback.class));
+ // Verify it was consumed
+ TaskViewTransitions.PendingTransition pendingBounds1 =
+ mTaskViewTransitions.findPending(mTaskViewTaskController, TRANSIT_CHANGE);
+ assertThat(pendingBounds1).isNull();
+
+ // Test that setting the same bounds doesn't creates a new transaction
+ mTaskViewTransitions.setTaskBounds(mTaskViewTaskController,
+ new Rect(0, 0, 100, 100));
+ TaskViewTransitions.PendingTransition pendingBounds2 =
+ mTaskViewTransitions.findPending(mTaskViewTaskController, TRANSIT_CHANGE);
+ assertThat(pendingBounds2).isNull();
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
index 5cd548bfe5ab..8eb5c6a08d88 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
@@ -30,10 +30,12 @@ import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_FIRST_CUSTOM;
import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY;
import static android.view.WindowManager.TRANSIT_OPEN;
+import static android.view.WindowManager.TRANSIT_SLEEP;
import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
import static android.window.TransitionInfo.FLAG_DISPLAY_HAS_ALERT_WINDOWS;
import static android.window.TransitionInfo.FLAG_IS_DISPLAY;
+import static android.window.TransitionInfo.FLAG_SYNC;
import static android.window.TransitionInfo.FLAG_TRANSLUCENT;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
@@ -63,6 +65,7 @@ import android.os.IBinder;
import android.os.Looper;
import android.os.RemoteException;
import android.util.ArraySet;
+import android.util.Pair;
import android.view.Surface;
import android.view.SurfaceControl;
import android.view.WindowManager;
@@ -587,7 +590,8 @@ public class ShellTransitionTests extends ShellTestCase {
requestStartTransition(transitions, transitTokenNotReady);
mDefaultHandler.setSimulateMerge(true);
- mDefaultHandler.mFinishes.get(0).onTransitionFinished(null /* wct */, null /* wctCB */);
+ mDefaultHandler.mFinishes.get(0).second.onTransitionFinished(
+ null /* wct */, null /* wctCB */);
// Make sure that the non-ready transition is not merged.
assertEquals(0, mDefaultHandler.mergeCount());
@@ -1059,6 +1063,223 @@ public class ShellTransitionTests extends ShellTestCase {
assertEquals(1, mDefaultHandler.activeCount());
}
+ @Test
+ public void testMultipleTracks() {
+ Transitions transitions = createTestTransitions();
+ transitions.replaceDefaultHandlerForTest(mDefaultHandler);
+ TestTransitionHandler alwaysMergeHandler = new TestTransitionHandler();
+ alwaysMergeHandler.setSimulateMerge(true);
+
+ final boolean[] becameIdle = new boolean[]{false};
+
+ final WindowContainerTransaction emptyWCT = new WindowContainerTransaction();
+ final SurfaceControl.Transaction mockSCT = mock(SurfaceControl.Transaction.class);
+
+ // Make this always merge so we can ensure that it does NOT get a merge-attempt for a
+ // different track.
+ IBinder transitA = transitions.startTransition(TRANSIT_OPEN, emptyWCT, alwaysMergeHandler);
+ // start tracking idle
+ transitions.runOnIdle(() -> becameIdle[0] = true);
+
+ IBinder transitB = transitions.startTransition(TRANSIT_OPEN, emptyWCT, mDefaultHandler);
+ IBinder transitC = transitions.startTransition(TRANSIT_CLOSE, emptyWCT, mDefaultHandler);
+
+ TransitionInfo infoA = new TransitionInfoBuilder(TRANSIT_OPEN)
+ .addChange(TRANSIT_OPEN).build();
+ infoA.setTrack(0);
+ TransitionInfo infoB = new TransitionInfoBuilder(TRANSIT_OPEN)
+ .addChange(TRANSIT_OPEN).build();
+ infoB.setTrack(1);
+ TransitionInfo infoC = new TransitionInfoBuilder(TRANSIT_CLOSE)
+ .addChange(TRANSIT_OPEN).addChange(TRANSIT_CLOSE).build();
+ infoC.setTrack(1);
+
+ transitions.onTransitionReady(transitA, infoA, mockSCT, mockSCT);
+ assertEquals(1, alwaysMergeHandler.activeCount());
+ transitions.onTransitionReady(transitB, infoB, mockSCT, mockSCT);
+ // should now be running in parallel
+ assertEquals(1, mDefaultHandler.activeCount());
+ assertEquals(1, alwaysMergeHandler.activeCount());
+ // make sure we didn't try to merge into a different track.
+ assertEquals(0, alwaysMergeHandler.mergeCount());
+
+ // This should be queued-up since it is on track 1 (same as B)
+ transitions.onTransitionReady(transitC, infoC, mockSCT, mockSCT);
+ assertEquals(1, mDefaultHandler.activeCount());
+ assertEquals(1, alwaysMergeHandler.activeCount());
+
+ // Now finish B and make sure C starts
+ mDefaultHandler.finishOne();
+ mMainExecutor.flushAll();
+
+ // Now C and A running in parallel
+ assertEquals(1, mDefaultHandler.activeCount());
+ assertEquals(1, alwaysMergeHandler.activeCount());
+ assertEquals(0, alwaysMergeHandler.mergeCount());
+
+ // Finish A
+ alwaysMergeHandler.finishOne();
+ mMainExecutor.flushAll();
+
+ // C still running
+ assertEquals(0, alwaysMergeHandler.activeCount());
+ assertEquals(1, mDefaultHandler.activeCount());
+ assertFalse(becameIdle[0]);
+
+ mDefaultHandler.finishOne();
+ mMainExecutor.flushAll();
+
+ assertEquals(0, mDefaultHandler.activeCount());
+ assertTrue(becameIdle[0]);
+ }
+
+ @Test
+ public void testSyncMultipleTracks() {
+ Transitions transitions = createTestTransitions();
+ transitions.replaceDefaultHandlerForTest(mDefaultHandler);
+ TestTransitionHandler secondHandler = new TestTransitionHandler();
+
+ // Disable the forced early-sync-finish so that we can test the ordering mechanics.
+ transitions.setDisableForceSyncForTest(true);
+ mDefaultHandler.mFinishOnSync = false;
+ secondHandler.mFinishOnSync = false;
+
+ final WindowContainerTransaction emptyWCT = new WindowContainerTransaction();
+ final SurfaceControl.Transaction mockSCT = mock(SurfaceControl.Transaction.class);
+
+ // Make this always merge so we can ensure that it does NOT get a merge-attempt for a
+ // different track.
+ IBinder transitA = transitions.startTransition(TRANSIT_OPEN, emptyWCT, mDefaultHandler);
+ IBinder transitB = transitions.startTransition(TRANSIT_OPEN, emptyWCT, secondHandler);
+ IBinder transitC = transitions.startTransition(TRANSIT_CLOSE, emptyWCT, secondHandler);
+ IBinder transitSync = transitions.startTransition(TRANSIT_CLOSE, emptyWCT, mDefaultHandler);
+ IBinder transitD = transitions.startTransition(TRANSIT_OPEN, emptyWCT, secondHandler);
+ IBinder transitE = transitions.startTransition(TRANSIT_OPEN, emptyWCT, mDefaultHandler);
+
+ TransitionInfo infoA = new TransitionInfoBuilder(TRANSIT_OPEN)
+ .addChange(TRANSIT_OPEN).build();
+ infoA.setTrack(0);
+ TransitionInfo infoB = new TransitionInfoBuilder(TRANSIT_OPEN)
+ .addChange(TRANSIT_OPEN).build();
+ infoB.setTrack(1);
+ TransitionInfo infoC = new TransitionInfoBuilder(TRANSIT_CLOSE)
+ .addChange(TRANSIT_OPEN).addChange(TRANSIT_CLOSE).build();
+ infoC.setTrack(1);
+ TransitionInfo infoSync = new TransitionInfoBuilder(TRANSIT_CLOSE)
+ .addChange(TRANSIT_OPEN).addChange(TRANSIT_CLOSE).build();
+ infoSync.setTrack(0);
+ infoSync.setFlags(FLAG_SYNC);
+ TransitionInfo infoD = new TransitionInfoBuilder(TRANSIT_OPEN)
+ .addChange(TRANSIT_OPEN).build();
+ infoD.setTrack(1);
+ TransitionInfo infoE = new TransitionInfoBuilder(TRANSIT_OPEN)
+ .addChange(TRANSIT_OPEN).build();
+ infoE.setTrack(0);
+
+ // Start A B and C where A is track 0, B and C are track 1 (C should be queued)
+ transitions.onTransitionReady(transitA, infoA, mockSCT, mockSCT);
+ transitions.onTransitionReady(transitB, infoB, mockSCT, mockSCT);
+ transitions.onTransitionReady(transitC, infoC, mockSCT, mockSCT);
+ // should now be running in parallel (with one queued)
+ assertEquals(1, mDefaultHandler.activeCount());
+ assertEquals(1, secondHandler.activeCount());
+
+ // Make the sync ready and the following (D, E) ready.
+ transitions.onTransitionReady(transitSync, infoSync, mockSCT, mockSCT);
+ transitions.onTransitionReady(transitD, infoD, mockSCT, mockSCT);
+ transitions.onTransitionReady(transitE, infoE, mockSCT, mockSCT);
+
+ // nothing should have happened yet since the sync is queued and blocking everything.
+ assertEquals(1, mDefaultHandler.activeCount());
+ assertEquals(1, secondHandler.activeCount());
+
+ // Finish A (which is track 0 like the sync).
+ mDefaultHandler.finishOne();
+ mMainExecutor.flushAll();
+
+ // Even though the sync is on track 0 and track 0 became idle, it should NOT be started yet
+ // because it must wait for everything. Additionally, D/E shouldn't start yet either.
+ assertEquals(0, mDefaultHandler.activeCount());
+ assertEquals(1, secondHandler.activeCount());
+
+ // Now finish B and C -- this should then allow the sync to start and D to run (in parallel)
+ secondHandler.finishOne();
+ secondHandler.finishOne();
+ mMainExecutor.flushAll();
+
+ // Now the sync and D (on track 1) should be running
+ assertEquals(1, mDefaultHandler.activeCount());
+ assertEquals(1, secondHandler.activeCount());
+
+ // finish the sync. track 0 still has E
+ mDefaultHandler.finishOne();
+ mMainExecutor.flushAll();
+ assertEquals(1, mDefaultHandler.activeCount());
+
+ mDefaultHandler.finishOne();
+ secondHandler.finishOne();
+ mMainExecutor.flushAll();
+
+ assertEquals(0, mDefaultHandler.activeCount());
+ assertEquals(0, secondHandler.activeCount());
+ }
+
+ @Test
+ public void testForceSyncTracks() {
+ Transitions transitions = createTestTransitions();
+ transitions.replaceDefaultHandlerForTest(mDefaultHandler);
+ TestTransitionHandler secondHandler = new TestTransitionHandler();
+
+ final WindowContainerTransaction emptyWCT = new WindowContainerTransaction();
+ final SurfaceControl.Transaction mockSCT = mock(SurfaceControl.Transaction.class);
+
+ // Make this always merge so we can ensure that it does NOT get a merge-attempt for a
+ // different track.
+ IBinder transitA = transitions.startTransition(TRANSIT_OPEN, emptyWCT, mDefaultHandler);
+ IBinder transitB = transitions.startTransition(TRANSIT_OPEN, emptyWCT, mDefaultHandler);
+ IBinder transitC = transitions.startTransition(TRANSIT_CLOSE, emptyWCT, secondHandler);
+ IBinder transitD = transitions.startTransition(TRANSIT_OPEN, emptyWCT, secondHandler);
+ IBinder transitSync = transitions.startTransition(TRANSIT_CLOSE, emptyWCT, mDefaultHandler);
+
+ TransitionInfo infoA = new TransitionInfoBuilder(TRANSIT_OPEN)
+ .addChange(TRANSIT_OPEN).build();
+ infoA.setTrack(0);
+ TransitionInfo infoB = new TransitionInfoBuilder(TRANSIT_OPEN)
+ .addChange(TRANSIT_OPEN).build();
+ infoB.setTrack(0);
+ TransitionInfo infoC = new TransitionInfoBuilder(TRANSIT_CLOSE)
+ .addChange(TRANSIT_OPEN).addChange(TRANSIT_CLOSE).build();
+ infoC.setTrack(1);
+ TransitionInfo infoD = new TransitionInfoBuilder(TRANSIT_OPEN)
+ .addChange(TRANSIT_OPEN).build();
+ infoD.setTrack(1);
+ TransitionInfo infoSync = new TransitionInfoBuilder(TRANSIT_CLOSE)
+ .addChange(TRANSIT_OPEN).addChange(TRANSIT_CLOSE).build();
+ infoSync.setTrack(0);
+ infoSync.setFlags(FLAG_SYNC);
+
+ transitions.onTransitionReady(transitA, infoA, mockSCT, mockSCT);
+ transitions.onTransitionReady(transitB, infoB, mockSCT, mockSCT);
+ transitions.onTransitionReady(transitC, infoC, mockSCT, mockSCT);
+ transitions.onTransitionReady(transitD, infoD, mockSCT, mockSCT);
+ // should now be running in parallel (with one queued in each)
+ assertEquals(1, mDefaultHandler.activeCount());
+ assertEquals(1, secondHandler.activeCount());
+
+ // Make the sync ready.
+ transitions.onTransitionReady(transitSync, infoSync, mockSCT, mockSCT);
+ mMainExecutor.flushAll();
+
+ // Everything should be forced-finish now except the sync
+ assertEquals(1, mDefaultHandler.activeCount());
+ assertEquals(0, secondHandler.activeCount());
+
+ mDefaultHandler.finishOne();
+ mMainExecutor.flushAll();
+
+ assertEquals(0, mDefaultHandler.activeCount());
+ }
+
class ChangeBuilder {
final TransitionInfo.Change mChange;
@@ -1097,9 +1318,11 @@ public class ShellTransitionTests extends ShellTestCase {
}
class TestTransitionHandler implements Transitions.TransitionHandler {
- ArrayList<Transitions.TransitionFinishCallback> mFinishes = new ArrayList<>();
+ ArrayList<Pair<IBinder, Transitions.TransitionFinishCallback>> mFinishes =
+ new ArrayList<>();
final ArrayList<IBinder> mMerged = new ArrayList<>();
boolean mSimulateMerge = false;
+ boolean mFinishOnSync = true;
final ArraySet<IBinder> mShouldMerge = new ArraySet<>();
@Override
@@ -1107,7 +1330,7 @@ public class ShellTransitionTests extends ShellTestCase {
@NonNull SurfaceControl.Transaction startTransaction,
@NonNull SurfaceControl.Transaction finishTransaction,
@NonNull Transitions.TransitionFinishCallback finishCallback) {
- mFinishes.add(finishCallback);
+ mFinishes.add(new Pair<>(transition, finishCallback));
return true;
}
@@ -1115,6 +1338,13 @@ public class ShellTransitionTests extends ShellTestCase {
public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
@NonNull Transitions.TransitionFinishCallback finishCallback) {
+ if (mFinishOnSync && info.getType() == TRANSIT_SLEEP) {
+ for (int i = 0; i < mFinishes.size(); ++i) {
+ if (mFinishes.get(i).first != mergeTarget) continue;
+ mFinishes.remove(i).second.onTransitionFinished(null, null);
+ return;
+ }
+ }
if (!(mSimulateMerge || mShouldMerge.contains(transition))) return;
mMerged.add(transition);
finishCallback.onTransitionFinished(null /* wct */, null /* wctCB */);
@@ -1136,18 +1366,19 @@ public class ShellTransitionTests extends ShellTestCase {
}
void finishAll() {
- final ArrayList<Transitions.TransitionFinishCallback> finishes = mFinishes;
+ final ArrayList<Pair<IBinder, Transitions.TransitionFinishCallback>> finishes =
+ mFinishes;
mFinishes = new ArrayList<>();
for (int i = finishes.size() - 1; i >= 0; --i) {
- finishes.get(i).onTransitionFinished(null /* wct */, null /* wctCB */);
+ finishes.get(i).second.onTransitionFinished(null /* wct */, null /* wctCB */);
}
mShouldMerge.clear();
}
void finishOne() {
- Transitions.TransitionFinishCallback fin = mFinishes.remove(0);
+ Pair<IBinder, Transitions.TransitionFinishCallback> fin = mFinishes.remove(0);
mMerged.clear();
- fin.onTransitionFinished(null /* wct */, null /* wctCB */);
+ fin.second.onTransitionFinished(null /* wct */, null /* wctCB */);
}
int activeCount() {
diff --git a/libs/hwui/DamageAccumulator.cpp b/libs/hwui/DamageAccumulator.cpp
index 9db47c3ba090..a8d170d00ef7 100644
--- a/libs/hwui/DamageAccumulator.cpp
+++ b/libs/hwui/DamageAccumulator.cpp
@@ -207,27 +207,6 @@ static void applyTransforms(DirtyStack* frame, DirtyStack* end) {
}
}
-static void computeTransformImpl(const DirtyStack* frame, const DirtyStack* end,
- Matrix4* outMatrix) {
- while (frame != end) {
- switch (frame->type) {
- case TransformRenderNode:
- frame->renderNode->applyViewPropertyTransforms(*outMatrix);
- break;
- case TransformMatrix4:
- outMatrix->multiply(*frame->matrix4);
- break;
- case TransformNone:
- // nothing to be done
- break;
- default:
- LOG_ALWAYS_FATAL("Tried to compute transform with an invalid type: %d",
- frame->type);
- }
- frame = frame->prev;
- }
-}
-
void DamageAccumulator::applyRenderNodeTransform(DirtyStack* frame) {
if (frame->pendingDirty.isEmpty()) {
return;
@@ -282,9 +261,6 @@ void DamageAccumulator::finish(SkRect* totalDirty) {
DamageAccumulator::StretchResult DamageAccumulator::findNearestStretchEffect() const {
DirtyStack* frame = mHead;
- const auto& headProperties = mHead->renderNode->properties();
- float startWidth = headProperties.getWidth();
- float startHeight = headProperties.getHeight();
while (frame->prev != frame) {
if (frame->type == TransformRenderNode) {
const auto& renderNode = frame->renderNode;
@@ -295,21 +271,16 @@ DamageAccumulator::StretchResult DamageAccumulator::findNearestStretchEffect() c
const float height = (float) frameRenderNodeProperties.getHeight();
if (!effect.isEmpty()) {
Matrix4 stretchMatrix;
- computeTransformImpl(mHead, frame, &stretchMatrix);
- Rect stretchRect = Rect(0.f, 0.f, startWidth, startHeight);
+ computeTransformImpl(frame, &stretchMatrix);
+ Rect stretchRect = Rect(0.f, 0.f, width, height);
stretchMatrix.mapRect(stretchRect);
return StretchResult{
- .stretchEffect = &effect,
- .childRelativeBounds = SkRect::MakeLTRB(
- stretchRect.left,
- stretchRect.top,
- stretchRect.right,
- stretchRect.bottom
- ),
- .width = width,
- .height = height
- };
+ .stretchEffect = &effect,
+ .parentBounds = SkRect::MakeLTRB(stretchRect.left, stretchRect.top,
+ stretchRect.right, stretchRect.bottom),
+ .width = width,
+ .height = height};
}
}
frame = frame->prev;
diff --git a/libs/hwui/DamageAccumulator.h b/libs/hwui/DamageAccumulator.h
index 90a35174d929..c4249af392d3 100644
--- a/libs/hwui/DamageAccumulator.h
+++ b/libs/hwui/DamageAccumulator.h
@@ -70,9 +70,9 @@ public:
const StretchEffect* stretchEffect;
/**
- * Bounds of the child relative to the stretch container
+ * Bounds of the stretching container
*/
- const SkRect childRelativeBounds;
+ const SkRect parentBounds;
/**
* Width of the stretch container
diff --git a/libs/hwui/jni/android_graphics_RenderNode.cpp b/libs/hwui/jni/android_graphics_RenderNode.cpp
index ac1f92dee507..24a785c18711 100644
--- a/libs/hwui/jni/android_graphics_RenderNode.cpp
+++ b/libs/hwui/jni/android_graphics_RenderNode.cpp
@@ -584,13 +584,15 @@ static void android_view_RenderNode_requestPositionUpdates(JNIEnv* env, jobject,
uirenderer::Rect bounds(props.getWidth(), props.getHeight());
bool useStretchShader =
Properties::getStretchEffectBehavior() != StretchEffectBehavior::UniformScale;
- if (useStretchShader && info.stretchEffectCount) {
+ // Compute the transform bounds first before calculating the stretch
+ transform.mapRect(bounds);
+
+ bool hasStretch = useStretchShader && info.stretchEffectCount;
+ if (hasStretch) {
handleStretchEffect(info, bounds);
}
- transform.mapRect(bounds);
-
- if (CC_LIKELY(transform.isPureTranslate())) {
+ if (CC_LIKELY(transform.isPureTranslate()) && !hasStretch) {
// snap/round the computed bounds, so they match the rounding behavior
// of the clear done in SurfaceView#draw().
bounds.snapGeometryToPixelBoundaries(false);
@@ -665,45 +667,42 @@ static void android_view_RenderNode_requestPositionUpdates(JNIEnv* env, jobject,
return env;
}
- void stretchTargetBounds(const StretchEffect& stretchEffect,
- float width, float height,
- const SkRect& childRelativeBounds,
- uirenderer::Rect& bounds) {
- float normalizedLeft = childRelativeBounds.left() / width;
- float normalizedTop = childRelativeBounds.top() / height;
- float normalizedRight = childRelativeBounds.right() / width;
- float normalizedBottom = childRelativeBounds.bottom() / height;
- float reverseLeft = width *
- (stretchEffect.computeStretchedPositionX(normalizedLeft) -
- normalizedLeft);
- float reverseTop = height *
- (stretchEffect.computeStretchedPositionY(normalizedTop) -
- normalizedTop);
- float reverseRight = width *
- (stretchEffect.computeStretchedPositionX(normalizedRight) -
- normalizedLeft);
- float reverseBottom = height *
- (stretchEffect.computeStretchedPositionY(normalizedBottom) -
- normalizedTop);
- bounds.left = reverseLeft;
- bounds.top = reverseTop;
- bounds.right = reverseRight;
- bounds.bottom = reverseBottom;
- }
-
void handleStretchEffect(const TreeInfo& info, uirenderer::Rect& targetBounds) {
// Search up to find the nearest stretcheffect parent
const DamageAccumulator::StretchResult result =
info.damageAccumulator->findNearestStretchEffect();
const StretchEffect* effect = result.stretchEffect;
- if (!effect) {
+ if (effect) {
+ // Compute the number of pixels that the stretching container
+ // scales by.
+ // Then compute the scale factor that the child would need
+ // to scale in order to occupy the same pixel bounds.
+ auto& parentBounds = result.parentBounds;
+ auto parentWidth = parentBounds.width();
+ auto parentHeight = parentBounds.height();
+ auto& stretchDirection = effect->getStretchDirection();
+ auto stretchX = stretchDirection.x();
+ auto stretchY = stretchDirection.y();
+ auto stretchXPixels = parentWidth * std::abs(stretchX);
+ auto stretchYPixels = parentHeight * std::abs(stretchY);
+ SkMatrix stretchMatrix;
+
+ auto childScaleX = 1 + (stretchXPixels / targetBounds.getWidth());
+ auto childScaleY = 1 + (stretchYPixels / targetBounds.getHeight());
+ auto pivotX = stretchX > 0 ? targetBounds.left : targetBounds.right;
+ auto pivotY = stretchY > 0 ? targetBounds.top : targetBounds.bottom;
+ stretchMatrix.setScale(childScaleX, childScaleY, pivotX, pivotY);
+ SkRect rect = SkRect::MakeLTRB(targetBounds.left, targetBounds.top,
+ targetBounds.right, targetBounds.bottom);
+ SkRect dst = stretchMatrix.mapRect(rect);
+ targetBounds.left = dst.left();
+ targetBounds.top = dst.top();
+ targetBounds.right = dst.right();
+ targetBounds.bottom = dst.bottom();
+ } else {
return;
}
- const auto& childRelativeBounds = result.childRelativeBounds;
- stretchTargetBounds(*effect, result.width, result.height,
- childRelativeBounds,targetBounds);
-
if (Properties::getStretchEffectBehavior() ==
StretchEffectBehavior::Shader) {
JNIEnv* env = jnienv();
@@ -714,9 +713,8 @@ static void android_view_RenderNode_requestPositionUpdates(JNIEnv* env, jobject,
gPositionListener.clazz, gPositionListener.callApplyStretch, mListener,
info.canvasContext.getFrameNumber(), result.width, result.height,
stretchDirection.fX, stretchDirection.fY, effect->maxStretchAmountX,
- effect->maxStretchAmountY, childRelativeBounds.left(),
- childRelativeBounds.top(), childRelativeBounds.right(),
- childRelativeBounds.bottom());
+ effect->maxStretchAmountY, targetBounds.left, targetBounds.top,
+ targetBounds.right, targetBounds.bottom);
if (!keepListening) {
env->DeleteGlobalRef(mListener);
mListener = nullptr;
diff --git a/media/java/android/media/ImageUtils.java b/media/java/android/media/ImageUtils.java
index 7ac84460463e..f4caad727407 100644
--- a/media/java/android/media/ImageUtils.java
+++ b/media/java/android/media/ImageUtils.java
@@ -20,6 +20,7 @@ import android.graphics.ImageFormat;
import android.graphics.PixelFormat;
import android.hardware.HardwareBuffer;
import android.media.Image.Plane;
+import android.util.Log;
import android.util.Size;
import libcore.io.Memory;
@@ -30,6 +31,7 @@ import java.nio.ByteBuffer;
* Package private utility class for hosting commonly used Image related methods.
*/
class ImageUtils {
+ private static final String IMAGEUTILS_LOG_TAG = "ImageUtils";
/**
* Only a subset of the formats defined in
@@ -266,11 +268,15 @@ class ImageUtils {
break;
case PixelFormat.RGBA_8888:
case PixelFormat.RGBX_8888:
+ case PixelFormat.RGBA_1010102:
estimatedBytePerPixel = 4.0;
break;
default:
- throw new UnsupportedOperationException(
- String.format("Invalid format specified %d", format));
+ if (Log.isLoggable(IMAGEUTILS_LOG_TAG, Log.VERBOSE)) {
+ Log.v(IMAGEUTILS_LOG_TAG, "getEstimatedNativeAllocBytes() uses default"
+ + "estimated native allocation size.");
+ }
+ estimatedBytePerPixel = 1.0;
}
return (int)(width * height * estimatedBytePerPixel * numImages);
@@ -295,6 +301,7 @@ class ImageUtils {
}
case PixelFormat.RGB_565:
case PixelFormat.RGBA_8888:
+ case PixelFormat.RGBA_1010102:
case PixelFormat.RGBX_8888:
case PixelFormat.RGB_888:
case ImageFormat.JPEG:
@@ -312,8 +319,11 @@ class ImageUtils {
case ImageFormat.PRIVATE:
return new Size(0, 0);
default:
- throw new UnsupportedOperationException(
- String.format("Invalid image format %d", image.getFormat()));
+ if (Log.isLoggable(IMAGEUTILS_LOG_TAG, Log.VERBOSE)) {
+ Log.v(IMAGEUTILS_LOG_TAG, "getEffectivePlaneSizeForImage() uses"
+ + "image's width and height for plane size.");
+ }
+ return new Size(image.getWidth(), image.getHeight());
}
}
diff --git a/media/java/android/media/VolumeProvider.java b/media/java/android/media/VolumeProvider.java
index 7cf63f4d5aec..22741f450c18 100644
--- a/media/java/android/media/VolumeProvider.java
+++ b/media/java/android/media/VolumeProvider.java
@@ -17,6 +17,7 @@ package android.media;
import android.annotation.IntDef;
import android.annotation.Nullable;
+import android.media.MediaRouter2.RoutingController;
import android.media.session.MediaSession;
import java.lang.annotation.Retention;
@@ -66,32 +67,28 @@ public abstract class VolumeProvider {
private Callback mCallback;
/**
- * Create a new volume provider for handling volume events. You must specify
- * the type of volume control, the maximum volume that can be used, and the
- * current volume on the output.
+ * Creates a new volume provider for handling volume events.
*
- * @param volumeControl The method for controlling volume that is used by
- * this provider.
+ * @param volumeControl See {@link #getVolumeControl()}.
* @param maxVolume The maximum allowed volume.
* @param currentVolume The current volume on the output.
*/
-
public VolumeProvider(@ControlType int volumeControl, int maxVolume, int currentVolume) {
this(volumeControl, maxVolume, currentVolume, null);
}
/**
- * Create a new volume provider for handling volume events. You must specify
- * the type of volume control, the maximum volume that can be used, and the
- * current volume on the output.
+ * Creates a new volume provider for handling volume events.
*
- * @param volumeControl The method for controlling volume that is used by
- * this provider.
+ * @param volumeControl See {@link #getVolumeControl()}.
* @param maxVolume The maximum allowed volume.
* @param currentVolume The current volume on the output.
- * @param volumeControlId The volume control ID of this provider.
+ * @param volumeControlId See {@link #getVolumeControlId()}.
*/
- public VolumeProvider(@ControlType int volumeControl, int maxVolume, int currentVolume,
+ public VolumeProvider(
+ @ControlType int volumeControl,
+ int maxVolume,
+ int currentVolume,
@Nullable String volumeControlId) {
mControlType = volumeControl;
mMaxVolume = maxVolume;
@@ -100,7 +97,10 @@ public abstract class VolumeProvider {
}
/**
- * Get the volume control type that this volume provider uses.
+ * Gets the volume control type that this volume provider uses.
+ *
+ * <p>One of {@link #VOLUME_CONTROL_FIXED}, {@link #VOLUME_CONTROL_ABSOLUTE}, or {@link
+ * #VOLUME_CONTROL_RELATIVE}.
*
* @return The volume control type for this volume provider
*/
@@ -110,7 +110,7 @@ public abstract class VolumeProvider {
}
/**
- * Get the maximum volume this provider allows.
+ * Gets the maximum volume this provider allows.
*
* @return The max allowed volume.
*/
@@ -129,8 +129,8 @@ public abstract class VolumeProvider {
}
/**
- * Notify the system that the current volume has been changed. This must be
- * called every time the volume changes to ensure it is displayed properly.
+ * Notifies the system that the current volume has been changed. This must be called every time
+ * the volume changes to ensure it is displayed properly.
*
* @param currentVolume The current volume on the output.
*/
@@ -142,10 +142,11 @@ public abstract class VolumeProvider {
}
/**
- * Gets the volume control ID. It can be used to identify which volume provider is
- * used by the session.
+ * Gets the {@link RoutingController#getId() routing controller id} of the {@link
+ * RoutingController} associated with this volume provider, or null if unset.
*
- * @return the volume control ID or {@code null} if it isn't set.
+ * <p>This id allows mapping this volume provider to a routing controller, which provides
+ * information about the media route and allows controlling its volume.
*/
@Nullable
public final String getVolumeControlId() {
diff --git a/media/java/android/media/session/MediaController.java b/media/java/android/media/session/MediaController.java
index 62c4a5167316..b43ff63f3fcc 100644
--- a/media/java/android/media/session/MediaController.java
+++ b/media/java/android/media/session/MediaController.java
@@ -29,7 +29,6 @@ import android.media.AudioAttributes;
import android.media.AudioManager;
import android.media.MediaMetadata;
import android.media.Rating;
-import android.media.RoutingSessionInfo;
import android.media.VolumeProvider;
import android.media.VolumeProvider.ControlType;
import android.media.session.MediaSession.QueueItem;
@@ -993,26 +992,23 @@ public final class MediaController {
/**
* Creates a new playback info.
*
- * @param playbackType The playback type. Should be {@link #PLAYBACK_TYPE_LOCAL} or
- * {@link #PLAYBACK_TYPE_REMOTE}
- * @param volumeControl The volume control. Should be one of:
- * {@link VolumeProvider#VOLUME_CONTROL_ABSOLUTE},
- * {@link VolumeProvider#VOLUME_CONTROL_RELATIVE}, and
- * {@link VolumeProvider#VOLUME_CONTROL_FIXED}.
+ * @param playbackType The playback type. Should be {@link #PLAYBACK_TYPE_LOCAL} or {@link
+ * #PLAYBACK_TYPE_REMOTE}
+ * @param volumeControl See {@link #getVolumeControl()}.
* @param maxVolume The max volume. Should be equal or greater than zero.
* @param currentVolume The current volume. Should be in the interval [0, maxVolume].
* @param audioAttrs The audio attributes for this playback. Should not be null.
- * @param volumeControlId The {@link RoutingSessionInfo#getId() routing session id} of the
- * {@link RoutingSessionInfo} associated with this controller, or null if not
- * applicable. This id allows mapping this controller to a routing session which, when
- * applicable, provides information about the remote device, and support for volume
- * adjustment.
+ * @param volumeControlId See {@link #getVolumeControlId()}.
* @hide
*/
@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
- public PlaybackInfo(@PlaybackType int playbackType, @ControlType int volumeControl,
- @IntRange(from = 0) int maxVolume, @IntRange(from = 0) int currentVolume,
- @NonNull AudioAttributes audioAttrs, @Nullable String volumeControlId) {
+ public PlaybackInfo(
+ @PlaybackType int playbackType,
+ @ControlType int volumeControl,
+ @IntRange(from = 0) int maxVolume,
+ @IntRange(from = 0) int currentVolume,
+ @NonNull AudioAttributes audioAttrs,
+ @Nullable String volumeControlId) {
mPlaybackType = playbackType;
mVolumeControl = volumeControl;
mMaxVolume = maxVolume;
@@ -1044,14 +1040,8 @@ public final class MediaController {
}
/**
- * Get the type of volume control that can be used. One of:
- * <ul>
- * <li>{@link VolumeProvider#VOLUME_CONTROL_ABSOLUTE}</li>
- * <li>{@link VolumeProvider#VOLUME_CONTROL_RELATIVE}</li>
- * <li>{@link VolumeProvider#VOLUME_CONTROL_FIXED}</li>
- * </ul>
- *
- * @return The type of volume control that may be used with this session.
+ * Get the volume control type associated to the session, as indicated by {@link
+ * VolumeProvider#getVolumeControl()}.
*/
public int getVolumeControl() {
return mVolumeControl;
@@ -1076,10 +1066,9 @@ public final class MediaController {
}
/**
- * Get the audio attributes for this session. The attributes will affect
- * volume handling for the session. When the volume type is
- * {@link PlaybackInfo#PLAYBACK_TYPE_REMOTE} these may be ignored by the
- * remote volume handler.
+ * Get the audio attributes for this session. The attributes will affect volume handling for
+ * the session. When the playback type is {@link PlaybackInfo#PLAYBACK_TYPE_REMOTE} these
+ * may be ignored by the remote volume handler.
*
* @return The attributes for this session.
*/
@@ -1088,19 +1077,9 @@ public final class MediaController {
}
/**
- * Gets the volume control ID for this session. It can be used to identify which
- * volume provider is used by the session.
- * <p>
- * When the session starts to use {@link #PLAYBACK_TYPE_REMOTE remote volume handling},
- * a volume provider should be set and it may set the volume control ID of the provider
- * if the session wants to inform which volume provider is used.
- * It can be {@code null} if the session didn't set the volume control ID or it uses
- * {@link #PLAYBACK_TYPE_LOCAL local playback}.
- * </p>
- *
- * @return the volume control ID for this session or {@code null} if it uses local playback
- * or not set.
- * @see VolumeProvider#getVolumeControlId()
+ * Get the routing controller ID for this session, as indicated by {@link
+ * VolumeProvider#getVolumeControlId()}. Returns null if unset, or if {@link
+ * #getPlaybackType()} is {@link #PLAYBACK_TYPE_LOCAL}.
*/
@Nullable
public String getVolumeControlId() {
diff --git a/media/java/android/media/soundtrigger/SoundTriggerManager.java b/media/java/android/media/soundtrigger/SoundTriggerManager.java
index c41bd1bc3094..b6d70afa85b7 100644
--- a/media/java/android/media/soundtrigger/SoundTriggerManager.java
+++ b/media/java/android/media/soundtrigger/SoundTriggerManager.java
@@ -22,6 +22,7 @@ import android.annotation.CallbackExecutor;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
+import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.annotation.TestApi;
@@ -56,6 +57,7 @@ import com.android.internal.app.ISoundTriggerSession;
import com.android.internal.util.Preconditions;
import java.util.HashMap;
+import java.util.List;
import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.Executor;
@@ -74,6 +76,7 @@ public final class SoundTriggerManager {
private static final String TAG = "SoundTriggerManager";
private final Context mContext;
+ private final ISoundTriggerService mSoundTriggerService;
private final ISoundTriggerSession mSoundTriggerSession;
private final IBinder mBinderToken = new Binder();
@@ -114,13 +117,97 @@ public final class SoundTriggerManager {
}
}
} catch (RemoteException e) {
- throw e.rethrowAsRuntimeException();
+ throw e.rethrowFromSystemServer();
}
mContext = context;
+ mSoundTriggerService = soundTriggerService;
mReceiverInstanceMap = new HashMap<UUID, SoundTriggerDetector>();
}
/**
+ * Construct a {@link SoundTriggerManager} which connects to a specified module.
+ *
+ * @param moduleProperties - Properties representing the module to attach to
+ * @return - A new {@link SoundTriggerManager} which interfaces with the test module.
+ * @hide
+ */
+ @TestApi
+ @SuppressLint("ManagerLookup")
+ public @NonNull SoundTriggerManager createManagerForModule(
+ @NonNull ModuleProperties moduleProperties) {
+ return new SoundTriggerManager(mContext, mSoundTriggerService,
+ Objects.requireNonNull(moduleProperties));
+ }
+
+ /**
+ * Construct a {@link SoundTriggerManager} which connects to a ST module
+ * which is available for instrumentation through {@link attachInstrumentation}.
+ *
+ * @return - A new {@link SoundTriggerManager} which interfaces with the test module.
+ * @hide
+ */
+ @TestApi
+ @SuppressLint("ManagerLookup")
+ public @NonNull SoundTriggerManager createManagerForTestModule() {
+ return new SoundTriggerManager(mContext, mSoundTriggerService, getTestModuleProperties());
+ }
+
+ private final @NonNull SoundTrigger.ModuleProperties getTestModuleProperties() {
+ var moduleProps = listModuleProperties()
+ .stream()
+ .filter((SoundTrigger.ModuleProperties prop)
+ -> prop.getSupportedModelArch().equals(SoundTrigger.FAKE_HAL_ARCH))
+ .findFirst()
+ .orElse(null);
+ if (moduleProps == null) {
+ throw new IllegalStateException("Fake ST HAL should always be available");
+ }
+ return moduleProps;
+ }
+
+ // Helper constructor to create a manager object attached to a specific ST module.
+ private SoundTriggerManager(@NonNull Context context,
+ @NonNull ISoundTriggerService soundTriggerService,
+ @NonNull ModuleProperties properties) {
+ try {
+ Identity originatorIdentity = new Identity();
+ originatorIdentity.packageName = ActivityThread.currentOpPackageName();
+ try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
+ mSoundTriggerSession = soundTriggerService.attachAsOriginator(
+ originatorIdentity,
+ Objects.requireNonNull(properties),
+ mBinderToken);
+ }
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ mContext = Objects.requireNonNull(context);
+ mSoundTriggerService = Objects.requireNonNull(soundTriggerService);
+ mReceiverInstanceMap = new HashMap<UUID, SoundTriggerDetector>();
+ }
+
+ /**
+ * Enumerate the available ST modules. Use {@link createManagerForModule(ModuleProperties)} to
+ * receive a {@link SoundTriggerManager} attached to a specified ST module.
+ * @return - List of available ST modules to attach to.
+ * @hide
+ */
+ @TestApi
+ public static @NonNull List<ModuleProperties> listModuleProperties() {
+ try {
+ ISoundTriggerService service = ISoundTriggerService.Stub.asInterface(
+ ServiceManager.getService(Context.SOUND_TRIGGER_SERVICE));
+ Identity originatorIdentity = new Identity();
+ originatorIdentity.packageName = ActivityThread.currentOpPackageName();
+ try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
+ return service.listModuleProperties(originatorIdentity);
+ }
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Updates the given sound trigger model.
* @deprecated replace with {@link #loadSoundModel}
* SoundTriggerService model database will be removed
@@ -317,6 +404,17 @@ public final class SoundTriggerManager {
SoundTrigger.GenericSoundModel getGenericSoundModel() {
return mGenericSoundModel;
}
+
+ /**
+ * Return a {@link SoundTrigger.SoundModel} view of the model for
+ * test purposes.
+ * @hide
+ */
+ @TestApi
+ public @NonNull SoundTrigger.SoundModel getSoundModel() {
+ return mGenericSoundModel;
+ }
+
}
@@ -369,7 +467,8 @@ public final class SoundTriggerManager {
*/
@RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER)
@UnsupportedAppUsage
- public int loadSoundModel(SoundModel soundModel) {
+ @TestApi
+ public int loadSoundModel(@NonNull SoundModel soundModel) {
if (soundModel == null || mSoundTriggerSession == null) {
return STATUS_ERROR;
}
diff --git a/packages/CompanionDeviceManager/res/layout/list_item_permission.xml b/packages/CompanionDeviceManager/res/layout/list_item_permission.xml
index 6c463e18ef11..6bfcd82b0a4a 100644
--- a/packages/CompanionDeviceManager/res/layout/list_item_permission.xml
+++ b/packages/CompanionDeviceManager/res/layout/list_item_permission.xml
@@ -22,7 +22,8 @@
android:orientation="horizontal"
android:paddingStart="32dp"
android:paddingEnd="32dp"
- android:paddingTop="12dp">
+ android:paddingTop="6dp"
+ android:paddingBottom="6dp">
<ImageView
android:id="@+id/permission_icon"
diff --git a/packages/CompanionDeviceManager/res/values/strings.xml b/packages/CompanionDeviceManager/res/values/strings.xml
index ebfb86d4d6ba..256e0eb9c847 100644
--- a/packages/CompanionDeviceManager/res/values/strings.xml
+++ b/packages/CompanionDeviceManager/res/values/strings.xml
@@ -113,6 +113,18 @@
<!-- Back button for the helper consent dialog [CHAR LIMIT=30] -->
<string name="consent_back">Back</string>
+ <!-- Action when permission list view is expanded CHAR LIMIT=30] -->
+ <string name="permission_expanded">Expanded</string>
+
+ <!-- Expand action permission list CHAR LIMIT=30] -->
+ <string name="permission_expand">Expand</string>
+
+ <!-- Action when permission list view is collapsed CHAR LIMIT=30] -->
+ <string name="permission_collapsed">Collapsed</string>
+
+ <!-- Collapse action permission list CHAR LIMIT=30] -->
+ <string name="permission_collapse">Collapse</string>
+
<!-- ================== System data transfer ==================== -->
<!-- Title of the permission sync confirmation dialog. [CHAR LIMIT=NONE] -->
<string name="permission_sync_confirmation_title">Give apps on &lt;strong&gt;<xliff:g id="companion_device_name" example="Galaxy Watch 5">%1$s</xliff:g>&lt;/strong&gt; the same permissions as on &lt;strong&gt;<xliff:g id="primary_device_name" example="Pixel 6">%2$s</xliff:g>&lt;/strong&gt;?</string>
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/PermissionListAdapter.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/PermissionListAdapter.java
index d2fd78012193..b86ef649331a 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/PermissionListAdapter.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/PermissionListAdapter.java
@@ -27,6 +27,7 @@ import android.util.ArrayMap;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
+import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.TextView;
@@ -121,6 +122,10 @@ class PermissionListAdapter extends RecyclerView.Adapter<PermissionListAdapter.V
if (viewHolder.mExpandButton.getTag() == null) {
viewHolder.mExpandButton.setTag(R.drawable.btn_expand_more);
}
+
+ setAccessibility(view, viewType,
+ AccessibilityNodeInfo.ACTION_CLICK, R.string.permission_expand);
+
// Add expand buttons if the permissions are more than PERMISSION_SIZE in this list also
// make the summary invisible by default.
if (mPermissions.size() > PERMISSION_SIZE) {
@@ -132,10 +137,18 @@ class PermissionListAdapter extends RecyclerView.Adapter<PermissionListAdapter.V
viewHolder.mExpandButton.setImageResource(R.drawable.btn_expand_less);
viewHolder.mPermissionSummary.setVisibility(View.VISIBLE);
viewHolder.mExpandButton.setTag(R.drawable.btn_expand_less);
+ view.setContentDescription(mContext.getString(R.string.permission_expanded));
+ setAccessibility(view, viewType,
+ AccessibilityNodeInfo.ACTION_CLICK, R.string.permission_collapse);
+ viewHolder.mPermissionSummary.setFocusable(true);
} else {
viewHolder.mExpandButton.setImageResource(R.drawable.btn_expand_more);
viewHolder.mPermissionSummary.setVisibility(View.GONE);
viewHolder.mExpandButton.setTag(R.drawable.btn_expand_more);
+ view.setContentDescription(mContext.getString(R.string.permission_collapsed));
+ setAccessibility(view, viewType,
+ AccessibilityNodeInfo.ACTION_CLICK, R.string.permission_expanded);
+ viewHolder.mPermissionSummary.setFocusable(false);
}
});
} else {
@@ -187,6 +200,18 @@ class PermissionListAdapter extends RecyclerView.Adapter<PermissionListAdapter.V
}
}
+ private void setAccessibility(View view, int viewType, int action, int resourceId) {
+ final String actionString = mContext.getString(resourceId);
+ final String permission = mContext.getString(sTitleMap.get(viewType));
+ view.setAccessibilityDelegate(new View.AccessibilityDelegate() {
+ public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfo(host, info);
+ info.addAction(new AccessibilityNodeInfo.AccessibilityAction(action,
+ actionString + permission));
+ }
+ });
+ }
+
void setPermissionType(List<Integer> permissions) {
mPermissions = permissions;
}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/UninstallUninstalling.java b/packages/PackageInstaller/src/com/android/packageinstaller/UninstallUninstalling.java
index b60aba8be6f7..e6710fff1bd5 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/UninstallUninstalling.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/UninstallUninstalling.java
@@ -83,7 +83,7 @@ public class UninstallUninstalling extends Activity implements
}
UserManager customUserManager = UninstallUninstalling.this
- .createContextAsUser(UserHandle.of(user.getIdentifier()), 0)
+ .createContextAsUser(user, 0)
.getSystemService(UserManager.class);
if (customUserManager.isUserOfType(UserManager.USER_TYPE_PROFILE_CLONE)) {
isCloneUser = true;
@@ -117,7 +117,7 @@ public class UninstallUninstalling extends Activity implements
int flags = allUsers ? PackageManager.DELETE_ALL_USERS : 0;
flags |= keepData ? PackageManager.DELETE_KEEP_DATA : 0;
- getPackageManager().getPackageInstaller().uninstall(
+ createContextAsUser(user, 0).getPackageManager().getPackageInstaller().uninstall(
new VersionedPackage(mAppInfo.packageName,
PackageManager.VERSION_CODE_HIGHEST),
flags, pendingIntent.getIntentSender());
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/UninstallerActivity.java b/packages/PackageInstaller/src/com/android/packageinstaller/UninstallerActivity.java
index 7250bdd4bec6..9c67817cbef4 100755..100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/UninstallerActivity.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/UninstallerActivity.java
@@ -367,10 +367,10 @@ public class UninstallerActivity extends Activity {
int flags = mDialogInfo.allUsers ? PackageManager.DELETE_ALL_USERS : 0;
flags |= keepData ? PackageManager.DELETE_KEEP_DATA : 0;
- getPackageManager().getPackageInstaller().uninstall(
- new VersionedPackage(mDialogInfo.appInfo.packageName,
- PackageManager.VERSION_CODE_HIGHEST),
- flags, pendingIntent.getIntentSender());
+ createContextAsUser(mDialogInfo.user, 0).getPackageManager().getPackageInstaller()
+ .uninstall(new VersionedPackage(mDialogInfo.appInfo.packageName,
+ PackageManager.VERSION_CODE_HIGHEST), flags,
+ pendingIntent.getIntentSender());
} catch (Exception e) {
notificationManager.cancel(uninstallId);
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 751fbaad2abe..a110f56d09bd 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -23,7 +23,9 @@
>
<!-- Standard permissions granted to the shell. -->
+ <uses-permission android:name="android.permission.MANAGE_HEALTH_PERMISSIONS" />
<uses-permission android:name="android.permission.MANAGE_HEALTH_DATA" />
+ <uses-permission android:name="android.permission.health.READ_EXERCISE_ROUTE" />
<uses-permission android:name="android.permission.MIGRATE_HEALTH_CONNECT_DATA" />
<uses-permission android:name="android.permission.LAUNCH_DEVICE_MANAGER_SETUP" />
<uses-permission android:name="android.permission.GET_RUNTIME_PERMISSIONS" />
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 9d32e905d85d..62c6c1df91b2 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -243,15 +243,11 @@ filegroup {
// domain
"tests/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractorTest.kt",
"tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt",
- "tests/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractorTest.kt",
- "tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt",
"tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt",
"tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt",
"tests/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorTest.kt",
"tests/src/com/android/systemui/keyguard/domain/quickaffordance/FakeKeyguardQuickAffordanceRegistry.kt",
"tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorWithCoroutinesTest.kt",
- "tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt",
- "tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorWithCoroutinesTest.kt",
// ui
"tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt",
"tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModelTest.kt",
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index a00f401756f7..24de487945db 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -380,6 +380,9 @@
<service android:name="SystemUIService"
android:exported="true"
/>
+ <service android:name=".wallet.controller.WalletContextualLocationsService"
+ android:exported="true"
+ />
<!-- Service for dumping extremely verbose content during a bug report -->
<service android:name=".dump.SystemUIAuxiliaryDumpService"
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/FontInterpolator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/FontInterpolator.kt
index f0a82113c3a3..83e44b69812b 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/FontInterpolator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/FontInterpolator.kt
@@ -18,8 +18,10 @@ package com.android.systemui.animation
import android.graphics.fonts.Font
import android.graphics.fonts.FontVariationAxis
+import android.util.LruCache
import android.util.MathUtils
import android.util.MathUtils.abs
+import androidx.annotation.VisibleForTesting
import java.lang.Float.max
import java.lang.Float.min
@@ -34,6 +36,10 @@ private const val FONT_ITALIC_MIN = 0f
private const val FONT_ITALIC_ANIMATION_STEP = 0.1f
private const val FONT_ITALIC_DEFAULT_VALUE = 0f
+// Benchmarked via Perfetto, difference between 10 and 50 entries is about 0.3ms in
+// frame draw time on a Pixel 6.
+@VisibleForTesting const val FONT_CACHE_MAX_ENTRIES = 10
+
/** Provide interpolation of two fonts by adjusting font variation settings. */
class FontInterpolator {
@@ -81,8 +87,8 @@ class FontInterpolator {
// Font interpolator has two level caches: one for input and one for font with different
// variation settings. No synchronization is needed since FontInterpolator is not designed to be
// thread-safe and can be used only on UI thread.
- private val interpCache = hashMapOf<InterpKey, Font>()
- private val verFontCache = hashMapOf<VarFontKey, Font>()
+ private val interpCache = LruCache<InterpKey, Font>(FONT_CACHE_MAX_ENTRIES)
+ private val verFontCache = LruCache<VarFontKey, Font>(FONT_CACHE_MAX_ENTRIES)
// Mutable keys for recycling.
private val tmpInterpKey = InterpKey(null, null, 0f)
@@ -152,7 +158,7 @@ class FontInterpolator {
tmpVarFontKey.set(start, newAxes)
val axesCachedFont = verFontCache[tmpVarFontKey]
if (axesCachedFont != null) {
- interpCache[InterpKey(start, end, progress)] = axesCachedFont
+ interpCache.put(InterpKey(start, end, progress), axesCachedFont)
return axesCachedFont
}
@@ -160,8 +166,8 @@ class FontInterpolator {
// Font.Builder#build won't throw IOException since creating fonts from existing fonts will
// not do any IO work.
val newFont = Font.Builder(start).setFontVariationSettings(newAxes.toTypedArray()).build()
- interpCache[InterpKey(start, end, progress)] = newFont
- verFontCache[VarFontKey(start, newAxes)] = newFont
+ interpCache.put(InterpKey(start, end, progress), newFont)
+ verFontCache.put(VarFontKey(start, newAxes), newFont)
return newFont
}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt
index 9e9929e79d47..3ee97be360f0 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt
@@ -24,8 +24,10 @@ import android.graphics.Canvas
import android.graphics.Typeface
import android.graphics.fonts.Font
import android.text.Layout
+import android.util.LruCache
private const val DEFAULT_ANIMATION_DURATION: Long = 300
+private const val TYPEFACE_CACHE_MAX_ENTRIES = 5
typealias GlyphCallback = (TextAnimator.PositionedGlyph, Float) -> Unit
/**
@@ -114,7 +116,7 @@ class TextAnimator(layout: Layout, private val invalidateCallback: () -> Unit) {
private val fontVariationUtils = FontVariationUtils()
- private val typefaceCache = HashMap<String, Typeface?>()
+ private val typefaceCache = LruCache<String, Typeface>(TYPEFACE_CACHE_MAX_ENTRIES)
fun updateLayout(layout: Layout) {
textInterpolator.layout = layout
@@ -218,12 +220,12 @@ class TextAnimator(layout: Layout, private val invalidateCallback: () -> Unit) {
}
if (!fvar.isNullOrBlank()) {
- textInterpolator.targetPaint.typeface =
- typefaceCache.getOrElse(fvar) {
- textInterpolator.targetPaint.fontVariationSettings = fvar
+ textInterpolator.targetPaint.typeface = typefaceCache.get(fvar) ?: run {
+ textInterpolator.targetPaint.fontVariationSettings = fvar
+ textInterpolator.targetPaint.typeface?.also {
typefaceCache.put(fvar, textInterpolator.targetPaint.typeface)
- textInterpolator.targetPaint.typeface
}
+ }
}
if (color != null) {
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt
index 6ca7f12e842b..3fda83d7b6f4 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt
@@ -263,6 +263,13 @@ class DefaultClockController(
view.animateDoze(dozeState.isActive, !hasJumped)
}
}
+
+ override fun onPickerCarouselSwiping(swipingFraction: Float, previewRatio: Float) {
+ // TODO(b/278936436): refactor this part when we change recomputePadding
+ // when on the side, swipingFraction = 0, translationY should offset
+ // the top margin change in recomputePadding to make clock be centered
+ view.translationY = 0.5f * view.bottom * (1 - swipingFraction)
+ }
}
inner class LargeClockAnimations(
diff --git a/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java b/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java
index e0d01845562f..1811c02d549d 100644
--- a/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java
+++ b/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java
@@ -187,7 +187,7 @@ public interface BcSmartspaceDataPlugin extends Plugin {
if (action.getIntent() != null) {
startIntent(v, action.getIntent(), showOnLockscreen);
} else if (action.getPendingIntent() != null) {
- startPendingIntent(action.getPendingIntent(), showOnLockscreen);
+ startPendingIntent(v, action.getPendingIntent(), showOnLockscreen);
}
} catch (ActivityNotFoundException e) {
Log.w(TAG, "Could not launch intent for action: " + action, e);
@@ -199,7 +199,7 @@ public interface BcSmartspaceDataPlugin extends Plugin {
if (action.getIntent() != null) {
startIntent(v, action.getIntent(), showOnLockscreen);
} else if (action.getPendingIntent() != null) {
- startPendingIntent(action.getPendingIntent(), showOnLockscreen);
+ startPendingIntent(v, action.getPendingIntent(), showOnLockscreen);
}
} catch (ActivityNotFoundException e) {
Log.w(TAG, "Could not launch intent for action: " + action, e);
@@ -210,6 +210,6 @@ public interface BcSmartspaceDataPlugin extends Plugin {
void startIntent(View v, Intent i, boolean showOnLockscreen);
/** Start the PendingIntent */
- void startPendingIntent(PendingIntent pi, boolean showOnLockscreen);
+ void startPendingIntent(View v, PendingIntent pi, boolean showOnLockscreen);
}
}
diff --git a/packages/SystemUI/res/layout/status_bar.xml b/packages/SystemUI/res/layout/status_bar.xml
index 64aa629b02f0..331307a0d9f4 100644
--- a/packages/SystemUI/res/layout/status_bar.xml
+++ b/packages/SystemUI/res/layout/status_bar.xml
@@ -44,6 +44,8 @@
<LinearLayout android:id="@+id/status_bar_contents"
android:layout_width="match_parent"
android:layout_height="match_parent"
+ android:clipChildren="false"
+ android:clipToPadding="false"
android:paddingStart="@dimen/status_bar_padding_start"
android:paddingEnd="@dimen/status_bar_padding_end"
android:paddingTop="@dimen/status_bar_padding_top"
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index bbac7b014064..4e68efeea870 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -404,7 +404,34 @@
<string name="biometric_dialog_last_pin_attempt_before_wipe_profile">If you enter an incorrect PIN on the next attempt, your work profile and its data will be deleted.</string>
<!-- Content of a dialog shown when the user only has one attempt left to provide the correct password before the work profile is removed. [CHAR LIMIT=NONE] -->
<string name="biometric_dialog_last_password_attempt_before_wipe_profile">If you enter an incorrect password on the next attempt, your work profile and its data will be deleted.</string>
-
+ <!-- Confirmation button label for a dialog shown when the system requires the user to re-enroll their biometrics. [CHAR LIMIT=20] -->
+ <string name="biometric_re_enroll_dialog_confirm">Set up</string>
+ <!-- Cancel button label for a dialog shown when the system requires the user to re-enroll their biometric. [CHAR LIMIT=20] -->
+ <string name="biometric_re_enroll_dialog_cancel">Not now</string>
+ <!-- Notification content shown when the system requires the user to re-enroll their biometrics. [CHAR LIMIT=NONE] -->
+ <string name="biometric_re_enroll_notification_content">This is required to improve security and performance</string>
+ <!-- Notification title shown when the system requires the user to re-enroll their fingerprint. [CHAR LIMIT=NONE] -->
+ <string name="fingerprint_re_enroll_notification_title">Set up Fingerprint Unlock again</string>
+ <!-- Name shown for system notifications related to the fingerprint unlock feature. [CHAR LIMIT=NONE] -->
+ <string name="fingerprint_re_enroll_notification_name">Fingerprint Unlock</string>
+ <!-- Title for a dialog shown when the system requires the user to re-enroll their fingerprint. [CHAR LIMIT=NONE] -->
+ <string name="fingerprint_re_enroll_dialog_title">Set up Fingerprint Unlock</string>
+ <!-- Content for a dialog shown when the system requires the user to re-enroll their fingerprint. [CHAR LIMIT=NONE] -->
+ <string name="fingerprint_re_enroll_dialog_content">To set up Fingerprint Unlock again, your current fingerprint images and models will be deleted.\n\nAfter they\’re deleted, you\’ll need to set up Fingerprint Unlock again to use your fingerprint to unlock your phone or verify it\’s you.</string>
+ <!-- Content for a dialog shown when the system requires the user to re-enroll their fingerprint (singular). [CHAR LIMIT=NONE] -->
+ <string name="fingerprint_re_enroll_dialog_content_singular">To set up Fingerprint Unlock again, your current fingerprint images and model will be deleted.\n\nAfter they\’re deleted, you\’ll need to set up Fingerprint Unlock again to use your fingerprint to unlock your phone or verify it\’s you.</string>
+ <!-- Content for a dialog shown when an error occurs while the user is trying to re-enroll their fingerprint. [CHAR LIMIT=NONE] -->
+ <string name="fingerprint_reenroll_failure_dialog_content">Couldn\u2019t set up fingerprint unlock. Go to Settings to try again.</string>
+ <!-- Notification title shown when the system requires the user to re-enroll their face. [CHAR LIMIT=NONE] -->
+ <string name="face_re_enroll_notification_title">Set up Face Unlock again</string>
+ <!-- Name shown for system notifications related to the face unlock feature. [CHAR LIMIT=NONE] -->
+ <string name="face_re_enroll_notification_name">Face Unlock</string>
+ <!-- Title for a dialog shown when the system requires the user to re-enroll their face. [CHAR LIMIT=NONE] -->
+ <string name="face_re_enroll_dialog_title">Set up Face Unlock</string>
+ <!-- Content for a dialog shown when the system requires the user to re-enroll their face. [CHAR LIMIT=NONE] -->
+ <string name="face_re_enroll_dialog_content">To set up Face Unlock again, your current face model will be deleted.\n\nYou\’ll need to set up this feature again to use your face to unlock your phone.</string>
+ <!-- Content for a dialog shown when an error occurs while the user is trying to re-enroll their face. [CHAR LIMIT=NONE] -->
+ <string name="face_reenroll_failure_dialog_content">Couldn\u2019t set up face unlock. Go to Settings to try again.</string>
<!-- Message shown when the system-provided fingerprint dialog is shown, asking for authentication -->
<string name="fingerprint_dialog_touch_sensor">Touch the fingerprint sensor</string>
<!-- Message shown to inform the user a face cannot be recognized and fingerprint should instead be used.[CHAR LIMIT=50] -->
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 10c08bc3e8b3..9573913e5e2f 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -1832,7 +1832,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
} else if (DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED
.equals(action)) {
mHandler.sendMessage(mHandler.obtainMessage(MSG_DPM_STATE_CHANGED,
- getSendingUserId()));
+ getSendingUserId(), 0));
} else if (ACTION_USER_UNLOCKED.equals(action)) {
mHandler.sendMessage(mHandler.obtainMessage(MSG_USER_UNLOCKED,
getSendingUserId(), 0));
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricNotificationBroadcastReceiver.java b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricNotificationBroadcastReceiver.java
new file mode 100644
index 000000000000..c22a66b210cb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricNotificationBroadcastReceiver.java
@@ -0,0 +1,72 @@
+/*
+ * 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.biometrics;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.hardware.biometrics.BiometricSourceType;
+
+import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.statusbar.phone.SystemUIDialog;
+
+import javax.inject.Inject;
+
+/**
+ * Receives broadcasts sent by {@link BiometricNotificationService} and takes
+ * the appropriate action.
+ */
+@SysUISingleton
+public class BiometricNotificationBroadcastReceiver extends BroadcastReceiver {
+ static final String ACTION_SHOW_FACE_REENROLL_DIALOG = "face_action_show_reenroll_dialog";
+ static final String ACTION_SHOW_FINGERPRINT_REENROLL_DIALOG =
+ "fingerprint_action_show_reenroll_dialog";
+
+ private static final String TAG = "BiometricNotificationBroadcastReceiver";
+
+ private final Context mContext;
+ private final BiometricNotificationDialogFactory mNotificationDialogFactory;
+ @Inject
+ BiometricNotificationBroadcastReceiver(Context context,
+ BiometricNotificationDialogFactory notificationDialogFactory) {
+ mContext = context;
+ mNotificationDialogFactory = notificationDialogFactory;
+ }
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final String action = intent.getAction();
+
+ switch (action) {
+ case ACTION_SHOW_FACE_REENROLL_DIALOG:
+ mNotificationDialogFactory.createReenrollDialog(mContext,
+ new SystemUIDialog(mContext),
+ BiometricSourceType.FACE)
+ .show();
+ break;
+ case ACTION_SHOW_FINGERPRINT_REENROLL_DIALOG:
+ mNotificationDialogFactory.createReenrollDialog(
+ mContext,
+ new SystemUIDialog(mContext),
+ BiometricSourceType.FINGERPRINT)
+ .show();
+ break;
+ default:
+ break;
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricNotificationDialogFactory.java b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricNotificationDialogFactory.java
new file mode 100644
index 000000000000..3e6508c6da70
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricNotificationDialogFactory.java
@@ -0,0 +1,177 @@
+/*
+ * 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.biometrics;
+
+import android.app.Dialog;
+import android.content.Context;
+import android.content.Intent;
+import android.hardware.biometrics.BiometricSourceType;
+import android.hardware.face.Face;
+import android.hardware.face.FaceManager;
+import android.hardware.fingerprint.Fingerprint;
+import android.hardware.fingerprint.FingerprintManager;
+import android.provider.Settings;
+import android.util.Log;
+
+import com.android.systemui.R;
+import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.statusbar.phone.SystemUIDialog;
+
+import javax.inject.Inject;
+
+/**
+ * Manages the creation of dialogs to be shown for biometric re enroll notifications.
+ */
+@SysUISingleton
+public class BiometricNotificationDialogFactory {
+ private static final String TAG = "BiometricNotificationDialogFactory";
+
+ @Inject
+ BiometricNotificationDialogFactory() {}
+
+ Dialog createReenrollDialog(final Context context, final SystemUIDialog sysuiDialog,
+ BiometricSourceType biometricSourceType) {
+ if (biometricSourceType == BiometricSourceType.FACE) {
+ sysuiDialog.setTitle(context.getString(R.string.face_re_enroll_dialog_title));
+ sysuiDialog.setMessage(context.getString(R.string.face_re_enroll_dialog_content));
+ } else if (biometricSourceType == BiometricSourceType.FINGERPRINT) {
+ FingerprintManager fingerprintManager = context.getSystemService(
+ FingerprintManager.class);
+ sysuiDialog.setTitle(context.getString(R.string.fingerprint_re_enroll_dialog_title));
+ if (fingerprintManager.getEnrolledFingerprints().size() == 1) {
+ sysuiDialog.setMessage(context.getString(
+ R.string.fingerprint_re_enroll_dialog_content_singular));
+ } else {
+ sysuiDialog.setMessage(context.getString(
+ R.string.fingerprint_re_enroll_dialog_content));
+ }
+ }
+
+ sysuiDialog.setPositiveButton(R.string.biometric_re_enroll_dialog_confirm,
+ (dialog, which) -> onReenrollDialogConfirm(context, biometricSourceType));
+ sysuiDialog.setNegativeButton(R.string.biometric_re_enroll_dialog_cancel,
+ (dialog, which) -> {});
+ return sysuiDialog;
+ }
+
+ private static Dialog createReenrollFailureDialog(Context context,
+ BiometricSourceType biometricType) {
+ final SystemUIDialog sysuiDialog = new SystemUIDialog(context);
+
+ if (biometricType == BiometricSourceType.FACE) {
+ sysuiDialog.setMessage(context.getString(
+ R.string.face_reenroll_failure_dialog_content));
+ } else if (biometricType == BiometricSourceType.FINGERPRINT) {
+ sysuiDialog.setMessage(context.getString(
+ R.string.fingerprint_reenroll_failure_dialog_content));
+ }
+
+ sysuiDialog.setPositiveButton(R.string.ok, (dialog, which) -> {});
+ return sysuiDialog;
+ }
+
+ private static void onReenrollDialogConfirm(final Context context,
+ BiometricSourceType biometricType) {
+ if (biometricType == BiometricSourceType.FACE) {
+ reenrollFace(context);
+ } else if (biometricType == BiometricSourceType.FINGERPRINT) {
+ reenrollFingerprint(context);
+ }
+ }
+
+ private static void reenrollFingerprint(Context context) {
+ FingerprintManager fingerprintManager = context.getSystemService(FingerprintManager.class);
+ if (fingerprintManager == null) {
+ Log.e(TAG, "Not launching enrollment. Fingerprint manager was null!");
+ createReenrollFailureDialog(context, BiometricSourceType.FINGERPRINT).show();
+ return;
+ }
+
+ if (!fingerprintManager.hasEnrolledTemplates(context.getUserId())) {
+ createReenrollFailureDialog(context, BiometricSourceType.FINGERPRINT).show();
+ return;
+ }
+
+ // Remove all enrolled fingerprint. Launch enrollment if successful.
+ fingerprintManager.removeAll(context.getUserId(),
+ new FingerprintManager.RemovalCallback() {
+ boolean mDidShowFailureDialog;
+
+ @Override
+ public void onRemovalError(Fingerprint fingerprint, int errMsgId,
+ CharSequence errString) {
+ Log.e(TAG, "Not launching enrollment."
+ + "Failed to remove existing face(s).");
+ if (!mDidShowFailureDialog) {
+ mDidShowFailureDialog = true;
+ createReenrollFailureDialog(context, BiometricSourceType.FINGERPRINT)
+ .show();
+ }
+ }
+
+ @Override
+ public void onRemovalSucceeded(Fingerprint fingerprint, int remaining) {
+ if (!mDidShowFailureDialog && remaining == 0) {
+ Intent intent = new Intent(Settings.ACTION_FINGERPRINT_ENROLL);
+ intent.setPackage("com.android.settings");
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ context.startActivity(intent);
+ }
+ }
+ });
+ }
+
+ private static void reenrollFace(Context context) {
+ FaceManager faceManager = context.getSystemService(FaceManager.class);
+ if (faceManager == null) {
+ Log.e(TAG, "Not launching enrollment. Face manager was null!");
+ createReenrollFailureDialog(context, BiometricSourceType.FACE).show();
+ return;
+ }
+
+ if (!faceManager.hasEnrolledTemplates(context.getUserId())) {
+ createReenrollFailureDialog(context, BiometricSourceType.FACE).show();
+ return;
+ }
+
+ // Remove all enrolled faces. Launch enrollment if successful.
+ faceManager.removeAll(context.getUserId(),
+ new FaceManager.RemovalCallback() {
+ boolean mDidShowFailureDialog;
+
+ @Override
+ public void onRemovalError(Face face, int errMsgId, CharSequence errString) {
+ Log.e(TAG, "Not launching enrollment."
+ + "Failed to remove existing face(s).");
+ if (!mDidShowFailureDialog) {
+ mDidShowFailureDialog = true;
+ createReenrollFailureDialog(context, BiometricSourceType.FACE).show();
+ }
+ }
+
+ @Override
+ public void onRemovalSucceeded(Face face, int remaining) {
+ if (!mDidShowFailureDialog && remaining == 0) {
+ Intent intent = new Intent("android.settings.FACE_ENROLL");
+ intent.setPackage("com.android.settings");
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ context.startActivity(intent);
+ }
+ }
+ });
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricNotificationService.java b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricNotificationService.java
new file mode 100644
index 000000000000..4b17be3c45d4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricNotificationService.java
@@ -0,0 +1,206 @@
+/*
+ * 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.biometrics;
+
+import static android.app.PendingIntent.FLAG_IMMUTABLE;
+
+import static com.android.systemui.biometrics.BiometricNotificationBroadcastReceiver.ACTION_SHOW_FACE_REENROLL_DIALOG;
+import static com.android.systemui.biometrics.BiometricNotificationBroadcastReceiver.ACTION_SHOW_FINGERPRINT_REENROLL_DIALOG;
+
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.hardware.biometrics.BiometricFaceConstants;
+import android.hardware.biometrics.BiometricFingerprintConstants;
+import android.hardware.biometrics.BiometricSourceType;
+import android.os.Handler;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.util.Log;
+
+import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.keyguard.KeyguardUpdateMonitorCallback;
+import com.android.systemui.CoreStartable;
+import com.android.systemui.R;
+import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
+
+import javax.inject.Inject;
+
+/**
+ * Handles showing system notifications related to biometric unlock.
+ */
+@SysUISingleton
+public class BiometricNotificationService implements CoreStartable {
+
+ private static final String TAG = "BiometricNotificationService";
+ private static final String CHANNEL_ID = "BiometricHiPriNotificationChannel";
+ private static final String CHANNEL_NAME = " Biometric Unlock";
+ private static final int FACE_NOTIFICATION_ID = 1;
+ private static final int FINGERPRINT_NOTIFICATION_ID = 2;
+ private static final long SHOW_NOTIFICATION_DELAY_MS = 5_000L; // 5 seconds
+ private static final int REENROLL_REQUIRED = 1;
+ private static final int REENROLL_NOT_REQUIRED = 0;
+
+ private final Context mContext;
+ private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+ private final KeyguardStateController mKeyguardStateController;
+ private final Handler mHandler;
+ private final NotificationManager mNotificationManager;
+ private final BiometricNotificationBroadcastReceiver mBroadcastReceiver;
+ private NotificationChannel mNotificationChannel;
+ private boolean mFaceNotificationQueued;
+ private boolean mFingerprintNotificationQueued;
+ private boolean mFingerprintReenrollRequired;
+
+ private final KeyguardStateController.Callback mKeyguardStateControllerCallback =
+ new KeyguardStateController.Callback() {
+ private boolean mIsShowing = true;
+ @Override
+ public void onKeyguardShowingChanged() {
+ if (mKeyguardStateController.isShowing()
+ || mKeyguardStateController.isShowing() == mIsShowing) {
+ mIsShowing = mKeyguardStateController.isShowing();
+ return;
+ }
+ mIsShowing = mKeyguardStateController.isShowing();
+ if (isFaceReenrollRequired(mContext) && !mFaceNotificationQueued) {
+ queueFaceReenrollNotification();
+ }
+ if (mFingerprintReenrollRequired && !mFingerprintNotificationQueued) {
+ mFingerprintReenrollRequired = false;
+ queueFingerprintReenrollNotification();
+ }
+ }
+ };
+
+ private final KeyguardUpdateMonitorCallback mKeyguardUpdateMonitorCallback =
+ new KeyguardUpdateMonitorCallback() {
+ @Override
+ public void onBiometricError(int msgId, String errString,
+ BiometricSourceType biometricSourceType) {
+ if (msgId == BiometricFaceConstants.BIOMETRIC_ERROR_RE_ENROLL
+ && biometricSourceType == BiometricSourceType.FACE) {
+ Settings.Secure.putIntForUser(mContext.getContentResolver(),
+ Settings.Secure.FACE_UNLOCK_RE_ENROLL, REENROLL_REQUIRED,
+ UserHandle.USER_CURRENT);
+ } else if (msgId == BiometricFingerprintConstants.BIOMETRIC_ERROR_RE_ENROLL
+ && biometricSourceType == BiometricSourceType.FINGERPRINT) {
+ mFingerprintReenrollRequired = true;
+ }
+ }
+ };
+
+
+ @Inject
+ public BiometricNotificationService(Context context,
+ KeyguardUpdateMonitor keyguardUpdateMonitor,
+ KeyguardStateController keyguardStateController,
+ Handler handler, NotificationManager notificationManager,
+ BiometricNotificationBroadcastReceiver biometricNotificationBroadcastReceiver) {
+ mContext = context;
+ mKeyguardUpdateMonitor = keyguardUpdateMonitor;
+ mKeyguardStateController = keyguardStateController;
+ mHandler = handler;
+ mNotificationManager = notificationManager;
+ mBroadcastReceiver = biometricNotificationBroadcastReceiver;
+ }
+
+ @Override
+ public void start() {
+ mKeyguardUpdateMonitor.registerCallback(mKeyguardUpdateMonitorCallback);
+ mKeyguardStateController.addCallback(mKeyguardStateControllerCallback);
+ mNotificationChannel = new NotificationChannel(CHANNEL_ID, CHANNEL_NAME,
+ NotificationManager.IMPORTANCE_HIGH);
+ final IntentFilter intentFilter = new IntentFilter();
+ intentFilter.addAction(ACTION_SHOW_FINGERPRINT_REENROLL_DIALOG);
+ intentFilter.addAction(ACTION_SHOW_FACE_REENROLL_DIALOG);
+ mContext.registerReceiver(mBroadcastReceiver, intentFilter,
+ Context.RECEIVER_EXPORTED_UNAUDITED);
+ }
+
+ private void queueFaceReenrollNotification() {
+ mFaceNotificationQueued = true;
+ final String title = mContext.getString(R.string.face_re_enroll_notification_title);
+ final String content = mContext.getString(
+ R.string.biometric_re_enroll_notification_content);
+ final String name = mContext.getString(R.string.face_re_enroll_notification_name);
+ mHandler.postDelayed(
+ () -> showNotification(ACTION_SHOW_FACE_REENROLL_DIALOG, title, content, name,
+ FACE_NOTIFICATION_ID),
+ SHOW_NOTIFICATION_DELAY_MS);
+ }
+
+ private void queueFingerprintReenrollNotification() {
+ mFingerprintNotificationQueued = true;
+ final String title = mContext.getString(R.string.fingerprint_re_enroll_notification_title);
+ final String content = mContext.getString(
+ R.string.biometric_re_enroll_notification_content);
+ final String name = mContext.getString(R.string.fingerprint_re_enroll_notification_name);
+ mHandler.postDelayed(
+ () -> showNotification(ACTION_SHOW_FINGERPRINT_REENROLL_DIALOG, title, content,
+ name, FINGERPRINT_NOTIFICATION_ID),
+ SHOW_NOTIFICATION_DELAY_MS);
+ }
+
+ private void showNotification(String action, CharSequence title, CharSequence content,
+ CharSequence name, int notificationId) {
+ if (notificationId == FACE_NOTIFICATION_ID) {
+ mFaceNotificationQueued = false;
+ } else if (notificationId == FINGERPRINT_NOTIFICATION_ID) {
+ mFingerprintNotificationQueued = false;
+ }
+
+ if (mNotificationManager == null) {
+ Log.e(TAG, "Failed to show notification "
+ + action + ". Notification manager is null!");
+ return;
+ }
+
+ final Intent onClickIntent = new Intent(action);
+ final PendingIntent onClickPendingIntent = PendingIntent.getBroadcastAsUser(mContext,
+ 0 /* requestCode */, onClickIntent, FLAG_IMMUTABLE, UserHandle.CURRENT);
+
+ final Notification notification = new Notification.Builder(mContext, CHANNEL_ID)
+ .setCategory(Notification.CATEGORY_SYSTEM)
+ .setSmallIcon(com.android.internal.R.drawable.ic_lock)
+ .setContentTitle(title)
+ .setContentText(content)
+ .setSubText(name)
+ .setContentIntent(onClickPendingIntent)
+ .setAutoCancel(true)
+ .setLocalOnly(true)
+ .setOnlyAlertOnce(true)
+ .setVisibility(Notification.VISIBILITY_SECRET)
+ .build();
+
+ mNotificationManager.createNotificationChannel(mNotificationChannel);
+ mNotificationManager.notifyAsUser(TAG, notificationId, notification, UserHandle.CURRENT);
+ }
+
+ private boolean isFaceReenrollRequired(Context context) {
+ final int settingValue =
+ Settings.Secure.getIntForUser(context.getContentResolver(),
+ Settings.Secure.FACE_UNLOCK_RE_ENROLL, REENROLL_NOT_REQUIRED,
+ UserHandle.USER_CURRENT);
+ return settingValue == REENROLL_REQUIRED;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
index de84cc22e0e1..5d6479ef4532 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
@@ -25,6 +25,7 @@ import com.android.systemui.SliceBroadcastRelayHandler
import com.android.systemui.accessibility.SystemActions
import com.android.systemui.accessibility.WindowMagnification
import com.android.systemui.biometrics.AuthController
+import com.android.systemui.biometrics.BiometricNotificationService
import com.android.systemui.clipboardoverlay.ClipboardListener
import com.android.systemui.controls.dagger.StartControlsStartableModule
import com.android.systemui.dagger.qualifiers.PerUser
@@ -75,6 +76,14 @@ abstract class SystemUICoreStartableModule {
@ClassKey(AuthController::class)
abstract fun bindAuthController(service: AuthController): CoreStartable
+ /** Inject into BiometricNotificationService */
+ @Binds
+ @IntoMap
+ @ClassKey(BiometricNotificationService::class)
+ abstract fun bindBiometricNotificationService(
+ service: BiometricNotificationService
+ ): CoreStartable
+
/** Inject into ChooserCoreStartable. */
@Binds
@IntoMap
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/data/repository/KeyboardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyboard/data/repository/KeyboardRepository.kt
index b86083abad21..1f1329111ce7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/data/repository/KeyboardRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/data/repository/KeyboardRepository.kt
@@ -106,7 +106,7 @@ constructor(
}
private fun isPhysicalFullKeyboard(deviceId: Int): Boolean {
- val device = inputManager.getInputDevice(deviceId)
+ val device = inputManager.getInputDevice(deviceId) ?: return false
return !device.isVirtual && device.isFullKeyboard
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
index 9ab2e9922531..2d1b7ae610c0 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
@@ -791,7 +791,7 @@ class KeyguardUnlockAnimationController @Inject constructor(
// Translate up from the bottom.
surfaceBehindMatrix.setTranslate(
- surfaceBehindRemoteAnimationTarget.screenSpaceBounds.left.toFloat(),
+ surfaceBehindRemoteAnimationTarget.localBounds.left.toFloat(),
surfaceHeight * SURFACE_BEHIND_START_TRANSLATION_Y * (1f - amount)
)
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt
index 3770b2885b18..96e97565d585 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt
@@ -60,6 +60,7 @@ private const val MIN_DURATION_CANCELLED_ANIMATION = 200L
private const val MIN_DURATION_COMMITTED_ANIMATION = 80L
private const val MIN_DURATION_COMMITTED_AFTER_FLING_ANIMATION = 120L
private const val MIN_DURATION_INACTIVE_BEFORE_FLUNG_ANIMATION = 50L
+private const val MIN_DURATION_INACTIVE_BEFORE_ACTIVE_ANIMATION = 80L
private const val MIN_DURATION_FLING_ANIMATION = 160L
private const val MIN_DURATION_ENTRY_TO_ACTIVE_CONSIDERED_AS_FLING = 100L
@@ -135,7 +136,8 @@ class BackPanelController internal constructor(
private lateinit var backCallback: NavigationEdgeBackPlugin.BackCallback
private var previousXTranslationOnActiveOffset = 0f
private var previousXTranslation = 0f
- private var totalTouchDelta = 0f
+ private var totalTouchDeltaActive = 0f
+ private var totalTouchDeltaInactive = 0f
private var touchDeltaStartX = 0f
private var velocityTracker: VelocityTracker? = null
set(value) {
@@ -154,7 +156,7 @@ class BackPanelController internal constructor(
private var gestureEntryTime = 0L
private var gestureInactiveTime = 0L
- private var gestureActiveTime = 0L
+ private var gesturePastActiveThresholdWhileInactiveTime = 0L
private val elapsedTimeSinceInactive
get() = SystemClock.uptimeMillis() - gestureInactiveTime
@@ -250,7 +252,7 @@ class BackPanelController internal constructor(
private fun updateConfiguration() {
params.update(resources)
mView.updateArrowPaint(params.arrowThickness)
- minFlingDistance = ViewConfiguration.get(context).scaledTouchSlop * 3
+ minFlingDistance = viewConfiguration.scaledTouchSlop * 3
}
private val configurationListener = object : ConfigurationController.ConfigurationListener {
@@ -403,30 +405,46 @@ class BackPanelController internal constructor(
}
GestureState.ACTIVE -> {
val isPastDynamicDeactivationThreshold =
- totalTouchDelta <= params.deactivationSwipeTriggerThreshold
+ totalTouchDeltaActive <= params.deactivationTriggerThreshold
val isMinDurationElapsed =
- elapsedTimeSinceEntry > MIN_DURATION_ACTIVE_BEFORE_INACTIVE_ANIMATION
+ elapsedTimeSinceEntry > MIN_DURATION_ACTIVE_BEFORE_INACTIVE_ANIMATION
if (isMinDurationElapsed && (!isWithinYActivationThreshold ||
- isPastDynamicDeactivationThreshold)
+ isPastDynamicDeactivationThreshold)
) {
updateArrowState(GestureState.INACTIVE)
}
}
GestureState.INACTIVE -> {
val isPastStaticThreshold =
- xTranslation > params.staticTriggerThreshold
- val isPastDynamicReactivationThreshold = totalTouchDelta > 0 &&
- abs(totalTouchDelta) >=
- params.reactivationTriggerThreshold
-
- if (isPastStaticThreshold &&
+ xTranslation > params.staticTriggerThreshold
+ val isPastDynamicReactivationThreshold =
+ totalTouchDeltaInactive >= params.reactivationTriggerThreshold
+ val isPastAllThresholds = isPastStaticThreshold &&
isPastDynamicReactivationThreshold &&
isWithinYActivationThreshold
- ) {
+ val isPastAllThresholdsForFirstTime = isPastAllThresholds &&
+ gesturePastActiveThresholdWhileInactiveTime == 0L
+
+ gesturePastActiveThresholdWhileInactiveTime = when {
+ isPastAllThresholdsForFirstTime -> SystemClock.uptimeMillis()
+ isPastAllThresholds -> gesturePastActiveThresholdWhileInactiveTime
+ else -> 0L
+ }
+
+ val elapsedTimePastAllThresholds =
+ SystemClock.uptimeMillis() - gesturePastActiveThresholdWhileInactiveTime
+
+ val isPastMinimumInactiveToActiveDuration =
+ elapsedTimePastAllThresholds > MIN_DURATION_INACTIVE_BEFORE_ACTIVE_ANIMATION
+
+ if (isPastAllThresholds && isPastMinimumInactiveToActiveDuration) {
+ // The minimum duration adds the 'edge stickiness'
+ // factor before pulling it off edge
updateArrowState(GestureState.ACTIVE)
}
}
+
else -> {}
}
}
@@ -451,19 +469,25 @@ class BackPanelController internal constructor(
previousXTranslation = xTranslation
if (abs(xDelta) > 0) {
- val range =
- params.run { deactivationSwipeTriggerThreshold..reactivationTriggerThreshold }
- val isTouchInContinuousDirection =
- sign(xDelta) == sign(totalTouchDelta) || totalTouchDelta in range
+ val isInSameDirection = sign(xDelta) == sign(totalTouchDeltaActive)
+ val isInDynamicRange = totalTouchDeltaActive in params.dynamicTriggerThresholdRange
+ val isTouchInContinuousDirection = isInSameDirection || isInDynamicRange
if (isTouchInContinuousDirection) {
// Direction has NOT changed, so keep counting the delta
- totalTouchDelta += xDelta
+ totalTouchDeltaActive += xDelta
} else {
// Direction has changed, so reset the delta
- totalTouchDelta = xDelta
+ totalTouchDeltaActive = xDelta
touchDeltaStartX = x
}
+
+ // Add a slop to to prevent small jitters when arrow is at edge in
+ // emitting small values that cause the arrow to poke out slightly
+ val minimumDelta = -viewConfiguration.scaledTouchSlop.toFloat()
+ totalTouchDeltaInactive = totalTouchDeltaInactive
+ .plus(xDelta)
+ .coerceAtLeast(minimumDelta)
}
updateArrowStateOnMove(yTranslation, xTranslation)
@@ -471,7 +495,7 @@ class BackPanelController internal constructor(
val gestureProgress = when (currentState) {
GestureState.ACTIVE -> fullScreenProgress(xTranslation)
GestureState.ENTRY -> staticThresholdProgress(xTranslation)
- GestureState.INACTIVE -> reactivationThresholdProgress(totalTouchDelta)
+ GestureState.INACTIVE -> reactivationThresholdProgress(totalTouchDeltaInactive)
else -> null
}
@@ -529,8 +553,7 @@ class BackPanelController internal constructor(
* the arrow is fully stretched (between 0.0 - 1.0f)
*/
private fun fullScreenProgress(xTranslation: Float): Float {
- val progress = abs((xTranslation - previousXTranslationOnActiveOffset) /
- (fullyStretchedThreshold - previousXTranslationOnActiveOffset))
+ val progress = (xTranslation - previousXTranslationOnActiveOffset) / fullyStretchedThreshold
return MathUtils.saturate(progress)
}
@@ -581,13 +604,15 @@ class BackPanelController internal constructor(
}
private var previousPreThresholdWidthInterpolator = params.entryWidthTowardsEdgeInterpolator
- fun preThresholdWidthStretchAmount(progress: Float): Float {
+ private fun preThresholdWidthStretchAmount(progress: Float): Float {
val interpolator = run {
- val isPastSlop = abs(totalTouchDelta) > ViewConfiguration.get(context).scaledTouchSlop
+ val isPastSlop = totalTouchDeltaInactive > viewConfiguration.scaledTouchSlop
if (isPastSlop) {
- if (totalTouchDelta > 0) {
+ if (totalTouchDeltaInactive > 0) {
params.entryWidthInterpolator
- } else params.entryWidthTowardsEdgeInterpolator
+ } else {
+ params.entryWidthTowardsEdgeInterpolator
+ }
} else {
previousPreThresholdWidthInterpolator
}.also { previousPreThresholdWidthInterpolator = it }
@@ -643,7 +668,7 @@ class BackPanelController internal constructor(
xVelocity.takeIf { mView.isLeftPanel } ?: (xVelocity * -1)
} ?: 0f
val isPastFlingVelocityThreshold =
- flingVelocity > ViewConfiguration.get(context).scaledMinimumFlingVelocity
+ flingVelocity > viewConfiguration.scaledMinimumFlingVelocity
return flingDistance > minFlingDistance && isPastFlingVelocityThreshold
}
@@ -861,7 +886,6 @@ class BackPanelController internal constructor(
}
GestureState.ACTIVE -> {
previousXTranslationOnActiveOffset = previousXTranslation
- gestureActiveTime = SystemClock.uptimeMillis()
updateRestingArrowDimens()
@@ -870,21 +894,24 @@ class BackPanelController internal constructor(
vibratorHelper.vibrate(VIBRATE_ACTIVATED_EFFECT)
}
- val startingVelocity = convertVelocityToSpringStartingVelocity(
- valueOnFastVelocity = 0f,
- valueOnSlowVelocity = if (previousState == GestureState.ENTRY) 2f else 4.5f
- )
+ val minimumPop = 2f
+ val maximumPop = 4.5f
when (previousState) {
- GestureState.ENTRY,
- GestureState.INACTIVE -> {
+ GestureState.ENTRY -> {
+ val startingVelocity = convertVelocityToSpringStartingVelocity(
+ valueOnFastVelocity = minimumPop,
+ valueOnSlowVelocity = maximumPop,
+ fastVelocityBound = 1f,
+ slowVelocityBound = 0.5f,
+ )
mView.popOffEdge(startingVelocity)
}
- GestureState.COMMITTED -> {
- // if previous state was committed then this activation
- // was due to a quick second swipe. Don't pop the arrow this time
+ GestureState.INACTIVE -> {
+ mView.popOffEdge(maximumPop)
}
- else -> { }
+
+ else -> {}
}
}
@@ -896,7 +923,7 @@ class BackPanelController internal constructor(
// but because we can also independently enter this state
// if touch Y >> touch X, we force it to deactivationSwipeTriggerThreshold
// so that gesture progress in this state is consistent regardless of entry
- totalTouchDelta = params.deactivationSwipeTriggerThreshold
+ totalTouchDeltaInactive = params.deactivationTriggerThreshold
val startingVelocity = convertVelocityToSpringStartingVelocity(
valueOnFastVelocity = -1.05f,
@@ -944,10 +971,12 @@ class BackPanelController internal constructor(
private fun convertVelocityToSpringStartingVelocity(
valueOnFastVelocity: Float,
valueOnSlowVelocity: Float,
+ fastVelocityBound: Float = 3f,
+ slowVelocityBound: Float = 0f,
): Float {
val factor = velocityTracker?.run {
computeCurrentVelocity(PX_PER_MS)
- MathUtils.smoothStep(0f, 3f, abs(xVelocity))
+ MathUtils.smoothStep(slowVelocityBound, fastVelocityBound, abs(xVelocity))
} ?: valueOnFastVelocity
return MathUtils.lerp(valueOnFastVelocity, valueOnSlowVelocity, 1 - factor)
@@ -982,7 +1011,7 @@ class BackPanelController internal constructor(
"$currentState",
"startX=$startX",
"startY=$startY",
- "xDelta=${"%.1f".format(totalTouchDelta)}",
+ "xDelta=${"%.1f".format(totalTouchDeltaActive)}",
"xTranslation=${"%.1f".format(previousXTranslation)}",
"pre=${"%.0f".format(staticThresholdProgress(previousXTranslation) * 100)}%",
"post=${"%.0f".format(fullScreenProgress(previousXTranslation) * 100)}%"
@@ -1023,7 +1052,7 @@ class BackPanelController internal constructor(
}
drawVerticalLine(x = params.staticTriggerThreshold, color = Color.BLUE)
- drawVerticalLine(x = params.deactivationSwipeTriggerThreshold, color = Color.BLUE)
+ drawVerticalLine(x = params.deactivationTriggerThreshold, color = Color.BLUE)
drawVerticalLine(x = startX, color = Color.GREEN)
drawVerticalLine(x = previousXTranslation, color = Color.DKGRAY)
}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgePanelParams.kt b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgePanelParams.kt
index c9d8c8495dcc..876c74a9f3e3 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgePanelParams.kt
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgePanelParams.kt
@@ -72,9 +72,11 @@ data class EdgePanelParams(private var resources: Resources) {
private set
var reactivationTriggerThreshold: Float = 0f
private set
- var deactivationSwipeTriggerThreshold: Float = 0f
+ var deactivationTriggerThreshold: Float = 0f
get() = -field
private set
+ lateinit var dynamicTriggerThresholdRange: ClosedRange<Float>
+ private set
var swipeProgressThreshold: Float = 0f
private set
@@ -122,8 +124,10 @@ data class EdgePanelParams(private var resources: Resources) {
staticTriggerThreshold = getDimen(R.dimen.navigation_edge_action_drag_threshold)
reactivationTriggerThreshold =
getDimen(R.dimen.navigation_edge_action_reactivation_drag_threshold)
- deactivationSwipeTriggerThreshold =
+ deactivationTriggerThreshold =
getDimen(R.dimen.navigation_edge_action_deactivation_drag_threshold)
+ dynamicTriggerThresholdRange =
+ reactivationTriggerThreshold..deactivationTriggerThreshold
swipeProgressThreshold = getDimen(R.dimen.navigation_edge_action_progress_threshold)
entryWidthInterpolator = PathInterpolator(.19f, 1.27f, .71f, .86f)
@@ -136,7 +140,6 @@ data class EdgePanelParams(private var resources: Resources) {
edgeCornerInterpolator = PathInterpolator(0f, 1.11f, .85f, .84f)
heightInterpolator = PathInterpolator(1f, .05f, .9f, -0.29f)
- val entryActiveHorizontalTranslationSpring = createSpring(800f, 0.76f)
val activeCommittedArrowLengthSpring = createSpring(1500f, 0.29f)
val activeCommittedArrowHeightSpring = createSpring(1500f, 0.29f)
val flungCommittedEdgeCornerSpring = createSpring(10000f, 1f)
@@ -150,7 +153,7 @@ data class EdgePanelParams(private var resources: Resources) {
horizontalTranslation = getDimen(R.dimen.navigation_edge_entry_margin),
scale = getDimenFloat(R.dimen.navigation_edge_entry_scale),
scalePivotX = getDimen(R.dimen.navigation_edge_pre_threshold_background_width),
- horizontalTranslationSpring = entryActiveHorizontalTranslationSpring,
+ horizontalTranslationSpring = createSpring(500f, 0.76f),
verticalTranslationSpring = createSpring(30000f, 1f),
scaleSpring = createSpring(120f, 0.8f),
arrowDimens = ArrowDimens(
@@ -202,7 +205,7 @@ data class EdgePanelParams(private var resources: Resources) {
activeIndicator = BackIndicatorDimens(
horizontalTranslation = getDimen(R.dimen.navigation_edge_active_margin),
scale = getDimenFloat(R.dimen.navigation_edge_active_scale),
- horizontalTranslationSpring = entryActiveHorizontalTranslationSpring,
+ horizontalTranslationSpring = createSpring(1000f, 0.7f),
scaleSpring = createSpring(450f, 0.39f),
scalePivotX = getDimen(R.dimen.navigation_edge_active_background_width),
arrowDimens = ArrowDimens(
@@ -222,8 +225,8 @@ data class EdgePanelParams(private var resources: Resources) {
farCornerRadius = getDimen(R.dimen.navigation_edge_active_far_corners),
widthSpring = createSpring(850f, 0.75f),
heightSpring = createSpring(10000f, 1f),
- edgeCornerRadiusSpring = createSpring(600f, 0.36f),
- farCornerRadiusSpring = createSpring(2500f, 0.855f),
+ edgeCornerRadiusSpring = createSpring(2600f, 0.855f),
+ farCornerRadiusSpring = createSpring(1200f, 0.30f),
)
)
@@ -250,10 +253,10 @@ data class EdgePanelParams(private var resources: Resources) {
getDimen(R.dimen.navigation_edge_pre_threshold_edge_corners),
farCornerRadius =
getDimen(R.dimen.navigation_edge_pre_threshold_far_corners),
- widthSpring = createSpring(250f, 0.65f),
+ widthSpring = createSpring(400f, 0.65f),
heightSpring = createSpring(1500f, 0.45f),
- farCornerRadiusSpring = createSpring(200f, 1f),
- edgeCornerRadiusSpring = createSpring(150f, 0.5f),
+ farCornerRadiusSpring = createSpring(300f, 1f),
+ edgeCornerRadiusSpring = createSpring(250f, 0.5f),
)
)
diff --git a/packages/SystemUI/src/com/android/systemui/smartspace/dagger/SmartspaceViewComponent.kt b/packages/SystemUI/src/com/android/systemui/smartspace/dagger/SmartspaceViewComponent.kt
index 26149321946d..ba9d13d57a74 100644
--- a/packages/SystemUI/src/com/android/systemui/smartspace/dagger/SmartspaceViewComponent.kt
+++ b/packages/SystemUI/src/com/android/systemui/smartspace/dagger/SmartspaceViewComponent.kt
@@ -75,7 +75,11 @@ interface SmartspaceViewComponent {
)
}
- override fun startPendingIntent(pi: PendingIntent, showOnLockscreen: Boolean) {
+ override fun startPendingIntent(
+ view: View,
+ pi: PendingIntent,
+ showOnLockscreen: Boolean
+ ) {
if (showOnLockscreen) {
pi.send()
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
index 826e28955afc..950dbd9300b3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
@@ -353,7 +353,11 @@ constructor(
}
}
- override fun startPendingIntent(pi: PendingIntent, showOnLockscreen: Boolean) {
+ override fun startPendingIntent(
+ view: View,
+ pi: PendingIntent,
+ showOnLockscreen: Boolean
+ ) {
if (showOnLockscreen) {
pi.send()
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt
index a352f23bfc1c..115e050258c3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt
@@ -16,6 +16,8 @@
package com.android.systemui.statusbar.notification.interruption
+import android.util.Log
+
import com.android.systemui.log.dagger.NotificationInterruptLog
import com.android.systemui.plugins.log.LogBuffer
import com.android.systemui.plugins.log.LogLevel.DEBUG
@@ -23,6 +25,7 @@ import com.android.systemui.plugins.log.LogLevel.INFO
import com.android.systemui.plugins.log.LogLevel.WARNING
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.logKey
+import com.android.systemui.util.Compile
import javax.inject.Inject
class NotificationInterruptLogger @Inject constructor(
@@ -44,11 +47,13 @@ class NotificationInterruptLogger @Inject constructor(
}
fun logNoBubbleNotAllowed(entry: NotificationEntry) {
- buffer.log(TAG, DEBUG, {
- str1 = entry.logKey
- }, {
- "No bubble up: not allowed to bubble: $str1"
- })
+ if (Compile.IS_DEBUG && Log.isLoggable(TAG, Log.DEBUG)) {
+ buffer.log(TAG, DEBUG, {
+ str1 = entry.logKey
+ }, {
+ "No bubble up: not allowed to bubble: $str1"
+ })
+ }
}
fun logNoBubbleNoMetadata(entry: NotificationEntry) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewbinder/NotificationShelfViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewbinder/NotificationShelfViewBinder.kt
index c82318913ced..99c6ddb8cfaa 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewbinder/NotificationShelfViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewbinder/NotificationShelfViewBinder.kt
@@ -33,12 +33,9 @@ import com.android.systemui.statusbar.notification.stack.NotificationStackScroll
import com.android.systemui.statusbar.phone.NotificationIconAreaController
import com.android.systemui.statusbar.phone.NotificationIconContainer
import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent.CentralSurfacesScope
-import com.android.systemui.util.kotlin.getValue
-import dagger.Lazy
import javax.inject.Inject
import kotlinx.coroutines.awaitCancellation
-import kotlinx.coroutines.flow.launchIn
-import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.launch
/**
* Controller class for [NotificationShelf]. This implementation serves as a temporary wrapper
@@ -47,39 +44,12 @@ import kotlinx.coroutines.flow.onEach
* removed, this class can go away and the ViewBinder can be used directly.
*/
@CentralSurfacesScope
-class NotificationShelfViewBinderWrapperControllerImpl
-@Inject
-constructor(
- private val shelf: NotificationShelf,
- private val viewModel: NotificationShelfViewModel,
- featureFlags: FeatureFlags,
- private val falsingManager: FalsingManager,
- hostControllerLazy: Lazy<NotificationStackScrollLayoutController>,
- private val notificationIconAreaController: NotificationIconAreaController,
-) : NotificationShelfController {
-
- private val hostController: NotificationStackScrollLayoutController by hostControllerLazy
+class NotificationShelfViewBinderWrapperControllerImpl @Inject constructor() :
+ NotificationShelfController {
override val view: NotificationShelf
get() = unsupported
- init {
- shelf.apply {
- setRefactorFlagEnabled(featureFlags.isEnabled(Flags.NOTIFICATION_SHELF_REFACTOR))
- useRoundnessSourceTypes(featureFlags.isEnabled(Flags.USE_ROUNDNESS_SOURCETYPES))
- setSensitiveRevealAnimEndabled(featureFlags.isEnabled(Flags.SENSITIVE_REVEAL_ANIM))
- }
- }
-
- fun init() {
- NotificationShelfViewBinder.bind(viewModel, shelf, falsingManager)
- hostController.setShelf(shelf)
- hostController.setOnNotificationRemovedListener { child, _ ->
- view.requestRoundnessResetFor(child)
- }
- notificationIconAreaController.setShelfIcons(shelf.shelfIcons)
- }
-
override val intrinsicHeight: Int
get() = unsupported
@@ -99,21 +69,32 @@ constructor(
get() = NotificationShelfController.throwIllegalFlagStateError(expected = true)
}
-/** Binds a [NotificationShelf] to its backend. */
+/** Binds a [NotificationShelf] to its [view model][NotificationShelfViewModel]. */
object NotificationShelfViewBinder {
fun bind(
- viewModel: NotificationShelfViewModel,
shelf: NotificationShelf,
+ viewModel: NotificationShelfViewModel,
falsingManager: FalsingManager,
+ featureFlags: FeatureFlags,
+ notificationIconAreaController: NotificationIconAreaController,
) {
ActivatableNotificationViewBinder.bind(viewModel, shelf, falsingManager)
- shelf.repeatWhenAttached {
- repeatOnLifecycle(Lifecycle.State.STARTED) {
- viewModel.canModifyColorOfNotifications
- .onEach(shelf::setCanModifyColorOfNotifications)
- .launchIn(this)
- viewModel.isClickable.onEach(shelf::setCanInteract).launchIn(this)
- registerViewListenersWhileAttached(shelf, viewModel)
+ shelf.apply {
+ setRefactorFlagEnabled(true)
+ useRoundnessSourceTypes(featureFlags.isEnabled(Flags.USE_ROUNDNESS_SOURCETYPES))
+ setSensitiveRevealAnimEndabled(featureFlags.isEnabled(Flags.SENSITIVE_REVEAL_ANIM))
+ // TODO(278765923): Replace with eventual NotificationIconContainerViewBinder#bind()
+ notificationIconAreaController.setShelfIcons(shelfIcons)
+ repeatWhenAttached {
+ repeatOnLifecycle(Lifecycle.State.STARTED) {
+ launch {
+ viewModel.canModifyColorOfNotifications.collect(
+ ::setCanModifyColorOfNotifications
+ )
+ }
+ launch { viewModel.isClickable.collect(::setCanInteract) }
+ registerViewListenersWhileAttached(shelf, viewModel)
+ }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 555d5027c7b6..587783d87ba5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -2839,6 +2839,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
* @param listener callback for notification removed
*/
public void setOnNotificationRemovedListener(OnNotificationRemovedListener listener) {
+ NotificationShelfController.assertRefactorFlagDisabled(mAmbientState.getFeatureFlags());
mOnNotificationRemovedListener = listener;
}
@@ -2852,10 +2853,14 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
if (!mChildTransferInProgress) {
onViewRemovedInternal(expandableView, this);
}
- if (mOnNotificationRemovedListener != null) {
- mOnNotificationRemovedListener.onNotificationRemoved(
- expandableView,
- mChildTransferInProgress);
+ if (mAmbientState.getFeatureFlags().isEnabled(Flags.NOTIFICATION_SHELF_REFACTOR)) {
+ mShelf.requestRoundnessResetFor(expandableView);
+ } else {
+ if (mOnNotificationRemovedListener != null) {
+ mOnNotificationRemovedListener.onNotificationRemoved(
+ expandableView,
+ mChildTransferInProgress);
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index 9979cc4fa9d5..b69ce3861342 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -105,11 +105,14 @@ import com.android.systemui.statusbar.notification.row.ExpandableView;
import com.android.systemui.statusbar.notification.row.NotificationGuts;
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
import com.android.systemui.statusbar.notification.row.NotificationSnooze;
+import com.android.systemui.statusbar.notification.stack.ui.viewbinder.NotificationListViewBinder;
+import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationListViewModel;
import com.android.systemui.statusbar.phone.CentralSurfaces;
import com.android.systemui.statusbar.phone.HeadsUpAppearanceController;
import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
import com.android.systemui.statusbar.phone.HeadsUpTouchHelper;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
+import com.android.systemui.statusbar.phone.NotificationIconAreaController;
import com.android.systemui.statusbar.phone.ScrimController;
import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent;
import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -122,16 +125,17 @@ import com.android.systemui.tuner.TunerService;
import com.android.systemui.util.Compile;
import com.android.systemui.util.settings.SecureSettings;
+import kotlin.Unit;
+
import java.util.ArrayList;
import java.util.List;
+import java.util.Optional;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import javax.inject.Inject;
import javax.inject.Named;
-import kotlin.Unit;
-
/**
* Controller for {@link NotificationStackScrollLayout}.
*/
@@ -151,6 +155,8 @@ public class NotificationStackScrollLayoutController {
private final ConfigurationController mConfigurationController;
private final ZenModeController mZenModeController;
private final MetricsLogger mMetricsLogger;
+ private final Optional<NotificationListViewModel> mViewModel;
+
private final DumpManager mDumpManager;
private final FalsingCollector mFalsingCollector;
private final FalsingManager mFalsingManager;
@@ -175,6 +181,8 @@ public class NotificationStackScrollLayoutController {
private final NotificationStackSizeCalculator mNotificationStackSizeCalculator;
private final StackStateLogger mStackStateLogger;
private final NotificationStackScrollLogger mLogger;
+ private final NotificationIconAreaController mNotifIconAreaController;
+
private final GroupExpansionManager mGroupExpansionManager;
private final NotifPipelineFlags mNotifPipelineFlags;
private final SeenNotificationsProvider mSeenNotificationsProvider;
@@ -642,6 +650,7 @@ public class NotificationStackScrollLayoutController {
KeyguardBypassController keyguardBypassController,
ZenModeController zenModeController,
NotificationLockscreenUserManager lockscreenUserManager,
+ Optional<NotificationListViewModel> nsslViewModel,
MetricsLogger metricsLogger,
DumpManager dumpManager,
FalsingCollector falsingCollector,
@@ -665,6 +674,7 @@ public class NotificationStackScrollLayoutController {
StackStateLogger stackLogger,
NotificationStackScrollLogger logger,
NotificationStackSizeCalculator notificationStackSizeCalculator,
+ NotificationIconAreaController notifIconAreaController,
FeatureFlags featureFlags,
NotificationTargetsHelper notificationTargetsHelper,
SecureSettings secureSettings,
@@ -686,6 +696,7 @@ public class NotificationStackScrollLayoutController {
mKeyguardBypassController = keyguardBypassController;
mZenModeController = zenModeController;
mLockscreenUserManager = lockscreenUserManager;
+ mViewModel = nsslViewModel;
mMetricsLogger = metricsLogger;
mDumpManager = dumpManager;
mLockscreenShadeTransitionController = lockscreenShadeTransitionController;
@@ -707,6 +718,7 @@ public class NotificationStackScrollLayoutController {
mVisibilityLocationProviderDelegator = visibilityLocationProviderDelegator;
mSeenNotificationsProvider = seenNotificationsProvider;
mShadeController = shadeController;
+ mNotifIconAreaController = notifIconAreaController;
mFeatureFlags = featureFlags;
mUseRoundnessSourceTypes = featureFlags.isEnabled(Flags.USE_ROUNDNESS_SOURCETYPES);
mNotificationTargetsHelper = notificationTargetsHelper;
@@ -820,6 +832,10 @@ public class NotificationStackScrollLayoutController {
mGroupExpansionManager.registerGroupExpansionChangeListener(
(changedRow, expanded) -> mView.onGroupExpandChanged(changedRow, expanded));
+
+ mViewModel.ifPresent(
+ vm -> NotificationListViewBinder
+ .bind(mView, vm, mFalsingManager, mFeatureFlags, mNotifIconAreaController));
}
private boolean isInVisibleLocation(NotificationEntry entry) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt
new file mode 100644
index 000000000000..45ae4e0afc3a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt
@@ -0,0 +1,51 @@
+/*
+ * 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.statusbar.notification.stack.ui.viewbinder
+
+import android.view.LayoutInflater
+import com.android.systemui.R
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.statusbar.NotificationShelf
+import com.android.systemui.statusbar.notification.shelf.ui.viewbinder.NotificationShelfViewBinder
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout
+import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationListViewModel
+import com.android.systemui.statusbar.phone.NotificationIconAreaController
+
+/** Binds a [NotificationStackScrollLayout] to its [view model][NotificationListViewModel]. */
+object NotificationListViewBinder {
+ @JvmStatic
+ fun bind(
+ view: NotificationStackScrollLayout,
+ viewModel: NotificationListViewModel,
+ falsingManager: FalsingManager,
+ featureFlags: FeatureFlags,
+ iconAreaController: NotificationIconAreaController,
+ ) {
+ val shelf =
+ LayoutInflater.from(view.context)
+ .inflate(R.layout.status_bar_notification_shelf, view, false) as NotificationShelf
+ NotificationShelfViewBinder.bind(
+ shelf,
+ viewModel.shelf,
+ falsingManager,
+ featureFlags,
+ iconAreaController
+ )
+ view.setShelf(shelf)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt
new file mode 100644
index 000000000000..aab1c2b877e8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt
@@ -0,0 +1,45 @@
+/*
+ * 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.statusbar.notification.stack.ui.viewmodel
+
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.statusbar.notification.shelf.ui.viewmodel.NotificationShelfViewModel
+import dagger.Module
+import dagger.Provides
+import java.util.Optional
+import javax.inject.Provider
+
+/** ViewModel for the list of notifications. */
+class NotificationListViewModel(
+ val shelf: NotificationShelfViewModel,
+)
+
+@Module
+object NotificationListViewModelModule {
+ @JvmStatic
+ @Provides
+ fun maybeProvideViewModel(
+ featureFlags: FeatureFlags,
+ shelfViewModel: Provider<NotificationShelfViewModel>,
+ ): Optional<NotificationListViewModel> =
+ if (featureFlags.isEnabled(Flags.NOTIFICATION_SHELF_REFACTOR)) {
+ Optional.of(NotificationListViewModel(shelfViewModel.get()))
+ } else {
+ Optional.empty()
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java
index 95d36b082b57..ef86162aecf5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java
@@ -55,6 +55,7 @@ import com.android.systemui.statusbar.notification.row.dagger.NotificationShelfC
import com.android.systemui.statusbar.notification.row.ui.viewmodel.ActivatableNotificationViewModelModule;
import com.android.systemui.statusbar.notification.shelf.ui.viewbinder.NotificationShelfViewBinderWrapperControllerImpl;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
+import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationListViewModelModule;
import com.android.systemui.statusbar.phone.KeyguardBottomAreaView;
import com.android.systemui.statusbar.phone.NotificationIconAreaController;
import com.android.systemui.statusbar.phone.StatusBarBoundsProvider;
@@ -87,7 +88,10 @@ import javax.inject.Named;
import javax.inject.Provider;
@Module(subcomponents = StatusBarFragmentComponent.class,
- includes = { ActivatableNotificationViewModelModule.class })
+ includes = {
+ ActivatableNotificationViewModelModule.class,
+ NotificationListViewModelModule.class,
+ })
public abstract class StatusBarViewModule {
public static final String SHADE_HEADER = "large_screen_shade_header";
@@ -117,9 +121,7 @@ public abstract class StatusBarViewModule {
NotificationShelfComponent.Builder notificationShelfComponentBuilder,
NotificationShelf notificationShelf) {
if (featureFlags.isEnabled(Flags.NOTIFICATION_SHELF_REFACTOR)) {
- NotificationShelfViewBinderWrapperControllerImpl impl = newImpl.get();
- impl.init();
- return impl;
+ return newImpl.get();
} else {
NotificationShelfComponent component = notificationShelfComponentBuilder
.notificationShelf(notificationShelf)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/model/SignalIconModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/model/SignalIconModel.kt
index 16e176613ec9..be2e41a2a581 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/model/SignalIconModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/model/SignalIconModel.kt
@@ -45,10 +45,6 @@ data class SignalIconModel(
}
companion object {
- /** Creates a [SignalIconModel] representing an empty and invalidated state. */
- fun createEmptyState(numberOfLevels: Int) =
- SignalIconModel(level = 0, numberOfLevels, showExclamationMark = true)
-
private const val COL_LEVEL = "level"
private const val COL_NUM_LEVELS = "numLevels"
private const val COL_SHOW_EXCLAMATION = "showExclamation"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt
index bfd133e6830c..54730ed82564 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt
@@ -17,7 +17,6 @@
package com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel
import com.android.settingslib.AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH
-import com.android.settingslib.AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH_NONE
import com.android.settingslib.graph.SignalDrawable
import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.Icon
@@ -78,13 +77,24 @@ constructor(
scope: CoroutineScope,
) : MobileIconViewModelCommon {
/** Whether or not to show the error state of [SignalDrawable] */
- private val showExclamationMark: Flow<Boolean> =
+ private val showExclamationMark: StateFlow<Boolean> =
combine(
- iconInteractor.isDefaultDataEnabled,
- iconInteractor.isDefaultConnectionFailed,
- ) { isDefaultDataEnabled, isDefaultConnectionFailed ->
- !isDefaultDataEnabled || isDefaultConnectionFailed
- }
+ iconInteractor.isDefaultDataEnabled,
+ iconInteractor.isDefaultConnectionFailed,
+ iconInteractor.isInService,
+ ) { isDefaultDataEnabled, isDefaultConnectionFailed, isInService ->
+ !isDefaultDataEnabled || isDefaultConnectionFailed || !isInService
+ }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), true)
+
+ private val shownLevel: StateFlow<Int> =
+ combine(
+ iconInteractor.level,
+ iconInteractor.isInService,
+ ) { level, isInService ->
+ if (isInService) level else 0
+ }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), 0)
override val isVisible: StateFlow<Boolean> =
if (!constants.hasDataCapabilities) {
@@ -107,18 +117,18 @@ constructor(
.stateIn(scope, SharingStarted.WhileSubscribed(), false)
override val icon: Flow<SignalIconModel> = run {
- val initial = SignalIconModel.createEmptyState(iconInteractor.numberOfLevels.value)
+ val initial =
+ SignalIconModel(
+ level = shownLevel.value,
+ numberOfLevels = iconInteractor.numberOfLevels.value,
+ showExclamationMark = showExclamationMark.value,
+ )
combine(
- iconInteractor.level,
+ shownLevel,
iconInteractor.numberOfLevels,
showExclamationMark,
- iconInteractor.isInService,
- ) { level, numberOfLevels, showExclamationMark, isInService ->
- if (!isInService) {
- SignalIconModel.createEmptyState(numberOfLevels)
- } else {
- SignalIconModel(level, numberOfLevels, showExclamationMark)
- }
+ ) { shownLevel, numberOfLevels, showExclamationMark ->
+ SignalIconModel(shownLevel, numberOfLevels, showExclamationMark)
}
.distinctUntilChanged()
.logDiffsForTable(
@@ -130,19 +140,9 @@ constructor(
}
override val contentDescription: Flow<ContentDescription> = run {
- val initial = ContentDescription.Resource(PHONE_SIGNAL_STRENGTH_NONE)
- combine(
- iconInteractor.level,
- iconInteractor.isInService,
- ) { level, isInService ->
- val resId =
- when {
- isInService -> PHONE_SIGNAL_STRENGTH[level]
- else -> PHONE_SIGNAL_STRENGTH_NONE
- }
- ContentDescription.Resource(resId)
- }
- .distinctUntilChanged()
+ val initial = ContentDescription.Resource(PHONE_SIGNAL_STRENGTH[0])
+ shownLevel
+ .map { ContentDescription.Resource(PHONE_SIGNAL_STRENGTH[it]) }
.stateIn(scope, SharingStarted.WhileSubscribed(), initial)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt
index b37c44a2f8cd..4e52be91f0af 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt
@@ -160,8 +160,10 @@ constructor(
val wifi = currentWifi
if (
- wifi is WifiNetworkModel.Active &&
- wifi.networkId == network.getNetId()
+ (wifi is WifiNetworkModel.Active &&
+ wifi.networkId == network.getNetId()) ||
+ (wifi is WifiNetworkModel.CarrierMerged &&
+ wifi.networkId == network.getNetId())
) {
val newNetworkModel = WifiNetworkModel.Inactive
currentWifi = newNetworkModel
diff --git a/packages/SystemUI/src/com/android/systemui/wallet/controller/WalletContextualLocationsService.kt b/packages/SystemUI/src/com/android/systemui/wallet/controller/WalletContextualLocationsService.kt
new file mode 100644
index 000000000000..1c17fc34a34a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/wallet/controller/WalletContextualLocationsService.kt
@@ -0,0 +1,93 @@
+package com.android.systemui.wallet.controller
+
+import android.content.Intent
+import android.os.IBinder
+import android.util.Log
+import androidx.annotation.VisibleForTesting
+import androidx.lifecycle.LifecycleService
+import androidx.lifecycle.lifecycleScope
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+
+/**
+ * Serves as an intermediary between QuickAccessWalletService and ContextualCardManager (in PCC).
+ * When QuickAccessWalletService has a list of store locations, WalletContextualLocationsService
+ * will send them to ContextualCardManager. When the user enters a store location, this Service
+ * class will be notified, and WalletContextualSuggestionsController will be updated.
+ */
+class WalletContextualLocationsService
+@Inject
+constructor(
+ private val controller: WalletContextualSuggestionsController,
+ private val featureFlags: FeatureFlags,
+) : LifecycleService() {
+ private var listener: IWalletCardsUpdatedListener? = null
+ private var scope: CoroutineScope = this.lifecycleScope
+
+ @VisibleForTesting
+ constructor(
+ controller: WalletContextualSuggestionsController,
+ featureFlags: FeatureFlags,
+ scope: CoroutineScope,
+ ) : this(controller, featureFlags) {
+ this.scope = scope
+ }
+
+ override fun onBind(intent: Intent): IBinder {
+ super.onBind(intent)
+ scope.launch {
+ controller.allWalletCards.collect { cards ->
+ val cardsSize = cards.size
+ Log.i(TAG, "Number of cards registered $cardsSize")
+ listener?.registerNewWalletCards(cards)
+ }
+ }
+ return binder
+ }
+
+ override fun onDestroy() {
+ super.onDestroy()
+ listener = null
+ }
+
+ @VisibleForTesting
+ fun addWalletCardsUpdatedListenerInternal(listener: IWalletCardsUpdatedListener) {
+ if (!featureFlags.isEnabled(Flags.ENABLE_WALLET_CONTEXTUAL_LOYALTY_CARDS)) {
+ return
+ }
+ this.listener = listener // Currently, only one listener at a time is supported
+ // Sends WalletCard objects from QuickAccessWalletService to the listener
+ val cards = controller.allWalletCards.value
+ if (!cards.isEmpty()) {
+ val cardsSize = cards.size
+ Log.i(TAG, "Number of cards registered $cardsSize")
+ listener.registerNewWalletCards(cards)
+ }
+ }
+
+ @VisibleForTesting
+ fun onWalletContextualLocationsStateUpdatedInternal(storeLocations: List<String>) {
+ if (!featureFlags.isEnabled(Flags.ENABLE_WALLET_CONTEXTUAL_LOYALTY_CARDS)) {
+ return
+ }
+ Log.i(TAG, "Entered store $storeLocations")
+ controller.setSuggestionCardIds(storeLocations.toSet())
+ }
+
+ private val binder: IWalletContextualLocationsService.Stub
+ = object : IWalletContextualLocationsService.Stub() {
+ override fun addWalletCardsUpdatedListener(listener: IWalletCardsUpdatedListener) {
+ addWalletCardsUpdatedListenerInternal(listener)
+ }
+ override fun onWalletContextualLocationsStateUpdated(storeLocations: List<String>) {
+ onWalletContextualLocationsStateUpdatedInternal(storeLocations)
+ }
+ }
+
+ companion object {
+ private const val TAG = "WalletContextualLocationsService"
+ }
+} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/wallet/controller/WalletContextualSuggestionsController.kt b/packages/SystemUI/src/com/android/systemui/wallet/controller/WalletContextualSuggestionsController.kt
index 518f5a774d7f..b3ad9b0c6a37 100644
--- a/packages/SystemUI/src/com/android/systemui/wallet/controller/WalletContextualSuggestionsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/wallet/controller/WalletContextualSuggestionsController.kt
@@ -36,6 +36,7 @@ import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.emptyFlow
@@ -57,7 +58,8 @@ constructor(
) {
private val cardsReceivedCallbacks: MutableSet<(List<WalletCard>) -> Unit> = mutableSetOf()
- private val allWalletCards: Flow<List<WalletCard>> =
+ /** All potential cards. */
+ val allWalletCards: StateFlow<List<WalletCard>> =
if (featureFlags.isEnabled(Flags.ENABLE_WALLET_CONTEXTUAL_LOYALTY_CARDS)) {
// TODO(b/237409756) determine if we should debounce this so we don't call the service
// too frequently. Also check if the list actually changed before calling callbacks.
@@ -107,12 +109,13 @@ constructor(
emptyList()
)
} else {
- emptyFlow()
+ MutableStateFlow<List<WalletCard>>(emptyList()).asStateFlow()
}
private val _suggestionCardIds: MutableStateFlow<Set<String>> = MutableStateFlow(emptySet())
private val contextualSuggestionsCardIds: Flow<Set<String>> = _suggestionCardIds.asStateFlow()
+ /** Contextually-relevant cards. */
val contextualSuggestionCards: Flow<List<WalletCard>> =
combine(allWalletCards, contextualSuggestionsCardIds) { cards, ids ->
val ret =
diff --git a/packages/SystemUI/src/com/android/systemui/wallet/dagger/WalletModule.java b/packages/SystemUI/src/com/android/systemui/wallet/dagger/WalletModule.java
index 9429d8991090..efba3e5d9c34 100644
--- a/packages/SystemUI/src/com/android/systemui/wallet/dagger/WalletModule.java
+++ b/packages/SystemUI/src/com/android/systemui/wallet/dagger/WalletModule.java
@@ -35,6 +35,8 @@ import dagger.multibindings.ClassKey;
import dagger.multibindings.IntoMap;
import dagger.multibindings.StringKey;
+import android.app.Service;
+import com.android.systemui.wallet.controller.WalletContextualLocationsService;
/**
* Module for injecting classes in Wallet.
@@ -42,6 +44,12 @@ import dagger.multibindings.StringKey;
@Module
public abstract class WalletModule {
+ @Binds
+ @IntoMap
+ @ClassKey(WalletContextualLocationsService.class)
+ abstract Service bindWalletContextualLocationsService(
+ WalletContextualLocationsService service);
+
/** */
@Binds
@IntoMap
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/FontInterpolatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/FontInterpolatorTest.kt
index 8a5c5b58d058..57a355f4e127 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/FontInterpolatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/FontInterpolatorTest.kt
@@ -106,4 +106,29 @@ class FontInterpolatorTest : SysuiTestCase() {
val reversedFont = interp.lerp(endFont, startFont, 0.5f)
assertThat(resultFont).isSameInstanceAs(reversedFont)
}
+
+ @Test
+ fun testCacheMaxSize() {
+ val interp = FontInterpolator()
+
+ val startFont = Font.Builder(sFont)
+ .setFontVariationSettings("'wght' 100")
+ .build()
+ val endFont = Font.Builder(sFont)
+ .setFontVariationSettings("'wght' 1")
+ .build()
+ val resultFont = interp.lerp(startFont, endFont, 0.5f)
+ for (i in 0..FONT_CACHE_MAX_ENTRIES + 1) {
+ val f1 = Font.Builder(sFont)
+ .setFontVariationSettings("'wght' ${i * 100}")
+ .build()
+ val f2 = Font.Builder(sFont)
+ .setFontVariationSettings("'wght' $i")
+ .build()
+ interp.lerp(f1, f2, 0.5f)
+ }
+
+ val cachedFont = interp.lerp(startFont, endFont, 0.5f)
+ assertThat(resultFont).isNotSameInstanceAs(cachedFont)
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricNotificationDialogFactoryTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricNotificationDialogFactoryTest.java
new file mode 100644
index 000000000000..362d26b040e8
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricNotificationDialogFactoryTest.java
@@ -0,0 +1,174 @@
+/*
+ * 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.biometrics;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.hardware.biometrics.BiometricSourceType;
+import android.hardware.face.FaceManager;
+import android.hardware.fingerprint.FingerprintManager;
+import android.provider.Settings;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.statusbar.phone.SystemUIDialog;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.util.concurrent.ExecutionException;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+public class BiometricNotificationDialogFactoryTest extends SysuiTestCase {
+ @Rule
+ public MockitoRule rule = MockitoJUnit.rule();
+
+ @Mock
+ FingerprintManager mFingerprintManager;
+ @Mock
+ FaceManager mFaceManager;
+ @Mock
+ SystemUIDialog mDialog;
+
+ private Context mContextSpy;
+ private final ArgumentCaptor<DialogInterface.OnClickListener> mOnClickListenerArgumentCaptor =
+ ArgumentCaptor.forClass(DialogInterface.OnClickListener.class);
+ private final ArgumentCaptor<Intent> mIntentArgumentCaptor =
+ ArgumentCaptor.forClass(Intent.class);
+ private BiometricNotificationDialogFactory mDialogFactory;
+
+ @Before
+ public void setUp() throws ExecutionException, InterruptedException {
+ mContext.addMockSystemService(FingerprintManager.class, mFingerprintManager);
+ mContext.addMockSystemService(FaceManager.class, mFaceManager);
+
+ when(mFingerprintManager.hasEnrolledTemplates(anyInt())).thenReturn(true);
+ when(mFaceManager.hasEnrolledTemplates(anyInt())).thenReturn(true);
+
+ mContextSpy = spy(mContext);
+ mDialogFactory = new BiometricNotificationDialogFactory();
+ }
+
+ @Test
+ public void testFingerprintReEnrollDialog_onRemovalSucceeded() {
+ mDialogFactory.createReenrollDialog(mContextSpy, mDialog,
+ BiometricSourceType.FINGERPRINT);
+
+ verify(mDialog).setPositiveButton(anyInt(), mOnClickListenerArgumentCaptor.capture());
+
+ DialogInterface.OnClickListener positiveOnClickListener =
+ mOnClickListenerArgumentCaptor.getValue();
+ positiveOnClickListener.onClick(null, DialogInterface.BUTTON_POSITIVE);
+ ArgumentCaptor<FingerprintManager.RemovalCallback> removalCallbackArgumentCaptor =
+ ArgumentCaptor.forClass(FingerprintManager.RemovalCallback.class);
+
+ verify(mFingerprintManager).removeAll(anyInt(), removalCallbackArgumentCaptor.capture());
+
+ removalCallbackArgumentCaptor.getValue().onRemovalSucceeded(null /* fp */,
+ 0 /* remaining */);
+
+ verify(mContextSpy).startActivity(mIntentArgumentCaptor.capture());
+ assertThat(mIntentArgumentCaptor.getValue().getAction()).isEqualTo(
+ Settings.ACTION_FINGERPRINT_ENROLL);
+ }
+
+ @Test
+ public void testFingerprintReEnrollDialog_onRemovalError() {
+ mDialogFactory.createReenrollDialog(mContextSpy, mDialog,
+ BiometricSourceType.FINGERPRINT);
+
+ verify(mDialog).setPositiveButton(anyInt(), mOnClickListenerArgumentCaptor.capture());
+
+ DialogInterface.OnClickListener positiveOnClickListener =
+ mOnClickListenerArgumentCaptor.getValue();
+ positiveOnClickListener.onClick(null, DialogInterface.BUTTON_POSITIVE);
+ ArgumentCaptor<FingerprintManager.RemovalCallback> removalCallbackArgumentCaptor =
+ ArgumentCaptor.forClass(FingerprintManager.RemovalCallback.class);
+
+ verify(mFingerprintManager).removeAll(anyInt(), removalCallbackArgumentCaptor.capture());
+
+ removalCallbackArgumentCaptor.getValue().onRemovalError(null /* fp */,
+ 0 /* errmsgId */, "Error" /* errString */);
+
+ verify(mContextSpy, never()).startActivity(any());
+ }
+
+ @Test
+ public void testFaceReEnrollDialog_onRemovalSucceeded() {
+ mDialogFactory.createReenrollDialog(mContextSpy, mDialog,
+ BiometricSourceType.FACE);
+
+ verify(mDialog).setPositiveButton(anyInt(), mOnClickListenerArgumentCaptor.capture());
+
+ DialogInterface.OnClickListener positiveOnClickListener =
+ mOnClickListenerArgumentCaptor.getValue();
+ positiveOnClickListener.onClick(null, DialogInterface.BUTTON_POSITIVE);
+ ArgumentCaptor<FaceManager.RemovalCallback> removalCallbackArgumentCaptor =
+ ArgumentCaptor.forClass(FaceManager.RemovalCallback.class);
+
+ verify(mFaceManager).removeAll(anyInt(), removalCallbackArgumentCaptor.capture());
+
+ removalCallbackArgumentCaptor.getValue().onRemovalSucceeded(null /* fp */,
+ 0 /* remaining */);
+
+ verify(mContextSpy).startActivity(mIntentArgumentCaptor.capture());
+ assertThat(mIntentArgumentCaptor.getValue().getAction()).isEqualTo(
+ "android.settings.FACE_ENROLL");
+ }
+
+ @Test
+ public void testFaceReEnrollDialog_onRemovalError() {
+ mDialogFactory.createReenrollDialog(mContextSpy, mDialog,
+ BiometricSourceType.FACE);
+
+ verify(mDialog).setPositiveButton(anyInt(), mOnClickListenerArgumentCaptor.capture());
+
+ DialogInterface.OnClickListener positiveOnClickListener =
+ mOnClickListenerArgumentCaptor.getValue();
+ positiveOnClickListener.onClick(null, DialogInterface.BUTTON_POSITIVE);
+ ArgumentCaptor<FaceManager.RemovalCallback> removalCallbackArgumentCaptor =
+ ArgumentCaptor.forClass(FaceManager.RemovalCallback.class);
+
+ verify(mFaceManager).removeAll(anyInt(), removalCallbackArgumentCaptor.capture());
+
+ removalCallbackArgumentCaptor.getValue().onRemovalError(null /* face */,
+ 0 /* errmsgId */, "Error" /* errString */);
+
+ verify(mContextSpy, never()).startActivity(any());
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricNotificationServiceTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricNotificationServiceTest.java
new file mode 100644
index 000000000000..b8bca3a403e1
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricNotificationServiceTest.java
@@ -0,0 +1,152 @@
+/*
+ * 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.biometrics;
+
+import static com.android.systemui.biometrics.BiometricNotificationBroadcastReceiver.ACTION_SHOW_FACE_REENROLL_DIALOG;
+import static com.android.systemui.biometrics.BiometricNotificationBroadcastReceiver.ACTION_SHOW_FINGERPRINT_REENROLL_DIALOG;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.hardware.biometrics.BiometricFaceConstants;
+import android.hardware.biometrics.BiometricFingerprintConstants;
+import android.hardware.biometrics.BiometricSourceType;
+import android.os.Handler;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.keyguard.KeyguardUpdateMonitorCallback;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+public class BiometricNotificationServiceTest extends SysuiTestCase {
+ @Rule
+ public MockitoRule rule = MockitoJUnit.rule();
+
+ @Mock
+ KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+ @Mock
+ KeyguardStateController mKeyguardStateController;
+ @Mock
+ NotificationManager mNotificationManager;
+
+ private static final String TAG = "BiometricNotificationService";
+ private static final int FACE_NOTIFICATION_ID = 1;
+ private static final int FINGERPRINT_NOTIFICATION_ID = 2;
+ private static final long SHOW_NOTIFICATION_DELAY_MS = 5_000L; // 5 seconds
+
+ private final ArgumentCaptor<Notification> mNotificationArgumentCaptor =
+ ArgumentCaptor.forClass(Notification.class);
+ private TestableLooper mLooper;
+ private KeyguardUpdateMonitorCallback mKeyguardUpdateMonitorCallback;
+ private KeyguardStateController.Callback mKeyguardStateControllerCallback;
+
+ @Before
+ public void setUp() {
+ mLooper = TestableLooper.get(this);
+ Handler handler = new Handler(mLooper.getLooper());
+ BiometricNotificationDialogFactory dialogFactory = new BiometricNotificationDialogFactory();
+ BiometricNotificationBroadcastReceiver broadcastReceiver =
+ new BiometricNotificationBroadcastReceiver(mContext, dialogFactory);
+ BiometricNotificationService biometricNotificationService =
+ new BiometricNotificationService(mContext,
+ mKeyguardUpdateMonitor, mKeyguardStateController, handler,
+ mNotificationManager,
+ broadcastReceiver);
+ biometricNotificationService.start();
+
+ ArgumentCaptor<KeyguardUpdateMonitorCallback> updateMonitorCallbackArgumentCaptor =
+ ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback.class);
+ ArgumentCaptor<KeyguardStateController.Callback> stateControllerCallbackArgumentCaptor =
+ ArgumentCaptor.forClass(KeyguardStateController.Callback.class);
+
+ verify(mKeyguardUpdateMonitor).registerCallback(
+ updateMonitorCallbackArgumentCaptor.capture());
+ verify(mKeyguardStateController).addCallback(
+ stateControllerCallbackArgumentCaptor.capture());
+
+ mKeyguardUpdateMonitorCallback = updateMonitorCallbackArgumentCaptor.getValue();
+ mKeyguardStateControllerCallback = stateControllerCallbackArgumentCaptor.getValue();
+ }
+
+ @Test
+ public void testShowFingerprintReEnrollNotification() {
+ when(mKeyguardStateController.isShowing()).thenReturn(false);
+
+ mKeyguardUpdateMonitorCallback.onBiometricError(
+ BiometricFingerprintConstants.BIOMETRIC_ERROR_RE_ENROLL,
+ "Testing Fingerprint Re-enrollment" /* errString */,
+ BiometricSourceType.FINGERPRINT
+ );
+ mKeyguardStateControllerCallback.onKeyguardShowingChanged();
+
+ mLooper.moveTimeForward(SHOW_NOTIFICATION_DELAY_MS);
+ mLooper.processAllMessages();
+
+ verify(mNotificationManager).notifyAsUser(eq(TAG), eq(FINGERPRINT_NOTIFICATION_ID),
+ mNotificationArgumentCaptor.capture(), any());
+
+ Notification fingerprintNotification = mNotificationArgumentCaptor.getValue();
+
+ assertThat(fingerprintNotification.contentIntent.getIntent().getAction())
+ .isEqualTo(ACTION_SHOW_FINGERPRINT_REENROLL_DIALOG);
+ }
+ @Test
+ public void testShowFaceReEnrollNotification() {
+ when(mKeyguardStateController.isShowing()).thenReturn(false);
+
+ mKeyguardUpdateMonitorCallback.onBiometricError(
+ BiometricFaceConstants.BIOMETRIC_ERROR_RE_ENROLL,
+ "Testing Face Re-enrollment" /* errString */,
+ BiometricSourceType.FACE
+ );
+ mKeyguardStateControllerCallback.onKeyguardShowingChanged();
+
+ mLooper.moveTimeForward(SHOW_NOTIFICATION_DELAY_MS);
+ mLooper.processAllMessages();
+
+ verify(mNotificationManager).notifyAsUser(eq(TAG), eq(FACE_NOTIFICATION_ID),
+ mNotificationArgumentCaptor.capture(), any());
+
+ Notification fingerprintNotification = mNotificationArgumentCaptor.getValue();
+
+ assertThat(fingerprintNotification.contentIntent.getIntent().getAction())
+ .isEqualTo(ACTION_SHOW_FACE_REENROLL_DIALOG);
+ }
+
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyboard/data/repository/KeyboardRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyboard/data/repository/KeyboardRepositoryTest.kt
index f6ff4b214035..6f9dedf9dda8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyboard/data/repository/KeyboardRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyboard/data/repository/KeyboardRepositoryTest.kt
@@ -96,6 +96,16 @@ class KeyboardRepositoryTest : SysuiTestCase() {
}
@Test
+ fun emitsDisconnected_whenDeviceWithIdDoesNotExist() =
+ testScope.runTest {
+ val deviceListener = captureDeviceListener()
+ val isKeyboardConnected by collectLastValue(underTest.keyboardConnected)
+
+ deviceListener.onInputDeviceAdded(NULL_DEVICE_ID)
+ assertThat(isKeyboardConnected).isFalse()
+ }
+
+ @Test
fun emitsDisconnected_whenKeyboardDisconnects() =
testScope.runTest {
val deviceListener = captureDeviceListener()
@@ -172,6 +182,7 @@ class KeyboardRepositoryTest : SysuiTestCase() {
private const val VIRTUAL_FULL_KEYBOARD_ID = 2
private const val PHYSICAL_NOT_FULL_KEYBOARD_ID = 3
private const val ANOTHER_PHYSICAL_FULL_KEYBOARD_ID = 4
+ private const val NULL_DEVICE_ID = 5
private val INPUT_DEVICES_MAP: Map<Int, InputDevice> =
mapOf(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
index 96fff6499be5..23f05233b5e5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
@@ -635,14 +635,14 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() {
}
companion object {
- private val ICON: Icon = mock {
- whenever(this.contentDescription)
- .thenReturn(
+ private const val CONTENT_DESCRIPTION_RESOURCE_ID = 1337
+ private val ICON: Icon =
+ Icon.Resource(
+ res = CONTENT_DESCRIPTION_RESOURCE_ID,
+ contentDescription =
ContentDescription.Resource(
res = CONTENT_DESCRIPTION_RESOURCE_ID,
- )
- )
- }
- private const val CONTENT_DESCRIPTION_RESOURCE_ID = 1337
+ ),
+ )
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/BackPanelControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/BackPanelControllerTest.kt
index 8e32f81b193f..d9428f897f0d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/BackPanelControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/BackPanelControllerTest.kt
@@ -124,7 +124,7 @@ class BackPanelControllerTest : SysuiTestCase() {
continueTouch(START_X + touchSlop.toFloat() + 1)
continueTouch(
START_X + touchSlop + triggerThreshold -
- mBackPanelController.params.deactivationSwipeTriggerThreshold
+ mBackPanelController.params.deactivationTriggerThreshold
)
clearInvocations(backCallback)
Thread.sleep(MIN_DURATION_ACTIVE_BEFORE_INACTIVE_ANIMATION)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
index 0ae012cd5e90..fbbb921f96a0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
@@ -72,9 +72,11 @@ import com.android.systemui.statusbar.notification.collection.render.SectionHead
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController.NotificationPanelEvent;
+import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationListViewModel;
import com.android.systemui.statusbar.phone.CentralSurfaces;
import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
+import com.android.systemui.statusbar.phone.NotificationIconAreaController;
import com.android.systemui.statusbar.phone.ScrimController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
@@ -92,6 +94,8 @@ import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.Optional;
+
/**
* Tests for {@link NotificationStackScrollLayoutController}.
*/
@@ -138,6 +142,7 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase {
@Mock private FeatureFlags mFeatureFlags;
@Mock private NotificationTargetsHelper mNotificationTargetsHelper;
@Mock private SecureSettings mSecureSettings;
+ @Mock private NotificationIconAreaController mIconAreaController;
@Captor
private ArgumentCaptor<StatusBarStateController.StateListener> mStateListenerArgumentCaptor;
@@ -430,6 +435,7 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase {
mKeyguardBypassController,
mZenModeController,
mNotificationLockscreenUserManager,
+ Optional.<NotificationListViewModel>empty(),
mMetricsLogger,
mDumpManager,
new FalsingCollectorFake(),
@@ -453,6 +459,7 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase {
mStackLogger,
mLogger,
mNotificationStackSizeCalculator,
+ mIconAreaController,
mFeatureFlags,
mNotificationTargetsHelper,
mSecureSettings,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt
index d30e0246c2dd..dc68180d962d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt
@@ -994,7 +994,7 @@ class WifiRepositoryImplTest : SysuiTestCase() {
}
@Test
- fun wifiNetwork_currentNetworkLost_flowHasNoNetwork() =
+ fun wifiNetwork_currentActiveNetworkLost_flowHasNoNetwork() =
testScope.runTest {
var latest: WifiNetworkModel? = null
val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this)
@@ -1012,6 +1012,33 @@ class WifiRepositoryImplTest : SysuiTestCase() {
job.cancel()
}
+ /** Possible regression test for b/278618530. */
+ @Test
+ fun wifiNetwork_currentCarrierMergedNetworkLost_flowHasNoNetwork() =
+ testScope.runTest {
+ var latest: WifiNetworkModel? = null
+ val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this)
+
+ val wifiInfo =
+ mock<WifiInfo>().apply {
+ whenever(this.isPrimary).thenReturn(true)
+ whenever(this.isCarrierMerged).thenReturn(true)
+ }
+
+ getNetworkCallback()
+ .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(wifiInfo))
+ assertThat(latest is WifiNetworkModel.CarrierMerged).isTrue()
+ assertThat((latest as WifiNetworkModel.CarrierMerged).networkId).isEqualTo(NETWORK_ID)
+
+ // WHEN we lose our current network
+ getNetworkCallback().onLost(NETWORK)
+
+ // THEN we update to no network
+ assertThat(latest is WifiNetworkModel.Inactive).isTrue()
+
+ job.cancel()
+ }
+
@Test
fun wifiNetwork_unknownNetworkLost_flowHasPreviousNetwork() =
testScope.runTest {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wallet/controller/WalletContextualLocationsServiceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/wallet/controller/WalletContextualLocationsServiceTest.kt
new file mode 100644
index 000000000000..af1d7881195e
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/wallet/controller/WalletContextualLocationsServiceTest.kt
@@ -0,0 +1,128 @@
+package com.android.systemui.wallet.controller
+
+import android.app.PendingIntent
+import android.content.Intent
+import android.graphics.Bitmap
+import android.graphics.drawable.Icon
+import android.os.Looper
+import android.service.quickaccesswallet.WalletCard
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.test.TestCoroutineScope
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.update
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.Mock
+import org.mockito.Mockito.anySet
+import org.mockito.Mockito.doNothing
+import org.mockito.Mockito.doReturn
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@RunWith(JUnit4::class)
+@SmallTest
+@kotlinx.coroutines.ExperimentalCoroutinesApi
+class WalletContextualLocationsServiceTest : SysuiTestCase() {
+ @Mock private lateinit var controller: WalletContextualSuggestionsController
+ private var featureFlags = FakeFeatureFlags()
+ private lateinit var underTest: WalletContextualLocationsService
+ private lateinit var testScope: TestScope
+ private var listenerRegisteredCount: Int = 0
+ private val listener: IWalletCardsUpdatedListener.Stub = object : IWalletCardsUpdatedListener.Stub() {
+ override fun registerNewWalletCards(cards: List<WalletCard?>) {
+ listenerRegisteredCount++
+ }
+ }
+
+ @Before
+ @kotlinx.coroutines.ExperimentalCoroutinesApi
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ doReturn(fakeWalletCards).whenever(controller).allWalletCards
+ doNothing().whenever(controller).setSuggestionCardIds(anySet())
+
+ if (Looper.myLooper() == null) Looper.prepare()
+
+ testScope = TestScope()
+ featureFlags.set(Flags.ENABLE_WALLET_CONTEXTUAL_LOYALTY_CARDS, true)
+ listenerRegisteredCount = 0
+
+ underTest = WalletContextualLocationsService(controller, featureFlags, testScope.backgroundScope)
+ }
+
+ @Test
+ @kotlinx.coroutines.ExperimentalCoroutinesApi
+ fun addListener() = testScope.runTest {
+ underTest.addWalletCardsUpdatedListenerInternal(listener)
+ assertThat(listenerRegisteredCount).isEqualTo(1)
+ }
+
+ @Test
+ @kotlinx.coroutines.ExperimentalCoroutinesApi
+ fun addStoreLocations() = testScope.runTest {
+ underTest.onWalletContextualLocationsStateUpdatedInternal(ArrayList<String>())
+ verify(controller, times(1)).setSuggestionCardIds(anySet())
+ }
+
+ @Test
+ @kotlinx.coroutines.ExperimentalCoroutinesApi
+ fun updateListenerAndLocationsState() = testScope.runTest {
+ // binds to the service and adds a listener
+ val underTestStub = getInterface
+ underTestStub.addWalletCardsUpdatedListener(listener)
+ assertThat(listenerRegisteredCount).isEqualTo(1)
+
+ // sends a list of card IDs to the controller
+ underTestStub.onWalletContextualLocationsStateUpdated(ArrayList<String>())
+ verify(controller, times(1)).setSuggestionCardIds(anySet())
+
+ // adds another listener
+ fakeWalletCards.update{ updatedFakeWalletCards }
+ runCurrent()
+ assertThat(listenerRegisteredCount).isEqualTo(2)
+
+ // sends another list of card IDs to the controller
+ underTestStub.onWalletContextualLocationsStateUpdated(ArrayList<String>())
+ verify(controller, times(2)).setSuggestionCardIds(anySet())
+ }
+
+ private val fakeWalletCards: MutableStateFlow<List<WalletCard>>
+ get() {
+ val intent = Intent(getContext(), WalletContextualLocationsService::class.java)
+ val pi: PendingIntent = PendingIntent.getActivity(getContext(), 0, intent, PendingIntent.FLAG_IMMUTABLE)
+ val icon: Icon = Icon.createWithBitmap(Bitmap.createBitmap(70, 50, Bitmap.Config.ARGB_8888))
+ val walletCards: ArrayList<WalletCard> = ArrayList<WalletCard>()
+ walletCards.add(WalletCard.Builder("card1", icon, "card", pi).build())
+ walletCards.add(WalletCard.Builder("card2", icon, "card", pi).build())
+ return MutableStateFlow<List<WalletCard>>(walletCards)
+ }
+
+ private val updatedFakeWalletCards: List<WalletCard>
+ get() {
+ val intent = Intent(getContext(), WalletContextualLocationsService::class.java)
+ val pi: PendingIntent = PendingIntent.getActivity(getContext(), 0, intent, PendingIntent.FLAG_IMMUTABLE)
+ val icon: Icon = Icon.createWithBitmap(Bitmap.createBitmap(70, 50, Bitmap.Config.ARGB_8888))
+ val walletCards: ArrayList<WalletCard> = ArrayList<WalletCard>()
+ walletCards.add(WalletCard.Builder("card3", icon, "card", pi).build())
+ return walletCards
+ }
+
+ private val getInterface: IWalletContextualLocationsService
+ get() {
+ val intent = Intent()
+ return IWalletContextualLocationsService.Stub.asInterface(underTest.onBind(intent))
+ }
+} \ No newline at end of file
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
index 9a257e54cf41..777c7c8e0da9 100644
--- a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
@@ -1417,20 +1417,29 @@ public class TouchExplorer extends BaseEventStreamTransformation
mSendTouchExplorationEndDelayed.forceSendAndRemove();
}
}
- if (!mState.isTouchInteracting()) {
+ if (!mState.isTouchInteracting() && !mState.isDragging()) {
// It makes no sense to delegate.
- Slog.e(LOG_TAG, "Error: Trying to delegate from "
- + mState.getStateSymbolicName(mState.getState()));
+ Slog.e(
+ LOG_TAG,
+ "Error: Trying to delegate from "
+ + mState.getStateSymbolicName(mState.getState()));
return;
}
- mState.startDelegating();
- MotionEvent prototype = mState.getLastReceivedEvent();
- if (prototype == null) {
+ MotionEvent event = mState.getLastReceivedEvent();
+ MotionEvent rawEvent = mState.getLastReceivedRawEvent();
+ if (event == null || rawEvent == null) {
Slog.d(LOG_TAG, "Unable to start delegating: unable to get last received event.");
return;
}
int policyFlags = mState.getLastReceivedPolicyFlags();
- mDispatcher.sendDownForAllNotInjectedPointers(prototype, policyFlags);
+ if (mState.isDragging()) {
+ // Send an event to the end of the drag gesture.
+ mDispatcher.sendMotionEvent(
+ event, ACTION_UP, rawEvent, ALL_POINTER_ID_BITS, policyFlags);
+ }
+ mState.startDelegating();
+ // Deliver all pointers to the view hierarchy.
+ mDispatcher.sendDownForAllNotInjectedPointers(event, policyFlags);
}
}
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/AlwaysOnMagnificationFeatureFlag.java b/services/accessibility/java/com/android/server/accessibility/magnification/AlwaysOnMagnificationFeatureFlag.java
index 16d2e6b47a54..93531ddea005 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/AlwaysOnMagnificationFeatureFlag.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/AlwaysOnMagnificationFeatureFlag.java
@@ -16,76 +16,31 @@
package com.android.server.accessibility.magnification;
-import android.annotation.NonNull;
import android.provider.DeviceConfig;
-import com.android.internal.annotations.VisibleForTesting;
-
-import java.util.concurrent.Executor;
-
/**
* Encapsulates the feature flags for always on magnification. {@see DeviceConfig}
*
* @hide
*/
-public class AlwaysOnMagnificationFeatureFlag {
+public class AlwaysOnMagnificationFeatureFlag extends MagnificationFeatureFlagBase {
private static final String NAMESPACE = DeviceConfig.NAMESPACE_WINDOW_MANAGER;
private static final String FEATURE_NAME_ENABLE_ALWAYS_ON_MAGNIFICATION =
"AlwaysOnMagnifier__enable_always_on_magnifier";
- private AlwaysOnMagnificationFeatureFlag() {}
-
- /** Returns true if the feature flag is enabled for always on magnification */
- public static boolean isAlwaysOnMagnificationEnabled() {
- return DeviceConfig.getBoolean(
- NAMESPACE,
- FEATURE_NAME_ENABLE_ALWAYS_ON_MAGNIFICATION,
- /* defaultValue= */ false);
+ @Override
+ String getNamespace() {
+ return NAMESPACE;
}
- /** Sets the feature flag. Only used for testing; requires shell permissions. */
- @VisibleForTesting
- public static boolean setAlwaysOnMagnificationEnabled(boolean isEnabled) {
- return DeviceConfig.setProperty(
- NAMESPACE,
- FEATURE_NAME_ENABLE_ALWAYS_ON_MAGNIFICATION,
- Boolean.toString(isEnabled),
- /* makeDefault= */ false);
- }
-
- /**
- * Adds a listener for when the feature flag changes.
- *
- * <p>{@see DeviceConfig#addOnPropertiesChangedListener(
- * String, Executor, DeviceConfig.OnPropertiesChangedListener)}
- */
- @NonNull
- public static DeviceConfig.OnPropertiesChangedListener addOnChangedListener(
- @NonNull Executor executor, @NonNull Runnable listener) {
- DeviceConfig.OnPropertiesChangedListener onChangedListener =
- properties -> {
- if (properties.getKeyset().contains(
- FEATURE_NAME_ENABLE_ALWAYS_ON_MAGNIFICATION)) {
- listener.run();
- }
- };
- DeviceConfig.addOnPropertiesChangedListener(
- NAMESPACE,
- executor,
- onChangedListener);
-
- return onChangedListener;
+ @Override
+ String getFeatureName() {
+ return FEATURE_NAME_ENABLE_ALWAYS_ON_MAGNIFICATION;
}
- /**
- * Remove a listener for when the feature flag changes.
- *
- * <p>{@see DeviceConfig#addOnPropertiesChangedListener(String, Executor,
- * DeviceConfig.OnPropertiesChangedListener)}
- */
- public static void removeOnChangedListener(
- @NonNull DeviceConfig.OnPropertiesChangedListener onChangedListener) {
- DeviceConfig.removeOnPropertiesChangedListener(onChangedListener);
+ @Override
+ boolean getDefaultValue() {
+ return false;
}
}
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
index ed8a35f45176..fbc7b3cbc63b 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
@@ -38,7 +38,6 @@ import android.graphics.Region;
import android.hardware.display.DisplayManagerInternal;
import android.os.Handler;
import android.os.Message;
-import android.provider.DeviceConfig;
import android.text.TextUtils;
import android.util.DisplayMetrics;
import android.util.MathUtils;
@@ -57,6 +56,7 @@ import com.android.internal.R;
import com.android.internal.accessibility.common.MagnificationConstants;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.ConcurrentUtils;
import com.android.internal.util.function.pooled.PooledLambda;
import com.android.server.LocalServices;
import com.android.server.accessibility.AccessibilityManagerService;
@@ -110,6 +110,7 @@ public class FullScreenMagnificationController implements
private boolean mAlwaysOnMagnificationEnabled = false;
private final DisplayManagerInternal mDisplayManagerInternal;
+ private final MagnificationThumbnailFeatureFlag mMagnificationThumbnailFeatureFlag;
@NonNull private final Supplier<MagnificationThumbnail> mThumbnailSupplier;
/**
@@ -177,9 +178,7 @@ public class FullScreenMagnificationController implements
mDisplayId, mMagnificationRegion);
mMagnificationRegion.getBounds(mMagnificationBounds);
- if (mMagnificationThumbnail == null) {
- mMagnificationThumbnail = mThumbnailSupplier.get();
- }
+ createThumbnailIfSupported();
return true;
}
@@ -207,7 +206,7 @@ public class FullScreenMagnificationController implements
mRegistered = false;
unregisterCallbackLocked(mDisplayId, delete);
- destroyThumbNail();
+ destroyThumbnail();
}
mUnregisterPending = false;
}
@@ -345,7 +344,7 @@ public class FullScreenMagnificationController implements
mMagnificationRegion.set(magnified);
mMagnificationRegion.getBounds(mMagnificationBounds);
- refreshThumbNail(getScale(), getCenterX(), getCenterY());
+ refreshThumbnail(getScale(), getCenterX(), getCenterY());
// It's possible that our magnification spec is invalid with the new bounds.
// Adjust the current spec's offsets if necessary.
@@ -405,9 +404,9 @@ public class FullScreenMagnificationController implements
}
if (isActivated()) {
- updateThumbNail(scale, centerX, centerY);
+ updateThumbnail(scale, centerX, centerY);
} else {
- hideThumbNail();
+ hideThumbnail();
}
}
@@ -538,7 +537,7 @@ public class FullScreenMagnificationController implements
mIdOfLastServiceToMagnify = INVALID_SERVICE_ID;
sendSpecToAnimation(spec, animationCallback);
- hideThumbNail();
+ hideThumbnail();
return changed;
}
@@ -596,16 +595,16 @@ public class FullScreenMagnificationController implements
}
@GuardedBy("mLock")
- void updateThumbNail(float scale, float centerX, float centerY) {
+ void updateThumbnail(float scale, float centerX, float centerY) {
if (mMagnificationThumbnail != null) {
- mMagnificationThumbnail.updateThumbNail(scale, centerX, centerY);
+ mMagnificationThumbnail.updateThumbnail(scale, centerX, centerY);
}
}
@GuardedBy("mLock")
- void refreshThumbNail(float scale, float centerX, float centerY) {
+ void refreshThumbnail(float scale, float centerX, float centerY) {
if (mMagnificationThumbnail != null) {
- mMagnificationThumbnail.setThumbNailBounds(
+ mMagnificationThumbnail.setThumbnailBounds(
mMagnificationBounds,
scale,
centerX,
@@ -615,20 +614,38 @@ public class FullScreenMagnificationController implements
}
@GuardedBy("mLock")
- void hideThumbNail() {
+ void hideThumbnail() {
if (mMagnificationThumbnail != null) {
- mMagnificationThumbnail.hideThumbNail();
+ mMagnificationThumbnail.hideThumbnail();
+ }
+ }
+
+ @GuardedBy("mLock")
+ void createThumbnailIfSupported() {
+ if (mMagnificationThumbnail == null) {
+ mMagnificationThumbnail = mThumbnailSupplier.get();
+ // We call refreshThumbnail when the thumbnail is just created to set current
+ // magnification bounds to thumbnail. It to prevent the thumbnail size has not yet
+ // updated properly and thus shows with huge size. (b/276314641)
+ refreshThumbnail(getScale(), getCenterX(), getCenterY());
}
}
@GuardedBy("mLock")
- void destroyThumbNail() {
+ void destroyThumbnail() {
if (mMagnificationThumbnail != null) {
- hideThumbNail();
+ hideThumbnail();
mMagnificationThumbnail = null;
}
}
+ void onThumbnailFeatureFlagChanged() {
+ synchronized (mLock) {
+ destroyThumbnail();
+ createThumbnailIfSupported();
+ }
+ }
+
/**
* Updates the current magnification spec.
*
@@ -768,20 +785,7 @@ public class FullScreenMagnificationController implements
lock,
magnificationInfoChangedCallback,
scaleProvider,
- () -> {
- if (DeviceConfig.getBoolean(
- DeviceConfig.NAMESPACE_ACCESSIBILITY,
- "enable_magnifier_thumbnail",
- /* defaultValue= */ false)) {
- return new MagnificationThumbnail(
- context,
- context.getSystemService(WindowManager.class),
- new Handler(context.getMainLooper())
- );
- }
-
- return null;
- });
+ /* thumbnailSupplier= */ null);
}
/** Constructor for tests */
@@ -791,7 +795,7 @@ public class FullScreenMagnificationController implements
@NonNull Object lock,
@NonNull MagnificationInfoChangedCallback magnificationInfoChangedCallback,
@NonNull MagnificationScaleProvider scaleProvider,
- @NonNull Supplier<MagnificationThumbnail> thumbnailSupplier) {
+ Supplier<MagnificationThumbnail> thumbnailSupplier) {
mControllerCtx = ctx;
mLock = lock;
mMainThreadId = mControllerCtx.getContext().getMainLooper().getThread().getId();
@@ -799,7 +803,41 @@ public class FullScreenMagnificationController implements
addInfoChangedCallback(magnificationInfoChangedCallback);
mScaleProvider = scaleProvider;
mDisplayManagerInternal = LocalServices.getService(DisplayManagerInternal.class);
- mThumbnailSupplier = thumbnailSupplier;
+ mMagnificationThumbnailFeatureFlag = new MagnificationThumbnailFeatureFlag();
+ mMagnificationThumbnailFeatureFlag.addOnChangedListener(
+ ConcurrentUtils.DIRECT_EXECUTOR, this::onMagnificationThumbnailFeatureFlagChanged);
+ if (thumbnailSupplier != null) {
+ mThumbnailSupplier = thumbnailSupplier;
+ } else {
+ mThumbnailSupplier = () -> {
+ if (mMagnificationThumbnailFeatureFlag.isFeatureFlagEnabled()) {
+ return new MagnificationThumbnail(
+ ctx.getContext(),
+ ctx.getContext().getSystemService(WindowManager.class),
+ new Handler(ctx.getContext().getMainLooper())
+ );
+ }
+ return null;
+ };
+ }
+ }
+
+ private void onMagnificationThumbnailFeatureFlagChanged() {
+ synchronized (mLock) {
+ for (int i = 0; i < mDisplays.size(); i++) {
+ onMagnificationThumbnailFeatureFlagChanged(mDisplays.keyAt(i));
+ }
+ }
+ }
+
+ private void onMagnificationThumbnailFeatureFlagChanged(int displayId) {
+ synchronized (mLock) {
+ final DisplayMagnification display = mDisplays.get(displayId);
+ if (display == null) {
+ return;
+ }
+ display.onThumbnailFeatureFlagChanged();
+ }
}
/**
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
index c1c47f53ab7e..7ee72dfa30fd 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
@@ -93,6 +93,7 @@ public class MagnificationController implements WindowMagnificationManager.Callb
private final SparseArray<DisableMagnificationCallback>
mMagnificationEndRunnableSparseArray = new SparseArray();
+ private final AlwaysOnMagnificationFeatureFlag mAlwaysOnMagnificationFeatureFlag;
private final MagnificationScaleProvider mScaleProvider;
private FullScreenMagnificationController mFullScreenMagnificationController;
private WindowMagnificationManager mWindowMagnificationMgr;
@@ -151,7 +152,8 @@ public class MagnificationController implements WindowMagnificationManager.Callb
mSupportWindowMagnification = context.getPackageManager().hasSystemFeature(
FEATURE_WINDOW_MAGNIFICATION);
- AlwaysOnMagnificationFeatureFlag.addOnChangedListener(
+ mAlwaysOnMagnificationFeatureFlag = new AlwaysOnMagnificationFeatureFlag();
+ mAlwaysOnMagnificationFeatureFlag.addOnChangedListener(
ConcurrentUtils.DIRECT_EXECUTOR, mAms::updateAlwaysOnMagnification);
}
@@ -710,7 +712,7 @@ public class MagnificationController implements WindowMagnificationManager.Callb
}
public boolean isAlwaysOnMagnificationFeatureFlagEnabled() {
- return AlwaysOnMagnificationFeatureFlag.isAlwaysOnMagnificationEnabled();
+ return mAlwaysOnMagnificationFeatureFlag.isFeatureFlagEnabled();
}
private DisableMagnificationCallback getDisableMagnificationEndRunnableLocked(
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationFeatureFlagBase.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationFeatureFlagBase.java
new file mode 100644
index 000000000000..2965887d8eb3
--- /dev/null
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationFeatureFlagBase.java
@@ -0,0 +1,115 @@
+/*
+ * 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.accessibility.magnification;
+
+import android.annotation.NonNull;
+import android.os.Binder;
+import android.provider.DeviceConfig;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.concurrent.Executor;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * Abstract base class to encapsulates the feature flags for magnification features.
+ * {@see DeviceConfig}
+ *
+ * @hide
+ */
+abstract class MagnificationFeatureFlagBase {
+
+ abstract String getNamespace();
+ abstract String getFeatureName();
+ abstract boolean getDefaultValue();
+
+ private void clearCallingIdentifyAndTryCatch(Runnable tryBlock, Runnable catchBlock) {
+ try {
+ Binder.withCleanCallingIdentity(() -> tryBlock.run());
+ } catch (Throwable throwable) {
+ catchBlock.run();
+ }
+ }
+
+ /** Returns true iff the feature flag is readable and enabled */
+ public boolean isFeatureFlagEnabled() {
+ AtomicBoolean isEnabled = new AtomicBoolean(getDefaultValue());
+
+ clearCallingIdentifyAndTryCatch(
+ () -> isEnabled.set(DeviceConfig.getBoolean(
+ getNamespace(),
+ getFeatureName(),
+ getDefaultValue())),
+ () -> isEnabled.set(getDefaultValue()));
+
+ return isEnabled.get();
+ }
+
+ /** Sets the feature flag. Only used for testing; requires shell permissions. */
+ @VisibleForTesting
+ public boolean setFeatureFlagEnabled(boolean isEnabled) {
+ AtomicBoolean success = new AtomicBoolean(getDefaultValue());
+
+ clearCallingIdentifyAndTryCatch(
+ () -> success.set(DeviceConfig.setProperty(
+ getNamespace(),
+ getFeatureName(),
+ Boolean.toString(isEnabled),
+ /* makeDefault= */ false)),
+ () -> success.set(getDefaultValue()));
+
+ return success.get();
+ }
+
+ /**
+ * Adds a listener for when the feature flag changes.
+ *
+ * <p>{@see DeviceConfig#addOnPropertiesChangedListener(
+ * String, Executor, DeviceConfig.OnPropertiesChangedListener)}
+ */
+ @NonNull
+ public DeviceConfig.OnPropertiesChangedListener addOnChangedListener(
+ @NonNull Executor executor, @NonNull Runnable listener) {
+ DeviceConfig.OnPropertiesChangedListener onChangedListener =
+ properties -> {
+ if (properties.getKeyset().contains(
+ getFeatureName())) {
+ listener.run();
+ }
+ };
+
+ clearCallingIdentifyAndTryCatch(
+ () -> DeviceConfig.addOnPropertiesChangedListener(
+ getNamespace(),
+ executor,
+ onChangedListener),
+ () -> {});
+
+ return onChangedListener;
+ }
+
+ /**
+ * Remove a listener for when the feature flag changes.
+ *
+ * <p>{@see DeviceConfig#addOnPropertiesChangedListener(String, Executor,
+ * DeviceConfig.OnPropertiesChangedListener)}
+ */
+ public void removeOnChangedListener(
+ @NonNull DeviceConfig.OnPropertiesChangedListener onChangedListener) {
+ DeviceConfig.removeOnPropertiesChangedListener(onChangedListener);
+ }
+}
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationThumbnail.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationThumbnail.java
index 5a783f47ccdc..03fa93d8a3bc 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationThumbnail.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationThumbnail.java
@@ -58,7 +58,9 @@ public class MagnificationThumbnail {
@VisibleForTesting
public final FrameLayout mThumbnailLayout;
- private final View mThumbNailView;
+ private final View mThumbnailView;
+ private int mThumbnailWidth;
+ private int mThumbnailHeight;
private final WindowManager.LayoutParams mBackgroundParams;
private boolean mVisible = false;
@@ -66,7 +68,7 @@ public class MagnificationThumbnail {
private static final float ASPECT_RATIO = 14f;
private static final float BG_ASPECT_RATIO = ASPECT_RATIO / 2f;
- private ObjectAnimator mThumbNailAnimator;
+ private ObjectAnimator mThumbnailAnimator;
private boolean mIsFadingIn;
/**
@@ -79,9 +81,11 @@ public class MagnificationThumbnail {
mWindowBounds = mWindowManager.getCurrentWindowMetrics().getBounds();
mThumbnailLayout = (FrameLayout) LayoutInflater.from(mContext)
.inflate(R.layout.thumbnail_background_view, /* root: */ null);
- mThumbNailView =
+ mThumbnailView =
mThumbnailLayout.findViewById(R.id.accessibility_magnification_thumbnail_view);
mBackgroundParams = createLayoutParams();
+ mThumbnailWidth = 0;
+ mThumbnailHeight = 0;
}
/**
@@ -90,35 +94,35 @@ public class MagnificationThumbnail {
* @param currentBounds the current magnification bounds
*/
@AnyThread
- public void setThumbNailBounds(Rect currentBounds, float scale, float centerX, float centerY) {
+ public void setThumbnailBounds(Rect currentBounds, float scale, float centerX, float centerY) {
if (DEBUG) {
- Log.d(LOG_TAG, "setThumbNailBounds " + currentBounds);
+ Log.d(LOG_TAG, "setThumbnailBounds " + currentBounds);
}
mHandler.post(() -> {
mWindowBounds = currentBounds;
setBackgroundBounds();
if (mVisible) {
- updateThumbNailMainThread(scale, centerX, centerY);
+ updateThumbnailMainThread(scale, centerX, centerY);
}
});
}
private void setBackgroundBounds() {
Point magnificationBoundary = getMagnificationThumbnailPadding(mContext);
- final int thumbNailWidth = (int) (mWindowBounds.width() / BG_ASPECT_RATIO);
- final int thumbNailHeight = (int) (mWindowBounds.height() / BG_ASPECT_RATIO);
+ mThumbnailWidth = (int) (mWindowBounds.width() / BG_ASPECT_RATIO);
+ mThumbnailHeight = (int) (mWindowBounds.height() / BG_ASPECT_RATIO);
int initX = magnificationBoundary.x;
int initY = magnificationBoundary.y;
- mBackgroundParams.width = thumbNailWidth;
- mBackgroundParams.height = thumbNailHeight;
+ mBackgroundParams.width = mThumbnailWidth;
+ mBackgroundParams.height = mThumbnailHeight;
mBackgroundParams.x = initX;
mBackgroundParams.y = initY;
}
@MainThread
- private void showThumbNail() {
+ private void showThumbnail() {
if (DEBUG) {
- Log.d(LOG_TAG, "showThumbNail " + mVisible);
+ Log.d(LOG_TAG, "showThumbnail " + mVisible);
}
animateThumbnail(true);
}
@@ -127,14 +131,14 @@ public class MagnificationThumbnail {
* Hides thumbnail and removes the view from the window when finished animating.
*/
@AnyThread
- public void hideThumbNail() {
- mHandler.post(this::hideThumbNailMainThread);
+ public void hideThumbnail() {
+ mHandler.post(this::hideThumbnailMainThread);
}
@MainThread
- private void hideThumbNailMainThread() {
+ private void hideThumbnailMainThread() {
if (DEBUG) {
- Log.d(LOG_TAG, "hideThumbNail " + mVisible);
+ Log.d(LOG_TAG, "hideThumbnail " + mVisible);
}
if (mVisible) {
animateThumbnail(false);
@@ -155,14 +159,14 @@ public class MagnificationThumbnail {
+ " fadeIn: " + fadeIn
+ " mVisible: " + mVisible
+ " isFadingIn: " + mIsFadingIn
- + " isRunning: " + mThumbNailAnimator
+ + " isRunning: " + mThumbnailAnimator
);
}
// Reset countdown to hide automatically
- mHandler.removeCallbacks(this::hideThumbNailMainThread);
+ mHandler.removeCallbacks(this::hideThumbnailMainThread);
if (fadeIn) {
- mHandler.postDelayed(this::hideThumbNailMainThread, LINGER_DURATION_MS);
+ mHandler.postDelayed(this::hideThumbnailMainThread, LINGER_DURATION_MS);
}
if (fadeIn == mIsFadingIn) {
@@ -175,18 +179,18 @@ public class MagnificationThumbnail {
mVisible = true;
}
- if (mThumbNailAnimator != null) {
- mThumbNailAnimator.cancel();
+ if (mThumbnailAnimator != null) {
+ mThumbnailAnimator.cancel();
}
- mThumbNailAnimator = ObjectAnimator.ofFloat(
+ mThumbnailAnimator = ObjectAnimator.ofFloat(
mThumbnailLayout,
"alpha",
fadeIn ? 1f : 0f
);
- mThumbNailAnimator.setDuration(
+ mThumbnailAnimator.setDuration(
fadeIn ? FADE_IN_ANIMATION_DURATION_MS : FADE_OUT_ANIMATION_DURATION_MS
);
- mThumbNailAnimator.addListener(new Animator.AnimatorListener() {
+ mThumbnailAnimator.addListener(new Animator.AnimatorListener() {
private boolean mIsCancelled;
@Override
@@ -231,7 +235,7 @@ public class MagnificationThumbnail {
}
});
- mThumbNailAnimator.start();
+ mThumbnailAnimator.start();
}
/**
@@ -246,38 +250,48 @@ public class MagnificationThumbnail {
* of the viewport, or {@link Float#NaN} to leave unchanged
*/
@AnyThread
- public void updateThumbNail(float scale, float centerX, float centerY) {
- mHandler.post(() -> updateThumbNailMainThread(scale, centerX, centerY));
+ public void updateThumbnail(float scale, float centerX, float centerY) {
+ mHandler.post(() -> updateThumbnailMainThread(scale, centerX, centerY));
}
@MainThread
- private void updateThumbNailMainThread(float scale, float centerX, float centerY) {
+ private void updateThumbnailMainThread(float scale, float centerX, float centerY) {
// Restart the fadeout countdown (or show if it's hidden)
- showThumbNail();
+ showThumbnail();
- var scaleDown = Float.isNaN(scale) ? mThumbNailView.getScaleX() : 1f / scale;
+ var scaleDown = Float.isNaN(scale) ? mThumbnailView.getScaleX() : 1f / scale;
if (!Float.isNaN(scale)) {
- mThumbNailView.setScaleX(scaleDown);
- mThumbNailView.setScaleY(scaleDown);
+ mThumbnailView.setScaleX(scaleDown);
+ mThumbnailView.setScaleY(scaleDown);
+ }
+ float thumbnailWidth;
+ float thumbnailHeight;
+ if (mThumbnailView.getWidth() == 0 || mThumbnailView.getHeight() == 0) {
+ // if the thumbnail view size is not updated correctly, we just use the cached values.
+ thumbnailWidth = mThumbnailWidth;
+ thumbnailHeight = mThumbnailHeight;
+ } else {
+ thumbnailWidth = mThumbnailView.getWidth();
+ thumbnailHeight = mThumbnailView.getHeight();
}
if (!Float.isNaN(centerX)) {
- var padding = mThumbNailView.getPaddingTop();
+ var padding = mThumbnailView.getPaddingTop();
var ratio = 1f / BG_ASPECT_RATIO;
- var centerXScaled = centerX * ratio - (mThumbNailView.getWidth() / 2f + padding);
- var centerYScaled = centerY * ratio - (mThumbNailView.getHeight() / 2f + padding);
+ var centerXScaled = centerX * ratio - (thumbnailWidth / 2f + padding);
+ var centerYScaled = centerY * ratio - (thumbnailHeight / 2f + padding);
if (DEBUG) {
Log.d(
LOG_TAG,
- "updateThumbNail centerXScaled : " + centerXScaled
+ "updateThumbnail centerXScaled : " + centerXScaled
+ " centerYScaled : " + centerYScaled
- + " getTranslationX : " + mThumbNailView.getTranslationX()
+ + " getTranslationX : " + mThumbnailView.getTranslationX()
+ " ratio : " + ratio
);
}
- mThumbNailView.setTranslationX(centerXScaled);
- mThumbNailView.setTranslationY(centerYScaled);
+ mThumbnailView.setTranslationX(centerXScaled);
+ mThumbnailView.setTranslationY(centerYScaled);
}
}
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationThumbnailFeatureFlag.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationThumbnailFeatureFlag.java
new file mode 100644
index 000000000000..519f31b86f78
--- /dev/null
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationThumbnailFeatureFlag.java
@@ -0,0 +1,46 @@
+/*
+ * 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.accessibility.magnification;
+
+import android.provider.DeviceConfig;
+
+/**
+ * Encapsulates the feature flags for magnification thumbnail. {@see DeviceConfig}
+ *
+ * @hide
+ */
+public class MagnificationThumbnailFeatureFlag extends MagnificationFeatureFlagBase {
+
+ private static final String NAMESPACE = DeviceConfig.NAMESPACE_ACCESSIBILITY;
+ private static final String FEATURE_NAME_ENABLE_MAGNIFIER_THUMBNAIL =
+ "enable_magnifier_thumbnail";
+
+ @Override
+ String getNamespace() {
+ return NAMESPACE;
+ }
+
+ @Override
+ String getFeatureName() {
+ return FEATURE_NAME_ENABLE_MAGNIFIER_THUMBNAIL;
+ }
+
+ @Override
+ boolean getDefaultValue() {
+ return false;
+ }
+}
diff --git a/services/companion/java/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncConnectionService.java b/services/companion/java/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncConnectionService.java
new file mode 100644
index 000000000000..19d8b8783d81
--- /dev/null
+++ b/services/companion/java/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncConnectionService.java
@@ -0,0 +1,77 @@
+/*
+ * 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.companion.datatransfer.contextsync;
+
+import android.content.ComponentName;
+import android.telecom.ConnectionService;
+import android.telecom.PhoneAccount;
+import android.telecom.PhoneAccountHandle;
+import android.telecom.TelecomManager;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.UUID;
+
+/** Service for Telecom to bind to when call metadata is synced between devices. */
+public class CallMetadataSyncConnectionService extends ConnectionService {
+
+ private TelecomManager mTelecomManager;
+ private final Map<String, PhoneAccountHandle> mPhoneAccountHandles = new HashMap<>();
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ mTelecomManager = getSystemService(TelecomManager.class);
+ }
+
+ /**
+ * Registers a {@link android.telecom.PhoneAccount} for a given call-capable app on the synced
+ * device.
+ */
+ public void registerPhoneAccount(String packageName, String humanReadableAppName) {
+ final PhoneAccount phoneAccount = createPhoneAccount(packageName, humanReadableAppName);
+ if (phoneAccount != null) {
+ mTelecomManager.registerPhoneAccount(phoneAccount);
+ mTelecomManager.enablePhoneAccount(mPhoneAccountHandles.get(packageName), true);
+ }
+ }
+
+ /**
+ * Unregisters a {@link android.telecom.PhoneAccount} for a given call-capable app on the synced
+ * device.
+ */
+ public void unregisterPhoneAccount(String packageName) {
+ mTelecomManager.unregisterPhoneAccount(mPhoneAccountHandles.remove(packageName));
+ }
+
+ @VisibleForTesting
+ PhoneAccount createPhoneAccount(String packageName, String humanReadableAppName) {
+ if (mPhoneAccountHandles.containsKey(packageName)) {
+ // Already exists!
+ return null;
+ }
+ final PhoneAccountHandle handle = new PhoneAccountHandle(
+ new ComponentName(this, CallMetadataSyncConnectionService.class),
+ UUID.randomUUID().toString());
+ mPhoneAccountHandles.put(packageName, handle);
+ return new PhoneAccount.Builder(handle, humanReadableAppName)
+ .setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER
+ | PhoneAccount.CAPABILITY_SELF_MANAGED).build();
+ }
+}
diff --git a/services/companion/java/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncData.java b/services/companion/java/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncData.java
new file mode 100644
index 000000000000..1e4bb9a504ba
--- /dev/null
+++ b/services/companion/java/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncData.java
@@ -0,0 +1,188 @@
+/*
+ * 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.companion.datatransfer.contextsync;
+
+import android.annotation.NonNull;
+import android.companion.ContextSyncMessage;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+
+/** A read-only snapshot of an {@link ContextSyncMessage}. */
+class CallMetadataSyncData {
+
+ final Map<Long, CallMetadataSyncData.Call> mCalls = new HashMap<>();
+ final List<CallMetadataSyncData.Call> mRequests = new ArrayList<>();
+
+ public void addCall(CallMetadataSyncData.Call call) {
+ mCalls.put(call.getId(), call);
+ }
+
+ public boolean hasCall(long id) {
+ return mCalls.containsKey(id);
+ }
+
+ public Collection<CallMetadataSyncData.Call> getCalls() {
+ return mCalls.values();
+ }
+
+ public void addRequest(CallMetadataSyncData.Call call) {
+ mRequests.add(call);
+ }
+
+ public List<CallMetadataSyncData.Call> getRequests() {
+ return mRequests;
+ }
+
+ public static class Call implements Parcelable {
+ private long mId;
+ private String mCallerId;
+ private byte[] mAppIcon;
+ private String mAppName;
+ private String mAppIdentifier;
+ private int mStatus;
+ private final Set<Integer> mControls = new HashSet<>();
+
+ public static Call fromParcel(Parcel parcel) {
+ final Call call = new Call();
+ call.setId(parcel.readLong());
+ call.setCallerId(parcel.readString());
+ call.setAppIcon(parcel.readBlob());
+ call.setAppName(parcel.readString());
+ call.setAppIdentifier(parcel.readString());
+ call.setStatus(parcel.readInt());
+ final int numberOfControls = parcel.readInt();
+ for (int i = 0; i < numberOfControls; i++) {
+ call.addControl(parcel.readInt());
+ }
+ return call;
+ }
+
+ @Override
+ public void writeToParcel(Parcel parcel, int parcelableFlags) {
+ parcel.writeLong(mId);
+ parcel.writeString(mCallerId);
+ parcel.writeBlob(mAppIcon);
+ parcel.writeString(mAppName);
+ parcel.writeString(mAppIdentifier);
+ parcel.writeInt(mStatus);
+ parcel.writeInt(mControls.size());
+ for (int control : mControls) {
+ parcel.writeInt(control);
+ }
+ }
+
+ void setId(long id) {
+ mId = id;
+ }
+
+ void setCallerId(String callerId) {
+ mCallerId = callerId;
+ }
+
+ void setAppIcon(byte[] appIcon) {
+ mAppIcon = appIcon;
+ }
+
+ void setAppName(String appName) {
+ mAppName = appName;
+ }
+
+ void setAppIdentifier(String appIdentifier) {
+ mAppIdentifier = appIdentifier;
+ }
+
+ void setStatus(int status) {
+ mStatus = status;
+ }
+
+ void addControl(int control) {
+ mControls.add(control);
+ }
+
+ long getId() {
+ return mId;
+ }
+
+ String getCallerId() {
+ return mCallerId;
+ }
+
+ byte[] getAppIcon() {
+ return mAppIcon;
+ }
+
+ String getAppName() {
+ return mAppName;
+ }
+
+ String getAppIdentifier() {
+ return mAppIdentifier;
+ }
+
+ int getStatus() {
+ return mStatus;
+ }
+
+ Set<Integer> getControls() {
+ return mControls;
+ }
+
+ boolean hasControl(int control) {
+ return mControls.contains(control);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other instanceof CallMetadataSyncData.Call) {
+ return ((Call) other).getId() == getId();
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hashCode(mId);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @NonNull public static final Parcelable.Creator<Call> CREATOR = new Parcelable.Creator<>() {
+
+ @Override
+ public Call createFromParcel(Parcel source) {
+ return Call.fromParcel(source);
+ }
+
+ @Override
+ public Call[] newArray(int size) {
+ return new Call[size];
+ }
+ };
+ }
+}
diff --git a/services/companion/java/com/android/server/companion/datatransfer/contextsync/CrossDeviceCall.java b/services/companion/java/com/android/server/companion/datatransfer/contextsync/CrossDeviceCall.java
index 077fd2a1157d..dd0bbf2790ee 100644
--- a/services/companion/java/com/android/server/companion/datatransfer/contextsync/CrossDeviceCall.java
+++ b/services/companion/java/com/android/server/companion/datatransfer/contextsync/CrossDeviceCall.java
@@ -39,6 +39,8 @@ public class CrossDeviceCall {
private static final String TAG = "CrossDeviceCall";
+ public static final String EXTRA_CALL_ID =
+ "com.android.companion.datatransfer.contextsync.extra.CALL_ID";
private static final int APP_ICON_BITMAP_DIMENSION = 256;
private static final AtomicLong sNextId = new AtomicLong(1);
@@ -47,6 +49,7 @@ public class CrossDeviceCall {
private final Call mCall;
@VisibleForTesting boolean mIsEnterprise;
@VisibleForTesting boolean mIsOtt;
+ private final String mCallingAppPackageName;
private String mCallingAppName;
private byte[] mCallingAppIcon;
private String mCallerDisplayName;
@@ -59,7 +62,7 @@ public class CrossDeviceCall {
CallAudioState callAudioState) {
mId = sNextId.getAndIncrement();
mCall = call;
- final String callingAppPackageName = call != null
+ mCallingAppPackageName = call != null
? call.getDetails().getAccountHandle().getComponentName().getPackageName() : null;
mIsOtt = call != null
&& (call.getDetails().getCallCapabilities() & Call.Details.PROPERTY_SELF_MANAGED)
@@ -69,13 +72,13 @@ public class CrossDeviceCall {
== Call.Details.PROPERTY_ENTERPRISE_CALL;
try {
final ApplicationInfo applicationInfo = packageManager
- .getApplicationInfo(callingAppPackageName,
+ .getApplicationInfo(mCallingAppPackageName,
PackageManager.ApplicationInfoFlags.of(0));
mCallingAppName = packageManager.getApplicationLabel(applicationInfo).toString();
mCallingAppIcon = renderDrawableToByteArray(
packageManager.getApplicationIcon(applicationInfo));
} catch (PackageManager.NameNotFoundException e) {
- Slog.e(TAG, "Could not get application info for package " + callingAppPackageName, e);
+ Slog.e(TAG, "Could not get application info for package " + mCallingAppPackageName, e);
}
mIsMuted = callAudioState != null && callAudioState.isMuted();
if (call != null) {
@@ -170,7 +173,8 @@ public class CrossDeviceCall {
}
}
- private int convertStateToStatus(int callState) {
+ /** Converts a Telecom call state to a Context Sync status. */
+ public static int convertStateToStatus(int callState) {
switch (callState) {
case Call.STATE_HOLDING:
return android.companion.Telecom.Call.ON_HOLD;
@@ -178,20 +182,30 @@ public class CrossDeviceCall {
return android.companion.Telecom.Call.ONGOING;
case Call.STATE_RINGING:
return android.companion.Telecom.Call.RINGING;
- case Call.STATE_NEW:
- case Call.STATE_DIALING:
- case Call.STATE_DISCONNECTED:
- case Call.STATE_SELECT_PHONE_ACCOUNT:
- case Call.STATE_CONNECTING:
- case Call.STATE_DISCONNECTING:
- case Call.STATE_PULLING_CALL:
- case Call.STATE_AUDIO_PROCESSING:
- case Call.STATE_SIMULATED_RINGING:
default:
return android.companion.Telecom.Call.UNKNOWN_STATUS;
}
}
+ /**
+ * Converts a Context Sync status to a Telecom call state. Note that this is lossy for
+ * and RINGING_SILENCED, as Telecom does not distinguish between RINGING and RINGING_SILENCED.
+ */
+ public static int convertStatusToState(int status) {
+ switch (status) {
+ case android.companion.Telecom.Call.ON_HOLD:
+ return Call.STATE_HOLDING;
+ case android.companion.Telecom.Call.ONGOING:
+ return Call.STATE_ACTIVE;
+ case android.companion.Telecom.Call.RINGING:
+ case android.companion.Telecom.Call.RINGING_SILENCED:
+ return Call.STATE_RINGING;
+ case android.companion.Telecom.Call.UNKNOWN_STATUS:
+ default:
+ return Call.STATE_NEW;
+ }
+ }
+
public long getId() {
return mId;
}
@@ -208,6 +222,10 @@ public class CrossDeviceCall {
return mCallingAppIcon;
}
+ public String getCallingAppPackageName() {
+ return mCallingAppPackageName;
+ }
+
/**
* Get a human-readable "caller id" to display as the origin of the call.
*
diff --git a/services/companion/java/com/android/server/companion/virtual/InputController.java b/services/companion/java/com/android/server/companion/virtual/InputController.java
index 1a0588e999e2..307f7bfc1fd5 100644
--- a/services/companion/java/com/android/server/companion/virtual/InputController.java
+++ b/services/companion/java/com/android/server/companion/virtual/InputController.java
@@ -367,7 +367,7 @@ class InputController {
"Could not send key event to input device for given token");
}
return mNativeWrapper.writeDpadKeyEvent(inputDeviceDescriptor.getNativePointer(),
- event.getKeyCode(), event.getAction());
+ event.getKeyCode(), event.getAction(), event.getEventTimeNanos());
}
}
@@ -380,7 +380,7 @@ class InputController {
"Could not send key event to input device for given token");
}
return mNativeWrapper.writeKeyEvent(inputDeviceDescriptor.getNativePointer(),
- event.getKeyCode(), event.getAction());
+ event.getKeyCode(), event.getAction(), event.getEventTimeNanos());
}
}
@@ -398,7 +398,7 @@ class InputController {
"Display id associated with this mouse is not currently targetable");
}
return mNativeWrapper.writeButtonEvent(inputDeviceDescriptor.getNativePointer(),
- event.getButtonCode(), event.getAction());
+ event.getButtonCode(), event.getAction(), event.getEventTimeNanos());
}
}
@@ -412,7 +412,8 @@ class InputController {
}
return mNativeWrapper.writeTouchEvent(inputDeviceDescriptor.getNativePointer(),
event.getPointerId(), event.getToolType(), event.getAction(), event.getX(),
- event.getY(), event.getPressure(), event.getMajorAxisSize());
+ event.getY(), event.getPressure(), event.getMajorAxisSize(),
+ event.getEventTimeNanos());
}
}
@@ -430,7 +431,7 @@ class InputController {
"Display id associated with this mouse is not currently targetable");
}
return mNativeWrapper.writeRelativeEvent(inputDeviceDescriptor.getNativePointer(),
- event.getRelativeX(), event.getRelativeY());
+ event.getRelativeX(), event.getRelativeY(), event.getEventTimeNanos());
}
}
@@ -448,7 +449,7 @@ class InputController {
"Display id associated with this mouse is not currently targetable");
}
return mNativeWrapper.writeScrollEvent(inputDeviceDescriptor.getNativePointer(),
- event.getXAxisMovement(), event.getYAxisMovement());
+ event.getXAxisMovement(), event.getYAxisMovement(), event.getEventTimeNanos());
}
}
@@ -514,15 +515,19 @@ class InputController {
private static native long nativeOpenUinputTouchscreen(String deviceName, int vendorId,
int productId, String phys, int height, int width);
private static native void nativeCloseUinput(long ptr);
- private static native boolean nativeWriteDpadKeyEvent(long ptr, int androidKeyCode, int action);
- private static native boolean nativeWriteKeyEvent(long ptr, int androidKeyCode, int action);
- private static native boolean nativeWriteButtonEvent(long ptr, int buttonCode, int action);
+ private static native boolean nativeWriteDpadKeyEvent(long ptr, int androidKeyCode, int action,
+ long eventTimeNanos);
+ private static native boolean nativeWriteKeyEvent(long ptr, int androidKeyCode, int action,
+ long eventTimeNanos);
+ private static native boolean nativeWriteButtonEvent(long ptr, int buttonCode, int action,
+ long eventTimeNanos);
private static native boolean nativeWriteTouchEvent(long ptr, int pointerId, int toolType,
- int action, float locationX, float locationY, float pressure, float majorAxisSize);
+ int action, float locationX, float locationY, float pressure, float majorAxisSize,
+ long eventTimeNanos);
private static native boolean nativeWriteRelativeEvent(long ptr, float relativeX,
- float relativeY);
+ float relativeY, long eventTimeNanos);
private static native boolean nativeWriteScrollEvent(long ptr, float xAxisMovement,
- float yAxisMovement);
+ float yAxisMovement, long eventTimeNanos);
/** Wrapper around the static native methods for tests. */
@VisibleForTesting
@@ -550,32 +555,37 @@ class InputController {
nativeCloseUinput(ptr);
}
- public boolean writeDpadKeyEvent(long ptr, int androidKeyCode, int action) {
- return nativeWriteDpadKeyEvent(ptr, androidKeyCode, action);
+ public boolean writeDpadKeyEvent(long ptr, int androidKeyCode, int action,
+ long eventTimeNanos) {
+ return nativeWriteDpadKeyEvent(ptr, androidKeyCode, action, eventTimeNanos);
}
- public boolean writeKeyEvent(long ptr, int androidKeyCode, int action) {
- return nativeWriteKeyEvent(ptr, androidKeyCode, action);
+ public boolean writeKeyEvent(long ptr, int androidKeyCode, int action,
+ long eventTimeNanos) {
+ return nativeWriteKeyEvent(ptr, androidKeyCode, action, eventTimeNanos);
}
- public boolean writeButtonEvent(long ptr, int buttonCode, int action) {
- return nativeWriteButtonEvent(ptr, buttonCode, action);
+ public boolean writeButtonEvent(long ptr, int buttonCode, int action,
+ long eventTimeNanos) {
+ return nativeWriteButtonEvent(ptr, buttonCode, action, eventTimeNanos);
}
public boolean writeTouchEvent(long ptr, int pointerId, int toolType, int action,
- float locationX, float locationY, float pressure, float majorAxisSize) {
+ float locationX, float locationY, float pressure, float majorAxisSize,
+ long eventTimeNanos) {
return nativeWriteTouchEvent(ptr, pointerId, toolType,
action, locationX, locationY,
- pressure, majorAxisSize);
+ pressure, majorAxisSize, eventTimeNanos);
}
- public boolean writeRelativeEvent(long ptr, float relativeX, float relativeY) {
- return nativeWriteRelativeEvent(ptr, relativeX, relativeY);
+ public boolean writeRelativeEvent(long ptr, float relativeX, float relativeY,
+ long eventTimeNanos) {
+ return nativeWriteRelativeEvent(ptr, relativeX, relativeY, eventTimeNanos);
}
- public boolean writeScrollEvent(long ptr, float xAxisMovement, float yAxisMovement) {
- return nativeWriteScrollEvent(ptr, xAxisMovement,
- yAxisMovement);
+ public boolean writeScrollEvent(long ptr, float xAxisMovement, float yAxisMovement,
+ long eventTimeNanos) {
+ return nativeWriteScrollEvent(ptr, xAxisMovement, yAxisMovement, eventTimeNanos);
}
}
diff --git a/services/core/java/android/os/BatteryStatsInternal.java b/services/core/java/android/os/BatteryStatsInternal.java
index 12ee13183221..0713999d4354 100644
--- a/services/core/java/android/os/BatteryStatsInternal.java
+++ b/services/core/java/android/os/BatteryStatsInternal.java
@@ -17,7 +17,6 @@
package android.os;
import android.annotation.IntDef;
-import android.annotation.NonNull;
import android.net.Network;
import com.android.internal.os.BinderCallsStats;
@@ -40,6 +39,8 @@ public abstract class BatteryStatsInternal {
public static final int CPU_WAKEUP_SUBSYSTEM_ALARM = 1;
public static final int CPU_WAKEUP_SUBSYSTEM_WIFI = 2;
public static final int CPU_WAKEUP_SUBSYSTEM_SOUND_TRIGGER = 3;
+ public static final int CPU_WAKEUP_SUBSYSTEM_SENSOR = 4;
+ public static final int CPU_WAKEUP_SUBSYSTEM_CELLULAR_DATA = 5;
/** @hide */
@IntDef(prefix = {"CPU_WAKEUP_SUBSYSTEM_"}, value = {
@@ -47,9 +48,11 @@ public abstract class BatteryStatsInternal {
CPU_WAKEUP_SUBSYSTEM_ALARM,
CPU_WAKEUP_SUBSYSTEM_WIFI,
CPU_WAKEUP_SUBSYSTEM_SOUND_TRIGGER,
+ CPU_WAKEUP_SUBSYSTEM_SENSOR,
+ CPU_WAKEUP_SUBSYSTEM_CELLULAR_DATA,
})
@Retention(RetentionPolicy.SOURCE)
- @interface CpuWakeupSubsystem {
+ public @interface CpuWakeupSubsystem {
}
/**
@@ -107,19 +110,16 @@ public abstract class BatteryStatsInternal {
public abstract void noteBinderThreadNativeIds(int[] binderThreadNativeTids);
/**
- * Reports any activity that could potentially have caused the CPU to wake up.
- * Accepts a timestamp to allow free ordering between the event and its reporting.
- * @param subsystem The subsystem this activity should be attributed to.
- * @param elapsedMillis The time when this activity happened in the elapsed timebase.
- * @param uids The uid (or uids) that should be blamed for this activity.
- */
- public abstract void noteCpuWakingActivity(@CpuWakeupSubsystem int subsystem,
- long elapsedMillis, @NonNull int... uids);
-
- /**
* Reports a sound trigger recognition event that may have woken up the CPU.
* @param elapsedMillis The time when the event happened in the elapsed timebase.
* @param uid The uid that requested this trigger.
*/
public abstract void noteWakingSoundTrigger(long elapsedMillis, int uid);
+
+ /**
+ * Reports an alarm batch that would have woken up the CPU.
+ * @param elapsedMillis The time at which this alarm batch was scheduled to go off.
+ * @param uids the uids of all apps that have any alarm in this batch.
+ */
+ public abstract void noteWakingAlarmBatch(long elapsedMillis, int... uids);
}
diff --git a/services/core/java/com/android/server/PackageWatchdog.java b/services/core/java/com/android/server/PackageWatchdog.java
index d256aead97e8..f4f5c951faaa 100644
--- a/services/core/java/com/android/server/PackageWatchdog.java
+++ b/services/core/java/com/android/server/PackageWatchdog.java
@@ -580,6 +580,7 @@ public class PackageWatchdog {
PackageHealthObserverImpact.USER_IMPACT_LEVEL_10,
PackageHealthObserverImpact.USER_IMPACT_LEVEL_30,
PackageHealthObserverImpact.USER_IMPACT_LEVEL_50,
+ PackageHealthObserverImpact.USER_IMPACT_LEVEL_60,
PackageHealthObserverImpact.USER_IMPACT_LEVEL_70,
PackageHealthObserverImpact.USER_IMPACT_LEVEL_100})
public @interface PackageHealthObserverImpact {
@@ -590,6 +591,7 @@ public class PackageWatchdog {
/* Actions having medium user impact, user of a device will likely notice. */
int USER_IMPACT_LEVEL_30 = 30;
int USER_IMPACT_LEVEL_50 = 50;
+ int USER_IMPACT_LEVEL_60 = 60;
int USER_IMPACT_LEVEL_70 = 70;
/* Action has high user impact, a last resort, user of a device will be very frustrated. */
int USER_IMPACT_LEVEL_100 = 100;
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index c0b3a90d923b..d140403f77c3 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -23,6 +23,7 @@ import static android.Manifest.permission.NETWORK_STACK;
import static android.Manifest.permission.POWER_SAVER;
import static android.Manifest.permission.UPDATE_DEVICE_STATS;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED;
+import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
import static android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK;
import static android.os.BatteryStats.POWER_DATA_UNAVAILABLE;
@@ -51,6 +52,7 @@ import android.os.BatteryConsumer;
import android.os.BatteryManagerInternal;
import android.os.BatteryStats;
import android.os.BatteryStatsInternal;
+import android.os.BatteryStatsInternal.CpuWakeupSubsystem;
import android.os.BatteryUsageStats;
import android.os.BatteryUsageStatsQuery;
import android.os.Binder;
@@ -474,6 +476,8 @@ public final class BatteryStatsService extends IBatteryStats.Stub
private int transportToSubsystem(NetworkCapabilities nc) {
if (nc.hasTransport(TRANSPORT_WIFI)) {
return CPU_WAKEUP_SUBSYSTEM_WIFI;
+ } else if (nc.hasTransport(TRANSPORT_CELLULAR)) {
+ return CPU_WAKEUP_SUBSYSTEM_CELLULAR_DATA;
}
return CPU_WAKEUP_SUBSYSTEM_UNKNOWN;
}
@@ -514,14 +518,32 @@ public final class BatteryStatsService extends IBatteryStats.Stub
}
@Override
- public void noteCpuWakingActivity(int subsystem, long elapsedMillis, int... uids) {
- Objects.requireNonNull(uids);
- mHandler.post(() -> mCpuWakeupStats.noteWakingActivity(subsystem, elapsedMillis, uids));
- }
- @Override
public void noteWakingSoundTrigger(long elapsedMillis, int uid) {
noteCpuWakingActivity(CPU_WAKEUP_SUBSYSTEM_SOUND_TRIGGER, elapsedMillis, uid);
}
+
+ @Override
+ public void noteWakingAlarmBatch(long elapsedMillis, int... uids) {
+ noteCpuWakingActivity(CPU_WAKEUP_SUBSYSTEM_ALARM, elapsedMillis, uids);
+ }
+ }
+
+ /**
+ * Reports any activity that could potentially have caused the CPU to wake up.
+ * Accepts a timestamp to allow free ordering between the event and its reporting.
+ *
+ * <p>
+ * This method can be called multiple times for the same wakeup and then all attribution
+ * reported will be unioned as long as all reports are made within a small amount of cpu uptime
+ * after the wakeup is reported to batterystats.
+ *
+ * @param subsystem The subsystem this activity should be attributed to.
+ * @param elapsedMillis The time when this activity happened in the elapsed timebase.
+ * @param uids The uid (or uids) that should be blamed for this activity.
+ */
+ void noteCpuWakingActivity(@CpuWakeupSubsystem int subsystem, long elapsedMillis, int... uids) {
+ Objects.requireNonNull(uids);
+ mHandler.post(() -> mCpuWakeupStats.noteWakingActivity(subsystem, elapsedMillis, uids));
}
@Override
@@ -1267,6 +1289,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub
if (callingUid != Process.SYSTEM_UID) {
throw new SecurityException("Calling uid " + callingUid + " is not system uid");
}
+ final long elapsedMillis = TimeUnit.NANOSECONDS.toMillis(elapsedNanos);
final SensorManager sm = mContext.getSystemService(SensorManager.class);
final Sensor sensor = sm.getSensorByHandle(sensorHandle);
@@ -1275,10 +1298,12 @@ public final class BatteryStatsService extends IBatteryStats.Stub
+ " received in noteWakeupSensorEvent");
return;
}
- Slog.i(TAG, "Sensor " + sensor + " wakeup event at " + elapsedNanos + " sent to uid "
- + uid);
- // TODO (b/275436924): Remove log and pipe to CpuWakeupStats for wakeup attribution
- // This method should return as quickly as possible. Use mHandler#post to do longer work.
+ if (uid < 0) {
+ Slog.wtf(TAG, "Invalid uid " + uid + " for sensor event with sensor: " + sensor);
+ return;
+ }
+ // TODO (b/278319756): Also pipe in Sensor type for more usefulness.
+ noteCpuWakingActivity(BatteryStatsInternal.CPU_WAKEUP_SUBSYSTEM_SENSOR, elapsedMillis, uid);
}
@Override
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index 2da9fa6088db..13c42eb66b69 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -85,6 +85,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
private final @NonNull AudioService mAudioService;
private final @NonNull Context mContext;
+ private final @NonNull AudioSystemAdapter mAudioSystem;
/** ID for Communication strategy retrieved form audio policy manager */
private int mCommunicationStrategyId = -1;
@@ -159,12 +160,14 @@ import java.util.concurrent.atomic.AtomicBoolean;
public static final long USE_SET_COMMUNICATION_DEVICE = 243827847L;
//-------------------------------------------------------------------
- /*package*/ AudioDeviceBroker(@NonNull Context context, @NonNull AudioService service) {
+ /*package*/ AudioDeviceBroker(@NonNull Context context, @NonNull AudioService service,
+ @NonNull AudioSystemAdapter audioSystem) {
mContext = context;
mAudioService = service;
mBtHelper = new BtHelper(this);
mDeviceInventory = new AudioDeviceInventory(this);
mSystemServer = SystemServerAdapter.getDefaultAdapter(mContext);
+ mAudioSystem = audioSystem;
init();
}
@@ -173,12 +176,14 @@ import java.util.concurrent.atomic.AtomicBoolean;
* in system_server */
AudioDeviceBroker(@NonNull Context context, @NonNull AudioService service,
@NonNull AudioDeviceInventory mockDeviceInventory,
- @NonNull SystemServerAdapter mockSystemServer) {
+ @NonNull SystemServerAdapter mockSystemServer,
+ @NonNull AudioSystemAdapter audioSystem) {
mContext = context;
mAudioService = service;
mBtHelper = new BtHelper(this);
mDeviceInventory = mockDeviceInventory;
mSystemServer = mockSystemServer;
+ mAudioSystem = audioSystem;
init();
}
@@ -540,7 +545,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
AudioAttributes attr =
AudioProductStrategy.getAudioAttributesForStrategyWithLegacyStreamType(
AudioSystem.STREAM_VOICE_CALL);
- List<AudioDeviceAttributes> devices = AudioSystem.getDevicesForAttributes(
+ List<AudioDeviceAttributes> devices = mAudioSystem.getDevicesForAttributes(
attr, false /* forVolume */);
if (devices.isEmpty()) {
if (mAudioService.isPlatformVoice()) {
@@ -1493,7 +1498,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
Log.v(TAG, "onSetForceUse(useCase<" + useCase + ">, config<" + config + ">, fromA2dp<"
+ fromA2dp + ">, eventSource<" + eventSource + ">)");
}
- AudioSystem.setForceUse(useCase, config);
+ mAudioSystem.setForceUse(useCase, config);
}
private void onSendBecomingNoisyIntent() {
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index dbfb832e592a..3487fc2c14be 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -199,6 +199,7 @@ import com.android.server.LocalServices;
import com.android.server.SystemService;
import com.android.server.audio.AudioServiceEvents.DeviceVolumeEvent;
import com.android.server.audio.AudioServiceEvents.PhoneStateEvent;
+import com.android.server.audio.AudioServiceEvents.VolChangedBroadcastEvent;
import com.android.server.audio.AudioServiceEvents.VolumeEvent;
import com.android.server.pm.UserManagerInternal;
import com.android.server.pm.UserManagerInternal.UserRestrictionsListener;
@@ -1214,7 +1215,7 @@ public class AudioService extends IAudioService.Stub
mUseFixedVolume = mContext.getResources().getBoolean(
com.android.internal.R.bool.config_useFixedVolume);
- mDeviceBroker = new AudioDeviceBroker(mContext, this);
+ mDeviceBroker = new AudioDeviceBroker(mContext, this, mAudioSystem);
mRecordMonitor = new RecordingActivityMonitor(mContext);
mRecordMonitor.registerRecordingCallback(mVoiceRecordingActivityMonitor, true);
@@ -1687,7 +1688,7 @@ public class AudioService extends IAudioService.Stub
synchronized (mSettingsLock) {
final int forDock = mDockAudioMediaEnabled ?
- AudioSystem.FORCE_ANALOG_DOCK : AudioSystem.FORCE_NONE;
+ AudioSystem.FORCE_DIGITAL_DOCK : AudioSystem.FORCE_NONE;
mDeviceBroker.setForceUse_Async(AudioSystem.FOR_DOCK, forDock, "onAudioServerDied");
sendEncodedSurroundMode(mContentResolver, "onAudioServerDied");
sendEnabledSurroundFormats(mContentResolver, true);
@@ -2323,9 +2324,10 @@ public class AudioService extends IAudioService.Stub
SENDMSG_QUEUE,
AudioSystem.FOR_DOCK,
mDockAudioMediaEnabled ?
- AudioSystem.FORCE_ANALOG_DOCK : AudioSystem.FORCE_NONE,
+ AudioSystem.FORCE_DIGITAL_DOCK : AudioSystem.FORCE_NONE,
new String("readDockAudioSettings"),
0);
+
}
@@ -3972,13 +3974,32 @@ public class AudioService extends IAudioService.Stub
Log.e(TAG, "Unsupported non-stream type based VolumeInfo", new Exception());
return;
}
+
int index = vi.getVolumeIndex();
if (index == VolumeInfo.INDEX_NOT_SET && !vi.hasMuteCommand()) {
throw new IllegalArgumentException(
"changing device volume requires a volume index or mute command");
}
- // TODO handle unmuting if current audio device
+ // force a cache clear to force reevaluating stream type to audio device selection
+ // that can interfere with the sending of the VOLUME_CHANGED_ACTION intent
+ mAudioSystem.clearRoutingCache();
+
+ // log the current device that will be used when evaluating the sending of the
+ // VOLUME_CHANGED_ACTION intent to see if the current device is the one being modified
+ final int currDev = getDeviceForStream(vi.getStreamType());
+
+ final boolean skipping = (currDev == ada.getInternalType());
+
+ AudioService.sVolumeLogger.enqueue(new DeviceVolumeEvent(vi.getStreamType(), index, ada,
+ currDev, callingPackage, skipping));
+
+ if (skipping) {
+ // setDeviceVolume was called on a device currently being used
+ return;
+ }
+
+ // TODO handle unmuting of current audio device
// if a stream is not muted but the VolumeInfo is for muting, set the volume index
// for the device to min volume
if (vi.hasMuteCommand() && vi.isMuted() && !isStreamMute(vi.getStreamType())) {
@@ -4129,11 +4150,11 @@ public class AudioService extends IAudioService.Stub
return;
}
- final EventLogger.Event event = (device == null)
- ? new VolumeEvent(VolumeEvent.VOL_SET_STREAM_VOL, streamType,
- index/*val1*/, flags/*val2*/, callingPackage)
- : new DeviceVolumeEvent(streamType, index, device, callingPackage);
- sVolumeLogger.enqueue(event);
+ if (device == null) {
+ // call was already logged in setDeviceVolume()
+ sVolumeLogger.enqueue(new VolumeEvent(VolumeEvent.VOL_SET_STREAM_VOL, streamType,
+ index/*val1*/, flags/*val2*/, callingPackage));
+ }
setStreamVolume(streamType, index, flags, device,
callingPackage, callingPackage, attributionTag,
Binder.getCallingUid(), callingOrSelfHasAudioSettingsPermission());
@@ -4547,7 +4568,11 @@ public class AudioService extends IAudioService.Stub
maybeSendSystemAudioStatusCommand(false);
}
}
- sendVolumeUpdate(streamType, oldIndex, index, flags, device);
+ if (ada == null) {
+ // only non-null when coming here from setDeviceVolume
+ // TODO change test to check early if device is current device or not
+ sendVolumeUpdate(streamType, oldIndex, index, flags, device);
+ }
}
private void dispatchAbsoluteVolumeChanged(int streamType, AbsoluteVolumeDeviceInfo deviceInfo,
@@ -8561,6 +8586,8 @@ public class AudioService extends IAudioService.Stub
mVolumeChanged.putExtra(AudioManager.EXTRA_PREV_VOLUME_STREAM_VALUE, oldIndex);
mVolumeChanged.putExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE_ALIAS,
mStreamVolumeAlias[mStreamType]);
+ AudioService.sVolumeLogger.enqueue(new VolChangedBroadcastEvent(
+ mStreamType, mStreamVolumeAlias[mStreamType], index));
sendBroadcastToAll(mVolumeChanged, mVolumeChangedOptions);
}
}
@@ -10837,7 +10864,7 @@ public class AudioService extends IAudioService.Stub
static final int LOG_NB_EVENTS_PHONE_STATE = 20;
static final int LOG_NB_EVENTS_DEVICE_CONNECTION = 50;
static final int LOG_NB_EVENTS_FORCE_USE = 20;
- static final int LOG_NB_EVENTS_VOLUME = 40;
+ static final int LOG_NB_EVENTS_VOLUME = 100;
static final int LOG_NB_EVENTS_DYN_POLICY = 10;
static final int LOG_NB_EVENTS_SPATIAL = 30;
static final int LOG_NB_EVENTS_SOUND_DOSE = 30;
diff --git a/services/core/java/com/android/server/audio/AudioServiceEvents.java b/services/core/java/com/android/server/audio/AudioServiceEvents.java
index 58caf5ae8ca0..b022b5bb866a 100644
--- a/services/core/java/com/android/server/audio/AudioServiceEvents.java
+++ b/services/core/java/com/android/server/audio/AudioServiceEvents.java
@@ -147,20 +147,45 @@ public class AudioServiceEvents {
}
}
+ static final class VolChangedBroadcastEvent extends EventLogger.Event {
+ final int mStreamType;
+ final int mAliasStreamType;
+ final int mIndex;
+
+ VolChangedBroadcastEvent(int stream, int alias, int index) {
+ mStreamType = stream;
+ mAliasStreamType = alias;
+ mIndex = index;
+ }
+
+ @Override
+ public String eventToString() {
+ return new StringBuilder("sending VOLUME_CHANGED stream:")
+ .append(AudioSystem.streamToString(mStreamType))
+ .append(" index:").append(mIndex)
+ .append(" alias:").append(AudioSystem.streamToString(mAliasStreamType))
+ .toString();
+ }
+ }
+
static final class DeviceVolumeEvent extends EventLogger.Event {
final int mStream;
final int mVolIndex;
final String mDeviceNativeType;
final String mDeviceAddress;
final String mCaller;
+ final int mDeviceForStream;
+ final boolean mSkipped;
DeviceVolumeEvent(int streamType, int index, @NonNull AudioDeviceAttributes device,
- String callingPackage) {
+ int deviceForStream, String callingPackage, boolean skipped) {
mStream = streamType;
mVolIndex = index;
mDeviceNativeType = "0x" + Integer.toHexString(device.getInternalType());
mDeviceAddress = device.getAddress();
+ mDeviceForStream = deviceForStream;
mCaller = callingPackage;
+ mSkipped = skipped;
// log metrics
new MediaMetrics.Item(MediaMetrics.Name.AUDIO_VOLUME_EVENT)
.set(MediaMetrics.Property.EVENT, "setDeviceVolume")
@@ -175,12 +200,18 @@ public class AudioServiceEvents {
@Override
public String eventToString() {
- return new StringBuilder("setDeviceVolume(stream:")
+ final StringBuilder sb = new StringBuilder("setDeviceVolume(stream:")
.append(AudioSystem.streamToString(mStream))
.append(" index:").append(mVolIndex)
.append(" device:").append(mDeviceNativeType)
.append(" addr:").append(mDeviceAddress)
- .append(") from ").append(mCaller).toString();
+ .append(") from ").append(mCaller);
+ if (mSkipped) {
+ sb.append(" skipped [device in use]");
+ } else {
+ sb.append(" currDevForStream:Ox").append(Integer.toHexString(mDeviceForStream));
+ }
+ return sb.toString();
}
}
diff --git a/services/core/java/com/android/server/audio/AudioSystemAdapter.java b/services/core/java/com/android/server/audio/AudioSystemAdapter.java
index 7af7ed5fff65..d066340c42b2 100644
--- a/services/core/java/com/android/server/audio/AudioSystemAdapter.java
+++ b/services/core/java/com/android/server/audio/AudioSystemAdapter.java
@@ -162,6 +162,16 @@ public class AudioSystemAdapter implements AudioSystem.RoutingUpdateCallback,
}
/**
+ * Empties the routing cache if enabled.
+ */
+ public void clearRoutingCache() {
+ if (DEBUG_CACHE) {
+ Log.d(TAG, "---- routing cache clear (from java) ----------");
+ }
+ invalidateRoutingCache();
+ }
+
+ /**
* @see AudioManager#addOnDevicesForAttributesChangedListener(
* AudioAttributes, Executor, OnDevicesForAttributesChangedListener)
*/
@@ -464,6 +474,7 @@ public class AudioSystemAdapter implements AudioSystem.RoutingUpdateCallback,
* @return
*/
public int setParameters(String keyValuePairs) {
+ invalidateRoutingCache();
return AudioSystem.setParameters(keyValuePairs);
}
diff --git a/services/core/java/com/android/server/audio/SpatializerHelper.java b/services/core/java/com/android/server/audio/SpatializerHelper.java
index 5edd43464c6b..462c9381b904 100644
--- a/services/core/java/com/android/server/audio/SpatializerHelper.java
+++ b/services/core/java/com/android/server/audio/SpatializerHelper.java
@@ -77,7 +77,7 @@ public class SpatializerHelper {
//------------------------------------------------------------
- private static final SparseIntArray SPAT_MODE_FOR_DEVICE_TYPE = new SparseIntArray(15) {
+ private static final SparseIntArray SPAT_MODE_FOR_DEVICE_TYPE = new SparseIntArray(14) {
{
append(AudioDeviceInfo.TYPE_BUILTIN_SPEAKER, SpatializationMode.SPATIALIZER_TRANSAURAL);
append(AudioDeviceInfo.TYPE_WIRED_HEADSET, SpatializationMode.SPATIALIZER_BINAURAL);
@@ -91,7 +91,6 @@ public class SpatializerHelper {
append(AudioDeviceInfo.TYPE_LINE_ANALOG, SpatializationMode.SPATIALIZER_TRANSAURAL);
append(AudioDeviceInfo.TYPE_LINE_DIGITAL, SpatializationMode.SPATIALIZER_TRANSAURAL);
append(AudioDeviceInfo.TYPE_AUX_LINE, SpatializationMode.SPATIALIZER_TRANSAURAL);
- append(AudioDeviceInfo.TYPE_HEARING_AID, SpatializationMode.SPATIALIZER_BINAURAL);
append(AudioDeviceInfo.TYPE_BLE_HEADSET, SpatializationMode.SPATIALIZER_BINAURAL);
append(AudioDeviceInfo.TYPE_BLE_SPEAKER, SpatializationMode.SPATIALIZER_TRANSAURAL);
// assumption that BLE broadcast would be mostly consumed on headsets
diff --git a/services/core/java/com/android/server/biometrics/OWNERS b/services/core/java/com/android/server/biometrics/OWNERS
index cd281e06f40d..1bf2aeffdf0c 100644
--- a/services/core/java/com/android/server/biometrics/OWNERS
+++ b/services/core/java/com/android/server/biometrics/OWNERS
@@ -6,6 +6,10 @@ jaggies@google.com
jbolinger@google.com
jeffpu@google.com
joshmccloskey@google.com
+diyab@google.com
+austindelgado@google.com
+spdonghao@google.com
+wenhuiy@google.com
firewall@google.com
jasonsfchang@google.com
diff --git a/services/core/java/com/android/server/biometrics/sensors/AuthSessionCoordinator.java b/services/core/java/com/android/server/biometrics/sensors/AuthSessionCoordinator.java
index 2653ce76459d..d9947ddedcef 100644
--- a/services/core/java/com/android/server/biometrics/sensors/AuthSessionCoordinator.java
+++ b/services/core/java/com/android/server/biometrics/sensors/AuthSessionCoordinator.java
@@ -95,8 +95,10 @@ public class AuthSessionCoordinator implements AuthSessionListener {
}
}
- mRingBuffer.addApiCall("internal : onAuthSessionEnded(" + mUserId + ")");
- clearSession();
+ if (mAuthOperations.isEmpty()) {
+ mRingBuffer.addApiCall("internal : onAuthSessionEnded(" + mUserId + ")");
+ clearSession();
+ }
}
private void clearSession() {
@@ -203,7 +205,7 @@ public class AuthSessionCoordinator implements AuthSessionListener {
return;
}
mAuthOperations.remove(sensorId);
- if (mIsAuthenticating && mAuthOperations.isEmpty()) {
+ if (mIsAuthenticating) {
endAuthSession();
}
}
diff --git a/services/core/java/com/android/server/display/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
index 299f86550efd..378363c870c7 100644
--- a/services/core/java/com/android/server/display/AutomaticBrightnessController.java
+++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
@@ -1160,6 +1160,14 @@ public class AutomaticBrightnessController {
update();
}
+ /**
+ * Convert a brightness float scale value to a nit value. Adjustments, such as RBC, are not
+ * applied. This is used when storing the brightness in nits for the default display and when
+ * passing the brightness value to follower displays.
+ *
+ * @param brightness The float scale value
+ * @return The nit value or -1f if no conversion is possible.
+ */
public float convertToNits(float brightness) {
if (mCurrentBrightnessMapper != null) {
return mCurrentBrightnessMapper.convertToNits(brightness);
@@ -1168,6 +1176,30 @@ public class AutomaticBrightnessController {
}
}
+ /**
+ * Convert a brightness float scale value to a nit value. Adjustments, such as RBC are applied.
+ * This is used when sending the brightness value to
+ * {@link com.android.server.display.BrightnessTracker}.
+ *
+ * @param brightness The float scale value
+ * @return The nit value or -1f if no conversion is possible.
+ */
+ public float convertToAdjustedNits(float brightness) {
+ if (mCurrentBrightnessMapper != null) {
+ return mCurrentBrightnessMapper.convertToAdjustedNits(brightness);
+ } else {
+ return -1.0f;
+ }
+ }
+
+ /**
+ * Convert a brightness nit value to a float scale value. It is assumed that the nit value
+ * provided does not have adjustments, such as RBC, applied.
+ *
+ * @param nits The nit value
+ * @return The float scale value or {@link PowerManager.BRIGHTNESS_INVALID_FLOAT} if no
+ * conversion is possible.
+ */
public float convertToFloatScale(float nits) {
if (mCurrentBrightnessMapper != null) {
return mCurrentBrightnessMapper.convertToFloatScale(nits);
diff --git a/services/core/java/com/android/server/display/BrightnessMappingStrategy.java b/services/core/java/com/android/server/display/BrightnessMappingStrategy.java
index 3456e3e262de..df2a8301f543 100644
--- a/services/core/java/com/android/server/display/BrightnessMappingStrategy.java
+++ b/services/core/java/com/android/server/display/BrightnessMappingStrategy.java
@@ -322,6 +322,14 @@ public abstract class BrightnessMappingStrategy {
public abstract float convertToNits(float brightness);
/**
+ * Converts the provided brightness value to nits if possible. Adjustments, such as RBC are
+ * applied.
+ *
+ * Returns -1.0f if there's no available mapping for the brightness to nits.
+ */
+ public abstract float convertToAdjustedNits(float brightness);
+
+ /**
* Converts the provided nit value to a float scale value if possible.
*
* Returns {@link PowerManager.BRIGHTNESS_INVALID_FLOAT} if there's no available mapping for
@@ -683,6 +691,11 @@ public abstract class BrightnessMappingStrategy {
}
@Override
+ public float convertToAdjustedNits(float brightness) {
+ return -1.0f;
+ }
+
+ @Override
public float convertToFloatScale(float nits) {
return PowerManager.BRIGHTNESS_INVALID_FLOAT;
}
@@ -804,6 +817,14 @@ public abstract class BrightnessMappingStrategy {
// a brightness in nits.
private Spline mBrightnessToNitsSpline;
+ // A spline mapping from nits with adjustments applied to the corresponding brightness
+ // value, normalized to the range [0, 1.0].
+ private Spline mAdjustedNitsToBrightnessSpline;
+
+ // A spline mapping from the system brightness value, normalized to the range [0, 1.0], to
+ // a brightness in nits with adjustments applied.
+ private Spline mBrightnessToAdjustedNitsSpline;
+
// The default brightness configuration.
private final BrightnessConfiguration mDefaultConfig;
@@ -843,6 +864,8 @@ public abstract class BrightnessMappingStrategy {
mNits = nits;
mBrightness = brightness;
computeNitsBrightnessSplines(mNits);
+ mAdjustedNitsToBrightnessSpline = mNitsToBrightnessSpline;
+ mBrightnessToAdjustedNitsSpline = mBrightnessToNitsSpline;
mDefaultConfig = config;
if (mLoggingEnabled) {
@@ -892,7 +915,7 @@ public abstract class BrightnessMappingStrategy {
nits = mDisplayWhiteBalanceController.calculateAdjustedBrightnessNits(nits);
}
- float brightness = mNitsToBrightnessSpline.interpolate(nits);
+ float brightness = mAdjustedNitsToBrightnessSpline.interpolate(nits);
// Correct the brightness according to the current application and its category, but
// only if no user data point is set (as this will override the user setting).
if (mUserLux == -1) {
@@ -930,6 +953,11 @@ public abstract class BrightnessMappingStrategy {
}
@Override
+ public float convertToAdjustedNits(float brightness) {
+ return mBrightnessToAdjustedNitsSpline.interpolate(brightness);
+ }
+
+ @Override
public float convertToFloatScale(float nits) {
return mNitsToBrightnessSpline.interpolate(nits);
}
@@ -989,7 +1017,13 @@ public abstract class BrightnessMappingStrategy {
@Override
public void recalculateSplines(boolean applyAdjustment, float[] adjustedNits) {
mBrightnessRangeAdjustmentApplied = applyAdjustment;
- computeNitsBrightnessSplines(mBrightnessRangeAdjustmentApplied ? adjustedNits : mNits);
+ if (applyAdjustment) {
+ mAdjustedNitsToBrightnessSpline = Spline.createSpline(adjustedNits, mBrightness);
+ mBrightnessToAdjustedNitsSpline = Spline.createSpline(mBrightness, adjustedNits);
+ } else {
+ mAdjustedNitsToBrightnessSpline = mNitsToBrightnessSpline;
+ mBrightnessToAdjustedNitsSpline = mBrightnessToNitsSpline;
+ }
}
@Override
@@ -999,6 +1033,8 @@ public abstract class BrightnessMappingStrategy {
pw.println(" mBrightnessSpline=" + mBrightnessSpline);
pw.println(" mNitsToBrightnessSpline=" + mNitsToBrightnessSpline);
pw.println(" mBrightnessToNitsSpline=" + mBrightnessToNitsSpline);
+ pw.println(" mAdjustedNitsToBrightnessSpline=" + mAdjustedNitsToBrightnessSpline);
+ pw.println(" mAdjustedBrightnessToNitsSpline=" + mBrightnessToAdjustedNitsSpline);
pw.println(" mMaxGamma=" + mMaxGamma);
pw.println(" mAutoBrightnessAdjustment=" + mAutoBrightnessAdjustment);
pw.println(" mUserLux=" + mUserLux);
@@ -1072,7 +1108,7 @@ public abstract class BrightnessMappingStrategy {
float defaultNits = defaultSpline.interpolate(lux);
float longTermNits = currSpline.interpolate(lux);
float shortTermNits = mBrightnessSpline.interpolate(lux);
- float brightness = mNitsToBrightnessSpline.interpolate(shortTermNits);
+ float brightness = mAdjustedNitsToBrightnessSpline.interpolate(shortTermNits);
String luxPrefix = (lux == mUserLux ? "^" : "");
String strLux = luxPrefix + toStrFloatForDump(lux);
@@ -1146,7 +1182,7 @@ public abstract class BrightnessMappingStrategy {
float[] defaultNits = defaultCurve.second;
float[] defaultBrightness = new float[defaultNits.length];
for (int i = 0; i < defaultBrightness.length; i++) {
- defaultBrightness[i] = mNitsToBrightnessSpline.interpolate(defaultNits[i]);
+ defaultBrightness[i] = mAdjustedNitsToBrightnessSpline.interpolate(defaultNits[i]);
}
Pair<float[], float[]> curve = getAdjustedCurve(defaultLux, defaultBrightness, mUserLux,
mUserBrightness, mAutoBrightnessAdjustment, mMaxGamma);
@@ -1154,7 +1190,7 @@ public abstract class BrightnessMappingStrategy {
float[] brightness = curve.second;
float[] nits = new float[brightness.length];
for (int i = 0; i < nits.length; i++) {
- nits[i] = mBrightnessToNitsSpline.interpolate(brightness[i]);
+ nits[i] = mBrightnessToAdjustedNitsSpline.interpolate(brightness[i]);
}
mBrightnessSpline = Spline.createSpline(lux, nits);
}
@@ -1162,7 +1198,7 @@ public abstract class BrightnessMappingStrategy {
private float getUnadjustedBrightness(float lux) {
Pair<float[], float[]> curve = mConfig.getCurve();
Spline spline = Spline.createSpline(curve.first, curve.second);
- return mNitsToBrightnessSpline.interpolate(spline.interpolate(lux));
+ return mAdjustedNitsToBrightnessSpline.interpolate(spline.interpolate(lux));
}
private float correctBrightness(float brightness, String packageName, int category) {
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 5d92c7f8cf05..26b6cb0ebe9b 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -1781,7 +1781,24 @@ public final class DisplayManagerService extends SystemService {
} else {
configurePreferredDisplayModeLocked(display);
}
- addDisplayPowerControllerLocked(display);
+ DisplayPowerControllerInterface dpc = addDisplayPowerControllerLocked(display);
+
+ if (dpc != null) {
+ final int leadDisplayId = display.getLeadDisplayIdLocked();
+ updateDisplayPowerControllerLeaderLocked(dpc, leadDisplayId);
+
+ // Loop through all the displays and check if any should follow this one - it could be
+ // that the follower display was added before the lead display.
+ mLogicalDisplayMapper.forEachLocked(d -> {
+ if (d.getLeadDisplayIdLocked() == displayId) {
+ DisplayPowerControllerInterface followerDpc =
+ mDisplayPowerControllers.get(d.getDisplayIdLocked());
+ if (followerDpc != null) {
+ updateDisplayPowerControllerLeaderLocked(followerDpc, displayId);
+ }
+ }
+ });
+ }
mDisplayStates.append(displayId, Display.STATE_UNKNOWN);
@@ -1832,8 +1849,8 @@ public final class DisplayManagerService extends SystemService {
}
}
- private void updateDisplayPowerControllerLeaderLocked(DisplayPowerControllerInterface dpc,
- int leadDisplayId) {
+ private void updateDisplayPowerControllerLeaderLocked(
+ @NonNull DisplayPowerControllerInterface dpc, int leadDisplayId) {
if (dpc.getLeadDisplayId() == leadDisplayId) {
// Lead display hasn't changed, nothing to do.
return;
@@ -1851,9 +1868,11 @@ public final class DisplayManagerService extends SystemService {
// And then, if it's following, register it with the new one.
if (leadDisplayId != Layout.NO_LEAD_DISPLAY) {
- final DisplayPowerControllerInterface newLead =
+ final DisplayPowerControllerInterface newLeader =
mDisplayPowerControllers.get(leadDisplayId);
- newLead.addDisplayBrightnessFollower(dpc);
+ if (newLeader != null) {
+ newLeader.addDisplayBrightnessFollower(dpc);
+ }
}
}
@@ -1872,6 +1891,7 @@ public final class DisplayManagerService extends SystemService {
final DisplayPowerControllerInterface dpc =
mDisplayPowerControllers.removeReturnOld(displayId);
if (dpc != null) {
+ updateDisplayPowerControllerLeaderLocked(dpc, Layout.NO_LEAD_DISPLAY);
dpc.stop();
}
mDisplayStates.delete(displayId);
@@ -3062,10 +3082,11 @@ public final class DisplayManagerService extends SystemService {
}
@RequiresPermission(Manifest.permission.READ_DEVICE_CONFIG)
- private void addDisplayPowerControllerLocked(LogicalDisplay display) {
+ private DisplayPowerControllerInterface addDisplayPowerControllerLocked(
+ LogicalDisplay display) {
if (mPowerHandler == null) {
// initPowerManagement has not yet been called.
- return;
+ return null;
}
if (mBrightnessTracker == null && display.getDisplayIdLocked() == Display.DEFAULT_DISPLAY) {
@@ -3086,7 +3107,7 @@ public final class DisplayManagerService extends SystemService {
if (hbmMetadata == null) {
Slog.wtf(TAG, "High Brightness Mode Metadata is null in DisplayManagerService for "
+ "display: " + display.getDisplayIdLocked());
- return;
+ return null;
}
if (DeviceConfig.getBoolean("display_manager",
"use_newly_structured_display_power_controller", true)) {
@@ -3101,6 +3122,7 @@ public final class DisplayManagerService extends SystemService {
() -> handleBrightnessChange(display), hbmMetadata, mBootCompleted);
}
mDisplayPowerControllers.append(display.getDisplayIdLocked(), displayPowerController);
+ return displayPowerController;
}
private void handleBrightnessChange(LogicalDisplay display) {
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 1acc20880258..f1efec04fa69 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -789,6 +789,17 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
}
}
+ @GuardedBy("mLock")
+ private void clearDisplayBrightnessFollowersLocked() {
+ for (int i = 0; i < mDisplayBrightnessFollowers.size(); i++) {
+ DisplayPowerControllerInterface follower = mDisplayBrightnessFollowers.valueAt(i);
+ mHandler.postAtTime(() -> follower.setBrightnessToFollow(
+ PowerManager.BRIGHTNESS_INVALID_FLOAT, /* nits= */ -1,
+ /* ambientLux= */ 0), mClock.uptimeMillis());
+ }
+ mDisplayBrightnessFollowers.clear();
+ }
+
@Nullable
@Override
public ParceledListSlice<AmbientBrightnessDayStats> getAmbientBrightnessStats(
@@ -946,6 +957,8 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
@Override
public void stop() {
synchronized (mLock) {
+ clearDisplayBrightnessFollowersLocked();
+
mStopped = true;
Message msg = mHandler.obtainMessage(MSG_STOP);
mHandler.sendMessageAtTime(msg, mClock.uptimeMillis());
@@ -1039,7 +1052,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
noteScreenBrightness(mPowerState.getScreenBrightness());
// Initialize all of the brightness tracking state
- final float brightness = convertToNits(mPowerState.getScreenBrightness());
+ final float brightness = convertToAdjustedNits(mPowerState.getScreenBrightness());
if (mBrightnessTracker != null && brightness >= PowerManager.BRIGHTNESS_MIN) {
mBrightnessTracker.start(brightness);
}
@@ -2698,7 +2711,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
private void notifyBrightnessTrackerChanged(float brightness, boolean userInitiated,
boolean wasShortTermModelActive) {
- final float brightnessInNits = convertToNits(brightness);
+ final float brightnessInNits = convertToAdjustedNits(brightness);
if (mUseAutoBrightness && brightnessInNits >= 0.0f
&& mAutomaticBrightnessController != null && mBrightnessTracker != null) {
// We only want to track changes on devices that can actually map the display backlight
@@ -2722,6 +2735,13 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
return mAutomaticBrightnessController.convertToNits(brightness);
}
+ private float convertToAdjustedNits(float brightness) {
+ if (mAutomaticBrightnessController == null) {
+ return -1f;
+ }
+ return mAutomaticBrightnessController.convertToAdjustedNits(brightness);
+ }
+
private float convertToFloatScale(float nits) {
if (mAutomaticBrightnessController == null) {
return PowerManager.BRIGHTNESS_INVALID_FLOAT;
@@ -3075,16 +3095,16 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
int appliedRbcStrength = event.isRbcEnabled() ? event.getRbcStrength() : -1;
float appliedHbmMaxNits =
event.getHbmMode() == BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF
- ? -1f : convertToNits(event.getHbmMax());
+ ? -1f : convertToAdjustedNits(event.getHbmMax());
// thermalCapNits set to -1 if not currently capping max brightness
float appliedThermalCapNits =
event.getThermalMax() == PowerManager.BRIGHTNESS_MAX
- ? -1f : convertToNits(event.getThermalMax());
+ ? -1f : convertToAdjustedNits(event.getThermalMax());
if (mIsDisplayInternal) {
FrameworkStatsLog.write(FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED,
- convertToNits(event.getInitialBrightness()),
- convertToNits(event.getBrightness()),
+ convertToAdjustedNits(event.getInitialBrightness()),
+ convertToAdjustedNits(event.getBrightness()),
event.getLux(),
event.getPhysicalDisplayId(),
event.wasShortTermModelActive(),
diff --git a/services/core/java/com/android/server/display/DisplayPowerController2.java b/services/core/java/com/android/server/display/DisplayPowerController2.java
index b36aedefa2a7..59e112ecbccb 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController2.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController2.java
@@ -764,6 +764,8 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
@Override
public void stop() {
synchronized (mLock) {
+ clearDisplayBrightnessFollowersLocked();
+
mStopped = true;
Message msg = mHandler.obtainMessage(MSG_STOP);
mHandler.sendMessageAtTime(msg, mClock.uptimeMillis());
@@ -854,7 +856,7 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
noteScreenBrightness(mPowerState.getScreenBrightness());
// Initialize all of the brightness tracking state
- final float brightness = mDisplayBrightnessController.convertToNits(
+ final float brightness = mDisplayBrightnessController.convertToAdjustedNits(
mPowerState.getScreenBrightness());
if (mBrightnessTracker != null && brightness >= PowerManager.BRIGHTNESS_MIN) {
mBrightnessTracker.start(brightness);
@@ -2165,7 +2167,8 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
private void notifyBrightnessTrackerChanged(float brightness, boolean userInitiated,
boolean wasShortTermModelActive) {
- final float brightnessInNits = mDisplayBrightnessController.convertToNits(brightness);
+ final float brightnessInNits =
+ mDisplayBrightnessController.convertToAdjustedNits(brightness);
if (mAutomaticBrightnessStrategy.shouldUseAutoBrightness() && brightnessInNits >= 0.0f
&& mAutomaticBrightnessController != null && mBrightnessTracker != null) {
// We only want to track changes on devices that can actually map the display backlight
@@ -2200,6 +2203,17 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
}
}
+ @GuardedBy("mLock")
+ private void clearDisplayBrightnessFollowersLocked() {
+ for (int i = 0; i < mDisplayBrightnessFollowers.size(); i++) {
+ DisplayPowerControllerInterface follower = mDisplayBrightnessFollowers.valueAt(i);
+ mHandler.postAtTime(() -> follower.setBrightnessToFollow(
+ PowerManager.BRIGHTNESS_INVALID_FLOAT, /* nits= */ -1,
+ /* ambientLux= */ 0), mClock.uptimeMillis());
+ }
+ mDisplayBrightnessFollowers.clear();
+ }
+
@Override
public void dump(final PrintWriter pw) {
synchronized (mLock) {
@@ -2438,15 +2452,16 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
int appliedRbcStrength = event.isRbcEnabled() ? event.getRbcStrength() : -1;
float appliedHbmMaxNits =
event.getHbmMode() == BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF
- ? -1f : mDisplayBrightnessController.convertToNits(event.getHbmMax());
+ ? -1f : mDisplayBrightnessController.convertToAdjustedNits(event.getHbmMax());
// thermalCapNits set to -1 if not currently capping max brightness
float appliedThermalCapNits =
event.getThermalMax() == PowerManager.BRIGHTNESS_MAX
- ? -1f : mDisplayBrightnessController.convertToNits(event.getThermalMax());
+ ? -1f : mDisplayBrightnessController.convertToAdjustedNits(event.getThermalMax());
if (mIsDisplayInternal) {
FrameworkStatsLog.write(FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED,
- mDisplayBrightnessController.convertToNits(event.getInitialBrightness()),
- mDisplayBrightnessController.convertToNits(event.getBrightness()),
+ mDisplayBrightnessController
+ .convertToAdjustedNits(event.getInitialBrightness()),
+ mDisplayBrightnessController.convertToAdjustedNits(event.getBrightness()),
event.getLux(),
event.getPhysicalDisplayId(),
event.wasShortTermModelActive(),
diff --git a/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java b/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java
index 2916fefefe1b..a3f8c4d16cd1 100644
--- a/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java
+++ b/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java
@@ -312,7 +312,10 @@ public final class DisplayBrightnessController {
}
/**
- * Convert a brightness float scale value to a nit value.
+ * Convert a brightness float scale value to a nit value. Adjustments, such as RBC, are not
+ * applied. This is used when storing the brightness in nits for the default display and when
+ * passing the brightness value to follower displays.
+ *
* @param brightness The float scale value
* @return The nit value or -1f if no conversion is possible.
*/
@@ -324,7 +327,24 @@ public final class DisplayBrightnessController {
}
/**
- * Convert a brightness nit value to a float scale value.
+ * Convert a brightness float scale value to a nit value. Adjustments, such as RBC are applied.
+ * This is used when sending the brightness value to
+ * {@link com.android.server.display.BrightnessTracker}.
+ *
+ * @param brightness The float scale value
+ * @return The nit value or -1f if no conversion is possible.
+ */
+ public float convertToAdjustedNits(float brightness) {
+ if (mAutomaticBrightnessController == null) {
+ return -1f;
+ }
+ return mAutomaticBrightnessController.convertToAdjustedNits(brightness);
+ }
+
+ /**
+ * Convert a brightness nit value to a float scale value. It is assumed that the nit value
+ * provided does not have adjustments, such as RBC, applied.
+ *
* @param nits The nit value
* @return The float scale value or {@link PowerManager.BRIGHTNESS_INVALID_FLOAT} if no
* conversion is possible.
diff --git a/services/core/java/com/android/server/notification/ManagedServices.java b/services/core/java/com/android/server/notification/ManagedServices.java
index 73440b7f2eec..12fc263e1c8c 100644
--- a/services/core/java/com/android/server/notification/ManagedServices.java
+++ b/services/core/java/com/android/server/notification/ManagedServices.java
@@ -140,34 +140,39 @@ abstract public class ManagedServices {
* The services that have been bound by us. If the service is also connected, it will also
* be in {@link #mServices}.
*/
+ @GuardedBy("mMutex")
private final ArrayList<Pair<ComponentName, Integer>> mServicesBound = new ArrayList<>();
+ @GuardedBy("mMutex")
private final ArraySet<Pair<ComponentName, Integer>> mServicesRebinding = new ArraySet<>();
// we need these packages to be protected because classes that inherit from it need to see it
protected final Object mDefaultsLock = new Object();
+ @GuardedBy("mDefaultsLock")
protected final ArraySet<ComponentName> mDefaultComponents = new ArraySet<>();
+ @GuardedBy("mDefaultsLock")
protected final ArraySet<String> mDefaultPackages = new ArraySet<>();
// lists the component names of all enabled (and therefore potentially connected)
// app services for current profiles.
- private ArraySet<ComponentName> mEnabledServicesForCurrentProfiles
- = new ArraySet<>();
+ @GuardedBy("mMutex")
+ private final ArraySet<ComponentName> mEnabledServicesForCurrentProfiles = new ArraySet<>();
// Just the packages from mEnabledServicesForCurrentProfiles
- private ArraySet<String> mEnabledServicesPackageNames = new ArraySet<>();
+ @GuardedBy("mMutex")
+ private final ArraySet<String> mEnabledServicesPackageNames = new ArraySet<>();
// Per user id, list of enabled packages that have nevertheless asked not to be run
- private final android.util.SparseSetArray<ComponentName> mSnoozing =
- new android.util.SparseSetArray<>();
+ @GuardedBy("mSnoozing")
+ private final SparseSetArray<ComponentName> mSnoozing = new SparseSetArray<>();
// List of approved packages or components (by user, then by primary/secondary) that are
// allowed to be bound as managed services. A package or component appearing in this list does
// not mean that we are currently bound to said package/component.
+ @GuardedBy("mApproved")
protected final ArrayMap<Integer, ArrayMap<Boolean, ArraySet<String>>> mApproved =
new ArrayMap<>();
-
// List of packages or components (by user) that are configured to be enabled/disabled
// explicitly by the user
@GuardedBy("mApproved")
protected ArrayMap<Integer, ArraySet<String>> mUserSetServices = new ArrayMap<>();
-
+ @GuardedBy("mApproved")
protected ArrayMap<Integer, Boolean> mIsUserChanged = new ArrayMap<>();
// True if approved services are stored in xml, not settings.
@@ -262,20 +267,18 @@ abstract public class ManagedServices {
@NonNull
ArrayMap<Boolean, ArrayList<ComponentName>> resetComponents(String packageName, int userId) {
// components that we want to enable
- ArrayList<ComponentName> componentsToEnable =
- new ArrayList<>(mDefaultComponents.size());
-
+ ArrayList<ComponentName> componentsToEnable;
// components that were removed
- ArrayList<ComponentName> disabledComponents =
- new ArrayList<>(mDefaultComponents.size());
-
+ ArrayList<ComponentName> disabledComponents;
// all components that are enabled now
- ArraySet<ComponentName> enabledComponents =
- new ArraySet<>(getAllowedComponents(userId));
+ ArraySet<ComponentName> enabledComponents = new ArraySet<>(getAllowedComponents(userId));
boolean changed = false;
synchronized (mDefaultsLock) {
+ componentsToEnable = new ArrayList<>(mDefaultComponents.size());
+ disabledComponents = new ArrayList<>(mDefaultComponents.size());
+
// record all components that are enabled but should not be by default
for (int i = 0; i < mDefaultComponents.size() && enabledComponents.size() > 0; i++) {
ComponentName currentDefault = mDefaultComponents.valueAt(i);
@@ -374,14 +377,14 @@ abstract public class ManagedServices {
}
}
- pw.println(" All " + getCaption() + "s (" + mEnabledServicesForCurrentProfiles.size()
- + ") enabled for current profiles:");
- for (ComponentName cmpt : mEnabledServicesForCurrentProfiles) {
- if (filter != null && !filter.matches(cmpt)) continue;
- pw.println(" " + cmpt);
- }
-
synchronized (mMutex) {
+ pw.println(" All " + getCaption() + "s (" + mEnabledServicesForCurrentProfiles.size()
+ + ") enabled for current profiles:");
+ for (ComponentName cmpt : mEnabledServicesForCurrentProfiles) {
+ if (filter != null && !filter.matches(cmpt)) continue;
+ pw.println(" " + cmpt);
+ }
+
pw.println(" Live " + getCaption() + "s (" + mServices.size() + "):");
for (ManagedServiceInfo info : mServices) {
if (filter != null && !filter.matches(info.component)) continue;
@@ -434,12 +437,12 @@ abstract public class ManagedServices {
}
}
- for (ComponentName cmpt : mEnabledServicesForCurrentProfiles) {
- if (filter != null && !filter.matches(cmpt)) continue;
- cmpt.dumpDebug(proto, ManagedServicesProto.ENABLED);
- }
synchronized (mMutex) {
+ for (ComponentName cmpt : mEnabledServicesForCurrentProfiles) {
+ if (filter != null && !filter.matches(cmpt)) continue;
+ cmpt.dumpDebug(proto, ManagedServicesProto.ENABLED);
+ }
for (ManagedServiceInfo info : mServices) {
if (filter != null && !filter.matches(info.component)) continue;
info.dumpDebug(proto, ManagedServicesProto.LIVE_SERVICES, this);
@@ -677,7 +680,9 @@ abstract public class ManagedServices {
if (isUserChanged == null) { //NLS
userSetComponent = TextUtils.emptyIfNull(userSetComponent);
} else { //NAS
- mIsUserChanged.put(resolvedUserId, Boolean.valueOf(isUserChanged));
+ synchronized (mApproved) {
+ mIsUserChanged.put(resolvedUserId, Boolean.valueOf(isUserChanged));
+ }
userSetComponent = Boolean.valueOf(isUserChanged) ? approved : "";
}
} else {
@@ -688,7 +693,9 @@ abstract public class ManagedServices {
if (isUserChanged_Old != null && Boolean.valueOf(isUserChanged_Old)) {
//user_set = true
userSetComponent = approved;
- mIsUserChanged.put(resolvedUserId, true);
+ synchronized (mApproved) {
+ mIsUserChanged.put(resolvedUserId, true);
+ }
needUpgradeUserset = false;
} else {
userSetComponent = "";
@@ -724,8 +731,11 @@ abstract public class ManagedServices {
}
void upgradeDefaultsXmlVersion() {
- // check if any defaults are loaded
- int defaultsSize = mDefaultComponents.size() + mDefaultPackages.size();
+ int defaultsSize;
+ synchronized (mDefaultsLock) {
+ // check if any defaults are loaded
+ defaultsSize = mDefaultComponents.size() + mDefaultPackages.size();
+ }
if (defaultsSize == 0) {
// load defaults from current allowed
if (this.mApprovalLevel == APPROVAL_BY_COMPONENT) {
@@ -741,8 +751,10 @@ abstract public class ManagedServices {
}
}
}
+ synchronized (mDefaultsLock) {
+ defaultsSize = mDefaultComponents.size() + mDefaultPackages.size();
+ }
// if no defaults are loaded, then load from config
- defaultsSize = mDefaultComponents.size() + mDefaultPackages.size();
if (defaultsSize == 0) {
loadDefaultsFromConfig();
}
@@ -806,7 +818,9 @@ abstract public class ManagedServices {
}
protected boolean isComponentEnabledForPackage(String pkg) {
- return mEnabledServicesPackageNames.contains(pkg);
+ synchronized (mMutex) {
+ return mEnabledServicesPackageNames.contains(pkg);
+ }
}
protected void setPackageOrComponentEnabled(String pkgOrComponent, int userId,
@@ -959,9 +973,13 @@ abstract public class ManagedServices {
}
public void onPackagesChanged(boolean removingPackage, String[] pkgList, int[] uidList) {
- if (DEBUG) Slog.d(TAG, "onPackagesChanged removingPackage=" + removingPackage
- + " pkgList=" + (pkgList == null ? null : Arrays.asList(pkgList))
- + " mEnabledServicesPackageNames=" + mEnabledServicesPackageNames);
+ if (DEBUG) {
+ synchronized (mMutex) {
+ Slog.d(TAG, "onPackagesChanged removingPackage=" + removingPackage
+ + " pkgList=" + (pkgList == null ? null : Arrays.asList(pkgList))
+ + " mEnabledServicesPackageNames=" + mEnabledServicesPackageNames);
+ }
+ }
if (pkgList != null && (pkgList.length > 0)) {
boolean anyServicesInvolved = false;
@@ -975,7 +993,7 @@ abstract public class ManagedServices {
}
}
for (String pkgName : pkgList) {
- if (mEnabledServicesPackageNames.contains(pkgName)) {
+ if (isComponentEnabledForPackage(pkgName)) {
anyServicesInvolved = true;
}
if (uidList != null && uidList.length > 0) {
@@ -1299,9 +1317,11 @@ abstract public class ManagedServices {
}
final Set<ComponentName> add = new HashSet<>(userComponents);
- ArraySet<ComponentName> snoozed = mSnoozing.get(userId);
- if (snoozed != null) {
- add.removeAll(snoozed);
+ synchronized (mSnoozing) {
+ ArraySet<ComponentName> snoozed = mSnoozing.get(userId);
+ if (snoozed != null) {
+ add.removeAll(snoozed);
+ }
}
componentsToBind.put(userId, add);
@@ -1605,9 +1625,12 @@ abstract public class ManagedServices {
}
}
+ @VisibleForTesting
boolean isBound(ComponentName cn, int userId) {
final Pair<ComponentName, Integer> servicesBindingTag = Pair.create(cn, userId);
- return mServicesBound.contains(servicesBindingTag);
+ synchronized (mMutex) {
+ return mServicesBound.contains(servicesBindingTag);
+ }
}
protected boolean isBoundOrRebinding(final ComponentName cn, final int userId) {
@@ -1833,7 +1856,9 @@ abstract public class ManagedServices {
public boolean isEnabledForCurrentProfiles() {
if (this.isSystem) return true;
if (this.connection == null) return false;
- return mEnabledServicesForCurrentProfiles.contains(this.component);
+ synchronized (mMutex) {
+ return mEnabledServicesForCurrentProfiles.contains(this.component);
+ }
}
/**
@@ -1877,7 +1902,9 @@ abstract public class ManagedServices {
/** convenience method for looking in mEnabledServicesForCurrentProfiles */
public boolean isComponentEnabledForCurrentProfiles(ComponentName component) {
- return mEnabledServicesForCurrentProfiles.contains(component);
+ synchronized (mMutex) {
+ return mEnabledServicesForCurrentProfiles.contains(component);
+ }
}
public static class UserProfiles {
diff --git a/services/core/java/com/android/server/pm/BroadcastHelper.java b/services/core/java/com/android/server/pm/BroadcastHelper.java
index 2704f56b539d..02052808dd18 100644
--- a/services/core/java/com/android/server/pm/BroadcastHelper.java
+++ b/services/core/java/com/android/server/pm/BroadcastHelper.java
@@ -393,7 +393,7 @@ public final class BroadcastHelper {
public static boolean isPrivacySafetyLabelChangeNotificationsEnabled(Context context) {
PackageManager packageManager = context.getPackageManager();
return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY,
- SAFETY_LABEL_CHANGE_NOTIFICATIONS_ENABLED, false)
+ SAFETY_LABEL_CHANGE_NOTIFICATIONS_ENABLED, true)
&& !packageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)
&& !packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK)
&& !packageManager.hasSystemFeature(PackageManager.FEATURE_WATCH);
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index fc6b4e9dcb20..4094b1a06d94 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -3484,6 +3484,11 @@ public class PhoneWindowManager implements WindowManagerPolicy {
interceptScreenshotChord(SCREENSHOT_KEY_OTHER, 0 /*pressDelay*/);
}
return true;
+ case KeyEvent.KEYCODE_ESCAPE:
+ if (down && repeatCount == 0) {
+ mContext.closeSystemDialogs();
+ }
+ return true;
}
return false;
diff --git a/services/core/java/com/android/server/power/stats/wakeups/CpuWakeupStats.java b/services/core/java/com/android/server/power/stats/wakeups/CpuWakeupStats.java
index 1d63489f3c4f..eb6d28e76ff8 100644
--- a/services/core/java/com/android/server/power/stats/wakeups/CpuWakeupStats.java
+++ b/services/core/java/com/android/server/power/stats/wakeups/CpuWakeupStats.java
@@ -17,6 +17,8 @@
package com.android.server.power.stats.wakeups;
import static android.os.BatteryStatsInternal.CPU_WAKEUP_SUBSYSTEM_ALARM;
+import static android.os.BatteryStatsInternal.CPU_WAKEUP_SUBSYSTEM_CELLULAR_DATA;
+import static android.os.BatteryStatsInternal.CPU_WAKEUP_SUBSYSTEM_SENSOR;
import static android.os.BatteryStatsInternal.CPU_WAKEUP_SUBSYSTEM_SOUND_TRIGGER;
import static android.os.BatteryStatsInternal.CPU_WAKEUP_SUBSYSTEM_UNKNOWN;
import static android.os.BatteryStatsInternal.CPU_WAKEUP_SUBSYSTEM_WIFI;
@@ -54,10 +56,11 @@ import java.util.regex.Pattern;
*/
public class CpuWakeupStats {
private static final String TAG = "CpuWakeupStats";
-
private static final String SUBSYSTEM_ALARM_STRING = "Alarm";
private static final String SUBSYSTEM_WIFI_STRING = "Wifi";
private static final String SUBSYSTEM_SOUND_TRIGGER_STRING = "Sound_trigger";
+ private static final String SUBSYSTEM_SENSOR_STRING = "Sensor";
+ private static final String SUBSYSTEM_CELLULAR_DATA_STRING = "Cellular_data";
private static final String TRACE_TRACK_WAKEUP_ATTRIBUTION = "wakeup_attribution";
@VisibleForTesting
static final long WAKEUP_REASON_HALF_WINDOW_MS = 500;
@@ -111,6 +114,10 @@ public class CpuWakeupStats {
return FrameworkStatsLog.KERNEL_WAKEUP_ATTRIBUTED__REASON__WIFI;
case CPU_WAKEUP_SUBSYSTEM_SOUND_TRIGGER:
return FrameworkStatsLog.KERNEL_WAKEUP_ATTRIBUTED__REASON__SOUND_TRIGGER;
+ case CPU_WAKEUP_SUBSYSTEM_SENSOR:
+ return FrameworkStatsLog.KERNEL_WAKEUP_ATTRIBUTED__REASON__SENSOR;
+ case CPU_WAKEUP_SUBSYSTEM_CELLULAR_DATA:
+ return FrameworkStatsLog.KERNEL_WAKEUP_ATTRIBUTED__REASON__CELLULAR_DATA;
}
return FrameworkStatsLog.KERNEL_WAKEUP_ATTRIBUTED__REASON__UNKNOWN;
}
@@ -542,6 +549,10 @@ public class CpuWakeupStats {
return CPU_WAKEUP_SUBSYSTEM_WIFI;
case SUBSYSTEM_SOUND_TRIGGER_STRING:
return CPU_WAKEUP_SUBSYSTEM_SOUND_TRIGGER;
+ case SUBSYSTEM_SENSOR_STRING:
+ return CPU_WAKEUP_SUBSYSTEM_SENSOR;
+ case SUBSYSTEM_CELLULAR_DATA_STRING:
+ return CPU_WAKEUP_SUBSYSTEM_CELLULAR_DATA;
}
return CPU_WAKEUP_SUBSYSTEM_UNKNOWN;
}
@@ -554,6 +565,10 @@ public class CpuWakeupStats {
return SUBSYSTEM_WIFI_STRING;
case CPU_WAKEUP_SUBSYSTEM_SOUND_TRIGGER:
return SUBSYSTEM_SOUND_TRIGGER_STRING;
+ case CPU_WAKEUP_SUBSYSTEM_SENSOR:
+ return SUBSYSTEM_SENSOR_STRING;
+ case CPU_WAKEUP_SUBSYSTEM_CELLULAR_DATA:
+ return SUBSYSTEM_CELLULAR_DATA_STRING;
case CPU_WAKEUP_SUBSYSTEM_UNKNOWN:
return "Unknown";
}
diff --git a/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java b/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
index 2007079ea5ca..0ca560398657 100644
--- a/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
+++ b/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
@@ -121,7 +121,7 @@ final class RollbackPackageHealthObserver implements PackageHealthObserver {
impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_30;
} else if (getAvailableRollback(failedPackage) != null) {
// Rollback is available, we may get a callback into #execute
- impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_30;
+ impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_60;
} else if (anyRollbackAvailable) {
// If any rollbacks are available, we will commit them
impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_70;
diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
index 0ce17ded31e4..6eded1a14dbf 100644
--- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
+++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
@@ -171,6 +171,8 @@ import android.util.SparseArray;
import android.util.SparseIntArray;
import android.util.StatsEvent;
import android.util.proto.ProtoOutputStream;
+import android.uwb.UwbActivityEnergyInfo;
+import android.uwb.UwbManager;
import android.view.Display;
import com.android.internal.annotations.GuardedBy;
@@ -346,6 +348,7 @@ public class StatsPullAtomService extends SystemService {
private StorageManager mStorageManager;
private WifiManager mWifiManager;
private TelephonyManager mTelephony;
+ private UwbManager mUwbManager;
private SubscriptionManager mSubscriptionManager;
private NetworkStatsManager mNetworkStatsManager;
@@ -415,6 +418,7 @@ public class StatsPullAtomService extends SystemService {
private final Object mWifiActivityInfoLock = new Object();
private final Object mModemActivityInfoLock = new Object();
private final Object mBluetoothActivityInfoLock = new Object();
+ private final Object mUwbActivityInfoLock = new Object();
private final Object mSystemElapsedRealtimeLock = new Object();
private final Object mSystemUptimeLock = new Object();
private final Object mProcessMemoryStateLock = new Object();
@@ -537,6 +541,10 @@ public class StatsPullAtomService extends SystemService {
synchronized (mBluetoothActivityInfoLock) {
return pullBluetoothActivityInfoLocked(atomTag, data);
}
+ case FrameworkStatsLog.UWB_ACTIVITY_INFO:
+ synchronized (mUwbActivityInfoLock) {
+ return pullUwbActivityInfoLocked(atomTag, data);
+ }
case FrameworkStatsLog.SYSTEM_ELAPSED_REALTIME:
synchronized (mSystemElapsedRealtimeLock) {
return pullSystemElapsedRealtimeLocked(atomTag, data);
@@ -778,8 +786,12 @@ public class StatsPullAtomService extends SystemService {
registerEventListeners();
});
} else if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) {
- // Network stats related pullers can only be initialized after service is ready.
- BackgroundThread.getHandler().post(() -> initAndRegisterNetworkStatsPullers());
+ BackgroundThread.getHandler().post(() -> {
+ // Network stats related pullers can only be initialized after service is ready.
+ initAndRegisterNetworkStatsPullers();
+ // For services that are not ready at boot phase PHASE_SYSTEM_SERVICES_READY
+ initAndRegisterDeferredPullers();
+ });
}
}
@@ -990,6 +1002,12 @@ public class StatsPullAtomService extends SystemService {
registerOemManagedBytesTransfer();
}
+ private void initAndRegisterDeferredPullers() {
+ mUwbManager = mContext.getSystemService(UwbManager.class);
+
+ registerUwbActivityInfo();
+ }
+
private IThermalService getIThermalService() {
synchronized (mThermalLock) {
if (mThermalService == null) {
@@ -2152,6 +2170,46 @@ public class StatsPullAtomService extends SystemService {
return StatsManager.PULL_SUCCESS;
}
+ private void registerUwbActivityInfo() {
+ int tagId = FrameworkStatsLog.UWB_ACTIVITY_INFO;
+ mStatsManager.setPullAtomCallback(
+ tagId,
+ null, // use default PullAtomMetadata values
+ DIRECT_EXECUTOR,
+ mStatsCallbackImpl
+ );
+ }
+
+ int pullUwbActivityInfoLocked(int atomTag, List<StatsEvent> pulledData) {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ SynchronousResultReceiver uwbReceiver = new SynchronousResultReceiver("uwb");
+ mUwbManager.getUwbActivityEnergyInfoAsync(Runnable::run,
+ info -> {
+ Bundle bundle = new Bundle();
+ bundle.putParcelable(BatteryStats.RESULT_RECEIVER_CONTROLLER_KEY, info);
+ uwbReceiver.send(0, bundle);
+ }
+ );
+ final UwbActivityEnergyInfo uwbInfo = awaitControllerInfo(uwbReceiver);
+ if (uwbInfo == null) {
+ return StatsManager.PULL_SKIP;
+ }
+ pulledData.add(
+ FrameworkStatsLog.buildStatsEvent(atomTag,
+ uwbInfo.getControllerTxDurationMillis(),
+ uwbInfo.getControllerRxDurationMillis(),
+ uwbInfo.getControllerIdleDurationMillis(),
+ uwbInfo.getControllerWakeCount()));
+ } catch (RuntimeException e) {
+ Slog.e(TAG, "failed to getUwbActivityEnergyInfoAsync", e);
+ return StatsManager.PULL_SKIP;
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ return StatsManager.PULL_SUCCESS;
+ }
+
private void registerSystemElapsedRealtime() {
int tagId = FrameworkStatsLog.SYSTEM_ELAPSED_REALTIME;
PullAtomMetadata metadata = new PullAtomMetadata.Builder()
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index e17866922990..51872b339c40 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -1229,10 +1229,11 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
return;
}
+ // Live wallpapers always are system wallpapers unless lock screen live wp is
+ // enabled.
+ which = mIsLockscreenLiveWallpaperEnabled ? mWallpaper.mWhich : FLAG_SYSTEM;
mWallpaper.primaryColors = primaryColors;
- // Live wallpapers always are system wallpapers.
- which = FLAG_SYSTEM;
// It's also the lock screen wallpaper when we don't have a bitmap in there.
if (displayId == DEFAULT_DISPLAY) {
final WallpaperData lockedWallpaper = mLockWallpaperMap.get(mWallpaper.userId);
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index be503fc61c4c..7d220df949e0 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -82,6 +82,7 @@ import static com.android.server.wm.Task.TAG_CLEANUP;
import static com.android.server.wm.WindowContainer.POSITION_TOP;
import android.Manifest;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.Activity;
import android.app.ActivityManager;
@@ -261,6 +262,8 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks {
/** Helper for {@link Task#fillTaskInfo}. */
final TaskInfoHelper mTaskInfoHelper = new TaskInfoHelper();
+ final OpaqueActivityHelper mOpaqueActivityHelper = new OpaqueActivityHelper();
+
private final ActivityTaskSupervisorHandler mHandler;
final Looper mLooper;
@@ -2906,6 +2909,38 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks {
}
}
+ /** The helper to get the top opaque activity of a container. */
+ static class OpaqueActivityHelper implements Predicate<ActivityRecord> {
+ private ActivityRecord mStarting;
+ private boolean mIncludeInvisibleAndFinishing;
+
+ ActivityRecord getOpaqueActivity(@NonNull WindowContainer<?> container) {
+ mIncludeInvisibleAndFinishing = true;
+ return container.getActivity(this,
+ true /* traverseTopToBottom */, null /* boundary */);
+ }
+
+ ActivityRecord getVisibleOpaqueActivity(@NonNull WindowContainer<?> container,
+ @Nullable ActivityRecord starting) {
+ mStarting = starting;
+ mIncludeInvisibleAndFinishing = false;
+ final ActivityRecord opaque = container.getActivity(this,
+ true /* traverseTopToBottom */, null /* boundary */);
+ mStarting = null;
+ return opaque;
+ }
+
+ @Override
+ public boolean test(ActivityRecord r) {
+ if (!mIncludeInvisibleAndFinishing && !r.visibleIgnoringKeyguard && r != mStarting) {
+ // Ignore invisible activities that are not the currently starting activity
+ // (about to be visible).
+ return false;
+ }
+ return r.occludesParent(mIncludeInvisibleAndFinishing /* includingFinishing */);
+ }
+ }
+
/**
* Fills the info that needs to iterate all activities of task, such as the number of
* non-finishing activities and collecting launch cookies.
diff --git a/services/core/java/com/android/server/wm/BLASTSyncEngine.java b/services/core/java/com/android/server/wm/BLASTSyncEngine.java
index d916a1be1a03..7ecc0839fe53 100644
--- a/services/core/java/com/android/server/wm/BLASTSyncEngine.java
+++ b/services/core/java/com/android/server/wm/BLASTSyncEngine.java
@@ -133,7 +133,7 @@ class BLASTSyncEngine {
return mOrphanTransaction;
}
- private void onSurfacePlacement() {
+ private void tryFinish() {
if (!mReady) return;
ProtoLog.v(WM_DEBUG_SYNC_ENGINE, "SyncGroup %d: onSurfacePlacement checking %s",
mSyncId, mRootMembers);
@@ -168,14 +168,13 @@ class BLASTSyncEngine {
class CommitCallback implements Runnable {
// Can run a second time if the action completes after the timeout.
boolean ran = false;
- public void onCommitted() {
+ public void onCommitted(SurfaceControl.Transaction t) {
synchronized (mWm.mGlobalLock) {
if (ran) {
return;
}
mHandler.removeCallbacks(this);
ran = true;
- SurfaceControl.Transaction t = new SurfaceControl.Transaction();
for (WindowContainer wc : wcAwaitingCommit) {
wc.onSyncTransactionCommitted(t);
}
@@ -194,12 +193,12 @@ class BLASTSyncEngine {
Slog.e(TAG, "WM sent Transaction to organized, but never received" +
" commit callback. Application ANR likely to follow.");
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
- onCommitted();
-
+ onCommitted(merged);
}
};
CommitCallback callback = new CommitCallback();
- merged.addTransactionCommittedListener((r) -> { r.run(); }, callback::onCommitted);
+ merged.addTransactionCommittedListener(Runnable::run,
+ () -> callback.onCommitted(new SurfaceControl.Transaction()));
mHandler.postDelayed(callback, BLAST_TIMEOUT_DURATION);
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "onTransactionReady");
@@ -223,6 +222,12 @@ class BLASTSyncEngine {
}
});
}
+ // Notify idle listeners
+ for (int i = mOnIdleListeners.size() - 1; i >= 0; --i) {
+ // If an idle listener adds a sync, though, then stop notifying.
+ if (mActiveSyncs.size() > 0) break;
+ mOnIdleListeners.get(i).run();
+ }
}
private void setReady(boolean ready) {
@@ -281,6 +286,8 @@ class BLASTSyncEngine {
*/
private final ArrayList<PendingSyncSet> mPendingSyncSets = new ArrayList<>();
+ private final ArrayList<Runnable> mOnIdleListeners = new ArrayList<>();
+
BLASTSyncEngine(WindowManagerService wms) {
this(wms, wms.mH);
}
@@ -379,10 +386,15 @@ class BLASTSyncEngine {
void onSurfacePlacement() {
// backwards since each state can remove itself if finished
for (int i = mActiveSyncs.size() - 1; i >= 0; --i) {
- mActiveSyncs.valueAt(i).onSurfacePlacement();
+ mActiveSyncs.valueAt(i).tryFinish();
}
}
+ /** Only use this for tests! */
+ void tryFinishForTest(int syncId) {
+ getSyncSet(syncId).tryFinish();
+ }
+
/**
* Queues a sync operation onto this engine. It will wait until any current/prior sync-sets
* have finished to run. This is needed right now because currently {@link BLASTSyncEngine}
@@ -409,4 +421,8 @@ class BLASTSyncEngine {
boolean hasPendingSyncSets() {
return !mPendingSyncSets.isEmpty();
}
+
+ void addOnIdleListener(Runnable onIdleListener) {
+ mOnIdleListeners.add(onIdleListener);
+ }
}
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index bad64d357b13..76d6951c438e 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -1858,7 +1858,8 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
return false;
}
if (mLastWallpaperVisible && r.windowsCanBeWallpaperTarget()
- && mFixedRotationTransitionListener.mAnimatingRecents == null) {
+ && mFixedRotationTransitionListener.mAnimatingRecents == null
+ && !mTransitionController.isTransientLaunch(r)) {
// Use normal rotation animation for orientation change of visible wallpaper if recents
// animation is not running (it may be swiping to home).
return false;
diff --git a/services/core/java/com/android/server/wm/Letterbox.java b/services/core/java/com/android/server/wm/Letterbox.java
index 3d006868c79c..35513707ebc5 100644
--- a/services/core/java/com/android/server/wm/Letterbox.java
+++ b/services/core/java/com/android/server/wm/Letterbox.java
@@ -18,6 +18,7 @@ package com.android.server.wm;
import static android.os.InputConstants.DEFAULT_DISPATCHING_TIMEOUT_MILLIS;
import static android.view.SurfaceControl.HIDDEN;
+import static android.window.TaskConstants.TASK_CHILD_LAYER_LETTERBOX_BACKGROUND;
import android.content.Context;
import android.graphics.Color;
@@ -361,7 +362,8 @@ public class Letterbox {
.setCallsite("LetterboxSurface.createSurface")
.build();
- t.setLayer(mSurface, -1).setColorSpaceAgnostic(mSurface, true);
+ t.setLayer(mSurface, TASK_CHILD_LAYER_LETTERBOX_BACKGROUND)
+ .setColorSpaceAgnostic(mSurface, true);
}
void attachInput(WindowState win) {
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 5c33e6470024..99d3cc0737d1 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -5652,7 +5652,7 @@ class Task extends TaskFragment {
(deferred) -> {
// Need to check again if deferred since the system might
// be in a different state.
- if (deferred && !canMoveTaskToBack(tr)) {
+ if (!isAttached() || (deferred && !canMoveTaskToBack(tr))) {
Slog.e(TAG, "Failed to move task to back after saying we could: "
+ tr.mTaskId);
transition.abort();
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 311b9a6d2876..3f7ab14d02be 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -102,8 +102,6 @@ import android.window.TaskFragmentOrganizerToken;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.common.ProtoLog;
-import com.android.internal.util.function.pooled.PooledLambda;
-import com.android.internal.util.function.pooled.PooledPredicate;
import com.android.server.am.HostingRecord;
import com.android.server.pm.pkg.AndroidPackage;
@@ -934,11 +932,10 @@ class TaskFragment extends WindowContainer<WindowContainer> {
if (!isAttached() || isForceHidden() || isForceTranslucent()) {
return true;
}
- final PooledPredicate p = PooledLambda.obtainPredicate(TaskFragment::isOpaqueActivity,
- PooledLambda.__(ActivityRecord.class), starting, false /* including*/);
- final ActivityRecord opaque = getActivity(p);
- p.recycle();
- return opaque == null;
+ // A TaskFragment isn't translucent if it has at least one visible activity that occludes
+ // this TaskFragment.
+ return mTaskSupervisor.mOpaqueActivityHelper.getVisibleOpaqueActivity(this,
+ starting) == null;
}
/**
@@ -951,25 +948,7 @@ class TaskFragment extends WindowContainer<WindowContainer> {
return true;
}
// Including finishing Activity if the TaskFragment is becoming invisible in the transition.
- final boolean includingFinishing = !isVisibleRequested();
- final PooledPredicate p = PooledLambda.obtainPredicate(TaskFragment::isOpaqueActivity,
- PooledLambda.__(ActivityRecord.class), null /* starting */, includingFinishing);
- final ActivityRecord opaque = getActivity(p);
- p.recycle();
- return opaque == null;
- }
-
- private static boolean isOpaqueActivity(@NonNull ActivityRecord r,
- @Nullable ActivityRecord starting, boolean includingFinishing) {
- if (!r.visibleIgnoringKeyguard && r != starting) {
- // Also ignore invisible activities that are not the currently starting
- // activity (about to be visible).
- return false;
- }
-
- // TaskFragment isn't translucent if it has at least one fullscreen activity that is
- // visible.
- return r.occludesParent(includingFinishing);
+ return mTaskSupervisor.mOpaqueActivityHelper.getOpaqueActivity(this) == null;
}
ActivityRecord getTopNonFinishingActivity() {
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 76b0e7b82ba6..452bd6d7b347 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -104,7 +104,8 @@ import java.util.Objects;
import java.util.function.Predicate;
/**
- * Represents a logical transition.
+ * Represents a logical transition. This keeps track of all the changes associated with a logical
+ * WM state -> state transition.
* @see TransitionController
*/
class Transition implements BLASTSyncEngine.TransactionReadyListener {
@@ -416,6 +417,10 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
return mFinishTransaction;
}
+ boolean isPending() {
+ return mState == STATE_PENDING;
+ }
+
boolean isCollecting() {
return mState == STATE_COLLECTING || mState == STATE_STARTED;
}
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index cbb4fe2eaa21..0ae9c4c41972 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -51,6 +51,7 @@ import android.window.TransitionRequestInfo;
import android.window.WindowContainerTransaction;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.ProtoLogGroup;
import com.android.internal.protolog.common.ProtoLog;
import com.android.server.FgThread;
@@ -88,6 +89,7 @@ class TransitionController {
private WindowProcessController mTransitionPlayerProc;
final ActivityTaskManagerService mAtm;
+ BLASTSyncEngine mSyncEngine;
final RemotePlayer mRemotePlayer;
SnapshotController mSnapshotController;
@@ -121,6 +123,26 @@ class TransitionController {
private final IBinder.DeathRecipient mTransitionPlayerDeath;
+ static class QueuedTransition {
+ final Transition mTransition;
+ final OnStartCollect mOnStartCollect;
+ final BLASTSyncEngine.SyncGroup mLegacySync;
+
+ QueuedTransition(Transition transition, OnStartCollect onStartCollect) {
+ mTransition = transition;
+ mOnStartCollect = onStartCollect;
+ mLegacySync = null;
+ }
+
+ QueuedTransition(BLASTSyncEngine.SyncGroup legacySync, OnStartCollect onStartCollect) {
+ mTransition = null;
+ mOnStartCollect = onStartCollect;
+ mLegacySync = legacySync;
+ }
+ }
+
+ private final ArrayList<QueuedTransition> mQueuedTransitions = new ArrayList<>();
+
/** The transition currently being constructed (collecting participants). */
private Transition mCollectingTransition = null;
@@ -158,6 +180,14 @@ class TransitionController {
mTransitionTracer = wms.mTransitionTracer;
mIsWaitingForDisplayEnabled = !wms.mDisplayEnabled;
registerLegacyListener(wms.mActivityManagerAppTransitionNotifier);
+ setSyncEngine(wms.mSyncEngine);
+ }
+
+ @VisibleForTesting
+ void setSyncEngine(BLASTSyncEngine syncEngine) {
+ mSyncEngine = syncEngine;
+ // Check the queue whenever the sync-engine becomes idle.
+ mSyncEngine.addOnIdleListener(this::tryStartCollectFromQueue);
}
private void detachPlayer() {
@@ -195,7 +225,7 @@ class TransitionController {
throw new IllegalStateException("Simultaneous transition collection not supported"
+ " yet. Use {@link #createPendingTransition} for explicit queueing.");
}
- Transition transit = new Transition(type, flags, this, mAtm.mWindowManager.mSyncEngine);
+ Transition transit = new Transition(type, flags, this, mSyncEngine);
ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Creating Transition: %s", transit);
moveToCollecting(transit);
return transit;
@@ -325,7 +355,7 @@ class TransitionController {
/** @return {@code true} if a transition is running */
boolean inTransition() {
// TODO(shell-transitions): eventually properly support multiple
- return isCollecting() || isPlaying();
+ return isCollecting() || isPlaying() || !mQueuedTransitions.isEmpty();
}
/** @return {@code true} if a transition is running in a participant subtree of wc */
@@ -453,8 +483,7 @@ class TransitionController {
// some frames before and after the display projection transaction is applied by the
// remote player. That may cause some buffers to show in different rotation. So use
// sync method to pause clients drawing until the projection transaction is applied.
- mAtm.mWindowManager.mSyncEngine.setSyncMethod(displayTransition.getSyncId(),
- BLASTSyncEngine.METHOD_BLAST);
+ mSyncEngine.setSyncMethod(displayTransition.getSyncId(), BLASTSyncEngine.METHOD_BLAST);
}
final Rect startBounds = displayChange.getStartAbsBounds();
final Rect endBounds = displayChange.getEndAbsBounds();
@@ -741,6 +770,31 @@ class TransitionController {
mStateValidators.clear();
}
+ void tryStartCollectFromQueue() {
+ if (mQueuedTransitions.isEmpty()) return;
+ // Only need to try the next one since, even when transition can collect in parallel,
+ // they still need to serialize on readiness.
+ final QueuedTransition queued = mQueuedTransitions.get(0);
+ if (mCollectingTransition != null || mSyncEngine.hasActiveSync()) {
+ return;
+ }
+ mQueuedTransitions.remove(0);
+ // This needs to happen immediately to prevent another sync from claiming the syncset
+ // out-of-order (moveToCollecting calls startSyncSet)
+ if (queued.mTransition != null) {
+ moveToCollecting(queued.mTransition);
+ } else {
+ // legacy sync
+ mSyncEngine.startSyncSet(queued.mLegacySync);
+ }
+ // Post this so that the now-playing transition logic isn't interrupted.
+ mAtm.mH.post(() -> {
+ synchronized (mAtm.mGlobalLock) {
+ queued.mOnStartCollect.onCollectStarted(true /* deferred */);
+ }
+ });
+ }
+
void moveToPlaying(Transition transition) {
if (transition != mCollectingTransition) {
throw new IllegalStateException("Trying to move non-collecting transition to playing");
@@ -749,6 +803,7 @@ class TransitionController {
mPlayingTransitions.add(transition);
updateRunningRemoteAnimation(transition, true /* isPlaying */);
mTransitionTracer.logState(transition);
+ // Sync engine should become idle after this, so the idle listener will check the queue.
}
void updateAnimatingState(SurfaceControl.Transaction t) {
@@ -758,12 +813,12 @@ class TransitionController {
t.setEarlyWakeupStart();
// Usually transitions put quite a load onto the system already (with all the things
// happening in app), so pause task snapshot persisting to not increase the load.
- mAtm.mWindowManager.mSnapshotController.setPause(true);
+ mSnapshotController.setPause(true);
mAnimatingState = true;
Transition.asyncTraceBegin("animating", 0x41bfaf1 /* hashcode of TAG */);
} else if (!animatingState && mAnimatingState) {
t.setEarlyWakeupEnd();
- mAtm.mWindowManager.mSnapshotController.setPause(false);
+ mSnapshotController.setPause(false);
mAnimatingState = false;
Transition.asyncTraceEnd(0x41bfaf1 /* hashcode of TAG */);
}
@@ -793,6 +848,7 @@ class TransitionController {
transition.abort();
mCollectingTransition = null;
mTransitionTracer.logState(transition);
+ // abort will call through the normal finish paths and thus check the queue.
}
/**
@@ -874,7 +930,7 @@ class TransitionController {
if (!mPlayingTransitions.isEmpty()) {
state = LEGACY_STATE_RUNNING;
} else if ((mCollectingTransition != null && mCollectingTransition.getLegacyIsReady())
- || mAtm.mWindowManager.mSyncEngine.hasPendingSyncSets()) {
+ || mSyncEngine.hasPendingSyncSets()) {
// The transition may not be "ready", but we have a sync-transaction waiting to start.
// Usually the pending transaction is for a transition, so assuming that is the case,
// we can't be IDLE for test purposes. Ideally, we should have a STATE_COLLECTING.
@@ -885,25 +941,43 @@ class TransitionController {
}
/** Returns {@code true} if it started collecting, {@code false} if it was queued. */
+ private void queueTransition(Transition transit, OnStartCollect onStartCollect) {
+ mQueuedTransitions.add(new QueuedTransition(transit, onStartCollect));
+ ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS_MIN,
+ "Queueing transition: %s", transit);
+ }
+
+ /** Returns {@code true} if it started collecting, {@code false} if it was queued. */
boolean startCollectOrQueue(Transition transit, OnStartCollect onStartCollect) {
- if (mAtm.mWindowManager.mSyncEngine.hasActiveSync()) {
+ if (!mQueuedTransitions.isEmpty()) {
+ // Just add to queue since we already have a queue.
+ queueTransition(transit, onStartCollect);
+ return false;
+ }
+ if (mSyncEngine.hasActiveSync()) {
if (!isCollecting()) {
Slog.w(TAG, "Ongoing Sync outside of transition.");
}
+ queueTransition(transit, onStartCollect);
+ return false;
+ }
+ moveToCollecting(transit);
+ onStartCollect.onCollectStarted(false /* deferred */);
+ return true;
+ }
+
+ /** Returns {@code true} if it started collecting, {@code false} if it was queued. */
+ boolean startLegacySyncOrQueue(BLASTSyncEngine.SyncGroup syncGroup, Runnable applySync) {
+ if (!mQueuedTransitions.isEmpty() || mSyncEngine.hasActiveSync()) {
+ // Just add to queue since we already have a queue.
+ mQueuedTransitions.add(new QueuedTransition(syncGroup, (d) -> applySync.run()));
ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS_MIN,
- "Queueing transition: %s", transit);
- mAtm.mWindowManager.mSyncEngine.queueSyncSet(
- // Make sure to collect immediately to prevent another transition
- // from sneaking in before it. Note: moveToCollecting internally
- // calls startSyncSet.
- () -> moveToCollecting(transit),
- () -> onStartCollect.onCollectStarted(true /* deferred */));
+ "Queueing legacy sync-set: %s", syncGroup.mSyncId);
return false;
- } else {
- moveToCollecting(transit);
- onStartCollect.onCollectStarted(false /* deferred */);
- return true;
}
+ mSyncEngine.startSyncSet(syncGroup);
+ applySync.run();
+ return true;
}
interface OnStartCollect {
diff --git a/services/core/java/com/android/server/wm/TransitionTracer.java b/services/core/java/com/android/server/wm/TransitionTracer.java
index a4c931c17a66..6597d4c7f916 100644
--- a/services/core/java/com/android/server/wm/TransitionTracer.java
+++ b/services/core/java/com/android/server/wm/TransitionTracer.java
@@ -154,6 +154,7 @@ public class TransitionTracer {
}
outputStream.write(com.android.server.wm.shell.Transition.TYPE, transition.mType);
+ outputStream.write(com.android.server.wm.shell.Transition.FLAGS, transition.getFlags());
for (int i = 0; i < targets.size(); ++i) {
final long changeToken = outputStream
@@ -162,6 +163,7 @@ public class TransitionTracer {
final Transition.ChangeInfo target = targets.get(i);
final int mode = target.getTransitMode(target.mContainer);
+ final int flags = target.getChangeFlags(target.mContainer);
final int layerId;
if (target.mContainer.mSurfaceControl.isValid()) {
layerId = target.mContainer.mSurfaceControl.getLayerId();
@@ -170,6 +172,7 @@ public class TransitionTracer {
}
outputStream.write(com.android.server.wm.shell.Target.MODE, mode);
+ outputStream.write(com.android.server.wm.shell.Target.FLAGS, flags);
outputStream.write(com.android.server.wm.shell.Target.LAYER_ID, layerId);
if (mActiveTracingEnabled) {
diff --git a/services/core/java/com/android/server/wm/WallpaperWindowToken.java b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
index 6c38c6fb7ac4..1ffee05d20ec 100644
--- a/services/core/java/com/android/server/wm/WallpaperWindowToken.java
+++ b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
@@ -129,26 +129,10 @@ class WallpaperWindowToken extends WindowToken {
}
void updateWallpaperWindows(boolean visible) {
- boolean changed = false;
if (mVisibleRequested != visible) {
ProtoLog.d(WM_DEBUG_WALLPAPER, "Wallpaper token %s visible=%b",
token, visible);
setVisibility(visible);
- changed = true;
- }
- if (mTransitionController.isShellTransitionsEnabled()) {
- // Apply legacy fixed rotation to wallpaper if it is becoming visible
- if (!mTransitionController.useShellTransitionsRotation() && changed && visible) {
- final WindowState wallpaperTarget =
- mDisplayContent.mWallpaperController.getWallpaperTarget();
- if (wallpaperTarget != null && wallpaperTarget.mToken.hasFixedRotationTransform()) {
- linkFixedRotationTransform(wallpaperTarget.mToken);
- }
- }
- // If wallpaper is in transition, setVisible() will be called from commitVisibility()
- // when finishing transition. Otherwise commitVisibility() is already called from above
- // setVisibility().
- return;
}
final WindowState wallpaperTarget =
@@ -172,6 +156,12 @@ class WallpaperWindowToken extends WindowToken {
linkFixedRotationTransform(wallpaperTarget.mToken);
}
}
+ if (mTransitionController.isShellTransitionsEnabled()) {
+ // If wallpaper is in transition, setVisible() will be called from commitVisibility()
+ // when finishing transition. Otherwise commitVisibility() is already called from above
+ // setVisibility().
+ return;
+ }
setVisible(visible);
}
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index ee86b97e9404..cd42528ad79b 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -231,19 +231,26 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
*/
final BLASTSyncEngine.SyncGroup syncGroup = prepareSyncWithOrganizer(callback);
final int syncId = syncGroup.mSyncId;
- if (!mService.mWindowManager.mSyncEngine.hasActiveSync()) {
- mService.mWindowManager.mSyncEngine.startSyncSet(syncGroup);
- applyTransaction(t, syncId, null /*transition*/, caller);
- setSyncReady(syncId);
+ if (mTransitionController.isShellTransitionsEnabled()) {
+ mTransitionController.startLegacySyncOrQueue(syncGroup, () -> {
+ applyTransaction(t, syncId, null /*transition*/, caller);
+ setSyncReady(syncId);
+ });
} else {
- // Because the BLAST engine only supports one sync at a time, queue the
- // transaction.
- mService.mWindowManager.mSyncEngine.queueSyncSet(
- () -> mService.mWindowManager.mSyncEngine.startSyncSet(syncGroup),
- () -> {
- applyTransaction(t, syncId, null /*transition*/, caller);
- setSyncReady(syncId);
- });
+ if (!mService.mWindowManager.mSyncEngine.hasActiveSync()) {
+ mService.mWindowManager.mSyncEngine.startSyncSet(syncGroup);
+ applyTransaction(t, syncId, null /*transition*/, caller);
+ setSyncReady(syncId);
+ } else {
+ // Because the BLAST engine only supports one sync at a time, queue the
+ // transaction.
+ mService.mWindowManager.mSyncEngine.queueSyncSet(
+ () -> mService.mWindowManager.mSyncEngine.startSyncSet(syncGroup),
+ () -> {
+ applyTransaction(t, syncId, null /*transition*/, caller);
+ setSyncReady(syncId);
+ });
+ }
}
return syncId;
}
diff --git a/services/core/jni/com_android_server_companion_virtual_InputController.cpp b/services/core/jni/com_android_server_companion_virtual_InputController.cpp
index 4898d95b8e4b..ad098b757eae 100644
--- a/services/core/jni/com_android_server_companion_virtual_InputController.cpp
+++ b/services/core/jni/com_android_server_companion_virtual_InputController.cpp
@@ -233,44 +233,50 @@ static void nativeCloseUinput(JNIEnv* env, jobject thiz, jlong ptr) {
// Native methods for VirtualDpad
static bool nativeWriteDpadKeyEvent(JNIEnv* env, jobject thiz, jlong ptr, jint androidKeyCode,
- jint action) {
+ jint action, jlong eventTimeNanos) {
VirtualDpad* virtualDpad = reinterpret_cast<VirtualDpad*>(ptr);
- return virtualDpad->writeDpadKeyEvent(androidKeyCode, action);
+ return virtualDpad->writeDpadKeyEvent(androidKeyCode, action,
+ std::chrono::nanoseconds(eventTimeNanos));
}
// Native methods for VirtualKeyboard
static bool nativeWriteKeyEvent(JNIEnv* env, jobject thiz, jlong ptr, jint androidKeyCode,
- jint action) {
+ jint action, jlong eventTimeNanos) {
VirtualKeyboard* virtualKeyboard = reinterpret_cast<VirtualKeyboard*>(ptr);
- return virtualKeyboard->writeKeyEvent(androidKeyCode, action);
+ return virtualKeyboard->writeKeyEvent(androidKeyCode, action,
+ std::chrono::nanoseconds(eventTimeNanos));
}
// Native methods for VirtualTouchscreen
static bool nativeWriteTouchEvent(JNIEnv* env, jobject thiz, jlong ptr, jint pointerId,
jint toolType, jint action, jfloat locationX, jfloat locationY,
- jfloat pressure, jfloat majorAxisSize) {
+ jfloat pressure, jfloat majorAxisSize, jlong eventTimeNanos) {
VirtualTouchscreen* virtualTouchscreen = reinterpret_cast<VirtualTouchscreen*>(ptr);
return virtualTouchscreen->writeTouchEvent(pointerId, toolType, action, locationX, locationY,
- pressure, majorAxisSize);
+ pressure, majorAxisSize,
+ std::chrono::nanoseconds(eventTimeNanos));
}
// Native methods for VirtualMouse
static bool nativeWriteButtonEvent(JNIEnv* env, jobject thiz, jlong ptr, jint buttonCode,
- jint action) {
+ jint action, jlong eventTimeNanos) {
VirtualMouse* virtualMouse = reinterpret_cast<VirtualMouse*>(ptr);
- return virtualMouse->writeButtonEvent(buttonCode, action);
+ return virtualMouse->writeButtonEvent(buttonCode, action,
+ std::chrono::nanoseconds(eventTimeNanos));
}
static bool nativeWriteRelativeEvent(JNIEnv* env, jobject thiz, jlong ptr, jfloat relativeX,
- jfloat relativeY) {
+ jfloat relativeY, jlong eventTimeNanos) {
VirtualMouse* virtualMouse = reinterpret_cast<VirtualMouse*>(ptr);
- return virtualMouse->writeRelativeEvent(relativeX, relativeY);
+ return virtualMouse->writeRelativeEvent(relativeX, relativeY,
+ std::chrono::nanoseconds(eventTimeNanos));
}
static bool nativeWriteScrollEvent(JNIEnv* env, jobject thiz, jlong ptr, jfloat xAxisMovement,
- jfloat yAxisMovement) {
+ jfloat yAxisMovement, jlong eventTimeNanos) {
VirtualMouse* virtualMouse = reinterpret_cast<VirtualMouse*>(ptr);
- return virtualMouse->writeScrollEvent(xAxisMovement, yAxisMovement);
+ return virtualMouse->writeScrollEvent(xAxisMovement, yAxisMovement,
+ std::chrono::nanoseconds(eventTimeNanos));
}
static JNINativeMethod methods[] = {
@@ -283,12 +289,12 @@ static JNINativeMethod methods[] = {
{"nativeOpenUinputTouchscreen", "(Ljava/lang/String;IILjava/lang/String;II)J",
(void*)nativeOpenUinputTouchscreen},
{"nativeCloseUinput", "(J)V", (void*)nativeCloseUinput},
- {"nativeWriteDpadKeyEvent", "(JII)Z", (void*)nativeWriteDpadKeyEvent},
- {"nativeWriteKeyEvent", "(JII)Z", (void*)nativeWriteKeyEvent},
- {"nativeWriteButtonEvent", "(JII)Z", (void*)nativeWriteButtonEvent},
- {"nativeWriteTouchEvent", "(JIIIFFFF)Z", (void*)nativeWriteTouchEvent},
- {"nativeWriteRelativeEvent", "(JFF)Z", (void*)nativeWriteRelativeEvent},
- {"nativeWriteScrollEvent", "(JFF)Z", (void*)nativeWriteScrollEvent},
+ {"nativeWriteDpadKeyEvent", "(JIIJ)Z", (void*)nativeWriteDpadKeyEvent},
+ {"nativeWriteKeyEvent", "(JIIJ)Z", (void*)nativeWriteKeyEvent},
+ {"nativeWriteButtonEvent", "(JIIJ)Z", (void*)nativeWriteButtonEvent},
+ {"nativeWriteTouchEvent", "(JIIIFFFFJ)Z", (void*)nativeWriteTouchEvent},
+ {"nativeWriteRelativeEvent", "(JFFJ)Z", (void*)nativeWriteRelativeEvent},
+ {"nativeWriteScrollEvent", "(JFFJ)Z", (void*)nativeWriteScrollEvent},
};
int register_android_server_companion_virtual_InputController(JNIEnv* env) {
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index 439ad76ced22..cf57b339cc22 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -529,7 +529,7 @@ void NativeInputManager::setDisplayViewports(JNIEnv* env, jobjectArray viewportO
} // release lock
mInputManager->getReader().requestRefreshConfiguration(
- InputReaderConfiguration::CHANGE_DISPLAY_INFO);
+ InputReaderConfiguration::Change::DISPLAY_INFO);
}
base::Result<std::unique_ptr<InputChannel>> NativeInputManager::createInputChannel(
@@ -1079,7 +1079,7 @@ void NativeInputManager::setPointerDisplayId(int32_t displayId) {
} // release lock
mInputManager->getReader().requestRefreshConfiguration(
- InputReaderConfiguration::CHANGE_DISPLAY_INFO);
+ InputReaderConfiguration::Change::DISPLAY_INFO);
}
void NativeInputManager::setPointerSpeed(int32_t speed) {
@@ -1095,7 +1095,7 @@ void NativeInputManager::setPointerSpeed(int32_t speed) {
} // release lock
mInputManager->getReader().requestRefreshConfiguration(
- InputReaderConfiguration::CHANGE_POINTER_SPEED);
+ InputReaderConfiguration::Change::POINTER_SPEED);
}
void NativeInputManager::setPointerAcceleration(float acceleration) {
@@ -1111,7 +1111,7 @@ void NativeInputManager::setPointerAcceleration(float acceleration) {
} // release lock
mInputManager->getReader().requestRefreshConfiguration(
- InputReaderConfiguration::CHANGE_POINTER_SPEED);
+ InputReaderConfiguration::Change::POINTER_SPEED);
}
void NativeInputManager::setTouchpadPointerSpeed(int32_t speed) {
@@ -1127,7 +1127,7 @@ void NativeInputManager::setTouchpadPointerSpeed(int32_t speed) {
} // release lock
mInputManager->getReader().requestRefreshConfiguration(
- InputReaderConfiguration::CHANGE_TOUCHPAD_SETTINGS);
+ InputReaderConfiguration::Change::TOUCHPAD_SETTINGS);
}
void NativeInputManager::setTouchpadNaturalScrollingEnabled(bool enabled) {
@@ -1143,7 +1143,7 @@ void NativeInputManager::setTouchpadNaturalScrollingEnabled(bool enabled) {
} // release lock
mInputManager->getReader().requestRefreshConfiguration(
- InputReaderConfiguration::CHANGE_TOUCHPAD_SETTINGS);
+ InputReaderConfiguration::Change::TOUCHPAD_SETTINGS);
}
void NativeInputManager::setTouchpadTapToClickEnabled(bool enabled) {
@@ -1159,7 +1159,7 @@ void NativeInputManager::setTouchpadTapToClickEnabled(bool enabled) {
} // release lock
mInputManager->getReader().requestRefreshConfiguration(
- InputReaderConfiguration::CHANGE_TOUCHPAD_SETTINGS);
+ InputReaderConfiguration::Change::TOUCHPAD_SETTINGS);
}
void NativeInputManager::setTouchpadRightClickZoneEnabled(bool enabled) {
@@ -1175,7 +1175,7 @@ void NativeInputManager::setTouchpadRightClickZoneEnabled(bool enabled) {
} // release lock
mInputManager->getReader().requestRefreshConfiguration(
- InputReaderConfiguration::CHANGE_TOUCHPAD_SETTINGS);
+ InputReaderConfiguration::Change::TOUCHPAD_SETTINGS);
}
void NativeInputManager::setInputDeviceEnabled(uint32_t deviceId, bool enabled) {
@@ -1193,7 +1193,7 @@ void NativeInputManager::setInputDeviceEnabled(uint32_t deviceId, bool enabled)
} // release lock
mInputManager->getReader().requestRefreshConfiguration(
- InputReaderConfiguration::CHANGE_ENABLED_STATE);
+ InputReaderConfiguration::Change::ENABLED_STATE);
}
void NativeInputManager::setShowTouches(bool enabled) {
@@ -1209,7 +1209,7 @@ void NativeInputManager::setShowTouches(bool enabled) {
} // release lock
mInputManager->getReader().requestRefreshConfiguration(
- InputReaderConfiguration::CHANGE_SHOW_TOUCHES);
+ InputReaderConfiguration::Change::SHOW_TOUCHES);
}
void NativeInputManager::requestPointerCapture(const sp<IBinder>& windowToken, bool enabled) {
@@ -1222,7 +1222,7 @@ void NativeInputManager::setInteractive(bool interactive) {
void NativeInputManager::reloadCalibration() {
mInputManager->getReader().requestRefreshConfiguration(
- InputReaderConfiguration::CHANGE_TOUCH_AFFINE_TRANSFORMATION);
+ InputReaderConfiguration::Change::TOUCH_AFFINE_TRANSFORMATION);
}
void NativeInputManager::setPointerIconType(PointerIconStyle iconId) {
@@ -1516,7 +1516,7 @@ void NativeInputManager::setPointerCapture(const PointerCaptureRequest& request)
} // release lock
mInputManager->getReader().requestRefreshConfiguration(
- InputReaderConfiguration::CHANGE_POINTER_CAPTURE);
+ InputReaderConfiguration::Change::POINTER_CAPTURE);
}
void NativeInputManager::loadPointerIcon(SpriteIcon* icon, int32_t displayId) {
@@ -1626,7 +1626,7 @@ void NativeInputManager::setStylusButtonMotionEventsEnabled(bool enabled) {
} // release lock
mInputManager->getReader().requestRefreshConfiguration(
- InputReaderConfiguration::CHANGE_STYLUS_BUTTON_REPORTING);
+ InputReaderConfiguration::Change::STYLUS_BUTTON_REPORTING);
}
FloatPoint NativeInputManager::getMouseCursorPosition() {
@@ -1649,7 +1649,7 @@ void NativeInputManager::setStylusPointerIconEnabled(bool enabled) {
} // release lock
mInputManager->getReader().requestRefreshConfiguration(
- InputReaderConfiguration::CHANGE_DISPLAY_INFO);
+ InputReaderConfiguration::Change::DISPLAY_INFO);
}
// ----------------------------------------------------------------------------
@@ -2300,14 +2300,14 @@ static void nativeReloadKeyboardLayouts(JNIEnv* env, jobject nativeImplObj) {
NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
im->getInputManager()->getReader().requestRefreshConfiguration(
- InputReaderConfiguration::CHANGE_KEYBOARD_LAYOUTS);
+ InputReaderConfiguration::Change::KEYBOARD_LAYOUTS);
}
static void nativeReloadDeviceAliases(JNIEnv* env, jobject nativeImplObj) {
NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
im->getInputManager()->getReader().requestRefreshConfiguration(
- InputReaderConfiguration::CHANGE_DEVICE_ALIAS);
+ InputReaderConfiguration::Change::DEVICE_ALIAS);
}
static void nativeSysfsNodeChanged(JNIEnv* env, jobject nativeImplObj, jstring path) {
@@ -2403,7 +2403,7 @@ static jboolean nativeCanDispatchToDisplay(JNIEnv* env, jobject nativeImplObj, j
static void nativeNotifyPortAssociationsChanged(JNIEnv* env, jobject nativeImplObj) {
NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
im->getInputManager()->getReader().requestRefreshConfiguration(
- InputReaderConfiguration::CHANGE_DISPLAY_INFO);
+ InputReaderConfiguration::Change::DISPLAY_INFO);
}
static void nativeSetDisplayEligibilityForPointerCapture(JNIEnv* env, jobject nativeImplObj,
@@ -2416,19 +2416,19 @@ static void nativeSetDisplayEligibilityForPointerCapture(JNIEnv* env, jobject na
static void nativeChangeUniqueIdAssociation(JNIEnv* env, jobject nativeImplObj) {
NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
im->getInputManager()->getReader().requestRefreshConfiguration(
- InputReaderConfiguration::CHANGE_DISPLAY_INFO);
+ InputReaderConfiguration::Change::DISPLAY_INFO);
}
static void nativeChangeTypeAssociation(JNIEnv* env, jobject nativeImplObj) {
NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
im->getInputManager()->getReader().requestRefreshConfiguration(
- InputReaderConfiguration::CHANGE_DEVICE_TYPE);
+ InputReaderConfiguration::Change::DEVICE_TYPE);
}
static void changeKeyboardLayoutAssociation(JNIEnv* env, jobject nativeImplObj) {
NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
im->getInputManager()->getReader().requestRefreshConfiguration(
- InputReaderConfiguration::CHANGE_KEYBOARD_LAYOUT_ASSOCIATION);
+ InputReaderConfiguration::Change::KEYBOARD_LAYOUT_ASSOCIATION);
}
static void nativeSetMotionClassifierEnabled(JNIEnv* env, jobject nativeImplObj, jboolean enabled) {
diff --git a/services/credentials/java/com/android/server/credentials/MetricUtilities.java b/services/credentials/java/com/android/server/credentials/MetricUtilities.java
index e25614846896..47b45ac1d53d 100644
--- a/services/credentials/java/com/android/server/credentials/MetricUtilities.java
+++ b/services/credentials/java/com/android/server/credentials/MetricUtilities.java
@@ -43,7 +43,9 @@ public class MetricUtilities {
public static final String USER_CANCELED_SUBSTRING = "TYPE_USER_CANCELED";
public static final int DEFAULT_INT_32 = -1;
+ public static final String DEFAULT_STRING = "";
public static final int[] DEFAULT_REPEATED_INT_32 = new int[0];
+ public static final String[] DEFAULT_REPEATED_STR = new String[0];
// Used for single count metric emits, such as singular amounts of various types
public static final int UNIT = 1;
// Used for zero count metric emits, such as zero amounts of various types
@@ -143,7 +145,12 @@ public class MetricUtilities {
finalPhaseMetric.getAuthenticationEntryCount(),
/* clicked_entries */ browsedClickedEntries,
/* provider_of_clicked_entry */ browsedProviderUid,
- /* api_status */ apiStatus
+ /* api_status */ apiStatus,
+ DEFAULT_REPEATED_INT_32,
+ DEFAULT_REPEATED_INT_32,
+ DEFAULT_REPEATED_STR,
+ DEFAULT_REPEATED_INT_32,
+ DEFAULT_STRING
);
} catch (Exception e) {
Log.w(TAG, "Unexpected error during metric logging: " + e);
@@ -222,7 +229,11 @@ public class MetricUtilities {
/* candidate_provider_credential_entry_type_count */
candidateCredentialTypeCountList,
/* candidate_provider_remote_entry_count */ candidateRemoteEntryCountList,
- /* candidate_provider_authentication_entry_count */ candidateAuthEntryCountList
+ /* candidate_provider_authentication_entry_count */ candidateAuthEntryCountList,
+ DEFAULT_REPEATED_STR,
+ false,
+ DEFAULT_REPEATED_STR,
+ DEFAULT_REPEATED_INT_32
);
} catch (Exception e) {
Log.w(TAG, "Unexpected error during metric logging: " + e);
@@ -285,10 +296,12 @@ public class MetricUtilities {
/* initial_timestamp_reference_nanoseconds */
initialPhaseMetric.getCredentialServiceStartedTimeNanoseconds(),
/* count_credential_request_classtypes */
- initialPhaseMetric.getCountRequestClassType()
+ initialPhaseMetric.getCountRequestClassType(),
// TODO(b/271135048) - add total count of request options
// TODO(b/271135048) - Uncomment once built past PWG review -
- // initialPhaseMetric.isOriginSpecified()
+ DEFAULT_REPEATED_STR,
+ DEFAULT_REPEATED_INT_32,
+ initialPhaseMetric.isOriginSpecified()
);
} catch (Exception e) {
Log.w(TAG, "Unexpected error during metric logging: " + e);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 7e5d5aae06e4..4d739d2c3685 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -160,6 +160,7 @@ import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC;
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX;
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_SOMETHING;
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
+import static android.app.admin.DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT;
import static android.app.admin.DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED;
import static android.app.admin.DevicePolicyManager.PERSONAL_APPS_NOT_SUSPENDED;
import static android.app.admin.DevicePolicyManager.PERSONAL_APPS_SUSPENDED_EXPLICITLY;
@@ -533,7 +534,6 @@ import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
@@ -1186,9 +1186,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
// Resume logging if all remaining users are affiliated.
maybeResumeDeviceWideLoggingLocked();
}
- if (isPolicyEngineForFinanceFlagEnabled() || isPermissionCheckFlagEnabled()) {
- mDevicePolicyEngine.handleUserRemoved(userHandle);
- }
+ }
+ if (isPolicyEngineForFinanceFlagEnabled() || isPermissionCheckFlagEnabled()) {
+ mDevicePolicyEngine.handleUserRemoved(userHandle);
}
} else if (Intent.ACTION_USER_STARTED.equals(action)) {
sendDeviceOwnerUserCommand(DeviceAdminReceiver.ACTION_USER_STARTED, userHandle);
@@ -4157,8 +4157,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_REMOVE_ACTIVE_ADMIN);
enforceUserUnlocked(userHandle);
+ ActiveAdmin admin;
synchronized (getLockObject()) {
- ActiveAdmin admin = getActiveAdminUncheckedLocked(adminReceiver, userHandle);
+ admin = getActiveAdminUncheckedLocked(adminReceiver, userHandle);
if (admin == null) {
return;
}
@@ -4169,14 +4170,13 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
+ adminReceiver);
return;
}
-
mInjector.binderWithCleanCallingIdentity(() ->
removeActiveAdminLocked(adminReceiver, userHandle));
- if (isPolicyEngineForFinanceFlagEnabled() || isPermissionCheckFlagEnabled()) {
- mDevicePolicyEngine.removePoliciesForAdmin(
- EnforcingAdmin.createEnterpriseEnforcingAdmin(
- adminReceiver, userHandle, admin));
- }
+ }
+ if (isPolicyEngineForFinanceFlagEnabled() || isPermissionCheckFlagEnabled()) {
+ mDevicePolicyEngine.removePoliciesForAdmin(
+ EnforcingAdmin.createEnterpriseEnforcingAdmin(
+ adminReceiver, userHandle, admin));
}
}
@@ -13804,8 +13804,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
admin,
new BooleanPolicyValue(hidden),
userId);
- Boolean resolvedPolicy = mDevicePolicyEngine.getResolvedPolicy(
- PolicyDefinition.APPLICATION_HIDDEN(packageName), userId);
result = mInjector.binderWithCleanCallingIdentity(() -> {
try {
// This is a best effort to continue returning the same value that was
@@ -16549,11 +16547,13 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
hasCallingOrSelfPermission(permission.NOTIFY_PENDING_SYSTEM_UPDATE),
"Only the system update service can broadcast update information");
- if (UserHandle.getCallingUserId() != UserHandle.USER_SYSTEM) {
- Slogf.w(LOG_TAG, "Only the system update service in the system user can broadcast "
- + "update information.");
- return;
- }
+ mInjector.binderWithCleanCallingIdentity(() -> {
+ if (!mUserManager.getUserInfo(UserHandle.getCallingUserId()).isMain()) {
+ Slogf.w(LOG_TAG, "Only the system update service in the main user can broadcast "
+ + "update information.");
+ return;
+ }
+ });
if (!mOwners.saveSystemUpdateInfo(info)) {
// Pending system update hasn't changed, don't send duplicate notification.
@@ -16661,8 +16661,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
enforcePermissionGrantStateOnFinancedDevice(packageName, permission);
}
}
+ EnforcingAdmin enforcingAdmin;
if (isPermissionCheckFlagEnabled()) {
- EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin(
+ enforcingAdmin = enforcePermissionAndGetEnforcingAdmin(
admin,
MANAGE_DEVICE_POLICY_RUNTIME_PERMISSIONS,
callerPackage,
@@ -16686,17 +16687,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
callback.sendResult(null);
return;
}
- // TODO(b/266924257): decide how to handle the internal state if the package doesn't
- // exist, or the permission isn't requested by the app, because we could end up with
- // inconsistent state between the policy engine and package manager. Also a package
- // might get removed or has it's permission updated after we've set the policy.
- mDevicePolicyEngine.setLocalPolicy(
- PolicyDefinition.PERMISSION_GRANT(packageName, permission),
- enforcingAdmin,
- new IntegerPolicyValue(grantState),
- caller.getUserId());
- // TODO: update javadoc to reflect that callback no longer return success/failure
- callback.sendResult(Bundle.EMPTY);
} else {
Preconditions.checkCallAuthorization((caller.hasAdminComponent()
&& (isProfileOwner(caller) || isDefaultDeviceOwner(caller)
@@ -16704,51 +16694,81 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
|| (caller.hasPackage() && isCallerDelegate(caller,
DELEGATION_PERMISSION_GRANT)));
synchronized (getLockObject()) {
- long ident = mInjector.binderClearCallingIdentity();
- try {
- boolean isPostQAdmin = getTargetSdk(caller.getPackageName(), caller.getUserId())
- >= android.os.Build.VERSION_CODES.Q;
- if (!isPostQAdmin) {
- // Legacy admins assume that they cannot control pre-M apps
- if (getTargetSdk(packageName, caller.getUserId())
- < android.os.Build.VERSION_CODES.M) {
+ long ident = mInjector.binderClearCallingIdentity();
+ try {
+ boolean isPostQAdmin = getTargetSdk(caller.getPackageName(), caller.getUserId())
+ >= android.os.Build.VERSION_CODES.Q;
+ if (!isPostQAdmin) {
+ // Legacy admins assume that they cannot control pre-M apps
+ if (getTargetSdk(packageName, caller.getUserId())
+ < android.os.Build.VERSION_CODES.M) {
+ callback.sendResult(null);
+ return;
+ }
+ }
+ if (!isRuntimePermission(permission)) {
callback.sendResult(null);
return;
}
- }
- if (!isRuntimePermission(permission)) {
+ } catch (SecurityException e) {
+ Slogf.e(LOG_TAG, "Could not set permission grant state", e);
callback.sendResult(null);
- return;
+ } finally {
+ mInjector.binderRestoreCallingIdentity(ident);
}
- if (grantState == PERMISSION_GRANT_STATE_GRANTED
- || grantState == DevicePolicyManager.PERMISSION_GRANT_STATE_DENIED
- || grantState == DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT) {
- AdminPermissionControlParams permissionParams =
- new AdminPermissionControlParams(packageName, permission,
- grantState,
- canAdminGrantSensorsPermissions());
- mInjector.getPermissionControllerManager(caller.getUserHandle())
- .setRuntimePermissionGrantStateByDeviceAdmin(
- caller.getPackageName(),
- permissionParams, mContext.getMainExecutor(),
- (permissionWasSet) -> {
- if (isPostQAdmin && !permissionWasSet) {
- callback.sendResult(null);
- return;
- }
-
- DevicePolicyEventLogger
- .createEvent(DevicePolicyEnums
- .SET_PERMISSION_GRANT_STATE)
- .setAdmin(caller.getPackageName())
- .setStrings(permission)
- .setInt(grantState)
- .setBoolean(
- /* isDelegate */ isCallerDelegate(caller))
- .write();
-
- callback.sendResult(Bundle.EMPTY);
- });
+ }
+ }
+ // TODO(b/278710449): enable when we stop policy enforecer callback from blocking the main
+ // thread
+ if (false) {
+ // TODO(b/266924257): decide how to handle the internal state if the package doesn't
+ // exist, or the permission isn't requested by the app, because we could end up with
+ // inconsistent state between the policy engine and package manager. Also a package
+ // might get removed or has it's permission updated after we've set the policy.
+ if (grantState == PERMISSION_GRANT_STATE_DEFAULT) {
+ mDevicePolicyEngine.removeLocalPolicy(
+ PolicyDefinition.PERMISSION_GRANT(packageName, permission),
+ enforcingAdmin,
+ caller.getUserId());
+ } else {
+ mDevicePolicyEngine.setLocalPolicy(
+ PolicyDefinition.PERMISSION_GRANT(packageName, permission),
+ enforcingAdmin,
+ new IntegerPolicyValue(grantState),
+ caller.getUserId());
+ }
+ int newState = mInjector.binderWithCleanCallingIdentity(() ->
+ getPermissionGrantStateForUser(
+ packageName, permission, caller, caller.getUserId()));
+ if (newState == grantState) {
+ callback.sendResult(Bundle.EMPTY);
+ } else {
+ callback.sendResult(null);
+ }
+ } else {
+ synchronized (getLockObject()) {
+ long ident = mInjector.binderClearCallingIdentity();
+ try {
+ boolean isPostQAdmin = getTargetSdk(caller.getPackageName(), caller.getUserId())
+ >= android.os.Build.VERSION_CODES.Q;
+ if (grantState == PERMISSION_GRANT_STATE_GRANTED
+ || grantState == DevicePolicyManager.PERMISSION_GRANT_STATE_DENIED
+ || grantState == DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT) {
+ AdminPermissionControlParams permissionParams =
+ new AdminPermissionControlParams(packageName, permission,
+ grantState,
+ canAdminGrantSensorsPermissions());
+ mInjector.getPermissionControllerManager(caller.getUserHandle())
+ .setRuntimePermissionGrantStateByDeviceAdmin(
+ caller.getPackageName(),
+ permissionParams, mContext.getMainExecutor(),
+ (permissionWasSet) -> {
+ if (isPostQAdmin && !permissionWasSet) {
+ callback.sendResult(null);
+ return;
+ }
+ callback.sendResult(Bundle.EMPTY);
+ });
}
} catch (SecurityException e) {
Slogf.e(LOG_TAG, "Could not set permission grant state", e);
@@ -16759,6 +16779,12 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
}
}
+ DevicePolicyEventLogger.createEvent(DevicePolicyEnums.SET_PERMISSION_GRANT_STATE)
+ .setAdmin(caller.getPackageName())
+ .setStrings(permission)
+ .setInt(grantState)
+ .setBoolean(/* isDelegate */ isCallerDelegate(caller))
+ .write();
}
private static final List<String> SENSOR_PERMISSIONS = new ArrayList<>();
@@ -16822,10 +16848,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
if (isFinancedDeviceOwner(caller)) {
enforcePermissionGrantStateOnFinancedDevice(packageName, permission);
}
- return mInjector.binderWithCleanCallingIdentity(() -> {
- return getPermissionGrantStateForUser(
- packageName, permission, caller, caller.getUserId());
- });
+ return mInjector.binderWithCleanCallingIdentity(() -> getPermissionGrantStateForUser(
+ packageName, permission, caller, caller.getUserId()));
}
}
@@ -18952,7 +18976,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
admin,
MANAGE_DEVICE_POLICY_RESET_PASSWORD,
caller.getPackageName(),
- UserHandle.USER_ALL);
+ userId);
Long currentTokenHandle = mDevicePolicyEngine.getLocalPolicySetByAdmin(
PolicyDefinition.RESET_PASSWORD_TOKEN,
enforcingAdmin,
@@ -19016,7 +19040,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
admin,
MANAGE_DEVICE_POLICY_RESET_PASSWORD,
caller.getPackageName(),
- UserHandle.USER_ALL);
+ userId);
Long currentTokenHandle = mDevicePolicyEngine.getLocalPolicySetByAdmin(
PolicyDefinition.RESET_PASSWORD_TOKEN,
enforcingAdmin,
@@ -19062,7 +19086,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
admin,
MANAGE_DEVICE_POLICY_RESET_PASSWORD,
caller.getPackageName(),
- UserHandle.USER_ALL);
+ userId);
Long currentTokenHandle = mDevicePolicyEngine.getLocalPolicySetByAdmin(
PolicyDefinition.RESET_PASSWORD_TOKEN,
enforcingAdmin,
@@ -19114,7 +19138,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
admin,
MANAGE_DEVICE_POLICY_RESET_PASSWORD,
caller.getPackageName(),
- UserHandle.USER_ALL);
+ userId);
Long currentTokenHandle = mDevicePolicyEngine.getLocalPolicySetByAdmin(
PolicyDefinition.RESET_PASSWORD_TOKEN,
enforcingAdmin,
@@ -19140,10 +19164,17 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
if (result) {
- DevicePolicyEventLogger
- .createEvent(DevicePolicyEnums.RESET_PASSWORD_WITH_TOKEN)
- .setAdmin(caller.getComponentName())
- .write();
+ if (isPermissionCheckFlagEnabled()) {
+ DevicePolicyEventLogger
+ .createEvent(DevicePolicyEnums.RESET_PASSWORD_WITH_TOKEN)
+ .setAdmin(callerPackageName)
+ .write();
+ } else {
+ DevicePolicyEventLogger
+ .createEvent(DevicePolicyEnums.RESET_PASSWORD_WITH_TOKEN)
+ .setAdmin(caller.getComponentName())
+ .write();
+ }
}
return result;
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java
index d65d366e4476..12a8a75317e5 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java
@@ -84,6 +84,7 @@ final class PolicyEnforcerCallbacks {
? DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT
: grantState;
+ // TODO(b/278710449): stop blocking in the main thread
BlockingCallback callback = new BlockingCallback();
// TODO: remove canAdminGrantSensorPermissions once we expose a new method in
// permissionController that doesn't need it.
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java
index ca857f121624..c4aa0bbc24b5 100644
--- a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java
+++ b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java
@@ -445,7 +445,7 @@ public final class DisplayPowerController2Test {
}
@Test
- public void testDisplayBrightnessFollowersRemoval() {
+ public void testDisplayBrightnessFollowersRemoval_RemoveSingleFollower() {
DisplayPowerControllerHolder followerDpc = createDisplayPowerController(FOLLOWER_DISPLAY_ID,
FOLLOWER_UNIQUE_ID);
DisplayPowerControllerHolder secondFollowerDpc = createDisplayPowerController(
@@ -520,6 +520,78 @@ public final class DisplayPowerController2Test {
}
@Test
+ public void testDisplayBrightnessFollowersRemoval_RemoveAllFollowers() {
+ DisplayPowerControllerHolder followerHolder =
+ createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_ID);
+ DisplayPowerControllerHolder secondFollowerHolder =
+ createDisplayPowerController(SECOND_FOLLOWER_DISPLAY_ID,
+ SECOND_FOLLOWER_UNIQUE_DISPLAY_ID);
+
+ DisplayPowerRequest dpr = new DisplayPowerRequest();
+ mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ followerHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ secondFollowerHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ advanceTime(1); // Run updatePowerState
+
+ ArgumentCaptor<BrightnessSetting.BrightnessSettingListener> listenerCaptor =
+ ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class);
+ verify(mHolder.brightnessSetting).registerListener(listenerCaptor.capture());
+ BrightnessSetting.BrightnessSettingListener listener = listenerCaptor.getValue();
+
+ // Set the initial brightness on the DPCs we're going to remove so we have a fixed value for
+ // it to return to.
+ listenerCaptor = ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class);
+ verify(followerHolder.brightnessSetting).registerListener(listenerCaptor.capture());
+ BrightnessSetting.BrightnessSettingListener followerListener = listenerCaptor.getValue();
+ listenerCaptor = ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class);
+ verify(secondFollowerHolder.brightnessSetting).registerListener(listenerCaptor.capture());
+ BrightnessSetting.BrightnessSettingListener secondFollowerListener =
+ listenerCaptor.getValue();
+ final float initialFollowerBrightness = 0.3f;
+ when(followerHolder.brightnessSetting.getBrightness()).thenReturn(
+ initialFollowerBrightness);
+ when(secondFollowerHolder.brightnessSetting.getBrightness()).thenReturn(
+ initialFollowerBrightness);
+ followerListener.onBrightnessChanged(initialFollowerBrightness);
+ secondFollowerListener.onBrightnessChanged(initialFollowerBrightness);
+ advanceTime(1);
+ verify(followerHolder.animator).animateTo(eq(initialFollowerBrightness),
+ anyFloat(), anyFloat());
+ verify(secondFollowerHolder.animator).animateTo(eq(initialFollowerBrightness),
+ anyFloat(), anyFloat());
+
+ mHolder.dpc.addDisplayBrightnessFollower(followerHolder.dpc);
+ mHolder.dpc.addDisplayBrightnessFollower(secondFollowerHolder.dpc);
+ clearInvocations(followerHolder.animator, secondFollowerHolder.animator);
+
+ // Validate both followers are correctly registered and receiving brightness updates
+ float brightness = 0.6f;
+ float nits = 600;
+ when(mHolder.automaticBrightnessController.convertToNits(brightness)).thenReturn(nits);
+ when(followerHolder.automaticBrightnessController.convertToFloatScale(nits))
+ .thenReturn(brightness);
+ when(secondFollowerHolder.automaticBrightnessController.convertToFloatScale(nits))
+ .thenReturn(brightness);
+ when(mHolder.brightnessSetting.getBrightness()).thenReturn(brightness);
+ listener.onBrightnessChanged(brightness);
+ advanceTime(1); // Send messages, run updatePowerState
+ verify(mHolder.animator).animateTo(eq(brightness), anyFloat(), anyFloat());
+ verify(followerHolder.animator).animateTo(eq(brightness), anyFloat(), anyFloat());
+ verify(secondFollowerHolder.animator).animateTo(eq(brightness), anyFloat(), anyFloat());
+
+ clearInvocations(mHolder.animator, followerHolder.animator, secondFollowerHolder.animator);
+
+ // Stop the lead DPC and validate that the followers go back to their original brightness.
+ mHolder.dpc.stop();
+ advanceTime(1);
+ verify(followerHolder.animator).animateTo(eq(initialFollowerBrightness),
+ anyFloat(), anyFloat());
+ verify(secondFollowerHolder.animator).animateTo(eq(initialFollowerBrightness),
+ anyFloat(), anyFloat());
+ clearInvocations(followerHolder.animator, secondFollowerHolder.animator);
+ }
+
+ @Test
public void testDoesNotSetScreenStateForNonDefaultDisplayUntilBootCompleted() {
// We should still set screen state for the default display
DisplayPowerRequest dpr = new DisplayPowerRequest();
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerControllerTest.java
index 0b97c5cb6ca1..415adbbac91e 100644
--- a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerControllerTest.java
@@ -91,7 +91,7 @@ public final class DisplayPowerControllerTest {
private static final int DISPLAY_ID = Display.DEFAULT_DISPLAY;
private static final String UNIQUE_ID = "unique_id_test123";
private static final int FOLLOWER_DISPLAY_ID = DISPLAY_ID + 1;
- private static final String FOLLOWER_UNIQUE_DISPLAY_ID = "unique_id_456";
+ private static final String FOLLOWER_UNIQUE_ID = "unique_id_456";
private static final int SECOND_FOLLOWER_DISPLAY_ID = FOLLOWER_DISPLAY_ID + 1;
private static final String SECOND_FOLLOWER_UNIQUE_DISPLAY_ID = "unique_id_789";
private static final float PROX_SENSOR_MAX_RANGE = 5;
@@ -279,7 +279,7 @@ public final class DisplayPowerControllerTest {
@Test
public void testProximitySensorListenerNotRegisteredForNonDefaultDisplay() {
DisplayPowerControllerHolder followerDpc =
- createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_DISPLAY_ID);
+ createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_ID);
when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
// send a display power request
@@ -298,7 +298,7 @@ public final class DisplayPowerControllerTest {
@Test
public void testDisplayBrightnessFollowers_BothDpcsSupportNits() {
DisplayPowerControllerHolder followerDpc =
- createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_DISPLAY_ID);
+ createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_ID);
DisplayPowerRequest dpr = new DisplayPowerRequest();
mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
@@ -344,7 +344,7 @@ public final class DisplayPowerControllerTest {
@Test
public void testDisplayBrightnessFollowers_FollowerDoesNotSupportNits() {
DisplayPowerControllerHolder followerDpc =
- createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_DISPLAY_ID);
+ createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_ID);
DisplayPowerRequest dpr = new DisplayPowerRequest();
mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
@@ -372,7 +372,7 @@ public final class DisplayPowerControllerTest {
@Test
public void testDisplayBrightnessFollowers_LeadDpcDoesNotSupportNits() {
DisplayPowerControllerHolder followerDpc =
- createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_DISPLAY_ID);
+ createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_ID);
DisplayPowerRequest dpr = new DisplayPowerRequest();
mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
@@ -398,7 +398,7 @@ public final class DisplayPowerControllerTest {
@Test
public void testDisplayBrightnessFollowers_NeitherDpcSupportsNits() {
DisplayPowerControllerHolder followerDpc =
- createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_DISPLAY_ID);
+ createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_ID);
DisplayPowerRequest dpr = new DisplayPowerRequest();
mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
@@ -449,9 +449,9 @@ public final class DisplayPowerControllerTest {
}
@Test
- public void testDisplayBrightnessFollowersRemoval() {
+ public void testDisplayBrightnessFollowersRemoval_RemoveSingleFollower() {
DisplayPowerControllerHolder followerHolder =
- createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_DISPLAY_ID);
+ createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_ID);
DisplayPowerControllerHolder secondFollowerHolder =
createDisplayPowerController(SECOND_FOLLOWER_DISPLAY_ID,
SECOND_FOLLOWER_UNIQUE_DISPLAY_ID);
@@ -525,6 +525,78 @@ public final class DisplayPowerControllerTest {
}
@Test
+ public void testDisplayBrightnessFollowersRemoval_RemoveAllFollowers() {
+ DisplayPowerControllerHolder followerHolder =
+ createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_ID);
+ DisplayPowerControllerHolder secondFollowerHolder =
+ createDisplayPowerController(SECOND_FOLLOWER_DISPLAY_ID,
+ SECOND_FOLLOWER_UNIQUE_DISPLAY_ID);
+
+ DisplayPowerRequest dpr = new DisplayPowerRequest();
+ mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ followerHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ secondFollowerHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ advanceTime(1); // Run updatePowerState
+
+ ArgumentCaptor<BrightnessSetting.BrightnessSettingListener> listenerCaptor =
+ ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class);
+ verify(mHolder.brightnessSetting).registerListener(listenerCaptor.capture());
+ BrightnessSetting.BrightnessSettingListener listener = listenerCaptor.getValue();
+
+ // Set the initial brightness on the DPCs we're going to remove so we have a fixed value for
+ // it to return to.
+ listenerCaptor = ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class);
+ verify(followerHolder.brightnessSetting).registerListener(listenerCaptor.capture());
+ BrightnessSetting.BrightnessSettingListener followerListener = listenerCaptor.getValue();
+ listenerCaptor = ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class);
+ verify(secondFollowerHolder.brightnessSetting).registerListener(listenerCaptor.capture());
+ BrightnessSetting.BrightnessSettingListener secondFollowerListener =
+ listenerCaptor.getValue();
+ final float initialFollowerBrightness = 0.3f;
+ when(followerHolder.brightnessSetting.getBrightness()).thenReturn(
+ initialFollowerBrightness);
+ when(secondFollowerHolder.brightnessSetting.getBrightness()).thenReturn(
+ initialFollowerBrightness);
+ followerListener.onBrightnessChanged(initialFollowerBrightness);
+ secondFollowerListener.onBrightnessChanged(initialFollowerBrightness);
+ advanceTime(1);
+ verify(followerHolder.animator).animateTo(eq(initialFollowerBrightness),
+ anyFloat(), anyFloat());
+ verify(secondFollowerHolder.animator).animateTo(eq(initialFollowerBrightness),
+ anyFloat(), anyFloat());
+
+ mHolder.dpc.addDisplayBrightnessFollower(followerHolder.dpc);
+ mHolder.dpc.addDisplayBrightnessFollower(secondFollowerHolder.dpc);
+ clearInvocations(followerHolder.animator, secondFollowerHolder.animator);
+
+ // Validate both followers are correctly registered and receiving brightness updates
+ float brightness = 0.6f;
+ float nits = 600;
+ when(mHolder.automaticBrightnessController.convertToNits(brightness)).thenReturn(nits);
+ when(followerHolder.automaticBrightnessController.convertToFloatScale(nits))
+ .thenReturn(brightness);
+ when(secondFollowerHolder.automaticBrightnessController.convertToFloatScale(nits))
+ .thenReturn(brightness);
+ when(mHolder.brightnessSetting.getBrightness()).thenReturn(brightness);
+ listener.onBrightnessChanged(brightness);
+ advanceTime(1); // Send messages, run updatePowerState
+ verify(mHolder.animator).animateTo(eq(brightness), anyFloat(), anyFloat());
+ verify(followerHolder.animator).animateTo(eq(brightness), anyFloat(), anyFloat());
+ verify(secondFollowerHolder.animator).animateTo(eq(brightness), anyFloat(), anyFloat());
+
+ clearInvocations(mHolder.animator, followerHolder.animator, secondFollowerHolder.animator);
+
+ // Stop the lead DPC and validate that the followers go back to their original brightness.
+ mHolder.dpc.stop();
+ advanceTime(1);
+ verify(followerHolder.animator).animateTo(eq(initialFollowerBrightness),
+ anyFloat(), anyFloat());
+ verify(secondFollowerHolder.animator).animateTo(eq(initialFollowerBrightness),
+ anyFloat(), anyFloat());
+ clearInvocations(followerHolder.animator, secondFollowerHolder.animator);
+ }
+
+ @Test
public void testDoesNotSetScreenStateForNonDefaultDisplayUntilBootCompleted() {
// We should still set screen state for the default display
DisplayPowerRequest dpr = new DisplayPowerRequest();
diff --git a/services/tests/mockingservicestests/src/com/android/server/rollback/OWNERS b/services/tests/mockingservicestests/src/com/android/server/rollback/OWNERS
new file mode 100644
index 000000000000..daa02111f71f
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/rollback/OWNERS
@@ -0,0 +1,3 @@
+ancr@google.com
+harshitmahajan@google.com
+robertogil@google.com
diff --git a/services/tests/mockingservicestests/src/com/android/server/rollback/RollbackPackageHealthObserverTest.java b/services/tests/mockingservicestests/src/com/android/server/rollback/RollbackPackageHealthObserverTest.java
index a14073006c31..35d4ffdd94d0 100644
--- a/services/tests/mockingservicestests/src/com/android/server/rollback/RollbackPackageHealthObserverTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/rollback/RollbackPackageHealthObserverTest.java
@@ -145,7 +145,7 @@ public class RollbackPackageHealthObserverTest {
observer.onHealthCheckFailed(null,
PackageWatchdog.FAILURE_REASON_NATIVE_CRASH, 1));
// non-native crash for the package
- assertEquals(PackageWatchdog.PackageHealthObserverImpact.USER_IMPACT_LEVEL_30,
+ assertEquals(PackageWatchdog.PackageHealthObserverImpact.USER_IMPACT_LEVEL_60,
observer.onHealthCheckFailed(testFailedPackage,
PackageWatchdog.FAILURE_REASON_APP_CRASH, 1));
// non-native crash for a different package
diff --git a/services/tests/servicestests/res/xml/irq_device_map_3.xml b/services/tests/servicestests/res/xml/irq_device_map_3.xml
index 7e2529aa0090..fd55428c48df 100644
--- a/services/tests/servicestests/res/xml/irq_device_map_3.xml
+++ b/services/tests/servicestests/res/xml/irq_device_map_3.xml
@@ -26,4 +26,10 @@
<device name="test.sound_trigger.device">
<subsystem>Sound_trigger</subsystem>
</device>
+ <device name="test.cellular_data.device">
+ <subsystem>Cellular_data</subsystem>
+ </device>
+ <device name="test.sensor.device">
+ <subsystem>Sensor</subsystem>
+ </device>
</irq-device-map> \ No newline at end of file
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java
index f1ad577fde88..a01c7bdd5144 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java
@@ -200,7 +200,7 @@ public class FullScreenMagnificationControllerTest {
assertFalse(mFullScreenMagnificationController.isRegistered(DISPLAY_0));
assertFalse(mFullScreenMagnificationController.isRegistered(DISPLAY_1));
- verify(mMockThumbnail, times(2)).hideThumbNail();
+ verify(mMockThumbnail, times(2)).hideThumbnail();
}
@Test
@@ -538,7 +538,10 @@ public class FullScreenMagnificationControllerTest {
mConfigCaptor.capture());
assertConfigEquals(config, mConfigCaptor.getValue());
- verify(mMockThumbnail).setThumbNailBounds(any(), anyFloat(), anyFloat(), anyFloat());
+ // The first time is triggered when the thumbnail is just created.
+ // The second time is triggered when the magnification region changed.
+ verify(mMockThumbnail, times(2)).setThumbnailBounds(
+ any(), anyFloat(), anyFloat(), anyFloat());
}
@Test
@@ -909,7 +912,7 @@ public class FullScreenMagnificationControllerTest {
verifyNoMoreInteractions(mMockWindowManager);
verify(mMockThumbnail)
- .updateThumbNail(eq(scale), eq(startCenter.x), eq(startCenter.y));
+ .updateThumbnail(eq(scale), eq(startCenter.x), eq(startCenter.y));
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationThumbnailTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationThumbnailTest.java
index 60c8148180da..3baa102b882b 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationThumbnailTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationThumbnailTest.java
@@ -66,14 +66,14 @@ public class MagnificationThumbnailTest {
@Test
public void updateThumbnailShows() {
- runOnMainSync(() -> mMagnificationThumbnail.updateThumbNail(
+ runOnMainSync(() -> mMagnificationThumbnail.updateThumbnail(
/* scale= */ 2f,
/* centerX= */ 5,
/* centerY= */ 10
));
idle();
- runOnMainSync(() -> mMagnificationThumbnail.updateThumbNail(
+ runOnMainSync(() -> mMagnificationThumbnail.updateThumbnail(
/* scale= */ 2.2f,
/* centerX= */ 15,
/* centerY= */ 50
@@ -86,7 +86,7 @@ public class MagnificationThumbnailTest {
@Test
public void updateThumbnailLingersThenHidesAfterTimeout() throws InterruptedException {
- runOnMainSync(() -> mMagnificationThumbnail.updateThumbNail(
+ runOnMainSync(() -> mMagnificationThumbnail.updateThumbnail(
/* scale= */ 2f,
/* centerX= */ 5,
/* centerY= */ 10
@@ -103,14 +103,14 @@ public class MagnificationThumbnailTest {
@Test
public void hideThumbnailRemoves() throws InterruptedException {
- runOnMainSync(() -> mMagnificationThumbnail.updateThumbNail(
+ runOnMainSync(() -> mMagnificationThumbnail.updateThumbnail(
/* scale= */ 2f,
/* centerX= */ 5,
/* centerY= */ 10
));
idle();
- runOnMainSync(() -> mMagnificationThumbnail.hideThumbNail());
+ runOnMainSync(() -> mMagnificationThumbnail.hideThumbnail());
idle();
// Wait for the fade out animation
@@ -122,10 +122,10 @@ public class MagnificationThumbnailTest {
@Test
public void hideShowHideShowHideRemoves() throws InterruptedException {
- runOnMainSync(() -> mMagnificationThumbnail.hideThumbNail());
+ runOnMainSync(() -> mMagnificationThumbnail.hideThumbnail());
idle();
- runOnMainSync(() -> mMagnificationThumbnail.updateThumbNail(
+ runOnMainSync(() -> mMagnificationThumbnail.updateThumbnail(
/* scale= */ 2f,
/* centerX= */ 5,
/* centerY= */ 10
@@ -135,17 +135,17 @@ public class MagnificationThumbnailTest {
// Wait for the fade in animation
Thread.sleep(200L);
- runOnMainSync(() -> mMagnificationThumbnail.hideThumbNail());
+ runOnMainSync(() -> mMagnificationThumbnail.hideThumbnail());
idle();
- runOnMainSync(() -> mMagnificationThumbnail.updateThumbNail(
+ runOnMainSync(() -> mMagnificationThumbnail.updateThumbnail(
/* scale= */ 2f,
/* centerX= */ 5,
/* centerY= */ 10
));
idle();
- runOnMainSync(() -> mMagnificationThumbnail.hideThumbNail());
+ runOnMainSync(() -> mMagnificationThumbnail.hideThumbnail());
idle();
@@ -158,7 +158,7 @@ public class MagnificationThumbnailTest {
@Test
public void hideWithoutShowDoesNothing() throws InterruptedException {
- runOnMainSync(() -> mMagnificationThumbnail.hideThumbNail());
+ runOnMainSync(() -> mMagnificationThumbnail.hideThumbnail());
idle();
// Wait for the fade out animation
@@ -172,7 +172,7 @@ public class MagnificationThumbnailTest {
@Test
public void whenHidden_setBoundsDoesNotShow() throws InterruptedException {
- runOnMainSync(() -> mMagnificationThumbnail.setThumbNailBounds(
+ runOnMainSync(() -> mMagnificationThumbnail.setThumbnailBounds(
new Rect(),
/* scale= */ 2f,
/* centerX= */ 5,
diff --git a/services/tests/servicestests/src/com/android/server/audio/AudioDeviceBrokerTest.java b/services/tests/servicestests/src/com/android/server/audio/AudioDeviceBrokerTest.java
index dad9fe8648b2..31599eed539d 100644
--- a/services/tests/servicestests/src/com/android/server/audio/AudioDeviceBrokerTest.java
+++ b/services/tests/servicestests/src/com/android/server/audio/AudioDeviceBrokerTest.java
@@ -74,7 +74,7 @@ public class AudioDeviceBrokerTest {
mSpyDevInventory = spy(new AudioDeviceInventory(mSpyAudioSystem));
mSpySystemServer = spy(new NoOpSystemServerAdapter());
mAudioDeviceBroker = new AudioDeviceBroker(mContext, mMockAudioService, mSpyDevInventory,
- mSpySystemServer);
+ mSpySystemServer, mSpyAudioSystem);
mSpyDevInventory.setDeviceBroker(mAudioDeviceBroker);
BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/AuthSessionCoordinatorTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/AuthSessionCoordinatorTest.java
index f26c7e63a273..9d84a074dd41 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/AuthSessionCoordinatorTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/AuthSessionCoordinatorTest.java
@@ -135,7 +135,7 @@ public class AuthSessionCoordinatorTest {
}
@Test
- public void testUserCanAuthDuringLockoutOfSameSession() {
+ public void testUserLockedDuringLockoutOfSameSession() {
mCoordinator.resetLockoutFor(PRIMARY_USER, BIOMETRIC_STRONG, 0 /* requestId */);
assertThat(mCoordinator.getLockoutStateFor(PRIMARY_USER, BIOMETRIC_CONVENIENCE)).isEqualTo(
@@ -151,9 +151,9 @@ public class AuthSessionCoordinatorTest {
0 /* requestId */);
assertThat(mCoordinator.getLockoutStateFor(PRIMARY_USER, BIOMETRIC_CONVENIENCE)).isEqualTo(
- LockoutTracker.LOCKOUT_NONE);
+ LockoutTracker.LOCKOUT_PERMANENT);
assertThat(mCoordinator.getLockoutStateFor(PRIMARY_USER, BIOMETRIC_WEAK)).isEqualTo(
- LockoutTracker.LOCKOUT_NONE);
+ LockoutTracker.LOCKOUT_PERMANENT);
assertThat(mCoordinator.getLockoutStateFor(PRIMARY_USER, BIOMETRIC_STRONG)).isEqualTo(
LockoutTracker.LOCKOUT_NONE);
}
@@ -191,9 +191,9 @@ public class AuthSessionCoordinatorTest {
0 /* requestId */);
assertThat(mCoordinator.getLockoutStateFor(PRIMARY_USER, BIOMETRIC_CONVENIENCE)).isEqualTo(
- LockoutTracker.LOCKOUT_NONE);
+ LockoutTracker.LOCKOUT_PERMANENT);
assertThat(mCoordinator.getLockoutStateFor(PRIMARY_USER, BIOMETRIC_WEAK)).isEqualTo(
- LockoutTracker.LOCKOUT_NONE);
+ LockoutTracker.LOCKOUT_PERMANENT);
assertThat(mCoordinator.getLockoutStateFor(PRIMARY_USER, BIOMETRIC_STRONG)).isEqualTo(
LockoutTracker.LOCKOUT_NONE);
diff --git a/services/tests/servicestests/src/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncConnectionServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncConnectionServiceTest.java
new file mode 100644
index 000000000000..3ed95eb3c878
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncConnectionServiceTest.java
@@ -0,0 +1,61 @@
+/*
+ * 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.companion.datatransfer.contextsync;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.platform.test.annotations.Presubmit;
+import android.telecom.PhoneAccount;
+import android.testing.AndroidTestingRunner;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@Presubmit
+@RunWith(AndroidTestingRunner.class)
+public class CallMetadataSyncConnectionServiceTest {
+
+ private CallMetadataSyncConnectionService mSyncConnectionService;
+
+ @Before
+ public void setUp() throws Exception {
+ mSyncConnectionService = new CallMetadataSyncConnectionService() {
+ @Override
+ public String getPackageName() {
+ return "android";
+ }
+ };
+ }
+
+ @Test
+ public void createPhoneAccount_success() {
+ final PhoneAccount phoneAccount = mSyncConnectionService.createPhoneAccount(
+ "com.google.test", "Test App");
+ assertWithMessage("Could not create phone account").that(phoneAccount).isNotNull();
+ }
+
+ @Test
+ public void createPhoneAccount_alreadyExists_doesNotCreateAnother() {
+ final PhoneAccount phoneAccount = mSyncConnectionService.createPhoneAccount(
+ "com.google.test", "Test App");
+ final PhoneAccount phoneAccount2 = mSyncConnectionService.createPhoneAccount(
+ "com.google.test", "Test App #2");
+ assertWithMessage("Could not create phone account").that(phoneAccount).isNotNull();
+ assertWithMessage("Unexpectedly created second phone account").that(phoneAccount2).isNull();
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncDataTest.java b/services/tests/servicestests/src/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncDataTest.java
new file mode 100644
index 000000000000..c5a9af7d909d
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncDataTest.java
@@ -0,0 +1,64 @@
+/*
+ * 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.companion.datatransfer.contextsync;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.Parcel;
+import android.testing.AndroidTestingRunner;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidTestingRunner.class)
+public class CallMetadataSyncDataTest {
+
+ @Test
+ public void call_writeToParcel_fromParcel_reconstructsSuccessfully() {
+ final CallMetadataSyncData.Call call = new CallMetadataSyncData.Call();
+ final long id = 5;
+ final String callerId = "callerId";
+ final byte[] appIcon = "appIcon".getBytes();
+ final String appName = "appName";
+ final String appIdentifier = "com.google.test";
+ final int status = 1;
+ final int control1 = 2;
+ final int control2 = 3;
+ call.setId(id);
+ call.setCallerId(callerId);
+ call.setAppIcon(appIcon);
+ call.setAppName(appName);
+ call.setAppIdentifier(appIdentifier);
+ call.setStatus(status);
+ call.addControl(control1);
+ call.addControl(control2);
+
+ Parcel parcel = Parcel.obtain();
+ call.writeToParcel(parcel, /* flags= */ 0);
+ parcel.setDataPosition(0);
+ final CallMetadataSyncData.Call reconstructedCall = CallMetadataSyncData.Call.fromParcel(
+ parcel);
+
+ assertThat(reconstructedCall.getId()).isEqualTo(id);
+ assertThat(reconstructedCall.getCallerId()).isEqualTo(callerId);
+ assertThat(reconstructedCall.getAppIcon()).isEqualTo(appIcon);
+ assertThat(reconstructedCall.getAppName()).isEqualTo(appName);
+ assertThat(reconstructedCall.getAppIdentifier()).isEqualTo(appIdentifier);
+ assertThat(reconstructedCall.getStatus()).isEqualTo(status);
+ assertThat(reconstructedCall.getControls()).containsExactly(control1, control2);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
index a4a3e363ab4d..c8c1d6f0f7ed 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
@@ -1160,6 +1160,7 @@ public class VirtualDeviceManagerServiceTest {
final int fd = 1;
final int keyCode = KeyEvent.KEYCODE_A;
final int action = VirtualKeyEvent.ACTION_UP;
+ final long eventTimeNanos = 5000L;
mInputController.addDeviceForTesting(BINDER, fd,
InputController.InputDeviceDescriptor.TYPE_KEYBOARD, DISPLAY_ID_1, PHYS,
DEVICE_NAME_1, INPUT_DEVICE_ID);
@@ -1167,8 +1168,9 @@ public class VirtualDeviceManagerServiceTest {
mDeviceImpl.sendKeyEvent(BINDER, new VirtualKeyEvent.Builder()
.setKeyCode(keyCode)
.setAction(action)
+ .setEventTimeNanos(eventTimeNanos)
.build());
- verify(mNativeWrapperMock).writeKeyEvent(fd, keyCode, action);
+ verify(mNativeWrapperMock).writeKeyEvent(fd, keyCode, action, eventTimeNanos);
}
@Test
@@ -1188,14 +1190,17 @@ public class VirtualDeviceManagerServiceTest {
final int fd = 1;
final int buttonCode = VirtualMouseButtonEvent.BUTTON_BACK;
final int action = VirtualMouseButtonEvent.ACTION_BUTTON_PRESS;
+ final long eventTimeNanos = 5000L;
mInputController.addDeviceForTesting(BINDER, fd,
InputController.InputDeviceDescriptor.TYPE_MOUSE, DISPLAY_ID_1, PHYS,
DEVICE_NAME_1, INPUT_DEVICE_ID);
doReturn(DISPLAY_ID_1).when(mInputManagerInternalMock).getVirtualMousePointerDisplayId();
mDeviceImpl.sendButtonEvent(BINDER, new VirtualMouseButtonEvent.Builder()
.setButtonCode(buttonCode)
- .setAction(action).build());
- verify(mNativeWrapperMock).writeButtonEvent(fd, buttonCode, action);
+ .setAction(action)
+ .setEventTimeNanos(eventTimeNanos)
+ .build());
+ verify(mNativeWrapperMock).writeButtonEvent(fd, buttonCode, action, eventTimeNanos);
}
@Test
@@ -1229,13 +1234,17 @@ public class VirtualDeviceManagerServiceTest {
final int fd = 1;
final float x = -0.2f;
final float y = 0.7f;
+ final long eventTimeNanos = 5000L;
mInputController.addDeviceForTesting(BINDER, fd,
InputController.InputDeviceDescriptor.TYPE_MOUSE, DISPLAY_ID_1, PHYS, DEVICE_NAME_1,
INPUT_DEVICE_ID);
doReturn(DISPLAY_ID_1).when(mInputManagerInternalMock).getVirtualMousePointerDisplayId();
mDeviceImpl.sendRelativeEvent(BINDER, new VirtualMouseRelativeEvent.Builder()
- .setRelativeX(x).setRelativeY(y).build());
- verify(mNativeWrapperMock).writeRelativeEvent(fd, x, y);
+ .setRelativeX(x)
+ .setRelativeY(y)
+ .setEventTimeNanos(eventTimeNanos)
+ .build());
+ verify(mNativeWrapperMock).writeRelativeEvent(fd, x, y, eventTimeNanos);
}
@Test
@@ -1270,14 +1279,17 @@ public class VirtualDeviceManagerServiceTest {
final int fd = 1;
final float x = 0.5f;
final float y = 1f;
+ final long eventTimeNanos = 5000L;
mInputController.addDeviceForTesting(BINDER, fd,
InputController.InputDeviceDescriptor.TYPE_MOUSE, DISPLAY_ID_1, PHYS, DEVICE_NAME_1,
INPUT_DEVICE_ID);
doReturn(DISPLAY_ID_1).when(mInputManagerInternalMock).getVirtualMousePointerDisplayId();
mDeviceImpl.sendScrollEvent(BINDER, new VirtualMouseScrollEvent.Builder()
.setXAxisMovement(x)
- .setYAxisMovement(y).build());
- verify(mNativeWrapperMock).writeScrollEvent(fd, x, y);
+ .setYAxisMovement(y)
+ .setEventTimeNanos(eventTimeNanos)
+ .build());
+ verify(mNativeWrapperMock).writeScrollEvent(fd, x, y, eventTimeNanos);
}
@Test
@@ -1318,6 +1330,7 @@ public class VirtualDeviceManagerServiceTest {
final float x = 100.5f;
final float y = 200.5f;
final int action = VirtualTouchEvent.ACTION_UP;
+ final long eventTimeNanos = 5000L;
mInputController.addDeviceForTesting(BINDER, fd,
InputController.InputDeviceDescriptor.TYPE_TOUCHSCREEN, DISPLAY_ID_1, PHYS,
DEVICE_NAME_1, INPUT_DEVICE_ID);
@@ -1327,9 +1340,10 @@ public class VirtualDeviceManagerServiceTest {
.setAction(action)
.setPointerId(pointerId)
.setToolType(toolType)
+ .setEventTimeNanos(eventTimeNanos)
.build());
verify(mNativeWrapperMock).writeTouchEvent(fd, pointerId, toolType, action, x, y, Float.NaN,
- Float.NaN);
+ Float.NaN, eventTimeNanos);
}
@Test
@@ -1342,6 +1356,7 @@ public class VirtualDeviceManagerServiceTest {
final int action = VirtualTouchEvent.ACTION_UP;
final float pressure = 1.0f;
final float majorAxisSize = 10.0f;
+ final long eventTimeNanos = 5000L;
mInputController.addDeviceForTesting(BINDER, fd,
InputController.InputDeviceDescriptor.TYPE_TOUCHSCREEN, DISPLAY_ID_1, PHYS,
DEVICE_NAME_1, INPUT_DEVICE_ID);
@@ -1353,9 +1368,10 @@ public class VirtualDeviceManagerServiceTest {
.setToolType(toolType)
.setPressure(pressure)
.setMajorAxisSize(majorAxisSize)
+ .setEventTimeNanos(eventTimeNanos)
.build());
verify(mNativeWrapperMock).writeTouchEvent(fd, pointerId, toolType, action, x, y, pressure,
- majorAxisSize);
+ majorAxisSize, eventTimeNanos);
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/display/BrightnessMappingStrategyTest.java b/services/tests/servicestests/src/com/android/server/display/BrightnessMappingStrategyTest.java
index 89ff2c258c26..5f81869903c3 100644
--- a/services/tests/servicestests/src/com/android/server/display/BrightnessMappingStrategyTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/BrightnessMappingStrategyTest.java
@@ -287,25 +287,37 @@ public class BrightnessMappingStrategyTest {
adjustedNits50p[i] = DISPLAY_RANGE_NITS[i] * 0.5f;
}
- // Default is unadjusted
+ // Default
assertEquals(DISPLAY_RANGE_NITS[0], strategy.convertToNits(BACKLIGHT_RANGE_ZERO_TO_ONE[0]),
- 0.0001f /* tolerance */);
+ TOLERANCE);
assertEquals(DISPLAY_RANGE_NITS[1], strategy.convertToNits(BACKLIGHT_RANGE_ZERO_TO_ONE[1]),
- 0.0001f /* tolerance */);
+ TOLERANCE);
+ assertEquals(DISPLAY_RANGE_NITS[0],
+ strategy.convertToAdjustedNits(BACKLIGHT_RANGE_ZERO_TO_ONE[0]), TOLERANCE);
+ assertEquals(DISPLAY_RANGE_NITS[1],
+ strategy.convertToAdjustedNits(BACKLIGHT_RANGE_ZERO_TO_ONE[1]), TOLERANCE);
- // When adjustment is turned on, adjustment array is used
+ // Adjustment is turned on
strategy.recalculateSplines(true, adjustedNits50p);
+ assertEquals(DISPLAY_RANGE_NITS[0], strategy.convertToNits(BACKLIGHT_RANGE_ZERO_TO_ONE[0]),
+ TOLERANCE);
+ assertEquals(DISPLAY_RANGE_NITS[1], strategy.convertToNits(BACKLIGHT_RANGE_ZERO_TO_ONE[1]),
+ TOLERANCE);
assertEquals(DISPLAY_RANGE_NITS[0] / 2,
- strategy.convertToNits(BACKLIGHT_RANGE_ZERO_TO_ONE[0]), 0.0001f /* tolerance */);
+ strategy.convertToAdjustedNits(BACKLIGHT_RANGE_ZERO_TO_ONE[0]), TOLERANCE);
assertEquals(DISPLAY_RANGE_NITS[1] / 2,
- strategy.convertToNits(BACKLIGHT_RANGE_ZERO_TO_ONE[1]), 0.0001f /* tolerance */);
+ strategy.convertToAdjustedNits(BACKLIGHT_RANGE_ZERO_TO_ONE[1]), TOLERANCE);
- // When adjustment is turned off, adjustment array is ignored
+ // Adjustment is turned off
strategy.recalculateSplines(false, adjustedNits50p);
assertEquals(DISPLAY_RANGE_NITS[0], strategy.convertToNits(BACKLIGHT_RANGE_ZERO_TO_ONE[0]),
- 0.0001f /* tolerance */);
+ TOLERANCE);
assertEquals(DISPLAY_RANGE_NITS[1], strategy.convertToNits(BACKLIGHT_RANGE_ZERO_TO_ONE[1]),
- 0.0001f /* tolerance */);
+ TOLERANCE);
+ assertEquals(DISPLAY_RANGE_NITS[0],
+ strategy.convertToAdjustedNits(BACKLIGHT_RANGE_ZERO_TO_ONE[0]), TOLERANCE);
+ assertEquals(DISPLAY_RANGE_NITS[1],
+ strategy.convertToAdjustedNits(BACKLIGHT_RANGE_ZERO_TO_ONE[1]), TOLERANCE);
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java b/services/tests/servicestests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java
index cfb432ab3784..d7b12e031c35 100644
--- a/services/tests/servicestests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java
@@ -279,20 +279,27 @@ public final class DisplayBrightnessControllerTest {
@Test
public void testConvertToNits() {
- float brightness = 0.5f;
- float nits = 300;
+ final float brightness = 0.5f;
+ final float nits = 300;
+ final float adjustedNits = 200;
// ABC is null
assertEquals(-1f, mDisplayBrightnessController.convertToNits(brightness),
/* delta= */ 0);
+ assertEquals(-1f, mDisplayBrightnessController.convertToAdjustedNits(brightness),
+ /* delta= */ 0);
AutomaticBrightnessController automaticBrightnessController =
mock(AutomaticBrightnessController.class);
when(automaticBrightnessController.convertToNits(brightness)).thenReturn(nits);
+ when(automaticBrightnessController.convertToAdjustedNits(brightness))
+ .thenReturn(adjustedNits);
mDisplayBrightnessController.setAutomaticBrightnessController(
automaticBrightnessController);
assertEquals(nits, mDisplayBrightnessController.convertToNits(brightness), /* delta= */ 0);
+ assertEquals(adjustedNits, mDisplayBrightnessController.convertToAdjustedNits(brightness),
+ /* delta= */ 0);
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/power/hint/HintManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/power/hint/HintManagerServiceTest.java
index 136507d429fc..726a4e285d68 100644
--- a/services/tests/servicestests/src/com/android/server/power/hint/HintManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/hint/HintManagerServiceTest.java
@@ -57,6 +57,8 @@ import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.concurrent.CountDownLatch;
+
/**
* Tests for {@link com.android.server.power.hint.HintManagerService}.
*
@@ -149,29 +151,28 @@ public class HintManagerServiceTest {
// Set session to background and calling updateHintAllowed() would invoke pause();
service.mUidObserver.onUidStateChanged(
a.mUid, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0);
- final Object sync = new Object();
+
+ // Using CountDownLatch to ensure above onUidStateChanged() job was digested.
+ final CountDownLatch latch = new CountDownLatch(1);
FgThread.getHandler().post(() -> {
- synchronized (sync) {
- sync.notify();
- }
+ latch.countDown();
});
- synchronized (sync) {
- sync.wait();
- }
+ latch.await();
+
assumeFalse(a.updateHintAllowed());
verify(mNativeWrapperMock, times(1)).halPauseHintSession(anyLong());
// Set session to foreground and calling updateHintAllowed() would invoke resume();
service.mUidObserver.onUidStateChanged(
a.mUid, ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND, 0, 0);
+
+ // Using CountDownLatch to ensure above onUidStateChanged() job was digested.
+ final CountDownLatch latch2 = new CountDownLatch(1);
FgThread.getHandler().post(() -> {
- synchronized (sync) {
- sync.notify();
- }
+ latch2.countDown();
});
- synchronized (sync) {
- sync.wait();
- }
+ latch2.await();
+
assumeTrue(a.updateHintAllowed());
verify(mNativeWrapperMock, times(1)).halResumeHintSession(anyLong());
}
@@ -237,15 +238,14 @@ public class HintManagerServiceTest {
// Set session to background, then the duration would not be updated.
service.mUidObserver.onUidStateChanged(
a.mUid, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0);
- final Object sync = new Object();
+
+ // Using CountDownLatch to ensure above onUidStateChanged() job was digested.
+ final CountDownLatch latch = new CountDownLatch(1);
FgThread.getHandler().post(() -> {
- synchronized (sync) {
- sync.notify();
- }
+ latch.countDown();
});
- synchronized (sync) {
- sync.wait();
- }
+ latch.await();
+
assumeFalse(a.updateHintAllowed());
a.reportActualWorkDuration(DURATIONS_THREE, TIMESTAMPS_THREE);
verify(mNativeWrapperMock, never()).halReportActualWorkDuration(anyLong(), any(), any());
@@ -287,15 +287,14 @@ public class HintManagerServiceTest {
service.mUidObserver.onUidStateChanged(
a.mUid, ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND, 0, 0);
- final Object sync = new Object();
+
+ // Using CountDownLatch to ensure above onUidStateChanged() job was digested.
+ final CountDownLatch latch = new CountDownLatch(1);
FgThread.getHandler().post(() -> {
- synchronized (sync) {
- sync.notify();
- }
+ latch.countDown();
});
- synchronized (sync) {
- sync.wait();
- }
+ latch.await();
+
assertFalse(a.updateHintAllowed());
}
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/wakeups/CpuWakeupStatsTest.java b/services/tests/servicestests/src/com/android/server/power/stats/wakeups/CpuWakeupStatsTest.java
index dca67d6da7ad..76b6a820e4a7 100644
--- a/services/tests/servicestests/src/com/android/server/power/stats/wakeups/CpuWakeupStatsTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/stats/wakeups/CpuWakeupStatsTest.java
@@ -17,6 +17,8 @@
package com.android.server.power.stats.wakeups;
import static android.os.BatteryStatsInternal.CPU_WAKEUP_SUBSYSTEM_ALARM;
+import static android.os.BatteryStatsInternal.CPU_WAKEUP_SUBSYSTEM_CELLULAR_DATA;
+import static android.os.BatteryStatsInternal.CPU_WAKEUP_SUBSYSTEM_SENSOR;
import static android.os.BatteryStatsInternal.CPU_WAKEUP_SUBSYSTEM_SOUND_TRIGGER;
import static android.os.BatteryStatsInternal.CPU_WAKEUP_SUBSYSTEM_UNKNOWN;
import static android.os.BatteryStatsInternal.CPU_WAKEUP_SUBSYSTEM_WIFI;
@@ -50,6 +52,8 @@ public class CpuWakeupStatsTest {
private static final String KERNEL_REASON_ALARM_IRQ = "120 test.alarm.device";
private static final String KERNEL_REASON_WIFI_IRQ = "130 test.wifi.device";
private static final String KERNEL_REASON_SOUND_TRIGGER_IRQ = "129 test.sound_trigger.device";
+ private static final String KERNEL_REASON_SENSOR_IRQ = "15 test.sensor.device";
+ private static final String KERNEL_REASON_CELLULAR_DATA_IRQ = "18 test.cellular_data.device";
private static final String KERNEL_REASON_UNKNOWN_IRQ = "140 test.unknown.device";
private static final String KERNEL_REASON_UNKNOWN = "free-form-reason test.alarm.device";
private static final String KERNEL_REASON_ALARM_ABNORMAL = "-1 test.alarm.device";
@@ -110,6 +114,83 @@ public class CpuWakeupStatsTest {
}
@Test
+ public void irqAttributionAllCombinations() {
+ final int[] subsystems = new int[] {
+ CPU_WAKEUP_SUBSYSTEM_ALARM,
+ CPU_WAKEUP_SUBSYSTEM_WIFI,
+ CPU_WAKEUP_SUBSYSTEM_SOUND_TRIGGER,
+ CPU_WAKEUP_SUBSYSTEM_SENSOR,
+ CPU_WAKEUP_SUBSYSTEM_CELLULAR_DATA,
+ };
+
+ final String[] kernelReasons = new String[] {
+ KERNEL_REASON_ALARM_IRQ,
+ KERNEL_REASON_WIFI_IRQ,
+ KERNEL_REASON_SOUND_TRIGGER_IRQ,
+ KERNEL_REASON_SENSOR_IRQ,
+ KERNEL_REASON_CELLULAR_DATA_IRQ,
+ };
+
+ final int[] uids = new int[] {
+ TEST_UID_2, TEST_UID_3, TEST_UID_4, TEST_UID_1, TEST_UID_5
+ };
+
+ final int[] procStates = new int[] {
+ TEST_PROC_STATE_2,
+ TEST_PROC_STATE_3,
+ TEST_PROC_STATE_4,
+ TEST_PROC_STATE_1,
+ TEST_PROC_STATE_5
+ };
+
+ final int total = subsystems.length;
+ assertThat(kernelReasons.length).isEqualTo(total);
+ assertThat(uids.length).isEqualTo(total);
+ assertThat(procStates.length).isEqualTo(total);
+
+ for (int mask = 1; mask < (1 << total); mask++) {
+ final CpuWakeupStats obj = new CpuWakeupStats(sContext, R.xml.irq_device_map_3,
+ mHandler);
+ populateDefaultProcStates(obj);
+
+ final long wakeupTime = mRandom.nextLong(123456);
+
+ String combinedKernelReason = null;
+ for (int i = 0; i < total; i++) {
+ if ((mask & (1 << i)) != 0) {
+ combinedKernelReason = (combinedKernelReason == null)
+ ? kernelReasons[i]
+ : String.join(":", combinedKernelReason, kernelReasons[i]);
+ }
+
+ obj.noteWakingActivity(subsystems[i], wakeupTime + 2, uids[i]);
+ }
+ obj.noteWakeupTimeAndReason(wakeupTime, 1, combinedKernelReason);
+
+ assertThat(obj.mWakeupAttribution.size()).isEqualTo(1);
+
+ final SparseArray<SparseIntArray> attribution = obj.mWakeupAttribution.get(wakeupTime);
+ assertThat(attribution.size()).isEqualTo(Integer.bitCount(mask));
+
+ for (int i = 0; i < total; i++) {
+ if ((mask & (1 << i)) == 0) {
+ assertThat(attribution.contains(subsystems[i])).isFalse();
+ continue;
+ }
+ assertThat(attribution.contains(subsystems[i])).isTrue();
+ assertThat(attribution.get(subsystems[i]).get(uids[i])).isEqualTo(procStates[i]);
+
+ for (int j = 0; j < total; j++) {
+ if (i == j) {
+ continue;
+ }
+ assertThat(attribution.get(subsystems[i]).indexOfKey(uids[j])).isLessThan(0);
+ }
+ }
+ }
+ }
+
+ @Test
public void alarmIrqAttributionSolo() {
final CpuWakeupStats obj = new CpuWakeupStats(sContext, R.xml.irq_device_map_3, mHandler);
final long wakeupTime = 12423121;
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
index 8fcbf2f9e97a..541739d50024 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
@@ -96,6 +96,7 @@ import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Set;
+import java.util.concurrent.CountDownLatch;
public class ManagedServicesTest extends UiServiceTestCase {
@@ -1920,6 +1921,18 @@ public class ManagedServicesTest extends UiServiceTestCase {
assertTrue(service.isBound(cn_disallowed, 0));
}
+ @Test
+ public void isComponentEnabledForCurrentProfiles_isThreadSafe() throws InterruptedException {
+ for (UserInfo userInfo : mUm.getUsers()) {
+ mService.addApprovedList("pkg1/cmp1:pkg2/cmp2:pkg3/cmp3", userInfo.id, true);
+ }
+ testThreadSafety(() -> {
+ mService.rebindServices(false, 0);
+ assertThat(mService.isComponentEnabledForCurrentProfiles(
+ new ComponentName("pkg1", "cmp1"))).isTrue();
+ }, 20, 30);
+ }
+
private void mockServiceInfoWithMetaData(List<ComponentName> componentNames,
ManagedServices service, ArrayMap<ComponentName, Bundle> metaDatas)
throws RemoteException {
@@ -2298,4 +2311,38 @@ public class ManagedServicesTest extends UiServiceTestCase {
return false;
}
}
+
+ /**
+ * Helper method to test the thread safety of some operations.
+ *
+ * <p>Runs the supplied {@code operationToTest}, {@code nRunsPerThread} times,
+ * concurrently using {@code nThreads} threads, and waits for all of them to finish.
+ */
+ private static void testThreadSafety(Runnable operationToTest, int nThreads,
+ int nRunsPerThread) throws InterruptedException {
+ final CountDownLatch startLatch = new CountDownLatch(1);
+ final CountDownLatch doneLatch = new CountDownLatch(nThreads);
+
+ for (int i = 0; i < nThreads; i++) {
+ Runnable threadRunnable = () -> {
+ try {
+ startLatch.await();
+ for (int j = 0; j < nRunsPerThread; j++) {
+ operationToTest.run();
+ }
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ } finally {
+ doneLatch.countDown();
+ }
+ };
+ new Thread(threadRunnable, "Test Thread #" + i).start();
+ }
+
+ // Ready set go
+ startLatch.countDown();
+
+ // Wait for all test threads to be done.
+ doneLatch.await();
+ }
}
diff --git a/services/tests/wmtests/AndroidManifest.xml b/services/tests/wmtests/AndroidManifest.xml
index f12b53acd06b..fe7cd4a5edd9 100644
--- a/services/tests/wmtests/AndroidManifest.xml
+++ b/services/tests/wmtests/AndroidManifest.xml
@@ -89,6 +89,12 @@
<activity android:name="com.android.server.wm.SurfaceControlViewHostTests$TestActivity" />
+ <activity android:name="android.server.wm.scvh.SurfaceSyncGroupActivity"
+ android:screenOrientation="locked"
+ android:turnScreenOn="true"
+ android:theme="@style/WhiteBackgroundTheme"
+ android:exported="true"/>
+
<service android:name="android.view.cts.surfacevalidator.LocalMediaProjectionService"
android:foregroundServiceType="mediaProjection"
android:enabled="true">
diff --git a/services/tests/wmtests/src/com/android/server/wm/SurfaceSyncGroupTests.java b/services/tests/wmtests/src/com/android/server/wm/SurfaceSyncGroupTests.java
new file mode 100644
index 000000000000..9db647acebb7
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/SurfaceSyncGroupTests.java
@@ -0,0 +1,187 @@
+/*
+ * 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.wm;
+
+import static android.window.SurfaceSyncGroup.TRANSACTION_READY_TIMEOUT;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import android.app.Instrumentation;
+import android.graphics.Bitmap;
+import android.graphics.Color;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.platform.test.annotations.Presubmit;
+import android.server.wm.scvh.SurfaceSyncGroupActivity;
+import android.view.SurfaceControl;
+import android.view.View;
+import android.view.ViewTreeObserver;
+import android.view.WindowManager;
+import android.view.cts.surfacevalidator.BitmapPixelChecker;
+import android.window.SurfaceSyncGroup;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.rule.ActivityTestRule;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+@Presubmit
+public class SurfaceSyncGroupTests {
+ private static final String TAG = "SurfaceSyncGroupTests";
+
+ @Rule
+ public ActivityTestRule<SurfaceSyncGroupActivity> mActivityRule = new ActivityTestRule<>(
+ SurfaceSyncGroupActivity.class);
+
+ private SurfaceSyncGroupActivity mActivity;
+
+ Instrumentation mInstrumentation;
+
+ private final HandlerThread mHandlerThread = new HandlerThread("applyTransaction");
+ private Handler mHandler;
+
+ @Before
+ public void setup() {
+ mActivity = mActivityRule.getActivity();
+ mInstrumentation = InstrumentationRegistry.getInstrumentation();
+ mHandlerThread.start();
+ mHandler = mHandlerThread.getThreadHandler();
+ }
+
+ @Test
+ public void testOverlappingSyncsEnsureOrder_WhenTimeout() throws InterruptedException {
+ WindowManager.LayoutParams params = new WindowManager.LayoutParams();
+ params.format = PixelFormat.TRANSLUCENT;
+
+ CountDownLatch secondDrawCompleteLatch = new CountDownLatch(1);
+ CountDownLatch bothSyncGroupsComplete = new CountDownLatch(2);
+ final SurfaceSyncGroup firstSsg = new SurfaceSyncGroup(TAG + "-first");
+ final SurfaceSyncGroup secondSsg = new SurfaceSyncGroup(TAG + "-second");
+ final SurfaceSyncGroup infiniteSsg = new SurfaceSyncGroup(TAG + "-infinite");
+
+ SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+ t.addTransactionCommittedListener(Runnable::run, bothSyncGroupsComplete::countDown);
+ firstSsg.addTransaction(t);
+
+ View backgroundView = mActivity.getBackgroundView();
+ firstSsg.add(backgroundView.getRootSurfaceControl(),
+ () -> mActivity.runOnUiThread(() -> backgroundView.setBackgroundColor(Color.RED)));
+
+ addSecondSyncGroup(secondSsg, secondDrawCompleteLatch, bothSyncGroupsComplete);
+
+ assertTrue("Failed to draw two frames",
+ secondDrawCompleteLatch.await(5, TimeUnit.SECONDS));
+
+ mHandler.postDelayed(() -> {
+ // Don't add a markSyncReady for the first sync group until after it's added to another
+ // SSG to ensure the timeout is longer than the second frame's timeout. The infinite SSG
+ // will never complete to ensure it reaches the timeout, but only after the second SSG
+ // had a chance to reach its timeout.
+ infiniteSsg.add(firstSsg, null /* runnable */);
+ firstSsg.markSyncReady();
+ }, 200);
+
+ assertTrue("Failed to wait for both SurfaceSyncGroups to apply",
+ bothSyncGroupsComplete.await(5, TimeUnit.SECONDS));
+
+ validateScreenshot();
+ }
+
+ @Test
+ public void testOverlappingSyncsEnsureOrder_WhileHoldingTransaction()
+ throws InterruptedException {
+ WindowManager.LayoutParams params = new WindowManager.LayoutParams();
+ params.format = PixelFormat.TRANSLUCENT;
+
+ CountDownLatch secondDrawCompleteLatch = new CountDownLatch(1);
+ CountDownLatch bothSyncGroupsComplete = new CountDownLatch(2);
+
+ final SurfaceSyncGroup firstSsg = new SurfaceSyncGroup(TAG + "-first",
+ transaction -> mHandler.postDelayed(() -> {
+ try {
+ assertTrue("Failed to draw two frames",
+ secondDrawCompleteLatch.await(5, TimeUnit.SECONDS));
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ transaction.apply();
+ }, TRANSACTION_READY_TIMEOUT + 200));
+ final SurfaceSyncGroup secondSsg = new SurfaceSyncGroup(TAG + "-second");
+
+ SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+ t.addTransactionCommittedListener(Runnable::run, bothSyncGroupsComplete::countDown);
+ firstSsg.addTransaction(t);
+
+ View backgroundView = mActivity.getBackgroundView();
+ firstSsg.add(backgroundView.getRootSurfaceControl(),
+ () -> mActivity.runOnUiThread(() -> backgroundView.setBackgroundColor(Color.RED)));
+ firstSsg.markSyncReady();
+
+ addSecondSyncGroup(secondSsg, secondDrawCompleteLatch, bothSyncGroupsComplete);
+
+ assertTrue("Failed to wait for both SurfaceSyncGroups to apply",
+ bothSyncGroupsComplete.await(5, TimeUnit.SECONDS));
+
+ validateScreenshot();
+ }
+
+ private void addSecondSyncGroup(SurfaceSyncGroup surfaceSyncGroup,
+ CountDownLatch waitForSecondDraw, CountDownLatch bothSyncGroupsComplete) {
+ View backgroundView = mActivity.getBackgroundView();
+ ViewTreeObserver viewTreeObserver = backgroundView.getViewTreeObserver();
+ viewTreeObserver.registerFrameCommitCallback(() -> mHandler.post(() -> {
+ surfaceSyncGroup.add(backgroundView.getRootSurfaceControl(),
+ () -> mActivity.runOnUiThread(
+ () -> backgroundView.setBackgroundColor(Color.BLUE)));
+
+ SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+ t.addTransactionCommittedListener(Runnable::run, bothSyncGroupsComplete::countDown);
+ surfaceSyncGroup.addTransaction(t);
+ surfaceSyncGroup.markSyncReady();
+ viewTreeObserver.registerFrameCommitCallback(waitForSecondDraw::countDown);
+ }));
+ }
+
+ private void validateScreenshot() {
+ Bitmap screenshot = mInstrumentation.getUiAutomation().takeScreenshot(
+ mActivity.getWindow());
+ assertNotNull("Failed to generate a screenshot", screenshot);
+ Bitmap swBitmap = screenshot.copy(Bitmap.Config.ARGB_8888, false);
+ screenshot.recycle();
+
+ BitmapPixelChecker pixelChecker = new BitmapPixelChecker(Color.BLUE);
+ int halfWidth = swBitmap.getWidth() / 2;
+ int halfHeight = swBitmap.getHeight() / 2;
+ // We don't need to check all the pixels since we only care that at least some of them are
+ // blue. If the buffers were submitted out of order, all the pixels will be red.
+ Rect bounds = new Rect(halfWidth, halfHeight, halfWidth + 10, halfHeight + 10);
+ int numMatchingPixels = pixelChecker.getNumMatchingPixels(swBitmap, bounds);
+ assertEquals("Expected 100 received " + numMatchingPixels + " matching pixels", 100,
+ numMatchingPixels);
+
+ swBitmap.recycle();
+ }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
index 43b429c76749..653b52b06720 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
@@ -109,14 +109,19 @@ public class TransitionTests extends WindowTestsBase {
final SurfaceControl.Transaction mMockT = mock(SurfaceControl.Transaction.class);
private BLASTSyncEngine mSyncEngine;
+ private Transition createTestTransition(int transitType, TransitionController controller) {
+ return new Transition(transitType, 0 /* flags */, controller, controller.mSyncEngine);
+ }
+
private Transition createTestTransition(int transitType) {
final TransitionController controller = new TestTransitionController(
mock(ActivityTaskManagerService.class));
mSyncEngine = createTestBLASTSyncEngine();
- final Transition t = new Transition(transitType, 0 /* flags */, controller, mSyncEngine);
- t.startCollecting(0 /* timeoutMs */);
- return t;
+ controller.setSyncEngine(mSyncEngine);
+ final Transition out = createTestTransition(transitType, controller);
+ out.startCollecting(0 /* timeoutMs */);
+ return out;
}
@Test
@@ -367,7 +372,6 @@ public class TransitionTests extends WindowTestsBase {
final ActivityRecord act = createActivityRecord(tasks[i]);
// alternate so that the transition doesn't get promoted to the display area
act.setVisibleRequested((i % 2) == 0); // starts invisible
- act.visibleIgnoringKeyguard = (i % 2) == 0;
if (i == showWallpaperTask) {
doReturn(true).when(act).showWallpaper();
}
@@ -754,10 +758,8 @@ public class TransitionTests extends WindowTestsBase {
final ActivityRecord closing = createActivityRecord(oldTask);
closing.setOccludesParent(true);
- closing.visibleIgnoringKeyguard = true;
final ActivityRecord opening = createActivityRecord(newTask);
opening.setOccludesParent(true);
- opening.visibleIgnoringKeyguard = true;
// Start states.
changes.put(newTask, new Transition.ChangeInfo(newTask, false /* vis */, true /* exChg */));
changes.put(oldTask, new Transition.ChangeInfo(oldTask, true /* vis */, false /* exChg */));
@@ -795,10 +797,8 @@ public class TransitionTests extends WindowTestsBase {
final ActivityRecord closing = createActivityRecord(oldTask);
closing.setOccludesParent(true);
- closing.visibleIgnoringKeyguard = true;
final ActivityRecord opening = createActivityRecord(newTask);
opening.setOccludesParent(false);
- opening.visibleIgnoringKeyguard = true;
// Start states.
changes.put(newTask, new Transition.ChangeInfo(newTask, false /* vis */, true /* exChg */));
changes.put(oldTask, new Transition.ChangeInfo(oldTask, true /* vis */, false /* exChg */));
@@ -837,10 +837,8 @@ public class TransitionTests extends WindowTestsBase {
final ActivityRecord closing = closingTaskFragment.getTopMostActivity();
closing.setOccludesParent(true);
- closing.visibleIgnoringKeyguard = true;
final ActivityRecord opening = openingTaskFragment.getTopMostActivity();
opening.setOccludesParent(true);
- opening.visibleIgnoringKeyguard = true;
// Start states.
changes.put(openingTaskFragment, new Transition.ChangeInfo(openingTaskFragment,
false /* vis */, true /* exChg */));
@@ -881,10 +879,8 @@ public class TransitionTests extends WindowTestsBase {
final ActivityRecord closing = closingTaskFragment.getTopMostActivity();
closing.setOccludesParent(true);
- closing.visibleIgnoringKeyguard = true;
final ActivityRecord opening = openingTaskFragment.getTopMostActivity();
opening.setOccludesParent(false);
- opening.visibleIgnoringKeyguard = true;
// Start states.
changes.put(openingTaskFragment, new Transition.ChangeInfo(openingTaskFragment,
false /* vis */, true /* exChg */));
@@ -925,10 +921,8 @@ public class TransitionTests extends WindowTestsBase {
final ActivityRecord opening = openingTaskFragment.getTopMostActivity();
opening.setOccludesParent(true);
- opening.visibleIgnoringKeyguard = true;
final ActivityRecord closing = closingTaskFragment.getTopMostActivity();
closing.setOccludesParent(true);
- closing.visibleIgnoringKeyguard = true;
closing.finishing = true;
// Start states.
changes.put(openingTaskFragment, new Transition.ChangeInfo(openingTaskFragment,
@@ -970,10 +964,8 @@ public class TransitionTests extends WindowTestsBase {
final ActivityRecord opening = openingTaskFragment.getTopMostActivity();
opening.setOccludesParent(true);
- opening.visibleIgnoringKeyguard = true;
final ActivityRecord closing = closingTaskFragment.getTopMostActivity();
closing.setOccludesParent(false);
- closing.visibleIgnoringKeyguard = true;
closing.finishing = true;
// Start states.
changes.put(openingTaskFragment, new Transition.ChangeInfo(openingTaskFragment,
@@ -1282,6 +1274,7 @@ public class TransitionTests extends WindowTestsBase {
@Test
public void testIntermediateVisibility() {
final TransitionController controller = new TestTransitionController(mAtm);
+ controller.setSyncEngine(mWm.mSyncEngine);
final ITransitionPlayer player = new ITransitionPlayer.Default();
controller.registerTransitionPlayer(player, null /* playerProc */);
final Transition openTransition = controller.createTransition(TRANSIT_OPEN);
@@ -1365,6 +1358,7 @@ public class TransitionTests extends WindowTestsBase {
super.dispatchLegacyAppTransitionFinished(ar);
}
};
+ controller.setSyncEngine(mWm.mSyncEngine);
controller.mSnapshotController = mWm.mSnapshotController;
final TaskSnapshotController taskSnapshotController = controller.mSnapshotController
.mTaskSnapshotController;
@@ -1462,6 +1456,7 @@ public class TransitionTests extends WindowTestsBase {
@Test
public void testNotReadyPushPop() {
final TransitionController controller = new TestTransitionController(mAtm);
+ controller.setSyncEngine(mWm.mSyncEngine);
final ITransitionPlayer player = new ITransitionPlayer.Default();
controller.registerTransitionPlayer(player, null /* playerProc */);
final Transition openTransition = controller.createTransition(TRANSIT_OPEN);
@@ -1918,6 +1913,110 @@ public class TransitionTests extends WindowTestsBase {
assertEquals(TRANSIT_CHANGE, info.getChanges().get(0).getMode());
}
+ @Test
+ public void testQueueStartCollect() {
+ final TransitionController controller = mAtm.getTransitionController();
+ final TestTransitionPlayer player = registerTestTransitionPlayer();
+
+ mSyncEngine = createTestBLASTSyncEngine();
+ controller.setSyncEngine(mSyncEngine);
+
+ final Transition transitA = createTestTransition(TRANSIT_OPEN, controller);
+ final Transition transitB = createTestTransition(TRANSIT_OPEN, controller);
+ final Transition transitC = createTestTransition(TRANSIT_OPEN, controller);
+
+ final boolean[] onStartA = new boolean[]{false, false};
+ final boolean[] onStartB = new boolean[]{false, false};
+ controller.startCollectOrQueue(transitA, (deferred) -> {
+ onStartA[0] = true;
+ onStartA[1] = deferred;
+ });
+ controller.startCollectOrQueue(transitB, (deferred) -> {
+ onStartB[0] = true;
+ onStartB[1] = deferred;
+ });
+ waitUntilHandlersIdle();
+
+ assertTrue(onStartA[0]);
+ assertFalse(onStartA[1]);
+ assertTrue(transitA.isCollecting());
+
+ // B should be queued, so no calls yet
+ assertFalse(onStartB[0]);
+ assertTrue(transitB.isPending());
+
+ // finish collecting A
+ transitA.start();
+ transitA.setAllReady();
+ mSyncEngine.tryFinishForTest(transitA.getSyncId());
+ waitUntilHandlersIdle();
+
+ assertTrue(transitA.isPlaying());
+ assertTrue(transitB.isCollecting());
+ assertTrue(onStartB[0]);
+ // Should receive deferred = true
+ assertTrue(onStartB[1]);
+
+ // finish collecting B
+ transitB.start();
+ transitB.setAllReady();
+ mSyncEngine.tryFinishForTest(transitB.getSyncId());
+ assertTrue(transitB.isPlaying());
+
+ // Now we should be able to start collecting directly a new transition
+ final boolean[] onStartC = new boolean[]{false, false};
+ controller.startCollectOrQueue(transitC, (deferred) -> {
+ onStartC[0] = true;
+ onStartC[1] = deferred;
+ });
+ waitUntilHandlersIdle();
+ assertTrue(onStartC[0]);
+ assertFalse(onStartC[1]);
+ assertTrue(transitC.isCollecting());
+ }
+
+ @Test
+ public void testQueueWithLegacy() {
+ final TransitionController controller = mAtm.getTransitionController();
+ final TestTransitionPlayer player = registerTestTransitionPlayer();
+
+ mSyncEngine = createTestBLASTSyncEngine();
+ controller.setSyncEngine(mSyncEngine);
+
+ final Transition transitA = createTestTransition(TRANSIT_OPEN, controller);
+ final Transition transitB = createTestTransition(TRANSIT_OPEN, controller);
+
+ controller.startCollectOrQueue(transitA, (deferred) -> {});
+
+ BLASTSyncEngine.SyncGroup legacySync = mSyncEngine.prepareSyncSet(
+ mock(BLASTSyncEngine.TransactionReadyListener.class), "test");
+ final boolean[] applyLegacy = new boolean[]{false};
+ controller.startLegacySyncOrQueue(legacySync, () -> applyLegacy[0] = true);
+ assertFalse(applyLegacy[0]);
+ waitUntilHandlersIdle();
+
+ controller.startCollectOrQueue(transitB, (deferred) -> {});
+ assertTrue(transitA.isCollecting());
+
+ // finish collecting A
+ transitA.start();
+ transitA.setAllReady();
+ mSyncEngine.tryFinishForTest(transitA.getSyncId());
+ waitUntilHandlersIdle();
+
+ assertTrue(transitA.isPlaying());
+ // legacy sync should start now
+ assertTrue(applyLegacy[0]);
+ // transitB must wait
+ assertTrue(transitB.isPending());
+
+ // finish legacy sync
+ mSyncEngine.setReady(legacySync.mSyncId);
+ mSyncEngine.tryFinishForTest(legacySync.mSyncId);
+ // transitioncontroller should be notified so it can start collecting B
+ assertTrue(transitB.isCollecting());
+ }
+
private static void makeTaskOrganized(Task... tasks) {
final ITaskOrganizer organizer = mock(ITaskOrganizer.class);
for (Task t : tasks) {
diff --git a/telecomm/java/android/telecom/CallStreamingService.java b/telecomm/java/android/telecom/CallStreamingService.java
index 3f538a7f262d..df48cd6a16e2 100644
--- a/telecomm/java/android/telecom/CallStreamingService.java
+++ b/telecomm/java/android/telecom/CallStreamingService.java
@@ -52,6 +52,7 @@ import java.lang.annotation.RetentionPolicy;
* </service>
* }
* </pre>
+ *
* @hide
*/
@SystemApi
@@ -65,7 +66,7 @@ public abstract class CallStreamingService extends Service {
private static final int MSG_SET_STREAMING_CALL_ADAPTER = 1;
private static final int MSG_CALL_STREAMING_STARTED = 2;
private static final int MSG_CALL_STREAMING_STOPPED = 3;
- private static final int MSG_CALL_STREAMING_CHANGED_CHANGED = 4;
+ private static final int MSG_CALL_STREAMING_STATE_CHANGED = 4;
/** Default Handler used to consolidate binder method calls onto a single thread. */
private final Handler mHandler = new Handler(Looper.getMainLooper()) {
@@ -77,8 +78,10 @@ public abstract class CallStreamingService extends Service {
switch (msg.what) {
case MSG_SET_STREAMING_CALL_ADAPTER:
- mStreamingCallAdapter = new StreamingCallAdapter(
- (IStreamingCallAdapter) msg.obj);
+ if (msg.obj != null) {
+ mStreamingCallAdapter = new StreamingCallAdapter(
+ (IStreamingCallAdapter) msg.obj);
+ }
break;
case MSG_CALL_STREAMING_STARTED:
mCall = (StreamingCall) msg.obj;
@@ -90,10 +93,12 @@ public abstract class CallStreamingService extends Service {
mStreamingCallAdapter = null;
CallStreamingService.this.onCallStreamingStopped();
break;
- case MSG_CALL_STREAMING_CHANGED_CHANGED:
+ case MSG_CALL_STREAMING_STATE_CHANGED:
int state = (int) msg.obj;
- mCall.requestStreamingState(state);
- CallStreamingService.this.onCallStreamingStateChanged(state);
+ if (mStreamingCallAdapter != null) {
+ mCall.requestStreamingState(state);
+ CallStreamingService.this.onCallStreamingStateChanged(state);
+ }
break;
default:
break;
@@ -128,7 +133,7 @@ public abstract class CallStreamingService extends Service {
@Override
public void onCallStreamingStateChanged(int state) throws RemoteException {
- mHandler.obtainMessage(MSG_CALL_STREAMING_CHANGED_CHANGED, state).sendToTarget();
+ mHandler.obtainMessage(MSG_CALL_STREAMING_STATE_CHANGED, state).sendToTarget();
}
}
@@ -173,9 +178,12 @@ public abstract class CallStreamingService extends Service {
STREAMING_FAILED_ALREADY_STREAMING,
STREAMING_FAILED_NO_SENDER,
STREAMING_FAILED_SENDER_BINDING_ERROR
- })
+ })
@Retention(RetentionPolicy.SOURCE)
- public @interface StreamingFailedReason {};
+ public @interface StreamingFailedReason {
+ }
+
+ ;
/**
* Called when a {@code StreamingCall} has been added to this call streaming session. The call
diff --git a/telephony/java/android/telephony/AnomalyReporter.java b/telephony/java/android/telephony/AnomalyReporter.java
index d676eeec4024..db38f8873a02 100644
--- a/telephony/java/android/telephony/AnomalyReporter.java
+++ b/telephony/java/android/telephony/AnomalyReporter.java
@@ -105,6 +105,8 @@ public final class AnomalyReporter {
* @param carrierId the carrier of the id associated with this event.
*/
public static void reportAnomaly(@NonNull UUID eventId, String description, int carrierId) {
+ Rlog.i(TAG, "reportAnomaly: Received anomaly event report with eventId= " + eventId
+ + " and description= " + description);
if (sContext == null) {
Rlog.w(TAG, "AnomalyReporter not yet initialized, dropping event=" + eventId);
return;
diff --git a/telephony/java/android/telephony/TelephonyScanManager.java b/telephony/java/android/telephony/TelephonyScanManager.java
index 19f2a9bc67a3..b7617099bae3 100644
--- a/telephony/java/android/telephony/TelephonyScanManager.java
+++ b/telephony/java/android/telephony/TelephonyScanManager.java
@@ -153,8 +153,9 @@ public final class TelephonyScanManager {
nsi = mScanInfo.get(message.arg2);
}
if (nsi == null) {
- throw new RuntimeException(
- "Failed to find NetworkScanInfo with id " + message.arg2);
+ Rlog.e(TAG, "Unexpceted message " + message.what
+ + " as there is no NetworkScanInfo with id " + message.arg2);
+ return;
}
final NetworkScanCallback callback = nsi.mCallback;