summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java75
-rw-r--r--core/api/current.txt20
-rw-r--r--core/api/system-current.txt7
-rw-r--r--core/java/android/app/Activity.java17
-rw-r--r--core/java/android/app/ActivityThread.java84
-rw-r--r--core/java/android/app/ClientTransactionHandler.java7
-rw-r--r--core/java/android/app/servertransaction/TransferSplashScreenViewStateItem.java38
-rw-r--r--core/java/android/hardware/camera2/CameraCharacteristics.java19
-rw-r--r--core/java/android/hardware/camera2/CameraManager.java17
-rw-r--r--core/java/android/hardware/camera2/impl/CameraMetadataNative.java9
-rw-r--r--core/java/android/hardware/camera2/params/DeviceStateSensorOrientationMap.java (renamed from core/java/android/hardware/camera2/params/DeviceStateOrientationMap.java)13
-rw-r--r--core/java/android/inputmethodservice/InputMethodService.java5
-rw-r--r--core/java/android/service/notification/NotificationListenerService.java9
-rw-r--r--core/java/android/view/AttachedSurfaceControl.java35
-rw-r--r--core/java/android/view/SurfaceControl.java77
-rw-r--r--core/java/android/view/SurfaceView.java39
-rw-r--r--core/java/android/view/View.java5
-rw-r--r--core/java/android/view/ViewRootImpl.java27
-rw-r--r--core/java/android/view/WindowManagerImpl.java35
-rw-r--r--core/java/android/window/SplashScreen.java5
-rw-r--r--core/java/android/window/SplashScreenView.java12
-rw-r--r--core/java/com/android/internal/policy/IKeyguardStateCallback.aidl1
-rw-r--r--core/java/com/android/internal/protolog/ProtoLogGroup.java1
-rw-r--r--core/jni/android_view_SurfaceControl.cpp8
-rw-r--r--core/res/res/values/config.xml2
-rw-r--r--data/etc/services.core.protolog.json87
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/sidecar/SidecarProvider.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/ISplitScreen.aidl103
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/ISplitScreenListener.aidl33
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/MainStage.java104
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/OutlineManager.java181
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/OutlineView.java82
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SideStage.java144
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitScreen.java99
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitScreenController.java595
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitScreenTransitions.java298
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitscreenEventLogger.java324
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/StageCoordinator.java1325
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/StageTaskListener.java288
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/StageTaskUnfoldController.java224
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenIconDrawableFactory.java10
-rw-r--r--media/java/android/media/AudioTrack.java12
-rwxr-xr-xmedia/java/android/media/IAudioService.aidl7
-rw-r--r--media/java/android/media/ISpatializerOutputCallback.aidl27
-rw-r--r--media/java/android/media/Spatializer.java126
-rw-r--r--packages/SystemUI/Android.bp16
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/Interpolators.java6
-rw-r--r--packages/SystemUI/docs/keyguard.md34
-rw-r--r--packages/SystemUI/docs/keyguard/aod.md1
-rw-r--r--packages/SystemUI/docs/keyguard/bouncer.md6
-rw-r--r--packages/SystemUI/plugin/src/com/android/systemui/flags/Flags.java6
-rw-r--r--packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java15
-rw-r--r--packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml31
-rw-r--r--packages/SystemUI/res-keyguard/layout/keyguard_slice_view.xml (renamed from packages/SystemUI/res-keyguard/layout/keyguard_status_area.xml)7
-rw-r--r--packages/SystemUI/res/drawable/media_output_dialog_button_background.xml29
-rw-r--r--packages/SystemUI/res/layout/media_output_dialog.xml41
-rw-r--r--packages/SystemUI/res/layout/media_output_list_item.xml89
-rw-r--r--packages/SystemUI/res/layout/qs_user_dialog_content.xml128
-rw-r--r--packages/SystemUI/res/layout/status_bar_expanded.xml12
-rw-r--r--packages/SystemUI/res/layout/udfps_aod_lock_icon.xml27
-rw-r--r--packages/SystemUI/res/values-night/styles.xml4
-rw-r--r--packages/SystemUI/res/values/dimens.xml8
-rw-r--r--packages/SystemUI/res/values/flags.xml7
-rw-r--r--packages/SystemUI/res/values/strings.xml2
-rw-r--r--packages/SystemUI/res/values/styles.xml24
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl9
-rw-r--r--packages/SystemUI/src-debug/com/android/systemui/flags/FeatureFlagManager.java88
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java53
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java39
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java2
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java89
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardSliceViewController.java13
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java2
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java5
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java91
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java10
-rw-r--r--packages/SystemUI/src/com/android/keyguard/LockIconViewController.java77
-rw-r--r--packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusViewModule.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/AutoReinflateContainer.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsHapticsSimulator.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/FeatureFlagManager.java38
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/FeatureFlagReader.java86
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.java49
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/FlagReader.java (renamed from packages/SystemUI/plugin/src/com/android/systemui/plugins/FlagReaderPlugin.java)37
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/FlagWriter.kt21
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/SystemPropertiesHelper.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java23
-rw-r--r--packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/log/dagger/SwipeStatusBarAwayLog.java36
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/SeekBarObserver.kt21
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java13
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputGroupAdapter.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/NavigationModeController.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java92
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java14
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSFragment.java51
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/user/UserDialog.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt14
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java13
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java31
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt15
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt25
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/charging/WiredChargingRippleController.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java22
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeStatusBarAwayGestureHandler.kt152
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeStatusBarAwayGestureLogger.kt64
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java14
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoHideController.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextView.java137
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java21
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java24
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java56
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java60
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowController.java20
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/TapAgainViewController.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt102
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/ConfigurationController.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java29
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java7
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagManagerTest.java46
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagReaderTest.java6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsTest.java15
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java89
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/SeekBarObserverTest.kt4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputGroupAdapterTest.java5
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/user/UserSwitchDialogControllerTest.kt6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java37
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java44
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt142
-rw-r--r--services/core/java/com/android/server/audio/AudioService.java21
-rw-r--r--services/core/java/com/android/server/audio/SpatializerHelper.java83
-rw-r--r--services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java5
-rw-r--r--services/core/java/com/android/server/policy/PhoneWindowManager.java14
-rw-r--r--services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java7
-rw-r--r--services/core/java/com/android/server/policy/keyguard/KeyguardServiceWrapper.java4
-rw-r--r--services/core/java/com/android/server/policy/keyguard/KeyguardStateMonitor.java10
-rw-r--r--services/core/java/com/android/server/vibrator/VibratorManagerService.java22
-rw-r--r--services/core/java/com/android/server/wm/ActivityRecord.java18
-rw-r--r--services/core/java/com/android/server/wm/ActivityStartController.java3
-rw-r--r--services/core/java/com/android/server/wm/ActivityStarter.java2
-rw-r--r--services/core/java/com/android/server/wm/AppTransitionController.java82
-rw-r--r--services/core/java/com/android/server/wm/DisplayContent.java32
-rw-r--r--services/core/java/com/android/server/wm/DisplayPolicy.java57
-rw-r--r--services/core/java/com/android/server/wm/KeyguardController.java39
-rw-r--r--services/core/java/com/android/server/wm/PossibleDisplayInfoMapper.java9
-rw-r--r--services/core/java/com/android/server/wm/RecentsAnimationController.java2
-rw-r--r--services/core/java/com/android/server/wm/RemoteAnimationController.java6
-rw-r--r--services/core/java/com/android/server/wm/RootWindowContainer.java7
-rw-r--r--services/core/java/com/android/server/wm/TaskOrganizerController.java17
-rw-r--r--services/core/java/com/android/server/wm/WallpaperAnimationAdapter.java15
-rw-r--r--services/core/java/com/android/server/wm/WallpaperController.java35
-rw-r--r--services/core/java/com/android/server/wm/WallpaperWindowToken.java7
-rw-r--r--services/core/java/com/android/server/wm/WindowContainer.java13
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerDebugConfig.java1
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerService.java17
-rw-r--r--services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java40
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java28
182 files changed, 6501 insertions, 1556 deletions
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java b/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java
index b49bbc5fca89..d4e32396187d 100644
--- a/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java
+++ b/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java
@@ -18,7 +18,6 @@ package com.android.server.appsearch;
import static android.app.appsearch.AppSearchResult.throwableToFailedResult;
import static android.os.Process.INVALID_UID;
-import android.Manifest;
import android.annotation.ElapsedRealtimeLong;
import android.annotation.NonNull;
import android.app.appsearch.AppSearchBatchResult;
@@ -332,7 +331,6 @@ public class AppSearchManagerService extends SystemService {
Objects.requireNonNull(callback);
long totalLatencyStartTimeMillis = SystemClock.elapsedRealtime();
- int callingPid = Binder.getCallingPid();
int callingUid = Binder.getCallingUid();
EXECUTOR.execute(() -> {
@AppSearchResult.ResultCode int statusCode = AppSearchResult.RESULT_OK;
@@ -345,7 +343,7 @@ public class AppSearchManagerService extends SystemService {
// Obtain the user where the client wants to run the operations in. This should
// end up being the same as userHandle, assuming it is not a special user and
// the client is allowed to run operations in that user.
- UserHandle targetUser = handleIncomingUser(userHandle, callingPid, callingUid);
+ UserHandle targetUser = handleIncomingUser(userHandle, callingUid);
verifyUserUnlocked(targetUser);
List<AppSearchSchema> schemas = new ArrayList<>(schemaBundles.size());
@@ -424,7 +422,6 @@ public class AppSearchManagerService extends SystemService {
Objects.requireNonNull(userHandle);
Objects.requireNonNull(callback);
- int callingPid = Binder.getCallingPid();
int callingUid = Binder.getCallingUid();
EXECUTOR.execute(() -> {
try {
@@ -433,7 +430,7 @@ public class AppSearchManagerService extends SystemService {
// Obtain the user where the client wants to run the operations in. This should
// end up being the same as userHandle, assuming it is not a special user and
// the client is allowed to run operations in that user.
- UserHandle targetUser = handleIncomingUser(userHandle, callingPid, callingUid);
+ UserHandle targetUser = handleIncomingUser(userHandle, callingUid);
verifyUserUnlocked(targetUser);
AppSearchUserInstance instance =
@@ -460,7 +457,6 @@ public class AppSearchManagerService extends SystemService {
Objects.requireNonNull(userHandle);
Objects.requireNonNull(callback);
- int callingPid = Binder.getCallingPid();
int callingUid = Binder.getCallingUid();
EXECUTOR.execute(() -> {
try {
@@ -469,7 +465,7 @@ public class AppSearchManagerService extends SystemService {
// Obtain the user where the client wants to run the operations in. This should
// end up being the same as userHandle, assuming it is not a special user and
// the client is allowed to run operations in that user.
- UserHandle targetUser = handleIncomingUser(userHandle, callingPid, callingUid);
+ UserHandle targetUser = handleIncomingUser(userHandle, callingUid);
verifyUserUnlocked(targetUser);
AppSearchUserInstance instance =
@@ -499,7 +495,6 @@ public class AppSearchManagerService extends SystemService {
Objects.requireNonNull(callback);
long totalLatencyStartTimeMillis = SystemClock.elapsedRealtime();
- int callingPid = Binder.getCallingPid();
int callingUid = Binder.getCallingUid();
EXECUTOR.execute(() -> {
@AppSearchResult.ResultCode int statusCode = AppSearchResult.RESULT_OK;
@@ -512,7 +507,7 @@ public class AppSearchManagerService extends SystemService {
// Obtain the user where the client wants to run the operations in. This should
// end up being the same as userHandle, assuming it is not a special user and
// the client is allowed to run operations in that user.
- UserHandle targetUser = handleIncomingUser(userHandle, callingPid, callingUid);
+ UserHandle targetUser = handleIncomingUser(userHandle, callingUid);
verifyUserUnlocked(targetUser);
AppSearchBatchResult.Builder<String, Void> resultBuilder =
@@ -589,7 +584,6 @@ public class AppSearchManagerService extends SystemService {
Objects.requireNonNull(callback);
long totalLatencyStartTimeMillis = SystemClock.elapsedRealtime();
- int callingPid = Binder.getCallingPid();
int callingUid = Binder.getCallingUid();
EXECUTOR.execute(() -> {
@AppSearchResult.ResultCode int statusCode = AppSearchResult.RESULT_OK;
@@ -602,7 +596,7 @@ public class AppSearchManagerService extends SystemService {
// Obtain the user where the client wants to run the operations in. This should
// end up being the same as userHandle, assuming it is not a special user and
// the client is allowed to run operations in that user.
- UserHandle targetUser = handleIncomingUser(userHandle, callingPid, callingUid);
+ UserHandle targetUser = handleIncomingUser(userHandle, callingUid);
verifyUserUnlocked(targetUser);
AppSearchBatchResult.Builder<String, Bundle> resultBuilder =
@@ -674,7 +668,6 @@ public class AppSearchManagerService extends SystemService {
Objects.requireNonNull(callback);
long totalLatencyStartTimeMillis = SystemClock.elapsedRealtime();
- int callingPid = Binder.getCallingPid();
int callingUid = Binder.getCallingUid();
EXECUTOR.execute(() -> {
@AppSearchResult.ResultCode int statusCode = AppSearchResult.RESULT_OK;
@@ -687,7 +680,7 @@ public class AppSearchManagerService extends SystemService {
// Obtain the user where the client wants to run the operations in. This should
// end up being the same as userHandle, assuming it is not a special user and
// the client is allowed to run operations in that user.
- UserHandle targetUser = handleIncomingUser(userHandle, callingPid, callingUid);
+ UserHandle targetUser = handleIncomingUser(userHandle, callingUid);
verifyUserUnlocked(targetUser);
instance = mAppSearchUserInstanceManager.getUserInstance(targetUser);
@@ -744,7 +737,6 @@ public class AppSearchManagerService extends SystemService {
Objects.requireNonNull(callback);
long totalLatencyStartTimeMillis = SystemClock.elapsedRealtime();
- int callingPid = Binder.getCallingPid();
int callingUid = Binder.getCallingUid();
EXECUTOR.execute(() -> {
@AppSearchResult.ResultCode int statusCode = AppSearchResult.RESULT_OK;
@@ -757,7 +749,7 @@ public class AppSearchManagerService extends SystemService {
// Obtain the user where the client wants to run the operations in. This should
// end up being the same as userHandle, assuming it is not a special user and
// the client is allowed to run operations in that user.
- UserHandle targetUser = handleIncomingUser(userHandle, callingPid, callingUid);
+ UserHandle targetUser = handleIncomingUser(userHandle, callingUid);
verifyUserUnlocked(targetUser);
instance = mAppSearchUserInstanceManager.getUserInstance(targetUser);
@@ -813,7 +805,6 @@ public class AppSearchManagerService extends SystemService {
Objects.requireNonNull(userHandle);
Objects.requireNonNull(callback);
- int callingPid = Binder.getCallingPid();
int callingUid = Binder.getCallingUid();
EXECUTOR.execute(() -> {
try {
@@ -822,7 +813,7 @@ public class AppSearchManagerService extends SystemService {
// Obtain the user where the client wants to run the operations in. This should
// end up being the same as userHandle, assuming it is not a special user and
// the client is allowed to run operations in that user.
- UserHandle targetUser = handleIncomingUser(userHandle, callingPid, callingUid);
+ UserHandle targetUser = handleIncomingUser(userHandle, callingUid);
verifyUserUnlocked(targetUser);
AppSearchUserInstance instance =
@@ -846,7 +837,6 @@ public class AppSearchManagerService extends SystemService {
Objects.requireNonNull(packageName);
Objects.requireNonNull(userHandle);
- int callingPid = Binder.getCallingPid();
int callingUid = Binder.getCallingUid();
EXECUTOR.execute(() -> {
try {
@@ -855,7 +845,7 @@ public class AppSearchManagerService extends SystemService {
// Obtain the user where the client wants to run the operations in. This should
// end up being the same as userHandle, assuming it is not a special user and
// the client is allowed to run operations in that user.
- UserHandle targetUser = handleIncomingUser(userHandle, callingPid, callingUid);
+ UserHandle targetUser = handleIncomingUser(userHandle, callingUid);
verifyUserUnlocked(targetUser);
AppSearchUserInstance instance =
@@ -884,7 +874,6 @@ public class AppSearchManagerService extends SystemService {
Objects.requireNonNull(userHandle);
Objects.requireNonNull(callback);
- int callingPid = Binder.getCallingPid();
int callingUid = Binder.getCallingUid();
EXECUTOR.execute(() -> {
try {
@@ -893,7 +882,7 @@ public class AppSearchManagerService extends SystemService {
// Obtain the user where the client wants to run the operations in. This should
// end up being the same as userHandle, assuming it is not a special user and
// the client is allowed to run operations in that user.
- UserHandle targetUser = handleIncomingUser(userHandle, callingPid, callingUid);
+ UserHandle targetUser = handleIncomingUser(userHandle, callingUid);
verifyUserUnlocked(targetUser);
AppSearchUserInstance instance =
@@ -940,7 +929,6 @@ public class AppSearchManagerService extends SystemService {
Objects.requireNonNull(userHandle);
Objects.requireNonNull(callback);
- int callingPid = Binder.getCallingPid();
int callingUid = Binder.getCallingUid();
EXECUTOR.execute(() -> {
try {
@@ -949,7 +937,7 @@ public class AppSearchManagerService extends SystemService {
// Obtain the user where the client wants to run the operations in. This should
// end up being the same as userHandle, assuming it is not a special user and
// the client is allowed to run operations in that user.
- UserHandle targetUser = handleIncomingUser(userHandle, callingPid, callingUid);
+ UserHandle targetUser = handleIncomingUser(userHandle, callingUid);
verifyUserUnlocked(targetUser);
AppSearchUserInstance instance =
@@ -1006,7 +994,6 @@ public class AppSearchManagerService extends SystemService {
Objects.requireNonNull(userHandle);
Objects.requireNonNull(callback);
- int callingPid = Binder.getCallingPid();
int callingUid = Binder.getCallingUid();
EXECUTOR.execute(() -> {
try {
@@ -1015,7 +1002,7 @@ public class AppSearchManagerService extends SystemService {
// Obtain the user where the client wants to run the operations in. This should
// end up being the same as userHandle, assuming it is not a special user and
// the client is allowed to run operations in that user.
- UserHandle targetUser = handleIncomingUser(userHandle, callingPid, callingUid);
+ UserHandle targetUser = handleIncomingUser(userHandle, callingUid);
verifyUserUnlocked(targetUser);
AppSearchUserInstance instance =
@@ -1057,7 +1044,6 @@ public class AppSearchManagerService extends SystemService {
Objects.requireNonNull(callback);
long totalLatencyStartTimeMillis = SystemClock.elapsedRealtime();
- int callingPid = Binder.getCallingPid();
int callingUid = Binder.getCallingUid();
EXECUTOR.execute(() -> {
@AppSearchResult.ResultCode int statusCode = AppSearchResult.RESULT_OK;
@@ -1070,7 +1056,7 @@ public class AppSearchManagerService extends SystemService {
// Obtain the user where the client wants to run the operations in. This should
// end up being the same as userHandle, assuming it is not a special user and
// the client is allowed to run operations in that user.
- UserHandle targetUser = handleIncomingUser(userHandle, callingPid, callingUid);
+ UserHandle targetUser = handleIncomingUser(userHandle, callingUid);
verifyUserUnlocked(targetUser);
AppSearchBatchResult.Builder<String, Void> resultBuilder =
@@ -1147,7 +1133,6 @@ public class AppSearchManagerService extends SystemService {
Objects.requireNonNull(callback);
long totalLatencyStartTimeMillis = SystemClock.elapsedRealtime();
- int callingPid = Binder.getCallingPid();
int callingUid = Binder.getCallingUid();
EXECUTOR.execute(() -> {
@AppSearchResult.ResultCode int statusCode = AppSearchResult.RESULT_OK;
@@ -1160,7 +1145,7 @@ public class AppSearchManagerService extends SystemService {
// Obtain the user where the client wants to run the operations in. This should
// end up being the same as userHandle, assuming it is not a special user and
// the client is allowed to run operations in that user.
- UserHandle targetUser = handleIncomingUser(userHandle, callingPid, callingUid);
+ UserHandle targetUser = handleIncomingUser(userHandle, callingUid);
verifyUserUnlocked(targetUser);
instance = mAppSearchUserInstanceManager.getUserInstance(targetUser);
@@ -1215,7 +1200,6 @@ public class AppSearchManagerService extends SystemService {
Objects.requireNonNull(userHandle);
Objects.requireNonNull(callback);
- int callingPid = Binder.getCallingPid();
int callingUid = Binder.getCallingUid();
EXECUTOR.execute(() -> {
try {
@@ -1224,7 +1208,7 @@ public class AppSearchManagerService extends SystemService {
// Obtain the user where the client wants to run the operations in. This should
// end up being the same as userHandle, assuming it is not a special user and
// the client is allowed to run operations in that user.
- UserHandle targetUser = handleIncomingUser(userHandle, callingPid, callingUid);
+ UserHandle targetUser = handleIncomingUser(userHandle, callingUid);
verifyUserUnlocked(targetUser);
AppSearchUserInstance instance =
@@ -1249,7 +1233,6 @@ public class AppSearchManagerService extends SystemService {
Objects.requireNonNull(userHandle);
long totalLatencyStartTimeMillis = SystemClock.elapsedRealtime();
- int callingPid = Binder.getCallingPid();
int callingUid = Binder.getCallingUid();
EXECUTOR.execute(() -> {
@AppSearchResult.ResultCode int statusCode = AppSearchResult.RESULT_OK;
@@ -1262,7 +1245,7 @@ public class AppSearchManagerService extends SystemService {
// Obtain the user where the client wants to run the operations in. This should
// end up being the same as userHandle, assuming it is not a special user and
// the client is allowed to run operations in that user.
- UserHandle targetUser = handleIncomingUser(userHandle, callingPid, callingUid);
+ UserHandle targetUser = handleIncomingUser(userHandle, callingUid);
verifyUserUnlocked(targetUser);
instance = mAppSearchUserInstanceManager.getUserInstance(targetUser);
@@ -1305,7 +1288,6 @@ public class AppSearchManagerService extends SystemService {
Objects.requireNonNull(callback);
long totalLatencyStartTimeMillis = SystemClock.elapsedRealtime();
- int callingPid = Binder.getCallingPid();
int callingUid = Binder.getCallingUid();
EXECUTOR.execute(() -> {
@@ -1319,7 +1301,7 @@ public class AppSearchManagerService extends SystemService {
// Obtain the user where the client wants to run the operations in. This should
// end up being the same as userHandle, assuming it is not a special user and
// the client is allowed to run operations in that user.
- UserHandle targetUser = handleIncomingUser(userHandle, callingPid, callingUid);
+ UserHandle targetUser = handleIncomingUser(userHandle, callingUid);
verifyUserUnlocked(targetUser);
Context targetUserContext = mContext.createContextAsUser(targetUser,
@@ -1407,22 +1389,12 @@ public class AppSearchManagerService extends SystemService {
/**
* Helper for dealing with incoming user arguments to system service calls.
*
- * <p>Takes care of checking permissions and if the target is special user, this method will
- * simply throw.
- *
* @param targetUserHandle The user which the caller is requesting to execute as.
- * @param callingPid The actual pid of the caller as determined by Binder.
* @param callingUid The actual uid of the caller as determined by Binder.
- *
* @return the user handle that the call should run as. Will always be a concrete user.
- *
- * @throws IllegalArgumentException if the target user is a special user.
- * @throws SecurityException if caller trying to interact across user without
- * {@link Manifest.permission#INTERACT_ACROSS_USERS_FULL}
*/
@NonNull
- private UserHandle handleIncomingUser(@NonNull UserHandle targetUserHandle, int callingPid,
- int callingUid) {
+ private UserHandle handleIncomingUser(@NonNull UserHandle targetUserHandle, int callingUid) {
UserHandle callingUserHandle = UserHandle.getUserHandleForUid(callingUid);
if (callingUserHandle.equals(targetUserHandle)) {
return targetUserHandle;
@@ -1434,16 +1406,9 @@ public class AppSearchManagerService extends SystemService {
"Call does not support special user " + targetUserHandle);
}
- if (mContext.checkPermission(
- Manifest.permission.INTERACT_ACROSS_USERS_FULL,
- callingPid,
- callingUid) == PackageManager.PERMISSION_GRANTED) {
- return targetUserHandle;
- }
throw new SecurityException(
- "Permission denied while calling from uid " + callingUid
- + " with " + targetUserHandle + "; Requires permission: "
- + Manifest.permission.INTERACT_ACROSS_USERS_FULL);
+ "Requested user, " + targetUserHandle + ", is not the same as the calling user, "
+ + callingUserHandle + ".");
}
/**
diff --git a/core/api/current.txt b/core/api/current.txt
index a79df58c7916..c7d92b0cd74e 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -17949,7 +17949,7 @@ package android.hardware.camera2 {
field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<int[]> EDGE_AVAILABLE_EDGE_MODES;
field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Boolean> FLASH_INFO_AVAILABLE;
field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<int[]> HOT_PIXEL_AVAILABLE_HOT_PIXEL_MODES;
- field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.hardware.camera2.params.DeviceStateOrientationMap> INFO_DEVICE_STATE_ORIENTATION_MAP;
+ field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.hardware.camera2.params.DeviceStateSensorOrientationMap> INFO_DEVICE_STATE_SENSOR_ORIENTATION_MAP;
field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Integer> INFO_SUPPORTED_HARDWARE_LEVEL;
field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.String> INFO_VERSION;
field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.util.Size[]> JPEG_AVAILABLE_THUMBNAIL_SIZES;
@@ -18644,7 +18644,7 @@ package android.hardware.camera2.params {
method public android.util.Rational getElement(int, int);
}
- public final class DeviceStateOrientationMap {
+ public final class DeviceStateSensorOrientationMap {
method public int getSensorOrientation(long);
field public static final long FOLDED = 4L; // 0x4L
field public static final long NORMAL = 0L; // 0x0L
@@ -46912,15 +46912,15 @@ package android.view {
}
@UiThread public interface AttachedSurfaceControl {
- method public default void addOnSurfaceTransformHintChangedListener(@NonNull android.view.AttachedSurfaceControl.OnSurfaceTransformHintChangedListener);
+ method public default void addOnBufferTransformHintChangedListener(@NonNull android.view.AttachedSurfaceControl.OnBufferTransformHintChangedListener);
method public boolean applyTransactionOnDraw(@NonNull android.view.SurfaceControl.Transaction);
method @Nullable public android.view.SurfaceControl.Transaction buildReparentTransaction(@NonNull android.view.SurfaceControl);
- method public default int getSurfaceTransformHint();
- method public default void removeOnSurfaceTransformHintChangedListener(@NonNull android.view.AttachedSurfaceControl.OnSurfaceTransformHintChangedListener);
+ method public default int getBufferTransformHint();
+ method public default void removeOnBufferTransformHintChangedListener(@NonNull android.view.AttachedSurfaceControl.OnBufferTransformHintChangedListener);
}
- @UiThread public static interface AttachedSurfaceControl.OnSurfaceTransformHintChangedListener {
- method public void onSurfaceTransformHintChanged(int);
+ @UiThread public static interface AttachedSurfaceControl.OnBufferTransformHintChangedListener {
+ method public void onBufferTransformHintChanged(int);
}
public final class Choreographer {
@@ -48410,6 +48410,12 @@ package android.view {
method public void readFromParcel(android.os.Parcel);
method public void release();
method public void writeToParcel(android.os.Parcel, int);
+ field public static final int BUFFER_TRANSFORM_IDENTITY = 0; // 0x0
+ field public static final int BUFFER_TRANSFORM_MIRROR_HORIZONTAL = 1; // 0x1
+ field public static final int BUFFER_TRANSFORM_MIRROR_VERTICAL = 2; // 0x2
+ field public static final int BUFFER_TRANSFORM_ROTATE_180 = 3; // 0x3
+ field public static final int BUFFER_TRANSFORM_ROTATE_270 = 7; // 0x7
+ field public static final int BUFFER_TRANSFORM_ROTATE_90 = 4; // 0x4
field @NonNull public static final android.os.Parcelable.Creator<android.view.SurfaceControl> CREATOR;
}
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 1d406a5fdb28..5b27019f300e 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -5377,10 +5377,12 @@ package android.media {
method @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public void addCompatibleAudioDevice(@NonNull android.media.AudioDeviceAttributes);
method @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public void addOnHeadTrackingModeChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.Spatializer.OnHeadTrackingModeChangedListener);
method @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public void clearOnHeadToSoundstagePoseUpdatedListener();
+ method @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public void clearOnSpatializerOutputChangedListener();
method @NonNull @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public java.util.List<android.media.AudioDeviceAttributes> getCompatibleAudioDevices();
method @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public int getDesiredHeadTrackingMode();
method @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public void getEffectParameter(int, @NonNull byte[]);
method @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public int getHeadTrackingMode();
+ method @IntRange(from=0) @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public int getOutput();
method @NonNull @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public java.util.List<java.lang.Integer> getSupportedHeadTrackingModes();
method @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public void recenterHeadTracker();
method @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public void removeCompatibleAudioDevice(@NonNull android.media.AudioDeviceAttributes);
@@ -5390,6 +5392,7 @@ package android.media {
method @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public void setEnabled(boolean);
method @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public void setGlobalTransform(@NonNull float[]);
method @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public void setOnHeadToSoundstagePoseUpdatedListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.Spatializer.OnHeadToSoundstagePoseUpdatedListener);
+ method @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public void setOnSpatializerOutputChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.Spatializer.OnSpatializerOutputChangedListener);
field @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public static final int HEAD_TRACKING_MODE_DISABLED = -1; // 0xffffffff
field @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public static final int HEAD_TRACKING_MODE_OTHER = 0; // 0x0
field @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public static final int HEAD_TRACKING_MODE_RELATIVE_DEVICE = 2; // 0x2
@@ -5406,6 +5409,10 @@ package android.media {
method public void onHeadTrackingModeChanged(@NonNull android.media.Spatializer, int);
}
+ public static interface Spatializer.OnSpatializerOutputChangedListener {
+ method public void onSpatializerOutputChanged(@NonNull android.media.Spatializer, @IntRange(from=0) int);
+ }
+
}
package android.media.audiofx {
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 22091fcde86f..f453ba16043c 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -77,7 +77,6 @@ import android.os.IBinder;
import android.os.Looper;
import android.os.Parcelable;
import android.os.PersistableBundle;
-import android.os.PowerManager;
import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager.ServiceNotFoundException;
@@ -141,7 +140,6 @@ import android.widget.AdapterView;
import android.widget.Toast;
import android.widget.Toolbar;
import android.window.SplashScreen;
-import android.window.SplashScreenView;
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
@@ -970,7 +968,6 @@ public class Activity extends ContextThemeWrapper
private UiTranslationController mUiTranslationController;
private SplashScreen mSplashScreen;
- private SplashScreenView mSplashScreenView;
private final WindowControllerCallback mWindowControllerCallback =
new WindowControllerCallback() {
@@ -1641,16 +1638,6 @@ public class Activity extends ContextThemeWrapper
}
}
- /** @hide */
- public void setSplashScreenView(SplashScreenView v) {
- mSplashScreenView = v;
- }
-
- /** @hide */
- SplashScreenView getSplashScreenView() {
- return mSplashScreenView;
- }
-
/**
* Same as {@link #onCreate(android.os.Bundle)} but called for those activities created with
* the attribute {@link android.R.attr#persistableMode} set to
@@ -8804,9 +8791,7 @@ public class Activity extends ContextThemeWrapper
* the activity is visible after the screen is turned on when the lockscreen is up. In addition,
* if this flag is set and the activity calls {@link
* KeyguardManager#requestDismissKeyguard(Activity, KeyguardManager.KeyguardDismissCallback)}
- * the screen will turn on. If the screen is off and device is not secured, this flag can turn
- * screen on and dismiss keyguard to make this activity visible and resume, which can be used to
- * replace {@link PowerManager#ACQUIRE_CAUSES_WAKEUP}
+ * the screen will turn on.
*
* @param turnScreenOn {@code true} to turn on the screen; {@code false} otherwise.
*
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index d0680f8c9268..431755e092e3 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -166,6 +166,7 @@ import android.view.Choreographer;
import android.view.Display;
import android.view.DisplayAdjustments;
import android.view.DisplayAdjustments.FixedRotationAdjustments;
+import android.view.SurfaceControl;
import android.view.ThreadedRenderer;
import android.view.View;
import android.view.ViewDebug;
@@ -235,7 +236,6 @@ import java.util.Map;
import java.util.Objects;
import java.util.TimeZone;
import java.util.concurrent.Executor;
-import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Consumer;
/**
@@ -4074,10 +4074,11 @@ public final class ActivityThread extends ClientTransactionHandler
@Override
public void handleAttachSplashScreenView(@NonNull ActivityClientRecord r,
- @Nullable SplashScreenView.SplashScreenViewParcelable parcelable) {
+ @Nullable SplashScreenView.SplashScreenViewParcelable parcelable,
+ @NonNull SurfaceControl startingWindowLeash) {
final DecorView decorView = (DecorView) r.window.peekDecorView();
if (parcelable != null && decorView != null) {
- createSplashScreen(r, decorView, parcelable);
+ createSplashScreen(r, decorView, parcelable, startingWindowLeash);
} else {
// shouldn't happen!
Slog.e(TAG, "handleAttachSplashScreenView failed, unable to attach");
@@ -4085,63 +4086,52 @@ public final class ActivityThread extends ClientTransactionHandler
}
private void createSplashScreen(ActivityClientRecord r, DecorView decorView,
- SplashScreenView.SplashScreenViewParcelable parcelable) {
+ SplashScreenView.SplashScreenViewParcelable parcelable,
+ @NonNull SurfaceControl startingWindowLeash) {
final SplashScreenView.Builder builder = new SplashScreenView.Builder(r.activity);
final SplashScreenView view = builder.createFromParcel(parcelable).build();
decorView.addView(view);
view.attachHostActivityAndSetSystemUIColors(r.activity, r.window);
view.requestLayout();
- // Ensure splash screen view is shown before remove the splash screen window.
- final ViewRootImpl impl = decorView.getViewRootImpl();
- final boolean hardwareEnabled = impl != null && impl.isHardwareEnabled();
- final AtomicBoolean notified = new AtomicBoolean();
- if (hardwareEnabled) {
- final Runnable frameCommit = new Runnable() {
- @Override
- public void run() {
- view.post(() -> {
- if (!notified.get()) {
- view.getViewTreeObserver().unregisterFrameCommitCallback(this);
- ActivityClient.getInstance().reportSplashScreenAttached(
- r.token);
- notified.set(true);
- }
- });
- }
- };
- view.getViewTreeObserver().registerFrameCommitCallback(frameCommit);
- } else {
- final ViewTreeObserver.OnDrawListener onDrawListener =
- new ViewTreeObserver.OnDrawListener() {
- @Override
- public void onDraw() {
- view.post(() -> {
- if (!notified.get()) {
- view.getViewTreeObserver().removeOnDrawListener(this);
- ActivityClient.getInstance().reportSplashScreenAttached(
- r.token);
- notified.set(true);
- }
- });
- }
- };
- view.getViewTreeObserver().addOnDrawListener(onDrawListener);
- }
+
+ view.getViewTreeObserver().addOnDrawListener(new ViewTreeObserver.OnDrawListener() {
+ @Override
+ public void onDraw() {
+ // Transfer the splash screen view from shell to client.
+ // Call syncTransferSplashscreenViewTransaction at the first onDraw so we can ensure
+ // the client view is ready to show and we can use applyTransactionOnDraw to make
+ // all transitions happen at the same frame.
+ syncTransferSplashscreenViewTransaction(
+ view, r.token, decorView, startingWindowLeash);
+ view.postOnAnimation(() -> view.getViewTreeObserver().removeOnDrawListener(this));
+ }
+ });
}
- @Override
- public void handOverSplashScreenView(@NonNull ActivityClientRecord r) {
- final SplashScreenView v = r.activity.getSplashScreenView();
- if (v == null) {
- return;
- }
+ private void reportSplashscreenViewShown(IBinder token, SplashScreenView view) {
+ ActivityClient.getInstance().reportSplashScreenAttached(token);
synchronized (this) {
if (mSplashScreenGlobal != null) {
- mSplashScreenGlobal.handOverSplashScreenView(r.token, v);
+ mSplashScreenGlobal.handOverSplashScreenView(token, view);
}
}
}
+ private void syncTransferSplashscreenViewTransaction(SplashScreenView view, IBinder token,
+ View decorView, @NonNull SurfaceControl startingWindowLeash) {
+ // Ensure splash screen view is shown before remove the splash screen window.
+ // Once the copied splash screen view is onDrawn on decor view, use applyTransactionOnDraw
+ // to ensure the transfer of surface view and hide starting window are happen at the same
+ // frame.
+ final SurfaceControl.Transaction transaction = new SurfaceControl.Transaction();
+ transaction.hide(startingWindowLeash);
+
+ decorView.getViewRootImpl().applyTransactionOnDraw(transaction);
+ view.syncTransferSurfaceOnDraw();
+ // Tell server we can remove the starting window
+ decorView.postOnAnimation(() -> reportSplashscreenViewShown(token, view));
+ }
+
/**
* Cycle activity through onPause and onUserLeaveHint so that PIP is entered if supported, then
* return to its previous state. This allows activities that rely on onUserLeaveHint instead of
diff --git a/core/java/android/app/ClientTransactionHandler.java b/core/java/android/app/ClientTransactionHandler.java
index 115101c0bff6..c743f6572d5e 100644
--- a/core/java/android/app/ClientTransactionHandler.java
+++ b/core/java/android/app/ClientTransactionHandler.java
@@ -28,6 +28,7 @@ import android.content.res.Configuration;
import android.os.IBinder;
import android.util.MergedConfiguration;
import android.view.DisplayAdjustments.FixedRotationAdjustments;
+import android.view.SurfaceControl;
import android.window.SplashScreenView.SplashScreenViewParcelable;
import com.android.internal.annotations.VisibleForTesting;
@@ -165,10 +166,8 @@ public abstract class ClientTransactionHandler {
/** Attach a splash screen window view to the top of the activity */
public abstract void handleAttachSplashScreenView(@NonNull ActivityClientRecord r,
- @NonNull SplashScreenViewParcelable parcelable);
-
- /** Hand over the splash screen window view to the activity */
- public abstract void handOverSplashScreenView(@NonNull ActivityClientRecord r);
+ @NonNull SplashScreenViewParcelable parcelable,
+ @NonNull SurfaceControl startingWindowLeash);
/** Perform activity launch. */
public abstract Activity handleLaunchActivity(@NonNull ActivityClientRecord r,
diff --git a/core/java/android/app/servertransaction/TransferSplashScreenViewStateItem.java b/core/java/android/app/servertransaction/TransferSplashScreenViewStateItem.java
index 5374984d31d0..767fd28b8a2a 100644
--- a/core/java/android/app/servertransaction/TransferSplashScreenViewStateItem.java
+++ b/core/java/android/app/servertransaction/TransferSplashScreenViewStateItem.java
@@ -16,17 +16,14 @@
package android.app.servertransaction;
-import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityThread;
import android.app.ClientTransactionHandler;
import android.os.Parcel;
+import android.view.SurfaceControl;
import android.window.SplashScreenView.SplashScreenViewParcelable;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
/**
* Transfer a splash screen view to an Activity.
* @hide
@@ -34,31 +31,13 @@ import java.lang.annotation.RetentionPolicy;
public class TransferSplashScreenViewStateItem extends ActivityTransactionItem {
private SplashScreenViewParcelable mSplashScreenViewParcelable;
- private @TransferRequest int mRequest;
-
- @IntDef(value = {
- ATTACH_TO,
- HANDOVER_TO
- })
- @Retention(RetentionPolicy.SOURCE)
- public @interface TransferRequest {}
- // request client to attach the view on it.
- public static final int ATTACH_TO = 0;
- // tell client that you can handle the splash screen view.
- public static final int HANDOVER_TO = 1;
+ private SurfaceControl mStartingWindowLeash;
@Override
public void execute(@NonNull ClientTransactionHandler client,
@NonNull ActivityThread.ActivityClientRecord r,
PendingTransactionActions pendingActions) {
- switch (mRequest) {
- case ATTACH_TO:
- client.handleAttachSplashScreenView(r, mSplashScreenViewParcelable);
- break;
- case HANDOVER_TO:
- client.handOverSplashScreenView(r);
- break;
- }
+ client.handleAttachSplashScreenView(r, mSplashScreenViewParcelable, mStartingWindowLeash);
}
@Override
@@ -68,26 +47,27 @@ public class TransferSplashScreenViewStateItem extends ActivityTransactionItem {
@Override
public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(mRequest);
dest.writeTypedObject(mSplashScreenViewParcelable, flags);
+ dest.writeTypedObject(mStartingWindowLeash, flags);
}
private TransferSplashScreenViewStateItem() {}
private TransferSplashScreenViewStateItem(Parcel in) {
- mRequest = in.readInt();
mSplashScreenViewParcelable = in.readTypedObject(SplashScreenViewParcelable.CREATOR);
+ mStartingWindowLeash = in.readTypedObject(SurfaceControl.CREATOR);
}
/** Obtain an instance initialized with provided params. */
- public static TransferSplashScreenViewStateItem obtain(@TransferRequest int state,
- @Nullable SplashScreenViewParcelable parcelable) {
+ public static TransferSplashScreenViewStateItem obtain(
+ @Nullable SplashScreenViewParcelable parcelable,
+ @Nullable SurfaceControl startingWindowLeash) {
TransferSplashScreenViewStateItem instance =
ObjectPool.obtain(TransferSplashScreenViewStateItem.class);
if (instance == null) {
instance = new TransferSplashScreenViewStateItem();
}
- instance.mRequest = state;
instance.mSplashScreenViewParcelable = parcelable;
+ instance.mStartingWindowLeash = startingWindowLeash;
return instance;
}
diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index ddac22cd42a3..9f77a7e72e70 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -22,7 +22,7 @@ import android.compat.annotation.UnsupportedAppUsage;
import android.hardware.camera2.impl.CameraMetadataNative;
import android.hardware.camera2.impl.PublicKey;
import android.hardware.camera2.impl.SyntheticKey;
-import android.hardware.camera2.params.DeviceStateOrientationMap;
+import android.hardware.camera2.params.DeviceStateSensorOrientationMap;
import android.hardware.camera2.params.RecommendedStreamConfigurationMap;
import android.hardware.camera2.params.SessionConfiguration;
import android.hardware.camera2.utils.TypeReference;
@@ -258,11 +258,12 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri
private <T> T overrideProperty(Key<T> key) {
if (CameraCharacteristics.SENSOR_ORIENTATION.equals(key) && (mFoldStateListener != null) &&
(mProperties.get(CameraCharacteristics.INFO_DEVICE_STATE_ORIENTATIONS) != null)) {
- DeviceStateOrientationMap deviceStateOrientationMap =
- mProperties.get(CameraCharacteristics.INFO_DEVICE_STATE_ORIENTATION_MAP);
+ DeviceStateSensorOrientationMap deviceStateSensorOrientationMap =
+ mProperties.get(CameraCharacteristics.INFO_DEVICE_STATE_SENSOR_ORIENTATION_MAP);
synchronized (mLock) {
- Integer ret = deviceStateOrientationMap.getSensorOrientation(mFoldedDeviceState ?
- DeviceStateOrientationMap.FOLDED : DeviceStateOrientationMap.NORMAL);
+ Integer ret = deviceStateSensorOrientationMap.getSensorOrientation(
+ mFoldedDeviceState ? DeviceStateSensorOrientationMap.FOLDED :
+ DeviceStateSensorOrientationMap.NORMAL);
if (ret >= 0) {
return (T) ret;
} else {
@@ -4056,7 +4057,7 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri
* Clients are advised to not cache or store the orientation value of such logical sensors.
* In case repeated queries to CameraCharacteristics are not preferred, then clients can
* also access the entire mapping from device state to sensor orientation in
- * {@link android.hardware.camera2.params.DeviceStateOrientationMap }.
+ * {@link android.hardware.camera2.params.DeviceStateSensorOrientationMap }.
* Do note that a dynamically changing sensor orientation value in camera characteristics
* will not be the best way to establish the orientation per frame. Clients that want to
* know the sensor orientation of a particular captured frame should query the
@@ -4384,7 +4385,7 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri
* values. The orientation value may need to change depending on the specific folding
* state. Information about the mapping between the device folding state and the
* sensor orientation can be obtained in
- * {@link android.hardware.camera2.params.DeviceStateOrientationMap }.
+ * {@link android.hardware.camera2.params.DeviceStateSensorOrientationMap }.
* Device state orientation maps are optional and maybe present on devices that support
* {@link CaptureRequest#SCALER_ROTATE_AND_CROP android.scaler.rotateAndCrop}.</p>
* <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
@@ -4398,8 +4399,8 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri
@PublicKey
@NonNull
@SyntheticKey
- public static final Key<android.hardware.camera2.params.DeviceStateOrientationMap> INFO_DEVICE_STATE_ORIENTATION_MAP =
- new Key<android.hardware.camera2.params.DeviceStateOrientationMap>("android.info.deviceStateOrientationMap", android.hardware.camera2.params.DeviceStateOrientationMap.class);
+ public static final Key<android.hardware.camera2.params.DeviceStateSensorOrientationMap> INFO_DEVICE_STATE_SENSOR_ORIENTATION_MAP =
+ new Key<android.hardware.camera2.params.DeviceStateSensorOrientationMap>("android.info.deviceStateSensorOrientationMap", android.hardware.camera2.params.DeviceStateSensorOrientationMap.class);
/**
* <p>HAL must populate the array with
diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java
index 9bb901f8c5f7..b7c5644df107 100644
--- a/core/java/android/hardware/camera2/CameraManager.java
+++ b/core/java/android/hardware/camera2/CameraManager.java
@@ -30,7 +30,6 @@ import android.hardware.ICameraServiceListener;
import android.hardware.camera2.impl.CameraDeviceImpl;
import android.hardware.camera2.impl.CameraInjectionSessionImpl;
import android.hardware.camera2.impl.CameraMetadataNative;
-import android.hardware.camera2.params.DeviceStateOrientationMap;
import android.hardware.camera2.params.ExtensionSessionConfiguration;
import android.hardware.camera2.params.SessionConfiguration;
import android.hardware.camera2.params.StreamConfiguration;
@@ -110,8 +109,16 @@ public final class CameraManager {
mHandlerThread.start();
mHandler = new Handler(mHandlerThread.getLooper());
mFoldStateListener = new FoldStateListener(context);
- context.getSystemService(DeviceStateManager.class)
- .registerCallback(new HandlerExecutor(mHandler), mFoldStateListener);
+ try {
+ context.getSystemService(DeviceStateManager.class)
+ .registerCallback(new HandlerExecutor(mHandler), mFoldStateListener);
+ } catch (IllegalStateException e) {
+ Log.v(TAG, "Failed to register device state listener!");
+ Log.v(TAG, "Device state dependent characteristics updates will not be functional!");
+ mHandlerThread.quitSafely();
+ mHandler = null;
+ mFoldStateListener = null;
+ }
}
private HandlerThread mHandlerThread;
@@ -177,7 +184,9 @@ public final class CameraManager {
synchronized (mLock) {
DeviceStateListener listener = chars.getDeviceStateListener();
listener.onDeviceStateChanged(mFoldedDeviceState);
- mDeviceStateListeners.add(new WeakReference<>(listener));
+ if (mFoldStateListener != null) {
+ mDeviceStateListeners.add(new WeakReference<>(listener));
+ }
}
}
diff --git a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
index 37450222aca9..e393a66eb733 100644
--- a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
+++ b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
@@ -50,11 +50,10 @@ import android.hardware.camera2.marshal.impl.MarshalQueryableStreamConfiguration
import android.hardware.camera2.marshal.impl.MarshalQueryableStreamConfigurationDuration;
import android.hardware.camera2.marshal.impl.MarshalQueryableString;
import android.hardware.camera2.params.Capability;
-import android.hardware.camera2.params.DeviceStateOrientationMap;
+import android.hardware.camera2.params.DeviceStateSensorOrientationMap;
import android.hardware.camera2.params.Face;
import android.hardware.camera2.params.HighSpeedVideoConfiguration;
import android.hardware.camera2.params.LensShadingMap;
-import android.hardware.camera2.params.MeteringRectangle;
import android.hardware.camera2.params.MandatoryStreamCombination;
import android.hardware.camera2.params.MultiResolutionStreamConfigurationMap;
import android.hardware.camera2.params.OisSample;
@@ -763,7 +762,7 @@ public class CameraMetadataNative implements Parcelable {
}
});
sGetCommandMap.put(
- CameraCharacteristics.INFO_DEVICE_STATE_ORIENTATION_MAP.getNativeKey(),
+ CameraCharacteristics.INFO_DEVICE_STATE_SENSOR_ORIENTATION_MAP.getNativeKey(),
new GetCommand() {
@Override
@SuppressWarnings("unchecked")
@@ -1004,7 +1003,7 @@ public class CameraMetadataNative implements Parcelable {
return map;
}
- private DeviceStateOrientationMap getDeviceStateOrientationMap() {
+ private DeviceStateSensorOrientationMap getDeviceStateOrientationMap() {
long[] mapArray = getBase(CameraCharacteristics.INFO_DEVICE_STATE_ORIENTATIONS);
// Do not warn if map is null while s is not. This is valid.
@@ -1012,7 +1011,7 @@ public class CameraMetadataNative implements Parcelable {
return null;
}
- DeviceStateOrientationMap map = new DeviceStateOrientationMap(mapArray);
+ DeviceStateSensorOrientationMap map = new DeviceStateSensorOrientationMap(mapArray);
return map;
}
diff --git a/core/java/android/hardware/camera2/params/DeviceStateOrientationMap.java b/core/java/android/hardware/camera2/params/DeviceStateSensorOrientationMap.java
index 3907f040759c..200409e9e000 100644
--- a/core/java/android/hardware/camera2/params/DeviceStateOrientationMap.java
+++ b/core/java/android/hardware/camera2/params/DeviceStateSensorOrientationMap.java
@@ -40,7 +40,7 @@ import java.util.Objects;
*
* @see CameraCharacteristics#SENSOR_ORIENTATION
*/
-public final class DeviceStateOrientationMap {
+public final class DeviceStateSensorOrientationMap {
/**
* Needs to be kept in sync with the HIDL/AIDL DeviceState
*/
@@ -85,10 +85,10 @@ public final class DeviceStateOrientationMap {
*
* @hide
*/
- public DeviceStateOrientationMap(final long[] elements) {
+ public DeviceStateSensorOrientationMap(final long[] elements) {
mElements = Objects.requireNonNull(elements, "elements must not be null");
if ((elements.length % 2) != 0) {
- throw new IllegalArgumentException("Device state orientation map length " +
+ throw new IllegalArgumentException("Device state sensor orientation map length " +
elements.length + " is not even!");
}
@@ -121,7 +121,8 @@ public final class DeviceStateOrientationMap {
}
/**
- * Check if this DeviceStateOrientationMap is equal to another DeviceStateOrientationMap.
+ * Check if this DeviceStateSensorOrientationMap is equal to another
+ * DeviceStateSensorOrientationMap.
*
* <p>Two device state orientation maps are equal if and only if all of their elements are
* {@link Object#equals equal}.</p>
@@ -136,8 +137,8 @@ public final class DeviceStateOrientationMap {
if (this == obj) {
return true;
}
- if (obj instanceof DeviceStateOrientationMap) {
- final DeviceStateOrientationMap other = (DeviceStateOrientationMap) obj;
+ if (obj instanceof DeviceStateSensorOrientationMap) {
+ final DeviceStateSensorOrientationMap other = (DeviceStateSensorOrientationMap) obj;
return Arrays.equals(mElements, other.mElements);
}
return false;
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index c0f008122b4f..9721279bcd24 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -790,11 +790,6 @@ public class InputMethodService extends AbstractInputMethodService {
return;
}
- if (Trace.isEnabled()) {
- Binder.enableTracing();
- } else {
- Binder.disableTracing();
- }
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMS.showSoftInput");
ImeTracing.getInstance().triggerServiceDump(
"InputMethodService.InputMethodImpl#showSoftInput", InputMethodService.this,
diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java
index 71f90fd28e3f..3d18a8933153 100644
--- a/core/java/android/service/notification/NotificationListenerService.java
+++ b/core/java/android/service/notification/NotificationListenerService.java
@@ -83,11 +83,11 @@ import java.util.Objects;
* &lt;/intent-filter>
* &lt;meta-data
* android:name="android.service.notification.default_filter_types"
- * android:value="conversations,alerting">
+ * android:value="conversations|alerting">
* &lt;/meta-data>
* &lt;meta-data
* android:name="android.service.notification.disabled_filter_types"
- * android:value="ongoing,silent">
+ * android:value="ongoing|silent">
* &lt;/meta-data>
* &lt;/service></pre>
*
@@ -112,8 +112,9 @@ public abstract class NotificationListenerService extends Service {
private final String TAG = getClass().getSimpleName();
/**
- * The name of the {@code meta-data} tag containing a comma separated list of default
- * integer notification types that should be provided to this listener. See
+ * The name of the {@code meta-data} tag containing a pipe separated list of default
+ * integer notification types or "ongoing", "conversations", "alerting", or "silent"
+ * that should be provided to this listener. See
* {@link #FLAG_FILTER_TYPE_ONGOING},
* {@link #FLAG_FILTER_TYPE_CONVERSATIONS}, {@link #FLAG_FILTER_TYPE_ALERTING),
* and {@link #FLAG_FILTER_TYPE_SILENT}.
diff --git a/core/java/android/view/AttachedSurfaceControl.java b/core/java/android/view/AttachedSurfaceControl.java
index b2fc9a0c85fc..69af2a5ce7fb 100644
--- a/core/java/android/view/AttachedSurfaceControl.java
+++ b/core/java/android/view/AttachedSurfaceControl.java
@@ -18,6 +18,7 @@ package android.view;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UiThread;
+import android.hardware.HardwareBuffer;
/**
* Provides an interface to the root-Surface of a View Hierarchy or Window. This
@@ -84,41 +85,43 @@ public interface AttachedSurfaceControl {
* Note, when using ANativeWindow APIs in conjunction with a NativeActivity Surface or
* SurfaceView Surface, the buffer producer will already have access to the transform hint and
* no additional work is needed.
+ *
+ * @see HardwareBuffer
*/
- default @Surface.Rotation int getSurfaceTransformHint() {
- return Surface.ROTATION_0;
+ default @SurfaceControl.BufferTransform int getBufferTransformHint() {
+ return SurfaceControl.BUFFER_TRANSFORM_IDENTITY;
}
/**
- * Surface transform hint change listener.
- * @see #getSurfaceTransformHint
+ * Buffer transform hint change listener.
+ * @see #getBufferTransformHint
*/
@UiThread
- interface OnSurfaceTransformHintChangedListener {
+ interface OnBufferTransformHintChangedListener {
/**
* @param hint new surface transform hint
- * @see #getSurfaceTransformHint
+ * @see #getBufferTransformHint
*/
- void onSurfaceTransformHintChanged(@Surface.Rotation int hint);
+ void onBufferTransformHintChanged(@SurfaceControl.BufferTransform int hint);
}
/**
- * Registers a surface transform hint changed listener to receive notifications about when
+ * Registers a {@link OnBufferTransformHintChangedListener} to receive notifications about when
* the transform hint changes.
*
- * @see #getSurfaceTransformHint
- * @see #removeOnSurfaceTransformHintChangedListener
+ * @see #getBufferTransformHint
+ * @see #removeOnBufferTransformHintChangedListener
*/
- default void addOnSurfaceTransformHintChangedListener(
- @NonNull OnSurfaceTransformHintChangedListener listener) {
+ default void addOnBufferTransformHintChangedListener(
+ @NonNull OnBufferTransformHintChangedListener listener) {
}
/**
- * Unregisters a surface transform hint changed listener.
+ * Unregisters a {@link OnBufferTransformHintChangedListener}.
*
- * @see #addOnSurfaceTransformHintChangedListener
+ * @see #addOnBufferTransformHintChangedListener
*/
- default void removeOnSurfaceTransformHintChangedListener(
- @NonNull OnSurfaceTransformHintChangedListener listener) {
+ default void removeOnBufferTransformHintChangedListener(
+ @NonNull OnBufferTransformHintChangedListener listener) {
}
}
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index c786f0f3cc68..960d23d7afb0 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -241,10 +241,81 @@ public final class SurfaceControl implements Parcelable {
private static native void nativeRemoveJankDataListener(long nativeListener);
private static native long nativeCreateJankDataListenerWrapper(OnJankDataListener listener);
private static native int nativeGetGPUContextPriority();
- private static native void nativeSetTransformHint(long nativeObject, int transformHint);
+ private static native void nativeSetTransformHint(long nativeObject,
+ @SurfaceControl.BufferTransform int transformHint);
private static native int nativeGetTransformHint(long nativeObject);
private static native int nativeGetLayerId(long nativeObject);
+ /**
+ * Transforms that can be applied to buffers as they are displayed to a window.
+ *
+ * Supported transforms are any combination of horizontal mirror, vertical mirror, and
+ * clock-wise 90 degree rotation, in that order. Rotations of 180 and 270 degrees are made up
+ * of those basic transforms.
+ * Mirrors {@code ANativeWindowTransform} definitions.
+ * @hide
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = {"BUFFER_TRANSFORM_"},
+ value = {BUFFER_TRANSFORM_IDENTITY, BUFFER_TRANSFORM_MIRROR_HORIZONTAL,
+ BUFFER_TRANSFORM_MIRROR_VERTICAL, BUFFER_TRANSFORM_ROTATE_90,
+ BUFFER_TRANSFORM_ROTATE_180, BUFFER_TRANSFORM_ROTATE_270,
+ BUFFER_TRANSFORM_MIRROR_HORIZONTAL | BUFFER_TRANSFORM_ROTATE_90,
+ BUFFER_TRANSFORM_MIRROR_VERTICAL | BUFFER_TRANSFORM_ROTATE_90})
+ public @interface BufferTransform {
+ }
+
+ /**
+ * Identity transform.
+ *
+ * These transforms that can be applied to buffers as they are displayed to a window.
+ * @see HardwareBuffer
+ *
+ * Supported transforms are any combination of horizontal mirror, vertical mirror, and
+ * clock-wise 90 degree rotation, in that order. Rotations of 180 and 270 degrees are
+ * made up of those basic transforms.
+ */
+ public static final int BUFFER_TRANSFORM_IDENTITY = 0x00;
+ /**
+ * Mirror horizontally. Can be combined with {@link #BUFFER_TRANSFORM_MIRROR_VERTICAL}
+ * and {@link #BUFFER_TRANSFORM_ROTATE_90}.
+ */
+ public static final int BUFFER_TRANSFORM_MIRROR_HORIZONTAL = 0x01;
+ /**
+ * Mirror vertically. Can be combined with {@link #BUFFER_TRANSFORM_MIRROR_HORIZONTAL}
+ * and {@link #BUFFER_TRANSFORM_ROTATE_90}.
+ */
+ public static final int BUFFER_TRANSFORM_MIRROR_VERTICAL = 0x02;
+ /**
+ * Rotate 90 degrees clock-wise. Can be combined with {@link
+ * #BUFFER_TRANSFORM_MIRROR_HORIZONTAL} and {@link #BUFFER_TRANSFORM_MIRROR_VERTICAL}.
+ */
+ public static final int BUFFER_TRANSFORM_ROTATE_90 = 0x04;
+ /**
+ * Rotate 180 degrees clock-wise. Cannot be combined with other transforms.
+ */
+ public static final int BUFFER_TRANSFORM_ROTATE_180 =
+ BUFFER_TRANSFORM_MIRROR_HORIZONTAL | BUFFER_TRANSFORM_MIRROR_VERTICAL;
+ /**
+ * Rotate 270 degrees clock-wise. Cannot be combined with other transforms.
+ */
+ public static final int BUFFER_TRANSFORM_ROTATE_270 =
+ BUFFER_TRANSFORM_ROTATE_180 | BUFFER_TRANSFORM_ROTATE_90;
+
+ /**
+ * @hide
+ */
+ public static @BufferTransform int rotationToBufferTransform(@Surface.Rotation int rotation) {
+ switch (rotation) {
+ case Surface.ROTATION_0: return BUFFER_TRANSFORM_IDENTITY;
+ case Surface.ROTATION_90: return BUFFER_TRANSFORM_ROTATE_90;
+ case Surface.ROTATION_180: return BUFFER_TRANSFORM_ROTATE_180;
+ case Surface.ROTATION_270: return BUFFER_TRANSFORM_ROTATE_270;
+ }
+ Log.e(TAG, "Trying to convert unknown rotation=" + rotation);
+ return BUFFER_TRANSFORM_IDENTITY;
+ }
+
@Nullable
@GuardedBy("mLock")
private ArrayList<OnReparentListener> mReparentListeners;
@@ -3659,7 +3730,7 @@ public final class SurfaceControl implements Parcelable {
/**
* @hide
*/
- public int getTransformHint() {
+ public @SurfaceControl.BufferTransform int getTransformHint() {
checkNotReleased();
return nativeGetTransformHint(mNativeObject);
}
@@ -3673,7 +3744,7 @@ public final class SurfaceControl implements Parcelable {
* with the same size.
* @hide
*/
- public void setTransformHint(@Surface.Rotation int transformHint) {
+ public void setTransformHint(@SurfaceControl.BufferTransform int transformHint) {
nativeSetTransformHint(mNativeObject, transformHint);
}
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index 60bb99d37c39..856dfe53dfac 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -214,7 +214,7 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
final Rect mSurfaceFrame = new Rect();
int mLastSurfaceWidth = -1, mLastSurfaceHeight = -1;
- int mTransformHint = 0;
+ @SurfaceControl.BufferTransform int mTransformHint = 0;
private boolean mGlobalListenersAdded;
private boolean mAttachedToWindow;
@@ -1104,7 +1104,7 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall
|| mWindowSpaceTop != mLocation[1];
final boolean layoutSizeChanged = getWidth() != mScreenRect.width()
|| getHeight() != mScreenRect.height();
- final boolean hintChanged = (viewRoot.getSurfaceTransformHint() != mTransformHint)
+ final boolean hintChanged = (viewRoot.getBufferTransformHint() != mTransformHint)
&& mRequestedVisible;
if (creating || formatChanged || sizeChanged || visibleChanged ||
@@ -1130,7 +1130,7 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall
mSurfaceHeight = myHeight;
mFormat = mRequestedFormat;
mLastWindowVisibility = mWindowVisibility;
- mTransformHint = viewRoot.getSurfaceTransformHint();
+ mTransformHint = viewRoot.getBufferTransformHint();
mScreenRect.left = mWindowSpaceLeft;
mScreenRect.top = mWindowSpaceTop;
@@ -1362,7 +1362,7 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall
if (mBlastBufferQueue != null) {
mBlastBufferQueue.destroy();
}
- mTransformHint = viewRoot.getSurfaceTransformHint();
+ mTransformHint = viewRoot.getBufferTransformHint();
mBlastSurfaceControl.setTransformHint(mTransformHint);
mBlastBufferQueue = new BLASTBufferQueue(name, mBlastSurfaceControl, mSurfaceWidth,
mSurfaceHeight, mFormat);
@@ -1889,18 +1889,45 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall
* @param p The SurfacePackage to embed.
*/
public void setChildSurfacePackage(@NonNull SurfaceControlViewHost.SurfacePackage p) {
+ setChildSurfacePackage(p, false /* applyTransactionOnDraw */);
+ }
+
+ /**
+ * Similar to setChildSurfacePackage, but using the BLAST queue so the transaction can be
+ * synchronized with the ViewRootImpl frame.
+ * @hide
+ */
+ public void setChildSurfacePackageOnDraw(
+ @NonNull SurfaceControlViewHost.SurfacePackage p) {
+ setChildSurfacePackage(p, true /* applyTransactionOnDraw */);
+ }
+
+ /**
+ * @param applyTransactionOnDraw Whether to apply transaction at onDraw or immediately.
+ */
+ private void setChildSurfacePackage(
+ @NonNull SurfaceControlViewHost.SurfacePackage p, boolean applyTransactionOnDraw) {
final SurfaceControl lastSc = mSurfacePackage != null ?
mSurfacePackage.getSurfaceControl() : null;
if (mSurfaceControl != null && lastSc != null) {
- mTmpTransaction.reparent(lastSc, null).apply();
+ mTmpTransaction.reparent(lastSc, null);
mSurfacePackage.release();
+ applyTransaction(applyTransactionOnDraw);
} else if (mSurfaceControl != null) {
reparentSurfacePackage(mTmpTransaction, p);
- mTmpTransaction.apply();
+ applyTransaction(applyTransactionOnDraw);
}
mSurfacePackage = p;
}
+ private void applyTransaction(boolean applyTransactionOnDraw) {
+ if (applyTransactionOnDraw) {
+ getViewRootImpl().applyTransactionOnDraw(mTmpTransaction);
+ } else {
+ mTmpTransaction.apply();
+ }
+ }
+
private void reparentSurfacePackage(SurfaceControl.Transaction t,
SurfaceControlViewHost.SurfacePackage p) {
final SurfaceControl sc = p.getSurfaceControl();
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index b729c9fb01b1..572a7cdabdc9 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -27060,7 +27060,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
switch (event.mAction) {
case DragEvent.ACTION_DRAG_STARTED: {
- if (result && li.mOnDragListener != null) {
+ if (result && li != null && li.mOnDragListener != null) {
sendWindowContentChangedAccessibilityEvent(
AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
}
@@ -27074,7 +27074,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
refreshDrawableState();
} break;
case DragEvent.ACTION_DROP: {
- if (result && (li.mOnDragListener != null | li.mOnReceiveContentListener != null)) {
+ if (result && li != null && (li.mOnDragListener != null
+ || li.mOnReceiveContentListener != null)) {
sendWindowContentChangedAccessibilityEvent(
AccessibilityEvent.CONTENT_CHANGE_TYPE_DRAG_DROPPED);
}
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index c45b27a8a2c2..5a3a9d5bb571 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -312,9 +312,10 @@ public final class ViewRootImpl implements ViewParent,
static final ArrayList<Runnable> sFirstDrawHandlers = new ArrayList<>();
static boolean sFirstDrawComplete = false;
- private ArrayList<OnSurfaceTransformHintChangedListener> mTransformHintListeners =
+ private ArrayList<OnBufferTransformHintChangedListener> mTransformHintListeners =
new ArrayList<>();
- private @Surface.Rotation int mPreviousTransformHint = Surface.ROTATION_0;
+ private @SurfaceControl.BufferTransform
+ int mPreviousTransformHint = SurfaceControl.BUFFER_TRANSFORM_IDENTITY;
/**
* Callback for notifying about global configuration changes.
*/
@@ -10499,38 +10500,38 @@ public final class ViewRootImpl implements ViewParent,
}
@Override
- public @Surface.Rotation int getSurfaceTransformHint() {
+ public @SurfaceControl.BufferTransform int getBufferTransformHint() {
return mSurfaceControl.getTransformHint();
}
@Override
- public void addOnSurfaceTransformHintChangedListener(
- OnSurfaceTransformHintChangedListener listener) {
+ public void addOnBufferTransformHintChangedListener(
+ OnBufferTransformHintChangedListener listener) {
Objects.requireNonNull(listener);
if (mTransformHintListeners.contains(listener)) {
throw new IllegalArgumentException(
- "attempt to call addOnSurfaceTransformHintChangedListener() "
+ "attempt to call addOnBufferTransformHintChangedListener() "
+ "with a previously registered listener");
}
mTransformHintListeners.add(listener);
}
@Override
- public void removeOnSurfaceTransformHintChangedListener(
- OnSurfaceTransformHintChangedListener listener) {
+ public void removeOnBufferTransformHintChangedListener(
+ OnBufferTransformHintChangedListener listener) {
Objects.requireNonNull(listener);
mTransformHintListeners.remove(listener);
}
- private void dispatchTransformHintChanged(@Surface.Rotation int hint) {
+ private void dispatchTransformHintChanged(@SurfaceControl.BufferTransform int hint) {
if (mTransformHintListeners.isEmpty()) {
return;
}
- ArrayList<OnSurfaceTransformHintChangedListener> listeners =
- (ArrayList<OnSurfaceTransformHintChangedListener>) mTransformHintListeners.clone();
+ ArrayList<OnBufferTransformHintChangedListener> listeners =
+ (ArrayList<OnBufferTransformHintChangedListener>) mTransformHintListeners.clone();
for (int i = 0; i < listeners.size(); i++) {
- OnSurfaceTransformHintChangedListener listener = listeners.get(i);
- listener.onSurfaceTransformHintChanged(hint);
+ OnBufferTransformHintChangedListener listener = listeners.get(i);
+ listener.onBufferTransformHintChanged(hint);
}
}
}
diff --git a/core/java/android/view/WindowManagerImpl.java b/core/java/android/view/WindowManagerImpl.java
index 7631269d9c1c..1c915cb016d4 100644
--- a/core/java/android/view/WindowManagerImpl.java
+++ b/core/java/android/view/WindowManagerImpl.java
@@ -352,25 +352,11 @@ public final class WindowManagerImpl implements WindowManager {
throw e.rethrowFromSystemServer();
}
- int size = possibleDisplayInfos.size();
- DisplayInfo currentDisplayInfo;
- WindowInsets windowInsets = null;
- if (size > 0) {
- currentDisplayInfo = possibleDisplayInfos.get(0);
-
- final WindowManager.LayoutParams params = new WindowManager.LayoutParams();
- final boolean isScreenRound = (currentDisplayInfo.flags & Display.FLAG_ROUND) != 0;
- // TODO(181127261) not computing insets correctly - need to have underlying
- // frame reflect the faked orientation.
- windowInsets = getWindowInsetsFromServerForDisplay(
- currentDisplayInfo.displayId, params,
- new Rect(0, 0, currentDisplayInfo.getNaturalWidth(),
- currentDisplayInfo.getNaturalHeight()), isScreenRound,
- WINDOWING_MODE_FULLSCREEN);
- }
-
Set<WindowMetrics> maxMetrics = new HashSet<>();
- for (int i = 0; i < size; i++) {
+ WindowInsets windowInsets;
+ DisplayInfo currentDisplayInfo;
+ final WindowManager.LayoutParams params = new WindowManager.LayoutParams();
+ for (int i = 0; i < possibleDisplayInfos.size(); i++) {
currentDisplayInfo = possibleDisplayInfos.get(i);
// Calculate max bounds for this rotation and state.
@@ -378,7 +364,18 @@ public final class WindowManagerImpl implements WindowManager {
currentDisplayInfo.logicalHeight);
// Calculate insets for the rotated max bounds.
- // TODO(181127261) calculate insets for each display rotation and state.
+ final boolean isScreenRound = (currentDisplayInfo.flags & Display.FLAG_ROUND) != 0;
+ // Initialize insets based upon display rotation. Note any window-provided insets
+ // will not be set.
+ windowInsets = getWindowInsetsFromServerForDisplay(
+ currentDisplayInfo.displayId, params,
+ new Rect(0, 0, currentDisplayInfo.getNaturalWidth(),
+ currentDisplayInfo.getNaturalHeight()), isScreenRound,
+ WINDOWING_MODE_FULLSCREEN);
+ // Set the hardware-provided insets.
+ windowInsets = new WindowInsets.Builder(windowInsets).setRoundedCorners(
+ currentDisplayInfo.roundedCorners)
+ .setDisplayCutout(currentDisplayInfo.displayCutout).build();
maxMetrics.add(new WindowMetrics(maxBounds, windowInsets));
}
diff --git a/core/java/android/window/SplashScreen.java b/core/java/android/window/SplashScreen.java
index 3e0075857402..3354a6ca7738 100644
--- a/core/java/android/window/SplashScreen.java
+++ b/core/java/android/window/SplashScreen.java
@@ -241,7 +241,6 @@ public interface SplashScreen {
public void handOverSplashScreenView(@NonNull IBinder token,
@NonNull SplashScreenView splashScreenView) {
- transferSurface(splashScreenView);
dispatchOnExitAnimation(token, splashScreenView);
}
@@ -265,9 +264,5 @@ public interface SplashScreen {
return impl != null && impl.mExitAnimationListener != null;
}
}
-
- private void transferSurface(@NonNull SplashScreenView splashScreenView) {
- splashScreenView.transferSurface();
- }
}
}
diff --git a/core/java/android/window/SplashScreenView.java b/core/java/android/window/SplashScreenView.java
index f14294ee512e..f748d4bc121d 100644
--- a/core/java/android/window/SplashScreenView.java
+++ b/core/java/android/window/SplashScreenView.java
@@ -464,7 +464,10 @@ public final class SplashScreenView extends FrameLayout {
}
- void transferSurface() {
+ /**
+ * @hide
+ */
+ public void syncTransferSurfaceOnDraw() {
if (mSurfacePackage == null) {
return;
}
@@ -474,8 +477,8 @@ public final class SplashScreenView extends FrameLayout {
String.format("SurfacePackage'surface reparented to %s", parent)));
Log.d(TAG, "Transferring surface " + mSurfaceView.toString());
}
- mSurfaceView.setChildSurfacePackage(mSurfacePackage);
+ mSurfaceView.setChildSurfacePackageOnDraw(mSurfacePackage);
}
void initIconAnimation(Drawable iconDrawable, long duration) {
@@ -533,10 +536,6 @@ public final class SplashScreenView extends FrameLayout {
restoreSystemUIColors();
mWindow = null;
}
- if (mHostActivity != null) {
- mHostActivity.setSplashScreenView(null);
- mHostActivity = null;
- }
mHasRemoved = true;
}
@@ -582,7 +581,6 @@ public final class SplashScreenView extends FrameLayout {
* @hide
*/
public void attachHostActivityAndSetSystemUIColors(Activity activity, Window window) {
- activity.setSplashScreenView(this);
mHostActivity = activity;
mWindow = window;
final WindowManager.LayoutParams attr = window.getAttributes();
diff --git a/core/java/com/android/internal/policy/IKeyguardStateCallback.aidl b/core/java/com/android/internal/policy/IKeyguardStateCallback.aidl
index 8e454db4cb04..419b1f8feac7 100644
--- a/core/java/com/android/internal/policy/IKeyguardStateCallback.aidl
+++ b/core/java/com/android/internal/policy/IKeyguardStateCallback.aidl
@@ -20,5 +20,4 @@ interface IKeyguardStateCallback {
void onSimSecureStateChanged(boolean simSecure);
void onInputRestrictedStateChanged(boolean inputRestricted);
void onTrustedChanged(boolean trusted);
- void onHasLockscreenWallpaperChanged(boolean hasLockscreenWallpaper);
} \ No newline at end of file
diff --git a/core/java/com/android/internal/protolog/ProtoLogGroup.java b/core/java/com/android/internal/protolog/ProtoLogGroup.java
index db019a67772f..954204fe781c 100644
--- a/core/java/com/android/internal/protolog/ProtoLogGroup.java
+++ b/core/java/com/android/internal/protolog/ProtoLogGroup.java
@@ -84,6 +84,7 @@ public enum ProtoLogGroup implements IProtoLogGroup {
Consts.TAG_WM),
WM_DEBUG_LAYER_MIRRORING(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, true,
Consts.TAG_WM),
+ WM_DEBUG_WALLPAPER(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, true, Consts.TAG_WM),
TEST_GROUP(true, true, false, "WindowManagerProtoLogTest");
private final boolean mEnabled;
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index 65ff7c7f5a0f..1452c67ae3c6 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -1815,16 +1815,12 @@ static void nativeSetTransformHint(JNIEnv* env, jclass clazz, jlong nativeSurfac
if (surface == nullptr) {
return;
}
- surface->setTransformHint(
- ui::Transform::toRotationFlags(static_cast<ui::Rotation>(transformHint)));
+ surface->setTransformHint(transformHint);
}
static jint nativeGetTransformHint(JNIEnv* env, jclass clazz, jlong nativeSurfaceControl) {
sp<SurfaceControl> surface(reinterpret_cast<SurfaceControl*>(nativeSurfaceControl));
- ui::Transform::RotationFlags transformHintRotationFlags =
- static_cast<ui::Transform::RotationFlags>(surface->getTransformHint());
-
- return toRotationInt(ui::Transform::toRotation((transformHintRotationFlags)));
+ return surface->getTransformHint();
}
static jint nativeGetLayerId(JNIEnv* env, jclass clazz, jlong nativeSurfaceControl) {
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index ddc9f0225025..3718d2832f51 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -4068,7 +4068,7 @@
<string translatable="false" name="config_inCallNotificationSound">/product/media/audio/ui/InCallNotification.ogg</string>
<!-- URI for default ringtone sound file to be used for silent ringer vibration -->
- <string translatable="false" name="config_defaultRingtoneVibrationSound">/product/media/audio/ui/AttentionalHaptics.ogg</string>
+ <string translatable="false" name="config_defaultRingtoneVibrationSound"></string>
<!-- Default number of notifications from the same app before they are automatically grouped by the OS -->
<integer translatable="false" name="config_autoGroupAtCount">4</integer>
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index 909ca3988c5d..95736074a3ef 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -595,6 +595,12 @@
"group": "WM_DEBUG_ORIENTATION",
"at": "com\/android\/server\/wm\/DisplayContent.java"
},
+ "-1478175541": {
+ "message": "No longer animating wallpaper targets!",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WALLPAPER",
+ "at": "com\/android\/server\/wm\/WallpaperController.java"
+ },
"-1474602871": {
"message": "Launch on display check: disallow launch on virtual display for not-embedded activity.",
"level": "DEBUG",
@@ -1621,6 +1627,12 @@
"group": "WM_DEBUG_TASKS",
"at": "com\/android\/server\/wm\/RootWindowContainer.java"
},
+ "-360208282": {
+ "message": "Animating wallpapers: old: %s hidden=%b new: %s hidden=%b",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WALLPAPER",
+ "at": "com\/android\/server\/wm\/WallpaperController.java"
+ },
"-354571697": {
"message": "Existence Changed in transition %d: %s",
"level": "VERBOSE",
@@ -1675,6 +1687,12 @@
"group": "WM_DEBUG_LAYER_MIRRORING",
"at": "com\/android\/server\/wm\/DisplayContent.java"
},
+ "-304728471": {
+ "message": "New wallpaper: target=%s prev=%s",
+ "level": "DEBUG",
+ "group": "WM_DEBUG_WALLPAPER",
+ "at": "com\/android\/server\/wm\/WallpaperController.java"
+ },
"-302468788": {
"message": "Expected target rootTask=%s to be top most but found rootTask=%s",
"level": "WARN",
@@ -1693,6 +1711,12 @@
"group": "WM_ERROR",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
+ "-275077723": {
+ "message": "New animation: %s old animation: %s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WALLPAPER",
+ "at": "com\/android\/server\/wm\/WallpaperController.java"
+ },
"-262984451": {
"message": "Relaunch failed %s",
"level": "INFO",
@@ -1747,6 +1771,12 @@
"group": "WM_DEBUG_LAYER_MIRRORING",
"at": "com\/android\/server\/wm\/DisplayContent.java"
},
+ "-182877285": {
+ "message": "Wallpaper layer changed: assigning layers + relayout",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WALLPAPER",
+ "at": "com\/android\/server\/wm\/DisplayContent.java"
+ },
"-177040661": {
"message": "Start rotation animation. customAnim=%s, mCurRotation=%s, mOriginalRotation=%s",
"level": "DEBUG",
@@ -2005,6 +2035,12 @@
"group": "WM_DEBUG_STARTING_WINDOW",
"at": "com\/android\/server\/wm\/ActivityRecord.java"
},
+ "114070759": {
+ "message": "New wallpaper target: %s prevTarget: %s caller=%s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WALLPAPER",
+ "at": "com\/android\/server\/wm\/WallpaperController.java"
+ },
"115358443": {
"message": "Focus changing: %s -> %s",
"level": "INFO",
@@ -2347,6 +2383,12 @@
"group": "WM_DEBUG_RESIZE",
"at": "com\/android\/server\/wm\/WindowState.java"
},
+ "422634333": {
+ "message": "First draw done in potential wallpaper target %s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WALLPAPER",
+ "at": "com\/android\/server\/wm\/DisplayContent.java"
+ },
"424524729": {
"message": "Attempted to add wallpaper window with unknown token %s. Aborting.",
"level": "WARN",
@@ -2371,12 +2413,6 @@
"group": "WM_ERROR",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
- "457951957": {
- "message": "\tNot visible=%s",
- "level": "DEBUG",
- "group": "WM_DEBUG_REMOTE_ANIMATIONS",
- "at": "com\/android\/server\/wm\/WallpaperAnimationAdapter.java"
- },
"463993897": {
"message": "Aborted waiting for drawn: %s",
"level": "WARN",
@@ -2425,6 +2461,12 @@
"group": "WM_SHOW_TRANSACTIONS",
"at": "com\/android\/server\/wm\/WindowContainerThumbnail.java"
},
+ "535103992": {
+ "message": "Wallpaper may change! Adjusting",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WALLPAPER",
+ "at": "com\/android\/server\/wm\/RootWindowContainer.java"
+ },
"539077569": {
"message": "Clear freezing of %s force=%b",
"level": "VERBOSE",
@@ -2653,6 +2695,12 @@
"group": "WM_DEBUG_STATES",
"at": "com\/android\/server\/wm\/ActivityRecord.java"
},
+ "733466617": {
+ "message": "Wallpaper token %s visible=%b",
+ "level": "DEBUG",
+ "group": "WM_DEBUG_WALLPAPER",
+ "at": "com\/android\/server\/wm\/WallpaperWindowToken.java"
+ },
"736692676": {
"message": "Config is relaunching %s",
"level": "VERBOSE",
@@ -2989,6 +3037,12 @@
"group": "WM_DEBUG_APP_TRANSITIONS",
"at": "com\/android\/server\/wm\/DisplayContent.java"
},
+ "1178653181": {
+ "message": "Old wallpaper still the target.",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WALLPAPER",
+ "at": "com\/android\/server\/wm\/WallpaperController.java"
+ },
"1186730970": {
"message": " no common mode yet, so set it",
"level": "VERBOSE",
@@ -3667,6 +3721,12 @@
"group": "WM_SHOW_TRANSACTIONS",
"at": "com\/android\/server\/wm\/WindowAnimator.java"
},
+ "1984843251": {
+ "message": "Hiding wallpaper %s from %s target=%s prev=%s callers=%s",
+ "level": "DEBUG",
+ "group": "WM_DEBUG_WALLPAPER",
+ "at": "com\/android\/server\/wm\/WallpaperController.java"
+ },
"1995093920": {
"message": "Checking to restart %s: changed=0x%s, handles=0x%s, mLastReportedConfiguration=%s",
"level": "VERBOSE",
@@ -3697,6 +3757,12 @@
"group": "WM_DEBUG_REMOTE_ANIMATIONS",
"at": "com\/android\/server\/wm\/RemoteAnimationController.java"
},
+ "2024493888": {
+ "message": "\tWallpaper of display=%s is not visible",
+ "level": "DEBUG",
+ "group": "WM_DEBUG_REMOTE_ANIMATIONS",
+ "at": "com\/android\/server\/wm\/WallpaperAnimationAdapter.java"
+ },
"2028163120": {
"message": "applyAnimation: anim=%s nextAppTransition=ANIM_SCALE_UP transit=%s isEntrance=%s Callers=%s",
"level": "VERBOSE",
@@ -3721,12 +3787,6 @@
"group": "WM_DEBUG_APP_TRANSITIONS",
"at": "com\/android\/server\/wm\/AppTransitionController.java"
},
- "2057434754": {
- "message": "\tvisible=%s",
- "level": "DEBUG",
- "group": "WM_DEBUG_REMOTE_ANIMATIONS",
- "at": "com\/android\/server\/wm\/WallpaperAnimationAdapter.java"
- },
"2060978050": {
"message": "moveWindowTokenToDisplay: Attempted to move token: %s to non-exiting displayId=%d",
"level": "WARN",
@@ -3867,6 +3927,9 @@
"WM_DEBUG_TASKS": {
"tag": "WindowManager"
},
+ "WM_DEBUG_WALLPAPER": {
+ "tag": "WindowManager"
+ },
"WM_DEBUG_WINDOW_INSETS": {
"tag": "WindowManager"
},
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SidecarProvider.java b/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SidecarProvider.java
index e6f8388b031f..224db0291902 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SidecarProvider.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SidecarProvider.java
@@ -36,6 +36,6 @@ public class SidecarProvider {
* @return API version string in MAJOR.MINOR.PATCH-description format.
*/
public static String getApiVersion() {
- return "0.1.0-settings_sample";
+ return "1.0.0-reference";
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/ISplitScreen.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/ISplitScreen.aidl
new file mode 100644
index 000000000000..45f6d3c8b154
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/ISplitScreen.aidl
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.stagesplit;
+
+import android.app.PendingIntent;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.UserHandle;
+import android.view.RemoteAnimationAdapter;
+import android.view.RemoteAnimationTarget;
+import android.window.RemoteTransition;
+
+import com.android.wm.shell.stagesplit.ISplitScreenListener;
+
+/**
+ * Interface that is exposed to remote callers to manipulate the splitscreen feature.
+ */
+interface ISplitScreen {
+
+ /**
+ * Registers a split screen listener.
+ */
+ oneway void registerSplitScreenListener(in ISplitScreenListener listener) = 1;
+
+ /**
+ * Unregisters a split screen listener.
+ */
+ oneway void unregisterSplitScreenListener(in ISplitScreenListener listener) = 2;
+
+ /**
+ * Hides the side-stage if it is currently visible.
+ */
+ oneway void setSideStageVisibility(boolean visible) = 3;
+
+ /**
+ * Removes a task from the side stage.
+ */
+ oneway void removeFromSideStage(int taskId) = 4;
+
+ /**
+ * Removes the split-screen stages and leaving indicated task to top. Passing INVALID_TASK_ID
+ * to indicate leaving no top task after leaving split-screen.
+ */
+ oneway void exitSplitScreen(int toTopTaskId) = 5;
+
+ /**
+ * @param exitSplitScreenOnHide if to exit split-screen if both stages are not visible.
+ */
+ oneway void exitSplitScreenOnHide(boolean exitSplitScreenOnHide) = 6;
+
+ /**
+ * Starts a task in a stage.
+ */
+ oneway void startTask(int taskId, int stage, int position, in Bundle options) = 7;
+
+ /**
+ * Starts a shortcut in a stage.
+ */
+ oneway void startShortcut(String packageName, String shortcutId, int stage, int position,
+ in Bundle options, in UserHandle user) = 8;
+
+ /**
+ * Starts an activity in a stage.
+ */
+ oneway void startIntent(in PendingIntent intent, in Intent fillInIntent, int stage,
+ int position, in Bundle options) = 9;
+
+ /**
+ * Starts tasks simultaneously in one transition.
+ */
+ oneway void startTasks(int mainTaskId, in Bundle mainOptions, int sideTaskId,
+ in Bundle sideOptions, int sidePosition, in RemoteTransition remoteTransition) = 10;
+
+ /**
+ * Version of startTasks using legacy transition system.
+ */
+ oneway void startTasksWithLegacyTransition(int mainTaskId, in Bundle mainOptions,
+ int sideTaskId, in Bundle sideOptions, int sidePosition,
+ in RemoteAnimationAdapter adapter) = 11;
+
+ /**
+ * Blocking call that notifies and gets additional split-screen targets when entering
+ * recents (for example: the dividerBar).
+ * @param cancel is true if leaving recents back to split (eg. the gesture was cancelled).
+ * @param appTargets apps that will be re-parented to display area
+ */
+ RemoteAnimationTarget[] onGoingToRecentsLegacy(boolean cancel,
+ in RemoteAnimationTarget[] appTargets) = 12;
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/ISplitScreenListener.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/ISplitScreenListener.aidl
new file mode 100644
index 000000000000..46e4299f99fa
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/ISplitScreenListener.aidl
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.stagesplit;
+
+/**
+ * Listener interface that Launcher attaches to SystemUI to get split-screen callbacks.
+ */
+oneway interface ISplitScreenListener {
+
+ /**
+ * Called when the stage position changes.
+ */
+ void onStagePositionChanged(int stage, int position);
+
+ /**
+ * Called when a task changes stages.
+ */
+ void onTaskStageChanged(int taskId, int stage, boolean visible);
+} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/MainStage.java b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/MainStage.java
new file mode 100644
index 000000000000..83855be91e04
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/MainStage.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2020 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.stagesplit;
+
+import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
+
+import android.annotation.Nullable;
+import android.graphics.Rect;
+import android.view.SurfaceSession;
+import android.window.WindowContainerToken;
+import android.window.WindowContainerTransaction;
+
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.SyncTransactionQueue;
+
+/**
+ * Main stage for split-screen mode. When split-screen is active all standard activity types launch
+ * on the main stage, except for task that are explicitly pinned to the {@link SideStage}.
+ * @see StageCoordinator
+ */
+class MainStage extends StageTaskListener {
+ private static final String TAG = MainStage.class.getSimpleName();
+
+ private boolean mIsActive = false;
+
+ MainStage(ShellTaskOrganizer taskOrganizer, int displayId,
+ StageListenerCallbacks callbacks, SyncTransactionQueue syncQueue,
+ SurfaceSession surfaceSession,
+ @Nullable StageTaskUnfoldController stageTaskUnfoldController) {
+ super(taskOrganizer, displayId, callbacks, syncQueue, surfaceSession,
+ stageTaskUnfoldController);
+ }
+
+ boolean isActive() {
+ return mIsActive;
+ }
+
+ void activate(Rect rootBounds, WindowContainerTransaction wct) {
+ if (mIsActive) return;
+
+ final WindowContainerToken rootToken = mRootTaskInfo.token;
+ wct.setBounds(rootToken, rootBounds)
+ .setWindowingMode(rootToken, WINDOWING_MODE_MULTI_WINDOW)
+ .setLaunchRoot(
+ rootToken,
+ CONTROLLED_WINDOWING_MODES,
+ CONTROLLED_ACTIVITY_TYPES)
+ .reparentTasks(
+ null /* currentParent */,
+ rootToken,
+ CONTROLLED_WINDOWING_MODES,
+ CONTROLLED_ACTIVITY_TYPES,
+ true /* onTop */)
+ // Moving the root task to top after the child tasks were re-parented , or the root
+ // task cannot be visible and focused.
+ .reorder(rootToken, true /* onTop */);
+
+ mIsActive = true;
+ }
+
+ void deactivate(WindowContainerTransaction wct) {
+ deactivate(wct, false /* toTop */);
+ }
+
+ void deactivate(WindowContainerTransaction wct, boolean toTop) {
+ if (!mIsActive) return;
+ mIsActive = false;
+
+ if (mRootTaskInfo == null) return;
+ final WindowContainerToken rootToken = mRootTaskInfo.token;
+ wct.setLaunchRoot(
+ rootToken,
+ null,
+ null)
+ .reparentTasks(
+ rootToken,
+ null /* newParent */,
+ CONTROLLED_WINDOWING_MODES_WHEN_ACTIVE,
+ CONTROLLED_ACTIVITY_TYPES,
+ toTop)
+ // We want this re-order to the bottom regardless since we are re-parenting
+ // all its tasks.
+ .reorder(rootToken, false /* onTop */);
+ }
+
+ void updateConfiguration(int windowingMode, Rect bounds, WindowContainerTransaction wct) {
+ wct.setBounds(mRootTaskInfo.token, bounds)
+ .setWindowingMode(mRootTaskInfo.token, windowingMode);
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/OutlineManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/OutlineManager.java
new file mode 100644
index 000000000000..8fbad52c630f
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/OutlineManager.java
@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.stagesplit;
+
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.os.Binder;
+import android.view.IWindow;
+import android.view.InsetsSource;
+import android.view.InsetsState;
+import android.view.LayoutInflater;
+import android.view.SurfaceControl;
+import android.view.SurfaceControlViewHost;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.view.WindowlessWindowManager;
+import android.widget.FrameLayout;
+
+import com.android.wm.shell.R;
+
+/**
+ * Handles drawing outline of the bounds of provided root surface. The outline will be drown with
+ * the consideration of display insets like status bar, navigation bar and display cutout.
+ */
+class OutlineManager extends WindowlessWindowManager {
+ private static final String WINDOW_NAME = "SplitOutlineLayer";
+ private final Context mContext;
+ private final Rect mRootBounds = new Rect();
+ private final Rect mTempRect = new Rect();
+ private final Rect mLastOutlineBounds = new Rect();
+ private final InsetsState mInsetsState = new InsetsState();
+ private final int mExpandedTaskBarHeight;
+ private OutlineView mOutlineView;
+ private SurfaceControlViewHost mViewHost;
+ private SurfaceControl mHostLeash;
+ private SurfaceControl mLeash;
+
+ OutlineManager(Context context, Configuration configuration) {
+ super(configuration, null /* rootSurface */, null /* hostInputToken */);
+ mContext = context.createWindowContext(context.getDisplay(), TYPE_APPLICATION_OVERLAY,
+ null /* options */);
+ mExpandedTaskBarHeight = mContext.getResources().getDimensionPixelSize(
+ com.android.internal.R.dimen.taskbar_frame_height);
+ }
+
+ @Override
+ protected void attachToParentSurface(IWindow window, SurfaceControl.Builder b) {
+ b.setParent(mHostLeash);
+ }
+
+ void inflate(SurfaceControl rootLeash, Rect rootBounds) {
+ if (mLeash != null || mViewHost != null) return;
+
+ mHostLeash = rootLeash;
+ mRootBounds.set(rootBounds);
+ mViewHost = new SurfaceControlViewHost(mContext, mContext.getDisplay(), this);
+
+ final FrameLayout rootLayout = (FrameLayout) LayoutInflater.from(mContext)
+ .inflate(R.layout.split_outline, null);
+ mOutlineView = rootLayout.findViewById(R.id.split_outline);
+
+ final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
+ 0 /* width */, 0 /* height */, TYPE_APPLICATION_OVERLAY,
+ FLAG_NOT_FOCUSABLE | FLAG_NOT_TOUCHABLE, PixelFormat.TRANSLUCENT);
+ lp.width = mRootBounds.width();
+ lp.height = mRootBounds.height();
+ lp.token = new Binder();
+ lp.setTitle(WINDOW_NAME);
+ lp.privateFlags |= PRIVATE_FLAG_NO_MOVE_ANIMATION | PRIVATE_FLAG_TRUSTED_OVERLAY;
+ // TODO(b/189839391): Set INPUT_FEATURE_NO_INPUT_CHANNEL after WM supports
+ // TRUSTED_OVERLAY for windowless window without input channel.
+ mViewHost.setView(rootLayout, lp);
+ mLeash = getSurfaceControl(mViewHost.getWindowToken());
+
+ drawOutline();
+ }
+
+ void release() {
+ if (mViewHost != null) {
+ mViewHost.release();
+ mViewHost = null;
+ }
+ mRootBounds.setEmpty();
+ mLastOutlineBounds.setEmpty();
+ mOutlineView = null;
+ mHostLeash = null;
+ mLeash = null;
+ }
+
+ @Nullable
+ SurfaceControl getOutlineLeash() {
+ return mLeash;
+ }
+
+ void setVisibility(boolean visible) {
+ if (mOutlineView != null) {
+ mOutlineView.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
+ }
+ }
+
+ void setRootBounds(Rect rootBounds) {
+ if (mViewHost == null || mViewHost.getView() == null) {
+ return;
+ }
+
+ if (!mRootBounds.equals(rootBounds)) {
+ WindowManager.LayoutParams lp =
+ (WindowManager.LayoutParams) mViewHost.getView().getLayoutParams();
+ lp.width = rootBounds.width();
+ lp.height = rootBounds.height();
+ mViewHost.relayout(lp);
+ mRootBounds.set(rootBounds);
+ drawOutline();
+ }
+ }
+
+ void onInsetsChanged(InsetsState insetsState) {
+ if (!mInsetsState.equals(insetsState)) {
+ mInsetsState.set(insetsState);
+ drawOutline();
+ }
+ }
+
+ private void computeOutlineBounds(Rect rootBounds, InsetsState insetsState, Rect outBounds) {
+ outBounds.set(rootBounds);
+ final InsetsSource taskBarInsetsSource =
+ insetsState.getSource(InsetsState.ITYPE_EXTRA_NAVIGATION_BAR);
+ // Only insets the divider bar with task bar when it's expanded so that the rounded corners
+ // will be drawn against task bar.
+ if (taskBarInsetsSource.getFrame().height() >= mExpandedTaskBarHeight) {
+ outBounds.inset(taskBarInsetsSource.calculateVisibleInsets(outBounds));
+ }
+
+ // Offset the coordinate from screen based to surface based.
+ outBounds.offset(-rootBounds.left, -rootBounds.top);
+ }
+
+ void drawOutline() {
+ if (mOutlineView == null) {
+ return;
+ }
+
+ computeOutlineBounds(mRootBounds, mInsetsState, mTempRect);
+ if (mTempRect.equals(mLastOutlineBounds)) {
+ return;
+ }
+
+ ViewGroup.MarginLayoutParams lp =
+ (ViewGroup.MarginLayoutParams) mOutlineView.getLayoutParams();
+ lp.leftMargin = mTempRect.left;
+ lp.topMargin = mTempRect.top;
+ lp.width = mTempRect.width();
+ lp.height = mTempRect.height();
+ mOutlineView.setLayoutParams(lp);
+ mLastOutlineBounds.set(mTempRect);
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/OutlineView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/OutlineView.java
new file mode 100644
index 000000000000..92b1381fc808
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/OutlineView.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.stagesplit;
+
+import static android.view.RoundedCorner.POSITION_BOTTOM_LEFT;
+import static android.view.RoundedCorner.POSITION_BOTTOM_RIGHT;
+import static android.view.RoundedCorner.POSITION_TOP_LEFT;
+import static android.view.RoundedCorner.POSITION_TOP_RIGHT;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.util.AttributeSet;
+import android.view.RoundedCorner;
+import android.view.View;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.internal.R;
+
+/** View for drawing split outline. */
+public class OutlineView extends View {
+ private final Paint mPaint = new Paint();
+ private final Path mPath = new Path();
+ private final float[] mRadii = new float[8];
+
+ public OutlineView(@NonNull Context context, @Nullable AttributeSet attrs) {
+ super(context, attrs);
+ mPaint.setStyle(Paint.Style.STROKE);
+ mPaint.setStrokeWidth(
+ getResources().getDimension(R.dimen.accessibility_focus_highlight_stroke_width));
+ mPaint.setColor(getResources().getColor(R.color.system_accent1_100, null));
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ // TODO(b/200850654): match the screen corners with the actual display decor.
+ mRadii[0] = mRadii[1] = getCornerRadius(POSITION_TOP_LEFT);
+ mRadii[2] = mRadii[3] = getCornerRadius(POSITION_TOP_RIGHT);
+ mRadii[4] = mRadii[5] = getCornerRadius(POSITION_BOTTOM_RIGHT);
+ mRadii[6] = mRadii[7] = getCornerRadius(POSITION_BOTTOM_LEFT);
+ }
+
+ private int getCornerRadius(@RoundedCorner.Position int position) {
+ final RoundedCorner roundedCorner = getDisplay().getRoundedCorner(position);
+ return roundedCorner == null ? 0 : roundedCorner.getRadius();
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ if (changed) {
+ mPath.reset();
+ mPath.addRoundRect(0, 0, getWidth(), getHeight(), mRadii, Path.Direction.CW);
+ }
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ canvas.drawPath(mPath, mPaint);
+ }
+
+ @Override
+ public boolean hasOverlappingRendering() {
+ return false;
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SideStage.java b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SideStage.java
new file mode 100644
index 000000000000..55c4f3aea19a
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SideStage.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2020 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.stagesplit;
+
+import android.annotation.CallSuper;
+import android.annotation.Nullable;
+import android.app.ActivityManager;
+import android.content.Context;
+import android.graphics.Rect;
+import android.view.InsetsSourceControl;
+import android.view.InsetsState;
+import android.view.SurfaceControl;
+import android.view.SurfaceSession;
+import android.window.WindowContainerToken;
+import android.window.WindowContainerTransaction;
+
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.DisplayInsetsController;
+import com.android.wm.shell.common.SyncTransactionQueue;
+
+/**
+ * Side stage for split-screen mode. Only tasks that are explicitly pinned to this stage show up
+ * here. All other task are launch in the {@link MainStage}.
+ *
+ * @see StageCoordinator
+ */
+class SideStage extends StageTaskListener implements
+ DisplayInsetsController.OnInsetsChangedListener {
+ private static final String TAG = SideStage.class.getSimpleName();
+ private final Context mContext;
+ private OutlineManager mOutlineManager;
+
+ SideStage(Context context, ShellTaskOrganizer taskOrganizer, int displayId,
+ StageListenerCallbacks callbacks, SyncTransactionQueue syncQueue,
+ SurfaceSession surfaceSession,
+ @Nullable StageTaskUnfoldController stageTaskUnfoldController) {
+ super(taskOrganizer, displayId, callbacks, syncQueue, surfaceSession,
+ stageTaskUnfoldController);
+ mContext = context;
+ }
+
+ void addTask(ActivityManager.RunningTaskInfo task, Rect rootBounds,
+ WindowContainerTransaction wct) {
+ final WindowContainerToken rootToken = mRootTaskInfo.token;
+ wct.setBounds(rootToken, rootBounds)
+ .reparent(task.token, rootToken, true /* onTop*/)
+ // Moving the root task to top after the child tasks were reparented , or the root
+ // task cannot be visible and focused.
+ .reorder(rootToken, true /* onTop */);
+ }
+
+ boolean removeAllTasks(WindowContainerTransaction wct, boolean toTop) {
+ // No matter if the root task is empty or not, moving the root to bottom because it no
+ // longer preserves visible child task.
+ wct.reorder(mRootTaskInfo.token, false /* onTop */);
+ if (mChildrenTaskInfo.size() == 0) return false;
+ wct.reparentTasks(
+ mRootTaskInfo.token,
+ null /* newParent */,
+ CONTROLLED_WINDOWING_MODES_WHEN_ACTIVE,
+ CONTROLLED_ACTIVITY_TYPES,
+ toTop);
+ return true;
+ }
+
+ boolean removeTask(int taskId, WindowContainerToken newParent, WindowContainerTransaction wct) {
+ final ActivityManager.RunningTaskInfo task = mChildrenTaskInfo.get(taskId);
+ if (task == null) return false;
+ wct.reparent(task.token, newParent, false /* onTop */);
+ return true;
+ }
+
+ @Nullable
+ public SurfaceControl getOutlineLeash() {
+ return mOutlineManager.getOutlineLeash();
+ }
+
+ @Override
+ @CallSuper
+ public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) {
+ super.onTaskAppeared(taskInfo, leash);
+ if (isRootTask(taskInfo)) {
+ mOutlineManager = new OutlineManager(mContext, taskInfo.configuration);
+ enableOutline(true);
+ }
+ }
+
+ @Override
+ @CallSuper
+ public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) {
+ super.onTaskInfoChanged(taskInfo);
+ if (isRootTask(taskInfo)) {
+ mOutlineManager.setRootBounds(taskInfo.configuration.windowConfiguration.getBounds());
+ }
+ }
+
+ private boolean isRootTask(ActivityManager.RunningTaskInfo taskInfo) {
+ return mRootTaskInfo != null && mRootTaskInfo.taskId == taskInfo.taskId;
+ }
+
+ void enableOutline(boolean enable) {
+ if (mOutlineManager == null) {
+ return;
+ }
+
+ if (enable) {
+ if (mRootTaskInfo != null) {
+ mOutlineManager.inflate(mRootLeash,
+ mRootTaskInfo.configuration.windowConfiguration.getBounds());
+ }
+ } else {
+ mOutlineManager.release();
+ }
+ }
+
+ void setOutlineVisibility(boolean visible) {
+ mOutlineManager.setVisibility(visible);
+ }
+
+ @Override
+ public void insetsChanged(InsetsState insetsState) {
+ mOutlineManager.onInsetsChanged(insetsState);
+ }
+
+ @Override
+ public void insetsControlChanged(InsetsState insetsState,
+ InsetsSourceControl[] activeControls) {
+ insetsChanged(insetsState);
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitScreen.java b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitScreen.java
new file mode 100644
index 000000000000..aec81a1ee86a
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitScreen.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2020 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.stagesplit;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+
+import com.android.wm.shell.common.annotations.ExternalThread;
+import com.android.wm.shell.common.split.SplitLayout.SplitPosition;
+
+import java.util.concurrent.Executor;
+
+/**
+ * Interface to engage split-screen feature.
+ * TODO: Figure out which of these are actually needed outside of the Shell
+ */
+@ExternalThread
+public interface SplitScreen {
+ /**
+ * Stage type isn't specified normally meaning to use what ever the default is.
+ * E.g. exit split-screen and launch the app in fullscreen.
+ */
+ int STAGE_TYPE_UNDEFINED = -1;
+ /**
+ * The main stage type.
+ * @see MainStage
+ */
+ int STAGE_TYPE_MAIN = 0;
+
+ /**
+ * The side stage type.
+ * @see SideStage
+ */
+ int STAGE_TYPE_SIDE = 1;
+
+ @IntDef(prefix = { "STAGE_TYPE_" }, value = {
+ STAGE_TYPE_UNDEFINED,
+ STAGE_TYPE_MAIN,
+ STAGE_TYPE_SIDE
+ })
+ @interface StageType {}
+
+ /** Callback interface for listening to changes in a split-screen stage. */
+ interface SplitScreenListener {
+ default void onStagePositionChanged(@StageType int stage, @SplitPosition int position) {}
+ default void onTaskStageChanged(int taskId, @StageType int stage, boolean visible) {}
+ default void onSplitVisibilityChanged(boolean visible) {}
+ }
+
+ /** Registers listener that gets split screen callback. */
+ void registerSplitScreenListener(@NonNull SplitScreenListener listener,
+ @NonNull Executor executor);
+
+ /** Unregisters listener that gets split screen callback. */
+ void unregisterSplitScreenListener(@NonNull SplitScreenListener listener);
+
+ /**
+ * Returns a binder that can be passed to an external process to manipulate SplitScreen.
+ */
+ default ISplitScreen createExternalInterface() {
+ return null;
+ }
+
+ /**
+ * Called when the keyguard occluded state changes.
+ * @param occluded Indicates if the keyguard is now occluded.
+ */
+ void onKeyguardOccludedChanged(boolean occluded);
+
+ /**
+ * Called when the visibility of the keyguard changes.
+ * @param showing Indicates if the keyguard is now visible.
+ */
+ void onKeyguardVisibilityChanged(boolean showing);
+
+ /** Get a string representation of a stage type */
+ static String stageTypeToString(@StageType int stage) {
+ switch (stage) {
+ case STAGE_TYPE_UNDEFINED: return "UNDEFINED";
+ case STAGE_TYPE_MAIN: return "MAIN";
+ case STAGE_TYPE_SIDE: return "SIDE";
+ default: return "UNKNOWN(" + stage + ")";
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitScreenController.java
new file mode 100644
index 000000000000..94db9cd958a3
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitScreenController.java
@@ -0,0 +1,595 @@
+/*
+ * Copyright (C) 2020 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.stagesplit;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.RemoteAnimationTarget.MODE_OPENING;
+
+import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
+import static com.android.wm.shell.common.split.SplitLayout.SPLIT_POSITION_BOTTOM_OR_RIGHT;
+import static com.android.wm.shell.common.split.SplitLayout.SPLIT_POSITION_TOP_OR_LEFT;
+
+import android.app.ActivityManager;
+import android.app.ActivityTaskManager;
+import android.app.PendingIntent;
+import android.content.ActivityNotFoundException;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.LauncherApps;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.util.ArrayMap;
+import android.util.Slog;
+import android.view.IRemoteAnimationFinishedCallback;
+import android.view.RemoteAnimationAdapter;
+import android.view.RemoteAnimationTarget;
+import android.view.SurfaceControl;
+import android.view.SurfaceSession;
+import android.view.WindowManager;
+import android.window.RemoteTransition;
+import android.window.WindowContainerTransaction;
+
+import androidx.annotation.BinderThread;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.internal.logging.InstanceId;
+import com.android.internal.util.FrameworkStatsLog;
+import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.DisplayImeController;
+import com.android.wm.shell.common.DisplayInsetsController;
+import com.android.wm.shell.common.RemoteCallable;
+import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.common.TransactionPool;
+import com.android.wm.shell.common.annotations.ExternalThread;
+import com.android.wm.shell.common.split.SplitLayout.SplitPosition;
+import com.android.wm.shell.draganddrop.DragAndDropPolicy;
+import com.android.wm.shell.transition.LegacyTransitions;
+import com.android.wm.shell.transition.Transitions;
+
+import java.io.PrintWriter;
+import java.util.Arrays;
+import java.util.Optional;
+import java.util.concurrent.Executor;
+
+import javax.inject.Provider;
+
+/**
+ * Class manages split-screen multitasking mode and implements the main interface
+ * {@link SplitScreen}.
+ * @see StageCoordinator
+ */
+// TODO(b/198577848): Implement split screen flicker test to consolidate CUJ of split screen.
+public class SplitScreenController implements DragAndDropPolicy.Starter,
+ RemoteCallable<SplitScreenController> {
+ private static final String TAG = SplitScreenController.class.getSimpleName();
+
+ private final ShellTaskOrganizer mTaskOrganizer;
+ private final SyncTransactionQueue mSyncQueue;
+ private final Context mContext;
+ private final RootTaskDisplayAreaOrganizer mRootTDAOrganizer;
+ private final ShellExecutor mMainExecutor;
+ private final SplitScreenImpl mImpl = new SplitScreenImpl();
+ private final DisplayImeController mDisplayImeController;
+ private final DisplayInsetsController mDisplayInsetsController;
+ private final Transitions mTransitions;
+ private final TransactionPool mTransactionPool;
+ private final SplitscreenEventLogger mLogger;
+ private final Provider<Optional<StageTaskUnfoldController>> mUnfoldControllerProvider;
+
+ private StageCoordinator mStageCoordinator;
+
+ public SplitScreenController(ShellTaskOrganizer shellTaskOrganizer,
+ SyncTransactionQueue syncQueue, Context context,
+ RootTaskDisplayAreaOrganizer rootTDAOrganizer,
+ ShellExecutor mainExecutor, DisplayImeController displayImeController,
+ DisplayInsetsController displayInsetsController,
+ Transitions transitions, TransactionPool transactionPool,
+ Provider<Optional<StageTaskUnfoldController>> unfoldControllerProvider) {
+ mTaskOrganizer = shellTaskOrganizer;
+ mSyncQueue = syncQueue;
+ mContext = context;
+ mRootTDAOrganizer = rootTDAOrganizer;
+ mMainExecutor = mainExecutor;
+ mDisplayImeController = displayImeController;
+ mDisplayInsetsController = displayInsetsController;
+ mTransitions = transitions;
+ mTransactionPool = transactionPool;
+ mUnfoldControllerProvider = unfoldControllerProvider;
+ mLogger = new SplitscreenEventLogger();
+ }
+
+ public SplitScreen asSplitScreen() {
+ return mImpl;
+ }
+
+ @Override
+ public Context getContext() {
+ return mContext;
+ }
+
+ @Override
+ public ShellExecutor getRemoteCallExecutor() {
+ return mMainExecutor;
+ }
+
+ public void onOrganizerRegistered() {
+ if (mStageCoordinator == null) {
+ // TODO: Multi-display
+ mStageCoordinator = new StageCoordinator(mContext, DEFAULT_DISPLAY, mSyncQueue,
+ mRootTDAOrganizer, mTaskOrganizer, mDisplayImeController,
+ mDisplayInsetsController, mTransitions, mTransactionPool, mLogger,
+ mUnfoldControllerProvider);
+ }
+ }
+
+ public boolean isSplitScreenVisible() {
+ return mStageCoordinator.isSplitScreenVisible();
+ }
+
+ public boolean moveToSideStage(int taskId, @SplitPosition int sideStagePosition) {
+ final ActivityManager.RunningTaskInfo task = mTaskOrganizer.getRunningTaskInfo(taskId);
+ if (task == null) {
+ throw new IllegalArgumentException("Unknown taskId" + taskId);
+ }
+ return moveToSideStage(task, sideStagePosition);
+ }
+
+ public boolean moveToSideStage(ActivityManager.RunningTaskInfo task,
+ @SplitPosition int sideStagePosition) {
+ return mStageCoordinator.moveToSideStage(task, sideStagePosition);
+ }
+
+ public boolean removeFromSideStage(int taskId) {
+ return mStageCoordinator.removeFromSideStage(taskId);
+ }
+
+ public void setSideStageOutline(boolean enable) {
+ mStageCoordinator.setSideStageOutline(enable);
+ }
+
+ public void setSideStagePosition(@SplitPosition int sideStagePosition) {
+ mStageCoordinator.setSideStagePosition(sideStagePosition, null /* wct */);
+ }
+
+ public void setSideStageVisibility(boolean visible) {
+ mStageCoordinator.setSideStageVisibility(visible);
+ }
+
+ public void enterSplitScreen(int taskId, boolean leftOrTop) {
+ moveToSideStage(taskId,
+ leftOrTop ? SPLIT_POSITION_TOP_OR_LEFT : SPLIT_POSITION_BOTTOM_OR_RIGHT);
+ }
+
+ public void exitSplitScreen(int toTopTaskId, int exitReason) {
+ mStageCoordinator.exitSplitScreen(toTopTaskId, exitReason);
+ }
+
+ public void onKeyguardOccludedChanged(boolean occluded) {
+ mStageCoordinator.onKeyguardOccludedChanged(occluded);
+ }
+
+ public void onKeyguardVisibilityChanged(boolean showing) {
+ mStageCoordinator.onKeyguardVisibilityChanged(showing);
+ }
+
+ public void exitSplitScreenOnHide(boolean exitSplitScreenOnHide) {
+ mStageCoordinator.exitSplitScreenOnHide(exitSplitScreenOnHide);
+ }
+
+ public void getStageBounds(Rect outTopOrLeftBounds, Rect outBottomOrRightBounds) {
+ mStageCoordinator.getStageBounds(outTopOrLeftBounds, outBottomOrRightBounds);
+ }
+
+ public void registerSplitScreenListener(SplitScreen.SplitScreenListener listener) {
+ mStageCoordinator.registerSplitScreenListener(listener);
+ }
+
+ public void unregisterSplitScreenListener(SplitScreen.SplitScreenListener listener) {
+ mStageCoordinator.unregisterSplitScreenListener(listener);
+ }
+
+ public void startTask(int taskId, @SplitScreen.StageType int stage,
+ @SplitPosition int position, @Nullable Bundle options) {
+ options = mStageCoordinator.resolveStartStage(stage, position, options, null /* wct */);
+
+ try {
+ ActivityTaskManager.getService().startActivityFromRecents(taskId, options);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to launch task", e);
+ }
+ }
+
+ public void startShortcut(String packageName, String shortcutId,
+ @SplitScreen.StageType int stage, @SplitPosition int position,
+ @Nullable Bundle options, UserHandle user) {
+ options = mStageCoordinator.resolveStartStage(stage, position, options, null /* wct */);
+
+ try {
+ LauncherApps launcherApps =
+ mContext.getSystemService(LauncherApps.class);
+ launcherApps.startShortcut(packageName, shortcutId, null /* sourceBounds */,
+ options, user);
+ } catch (ActivityNotFoundException e) {
+ Slog.e(TAG, "Failed to launch shortcut", e);
+ }
+ }
+
+ public void startIntent(PendingIntent intent, Intent fillInIntent,
+ @SplitScreen.StageType int stage, @SplitPosition int position,
+ @Nullable Bundle options) {
+ if (!Transitions.ENABLE_SHELL_TRANSITIONS) {
+ startIntentLegacy(intent, fillInIntent, stage, position, options);
+ return;
+ }
+ mStageCoordinator.startIntent(intent, fillInIntent, stage, position, options,
+ null /* remote */);
+ }
+
+ private void startIntentLegacy(PendingIntent intent, Intent fillInIntent,
+ @SplitScreen.StageType int stage, @SplitPosition int position,
+ @Nullable Bundle options) {
+ LegacyTransitions.ILegacyTransition transition = new LegacyTransitions.ILegacyTransition() {
+ @Override
+ public void onAnimationStart(int transit, RemoteAnimationTarget[] apps,
+ RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps,
+ IRemoteAnimationFinishedCallback finishedCallback,
+ SurfaceControl.Transaction t) {
+ mStageCoordinator.updateSurfaceBounds(null /* layout */, t);
+
+ if (apps != null) {
+ for (int i = 0; i < apps.length; ++i) {
+ if (apps[i].mode == MODE_OPENING) {
+ t.show(apps[i].leash);
+ }
+ }
+ }
+
+ t.apply();
+ if (finishedCallback != null) {
+ try {
+ finishedCallback.onAnimationFinished();
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error finishing legacy transition: ", e);
+ }
+ }
+ }
+ };
+ WindowContainerTransaction wct = new WindowContainerTransaction();
+ options = mStageCoordinator.resolveStartStage(stage, position, options, wct);
+ wct.sendPendingIntent(intent, fillInIntent, options);
+ mSyncQueue.queue(transition, WindowManager.TRANSIT_OPEN, wct);
+ }
+
+ RemoteAnimationTarget[] onGoingToRecentsLegacy(boolean cancel, RemoteAnimationTarget[] apps) {
+ if (!isSplitScreenVisible()) return null;
+ final SurfaceControl.Builder builder = new SurfaceControl.Builder(new SurfaceSession())
+ .setContainerLayer()
+ .setName("RecentsAnimationSplitTasks")
+ .setHidden(false)
+ .setCallsite("SplitScreenController#onGoingtoRecentsLegacy");
+ mRootTDAOrganizer.attachToDisplayArea(DEFAULT_DISPLAY, builder);
+ SurfaceControl sc = builder.build();
+ SurfaceControl.Transaction transaction = new SurfaceControl.Transaction();
+
+ // Ensure that we order these in the parent in the right z-order as their previous order
+ Arrays.sort(apps, (a1, a2) -> a1.prefixOrderIndex - a2.prefixOrderIndex);
+ int layer = 1;
+ for (RemoteAnimationTarget appTarget : apps) {
+ transaction.reparent(appTarget.leash, sc);
+ transaction.setPosition(appTarget.leash, appTarget.screenSpaceBounds.left,
+ appTarget.screenSpaceBounds.top);
+ transaction.setLayer(appTarget.leash, layer++);
+ }
+ transaction.apply();
+ transaction.close();
+ return new RemoteAnimationTarget[]{
+ mStageCoordinator.getDividerBarLegacyTarget(),
+ mStageCoordinator.getOutlineLegacyTarget()};
+ }
+
+ /**
+ * Sets drag info to be logged when splitscreen is entered.
+ */
+ public void logOnDroppedToSplit(@SplitPosition int position, InstanceId dragSessionId) {
+ mStageCoordinator.logOnDroppedToSplit(position, dragSessionId);
+ }
+
+ public void dump(@NonNull PrintWriter pw, String prefix) {
+ pw.println(prefix + TAG);
+ if (mStageCoordinator != null) {
+ mStageCoordinator.dump(pw, prefix);
+ }
+ }
+
+ /**
+ * The interface for calls from outside the Shell, within the host process.
+ */
+ @ExternalThread
+ private class SplitScreenImpl implements SplitScreen {
+ private ISplitScreenImpl mISplitScreen;
+ private final ArrayMap<SplitScreenListener, Executor> mExecutors = new ArrayMap<>();
+ private final SplitScreenListener mListener = new SplitScreenListener() {
+ @Override
+ public void onStagePositionChanged(int stage, int position) {
+ for (int i = 0; i < mExecutors.size(); i++) {
+ final int index = i;
+ mExecutors.valueAt(index).execute(() -> {
+ mExecutors.keyAt(index).onStagePositionChanged(stage, position);
+ });
+ }
+ }
+
+ @Override
+ public void onTaskStageChanged(int taskId, int stage, boolean visible) {
+ for (int i = 0; i < mExecutors.size(); i++) {
+ final int index = i;
+ mExecutors.valueAt(index).execute(() -> {
+ mExecutors.keyAt(index).onTaskStageChanged(taskId, stage, visible);
+ });
+ }
+ }
+
+ @Override
+ public void onSplitVisibilityChanged(boolean visible) {
+ for (int i = 0; i < mExecutors.size(); i++) {
+ final int index = i;
+ mExecutors.valueAt(index).execute(() -> {
+ mExecutors.keyAt(index).onSplitVisibilityChanged(visible);
+ });
+ }
+ }
+ };
+
+ @Override
+ public ISplitScreen createExternalInterface() {
+ if (mISplitScreen != null) {
+ mISplitScreen.invalidate();
+ }
+ mISplitScreen = new ISplitScreenImpl(SplitScreenController.this);
+ return mISplitScreen;
+ }
+
+ @Override
+ public void onKeyguardOccludedChanged(boolean occluded) {
+ mMainExecutor.execute(() -> {
+ SplitScreenController.this.onKeyguardOccludedChanged(occluded);
+ });
+ }
+
+ @Override
+ public void registerSplitScreenListener(SplitScreenListener listener, Executor executor) {
+ if (mExecutors.containsKey(listener)) return;
+
+ mMainExecutor.execute(() -> {
+ if (mExecutors.size() == 0) {
+ SplitScreenController.this.registerSplitScreenListener(mListener);
+ }
+
+ mExecutors.put(listener, executor);
+ });
+
+ executor.execute(() -> {
+ mStageCoordinator.sendStatusToListener(listener);
+ });
+ }
+
+ @Override
+ public void unregisterSplitScreenListener(SplitScreenListener listener) {
+ mMainExecutor.execute(() -> {
+ mExecutors.remove(listener);
+
+ if (mExecutors.size() == 0) {
+ SplitScreenController.this.unregisterSplitScreenListener(mListener);
+ }
+ });
+ }
+
+ @Override
+ public void onKeyguardVisibilityChanged(boolean showing) {
+ mMainExecutor.execute(() -> {
+ SplitScreenController.this.onKeyguardVisibilityChanged(showing);
+ });
+ }
+ }
+
+ /**
+ * The interface for calls from outside the host process.
+ */
+ @BinderThread
+ private static class ISplitScreenImpl extends ISplitScreen.Stub {
+ private SplitScreenController mController;
+ private ISplitScreenListener mListener;
+ private final SplitScreen.SplitScreenListener mSplitScreenListener =
+ new SplitScreen.SplitScreenListener() {
+ @Override
+ public void onStagePositionChanged(int stage, int position) {
+ try {
+ if (mListener != null) {
+ mListener.onStagePositionChanged(stage, position);
+ }
+ } catch (RemoteException e) {
+ Slog.e(TAG, "onStagePositionChanged", e);
+ }
+ }
+
+ @Override
+ public void onTaskStageChanged(int taskId, int stage, boolean visible) {
+ try {
+ if (mListener != null) {
+ mListener.onTaskStageChanged(taskId, stage, visible);
+ }
+ } catch (RemoteException e) {
+ Slog.e(TAG, "onTaskStageChanged", e);
+ }
+ }
+ };
+ private final IBinder.DeathRecipient mListenerDeathRecipient =
+ new IBinder.DeathRecipient() {
+ @Override
+ @BinderThread
+ public void binderDied() {
+ final SplitScreenController controller = mController;
+ controller.getRemoteCallExecutor().execute(() -> {
+ mListener = null;
+ controller.unregisterSplitScreenListener(mSplitScreenListener);
+ });
+ }
+ };
+
+ public ISplitScreenImpl(SplitScreenController controller) {
+ mController = controller;
+ }
+
+ /**
+ * Invalidates this instance, preventing future calls from updating the controller.
+ */
+ void invalidate() {
+ mController = null;
+ }
+
+ @Override
+ public void registerSplitScreenListener(ISplitScreenListener listener) {
+ executeRemoteCallWithTaskPermission(mController, "registerSplitScreenListener",
+ (controller) -> {
+ if (mListener != null) {
+ mListener.asBinder().unlinkToDeath(mListenerDeathRecipient,
+ 0 /* flags */);
+ }
+ if (listener != null) {
+ try {
+ listener.asBinder().linkToDeath(mListenerDeathRecipient,
+ 0 /* flags */);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to link to death");
+ return;
+ }
+ }
+ mListener = listener;
+ controller.registerSplitScreenListener(mSplitScreenListener);
+ });
+ }
+
+ @Override
+ public void unregisterSplitScreenListener(ISplitScreenListener listener) {
+ executeRemoteCallWithTaskPermission(mController, "unregisterSplitScreenListener",
+ (controller) -> {
+ if (mListener != null) {
+ mListener.asBinder().unlinkToDeath(mListenerDeathRecipient,
+ 0 /* flags */);
+ }
+ mListener = null;
+ controller.unregisterSplitScreenListener(mSplitScreenListener);
+ });
+ }
+
+ @Override
+ public void exitSplitScreen(int toTopTaskId) {
+ executeRemoteCallWithTaskPermission(mController, "exitSplitScreen",
+ (controller) -> {
+ controller.exitSplitScreen(toTopTaskId,
+ FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__UNKNOWN_EXIT);
+ });
+ }
+
+ @Override
+ public void exitSplitScreenOnHide(boolean exitSplitScreenOnHide) {
+ executeRemoteCallWithTaskPermission(mController, "exitSplitScreenOnHide",
+ (controller) -> {
+ controller.exitSplitScreenOnHide(exitSplitScreenOnHide);
+ });
+ }
+
+ @Override
+ public void setSideStageVisibility(boolean visible) {
+ executeRemoteCallWithTaskPermission(mController, "setSideStageVisibility",
+ (controller) -> {
+ controller.setSideStageVisibility(visible);
+ });
+ }
+
+ @Override
+ public void removeFromSideStage(int taskId) {
+ executeRemoteCallWithTaskPermission(mController, "removeFromSideStage",
+ (controller) -> {
+ controller.removeFromSideStage(taskId);
+ });
+ }
+
+ @Override
+ public void startTask(int taskId, int stage, int position, @Nullable Bundle options) {
+ executeRemoteCallWithTaskPermission(mController, "startTask",
+ (controller) -> {
+ controller.startTask(taskId, stage, position, options);
+ });
+ }
+
+ @Override
+ public void startTasksWithLegacyTransition(int mainTaskId, @Nullable Bundle mainOptions,
+ int sideTaskId, @Nullable Bundle sideOptions, @SplitPosition int sidePosition,
+ RemoteAnimationAdapter adapter) {
+ executeRemoteCallWithTaskPermission(mController, "startTasks",
+ (controller) -> controller.mStageCoordinator.startTasksWithLegacyTransition(
+ mainTaskId, mainOptions, sideTaskId, sideOptions, sidePosition,
+ adapter));
+ }
+
+ @Override
+ public void startTasks(int mainTaskId, @Nullable Bundle mainOptions,
+ int sideTaskId, @Nullable Bundle sideOptions,
+ @SplitPosition int sidePosition,
+ @Nullable RemoteTransition remoteTransition) {
+ executeRemoteCallWithTaskPermission(mController, "startTasks",
+ (controller) -> controller.mStageCoordinator.startTasks(mainTaskId, mainOptions,
+ sideTaskId, sideOptions, sidePosition, remoteTransition));
+ }
+
+ @Override
+ public void startShortcut(String packageName, String shortcutId, int stage, int position,
+ @Nullable Bundle options, UserHandle user) {
+ executeRemoteCallWithTaskPermission(mController, "startShortcut",
+ (controller) -> {
+ controller.startShortcut(packageName, shortcutId, stage, position,
+ options, user);
+ });
+ }
+
+ @Override
+ public void startIntent(PendingIntent intent, Intent fillInIntent, int stage, int position,
+ @Nullable Bundle options) {
+ executeRemoteCallWithTaskPermission(mController, "startIntent",
+ (controller) -> {
+ controller.startIntent(intent, fillInIntent, stage, position, options);
+ });
+ }
+
+ @Override
+ public RemoteAnimationTarget[] onGoingToRecentsLegacy(boolean cancel,
+ RemoteAnimationTarget[] apps) {
+ final RemoteAnimationTarget[][] out = new RemoteAnimationTarget[][]{null};
+ executeRemoteCallWithTaskPermission(mController, "onGoingToRecentsLegacy",
+ (controller) -> out[0] = controller.onGoingToRecentsLegacy(cancel, apps),
+ true /* blocking */);
+ return out[0];
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitScreenTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitScreenTransitions.java
new file mode 100644
index 000000000000..af9a5aa501e8
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitScreenTransitions.java
@@ -0,0 +1,298 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.stagesplit;
+
+import static android.view.WindowManager.TRANSIT_CHANGE;
+import static android.view.WindowManager.TRANSIT_CLOSE;
+import static android.view.WindowManager.TRANSIT_OPEN;
+import static android.view.WindowManager.TRANSIT_TO_BACK;
+import static android.view.WindowManager.TRANSIT_TO_FRONT;
+import static android.window.TransitionInfo.FLAG_FIRST_CUSTOM;
+
+import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_DISMISS_SNAP;
+import static com.android.wm.shell.transition.Transitions.isOpeningType;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.graphics.Rect;
+import android.os.IBinder;
+import android.view.SurfaceControl;
+import android.view.WindowManager;
+import android.window.RemoteTransition;
+import android.window.TransitionInfo;
+import android.window.WindowContainerToken;
+import android.window.WindowContainerTransaction;
+
+import com.android.wm.shell.common.TransactionPool;
+import com.android.wm.shell.transition.OneShotRemoteHandler;
+import com.android.wm.shell.transition.Transitions;
+
+import java.util.ArrayList;
+
+/** Manages transition animations for split-screen. */
+class SplitScreenTransitions {
+ private static final String TAG = "SplitScreenTransitions";
+
+ /** Flag applied to a transition change to identify it as a divider bar for animation. */
+ public static final int FLAG_IS_DIVIDER_BAR = FLAG_FIRST_CUSTOM;
+
+ private final TransactionPool mTransactionPool;
+ private final Transitions mTransitions;
+ private final Runnable mOnFinish;
+
+ IBinder mPendingDismiss = null;
+ IBinder mPendingEnter = null;
+
+ private IBinder mAnimatingTransition = null;
+ private OneShotRemoteHandler mRemoteHandler = null;
+
+ private Transitions.TransitionFinishCallback mRemoteFinishCB = (wct, wctCB) -> {
+ if (wct != null || wctCB != null) {
+ throw new UnsupportedOperationException("finish transactions not supported yet.");
+ }
+ onFinish();
+ };
+
+ /** Keeps track of currently running animations */
+ private final ArrayList<Animator> mAnimations = new ArrayList<>();
+
+ private Transitions.TransitionFinishCallback mFinishCallback = null;
+ private SurfaceControl.Transaction mFinishTransaction;
+
+ SplitScreenTransitions(@NonNull TransactionPool pool, @NonNull Transitions transitions,
+ @NonNull Runnable onFinishCallback) {
+ mTransactionPool = pool;
+ mTransitions = transitions;
+ mOnFinish = onFinishCallback;
+ }
+
+ void playAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
+ @NonNull Transitions.TransitionFinishCallback finishCallback,
+ @NonNull WindowContainerToken mainRoot, @NonNull WindowContainerToken sideRoot) {
+ mFinishCallback = finishCallback;
+ mAnimatingTransition = transition;
+ if (mRemoteHandler != null) {
+ mRemoteHandler.startAnimation(transition, info, startTransaction, finishTransaction,
+ mRemoteFinishCB);
+ mRemoteHandler = null;
+ return;
+ }
+ playInternalAnimation(transition, info, startTransaction, mainRoot, sideRoot);
+ }
+
+ private void playInternalAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction t, @NonNull WindowContainerToken mainRoot,
+ @NonNull WindowContainerToken sideRoot) {
+ mFinishTransaction = mTransactionPool.acquire();
+
+ // Play some place-holder fade animations
+ for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+ final TransitionInfo.Change change = info.getChanges().get(i);
+ final SurfaceControl leash = change.getLeash();
+ final int mode = info.getChanges().get(i).getMode();
+
+ if (mode == TRANSIT_CHANGE) {
+ if (change.getParent() != null) {
+ // This is probably reparented, so we want the parent to be immediately visible
+ final TransitionInfo.Change parentChange = info.getChange(change.getParent());
+ t.show(parentChange.getLeash());
+ t.setAlpha(parentChange.getLeash(), 1.f);
+ // and then animate this layer outside the parent (since, for example, this is
+ // the home task animating from fullscreen to part-screen).
+ t.reparent(leash, info.getRootLeash());
+ t.setLayer(leash, info.getChanges().size() - i);
+ // build the finish reparent/reposition
+ mFinishTransaction.reparent(leash, parentChange.getLeash());
+ mFinishTransaction.setPosition(leash,
+ change.getEndRelOffset().x, change.getEndRelOffset().y);
+ }
+ // TODO(shell-transitions): screenshot here
+ final Rect startBounds = new Rect(change.getStartAbsBounds());
+ if (info.getType() == TRANSIT_SPLIT_DISMISS_SNAP) {
+ // Dismissing split via snap which means the still-visible task has been
+ // dragged to its end position at animation start so reflect that here.
+ startBounds.offsetTo(change.getEndAbsBounds().left,
+ change.getEndAbsBounds().top);
+ }
+ final Rect endBounds = new Rect(change.getEndAbsBounds());
+ startBounds.offset(-info.getRootOffset().x, -info.getRootOffset().y);
+ endBounds.offset(-info.getRootOffset().x, -info.getRootOffset().y);
+ startExampleResizeAnimation(leash, startBounds, endBounds);
+ }
+ if (change.getParent() != null) {
+ continue;
+ }
+
+ if (transition == mPendingEnter && (mainRoot.equals(change.getContainer())
+ || sideRoot.equals(change.getContainer()))) {
+ t.setWindowCrop(leash, change.getStartAbsBounds().width(),
+ change.getStartAbsBounds().height());
+ }
+ boolean isOpening = isOpeningType(info.getType());
+ if (isOpening && (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT)) {
+ // fade in
+ startExampleAnimation(leash, true /* show */);
+ } else if (!isOpening && (mode == TRANSIT_CLOSE || mode == TRANSIT_TO_BACK)) {
+ // fade out
+ if (info.getType() == TRANSIT_SPLIT_DISMISS_SNAP) {
+ // Dismissing via snap-to-top/bottom means that the dismissed task is already
+ // not-visible (usually cropped to oblivion) so immediately set its alpha to 0
+ // and don't animate it so it doesn't pop-in when reparented.
+ t.setAlpha(leash, 0.f);
+ } else {
+ startExampleAnimation(leash, false /* show */);
+ }
+ }
+ }
+ t.apply();
+ onFinish();
+ }
+
+ /** Starts a transition to enter split with a remote transition animator. */
+ IBinder startEnterTransition(@WindowManager.TransitionType int transitType,
+ @NonNull WindowContainerTransaction wct, @Nullable RemoteTransition remoteTransition,
+ @NonNull Transitions.TransitionHandler handler) {
+ if (remoteTransition != null) {
+ // Wrapping it for ease-of-use (OneShot handles all the binder linking/death stuff)
+ mRemoteHandler = new OneShotRemoteHandler(
+ mTransitions.getMainExecutor(), remoteTransition);
+ }
+ final IBinder transition = mTransitions.startTransition(transitType, wct, handler);
+ mPendingEnter = transition;
+ if (mRemoteHandler != null) {
+ mRemoteHandler.setTransition(transition);
+ }
+ return transition;
+ }
+
+ /** Starts a transition for dismissing split after dragging the divider to a screen edge */
+ IBinder startSnapToDismiss(@NonNull WindowContainerTransaction wct,
+ @NonNull Transitions.TransitionHandler handler) {
+ final IBinder transition = mTransitions.startTransition(
+ TRANSIT_SPLIT_DISMISS_SNAP, wct, handler);
+ mPendingDismiss = transition;
+ return transition;
+ }
+
+ void onFinish() {
+ if (!mAnimations.isEmpty()) return;
+ mOnFinish.run();
+ if (mFinishTransaction != null) {
+ mFinishTransaction.apply();
+ mTransactionPool.release(mFinishTransaction);
+ mFinishTransaction = null;
+ }
+ mFinishCallback.onTransitionFinished(null /* wct */, null /* wctCB */);
+ mFinishCallback = null;
+ if (mAnimatingTransition == mPendingEnter) {
+ mPendingEnter = null;
+ }
+ if (mAnimatingTransition == mPendingDismiss) {
+ mPendingDismiss = null;
+ }
+ mAnimatingTransition = null;
+ }
+
+ // TODO(shell-transitions): real animations
+ private void startExampleAnimation(@NonNull SurfaceControl leash, boolean show) {
+ final float end = show ? 1.f : 0.f;
+ final float start = 1.f - end;
+ final SurfaceControl.Transaction transaction = mTransactionPool.acquire();
+ final ValueAnimator va = ValueAnimator.ofFloat(start, end);
+ va.setDuration(500);
+ va.addUpdateListener(animation -> {
+ float fraction = animation.getAnimatedFraction();
+ transaction.setAlpha(leash, start * (1.f - fraction) + end * fraction);
+ transaction.apply();
+ });
+ final Runnable finisher = () -> {
+ transaction.setAlpha(leash, end);
+ transaction.apply();
+ mTransactionPool.release(transaction);
+ mTransitions.getMainExecutor().execute(() -> {
+ mAnimations.remove(va);
+ onFinish();
+ });
+ };
+ va.addListener(new Animator.AnimatorListener() {
+ @Override
+ public void onAnimationStart(Animator animation) { }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ finisher.run();
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ finisher.run();
+ }
+
+ @Override
+ public void onAnimationRepeat(Animator animation) { }
+ });
+ mAnimations.add(va);
+ mTransitions.getAnimExecutor().execute(va::start);
+ }
+
+ // TODO(shell-transitions): real animations
+ private void startExampleResizeAnimation(@NonNull SurfaceControl leash,
+ @NonNull Rect startBounds, @NonNull Rect endBounds) {
+ final SurfaceControl.Transaction transaction = mTransactionPool.acquire();
+ final ValueAnimator va = ValueAnimator.ofFloat(0.f, 1.f);
+ va.setDuration(500);
+ va.addUpdateListener(animation -> {
+ float fraction = animation.getAnimatedFraction();
+ transaction.setWindowCrop(leash,
+ (int) (startBounds.width() * (1.f - fraction) + endBounds.width() * fraction),
+ (int) (startBounds.height() * (1.f - fraction)
+ + endBounds.height() * fraction));
+ transaction.setPosition(leash,
+ startBounds.left * (1.f - fraction) + endBounds.left * fraction,
+ startBounds.top * (1.f - fraction) + endBounds.top * fraction);
+ transaction.apply();
+ });
+ final Runnable finisher = () -> {
+ transaction.setWindowCrop(leash, 0, 0);
+ transaction.setPosition(leash, endBounds.left, endBounds.top);
+ transaction.apply();
+ mTransactionPool.release(transaction);
+ mTransitions.getMainExecutor().execute(() -> {
+ mAnimations.remove(va);
+ onFinish();
+ });
+ };
+ va.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ finisher.run();
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ finisher.run();
+ }
+ });
+ mAnimations.add(va);
+ mTransitions.getAnimExecutor().execute(va::start);
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitscreenEventLogger.java b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitscreenEventLogger.java
new file mode 100644
index 000000000000..aab7902232bf
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitscreenEventLogger.java
@@ -0,0 +1,324 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.stagesplit;
+
+import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__ENTER_REASON__OVERVIEW;
+import static com.android.wm.shell.common.split.SplitLayout.SPLIT_POSITION_TOP_OR_LEFT;
+import static com.android.wm.shell.common.split.SplitLayout.SPLIT_POSITION_UNDEFINED;
+
+import com.android.internal.logging.InstanceId;
+import com.android.internal.logging.InstanceIdSequence;
+import com.android.internal.util.FrameworkStatsLog;
+import com.android.wm.shell.common.split.SplitLayout.SplitPosition;
+
+/**
+ * Helper class that to log Drag & Drop UIEvents for a single session, see also go/uievent
+ */
+public class SplitscreenEventLogger {
+
+ // Used to generate instance ids for this drag if one is not provided
+ private final InstanceIdSequence mIdSequence;
+
+ // The instance id for the current splitscreen session (from start to end)
+ private InstanceId mLoggerSessionId;
+
+ // Drag info
+ private @SplitPosition int mDragEnterPosition;
+ private InstanceId mDragEnterSessionId;
+
+ // For deduping async events
+ private int mLastMainStagePosition = -1;
+ private int mLastMainStageUid = -1;
+ private int mLastSideStagePosition = -1;
+ private int mLastSideStageUid = -1;
+ private float mLastSplitRatio = -1f;
+
+ public SplitscreenEventLogger() {
+ mIdSequence = new InstanceIdSequence(Integer.MAX_VALUE);
+ }
+
+ /**
+ * Return whether a splitscreen session has started.
+ */
+ public boolean hasStartedSession() {
+ return mLoggerSessionId != null;
+ }
+
+ /**
+ * May be called before logEnter() to indicate that the session was started from a drag.
+ */
+ public void enterRequestedByDrag(@SplitPosition int position, InstanceId dragSessionId) {
+ mDragEnterPosition = position;
+ mDragEnterSessionId = dragSessionId;
+ }
+
+ /**
+ * Logs when the user enters splitscreen.
+ */
+ public void logEnter(float splitRatio,
+ @SplitPosition int mainStagePosition, int mainStageUid,
+ @SplitPosition int sideStagePosition, int sideStageUid,
+ boolean isLandscape) {
+ mLoggerSessionId = mIdSequence.newInstanceId();
+ int enterReason = mDragEnterPosition != SPLIT_POSITION_UNDEFINED
+ ? getDragEnterReasonFromSplitPosition(mDragEnterPosition, isLandscape)
+ : SPLITSCREEN_UICHANGED__ENTER_REASON__OVERVIEW;
+ updateMainStageState(getMainStagePositionFromSplitPosition(mainStagePosition, isLandscape),
+ mainStageUid);
+ updateSideStageState(getSideStagePositionFromSplitPosition(sideStagePosition, isLandscape),
+ sideStageUid);
+ updateSplitRatioState(splitRatio);
+ FrameworkStatsLog.write(FrameworkStatsLog.SPLITSCREEN_UI_CHANGED,
+ FrameworkStatsLog.SPLITSCREEN_UICHANGED__ACTION__ENTER,
+ enterReason,
+ 0 /* exitReason */,
+ splitRatio,
+ mLastMainStagePosition,
+ mLastMainStageUid,
+ mLastSideStagePosition,
+ mLastSideStageUid,
+ mDragEnterSessionId != null ? mDragEnterSessionId.getId() : 0,
+ mLoggerSessionId.getId());
+ }
+
+ /**
+ * Logs when the user exits splitscreen. Only one of the main or side stages should be
+ * specified to indicate which position was focused as a part of exiting (both can be unset).
+ */
+ public void logExit(int exitReason, @SplitPosition int mainStagePosition, int mainStageUid,
+ @SplitPosition int sideStagePosition, int sideStageUid, boolean isLandscape) {
+ if (mLoggerSessionId == null) {
+ // Ignore changes until we've started logging the session
+ return;
+ }
+ if ((mainStagePosition != SPLIT_POSITION_UNDEFINED
+ && sideStagePosition != SPLIT_POSITION_UNDEFINED)
+ || (mainStageUid != 0 && sideStageUid != 0)) {
+ throw new IllegalArgumentException("Only main or side stage should be set");
+ }
+ FrameworkStatsLog.write(FrameworkStatsLog.SPLITSCREEN_UI_CHANGED,
+ FrameworkStatsLog.SPLITSCREEN_UICHANGED__ACTION__EXIT,
+ 0 /* enterReason */,
+ exitReason,
+ 0f /* splitRatio */,
+ getMainStagePositionFromSplitPosition(mainStagePosition, isLandscape),
+ mainStageUid,
+ getSideStagePositionFromSplitPosition(sideStagePosition, isLandscape),
+ sideStageUid,
+ 0 /* dragInstanceId */,
+ mLoggerSessionId.getId());
+
+ // Reset states
+ mLoggerSessionId = null;
+ mDragEnterPosition = SPLIT_POSITION_UNDEFINED;
+ mDragEnterSessionId = null;
+ mLastMainStagePosition = -1;
+ mLastMainStageUid = -1;
+ mLastSideStagePosition = -1;
+ mLastSideStageUid = -1;
+ }
+
+ /**
+ * Logs when an app in the main stage changes.
+ */
+ public void logMainStageAppChange(@SplitPosition int mainStagePosition, int mainStageUid,
+ boolean isLandscape) {
+ if (mLoggerSessionId == null) {
+ // Ignore changes until we've started logging the session
+ return;
+ }
+ if (!updateMainStageState(getMainStagePositionFromSplitPosition(mainStagePosition,
+ isLandscape), mainStageUid)) {
+ // Ignore if there are no user perceived changes
+ return;
+ }
+
+ FrameworkStatsLog.write(FrameworkStatsLog.SPLITSCREEN_UI_CHANGED,
+ FrameworkStatsLog.SPLITSCREEN_UICHANGED__ACTION__APP_CHANGE,
+ 0 /* enterReason */,
+ 0 /* exitReason */,
+ 0f /* splitRatio */,
+ mLastMainStagePosition,
+ mLastMainStageUid,
+ 0 /* sideStagePosition */,
+ 0 /* sideStageUid */,
+ 0 /* dragInstanceId */,
+ mLoggerSessionId.getId());
+ }
+
+ /**
+ * Logs when an app in the side stage changes.
+ */
+ public void logSideStageAppChange(@SplitPosition int sideStagePosition, int sideStageUid,
+ boolean isLandscape) {
+ if (mLoggerSessionId == null) {
+ // Ignore changes until we've started logging the session
+ return;
+ }
+ if (!updateSideStageState(getSideStagePositionFromSplitPosition(sideStagePosition,
+ isLandscape), sideStageUid)) {
+ // Ignore if there are no user perceived changes
+ return;
+ }
+
+ FrameworkStatsLog.write(FrameworkStatsLog.SPLITSCREEN_UI_CHANGED,
+ FrameworkStatsLog.SPLITSCREEN_UICHANGED__ACTION__APP_CHANGE,
+ 0 /* enterReason */,
+ 0 /* exitReason */,
+ 0f /* splitRatio */,
+ 0 /* mainStagePosition */,
+ 0 /* mainStageUid */,
+ mLastSideStagePosition,
+ mLastSideStageUid,
+ 0 /* dragInstanceId */,
+ mLoggerSessionId.getId());
+ }
+
+ /**
+ * Logs when the splitscreen ratio changes.
+ */
+ public void logResize(float splitRatio) {
+ if (mLoggerSessionId == null) {
+ // Ignore changes until we've started logging the session
+ return;
+ }
+ if (splitRatio <= 0f || splitRatio >= 1f) {
+ // Don't bother reporting resizes that end up dismissing the split, that will be logged
+ // via the exit event
+ return;
+ }
+ if (!updateSplitRatioState(splitRatio)) {
+ // Ignore if there are no user perceived changes
+ return;
+ }
+ FrameworkStatsLog.write(FrameworkStatsLog.SPLITSCREEN_UI_CHANGED,
+ FrameworkStatsLog.SPLITSCREEN_UICHANGED__ACTION__RESIZE,
+ 0 /* enterReason */,
+ 0 /* exitReason */,
+ mLastSplitRatio,
+ 0 /* mainStagePosition */, 0 /* mainStageUid */,
+ 0 /* sideStagePosition */, 0 /* sideStageUid */,
+ 0 /* dragInstanceId */,
+ mLoggerSessionId.getId());
+ }
+
+ /**
+ * Logs when the apps in splitscreen are swapped.
+ */
+ public void logSwap(@SplitPosition int mainStagePosition, int mainStageUid,
+ @SplitPosition int sideStagePosition, int sideStageUid, boolean isLandscape) {
+ if (mLoggerSessionId == null) {
+ // Ignore changes until we've started logging the session
+ return;
+ }
+
+ updateMainStageState(getMainStagePositionFromSplitPosition(mainStagePosition, isLandscape),
+ mainStageUid);
+ updateSideStageState(getSideStagePositionFromSplitPosition(sideStagePosition, isLandscape),
+ sideStageUid);
+ FrameworkStatsLog.write(FrameworkStatsLog.SPLITSCREEN_UI_CHANGED,
+ FrameworkStatsLog.SPLITSCREEN_UICHANGED__ACTION__SWAP,
+ 0 /* enterReason */,
+ 0 /* exitReason */,
+ 0f /* splitRatio */,
+ mLastMainStagePosition,
+ mLastMainStageUid,
+ mLastSideStagePosition,
+ mLastSideStageUid,
+ 0 /* dragInstanceId */,
+ mLoggerSessionId.getId());
+ }
+
+ private boolean updateMainStageState(int mainStagePosition, int mainStageUid) {
+ boolean changed = (mLastMainStagePosition != mainStagePosition)
+ || (mLastMainStageUid != mainStageUid);
+ if (!changed) {
+ return false;
+ }
+
+ mLastMainStagePosition = mainStagePosition;
+ mLastMainStageUid = mainStageUid;
+ return true;
+ }
+
+ private boolean updateSideStageState(int sideStagePosition, int sideStageUid) {
+ boolean changed = (mLastSideStagePosition != sideStagePosition)
+ || (mLastSideStageUid != sideStageUid);
+ if (!changed) {
+ return false;
+ }
+
+ mLastSideStagePosition = sideStagePosition;
+ mLastSideStageUid = sideStageUid;
+ return true;
+ }
+
+ private boolean updateSplitRatioState(float splitRatio) {
+ boolean changed = Float.compare(mLastSplitRatio, splitRatio) != 0;
+ if (!changed) {
+ return false;
+ }
+
+ mLastSplitRatio = splitRatio;
+ return true;
+ }
+
+ public int getDragEnterReasonFromSplitPosition(@SplitPosition int position,
+ boolean isLandscape) {
+ if (isLandscape) {
+ return position == SPLIT_POSITION_TOP_OR_LEFT
+ ? FrameworkStatsLog.SPLITSCREEN_UICHANGED__ENTER_REASON__DRAG_LEFT
+ : FrameworkStatsLog.SPLITSCREEN_UICHANGED__ENTER_REASON__DRAG_RIGHT;
+ } else {
+ return position == SPLIT_POSITION_TOP_OR_LEFT
+ ? FrameworkStatsLog.SPLITSCREEN_UICHANGED__ENTER_REASON__DRAG_TOP
+ : FrameworkStatsLog.SPLITSCREEN_UICHANGED__ENTER_REASON__DRAG_BOTTOM;
+ }
+ }
+
+ private int getMainStagePositionFromSplitPosition(@SplitPosition int position,
+ boolean isLandscape) {
+ if (position == SPLIT_POSITION_UNDEFINED) {
+ return 0;
+ }
+ if (isLandscape) {
+ return position == SPLIT_POSITION_TOP_OR_LEFT
+ ? FrameworkStatsLog.SPLITSCREEN_UICHANGED__MAIN_STAGE_POSITION__LEFT
+ : FrameworkStatsLog.SPLITSCREEN_UICHANGED__MAIN_STAGE_POSITION__RIGHT;
+ } else {
+ return position == SPLIT_POSITION_TOP_OR_LEFT
+ ? FrameworkStatsLog.SPLITSCREEN_UICHANGED__MAIN_STAGE_POSITION__TOP
+ : FrameworkStatsLog.SPLITSCREEN_UICHANGED__MAIN_STAGE_POSITION__BOTTOM;
+ }
+ }
+
+ private int getSideStagePositionFromSplitPosition(@SplitPosition int position,
+ boolean isLandscape) {
+ if (position == SPLIT_POSITION_UNDEFINED) {
+ return 0;
+ }
+ if (isLandscape) {
+ return position == SPLIT_POSITION_TOP_OR_LEFT
+ ? FrameworkStatsLog.SPLITSCREEN_UICHANGED__SIDE_STAGE_POSITION__LEFT
+ : FrameworkStatsLog.SPLITSCREEN_UICHANGED__SIDE_STAGE_POSITION__RIGHT;
+ } else {
+ return position == SPLIT_POSITION_TOP_OR_LEFT
+ ? FrameworkStatsLog.SPLITSCREEN_UICHANGED__SIDE_STAGE_POSITION__TOP
+ : FrameworkStatsLog.SPLITSCREEN_UICHANGED__SIDE_STAGE_POSITION__BOTTOM;
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/StageCoordinator.java
new file mode 100644
index 000000000000..2f75f8bdc64c
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/StageCoordinator.java
@@ -0,0 +1,1325 @@
+/*
+ * Copyright (C) 2020 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.stagesplit;
+
+import static android.app.ActivityOptions.KEY_LAUNCH_ROOT_TASK_TOKEN;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
+import static android.view.WindowManager.TRANSIT_OPEN;
+import static android.view.WindowManager.TRANSIT_TO_BACK;
+import static android.view.WindowManager.TRANSIT_TO_FRONT;
+import static android.view.WindowManager.transitTypeToString;
+import static android.view.WindowManagerPolicyConstants.SPLIT_DIVIDER_LAYER;
+
+import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__APP_DOES_NOT_SUPPORT_MULTIWINDOW;
+import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__APP_FINISHED;
+import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__DEVICE_FOLDED;
+import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__DRAG_DIVIDER;
+import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__RETURN_HOME;
+import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__SCREEN_LOCKED_SHOW_ON_TOP;
+import static com.android.wm.shell.common.split.SplitLayout.SPLIT_POSITION_BOTTOM_OR_RIGHT;
+import static com.android.wm.shell.common.split.SplitLayout.SPLIT_POSITION_TOP_OR_LEFT;
+import static com.android.wm.shell.common.split.SplitLayout.SPLIT_POSITION_UNDEFINED;
+import static com.android.wm.shell.stagesplit.SplitScreen.STAGE_TYPE_MAIN;
+import static com.android.wm.shell.stagesplit.SplitScreen.STAGE_TYPE_SIDE;
+import static com.android.wm.shell.stagesplit.SplitScreen.STAGE_TYPE_UNDEFINED;
+import static com.android.wm.shell.stagesplit.SplitScreen.stageTypeToString;
+import static com.android.wm.shell.stagesplit.SplitScreenTransitions.FLAG_IS_DIVIDER_BAR;
+import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS;
+import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_DISMISS_SNAP;
+import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE;
+import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_SCREEN_PAIR_OPEN;
+import static com.android.wm.shell.transition.Transitions.isClosingType;
+import static com.android.wm.shell.transition.Transitions.isOpeningType;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.ActivityManager;
+import android.app.ActivityOptions;
+import android.app.ActivityTaskManager;
+import android.app.PendingIntent;
+import android.app.WindowConfiguration;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Rect;
+import android.hardware.devicestate.DeviceStateManager;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+import android.util.Slog;
+import android.view.IRemoteAnimationFinishedCallback;
+import android.view.IRemoteAnimationRunner;
+import android.view.RemoteAnimationAdapter;
+import android.view.RemoteAnimationTarget;
+import android.view.SurfaceControl;
+import android.view.SurfaceSession;
+import android.view.WindowManager;
+import android.window.DisplayAreaInfo;
+import android.window.RemoteTransition;
+import android.window.TransitionInfo;
+import android.window.TransitionRequestInfo;
+import android.window.WindowContainerToken;
+import android.window.WindowContainerTransaction;
+
+import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.logging.InstanceId;
+import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.DisplayImeController;
+import com.android.wm.shell.common.DisplayInsetsController;
+import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.common.TransactionPool;
+import com.android.wm.shell.common.split.SplitLayout;
+import com.android.wm.shell.common.split.SplitLayout.SplitPosition;
+import com.android.wm.shell.common.split.SplitWindowManager;
+import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.transition.Transitions;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+
+import javax.inject.Provider;
+
+/**
+ * Coordinates the staging (visibility, sizing, ...) of the split-screen {@link MainStage} and
+ * {@link SideStage} stages.
+ * Some high-level rules:
+ * - The {@link StageCoordinator} is only considered active if the {@link SideStage} contains at
+ * least one child task.
+ * - The {@link MainStage} should only have children if the coordinator is active.
+ * - The {@link SplitLayout} divider is only visible if both the {@link MainStage}
+ * and {@link SideStage} are visible.
+ * - The {@link MainStage} configuration is fullscreen when the {@link SideStage} isn't visible.
+ * This rules are mostly implemented in {@link #onStageVisibilityChanged(StageListenerImpl)} and
+ * {@link #onStageHasChildrenChanged(StageListenerImpl).}
+ */
+class StageCoordinator implements SplitLayout.SplitLayoutHandler,
+ RootTaskDisplayAreaOrganizer.RootTaskDisplayAreaListener, Transitions.TransitionHandler {
+
+ private static final String TAG = StageCoordinator.class.getSimpleName();
+
+ /** internal value for mDismissTop that represents no dismiss */
+ private static final int NO_DISMISS = -2;
+
+ private final SurfaceSession mSurfaceSession = new SurfaceSession();
+
+ private final MainStage mMainStage;
+ private final StageListenerImpl mMainStageListener = new StageListenerImpl();
+ private final StageTaskUnfoldController mMainUnfoldController;
+ private final SideStage mSideStage;
+ private final StageListenerImpl mSideStageListener = new StageListenerImpl();
+ private final StageTaskUnfoldController mSideUnfoldController;
+ @SplitPosition
+ private int mSideStagePosition = SPLIT_POSITION_BOTTOM_OR_RIGHT;
+
+ private final int mDisplayId;
+ private SplitLayout mSplitLayout;
+ private boolean mDividerVisible;
+ private final SyncTransactionQueue mSyncQueue;
+ private final RootTaskDisplayAreaOrganizer mRootTDAOrganizer;
+ private final ShellTaskOrganizer mTaskOrganizer;
+ private DisplayAreaInfo mDisplayAreaInfo;
+ private final Context mContext;
+ private final List<SplitScreen.SplitScreenListener> mListeners = new ArrayList<>();
+ private final DisplayImeController mDisplayImeController;
+ private final DisplayInsetsController mDisplayInsetsController;
+ private final SplitScreenTransitions mSplitTransitions;
+ private final SplitscreenEventLogger mLogger;
+ private boolean mExitSplitScreenOnHide;
+ private boolean mKeyguardOccluded;
+
+ // TODO(b/187041611): remove this flag after totally deprecated legacy split
+ /** Whether the device is supporting legacy split or not. */
+ private boolean mUseLegacySplit;
+
+ @SplitScreen.StageType private int mDismissTop = NO_DISMISS;
+
+ /** The target stage to dismiss to when unlock after folded. */
+ @SplitScreen.StageType private int mTopStageAfterFoldDismiss = STAGE_TYPE_UNDEFINED;
+
+ private final Runnable mOnTransitionAnimationComplete = () -> {
+ // If still playing, let it finish.
+ if (!isSplitScreenVisible()) {
+ // Update divider state after animation so that it is still around and positioned
+ // properly for the animation itself.
+ setDividerVisibility(false);
+ mSplitLayout.resetDividerPosition();
+ }
+ mDismissTop = NO_DISMISS;
+ };
+
+ private final SplitWindowManager.ParentContainerCallbacks mParentContainerCallbacks =
+ new SplitWindowManager.ParentContainerCallbacks() {
+ @Override
+ public void attachToParentSurface(SurfaceControl.Builder b) {
+ mRootTDAOrganizer.attachToDisplayArea(mDisplayId, b);
+ }
+
+ @Override
+ public void onLeashReady(SurfaceControl leash) {
+ mSyncQueue.runInSync(t -> applyDividerVisibility(t));
+ }
+ };
+
+ StageCoordinator(Context context, int displayId, SyncTransactionQueue syncQueue,
+ RootTaskDisplayAreaOrganizer rootTDAOrganizer, ShellTaskOrganizer taskOrganizer,
+ DisplayImeController displayImeController,
+ DisplayInsetsController displayInsetsController, Transitions transitions,
+ TransactionPool transactionPool, SplitscreenEventLogger logger,
+ Provider<Optional<StageTaskUnfoldController>> unfoldControllerProvider) {
+ mContext = context;
+ mDisplayId = displayId;
+ mSyncQueue = syncQueue;
+ mRootTDAOrganizer = rootTDAOrganizer;
+ mTaskOrganizer = taskOrganizer;
+ mLogger = logger;
+ mMainUnfoldController = unfoldControllerProvider.get().orElse(null);
+ mSideUnfoldController = unfoldControllerProvider.get().orElse(null);
+
+ mMainStage = new MainStage(
+ mTaskOrganizer,
+ mDisplayId,
+ mMainStageListener,
+ mSyncQueue,
+ mSurfaceSession,
+ mMainUnfoldController);
+ mSideStage = new SideStage(
+ mContext,
+ mTaskOrganizer,
+ mDisplayId,
+ mSideStageListener,
+ mSyncQueue,
+ mSurfaceSession,
+ mSideUnfoldController);
+ mDisplayImeController = displayImeController;
+ mDisplayInsetsController = displayInsetsController;
+ mDisplayInsetsController.addInsetsChangedListener(mDisplayId, mSideStage);
+ mRootTDAOrganizer.registerListener(displayId, this);
+ final DeviceStateManager deviceStateManager =
+ mContext.getSystemService(DeviceStateManager.class);
+ deviceStateManager.registerCallback(taskOrganizer.getExecutor(),
+ new DeviceStateManager.FoldStateListener(mContext, this::onFoldedStateChanged));
+ mSplitTransitions = new SplitScreenTransitions(transactionPool, transitions,
+ mOnTransitionAnimationComplete);
+ transitions.addHandler(this);
+ }
+
+ @VisibleForTesting
+ StageCoordinator(Context context, int displayId, SyncTransactionQueue syncQueue,
+ RootTaskDisplayAreaOrganizer rootTDAOrganizer, ShellTaskOrganizer taskOrganizer,
+ MainStage mainStage, SideStage sideStage, DisplayImeController displayImeController,
+ DisplayInsetsController displayInsetsController, SplitLayout splitLayout,
+ Transitions transitions, TransactionPool transactionPool,
+ SplitscreenEventLogger logger,
+ Provider<Optional<StageTaskUnfoldController>> unfoldControllerProvider) {
+ mContext = context;
+ mDisplayId = displayId;
+ mSyncQueue = syncQueue;
+ mRootTDAOrganizer = rootTDAOrganizer;
+ mTaskOrganizer = taskOrganizer;
+ mMainStage = mainStage;
+ mSideStage = sideStage;
+ mDisplayImeController = displayImeController;
+ mDisplayInsetsController = displayInsetsController;
+ mRootTDAOrganizer.registerListener(displayId, this);
+ mSplitLayout = splitLayout;
+ mSplitTransitions = new SplitScreenTransitions(transactionPool, transitions,
+ mOnTransitionAnimationComplete);
+ mMainUnfoldController = unfoldControllerProvider.get().orElse(null);
+ mSideUnfoldController = unfoldControllerProvider.get().orElse(null);
+ mLogger = logger;
+ transitions.addHandler(this);
+ }
+
+ @VisibleForTesting
+ SplitScreenTransitions getSplitTransitions() {
+ return mSplitTransitions;
+ }
+
+ boolean isSplitScreenVisible() {
+ return mSideStageListener.mVisible && mMainStageListener.mVisible;
+ }
+
+ boolean moveToSideStage(ActivityManager.RunningTaskInfo task,
+ @SplitPosition int sideStagePosition) {
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ setSideStagePosition(sideStagePosition, wct);
+ mMainStage.activate(getMainStageBounds(), wct);
+ mSideStage.addTask(task, getSideStageBounds(), wct);
+ mSyncQueue.queue(wct);
+ mSyncQueue.runInSync(t -> updateSurfaceBounds(null /* layout */, t));
+ return true;
+ }
+
+ boolean removeFromSideStage(int taskId) {
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+
+ /**
+ * {@link MainStage} will be deactivated in {@link #onStageHasChildrenChanged} if the
+ * {@link SideStage} no longer has children.
+ */
+ final boolean result = mSideStage.removeTask(taskId,
+ mMainStage.isActive() ? mMainStage.mRootTaskInfo.token : null,
+ wct);
+ mTaskOrganizer.applyTransaction(wct);
+ return result;
+ }
+
+ void setSideStageOutline(boolean enable) {
+ mSideStage.enableOutline(enable);
+ }
+
+ /** Starts 2 tasks in one transition. */
+ void startTasks(int mainTaskId, @Nullable Bundle mainOptions, int sideTaskId,
+ @Nullable Bundle sideOptions, @SplitPosition int sidePosition,
+ @Nullable RemoteTransition remoteTransition) {
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ mainOptions = mainOptions != null ? mainOptions : new Bundle();
+ sideOptions = sideOptions != null ? sideOptions : new Bundle();
+ setSideStagePosition(sidePosition, wct);
+
+ // Build a request WCT that will launch both apps such that task 0 is on the main stage
+ // while task 1 is on the side stage.
+ mMainStage.activate(getMainStageBounds(), wct);
+ mSideStage.setBounds(getSideStageBounds(), wct);
+
+ // Make sure the launch options will put tasks in the corresponding split roots
+ addActivityOptions(mainOptions, mMainStage);
+ addActivityOptions(sideOptions, mSideStage);
+
+ // Add task launch requests
+ wct.startTask(mainTaskId, mainOptions);
+ wct.startTask(sideTaskId, sideOptions);
+
+ mSplitTransitions.startEnterTransition(
+ TRANSIT_SPLIT_SCREEN_PAIR_OPEN, wct, remoteTransition, this);
+ }
+
+ /** Starts 2 tasks in one legacy transition. */
+ void startTasksWithLegacyTransition(int mainTaskId, @Nullable Bundle mainOptions,
+ int sideTaskId, @Nullable Bundle sideOptions, @SplitPosition int sidePosition,
+ RemoteAnimationAdapter adapter) {
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ // Need to add another wrapper here in shell so that we can inject the divider bar
+ // and also manage the process elevation via setRunningRemote
+ IRemoteAnimationRunner wrapper = new IRemoteAnimationRunner.Stub() {
+ @Override
+ public void onAnimationStart(@WindowManager.TransitionOldType int transit,
+ RemoteAnimationTarget[] apps,
+ RemoteAnimationTarget[] wallpapers,
+ RemoteAnimationTarget[] nonApps,
+ final IRemoteAnimationFinishedCallback finishedCallback) {
+ RemoteAnimationTarget[] augmentedNonApps =
+ new RemoteAnimationTarget[nonApps.length + 1];
+ for (int i = 0; i < nonApps.length; ++i) {
+ augmentedNonApps[i] = nonApps[i];
+ }
+ augmentedNonApps[augmentedNonApps.length - 1] = getDividerBarLegacyTarget();
+ try {
+ ActivityTaskManager.getService().setRunningRemoteTransitionDelegate(
+ adapter.getCallingApplication());
+ adapter.getRunner().onAnimationStart(transit, apps, wallpapers, nonApps,
+ finishedCallback);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error starting remote animation", e);
+ }
+ }
+
+ @Override
+ public void onAnimationCancelled() {
+ try {
+ adapter.getRunner().onAnimationCancelled();
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error starting remote animation", e);
+ }
+ }
+ };
+ RemoteAnimationAdapter wrappedAdapter = new RemoteAnimationAdapter(
+ wrapper, adapter.getDuration(), adapter.getStatusBarTransitionDelay());
+
+ if (mainOptions == null) {
+ mainOptions = ActivityOptions.makeRemoteAnimation(wrappedAdapter).toBundle();
+ } else {
+ ActivityOptions mainActivityOptions = ActivityOptions.fromBundle(mainOptions);
+ mainActivityOptions.update(ActivityOptions.makeRemoteAnimation(wrappedAdapter));
+ }
+
+ sideOptions = sideOptions != null ? sideOptions : new Bundle();
+ setSideStagePosition(sidePosition, wct);
+
+ // Build a request WCT that will launch both apps such that task 0 is on the main stage
+ // while task 1 is on the side stage.
+ mMainStage.activate(getMainStageBounds(), wct);
+ mSideStage.setBounds(getSideStageBounds(), wct);
+
+ // Make sure the launch options will put tasks in the corresponding split roots
+ addActivityOptions(mainOptions, mMainStage);
+ addActivityOptions(sideOptions, mSideStage);
+
+ // Add task launch requests
+ wct.startTask(mainTaskId, mainOptions);
+ wct.startTask(sideTaskId, sideOptions);
+
+ // Using legacy transitions, so we can't use blast sync since it conflicts.
+ mTaskOrganizer.applyTransaction(wct);
+ }
+
+ public void startIntent(PendingIntent intent, Intent fillInIntent,
+ @SplitScreen.StageType int stage, @SplitPosition int position,
+ @androidx.annotation.Nullable Bundle options,
+ @Nullable RemoteTransition remoteTransition) {
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ options = resolveStartStage(stage, position, options, wct);
+ wct.sendPendingIntent(intent, fillInIntent, options);
+ mSplitTransitions.startEnterTransition(
+ TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE, wct, remoteTransition, this);
+ }
+
+ Bundle resolveStartStage(@SplitScreen.StageType int stage,
+ @SplitPosition int position, @androidx.annotation.Nullable Bundle options,
+ @androidx.annotation.Nullable WindowContainerTransaction wct) {
+ switch (stage) {
+ case STAGE_TYPE_UNDEFINED: {
+ // Use the stage of the specified position is valid.
+ if (position != SPLIT_POSITION_UNDEFINED) {
+ if (position == getSideStagePosition()) {
+ options = resolveStartStage(STAGE_TYPE_SIDE, position, options, wct);
+ } else {
+ options = resolveStartStage(STAGE_TYPE_MAIN, position, options, wct);
+ }
+ } else {
+ // Exit split-screen and launch fullscreen since stage wasn't specified.
+ prepareExitSplitScreen(STAGE_TYPE_UNDEFINED, wct);
+ }
+ break;
+ }
+ case STAGE_TYPE_SIDE: {
+ if (position != SPLIT_POSITION_UNDEFINED) {
+ setSideStagePosition(position, wct);
+ } else {
+ position = getSideStagePosition();
+ }
+ if (options == null) {
+ options = new Bundle();
+ }
+ updateActivityOptions(options, position);
+ break;
+ }
+ case STAGE_TYPE_MAIN: {
+ if (position != SPLIT_POSITION_UNDEFINED) {
+ // Set the side stage opposite of what we want to the main stage.
+ final int sideStagePosition = position == SPLIT_POSITION_TOP_OR_LEFT
+ ? SPLIT_POSITION_BOTTOM_OR_RIGHT : SPLIT_POSITION_TOP_OR_LEFT;
+ setSideStagePosition(sideStagePosition, wct);
+ } else {
+ position = getMainStagePosition();
+ }
+ if (options == null) {
+ options = new Bundle();
+ }
+ updateActivityOptions(options, position);
+ break;
+ }
+ default:
+ throw new IllegalArgumentException("Unknown stage=" + stage);
+ }
+
+ return options;
+ }
+
+ @SplitPosition
+ int getSideStagePosition() {
+ return mSideStagePosition;
+ }
+
+ @SplitPosition
+ int getMainStagePosition() {
+ return mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT
+ ? SPLIT_POSITION_BOTTOM_OR_RIGHT : SPLIT_POSITION_TOP_OR_LEFT;
+ }
+
+ void setSideStagePosition(@SplitPosition int sideStagePosition,
+ @Nullable WindowContainerTransaction wct) {
+ setSideStagePosition(sideStagePosition, true /* updateBounds */, wct);
+ }
+
+ private void setSideStagePosition(@SplitPosition int sideStagePosition, boolean updateBounds,
+ @Nullable WindowContainerTransaction wct) {
+ if (mSideStagePosition == sideStagePosition) return;
+ mSideStagePosition = sideStagePosition;
+ sendOnStagePositionChanged();
+
+ if (mSideStageListener.mVisible && updateBounds) {
+ if (wct == null) {
+ // onLayoutChanged builds/applies a wct with the contents of updateWindowBounds.
+ onLayoutChanged(mSplitLayout);
+ } else {
+ updateWindowBounds(mSplitLayout, wct);
+ updateUnfoldBounds();
+ }
+ }
+ }
+
+ void setSideStageVisibility(boolean visible) {
+ if (mSideStageListener.mVisible == visible) return;
+
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ mSideStage.setVisibility(visible, wct);
+ mTaskOrganizer.applyTransaction(wct);
+ }
+
+ void onKeyguardOccludedChanged(boolean occluded) {
+ // Do not exit split directly, because it needs to wait for task info update to determine
+ // which task should remain on top after split dismissed.
+ mKeyguardOccluded = occluded;
+ }
+
+ void onKeyguardVisibilityChanged(boolean showing) {
+ if (!showing && mMainStage.isActive()
+ && mTopStageAfterFoldDismiss != STAGE_TYPE_UNDEFINED) {
+ exitSplitScreen(mTopStageAfterFoldDismiss == STAGE_TYPE_MAIN ? mMainStage : mSideStage,
+ SPLITSCREEN_UICHANGED__EXIT_REASON__DEVICE_FOLDED);
+ }
+ }
+
+ void exitSplitScreenOnHide(boolean exitSplitScreenOnHide) {
+ mExitSplitScreenOnHide = exitSplitScreenOnHide;
+ }
+
+ void exitSplitScreen(int toTopTaskId, int exitReason) {
+ StageTaskListener childrenToTop = null;
+ if (mMainStage.containsTask(toTopTaskId)) {
+ childrenToTop = mMainStage;
+ } else if (mSideStage.containsTask(toTopTaskId)) {
+ childrenToTop = mSideStage;
+ }
+
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ if (childrenToTop != null) {
+ childrenToTop.reorderChild(toTopTaskId, true /* onTop */, wct);
+ }
+ applyExitSplitScreen(childrenToTop, wct, exitReason);
+ }
+
+ private void exitSplitScreen(StageTaskListener childrenToTop, int exitReason) {
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ applyExitSplitScreen(childrenToTop, wct, exitReason);
+ }
+
+ private void applyExitSplitScreen(
+ StageTaskListener childrenToTop,
+ WindowContainerTransaction wct, int exitReason) {
+ mSideStage.removeAllTasks(wct, childrenToTop == mSideStage);
+ mMainStage.deactivate(wct, childrenToTop == mMainStage);
+ mTaskOrganizer.applyTransaction(wct);
+ mSyncQueue.runInSync(t -> t
+ .setWindowCrop(mMainStage.mRootLeash, null)
+ .setWindowCrop(mSideStage.mRootLeash, null));
+ // Hide divider and reset its position.
+ setDividerVisibility(false);
+ mSplitLayout.resetDividerPosition();
+ mTopStageAfterFoldDismiss = STAGE_TYPE_UNDEFINED;
+ if (childrenToTop != null) {
+ logExitToStage(exitReason, childrenToTop == mMainStage);
+ } else {
+ logExit(exitReason);
+ }
+ }
+
+ /**
+ * Unlike exitSplitScreen, this takes a stagetype vs an actual stage-reference and populates
+ * an existing WindowContainerTransaction (rather than applying immediately). This is intended
+ * to be used when exiting split might be bundled with other window operations.
+ */
+ void prepareExitSplitScreen(@SplitScreen.StageType int stageToTop,
+ @NonNull WindowContainerTransaction wct) {
+ mSideStage.removeAllTasks(wct, stageToTop == STAGE_TYPE_SIDE);
+ mMainStage.deactivate(wct, stageToTop == STAGE_TYPE_MAIN);
+ }
+
+ void getStageBounds(Rect outTopOrLeftBounds, Rect outBottomOrRightBounds) {
+ outTopOrLeftBounds.set(mSplitLayout.getBounds1());
+ outBottomOrRightBounds.set(mSplitLayout.getBounds2());
+ }
+
+ private void addActivityOptions(Bundle opts, StageTaskListener stage) {
+ opts.putParcelable(KEY_LAUNCH_ROOT_TASK_TOKEN, stage.mRootTaskInfo.token);
+ }
+
+ void updateActivityOptions(Bundle opts, @SplitPosition int position) {
+ addActivityOptions(opts, position == mSideStagePosition ? mSideStage : mMainStage);
+ }
+
+ void registerSplitScreenListener(SplitScreen.SplitScreenListener listener) {
+ if (mListeners.contains(listener)) return;
+ mListeners.add(listener);
+ sendStatusToListener(listener);
+ }
+
+ void unregisterSplitScreenListener(SplitScreen.SplitScreenListener listener) {
+ mListeners.remove(listener);
+ }
+
+ void sendStatusToListener(SplitScreen.SplitScreenListener listener) {
+ listener.onStagePositionChanged(STAGE_TYPE_MAIN, getMainStagePosition());
+ listener.onStagePositionChanged(STAGE_TYPE_SIDE, getSideStagePosition());
+ listener.onSplitVisibilityChanged(isSplitScreenVisible());
+ mSideStage.onSplitScreenListenerRegistered(listener, STAGE_TYPE_SIDE);
+ mMainStage.onSplitScreenListenerRegistered(listener, STAGE_TYPE_MAIN);
+ }
+
+ private void sendOnStagePositionChanged() {
+ for (int i = mListeners.size() - 1; i >= 0; --i) {
+ final SplitScreen.SplitScreenListener l = mListeners.get(i);
+ l.onStagePositionChanged(STAGE_TYPE_MAIN, getMainStagePosition());
+ l.onStagePositionChanged(STAGE_TYPE_SIDE, getSideStagePosition());
+ }
+ }
+
+ private void onStageChildTaskStatusChanged(StageListenerImpl stageListener, int taskId,
+ boolean present, boolean visible) {
+ int stage;
+ if (present) {
+ stage = stageListener == mSideStageListener ? STAGE_TYPE_SIDE : STAGE_TYPE_MAIN;
+ } else {
+ // No longer on any stage
+ stage = STAGE_TYPE_UNDEFINED;
+ }
+ if (stage == STAGE_TYPE_MAIN) {
+ mLogger.logMainStageAppChange(getMainStagePosition(), mMainStage.getTopChildTaskUid(),
+ mSplitLayout.isLandscape());
+ } else {
+ mLogger.logSideStageAppChange(getSideStagePosition(), mSideStage.getTopChildTaskUid(),
+ mSplitLayout.isLandscape());
+ }
+
+ for (int i = mListeners.size() - 1; i >= 0; --i) {
+ mListeners.get(i).onTaskStageChanged(taskId, stage, visible);
+ }
+ }
+
+ private void sendSplitVisibilityChanged() {
+ for (int i = mListeners.size() - 1; i >= 0; --i) {
+ final SplitScreen.SplitScreenListener l = mListeners.get(i);
+ l.onSplitVisibilityChanged(mDividerVisible);
+ }
+
+ if (mMainUnfoldController != null && mSideUnfoldController != null) {
+ mMainUnfoldController.onSplitVisibilityChanged(mDividerVisible);
+ mSideUnfoldController.onSplitVisibilityChanged(mDividerVisible);
+ }
+ }
+
+ private void onStageRootTaskAppeared(StageListenerImpl stageListener) {
+ if (mMainStageListener.mHasRootTask && mSideStageListener.mHasRootTask) {
+ mUseLegacySplit = mContext.getResources().getBoolean(R.bool.config_useLegacySplit);
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ // Make the stages adjacent to each other so they occlude what's behind them.
+ wct.setAdjacentRoots(mMainStage.mRootTaskInfo.token, mSideStage.mRootTaskInfo.token);
+
+ // Only sets side stage as launch-adjacent-flag-root when the device is not using legacy
+ // split to prevent new split behavior confusing users.
+ if (!mUseLegacySplit) {
+ wct.setLaunchAdjacentFlagRoot(mSideStage.mRootTaskInfo.token);
+ }
+
+ mTaskOrganizer.applyTransaction(wct);
+ }
+ }
+
+ private void onStageRootTaskVanished(StageListenerImpl stageListener) {
+ if (stageListener == mMainStageListener || stageListener == mSideStageListener) {
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ // Deactivate the main stage if it no longer has a root task.
+ mMainStage.deactivate(wct);
+
+ if (!mUseLegacySplit) {
+ wct.clearLaunchAdjacentFlagRoot(mSideStage.mRootTaskInfo.token);
+ }
+
+ mTaskOrganizer.applyTransaction(wct);
+ }
+ }
+
+ private void setDividerVisibility(boolean visible) {
+ if (mDividerVisible == visible) return;
+ mDividerVisible = visible;
+ if (visible) {
+ mSplitLayout.init();
+ updateUnfoldBounds();
+ } else {
+ mSplitLayout.release();
+ }
+ sendSplitVisibilityChanged();
+ }
+
+ private void onStageVisibilityChanged(StageListenerImpl stageListener) {
+ final boolean sideStageVisible = mSideStageListener.mVisible;
+ final boolean mainStageVisible = mMainStageListener.mVisible;
+ final boolean bothStageVisible = sideStageVisible && mainStageVisible;
+ final boolean bothStageInvisible = !sideStageVisible && !mainStageVisible;
+ final boolean sameVisibility = sideStageVisible == mainStageVisible;
+ // Only add or remove divider when both visible or both invisible to avoid sometimes we only
+ // got one stage visibility changed for a moment and it will cause flicker.
+ if (sameVisibility) {
+ setDividerVisibility(bothStageVisible);
+ }
+
+ if (bothStageInvisible) {
+ if (mExitSplitScreenOnHide
+ // Don't dismiss staged split when both stages are not visible due to sleeping display,
+ // like the cases keyguard showing or screen off.
+ || (!mMainStage.mRootTaskInfo.isSleeping && !mSideStage.mRootTaskInfo.isSleeping)) {
+ exitSplitScreen(null /* childrenToTop */,
+ SPLITSCREEN_UICHANGED__EXIT_REASON__RETURN_HOME);
+ }
+ } else if (mKeyguardOccluded) {
+ // At least one of the stages is visible while keyguard occluded. Dismiss split because
+ // there's show-when-locked activity showing on top of keyguard. Also make sure the
+ // task contains show-when-locked activity remains on top after split dismissed.
+ final StageTaskListener toTop =
+ mainStageVisible ? mMainStage : (sideStageVisible ? mSideStage : null);
+ exitSplitScreen(toTop, SPLITSCREEN_UICHANGED__EXIT_REASON__SCREEN_LOCKED_SHOW_ON_TOP);
+ }
+
+ mSyncQueue.runInSync(t -> {
+ // Same above, we only set root tasks and divider leash visibility when both stage
+ // change to visible or invisible to avoid flicker.
+ if (sameVisibility) {
+ t.setVisibility(mSideStage.mRootLeash, bothStageVisible)
+ .setVisibility(mMainStage.mRootLeash, bothStageVisible);
+ applyDividerVisibility(t);
+ applyOutlineVisibility(t);
+ }
+ });
+ }
+
+ private void applyDividerVisibility(SurfaceControl.Transaction t) {
+ final SurfaceControl dividerLeash = mSplitLayout.getDividerLeash();
+ if (dividerLeash == null) {
+ return;
+ }
+
+ if (mDividerVisible) {
+ t.show(dividerLeash)
+ .setLayer(dividerLeash, SPLIT_DIVIDER_LAYER)
+ .setPosition(dividerLeash,
+ mSplitLayout.getDividerBounds().left,
+ mSplitLayout.getDividerBounds().top);
+ } else {
+ t.hide(dividerLeash);
+ }
+ }
+
+ private void applyOutlineVisibility(SurfaceControl.Transaction t) {
+ final SurfaceControl outlineLeash = mSideStage.getOutlineLeash();
+ if (outlineLeash == null) {
+ return;
+ }
+
+ if (mDividerVisible) {
+ t.show(outlineLeash).setLayer(outlineLeash, SPLIT_DIVIDER_LAYER);
+ } else {
+ t.hide(outlineLeash);
+ }
+ }
+
+ private void onStageHasChildrenChanged(StageListenerImpl stageListener) {
+ final boolean hasChildren = stageListener.mHasChildren;
+ final boolean isSideStage = stageListener == mSideStageListener;
+ if (!hasChildren) {
+ if (isSideStage && mMainStageListener.mVisible) {
+ // Exit to main stage if side stage no longer has children.
+ exitSplitScreen(mMainStage, SPLITSCREEN_UICHANGED__EXIT_REASON__APP_FINISHED);
+ } else if (!isSideStage && mSideStageListener.mVisible) {
+ // Exit to side stage if main stage no longer has children.
+ exitSplitScreen(mSideStage, SPLITSCREEN_UICHANGED__EXIT_REASON__APP_FINISHED);
+ }
+ } else if (isSideStage) {
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ // Make sure the main stage is active.
+ mMainStage.activate(getMainStageBounds(), wct);
+ mSideStage.setBounds(getSideStageBounds(), wct);
+ mTaskOrganizer.applyTransaction(wct);
+ }
+ if (!mLogger.hasStartedSession() && mMainStageListener.mHasChildren
+ && mSideStageListener.mHasChildren) {
+ mLogger.logEnter(mSplitLayout.getDividerPositionAsFraction(),
+ getMainStagePosition(), mMainStage.getTopChildTaskUid(),
+ getSideStagePosition(), mSideStage.getTopChildTaskUid(),
+ mSplitLayout.isLandscape());
+ }
+ }
+
+ @VisibleForTesting
+ IBinder onSnappedToDismissTransition(boolean mainStageToTop) {
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ prepareExitSplitScreen(mainStageToTop ? STAGE_TYPE_MAIN : STAGE_TYPE_SIDE, wct);
+ return mSplitTransitions.startSnapToDismiss(wct, this);
+ }
+
+ @Override
+ public void onSnappedToDismiss(boolean bottomOrRight) {
+ final boolean mainStageToTop =
+ bottomOrRight ? mSideStagePosition == SPLIT_POSITION_BOTTOM_OR_RIGHT
+ : mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT;
+ if (ENABLE_SHELL_TRANSITIONS) {
+ onSnappedToDismissTransition(mainStageToTop);
+ return;
+ }
+ exitSplitScreen(mainStageToTop ? mMainStage : mSideStage,
+ SPLITSCREEN_UICHANGED__EXIT_REASON__DRAG_DIVIDER);
+ }
+
+ @Override
+ public void onDoubleTappedDivider() {
+ setSideStagePosition(mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT
+ ? SPLIT_POSITION_BOTTOM_OR_RIGHT : SPLIT_POSITION_TOP_OR_LEFT, null /* wct */);
+ mLogger.logSwap(getMainStagePosition(), mMainStage.getTopChildTaskUid(),
+ getSideStagePosition(), mSideStage.getTopChildTaskUid(),
+ mSplitLayout.isLandscape());
+ }
+
+ @Override
+ public void onLayoutChanging(SplitLayout layout) {
+ mSyncQueue.runInSync(t -> updateSurfaceBounds(layout, t));
+ mSideStage.setOutlineVisibility(false);
+ }
+
+ @Override
+ public void onLayoutChanged(SplitLayout layout) {
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ updateWindowBounds(layout, wct);
+ updateUnfoldBounds();
+ mSyncQueue.queue(wct);
+ mSyncQueue.runInSync(t -> updateSurfaceBounds(layout, t));
+ mSideStage.setOutlineVisibility(true);
+ mLogger.logResize(mSplitLayout.getDividerPositionAsFraction());
+ }
+
+ private void updateUnfoldBounds() {
+ if (mMainUnfoldController != null && mSideUnfoldController != null) {
+ mMainUnfoldController.onLayoutChanged(getMainStageBounds());
+ mSideUnfoldController.onLayoutChanged(getSideStageBounds());
+ }
+ }
+
+ /**
+ * Populates `wct` with operations that match the split windows to the current layout.
+ * To match relevant surfaces, make sure to call updateSurfaceBounds after `wct` is applied
+ */
+ private void updateWindowBounds(SplitLayout layout, WindowContainerTransaction wct) {
+ final StageTaskListener topLeftStage =
+ mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mSideStage : mMainStage;
+ final StageTaskListener bottomRightStage =
+ mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mMainStage : mSideStage;
+ layout.applyTaskChanges(wct, topLeftStage.mRootTaskInfo, bottomRightStage.mRootTaskInfo);
+ }
+
+ void updateSurfaceBounds(@Nullable SplitLayout layout, @NonNull SurfaceControl.Transaction t) {
+ final StageTaskListener topLeftStage =
+ mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mSideStage : mMainStage;
+ final StageTaskListener bottomRightStage =
+ mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mMainStage : mSideStage;
+ (layout != null ? layout : mSplitLayout).applySurfaceChanges(t, topLeftStage.mRootLeash,
+ bottomRightStage.mRootLeash, topLeftStage.mDimLayer, bottomRightStage.mDimLayer);
+ }
+
+ @Override
+ public int getSplitItemPosition(WindowContainerToken token) {
+ if (token == null) {
+ return SPLIT_POSITION_UNDEFINED;
+ }
+
+ if (token.equals(mMainStage.mRootTaskInfo.getToken())) {
+ return getMainStagePosition();
+ } else if (token.equals(mSideStage.mRootTaskInfo.getToken())) {
+ return getSideStagePosition();
+ }
+
+ return SPLIT_POSITION_UNDEFINED;
+ }
+
+ @Override
+ public void onLayoutShifted(int offsetX, int offsetY, SplitLayout layout) {
+ final StageTaskListener topLeftStage =
+ mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mSideStage : mMainStage;
+ final StageTaskListener bottomRightStage =
+ mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mMainStage : mSideStage;
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ layout.applyLayoutShifted(wct, offsetX, offsetY, topLeftStage.mRootTaskInfo,
+ bottomRightStage.mRootTaskInfo);
+ mTaskOrganizer.applyTransaction(wct);
+ }
+
+ @Override
+ public void onDisplayAreaAppeared(DisplayAreaInfo displayAreaInfo) {
+ mDisplayAreaInfo = displayAreaInfo;
+ if (mSplitLayout == null) {
+ mSplitLayout = new SplitLayout(TAG + "SplitDivider", mContext,
+ mDisplayAreaInfo.configuration, this, mParentContainerCallbacks,
+ mDisplayImeController, mTaskOrganizer);
+ mDisplayInsetsController.addInsetsChangedListener(mDisplayId, mSplitLayout);
+
+ if (mMainUnfoldController != null && mSideUnfoldController != null) {
+ mMainUnfoldController.init();
+ mSideUnfoldController.init();
+ }
+ }
+ }
+
+ @Override
+ public void onDisplayAreaVanished(DisplayAreaInfo displayAreaInfo) {
+ throw new IllegalStateException("Well that was unexpected...");
+ }
+
+ @Override
+ public void onDisplayAreaInfoChanged(DisplayAreaInfo displayAreaInfo) {
+ mDisplayAreaInfo = displayAreaInfo;
+ if (mSplitLayout != null
+ && mSplitLayout.updateConfiguration(mDisplayAreaInfo.configuration)
+ && mMainStage.isActive()) {
+ onLayoutChanged(mSplitLayout);
+ }
+ }
+
+ private void onFoldedStateChanged(boolean folded) {
+ mTopStageAfterFoldDismiss = STAGE_TYPE_UNDEFINED;
+ if (!folded) return;
+
+ if (mMainStage.isFocused()) {
+ mTopStageAfterFoldDismiss = STAGE_TYPE_MAIN;
+ } else if (mSideStage.isFocused()) {
+ mTopStageAfterFoldDismiss = STAGE_TYPE_SIDE;
+ }
+ }
+
+ private Rect getSideStageBounds() {
+ return mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT
+ ? mSplitLayout.getBounds1() : mSplitLayout.getBounds2();
+ }
+
+ private Rect getMainStageBounds() {
+ return mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT
+ ? mSplitLayout.getBounds2() : mSplitLayout.getBounds1();
+ }
+
+ /**
+ * Get the stage that should contain this `taskInfo`. The stage doesn't necessarily contain
+ * this task (yet) so this can also be used to identify which stage to put a task into.
+ */
+ private StageTaskListener getStageOfTask(ActivityManager.RunningTaskInfo taskInfo) {
+ // TODO(b/184679596): Find a way to either include task-org information in the transition,
+ // or synchronize task-org callbacks so we can use stage.containsTask
+ if (mMainStage.mRootTaskInfo != null
+ && taskInfo.parentTaskId == mMainStage.mRootTaskInfo.taskId) {
+ return mMainStage;
+ } else if (mSideStage.mRootTaskInfo != null
+ && taskInfo.parentTaskId == mSideStage.mRootTaskInfo.taskId) {
+ return mSideStage;
+ }
+ return null;
+ }
+
+ @SplitScreen.StageType
+ private int getStageType(StageTaskListener stage) {
+ return stage == mMainStage ? STAGE_TYPE_MAIN : STAGE_TYPE_SIDE;
+ }
+
+ @Override
+ public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
+ @Nullable TransitionRequestInfo request) {
+ final ActivityManager.RunningTaskInfo triggerTask = request.getTriggerTask();
+ if (triggerTask == null) {
+ // still want to monitor everything while in split-screen, so return non-null.
+ return isSplitScreenVisible() ? new WindowContainerTransaction() : null;
+ }
+
+ WindowContainerTransaction out = null;
+ final @WindowManager.TransitionType int type = request.getType();
+ if (isSplitScreenVisible()) {
+ // try to handle everything while in split-screen, so return a WCT even if it's empty.
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " split is active so using split"
+ + "Transition to handle request. triggerTask=%d type=%s mainChildren=%d"
+ + " sideChildren=%d", triggerTask.taskId, transitTypeToString(type),
+ mMainStage.getChildCount(), mSideStage.getChildCount());
+ out = new WindowContainerTransaction();
+ final StageTaskListener stage = getStageOfTask(triggerTask);
+ if (stage != null) {
+ // dismiss split if the last task in one of the stages is going away
+ if (isClosingType(type) && stage.getChildCount() == 1) {
+ // The top should be the opposite side that is closing:
+ mDismissTop = getStageType(stage) == STAGE_TYPE_MAIN
+ ? STAGE_TYPE_SIDE : STAGE_TYPE_MAIN;
+ }
+ } else {
+ if (triggerTask.getActivityType() == ACTIVITY_TYPE_HOME && isOpeningType(type)) {
+ // Going home so dismiss both.
+ mDismissTop = STAGE_TYPE_UNDEFINED;
+ }
+ }
+ if (mDismissTop != NO_DISMISS) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " splitTransition "
+ + " deduced Dismiss from request. toTop=%s",
+ stageTypeToString(mDismissTop));
+ prepareExitSplitScreen(mDismissTop, out);
+ mSplitTransitions.mPendingDismiss = transition;
+ }
+ } else {
+ // Not in split mode, so look for an open into a split stage just so we can whine and
+ // complain about how this isn't a supported operation.
+ if ((type == TRANSIT_OPEN || type == TRANSIT_TO_FRONT)) {
+ if (getStageOfTask(triggerTask) != null) {
+ throw new IllegalStateException("Entering split implicitly with only one task"
+ + " isn't supported.");
+ }
+ }
+ }
+ return out;
+ }
+
+ @Override
+ public boolean startAnimation(@NonNull IBinder transition,
+ @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ if (transition != mSplitTransitions.mPendingDismiss
+ && transition != mSplitTransitions.mPendingEnter) {
+ // Not entering or exiting, so just do some house-keeping and validation.
+
+ // If we're not in split-mode, just abort so something else can handle it.
+ if (!isSplitScreenVisible()) return false;
+
+ for (int iC = 0; iC < info.getChanges().size(); ++iC) {
+ final TransitionInfo.Change change = info.getChanges().get(iC);
+ final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
+ if (taskInfo == null || !taskInfo.hasParentTask()) continue;
+ final StageTaskListener stage = getStageOfTask(taskInfo);
+ if (stage == null) continue;
+ if (isOpeningType(change.getMode())) {
+ if (!stage.containsTask(taskInfo.taskId)) {
+ Log.w(TAG, "Expected onTaskAppeared on " + stage + " to have been called"
+ + " with " + taskInfo.taskId + " before startAnimation().");
+ }
+ } else if (isClosingType(change.getMode())) {
+ if (stage.containsTask(taskInfo.taskId)) {
+ Log.w(TAG, "Expected onTaskVanished on " + stage + " to have been called"
+ + " with " + taskInfo.taskId + " before startAnimation().");
+ }
+ }
+ }
+ if (mMainStage.getChildCount() == 0 || mSideStage.getChildCount() == 0) {
+ // TODO(shell-transitions): Implement a fallback behavior for now.
+ throw new IllegalStateException("Somehow removed the last task in a stage"
+ + " outside of a proper transition");
+ // This can happen in some pathological cases. For example:
+ // 1. main has 2 tasks [Task A (Single-task), Task B], side has one task [Task C]
+ // 2. Task B closes itself and starts Task A in LAUNCH_ADJACENT at the same time
+ // In this case, the result *should* be that we leave split.
+ // TODO(b/184679596): Find a way to either include task-org information in
+ // the transition, or synchronize task-org callbacks.
+ }
+
+ // Use normal animations.
+ return false;
+ }
+
+ boolean shouldAnimate = true;
+ if (mSplitTransitions.mPendingEnter == transition) {
+ shouldAnimate = startPendingEnterAnimation(transition, info, startTransaction);
+ } else if (mSplitTransitions.mPendingDismiss == transition) {
+ shouldAnimate = startPendingDismissAnimation(transition, info, startTransaction);
+ }
+ if (!shouldAnimate) return false;
+
+ mSplitTransitions.playAnimation(transition, info, startTransaction, finishTransaction,
+ finishCallback, mMainStage.mRootTaskInfo.token, mSideStage.mRootTaskInfo.token);
+ return true;
+ }
+
+ private boolean startPendingEnterAnimation(@NonNull IBinder transition,
+ @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t) {
+ if (info.getType() == TRANSIT_SPLIT_SCREEN_PAIR_OPEN) {
+ // First, verify that we actually have opened 2 apps in split.
+ TransitionInfo.Change mainChild = null;
+ TransitionInfo.Change sideChild = null;
+ for (int iC = 0; iC < info.getChanges().size(); ++iC) {
+ final TransitionInfo.Change change = info.getChanges().get(iC);
+ final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
+ if (taskInfo == null || !taskInfo.hasParentTask()) continue;
+ final @SplitScreen.StageType int stageType = getStageType(getStageOfTask(taskInfo));
+ if (stageType == STAGE_TYPE_MAIN) {
+ mainChild = change;
+ } else if (stageType == STAGE_TYPE_SIDE) {
+ sideChild = change;
+ }
+ }
+ if (mainChild == null || sideChild == null) {
+ throw new IllegalStateException("Launched 2 tasks in split, but didn't receive"
+ + " 2 tasks in transition. Possibly one of them failed to launch");
+ // TODO: fallback logic. Probably start a new transition to exit split before
+ // applying anything here. Ideally consolidate with transition-merging.
+ }
+
+ // Update local states (before animating).
+ setDividerVisibility(true);
+ setSideStagePosition(SPLIT_POSITION_BOTTOM_OR_RIGHT, false /* updateBounds */,
+ null /* wct */);
+ setSplitsVisible(true);
+
+ addDividerBarToTransition(info, t, true /* show */);
+
+ // Make some noise if things aren't totally expected. These states shouldn't effect
+ // transitions locally, but remotes (like Launcher) may get confused if they were
+ // depending on listener callbacks. This can happen because task-organizer callbacks
+ // aren't serialized with transition callbacks.
+ // TODO(b/184679596): Find a way to either include task-org information in
+ // the transition, or synchronize task-org callbacks.
+ if (!mMainStage.containsTask(mainChild.getTaskInfo().taskId)) {
+ Log.w(TAG, "Expected onTaskAppeared on " + mMainStage
+ + " to have been called with " + mainChild.getTaskInfo().taskId
+ + " before startAnimation().");
+ }
+ if (!mSideStage.containsTask(sideChild.getTaskInfo().taskId)) {
+ Log.w(TAG, "Expected onTaskAppeared on " + mSideStage
+ + " to have been called with " + sideChild.getTaskInfo().taskId
+ + " before startAnimation().");
+ }
+ return true;
+ } else {
+ // TODO: other entry method animations
+ throw new RuntimeException("Unsupported split-entry");
+ }
+ }
+
+ private boolean startPendingDismissAnimation(@NonNull IBinder transition,
+ @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t) {
+ // Make some noise if things aren't totally expected. These states shouldn't effect
+ // transitions locally, but remotes (like Launcher) may get confused if they were
+ // depending on listener callbacks. This can happen because task-organizer callbacks
+ // aren't serialized with transition callbacks.
+ // TODO(b/184679596): Find a way to either include task-org information in
+ // the transition, or synchronize task-org callbacks.
+ if (mMainStage.getChildCount() != 0) {
+ final StringBuilder tasksLeft = new StringBuilder();
+ for (int i = 0; i < mMainStage.getChildCount(); ++i) {
+ tasksLeft.append(i != 0 ? ", " : "");
+ tasksLeft.append(mMainStage.mChildrenTaskInfo.keyAt(i));
+ }
+ Log.w(TAG, "Expected onTaskVanished on " + mMainStage
+ + " to have been called with [" + tasksLeft.toString()
+ + "] before startAnimation().");
+ }
+ if (mSideStage.getChildCount() != 0) {
+ final StringBuilder tasksLeft = new StringBuilder();
+ for (int i = 0; i < mSideStage.getChildCount(); ++i) {
+ tasksLeft.append(i != 0 ? ", " : "");
+ tasksLeft.append(mSideStage.mChildrenTaskInfo.keyAt(i));
+ }
+ Log.w(TAG, "Expected onTaskVanished on " + mSideStage
+ + " to have been called with [" + tasksLeft.toString()
+ + "] before startAnimation().");
+ }
+
+ // Update local states.
+ setSplitsVisible(false);
+ // Wait until after animation to update divider
+
+ if (info.getType() == TRANSIT_SPLIT_DISMISS_SNAP) {
+ // Reset crops so they don't interfere with subsequent launches
+ t.setWindowCrop(mMainStage.mRootLeash, null);
+ t.setWindowCrop(mSideStage.mRootLeash, null);
+ }
+
+ if (mDismissTop == STAGE_TYPE_UNDEFINED) {
+ // Going home (dismissing both splits)
+
+ // TODO: Have a proper remote for this. Until then, though, reset state and use the
+ // normal animation stuff (which falls back to the normal launcher remote).
+ t.hide(mSplitLayout.getDividerLeash());
+ setDividerVisibility(false);
+ mSplitTransitions.mPendingDismiss = null;
+ return false;
+ }
+
+ addDividerBarToTransition(info, t, false /* show */);
+ // We're dismissing split by moving the other one to fullscreen.
+ // Since we don't have any animations for this yet, just use the internal example
+ // animations.
+ return true;
+ }
+
+ private void addDividerBarToTransition(@NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction t, boolean show) {
+ final SurfaceControl leash = mSplitLayout.getDividerLeash();
+ final TransitionInfo.Change barChange = new TransitionInfo.Change(null /* token */, leash);
+ final Rect bounds = mSplitLayout.getDividerBounds();
+ barChange.setStartAbsBounds(bounds);
+ barChange.setEndAbsBounds(bounds);
+ barChange.setMode(show ? TRANSIT_TO_FRONT : TRANSIT_TO_BACK);
+ barChange.setFlags(FLAG_IS_DIVIDER_BAR);
+ // Technically this should be order-0, but this is running after layer assignment
+ // and it's a special case, so just add to end.
+ info.addChange(barChange);
+ // Be default, make it visible. The remote animator can adjust alpha if it plans to animate.
+ if (show) {
+ t.setAlpha(leash, 1.f);
+ t.setLayer(leash, SPLIT_DIVIDER_LAYER);
+ t.setPosition(leash, bounds.left, bounds.top);
+ t.show(leash);
+ }
+ }
+
+ RemoteAnimationTarget getDividerBarLegacyTarget() {
+ final Rect bounds = mSplitLayout.getDividerBounds();
+ return new RemoteAnimationTarget(-1 /* taskId */, -1 /* mode */,
+ mSplitLayout.getDividerLeash(), false /* isTranslucent */, null /* clipRect */,
+ null /* contentInsets */, Integer.MAX_VALUE /* prefixOrderIndex */,
+ new android.graphics.Point(0, 0) /* position */, bounds, bounds,
+ new WindowConfiguration(), true, null /* startLeash */, null /* startBounds */,
+ null /* taskInfo */, false /* allowEnterPip */, TYPE_DOCK_DIVIDER);
+ }
+
+ RemoteAnimationTarget getOutlineLegacyTarget() {
+ final Rect bounds = mSideStage.mRootTaskInfo.configuration.windowConfiguration.getBounds();
+ // Leverage TYPE_DOCK_DIVIDER type when wrapping outline remote animation target in order to
+ // distinguish as a split auxiliary target in Launcher.
+ return new RemoteAnimationTarget(-1 /* taskId */, -1 /* mode */,
+ mSideStage.getOutlineLeash(), false /* isTranslucent */, null /* clipRect */,
+ null /* contentInsets */, Integer.MAX_VALUE /* prefixOrderIndex */,
+ new android.graphics.Point(0, 0) /* position */, bounds, bounds,
+ new WindowConfiguration(), true, null /* startLeash */, null /* startBounds */,
+ null /* taskInfo */, false /* allowEnterPip */, TYPE_DOCK_DIVIDER);
+ }
+
+ @Override
+ public void dump(@NonNull PrintWriter pw, String prefix) {
+ final String innerPrefix = prefix + " ";
+ final String childPrefix = innerPrefix + " ";
+ pw.println(prefix + TAG + " mDisplayId=" + mDisplayId);
+ pw.println(innerPrefix + "mDividerVisible=" + mDividerVisible);
+ pw.println(innerPrefix + "MainStage");
+ pw.println(childPrefix + "isActive=" + mMainStage.isActive());
+ mMainStageListener.dump(pw, childPrefix);
+ pw.println(innerPrefix + "SideStage");
+ mSideStageListener.dump(pw, childPrefix);
+ pw.println(innerPrefix + "mSplitLayout=" + mSplitLayout);
+ }
+
+ /**
+ * Directly set the visibility of both splits. This assumes hasChildren matches visibility.
+ * This is intended for batch use, so it assumes other state management logic is already
+ * handled.
+ */
+ private void setSplitsVisible(boolean visible) {
+ mMainStageListener.mVisible = mSideStageListener.mVisible = visible;
+ mMainStageListener.mHasChildren = mSideStageListener.mHasChildren = visible;
+ }
+
+ /**
+ * Sets drag info to be logged when splitscreen is next entered.
+ */
+ public void logOnDroppedToSplit(@SplitPosition int position, InstanceId dragSessionId) {
+ mLogger.enterRequestedByDrag(position, dragSessionId);
+ }
+
+ /**
+ * Logs the exit of splitscreen.
+ */
+ private void logExit(int exitReason) {
+ mLogger.logExit(exitReason,
+ SPLIT_POSITION_UNDEFINED, 0 /* mainStageUid */,
+ SPLIT_POSITION_UNDEFINED, 0 /* sideStageUid */,
+ mSplitLayout.isLandscape());
+ }
+
+ /**
+ * Logs the exit of splitscreen to a specific stage. This must be called before the exit is
+ * executed.
+ */
+ private void logExitToStage(int exitReason, boolean toMainStage) {
+ mLogger.logExit(exitReason,
+ toMainStage ? getMainStagePosition() : SPLIT_POSITION_UNDEFINED,
+ toMainStage ? mMainStage.getTopChildTaskUid() : 0 /* mainStageUid */,
+ !toMainStage ? getSideStagePosition() : SPLIT_POSITION_UNDEFINED,
+ !toMainStage ? mSideStage.getTopChildTaskUid() : 0 /* sideStageUid */,
+ mSplitLayout.isLandscape());
+ }
+
+ class StageListenerImpl implements StageTaskListener.StageListenerCallbacks {
+ boolean mHasRootTask = false;
+ boolean mVisible = false;
+ boolean mHasChildren = false;
+
+ @Override
+ public void onRootTaskAppeared() {
+ mHasRootTask = true;
+ StageCoordinator.this.onStageRootTaskAppeared(this);
+ }
+
+ @Override
+ public void onStatusChanged(boolean visible, boolean hasChildren) {
+ if (!mHasRootTask) return;
+
+ if (mHasChildren != hasChildren) {
+ mHasChildren = hasChildren;
+ StageCoordinator.this.onStageHasChildrenChanged(this);
+ }
+ if (mVisible != visible) {
+ mVisible = visible;
+ StageCoordinator.this.onStageVisibilityChanged(this);
+ }
+ }
+
+ @Override
+ public void onChildTaskStatusChanged(int taskId, boolean present, boolean visible) {
+ StageCoordinator.this.onStageChildTaskStatusChanged(this, taskId, present, visible);
+ }
+
+ @Override
+ public void onRootTaskVanished() {
+ reset();
+ StageCoordinator.this.onStageRootTaskVanished(this);
+ }
+
+ @Override
+ public void onNoLongerSupportMultiWindow() {
+ if (mMainStage.isActive()) {
+ StageCoordinator.this.exitSplitScreen(null /* childrenToTop */,
+ SPLITSCREEN_UICHANGED__EXIT_REASON__APP_DOES_NOT_SUPPORT_MULTIWINDOW);
+ }
+ }
+
+ private void reset() {
+ mHasRootTask = false;
+ mVisible = false;
+ mHasChildren = false;
+ }
+
+ public void dump(@NonNull PrintWriter pw, String prefix) {
+ pw.println(prefix + "mHasRootTask=" + mHasRootTask);
+ pw.println(prefix + "mVisible=" + mVisible);
+ pw.println(prefix + "mHasChildren=" + mHasChildren);
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/StageTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/StageTaskListener.java
new file mode 100644
index 000000000000..8b36c9406b15
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/StageTaskListener.java
@@ -0,0 +1,288 @@
+/*
+ * Copyright (C) 2020 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.stagesplit;
+
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+
+import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS;
+
+import android.annotation.CallSuper;
+import android.annotation.Nullable;
+import android.app.ActivityManager;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.util.SparseArray;
+import android.view.SurfaceControl;
+import android.view.SurfaceSession;
+import android.window.WindowContainerTransaction;
+
+import androidx.annotation.NonNull;
+
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.SurfaceUtils;
+import com.android.wm.shell.common.SyncTransactionQueue;
+
+import java.io.PrintWriter;
+
+/**
+ * Base class that handle common task org. related for split-screen stages.
+ * Note that this class and its sub-class do not directly perform hierarchy operations.
+ * They only serve to hold a collection of tasks and provide APIs like
+ * {@link #setBounds(Rect, WindowContainerTransaction)} for the centralized {@link StageCoordinator}
+ * to perform operations in-sync with other containers.
+ *
+ * @see StageCoordinator
+ */
+class StageTaskListener implements ShellTaskOrganizer.TaskListener {
+ private static final String TAG = StageTaskListener.class.getSimpleName();
+
+ protected static final int[] CONTROLLED_ACTIVITY_TYPES = {ACTIVITY_TYPE_STANDARD};
+ protected static final int[] CONTROLLED_WINDOWING_MODES =
+ {WINDOWING_MODE_FULLSCREEN, WINDOWING_MODE_UNDEFINED};
+ protected static final int[] CONTROLLED_WINDOWING_MODES_WHEN_ACTIVE =
+ {WINDOWING_MODE_FULLSCREEN, WINDOWING_MODE_UNDEFINED, WINDOWING_MODE_MULTI_WINDOW};
+
+ /** Callback interface for listening to changes in a split-screen stage. */
+ public interface StageListenerCallbacks {
+ void onRootTaskAppeared();
+
+ void onStatusChanged(boolean visible, boolean hasChildren);
+
+ void onChildTaskStatusChanged(int taskId, boolean present, boolean visible);
+
+ void onRootTaskVanished();
+ void onNoLongerSupportMultiWindow();
+ }
+
+ private final StageListenerCallbacks mCallbacks;
+ private final SurfaceSession mSurfaceSession;
+ protected final SyncTransactionQueue mSyncQueue;
+
+ protected ActivityManager.RunningTaskInfo mRootTaskInfo;
+ protected SurfaceControl mRootLeash;
+ protected SurfaceControl mDimLayer;
+ protected SparseArray<ActivityManager.RunningTaskInfo> mChildrenTaskInfo = new SparseArray<>();
+ private final SparseArray<SurfaceControl> mChildrenLeashes = new SparseArray<>();
+
+ private final StageTaskUnfoldController mStageTaskUnfoldController;
+
+ StageTaskListener(ShellTaskOrganizer taskOrganizer, int displayId,
+ StageListenerCallbacks callbacks, SyncTransactionQueue syncQueue,
+ SurfaceSession surfaceSession,
+ @Nullable StageTaskUnfoldController stageTaskUnfoldController) {
+ mCallbacks = callbacks;
+ mSyncQueue = syncQueue;
+ mSurfaceSession = surfaceSession;
+ mStageTaskUnfoldController = stageTaskUnfoldController;
+ taskOrganizer.createRootTask(displayId, WINDOWING_MODE_MULTI_WINDOW, this);
+ }
+
+ int getChildCount() {
+ return mChildrenTaskInfo.size();
+ }
+
+ boolean containsTask(int taskId) {
+ return mChildrenTaskInfo.contains(taskId);
+ }
+
+ /**
+ * Returns the top activity uid for the top child task.
+ */
+ int getTopChildTaskUid() {
+ for (int i = mChildrenTaskInfo.size() - 1; i >= 0; --i) {
+ final ActivityManager.RunningTaskInfo info = mChildrenTaskInfo.valueAt(i);
+ if (info.topActivityInfo == null) {
+ continue;
+ }
+ return info.topActivityInfo.applicationInfo.uid;
+ }
+ return 0;
+ }
+
+ /** @return {@code true} if this listener contains the currently focused task. */
+ boolean isFocused() {
+ if (mRootTaskInfo == null) {
+ return false;
+ }
+
+ if (mRootTaskInfo.isFocused) {
+ return true;
+ }
+
+ for (int i = mChildrenTaskInfo.size() - 1; i >= 0; --i) {
+ if (mChildrenTaskInfo.valueAt(i).isFocused) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ @Override
+ @CallSuper
+ public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) {
+ if (mRootTaskInfo == null && !taskInfo.hasParentTask()) {
+ mRootLeash = leash;
+ mRootTaskInfo = taskInfo;
+ mCallbacks.onRootTaskAppeared();
+ sendStatusChanged();
+ mSyncQueue.runInSync(t -> {
+ t.hide(mRootLeash);
+ mDimLayer =
+ SurfaceUtils.makeDimLayer(t, mRootLeash, "Dim layer", mSurfaceSession);
+ });
+ } else if (taskInfo.parentTaskId == mRootTaskInfo.taskId) {
+ final int taskId = taskInfo.taskId;
+ mChildrenLeashes.put(taskId, leash);
+ mChildrenTaskInfo.put(taskId, taskInfo);
+ updateChildTaskSurface(taskInfo, leash, true /* firstAppeared */);
+ mCallbacks.onChildTaskStatusChanged(taskId, true /* present */, taskInfo.isVisible);
+ if (ENABLE_SHELL_TRANSITIONS) {
+ // Status is managed/synchronized by the transition lifecycle.
+ return;
+ }
+ sendStatusChanged();
+ } else {
+ throw new IllegalArgumentException(this + "\n Unknown task: " + taskInfo
+ + "\n mRootTaskInfo: " + mRootTaskInfo);
+ }
+
+ if (mStageTaskUnfoldController != null) {
+ mStageTaskUnfoldController.onTaskAppeared(taskInfo, leash);
+ }
+ }
+
+ @Override
+ @CallSuper
+ public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) {
+ if (!taskInfo.supportsMultiWindow) {
+ // Leave split screen if the task no longer supports multi window.
+ mCallbacks.onNoLongerSupportMultiWindow();
+ return;
+ }
+ if (mRootTaskInfo.taskId == taskInfo.taskId) {
+ mRootTaskInfo = taskInfo;
+ } else if (taskInfo.parentTaskId == mRootTaskInfo.taskId) {
+ mChildrenTaskInfo.put(taskInfo.taskId, taskInfo);
+ mCallbacks.onChildTaskStatusChanged(taskInfo.taskId, true /* present */,
+ taskInfo.isVisible);
+ if (!ENABLE_SHELL_TRANSITIONS) {
+ updateChildTaskSurface(
+ taskInfo, mChildrenLeashes.get(taskInfo.taskId), false /* firstAppeared */);
+ }
+ } else {
+ throw new IllegalArgumentException(this + "\n Unknown task: " + taskInfo
+ + "\n mRootTaskInfo: " + mRootTaskInfo);
+ }
+ if (ENABLE_SHELL_TRANSITIONS) {
+ // Status is managed/synchronized by the transition lifecycle.
+ return;
+ }
+ sendStatusChanged();
+ }
+
+ @Override
+ @CallSuper
+ public void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo) {
+ final int taskId = taskInfo.taskId;
+ if (mRootTaskInfo.taskId == taskId) {
+ mCallbacks.onRootTaskVanished();
+ mSyncQueue.runInSync(t -> t.remove(mDimLayer));
+ mRootTaskInfo = null;
+ } else if (mChildrenTaskInfo.contains(taskId)) {
+ mChildrenTaskInfo.remove(taskId);
+ mChildrenLeashes.remove(taskId);
+ mCallbacks.onChildTaskStatusChanged(taskId, false /* present */, taskInfo.isVisible);
+ if (ENABLE_SHELL_TRANSITIONS) {
+ // Status is managed/synchronized by the transition lifecycle.
+ return;
+ }
+ sendStatusChanged();
+ } else {
+ throw new IllegalArgumentException(this + "\n Unknown task: " + taskInfo
+ + "\n mRootTaskInfo: " + mRootTaskInfo);
+ }
+
+ if (mStageTaskUnfoldController != null) {
+ mStageTaskUnfoldController.onTaskVanished(taskInfo);
+ }
+ }
+
+ @Override
+ public void attachChildSurfaceToTask(int taskId, SurfaceControl.Builder b) {
+ if (mRootTaskInfo.taskId == taskId) {
+ b.setParent(mRootLeash);
+ } else if (mChildrenLeashes.contains(taskId)) {
+ b.setParent(mChildrenLeashes.get(taskId));
+ } else {
+ throw new IllegalArgumentException("There is no surface for taskId=" + taskId);
+ }
+ }
+
+ void setBounds(Rect bounds, WindowContainerTransaction wct) {
+ wct.setBounds(mRootTaskInfo.token, bounds);
+ }
+
+ void reorderChild(int taskId, boolean onTop, WindowContainerTransaction wct) {
+ if (!containsTask(taskId)) {
+ return;
+ }
+ wct.reorder(mChildrenTaskInfo.get(taskId).token, onTop /* onTop */);
+ }
+
+ void setVisibility(boolean visible, WindowContainerTransaction wct) {
+ wct.reorder(mRootTaskInfo.token, visible /* onTop */);
+ }
+
+ void onSplitScreenListenerRegistered(SplitScreen.SplitScreenListener listener,
+ @SplitScreen.StageType int stage) {
+ for (int i = mChildrenTaskInfo.size() - 1; i >= 0; --i) {
+ int taskId = mChildrenTaskInfo.keyAt(i);
+ listener.onTaskStageChanged(taskId, stage,
+ mChildrenTaskInfo.get(taskId).isVisible);
+ }
+ }
+
+ private void updateChildTaskSurface(ActivityManager.RunningTaskInfo taskInfo,
+ SurfaceControl leash, boolean firstAppeared) {
+ final Point taskPositionInParent = taskInfo.positionInParent;
+ mSyncQueue.runInSync(t -> {
+ t.setWindowCrop(leash, null);
+ t.setPosition(leash, taskPositionInParent.x, taskPositionInParent.y);
+ if (firstAppeared && !ENABLE_SHELL_TRANSITIONS) {
+ t.setAlpha(leash, 1f);
+ t.setMatrix(leash, 1, 0, 0, 1);
+ t.show(leash);
+ }
+ });
+ }
+
+ private void sendStatusChanged() {
+ mCallbacks.onStatusChanged(mRootTaskInfo.isVisible, mChildrenTaskInfo.size() > 0);
+ }
+
+ @Override
+ @CallSuper
+ public void dump(@NonNull PrintWriter pw, String prefix) {
+ final String innerPrefix = prefix + " ";
+ final String childPrefix = innerPrefix + " ";
+ pw.println(prefix + this);
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/StageTaskUnfoldController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/StageTaskUnfoldController.java
new file mode 100644
index 000000000000..62b9da6d4715
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/StageTaskUnfoldController.java
@@ -0,0 +1,224 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.stagesplit;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import android.animation.RectEvaluator;
+import android.animation.TypeEvaluator;
+import android.annotation.NonNull;
+import android.app.ActivityManager;
+import android.content.Context;
+import android.graphics.Rect;
+import android.util.SparseArray;
+import android.view.InsetsSource;
+import android.view.InsetsState;
+import android.view.SurfaceControl;
+
+import com.android.internal.policy.ScreenDecorationsUtils;
+import com.android.wm.shell.common.DisplayInsetsController;
+import com.android.wm.shell.common.DisplayInsetsController.OnInsetsChangedListener;
+import com.android.wm.shell.common.TransactionPool;
+import com.android.wm.shell.unfold.ShellUnfoldProgressProvider;
+import com.android.wm.shell.unfold.ShellUnfoldProgressProvider.UnfoldListener;
+import com.android.wm.shell.unfold.UnfoldBackgroundController;
+
+import java.util.concurrent.Executor;
+
+/**
+ * Controls transformations of the split screen task surfaces in response
+ * to the unfolding/folding action on foldable devices
+ */
+public class StageTaskUnfoldController implements UnfoldListener, OnInsetsChangedListener {
+
+ private static final TypeEvaluator<Rect> RECT_EVALUATOR = new RectEvaluator(new Rect());
+ private static final float CROPPING_START_MARGIN_FRACTION = 0.05f;
+
+ private final SparseArray<AnimationContext> mAnimationContextByTaskId = new SparseArray<>();
+ private final ShellUnfoldProgressProvider mUnfoldProgressProvider;
+ private final DisplayInsetsController mDisplayInsetsController;
+ private final UnfoldBackgroundController mBackgroundController;
+ private final Executor mExecutor;
+ private final int mExpandedTaskBarHeight;
+ private final float mWindowCornerRadiusPx;
+ private final Rect mStageBounds = new Rect();
+ private final TransactionPool mTransactionPool;
+
+ private InsetsSource mTaskbarInsetsSource;
+ private boolean mBothStagesVisible;
+
+ public StageTaskUnfoldController(@NonNull Context context,
+ @NonNull TransactionPool transactionPool,
+ @NonNull ShellUnfoldProgressProvider unfoldProgressProvider,
+ @NonNull DisplayInsetsController displayInsetsController,
+ @NonNull UnfoldBackgroundController backgroundController,
+ @NonNull Executor executor) {
+ mUnfoldProgressProvider = unfoldProgressProvider;
+ mTransactionPool = transactionPool;
+ mExecutor = executor;
+ mBackgroundController = backgroundController;
+ mDisplayInsetsController = displayInsetsController;
+ mWindowCornerRadiusPx = ScreenDecorationsUtils.getWindowCornerRadius(context);
+ mExpandedTaskBarHeight = context.getResources().getDimensionPixelSize(
+ com.android.internal.R.dimen.taskbar_frame_height);
+ }
+
+ /**
+ * Initializes the controller, starts listening for the external events
+ */
+ public void init() {
+ mUnfoldProgressProvider.addListener(mExecutor, this);
+ mDisplayInsetsController.addInsetsChangedListener(DEFAULT_DISPLAY, this);
+ }
+
+ @Override
+ public void insetsChanged(InsetsState insetsState) {
+ mTaskbarInsetsSource = insetsState.getSource(InsetsState.ITYPE_EXTRA_NAVIGATION_BAR);
+ for (int i = mAnimationContextByTaskId.size() - 1; i >= 0; i--) {
+ AnimationContext context = mAnimationContextByTaskId.valueAt(i);
+ context.update();
+ }
+ }
+
+ /**
+ * Called when split screen task appeared
+ * @param taskInfo info for the appeared task
+ * @param leash surface leash for the appeared task
+ */
+ public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) {
+ AnimationContext context = new AnimationContext(leash);
+ mAnimationContextByTaskId.put(taskInfo.taskId, context);
+ }
+
+ /**
+ * Called when a split screen task vanished
+ * @param taskInfo info for the vanished task
+ */
+ public void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo) {
+ AnimationContext context = mAnimationContextByTaskId.get(taskInfo.taskId);
+ if (context != null) {
+ final SurfaceControl.Transaction transaction = mTransactionPool.acquire();
+ resetSurface(transaction, context);
+ transaction.apply();
+ mTransactionPool.release(transaction);
+ }
+ mAnimationContextByTaskId.remove(taskInfo.taskId);
+ }
+
+ @Override
+ public void onStateChangeProgress(float progress) {
+ if (mAnimationContextByTaskId.size() == 0 || !mBothStagesVisible) return;
+
+ final SurfaceControl.Transaction transaction = mTransactionPool.acquire();
+ mBackgroundController.ensureBackground(transaction);
+
+ for (int i = mAnimationContextByTaskId.size() - 1; i >= 0; i--) {
+ AnimationContext context = mAnimationContextByTaskId.valueAt(i);
+
+ context.mCurrentCropRect.set(RECT_EVALUATOR
+ .evaluate(progress, context.mStartCropRect, context.mEndCropRect));
+
+ transaction.setWindowCrop(context.mLeash, context.mCurrentCropRect)
+ .setCornerRadius(context.mLeash, mWindowCornerRadiusPx);
+ }
+
+ transaction.apply();
+
+ mTransactionPool.release(transaction);
+ }
+
+ @Override
+ public void onStateChangeFinished() {
+ resetTransformations();
+ }
+
+ /**
+ * Called when split screen visibility changes
+ * @param bothStagesVisible true if both stages of the split screen are visible
+ */
+ public void onSplitVisibilityChanged(boolean bothStagesVisible) {
+ mBothStagesVisible = bothStagesVisible;
+ if (!bothStagesVisible) {
+ resetTransformations();
+ }
+ }
+
+ /**
+ * Called when split screen stage bounds changed
+ * @param bounds new bounds for this stage
+ */
+ public void onLayoutChanged(Rect bounds) {
+ mStageBounds.set(bounds);
+
+ for (int i = mAnimationContextByTaskId.size() - 1; i >= 0; i--) {
+ final AnimationContext context = mAnimationContextByTaskId.valueAt(i);
+ context.update();
+ }
+ }
+
+ private void resetTransformations() {
+ final SurfaceControl.Transaction transaction = mTransactionPool.acquire();
+
+ for (int i = mAnimationContextByTaskId.size() - 1; i >= 0; i--) {
+ final AnimationContext context = mAnimationContextByTaskId.valueAt(i);
+ resetSurface(transaction, context);
+ }
+ mBackgroundController.removeBackground(transaction);
+ transaction.apply();
+
+ mTransactionPool.release(transaction);
+ }
+
+ private void resetSurface(SurfaceControl.Transaction transaction, AnimationContext context) {
+ transaction
+ .setWindowCrop(context.mLeash, null)
+ .setCornerRadius(context.mLeash, 0.0F);
+ }
+
+ private class AnimationContext {
+ final SurfaceControl mLeash;
+ final Rect mStartCropRect = new Rect();
+ final Rect mEndCropRect = new Rect();
+ final Rect mCurrentCropRect = new Rect();
+
+ private AnimationContext(SurfaceControl leash) {
+ this.mLeash = leash;
+ update();
+ }
+
+ private void update() {
+ mStartCropRect.set(mStageBounds);
+
+ if (mTaskbarInsetsSource != null) {
+ // Only insets the cropping window with taskbar when taskbar is expanded
+ if (mTaskbarInsetsSource.getFrame().height() >= mExpandedTaskBarHeight) {
+ mStartCropRect.inset(mTaskbarInsetsSource
+ .calculateVisibleInsets(mStartCropRect));
+ }
+ }
+
+ // Offset to surface coordinates as layout bounds are in screen coordinates
+ mStartCropRect.offsetTo(0, 0);
+
+ mEndCropRect.set(mStartCropRect);
+
+ int maxSize = Math.max(mEndCropRect.width(), mEndCropRect.height());
+ int margin = (int) (maxSize * CROPPING_START_MARGIN_FRACTION);
+ mStartCropRect.inset(margin, margin, margin, margin);
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenIconDrawableFactory.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenIconDrawableFactory.java
index f0685a81d25f..38122ffc032b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenIconDrawableFactory.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenIconDrawableFactory.java
@@ -38,6 +38,7 @@ import android.graphics.drawable.Animatable;
import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.os.Trace;
+import android.util.Log;
import android.util.PathParser;
import android.window.SplashScreenView;
@@ -50,6 +51,8 @@ import com.android.internal.R;
*/
public class SplashscreenIconDrawableFactory {
+ private static final String TAG = "SplashscreenIconDrawableFactory";
+
/**
* @return An array containing the foreground drawable at index 0 and if needed a background
* drawable at index 1.
@@ -282,7 +285,12 @@ public class SplashscreenIconDrawableFactory {
if (startListener != null) {
startListener.run();
}
- mAnimatableIcon.start();
+ try {
+ mAnimatableIcon.start();
+ } catch (Exception ex) {
+ Log.e(TAG, "Error while running the splash screen animated icon", ex);
+ animation.cancel();
+ }
}
@Override
diff --git a/media/java/android/media/AudioTrack.java b/media/java/android/media/AudioTrack.java
index aa72d965f793..476a9a58ef4b 100644
--- a/media/java/android/media/AudioTrack.java
+++ b/media/java/android/media/AudioTrack.java
@@ -1805,9 +1805,15 @@ public class AudioTrack extends PlayerBase
return false;
}
final int channelCount = AudioFormat.channelCountFromOutChannelMask(channelConfig);
- final int channelCountLimit = AudioFormat.isEncodingLinearFrames(encoding)
- ? AudioSystem.OUT_CHANNEL_COUNT_MAX // PCM limited to OUT_CHANNEL_COUNT_MAX
- : AudioSystem.FCC_24; // Compressed limited to 24 channels
+ final int channelCountLimit;
+ try {
+ channelCountLimit = AudioFormat.isEncodingLinearFrames(encoding)
+ ? AudioSystem.OUT_CHANNEL_COUNT_MAX // PCM limited to OUT_CHANNEL_COUNT_MAX
+ : AudioSystem.FCC_24; // Compressed limited to 24 channels
+ } catch (IllegalArgumentException iae) {
+ loge("Unsupported encoding " + iae);
+ return false;
+ }
if (channelCount > channelCountLimit) {
loge("Channel configuration contains too many channels for encoding "
+ encoding + "(" + channelCount + " > " + channelCountLimit + ")");
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index 6d52b664cee9..abd067cfe2f3 100755
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -38,6 +38,7 @@ import android.media.IStrategyPreferredDevicesDispatcher;
import android.media.ISpatializerCallback;
import android.media.ISpatializerHeadTrackingModeCallback;
import android.media.ISpatializerHeadToSoundStagePoseCallback;
+import android.media.ISpatializerOutputCallback;
import android.media.IVolumeController;
import android.media.IVolumeController;
import android.media.PlayerBase;
@@ -440,4 +441,10 @@ interface IAudioService {
void setSpatializerParameter(int key, in byte[] value);
void getSpatializerParameter(int key, inout byte[] value);
+
+ int getSpatializerOutput();
+
+ void registerSpatializerOutputCallback(in ISpatializerOutputCallback cb);
+
+ void unregisterSpatializerOutputCallback(in ISpatializerOutputCallback cb);
}
diff --git a/media/java/android/media/ISpatializerOutputCallback.aidl b/media/java/android/media/ISpatializerOutputCallback.aidl
new file mode 100644
index 000000000000..57572a81a366
--- /dev/null
+++ b/media/java/android/media/ISpatializerOutputCallback.aidl
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media;
+
+/**
+ * AIDL for the AudioService to signal Spatializer output changes.
+ *
+ * {@hide}
+ */
+oneway interface ISpatializerOutputCallback {
+
+ void dispatchSpatializerOutputChanged(int output);
+}
diff --git a/media/java/android/media/Spatializer.java b/media/java/android/media/Spatializer.java
index 8b1624b5eb16..e6fff392c264 100644
--- a/media/java/android/media/Spatializer.java
+++ b/media/java/android/media/Spatializer.java
@@ -18,6 +18,7 @@ package android.media;
import android.annotation.CallbackExecutor;
import android.annotation.IntDef;
+import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
@@ -63,7 +64,9 @@ public class Spatializer {
/**
* Returns whether spatialization is enabled or not.
* A false value can originate for instance from the user electing to
- * disable the feature.<br>
+ * disable the feature, or when the feature is not supported on the device (indicated
+ * by {@link #getImmersiveAudioLevel()} returning {@link #SPATIALIZER_IMMERSIVE_LEVEL_NONE}).
+ * <br>
* Note that this state reflects a platform-wide state of the "desire" to use spatialization,
* but availability of the audio processing is still dictated by the compatibility between
* the effect and the hardware configuration, as indicated by {@link #isAvailable()}.
@@ -85,7 +88,10 @@ public class Spatializer {
* incompatible with sound spatialization, such as playback on a monophonic speaker.<br>
* Note that spatialization can be available, but disabled by the user, in which case this
* method would still return {@code true}, whereas {@link #isEnabled()}
- * would return {@code false}.
+ * would return {@code false}.<br>
+ * Also when the feature is not supported on the device (indicated
+ * by {@link #getImmersiveAudioLevel()} returning {@link #SPATIALIZER_IMMERSIVE_LEVEL_NONE}),
+ * the return value will be false.
* @return {@code true} if the spatializer effect is available and capable
* of processing the audio for the current configuration of the device,
* {@code false} otherwise.
@@ -293,6 +299,24 @@ public class Spatializer {
@HeadTrackingModeSet int mode);
}
+
+ /**
+ * @hide
+ * An interface to be notified of changes to the output stream used by the spatializer
+ * effect.
+ * @see #getOutput()
+ */
+ @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
+ public interface OnSpatializerOutputChangedListener {
+ /**
+ * Called when the id of the output stream of the spatializer effect changed.
+ * @param spatializer the {@code Spatializer} instance whose output is updated
+ * @param output the id of the output stream, or 0 when there is no spatializer output
+ */
+ void onSpatializerOutputChanged(@NonNull Spatializer spatializer,
+ @IntRange(from = 0) int output);
+ }
+
/**
* @hide
* An interface to be notified of updates to the head to soundstage pose, as represented by the
@@ -839,6 +863,73 @@ public class Spatializer {
}
}
+ /**
+ * @hide
+ * Returns the id of the output stream used for the spatializer effect playback
+ * @return id of the output stream, or 0 if no spatializer playback is active
+ */
+ @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
+ @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
+ public @IntRange(from = 0) int getOutput() {
+ try {
+ return mAm.getService().getSpatializerOutput();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error calling getSpatializerOutput", e);
+ return 0;
+ }
+ }
+
+ /**
+ * @hide
+ * Sets the listener to receive spatializer effect output updates
+ * @param executor the {@code Executor} handling the callbacks
+ * @param listener the listener to register
+ * @see #clearOnSpatializerOutputChangedListener()
+ * @see #getOutput()
+ */
+ @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
+ @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
+ public void setOnSpatializerOutputChangedListener(
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull OnSpatializerOutputChangedListener listener) {
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(listener);
+ synchronized (mOutputListenerLock) {
+ if (mOutputListener != null) {
+ throw new IllegalStateException("Trying to overwrite existing listener");
+ }
+ mOutputListener =
+ new ListenerInfo<OnSpatializerOutputChangedListener>(listener, executor);
+ mOutputDispatcher = new SpatializerOutputDispatcherStub();
+ try {
+ mAm.getService().registerSpatializerOutputCallback(mOutputDispatcher);
+ } catch (RemoteException e) {
+ mOutputListener = null;
+ mOutputDispatcher = null;
+ }
+ }
+ }
+
+ /**
+ * @hide
+ * Clears the listener for spatializer effect output updates
+ * @see #setOnSpatializerOutputChangedListener(Executor, OnSpatializerOutputChangedListener)
+ */
+ @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
+ @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
+ public void clearOnSpatializerOutputChangedListener() {
+ synchronized (mOutputListenerLock) {
+ if (mOutputDispatcher == null) {
+ throw (new IllegalStateException("No listener to clear"));
+ }
+ try {
+ mAm.getService().unregisterSpatializerOutputCallback(mOutputDispatcher);
+ } catch (RemoteException e) { }
+ mOutputListener = null;
+ mOutputDispatcher = null;
+ }
+ }
+
//-----------------------------------------------------------------------------
// callback helper definitions
@@ -964,4 +1055,35 @@ public class Spatializer {
}
}
}
+
+ //-----------------------------------------------------------------------------
+ // output callback management and stub
+ private final Object mOutputListenerLock = new Object();
+ /**
+ * Listener for output updates
+ */
+ @GuardedBy("mOutputListenerLock")
+ private @Nullable ListenerInfo<OnSpatializerOutputChangedListener> mOutputListener;
+ @GuardedBy("mOutputListenerLock")
+ private @Nullable SpatializerOutputDispatcherStub mOutputDispatcher;
+
+ private final class SpatializerOutputDispatcherStub
+ extends ISpatializerOutputCallback.Stub {
+
+ @Override
+ public void dispatchSpatializerOutputChanged(int output) {
+ // make a copy of ref to listener so callback is not executed under lock
+ final ListenerInfo<OnSpatializerOutputChangedListener> listener;
+ synchronized (mOutputListenerLock) {
+ listener = mOutputListener;
+ }
+ if (listener == null) {
+ return;
+ }
+ try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
+ listener.mExecutor.execute(() -> listener.mListener
+ .onSpatializerOutputChanged(Spatializer.this, output));
+ }
+ }
+ }
}
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 6671308ef66a..c6cca5adaeb0 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -50,6 +50,16 @@ java_library {
srcs: ["src/com/android/systemui/EventLogTags.logtags"],
}
+filegroup {
+ name: "ReleaseJavaFiles",
+ srcs: ["src/com/android/systemui/flags/FeatureFlagManager.java"],
+}
+
+filegroup {
+ name: "DebugJavaFiles",
+ srcs: ["src-debug/com/android/systemui/flags/FeatureFlagManager.java"],
+}
+
android_library {
name: "SystemUI-core",
srcs: [
@@ -57,6 +67,12 @@ android_library {
"src/**/*.java",
"src/**/I*.aidl",
],
+ product_variables: {
+ debuggable: {
+ srcs: [":DebugJavaFiles"],
+ exclude_srcs: [":ReleaseJavaFiles"],
+ },
+ },
resource_dirs: [
"res-product",
"res-keyguard",
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/Interpolators.java b/packages/SystemUI/animation/src/com/android/systemui/animation/Interpolators.java
index 27658824933a..a3d924fbad60 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/Interpolators.java
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/Interpolators.java
@@ -200,10 +200,10 @@ public class Interpolators {
/**
* Interpolate alpha for notifications background scrim during shade expansion.
* @param fraction Shade expansion fraction
- * @param forNotification If we want the alpha of the notification shade or the scrim.
+ * @param forUiContent If we want the alpha of the scrims, or ui that's on top of them.
*/
- public static float getNotificationScrimAlpha(float fraction, boolean forNotification) {
- if (forNotification) {
+ public static float getNotificationScrimAlpha(float fraction, boolean forUiContent) {
+ if (forUiContent) {
fraction = MathUtils.constrainedMap(0f, 1f, 0.3f, 1f, fraction);
} else {
fraction = MathUtils.constrainedMap(0f, 1f, 0f, 0.5f, fraction);
diff --git a/packages/SystemUI/docs/keyguard.md b/packages/SystemUI/docs/keyguard.md
index e3d48aee96e4..5e7bc1c8cad2 100644
--- a/packages/SystemUI/docs/keyguard.md
+++ b/packages/SystemUI/docs/keyguard.md
@@ -8,5 +8,39 @@ Keyguard is responsible for:
Keyguard is the first screen available when turning on the device, as long as the user has not specified a security method of NONE.
+## Critical User Journeys
+
+The journeys below generally refer to Keyguard's portion of the overall flow, especially regarding use of the power button. Power button key interpretation (short press, long press, very long press, multi press) is done in [PhoneWindowManager][4], with calls to [PowerManagerService][2] to sleep or wake up, if needed.
+
+### Power On - AOD enabled or disabled
+
+Begins with the device in low power mode, with the display active for [AOD][3] or inactive. [PowerManagerService][2] can be directed to wake up on various user-configurable signals, such as lift to wake, screen taps, among others. [AOD][2], whether visibly enabled or not, handles these signals to transition AOD to full Lockscreen content. See more in [AOD][3].
+
+### Power Off
+
+An indication to power off the device most likely comes from one of two signals: the user presses the power button or the screen timeout has passed. This may [lock the device](#How-the-device-locks)
+
+#### On Lockscreen
+
+#### On Lockscreen, occluded by an activity
+
+#### Device unlocked, Keyguard has gone away
+
+### Pulsing (Incoming notifications while dozing)
+
+### How the device locks
+
+More coming
+* Screen timeout
+* Smart lock
+* Device policy
+* Power button instantly locks setting
+* Lock timeout after screen timeout setting
+
+
[1]: /frameworks/base/packages/SystemUI/docs/keyguard/bouncer.md
+[2]: /frameworks/base/services/core/java/com/android/server/power/PowerManagerService.java
+[3]: /frameworks/base/packages/SystemUI/docs/keyguard/aod.md
+[4]: /frameworks/base/services/core/java/com/android/server/policy/PhoneWindowManager.java
+
diff --git a/packages/SystemUI/docs/keyguard/aod.md b/packages/SystemUI/docs/keyguard/aod.md
new file mode 100644
index 000000000000..6d76ed55174a
--- /dev/null
+++ b/packages/SystemUI/docs/keyguard/aod.md
@@ -0,0 +1 @@
+# Always-on Display (AOD)
diff --git a/packages/SystemUI/docs/keyguard/bouncer.md b/packages/SystemUI/docs/keyguard/bouncer.md
index a724966a639a..51f851608a34 100644
--- a/packages/SystemUI/docs/keyguard/bouncer.md
+++ b/packages/SystemUI/docs/keyguard/bouncer.md
@@ -7,9 +7,9 @@
The bouncer contains a hierarchy of controllers/views to render the user's security method and to manage the authentication attempts.
1. [KeyguardBouncer][1] - Entrypoint for managing the bouncer visibility.
- 1. [KeyguardHostViewController][2] - Intercepts media keys. Can most likely be merged with the next item.
- 1. [KeyguardSecurityContainerController][3] - Manages unlock attempt responses, one-handed use
- 1. [KeyguardSecurityViewFlipperController][4] - Based upon the [KeyguardSecurityModel#SecurityMode][5], will instantiate the required view and controller. PIN, Pattern, etc.
+ 1. [KeyguardHostViewController][2] - Intercepts media keys. Can most likely be merged with the next item.
+ 1. [KeyguardSecurityContainerController][3] - Manages unlock attempt responses, one-handed use
+ 1. [KeyguardSecurityViewFlipperController][4] - Based upon the [KeyguardSecurityModel#SecurityMode][5], will instantiate the required view and controller. PIN, Pattern, etc.
[1]: /frameworks/base/packages/SystemUI/com/android/systemui/statusbar/phone/KeyguardBouncer
[2]: /frameworks/base/packages/SystemUI/com/android/keyguard/KeyguardHostViewController
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/flags/Flags.java b/packages/SystemUI/plugin/src/com/android/systemui/flags/Flags.java
index d5b9243ebe86..e983818ef040 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/flags/Flags.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/flags/Flags.java
@@ -18,6 +18,12 @@ package com.android.systemui.flags;
/**
* List of {@link Flag} objects for use in SystemUI.
+ *
+ * Flag Ids are integers. They must be unique.
+ *
+ * On public release builds, flags will always return their default value. There is no way to
+ * change their value on release builds.
*/
public class Flags {
+ public static final BooleanFlag THE_FIRST_FLAG = new BooleanFlag(1, false);
}
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java
index a03d84956ddc..757dc2ec807d 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java
@@ -52,7 +52,16 @@ public interface QS extends FragmentBase {
boolean isShowingDetail();
void closeDetail();
void animateHeaderSlidingOut();
- void setQsExpansion(float qsExpansionFraction, float headerTranslation);
+
+ /**
+ * Asks QS to update its presentation, according to {@code NotificationPanelViewController}.
+ *
+ * @param qsExpansionFraction How much each UI element in QS should be expanded (QQS to QS.)
+ * @param panelExpansionFraction Whats the expansion of the whole shade.
+ * @param headerTranslation How much we should vertically translate QS.
+ */
+ void setQsExpansion(float qsExpansionFraction, float panelExpansionFraction,
+ float headerTranslation);
void setHeaderListening(boolean listening);
void notifyCustomizeChanged();
void setContainerController(QSContainerController controller);
@@ -75,13 +84,13 @@ public interface QS extends FragmentBase {
/**
* If QS should translate as we pull it down, or if it should be static.
*/
- void setTranslateWhileExpanding(boolean shouldTranslate);
+ void setInSplitShade(boolean shouldTranslate);
/**
* Set the amount of pixels we have currently dragged down if we're transitioning to the full
* shade. 0.0f means we're not transitioning yet.
*/
- default void setTransitionToFullShadeAmount(float pxAmount, boolean animated) {}
+ default void setTransitionToFullShadeAmount(float pxAmount, float progress) {}
/**
* A rounded corner clipping that makes QS feel as if it were behind everything.
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml b/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml
index 28c61663bd4d..87a9825af1cb 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml
@@ -51,7 +51,7 @@
android:id="@+id/lockscreen_clock_view_large"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_below="@id/keyguard_status_area"
+ android:layout_below="@id/keyguard_slice_view"
android:visibility="gone">
<com.android.keyguard.AnimatableClockView
android:id="@+id/animatable_clock_view_large"
@@ -68,19 +68,28 @@
lockScreenWeight="400"
/>
</FrameLayout>
- <include layout="@layout/keyguard_status_area"
+
+ <!-- Not quite optimal but needed to translate these items as a group. The
+ NotificationIconContainer has its own logic for translation. -->
+ <LinearLayout
android:id="@+id/keyguard_status_area"
+ android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
- android:layout_below="@id/lockscreen_clock_view" />
+ android:layout_below="@id/lockscreen_clock_view">
- <com.android.systemui.statusbar.phone.NotificationIconContainer
- android:id="@+id/left_aligned_notification_icon_container"
- android:layout_width="match_parent"
- android:layout_height="@dimen/notification_shelf_height"
- android:layout_below="@id/keyguard_status_area"
- android:paddingStart="@dimen/below_clock_padding_start_icons"
- android:visibility="invisible"
- />
+ <include layout="@layout/keyguard_slice_view"
+ android:id="@+id/keyguard_slice_view"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content" />
+
+ <com.android.systemui.statusbar.phone.NotificationIconContainer
+ android:id="@+id/left_aligned_notification_icon_container"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/notification_shelf_height"
+ android:paddingStart="@dimen/below_clock_padding_start_icons"
+ android:visibility="invisible"
+ />
+ </LinearLayout>
</com.android.keyguard.KeyguardClockSwitch>
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_status_area.xml b/packages/SystemUI/res-keyguard/layout/keyguard_slice_view.xml
index 95eb5c1d5f1a..1863d1112947 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_status_area.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_slice_view.xml
@@ -22,11 +22,10 @@
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_gravity="center_horizontal"
+ android:layout_gravity="start"
android:clipToPadding="false"
android:orientation="vertical"
- android:paddingStart="@dimen/below_clock_padding_start"
- android:layout_centerHorizontal="true">
+ android:paddingStart="@dimen/below_clock_padding_start">
<TextView
android:id="@+id/title"
android:layout_width="match_parent"
@@ -42,6 +41,6 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
- android:gravity="center"
+ android:gravity="start"
/>
</com.android.keyguard.KeyguardSliceView>
diff --git a/packages/SystemUI/res/drawable/media_output_dialog_button_background.xml b/packages/SystemUI/res/drawable/media_output_dialog_button_background.xml
new file mode 100644
index 000000000000..363a022efdac
--- /dev/null
+++ b/packages/SystemUI/res/drawable/media_output_dialog_button_background.xml
@@ -0,0 +1,29 @@
+<!--
+ ~ Copyright (C) 2021 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ android:shape="rectangle">
+ <stroke
+ android:color="?androidprv:attr/colorAccentPrimaryVariant"
+ android:width="1dp"/>
+ <corners android:radius="20dp"/>
+ <padding
+ android:left="16dp"
+ android:right="16dp"
+ android:top="8dp"
+ android:bottom="8dp" />
+ <solid android:color="@android:color/transparent" />
+</shape>
diff --git a/packages/SystemUI/res/layout/media_output_dialog.xml b/packages/SystemUI/res/layout/media_output_dialog.xml
index b33889469f48..cd6bc11bacf3 100644
--- a/packages/SystemUI/res/layout/media_output_dialog.xml
+++ b/packages/SystemUI/res/layout/media_output_dialog.xml
@@ -24,41 +24,44 @@
<LinearLayout
android:layout_width="match_parent"
- android:layout_height="94dp"
+ android:layout_height="96dp"
android:gravity="start|center_vertical"
android:paddingStart="16dp"
android:orientation="horizontal">
<ImageView
android:id="@+id/header_icon"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:paddingEnd="@dimen/media_output_dialog_header_icon_padding"
+ android:layout_width="48dp"
+ android:layout_height="48dp"
android:importantForAccessibility="no"/>
<LinearLayout
android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginEnd="16dp"
+ android:layout_height="match_parent"
+ android:paddingStart="16dp"
+ android:paddingTop="20dp"
+ android:paddingBottom="24dp"
+ android:paddingEnd="24dp"
android:orientation="vertical">
<TextView
android:id="@+id/header_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="end"
+ android:gravity="center_vertical"
android:maxLines="1"
android:textColor="?android:attr/textColorPrimary"
android:fontFamily="@*android:string/config_headlineFontFamilyMedium"
android:textSize="20sp"/>
-
<TextView
android:id="@+id/header_subtitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
+ android:gravity="center_vertical"
android:ellipsize="end"
android:maxLines="1"
+ android:textColor="?android:attr/textColorTertiary"
android:fontFamily="roboto-regular"
- android:textSize="14sp"/>
-
+ android:textSize="16sp"/>
</LinearLayout>
</LinearLayout>
@@ -81,21 +84,21 @@
android:overScrollMode="never"/>
</LinearLayout>
- <View
- android:layout_width="match_parent"
- android:layout_height="1dp"
- android:background="?android:attr/listDivider"/>
-
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
+ android:layout_marginTop="16dp"
+ android:layout_marginStart="24dp"
+ android:layout_marginBottom="18dp"
+ android:layout_marginEnd="24dp"
android:orientation="horizontal">
<Button
android:id="@+id/stop"
- style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored"
+ style="@style/MediaOutputRoundedOutlinedButton"
android:layout_width="wrap_content"
- android:layout_height="64dp"
+ android:layout_height="36dp"
+ android:minWidth="0dp"
android:text="@string/keyboard_key_media_stop"
android:visibility="gone"/>
@@ -106,10 +109,10 @@
<Button
android:id="@+id/done"
- style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored"
+ style="@style/MediaOutputRoundedOutlinedButton"
android:layout_width="wrap_content"
- android:layout_height="64dp"
- android:layout_marginEnd="0dp"
+ android:layout_height="36dp"
+ android:minWidth="0dp"
android:text="@string/inline_done_button"/>
</LinearLayout>
</LinearLayout> \ No newline at end of file
diff --git a/packages/SystemUI/res/layout/media_output_list_item.xml b/packages/SystemUI/res/layout/media_output_list_item.xml
index 16c03e124794..a5a7efa24f47 100644
--- a/packages/SystemUI/res/layout/media_output_list_item.xml
+++ b/packages/SystemUI/res/layout/media_output_list_item.xml
@@ -23,17 +23,20 @@
android:orientation="vertical">
<FrameLayout
android:layout_width="match_parent"
- android:layout_height="64dp">
+ android:layout_height="88dp"
+ android:paddingTop="24dp"
+ android:paddingBottom="16dp"
+ android:paddingStart="24dp"
+ android:paddingEnd="8dp">
<FrameLayout
- android:layout_width="36dp"
- android:layout_height="36dp"
- android:layout_gravity="center_vertical|start"
- android:layout_marginStart="16dp">
+ android:layout_width="48dp"
+ android:layout_height="48dp"
+ android:layout_gravity="center_vertical|start">
<ImageView
android:id="@+id/title_icon"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
+ android:layout_width="48dp"
+ android:layout_height="48dp"
android:layout_gravity="center"/>
</FrameLayout>
@@ -42,49 +45,69 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical|start"
- android:layout_marginStart="68dp"
+ android:layout_marginStart="64dp"
android:ellipsize="end"
android:maxLines="1"
android:textColor="?android:attr/textColorPrimary"
- android:textSize="14sp"/>
+ android:textSize="16sp"/>
<RelativeLayout
android:id="@+id/two_line_layout"
android:layout_width="wrap_content"
android:layout_height="48dp"
- android:layout_marginStart="52dp"
- android:layout_marginEnd="69dp"
- android:layout_marginTop="10dp">
+ android:layout_marginStart="48dp">
<TextView
android:id="@+id/two_line_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
- android:layout_marginEnd="15dp"
+ android:layout_marginEnd="48dp"
android:ellipsize="end"
android:maxLines="1"
android:textColor="?android:attr/textColorPrimary"
- android:textSize="14sp"/>
+ android:textSize="16sp"/>
<TextView
android:id="@+id/subtitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="15dp"
- android:layout_marginBottom="7dp"
+ android:layout_marginTop="4dp"
android:layout_alignParentBottom="true"
android:ellipsize="end"
android:maxLines="1"
android:textColor="?android:attr/textColorSecondary"
- android:textSize="12sp"
+ android:textSize="14sp"
android:fontFamily="roboto-regular"
android:visibility="gone"/>
<SeekBar
android:id="@+id/volume_seekbar"
+ android:layout_marginTop="16dp"
+ android:layout_marginEnd="8dp"
style="@*android:style/Widget.DeviceDefault.SeekBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"/>
+ <ImageView
+ android:id="@+id/add_icon"
+ android:layout_width="24dp"
+ android:layout_height="24dp"
+ android:layout_gravity="right"
+ android:layout_marginEnd="24dp"
+ android:layout_alignParentRight="true"
+ android:src="@drawable/ic_add"
+ android:tint="?android:attr/colorAccent"
+ />
+ <CheckBox
+ android:id="@+id/check_box"
+ android:layout_width="24dp"
+ android:layout_height="24dp"
+ android:layout_gravity="right"
+ android:layout_marginEnd="24dp"
+ android:layout_alignParentRight="true"
+ android:button="@drawable/ic_check_box"
+ android:visibility="gone"
+ />
</RelativeLayout>
<ProgressBar
@@ -92,47 +115,17 @@
style="@*android:style/Widget.Material.ProgressBar.Horizontal"
android:layout_width="258dp"
android:layout_height="18dp"
- android:layout_marginStart="68dp"
- android:layout_marginTop="40dp"
+ android:layout_marginStart="64dp"
+ android:layout_marginTop="28dp"
android:indeterminate="true"
android:indeterminateOnly="true"
android:visibility="gone"/>
-
- <View
- android:id="@+id/end_divider"
- android:layout_width="1dp"
- android:layout_height="36dp"
- android:layout_marginEnd="68dp"
- android:layout_gravity="right|center_vertical"
- android:background="?android:attr/listDivider"
- android:visibility="gone"/>
-
- <ImageView
- android:id="@+id/add_icon"
- android:layout_width="24dp"
- android:layout_height="24dp"
- android:layout_gravity="right|center_vertical"
- android:layout_marginEnd="24dp"
- android:src="@drawable/ic_add"
- android:tint="?android:attr/colorAccent"
- android:visibility="gone"/>
-
- <CheckBox
- android:id="@+id/check_box"
- android:layout_width="24dp"
- android:layout_height="24dp"
- android:layout_gravity="right|center_vertical"
- android:layout_marginEnd="24dp"
- android:button="@drawable/ic_check_box"
- android:visibility="gone"/>
</FrameLayout>
<View
android:id="@+id/bottom_divider"
android:layout_width="match_parent"
android:layout_height="1dp"
- android:layout_marginTop="12dp"
- android:layout_marginBottom="12dp"
android:layout_gravity="bottom"
android:background="?android:attr/listDivider"
android:visibility="gone"/>
diff --git a/packages/SystemUI/res/layout/qs_user_dialog_content.xml b/packages/SystemUI/res/layout/qs_user_dialog_content.xml
index 321fe68e2f13..543b7d77243b 100644
--- a/packages/SystemUI/res/layout/qs_user_dialog_content.xml
+++ b/packages/SystemUI/res/layout/qs_user_dialog_content.xml
@@ -16,74 +16,78 @@
~ limitations under the License.
-->
-<androidx.constraintlayout.widget.ConstraintLayout
+<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:sysui="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:padding="24dp"
- android:layout_marginStart="16dp"
- android:layout_marginEnd="16dp"
- android:background="@drawable/qs_dialog_bg"
->
- <TextView
- android:id="@+id/title"
+ android:layout_height="wrap_content">
+ <androidx.constraintlayout.widget.ConstraintLayout
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_width="0dp"
- android:textAlignment="center"
- android:text="@string/qs_user_switch_dialog_title"
- android:textAppearance="@style/TextAppearance.QSDialog.Title"
- android:layout_marginBottom="32dp"
- sysui:layout_constraintTop_toTopOf="parent"
- sysui:layout_constraintStart_toStartOf="parent"
- sysui:layout_constraintEnd_toEndOf="parent"
- sysui:layout_constraintBottom_toTopOf="@id/grid"
+ android:padding="24dp"
+ android:layout_marginStart="16dp"
+ android:layout_marginEnd="16dp"
+ >
+ <TextView
+ android:id="@+id/title"
+ android:layout_height="wrap_content"
+ android:layout_width="0dp"
+ android:textAlignment="center"
+ android:text="@string/qs_user_switch_dialog_title"
+ android:textAppearance="@style/TextAppearance.QSDialog.Title"
+ android:layout_marginBottom="32dp"
+ sysui:layout_constraintTop_toTopOf="parent"
+ sysui:layout_constraintStart_toStartOf="parent"
+ sysui:layout_constraintEnd_toEndOf="parent"
+ sysui:layout_constraintBottom_toTopOf="@id/grid"
+ />
+
+ <com.android.systemui.qs.PseudoGridView
+ android:id="@+id/grid"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="28dp"
+ sysui:verticalSpacing="4dp"
+ sysui:horizontalSpacing="4dp"
+ sysui:fixedChildWidth="80dp"
+ sysui:layout_constraintTop_toBottomOf="@id/title"
+ sysui:layout_constraintStart_toStartOf="parent"
+ sysui:layout_constraintEnd_toEndOf="parent"
+ sysui:layout_constraintBottom_toTopOf="@id/barrier"
/>
- <com.android.systemui.qs.PseudoGridView
- android:id="@+id/grid"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginBottom="28dp"
- sysui:verticalSpacing="4dp"
- sysui:horizontalSpacing="4dp"
- sysui:fixedChildWidth="80dp"
- sysui:layout_constraintTop_toBottomOf="@id/title"
- sysui:layout_constraintStart_toStartOf="parent"
- sysui:layout_constraintEnd_toEndOf="parent"
- sysui:layout_constraintBottom_toTopOf="@id/barrier"
- />
+ <androidx.constraintlayout.widget.Barrier
+ android:id="@+id/barrier"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ sysui:barrierDirection="top"
+ sysui:constraint_referenced_ids="settings,done"
+ />
- <androidx.constraintlayout.widget.Barrier
- android:id="@+id/barrier"
- android:layout_height="wrap_content"
- android:layout_width="wrap_content"
- sysui:barrierDirection="top"
- sysui:constraint_referenced_ids="settings,done"
- />
+ <Button
+ android:id="@+id/settings"
+ android:layout_width="wrap_content"
+ android:layout_height="48dp"
+ android:text="@string/quick_settings_more_user_settings"
+ sysui:layout_constraintTop_toBottomOf="@id/barrier"
+ sysui:layout_constraintBottom_toBottomOf="parent"
+ sysui:layout_constraintStart_toStartOf="parent"
+ sysui:layout_constraintEnd_toStartOf="@id/done"
+ sysui:layout_constraintHorizontal_chainStyle="spread_inside"
+ style="@style/Widget.QSDialog.Button.BorderButton"
+ />
- <Button
- android:id="@+id/settings"
- android:layout_width="wrap_content"
- android:layout_height="48dp"
- android:text="@string/quick_settings_more_user_settings"
- sysui:layout_constraintTop_toBottomOf="@id/barrier"
- sysui:layout_constraintBottom_toBottomOf="parent"
- sysui:layout_constraintStart_toStartOf="parent"
- sysui:layout_constraintEnd_toStartOf="@id/done"
- sysui:layout_constraintHorizontal_chainStyle="spread_inside"
- style="@style/Widget.QSDialog.Button.BorderButton"
- />
+ <Button
+ android:id="@+id/done"
+ android:layout_width="wrap_content"
+ android:layout_height="48dp"
+ android:text="@string/quick_settings_done"
+ sysui:layout_constraintTop_toBottomOf="@id/barrier"
+ sysui:layout_constraintBottom_toBottomOf="parent"
+ sysui:layout_constraintStart_toEndOf="@id/settings"
+ sysui:layout_constraintEnd_toEndOf="parent"
+ style="@style/Widget.QSDialog.Button"
+ />
- <Button
- android:id="@+id/done"
- android:layout_width="wrap_content"
- android:layout_height="48dp"
- android:text="@string/quick_settings_done"
- sysui:layout_constraintTop_toBottomOf="@id/barrier"
- sysui:layout_constraintBottom_toBottomOf="parent"
- sysui:layout_constraintStart_toEndOf="@id/settings"
- sysui:layout_constraintEnd_toEndOf="parent"
- style="@style/Widget.QSDialog.Button"
- />
-</androidx.constraintlayout.widget.ConstraintLayout> \ No newline at end of file
+ </androidx.constraintlayout.widget.ConstraintLayout>
+</FrameLayout> \ No newline at end of file
diff --git a/packages/SystemUI/res/layout/status_bar_expanded.xml b/packages/SystemUI/res/layout/status_bar_expanded.xml
index 18315f1dff42..cc1af873ce2b 100644
--- a/packages/SystemUI/res/layout/status_bar_expanded.xml
+++ b/packages/SystemUI/res/layout/status_bar_expanded.xml
@@ -65,18 +65,6 @@
android:layout_gravity="center"
android:scaleType="centerCrop"/>
- <!-- Fingerprint -->
- <!-- AOD dashed fingerprint icon with moving dashes -->
- <com.airbnb.lottie.LottieAnimationView
- android:id="@+id/lock_udfps_aod_fp"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:padding="@dimen/lock_icon_padding"
- android:layout_gravity="center"
- android:scaleType="centerCrop"
- systemui:lottie_autoPlay="false"
- systemui:lottie_loop="true"
- systemui:lottie_rawRes="@raw/udfps_aod_fp"/>
</com.android.keyguard.LockIconView>
<com.android.systemui.statusbar.phone.NotificationsQuickSettingsContainer
diff --git a/packages/SystemUI/res/layout/udfps_aod_lock_icon.xml b/packages/SystemUI/res/layout/udfps_aod_lock_icon.xml
new file mode 100644
index 000000000000..f5bfa49e4b32
--- /dev/null
+++ b/packages/SystemUI/res/layout/udfps_aod_lock_icon.xml
@@ -0,0 +1,27 @@
+<!--
+ ~ Copyright (C) 2021 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<com.airbnb.lottie.LottieAnimationView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:systemui="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/lock_udfps_aod_fp"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:padding="@dimen/lock_icon_padding"
+ android:layout_gravity="center"
+ android:scaleType="centerCrop"
+ systemui:lottie_autoPlay="false"
+ systemui:lottie_loop="true"
+ systemui:lottie_rawRes="@raw/udfps_aod_fp"/> \ No newline at end of file
diff --git a/packages/SystemUI/res/values-night/styles.xml b/packages/SystemUI/res/values-night/styles.xml
index ffcc3a821d36..07e28b6d7f20 100644
--- a/packages/SystemUI/res/values-night/styles.xml
+++ b/packages/SystemUI/res/values-night/styles.xml
@@ -16,7 +16,9 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android">
- <style name="Theme.SystemUI.Dialog" parent="@android:style/Theme.DeviceDefault.Dialog" />
+ <style name="Theme.SystemUI.Dialog" parent="@android:style/Theme.DeviceDefault.Dialog">
+ <item name="android:buttonCornerRadius">28dp</item>
+ </style>
<style name="Theme.SystemUI.Dialog.Alert" parent="@*android:style/Theme.DeviceDefault.Dialog.Alert" />
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 946bbc289a03..7293f3148ae4 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1439,10 +1439,10 @@
<!-- Output switcher panel related dimensions -->
<dimen name="media_output_dialog_list_margin">12dp</dimen>
<dimen name="media_output_dialog_list_max_height">364dp</dimen>
- <dimen name="media_output_dialog_header_album_icon_size">52dp</dimen>
- <dimen name="media_output_dialog_header_back_icon_size">36dp</dimen>
+ <dimen name="media_output_dialog_header_album_icon_size">48dp</dimen>
+ <dimen name="media_output_dialog_header_back_icon_size">32dp</dimen>
<dimen name="media_output_dialog_header_icon_padding">16dp</dimen>
- <dimen name="media_output_dialog_icon_corner_radius">16dp</dimen>
+ <dimen name="media_output_dialog_icon_corner_radius">8dp</dimen>
<dimen name="media_output_dialog_title_anim_y_delta">12.5dp</dimen>
<!-- Distance that the full shade transition takes in order for qs to fully transition to the
@@ -1602,7 +1602,7 @@
<!-- Internet panel related dimensions -->
<dimen name="internet_dialog_list_margin">12dp</dimen>
- <dimen name="internet_dialog_list_max_height">646dp</dimen>
+ <dimen name="internet_dialog_list_max_height">662dp</dimen>
<!-- The width of large/content heavy dialogs (e.g. Internet, Media output, etc) -->
<dimen name="large_dialog_width">@dimen/match_parent</dimen>
diff --git a/packages/SystemUI/res/values/flags.xml b/packages/SystemUI/res/values/flags.xml
index d6d98b9397ac..675838766175 100644
--- a/packages/SystemUI/res/values/flags.xml
+++ b/packages/SystemUI/res/values/flags.xml
@@ -24,9 +24,6 @@
<bool name="flag_monet">true</bool>
- <!-- AOD/Lockscreen alternate layout -->
- <bool name="flag_keyguard_layout">true</bool>
-
<!-- People Tile flag -->
<bool name="flag_conversations">false</bool>
@@ -46,7 +43,9 @@
<bool name="flag_ongoing_call_status_bar_chip">true</bool>
- <bool name="flag_ongoing_call_in_immersive">false</bool>
+ <bool name="flag_ongoing_call_in_immersive">true</bool>
+
+ <bool name="flag_ongoing_call_in_immersive_chip_tap">true</bool>
<bool name="flag_smartspace">false</bool>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index ec63df5e0fe2..523b9c8c8f87 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -2830,6 +2830,8 @@
<string name="controls_media_settings_button">Settings</string>
<!-- Description for media control's playing media item, including information for the media's title, the artist, and source app [CHAR LIMIT=NONE]-->
<string name="controls_media_playing_item_description"><xliff:g id="song_name" example="Daily mix">%1$s</xliff:g> by <xliff:g id="artist_name" example="Various artists">%2$s</xliff:g> is playing from <xliff:g id="app_label" example="Spotify">%3$s</xliff:g></string>
+ <!-- Content description for media cotnrols progress bar [CHAR_LIMIT=NONE] -->
+ <string name="controls_media_seekbar_description"><xliff:g id="elapsed_time" example="1:30">%1$s</xliff:g> of <xliff:g id="total_time" example="3:00">%2$s</xliff:g></string>
<!-- Title for Smartspace recommendation card within media controls. The "Play" means the action to play a media [CHAR_LIMIT=10] -->
<string name="controls_media_smartspace_rec_title">Play</string>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 6e51ec715da5..ff299eae8cf2 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -417,7 +417,9 @@
<item name="android:windowIsFloating">true</item>
</style>
- <style name="Theme.SystemUI.Dialog" parent="@android:style/Theme.DeviceDefault.Light.Dialog" />
+ <style name="Theme.SystemUI.Dialog" parent="@android:style/Theme.DeviceDefault.Light.Dialog">
+ <item name="android:buttonCornerRadius">28dp</item>
+ </style>
<style name="Theme.SystemUI.Dialog.Alert" parent="@*android:style/Theme.DeviceDefault.Light.Dialog.Alert" />
@@ -513,6 +515,10 @@
<item name="android:background">@drawable/btn_borderless_rect</item>
</style>
+ <style name="MediaOutputRoundedOutlinedButton" parent="@android:style/Widget.Material.Button">
+ <item name="android:background">@drawable/media_output_dialog_button_background</item>
+ </style>
+
<style name="TunerSettings" parent="@android:style/Theme.DeviceDefault.Settings">
<item name="android:windowActionBar">false</item>
<item name="preferenceTheme">@style/TunerPreferenceTheme</item>
@@ -929,26 +935,14 @@
<item name="actionDividerHeight">32dp</item>
</style>
- <style name="Theme.SystemUI.Dialog.QSDialog">
- <item name="android:windowIsTranslucent">true</item>
- <item name="android:windowBackground">@android:color/transparent</item>
- <item name="android:windowIsFloating">true</item>
- <item name="android:backgroundDimEnabled">true</item>
- <item name="android:windowCloseOnTouchOutside">true</item>
- <item name="android:windowAnimationStyle">@android:style/Animation.Dialog</item>
- <item name="android:dialogCornerRadius">28dp</item>
- <item name="android:buttonCornerRadius">28dp</item>
- <item name="android:colorBackground">@color/prv_color_surface</item>
- </style>
-
- <style name="TextAppearance.QSDialog.Title" parent="Theme.SystemUI.Dialog.QSDialog">
+ <style name="TextAppearance.QSDialog.Title" parent="Theme.SystemUI.Dialog">
<item name="android:textColor">?android:attr/textColorPrimary</item>
<item name="android:textSize">24sp</item>
<item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
<item name="android:lineHeight">32sp</item>
</style>
- <style name="Widget.QSDialog.Button" parent = "Theme.SystemUI.Dialog.QSDialog">
+ <style name="Widget.QSDialog.Button" parent = "Theme.SystemUI.Dialog">
<item name="android:background">@drawable/qs_dialog_btn_filled</item>
<item name="android:textColor">@color/prv_text_color_on_accent</item>
<item name="android:textSize">14sp</item>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
index 11557ad87929..5b7e500c4630 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
@@ -143,5 +143,12 @@ interface ISystemUiProxy {
/** Notifies when taskbar status updated */
oneway void notifyTaskbarStatus(boolean visible, boolean stashed) = 47;
- // Next id = 48
+ /**
+ * Notifies sysui when taskbar requests autoHide to stop auto-hiding
+ * If called to suspend, caller is also responsible for calling this method to un-suspend
+ * @param suspend should be true to stop auto-hide, false to resume normal behavior
+ */
+ oneway void notifyTaskbarAutohideSuspend(boolean suspend) = 48;
+
+ // Next id = 49
}
diff --git a/packages/SystemUI/src-debug/com/android/systemui/flags/FeatureFlagManager.java b/packages/SystemUI/src-debug/com/android/systemui/flags/FeatureFlagManager.java
new file mode 100644
index 000000000000..b84c7bf0bd5f
--- /dev/null
+++ b/packages/SystemUI/src-debug/com/android/systemui/flags/FeatureFlagManager.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.flags;
+
+import com.android.systemui.dagger.SysUISingleton;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import javax.inject.Inject;
+
+/**
+ * Concrete implementation of the a Flag manager that returns default values for debug builds
+ */
+@SysUISingleton
+public class FeatureFlagManager implements FlagReader, FlagWriter {
+ private static final String SYSPROP_PREFIX = "persist.systemui.flag_";
+ private static final String FIELD_TYPE = "type";
+ private static final String FIELD_VALUE = "value";
+ private static final String TYPE_BOOLEAN = "boolean";
+ private final SystemPropertiesHelper mSystemPropertiesHelper;
+
+ @Inject
+ public FeatureFlagManager(SystemPropertiesHelper systemPropertiesHelper) {
+ mSystemPropertiesHelper = systemPropertiesHelper;
+ }
+
+ /** Return a {@link BooleanFlag}'s value. */
+ public boolean isEnabled(int key, boolean defaultValue) {
+ String data = mSystemPropertiesHelper.get(keyToSysPropKey(key));
+ if (data.isEmpty()) {
+ return defaultValue;
+ }
+ JSONObject json;
+ try {
+ json = new JSONObject(data);
+ if (!assertType(json, TYPE_BOOLEAN)) {
+ return defaultValue;
+ }
+ return json.getBoolean(FIELD_VALUE);
+ } catch (JSONException e) {
+ // TODO: delete the property
+ return defaultValue;
+ }
+ }
+
+ public void setEnabled(int key, boolean value) {
+ JSONObject json = new JSONObject();
+ try {
+ json.put(FIELD_TYPE, TYPE_BOOLEAN);
+ json.put(FIELD_VALUE, value);
+ mSystemPropertiesHelper.set(keyToSysPropKey(key), json.toString());
+ } catch (JSONException e) {
+ // no-op
+ }
+ }
+
+ public void addListener(Listener run) {}
+
+ public void removeListener(Listener run) {}
+
+ private static String keyToSysPropKey(int key) {
+ return SYSPROP_PREFIX + key;
+ }
+
+ private static boolean assertType(JSONObject json, String type) {
+ try {
+ return json.getString(FIELD_TYPE).equals(TYPE_BOOLEAN);
+ } catch (JSONException e) {
+ return false;
+ }
+ }
+
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
index f81f0b9ca2c3..6fd0c821bad1 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
@@ -41,7 +41,7 @@ public class KeyguardClockSwitch extends RelativeLayout {
private static final long CLOCK_OUT_MILLIS = 150;
private static final long CLOCK_IN_MILLIS = 200;
- private static final long SMARTSPACE_MOVE_MILLIS = 350;
+ private static final long STATUS_AREA_MOVE_MILLIS = 350;
@IntDef({LARGE, SMALL})
@Retention(RetentionPolicy.SOURCE)
@@ -63,13 +63,7 @@ public class KeyguardClockSwitch extends RelativeLayout {
private AnimatableClockView mClockView;
private AnimatableClockView mLargeClockView;
- /**
- * Status area (date and other stuff) shown below the clock. Plugin can decide whether or not to
- * show it below the alternate clock.
- */
- private View mKeyguardStatusArea;
- /** Mutually exclusive with mKeyguardStatusArea */
- private View mSmartspaceView;
+ private View mStatusArea;
private int mSmartspaceTopOffset;
/**
@@ -85,7 +79,7 @@ public class KeyguardClockSwitch extends RelativeLayout {
@VisibleForTesting AnimatorSet mClockInAnim = null;
@VisibleForTesting AnimatorSet mClockOutAnim = null;
- private ObjectAnimator mSmartspaceAnim = null;
+ private ObjectAnimator mStatusAreaAnim = null;
/**
* If the Keyguard Slice has a header (big center-aligned text.)
@@ -131,7 +125,7 @@ public class KeyguardClockSwitch extends RelativeLayout {
mClockView = findViewById(R.id.animatable_clock_view);
mLargeClockFrame = findViewById(R.id.lockscreen_clock_view_large);
mLargeClockView = findViewById(R.id.animatable_clock_view_large);
- mKeyguardStatusArea = findViewById(R.id.keyguard_status_area);
+ mStatusArea = findViewById(R.id.keyguard_status_area);
onDensityOrFontScaleChanged();
}
@@ -200,22 +194,22 @@ public class KeyguardClockSwitch extends RelativeLayout {
private void animateClockChange(boolean useLargeClock) {
if (mClockInAnim != null) mClockInAnim.cancel();
if (mClockOutAnim != null) mClockOutAnim.cancel();
- if (mSmartspaceAnim != null) mSmartspaceAnim.cancel();
+ if (mStatusAreaAnim != null) mStatusAreaAnim.cancel();
View in, out;
int direction = 1;
- float smartspaceYTranslation;
+ float statusAreaYTranslation;
if (useLargeClock) {
out = mClockFrame;
in = mLargeClockFrame;
if (indexOfChild(in) == -1) addView(in);
direction = -1;
- smartspaceYTranslation = mSmartspaceView == null ? 0
- : mClockFrame.getTop() - mSmartspaceView.getTop() + mSmartspaceTopOffset;
+ statusAreaYTranslation = mClockFrame.getTop() - mStatusArea.getTop()
+ + mSmartspaceTopOffset;
} else {
in = mClockFrame;
out = mLargeClockFrame;
- smartspaceYTranslation = 0f;
+ statusAreaYTranslation = 0f;
// Must remove in order for notifications to appear in the proper place
removeView(out);
@@ -251,18 +245,16 @@ public class KeyguardClockSwitch extends RelativeLayout {
mClockInAnim.start();
mClockOutAnim.start();
- if (mSmartspaceView != null) {
- mSmartspaceAnim = ObjectAnimator.ofFloat(mSmartspaceView, View.TRANSLATION_Y,
- smartspaceYTranslation);
- mSmartspaceAnim.setDuration(SMARTSPACE_MOVE_MILLIS);
- mSmartspaceAnim.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
- mSmartspaceAnim.addListener(new AnimatorListenerAdapter() {
- public void onAnimationEnd(Animator animation) {
- mSmartspaceAnim = null;
- }
- });
- mSmartspaceAnim.start();
- }
+ mStatusAreaAnim = ObjectAnimator.ofFloat(mStatusArea, View.TRANSLATION_Y,
+ statusAreaYTranslation);
+ mStatusAreaAnim.setDuration(STATUS_AREA_MOVE_MILLIS);
+ mStatusAreaAnim.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
+ mStatusAreaAnim.addListener(new AnimatorListenerAdapter() {
+ public void onAnimationEnd(Animator animation) {
+ mStatusAreaAnim = null;
+ }
+ });
+ mStatusAreaAnim.start();
}
/**
@@ -352,10 +344,6 @@ public class KeyguardClockSwitch extends RelativeLayout {
}
}
- void setSmartspaceView(View smartspaceView) {
- mSmartspaceView = smartspaceView;
- }
-
void updateColors(ColorExtractor.GradientColors colors) {
mSupportsDarkText = colors.supportsDarkText();
mColorPalette = colors.getColorPalette();
@@ -369,8 +357,7 @@ public class KeyguardClockSwitch extends RelativeLayout {
pw.println(" mClockPlugin: " + mClockPlugin);
pw.println(" mClockFrame: " + mClockFrame);
pw.println(" mLargeClockFrame: " + mLargeClockFrame);
- pw.println(" mKeyguardStatusArea: " + mKeyguardStatusArea);
- pw.println(" mSmartspaceView: " + mSmartspaceView);
+ pw.println(" mStatusArea: " + mStatusArea);
pw.println(" mDarkAmount: " + mDarkAmount);
pw.println(" mSupportsDarkText: " + mSupportsDarkText);
pw.println(" mColorPalette: " + Arrays.toString(mColorPalette));
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
index 4111020afbf5..01976b41a4b2 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
@@ -25,7 +25,9 @@ import android.app.WallpaperManager;
import android.content.res.Resources;
import android.text.TextUtils;
import android.view.View;
+import android.view.ViewGroup;
import android.widget.FrameLayout;
+import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import com.android.internal.colorextraction.ColorExtractor;
@@ -99,7 +101,8 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
private final ClockManager.ClockChangedListener mClockChangedListener = this::setClockPlugin;
- // If set, will replace keyguard_status_area
+ private ViewGroup mStatusArea;
+ // If set will replace keyguard_slice_view
private View mSmartspaceView;
private final KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
@@ -191,8 +194,8 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
mView.getResources().getDimensionPixelSize(R.dimen.keyguard_clock_top_margin);
if (mOnlyClock) {
- View ksa = mView.findViewById(R.id.keyguard_status_area);
- ksa.setVisibility(View.GONE);
+ View ksv = mView.findViewById(R.id.keyguard_slice_view);
+ ksv.setVisibility(View.GONE);
View nic = mView.findViewById(
R.id.left_aligned_notification_icon_container);
@@ -201,19 +204,18 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
}
updateAodIcons();
+ mStatusArea = mView.findViewById(R.id.keyguard_status_area);
+
if (mSmartspaceController.isEnabled()) {
mSmartspaceView = mSmartspaceController.buildAndConnectView(mView);
+ View ksv = mView.findViewById(R.id.keyguard_slice_view);
+ int ksvIndex = mStatusArea.indexOfChild(ksv);
+ ksv.setVisibility(View.GONE);
- View ksa = mView.findViewById(R.id.keyguard_status_area);
- int ksaIndex = mView.indexOfChild(ksa);
- ksa.setVisibility(View.GONE);
-
- // Place smartspace view below normal clock...
- RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams(
+ LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
MATCH_PARENT, WRAP_CONTENT);
- lp.addRule(RelativeLayout.BELOW, R.id.lockscreen_clock_view);
- mView.addView(mSmartspaceView, ksaIndex, lp);
+ mStatusArea.addView(mSmartspaceView, ksvIndex, lp);
int startPadding = getContext().getResources()
.getDimensionPixelSize(R.dimen.below_clock_padding_start);
int endPadding = getContext().getResources()
@@ -221,14 +223,6 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
mSmartspaceView.setPaddingRelative(startPadding, 0, endPadding, 0);
updateClockLayout();
-
- View nic = mView.findViewById(
- R.id.left_aligned_notification_icon_container);
- lp = (RelativeLayout.LayoutParams) nic.getLayoutParams();
- lp.addRule(RelativeLayout.BELOW, mSmartspaceView.getId());
- nic.setLayoutParams(lp);
-
- mView.setSmartspaceView(mSmartspaceView);
mSmartspaceTransitionController.setLockscreenSmartspace(mSmartspaceView);
}
}
@@ -244,8 +238,6 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
}
mColorExtractor.removeOnColorsChangedListener(mColorsListener);
mView.setClockPlugin(null, mStatusBarStateController.getState());
-
- mSmartspaceController.disconnect();
}
/**
@@ -328,8 +320,8 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
PropertyAnimator.setProperty(mLargeClockFrame, AnimatableProperty.SCALE_Y,
scale, props, animate);
- if (mSmartspaceView != null) {
- PropertyAnimator.setProperty(mSmartspaceView, AnimatableProperty.TRANSLATION_X,
+ if (mStatusArea != null) {
+ PropertyAnimator.setProperty(mStatusArea, AnimatableProperty.TRANSLATION_X,
x, props, animate);
// If we're unlocking with the SmartSpace shared element transition, let the controller
@@ -340,7 +332,6 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
}
mKeyguardSliceViewController.updatePosition(x, props, animate);
- mNotificationIconAreaController.updatePosition(x, props, animate);
}
/** Sets an alpha value on every child view except for the smartspace. */
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index 9d649e78c363..d4d3d5b3ea2d 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -209,7 +209,7 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
private ConfigurationController.ConfigurationListener mConfigurationListener =
new ConfigurationController.ConfigurationListener() {
@Override
- public void onOverlayChanged() {
+ public void onThemeChanged() {
mSecurityViewFlipperController.reloadColors();
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java
index 428006ebf446..9b76bab5c2a7 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java
@@ -33,12 +33,10 @@ import android.os.Trace;
import android.text.TextUtils;
import android.text.TextUtils.TruncateAt;
import android.util.AttributeSet;
-import android.util.TypedValue;
import android.view.Gravity;
import android.view.View;
import android.view.animation.Animation;
import android.widget.LinearLayout;
-import android.widget.RelativeLayout;
import android.widget.TextView;
import androidx.slice.SliceItem;
@@ -85,8 +83,6 @@ public class KeyguardSliceView extends LinearLayout {
private boolean mHasHeader;
private View.OnClickListener mOnClickListener;
- private int mLockScreenMode = KeyguardUpdateMonitor.LOCK_SCREEN_MODE_NORMAL;
-
public KeyguardSliceView(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -136,35 +132,6 @@ public class KeyguardSliceView extends LinearLayout {
}
}
- /**
- * Updates the lockscreen mode which may change the layout of the keyguard slice view.
- */
- public void updateLockScreenMode(int mode) {
- mLockScreenMode = mode;
- if (mLockScreenMode == KeyguardUpdateMonitor.LOCK_SCREEN_MODE_LAYOUT_1) {
- mTitle.setPaddingRelative(0, 0, 0, 0);
- mTitle.setGravity(Gravity.START);
- setGravity(Gravity.START);
- RelativeLayout.LayoutParams lp = (RelativeLayout.LayoutParams) getLayoutParams();
- lp.removeRule(RelativeLayout.CENTER_HORIZONTAL);
- setLayoutParams(lp);
- } else {
- final int horizontalPaddingDpValue = (int) TypedValue.applyDimension(
- TypedValue.COMPLEX_UNIT_DIP,
- 44,
- getResources().getDisplayMetrics()
- );
- mTitle.setPaddingRelative(horizontalPaddingDpValue, 0, horizontalPaddingDpValue, 0);
- mTitle.setGravity(Gravity.CENTER_HORIZONTAL);
- setGravity(Gravity.CENTER_HORIZONTAL);
- RelativeLayout.LayoutParams lp = (RelativeLayout.LayoutParams) getLayoutParams();
- lp.addRule(RelativeLayout.CENTER_HORIZONTAL);
- setLayoutParams(lp);
- }
- mRow.setLockscreenMode(mode);
- requestLayout();
- }
-
Map<View, PendingIntent> showSlice(RowContent header, List<SliceContent> subItems) {
Trace.beginSection("KeyguardSliceView#showSlice");
mHasHeader = header != null;
@@ -189,8 +156,7 @@ public class KeyguardSliceView extends LinearLayout {
final int startIndex = mHasHeader ? 1 : 0; // First item is header; skip it
mRow.setVisibility(subItemsCount > 0 ? VISIBLE : GONE);
LinearLayout.LayoutParams layoutParams = (LayoutParams) mRow.getLayoutParams();
- layoutParams.gravity = mLockScreenMode != KeyguardUpdateMonitor.LOCK_SCREEN_MODE_NORMAL
- ? Gravity.START : Gravity.CENTER;
+ layoutParams.gravity = Gravity.START;
mRow.setLayoutParams(layoutParams);
for (int i = startIndex; i < subItemsCount; i++) {
@@ -224,8 +190,7 @@ public class KeyguardSliceView extends LinearLayout {
final int iconSize = mHasHeader ? mIconSizeWithHeader : mIconSize;
iconDrawable = icon.getIcon().loadDrawable(mContext);
if (iconDrawable != null) {
- if ((iconDrawable instanceof InsetDrawable)
- && mLockScreenMode == KeyguardUpdateMonitor.LOCK_SCREEN_MODE_LAYOUT_1) {
+ if (iconDrawable instanceof InsetDrawable) {
// System icons (DnD) use insets which are fine for centered slice content
// but will cause a slight indent for left/right-aligned slice views
iconDrawable = ((InsetDrawable) iconDrawable).getDrawable();
@@ -321,7 +286,6 @@ public class KeyguardSliceView extends LinearLayout {
pw.println(" mTextColor: " + Integer.toHexString(mTextColor));
pw.println(" mDarkAmount: " + mDarkAmount);
pw.println(" mHasHeader: " + mHasHeader);
- pw.println(" mLockScreenMode: " + mLockScreenMode);
}
@Override
@@ -332,7 +296,6 @@ public class KeyguardSliceView extends LinearLayout {
public static class Row extends LinearLayout {
private Set<KeyguardSliceTextView> mKeyguardSliceTextViewSet = new HashSet();
- private int mLockScreenModeRow = KeyguardUpdateMonitor.LOCK_SCREEN_MODE_NORMAL;
/**
* This view is visible in AOD, which means that the device will sleep if we
@@ -407,11 +370,7 @@ public class KeyguardSliceView extends LinearLayout {
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
if (child instanceof KeyguardSliceTextView) {
- if (mLockScreenModeRow == KeyguardUpdateMonitor.LOCK_SCREEN_MODE_LAYOUT_1) {
- ((KeyguardSliceTextView) child).setMaxWidth(Integer.MAX_VALUE);
- } else {
- ((KeyguardSliceTextView) child).setMaxWidth(width / 3);
- }
+ ((KeyguardSliceTextView) child).setMaxWidth(Integer.MAX_VALUE);
}
}
@@ -443,7 +402,6 @@ public class KeyguardSliceView extends LinearLayout {
super.addView(view, index);
if (view instanceof KeyguardSliceTextView) {
- ((KeyguardSliceTextView) view).setLockScreenMode(mLockScreenModeRow);
mKeyguardSliceTextViewSet.add((KeyguardSliceTextView) view);
}
}
@@ -455,24 +413,6 @@ public class KeyguardSliceView extends LinearLayout {
mKeyguardSliceTextViewSet.remove((KeyguardSliceTextView) view);
}
}
-
- /**
- * Updates the lockscreen mode which may change the layout of this view.
- */
- public void setLockscreenMode(int mode) {
- mLockScreenModeRow = mode;
- if (mLockScreenModeRow == KeyguardUpdateMonitor.LOCK_SCREEN_MODE_LAYOUT_1) {
- setOrientation(LinearLayout.VERTICAL);
- setGravity(Gravity.START);
- } else {
- setOrientation(LinearLayout.HORIZONTAL);
- setGravity(Gravity.CENTER);
- }
-
- for (KeyguardSliceTextView textView : mKeyguardSliceTextViewSet) {
- textView.setLockScreenMode(mLockScreenModeRow);
- }
- }
}
/**
@@ -480,7 +420,6 @@ public class KeyguardSliceView extends LinearLayout {
*/
@VisibleForTesting
static class KeyguardSliceTextView extends TextView {
- private int mLockScreenMode = KeyguardUpdateMonitor.LOCK_SCREEN_MODE_NORMAL;
@StyleRes
private static int sStyleId = R.style.TextAppearance_Keyguard_Secondary;
@@ -509,13 +448,8 @@ public class KeyguardSliceView extends LinearLayout {
boolean hasText = !TextUtils.isEmpty(getText());
int padding = (int) getContext().getResources()
.getDimension(R.dimen.widget_horizontal_padding) / 2;
- if (mLockScreenMode == KeyguardUpdateMonitor.LOCK_SCREEN_MODE_LAYOUT_1) {
- // orientation is vertical, so add padding to top & bottom
- setPadding(0, padding, 0, hasText ? padding : 0);
- } else {
- // orientation is horizontal, so add padding to left & right
- setPadding(padding, 0, padding * (hasText ? 1 : -1), 0);
- }
+ // orientation is vertical, so add padding to top & bottom
+ setPadding(0, padding, 0, hasText ? padding : 0);
setCompoundDrawablePadding((int) mContext.getResources()
.getDimension(R.dimen.widget_icon_padding));
@@ -543,18 +477,5 @@ public class KeyguardSliceView extends LinearLayout {
}
}
}
-
- /**
- * Updates the lockscreen mode which may change the layout of this view.
- */
- public void setLockScreenMode(int mode) {
- mLockScreenMode = mode;
- if (mLockScreenMode == KeyguardUpdateMonitor.LOCK_SCREEN_MODE_LAYOUT_1) {
- setTextAlignment(View.TEXT_ALIGNMENT_VIEW_START);
- } else {
- setTextAlignment(View.TEXT_ALIGNMENT_CENTER);
- }
- updatePadding();
- }
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceViewController.java
index 8038ce4c7b69..d05cc4ea8101 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceViewController.java
@@ -73,7 +73,6 @@ public class KeyguardSliceViewController extends ViewController<KeyguardSliceVie
private Uri mKeyguardSliceUri;
private Slice mSlice;
private Map<View, PendingIntent> mClickActions;
- private int mLockScreenMode = KeyguardUpdateMonitor.LOCK_SCREEN_MODE_NORMAL;
TunerService.Tunable mTunable = (key, newValue) -> setupUri(newValue);
@@ -84,7 +83,7 @@ public class KeyguardSliceViewController extends ViewController<KeyguardSliceVie
mView.onDensityOrFontScaleChanged();
}
@Override
- public void onOverlayChanged() {
+ public void onThemeChanged() {
mView.onOverlayChanged();
}
};
@@ -137,7 +136,6 @@ public class KeyguardSliceViewController extends ViewController<KeyguardSliceVie
TAG + "@" + Integer.toHexString(
KeyguardSliceViewController.this.hashCode()),
KeyguardSliceViewController.this);
- mView.updateLockScreenMode(mLockScreenMode);
}
@Override
@@ -160,14 +158,6 @@ public class KeyguardSliceViewController extends ViewController<KeyguardSliceVie
}
/**
- * Updates the lockscreen mode which may change the layout of the keyguard slice view.
- */
- public void updateLockScreenMode(int mode) {
- mLockScreenMode = mode;
- mView.updateLockScreenMode(mLockScreenMode);
- }
-
- /**
* Sets the slice provider Uri.
*/
public void setupUri(String uriString) {
@@ -249,6 +239,5 @@ public class KeyguardSliceViewController extends ViewController<KeyguardSliceVie
public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) {
pw.println(" mSlice: " + mSlice);
pw.println(" mClickActions: " + mClickActions);
- pw.println(" mLockScreenMode: " + mLockScreenMode);
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
index 2362a1ad74a0..a72a050ee023 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
@@ -88,7 +88,7 @@ public class KeyguardStatusView extends GridLayout {
mClockView.setAccessibilityDelegate(new KeyguardClockAccessibilityDelegate(mContext));
}
- mKeyguardSlice = findViewById(R.id.keyguard_status_area);
+ mKeyguardSlice = findViewById(R.id.keyguard_slice_view);
mTextColor = mClockView.getCurrentTextColor();
mKeyguardSlice.setContentChangeListener(this::onSliceContentChanged);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
index d23b1c88b345..c58710ceec65 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
@@ -249,11 +249,6 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV
private KeyguardUpdateMonitorCallback mInfoCallback = new KeyguardUpdateMonitorCallback() {
@Override
- public void onLockScreenModeChanged(int mode) {
- mKeyguardSliceViewController.updateLockScreenMode(mode);
- }
-
- @Override
public void onTimeChanged() {
refreshTime();
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 5969e9290c9c..5707fa7b18d7 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -71,7 +71,6 @@ import android.os.ServiceManager;
import android.os.Trace;
import android.os.UserHandle;
import android.os.UserManager;
-import android.os.Vibrator;
import android.provider.Settings;
import android.service.dreams.DreamService;
import android.service.dreams.IDreamManager;
@@ -86,7 +85,6 @@ import android.util.Log;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
-import androidx.annotation.Nullable;
import androidx.lifecycle.Observer;
import com.android.internal.annotations.VisibleForTesting;
@@ -98,7 +96,6 @@ import com.android.systemui.DejankUtils;
import com.android.systemui.Dumpable;
import com.android.systemui.R;
import com.android.systemui.biometrics.AuthController;
-import com.android.systemui.biometrics.UdfpsController;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Background;
@@ -191,9 +188,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
private static final int MSG_TIME_FORMAT_UPDATE = 344;
private static final int MSG_REQUIRE_NFC_UNLOCK = 345;
- public static final int LOCK_SCREEN_MODE_NORMAL = 0;
- public static final int LOCK_SCREEN_MODE_LAYOUT_1 = 1;
-
/** Biometric authentication state: Not listening. */
private static final int BIOMETRIC_STATE_STOPPED = 0;
@@ -277,7 +271,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
private boolean mBouncer; // true if bouncerIsOrWillBeShowing
private boolean mAuthInterruptActive;
private boolean mNeedsSlowUnlockTransition;
- private boolean mHasLockscreenWallpaper;
private boolean mAssistantVisible;
private boolean mKeyguardOccluded;
private boolean mOccludingAppRequestingFp;
@@ -286,9 +279,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
@VisibleForTesting
protected boolean mTelephonyCapable;
- private final boolean mAcquiredHapticEnabled = false;
- @Nullable private final Vibrator mVibrator;
-
// Device provisioning state
private boolean mDeviceProvisioned;
@@ -1407,7 +1397,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
@VisibleForTesting
final FingerprintManager.AuthenticationCallback mFingerprintAuthenticationCallback
= new AuthenticationCallback() {
- private boolean mPlayedAcquiredHaptic;
@Override
public void onAuthenticationFailed() {
@@ -1419,11 +1408,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
Trace.beginSection("KeyguardUpdateMonitor#onAuthenticationSucceeded");
handleFingerprintAuthenticated(result.getUserId(), result.isStrongBiometric());
Trace.endSection();
-
- // on auth success, we sometimes never received an acquired haptic
- if (!mPlayedAcquiredHaptic && isUdfpsEnrolled()) {
- playAcquiredHaptic();
- }
}
@Override
@@ -1439,17 +1423,11 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
@Override
public void onAuthenticationAcquired(int acquireInfo) {
handleFingerprintAcquired(acquireInfo);
- if (acquireInfo == FingerprintManager.FINGERPRINT_ACQUIRED_GOOD
- && isUdfpsEnrolled()) {
- mPlayedAcquiredHaptic = true;
- playAcquiredHaptic();
- }
}
@Override
public void onUdfpsPointerDown(int sensorId) {
Log.d(TAG, "onUdfpsPointerDown, sensorId: " + sensorId);
- mPlayedAcquiredHaptic = false;
}
@Override
@@ -1458,17 +1436,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
}
};
- /**
- * Play haptic to signal udfps fingeprrint acquired.
- */
- @VisibleForTesting
- public void playAcquiredHaptic() {
- if (mAcquiredHapticEnabled && mVibrator != null) {
- mVibrator.vibrate(UdfpsController.EFFECT_CLICK,
- UdfpsController.VIBRATION_SONIFICATION_ATTRIBUTES);
- }
- }
-
private final FaceManager.FaceDetectionCallback mFaceDetectionCallback
= (sensorId, userId, isStrongBiometric) -> {
// Trigger the face success path so the bouncer can be shown
@@ -1772,8 +1739,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
AuthController authController,
TelephonyListenerManager telephonyListenerManager,
FeatureFlags featureFlags,
- InteractionJankMonitor interactionJankMonitor,
- @Nullable Vibrator vibrator) {
+ InteractionJankMonitor interactionJankMonitor
+ ) {
mContext = context;
mSubscriptionManager = SubscriptionManager.from(context);
mTelephonyListenerManager = telephonyListenerManager;
@@ -1789,7 +1756,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
mLockPatternUtils = lockPatternUtils;
mAuthController = authController;
dumpManager.registerDumpable(getClass().getName(), this);
- mVibrator = vibrator;
mHandler = new Handler(mainLooper) {
@Override
@@ -1898,9 +1864,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
case MSG_KEYGUARD_GOING_AWAY:
handleKeyguardGoingAway((boolean) msg.obj);
break;
- case MSG_LOCK_SCREEN_MODE:
- handleLockScreenMode();
- break;
case MSG_TIME_FORMAT_UPDATE:
handleTimeFormatUpdate((String) msg.obj);
break;
@@ -2042,8 +2005,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
}
}
- updateLockScreenMode(featureFlags.isKeyguardLayoutEnabled());
-
mTimeFormatChangeObserver = new ContentObserver(mHandler) {
@Override
public void onChange(boolean selfChange) {
@@ -2059,14 +2020,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
false, mTimeFormatChangeObserver, UserHandle.USER_ALL);
}
- private void updateLockScreenMode(boolean isEnabled) {
- final int newMode = isEnabled ? LOCK_SCREEN_MODE_LAYOUT_1 : LOCK_SCREEN_MODE_NORMAL;
- if (newMode != mLockScreenMode) {
- mLockScreenMode = newMode;
- mHandler.sendEmptyMessage(MSG_LOCK_SCREEN_MODE);
- }
- }
-
private void updateUdfpsEnrolled(int userId) {
mIsUdfpsEnrolled = mAuthController.isUdfpsEnrolled(userId);
}
@@ -2579,31 +2532,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
}
/**
- * Update the state whether Keyguard currently has a lockscreen wallpaper.
- *
- * @param hasLockscreenWallpaper Whether Keyguard has a lockscreen wallpaper.
- */
- public void setHasLockscreenWallpaper(boolean hasLockscreenWallpaper) {
- Assert.isMainThread();
- if (hasLockscreenWallpaper != mHasLockscreenWallpaper) {
- mHasLockscreenWallpaper = hasLockscreenWallpaper;
- for (int i = 0; i < mCallbacks.size(); i++) {
- KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
- if (cb != null) {
- cb.onHasLockscreenWallpaperChanged(hasLockscreenWallpaper);
- }
- }
- }
- }
-
- /**
- * @return Whether Keyguard has a lockscreen wallpaper.
- */
- public boolean hasLockscreenWallpaper() {
- return mHasLockscreenWallpaper;
- }
-
- /**
* Handle {@link #MSG_DPM_STATE_CHANGED}
*/
private void handleDevicePolicyManagerStateChanged(int userId) {
@@ -2722,20 +2650,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
}
/**
- * Handle {@link #MSG_LOCK_SCREEN_MODE}
- */
- private void handleLockScreenMode() {
- Assert.isMainThread();
- if (DEBUG) Log.d(TAG, "handleLockScreenMode(" + mLockScreenMode + ")");
- for (int i = 0; i < mCallbacks.size(); i++) {
- KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
- if (cb != null) {
- cb.onLockScreenModeChanged(mLockScreenMode);
- }
- }
- }
-
- /**
* Handle (@line #MSG_TIMEZONE_UPDATE}
*/
private void handleTimeZoneUpdate(String timeZone) {
@@ -3113,7 +3027,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
callback.onKeyguardOccludedChanged(mKeyguardOccluded);
callback.onKeyguardVisibilityChangedRaw(mKeyguardIsVisible);
callback.onTelephonyCapable(mTelephonyCapable);
- callback.onLockScreenModeChanged(mLockScreenMode);
for (Entry<Integer, SimData> data : mSimDatas.entrySet()) {
final SimData state = data.getValue();
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
index 6aa7aaa4d488..12431984c9b9 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
@@ -292,11 +292,6 @@ public class KeyguardUpdateMonitorCallback {
public void onStrongAuthStateChanged(int userId) { }
/**
- * Called when the state whether we have a lockscreen wallpaper has changed.
- */
- public void onHasLockscreenWallpaperChanged(boolean hasLockscreenWallpaper) { }
-
- /**
* Called when the dream's window state is changed.
* @param dreaming true if the dream's window has been created and is visible
*/
@@ -330,11 +325,6 @@ public class KeyguardUpdateMonitorCallback {
public void onSecondaryLockscreenRequirementChanged(int userId) { }
/**
- * Called to switch lock screen layout/clock layouts
- */
- public void onLockScreenModeChanged(int mode) { }
-
- /**
* Called when notifying user to unlock in order to use NFC.
*/
public void onRequireUnlockForNfc() { }
diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
index 8cfd225fc4df..321c1a309cb5 100644
--- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
@@ -22,8 +22,8 @@ import static com.android.systemui.classifier.Classifier.LOCK_ICON;
import static com.android.systemui.doze.util.BurnInHelperKt.getBurnInOffset;
import static com.android.systemui.doze.util.BurnInHelperKt.getBurnInProgressOffset;
-import android.content.Context;
import android.content.res.Configuration;
+import android.content.res.Resources;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.drawable.AnimatedVectorDrawable;
@@ -38,6 +38,7 @@ import android.util.DisplayMetrics;
import android.util.MathUtils;
import android.view.GestureDetector;
import android.view.GestureDetector.SimpleOnGestureListener;
+import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.accessibility.AccessibilityManager;
@@ -98,9 +99,10 @@ public class LockIconViewController extends ViewController<LockIconView> impleme
@NonNull private final AccessibilityManager mAccessibilityManager;
@NonNull private final ConfigurationController mConfigurationController;
@NonNull private final DelayableExecutor mExecutor;
+ @NonNull private final LayoutInflater mLayoutInflater;
private boolean mUdfpsEnrolled;
- @NonNull private LottieAnimationView mAodFp;
+ @Nullable private LottieAnimationView mAodFp;
@NonNull private final AnimatedVectorDrawable mFpToUnlockIcon;
@NonNull private final AnimatedVectorDrawable mLockToUnlockIcon;
@@ -154,7 +156,9 @@ public class LockIconViewController extends ViewController<LockIconView> impleme
@NonNull ConfigurationController configurationController,
@NonNull @Main DelayableExecutor executor,
@Nullable Vibrator vibrator,
- @Nullable AuthRippleController authRippleController
+ @Nullable AuthRippleController authRippleController,
+ @NonNull @Main Resources resources,
+ @NonNull LayoutInflater inflater
) {
super(view);
mStatusBarStateController = statusBarStateController;
@@ -168,27 +172,19 @@ public class LockIconViewController extends ViewController<LockIconView> impleme
mExecutor = executor;
mVibrator = vibrator;
mAuthRippleController = authRippleController;
+ mLayoutInflater = inflater;
- final Context context = view.getContext();
- mAodFp = mView.findViewById(R.id.lock_udfps_aod_fp);
- mMaxBurnInOffsetX = context.getResources()
- .getDimensionPixelSize(R.dimen.udfps_burn_in_offset_x);
- mMaxBurnInOffsetY = context.getResources()
- .getDimensionPixelSize(R.dimen.udfps_burn_in_offset_y);
-
- mUnlockIcon = mView.getContext().getResources().getDrawable(
- R.drawable.ic_unlock,
- mView.getContext().getTheme());
- mLockIcon = mView.getContext().getResources().getDrawable(
- R.anim.lock_to_unlock,
- mView.getContext().getTheme());
- mFpToUnlockIcon = (AnimatedVectorDrawable) mView.getContext().getResources().getDrawable(
+ mMaxBurnInOffsetX = resources.getDimensionPixelSize(R.dimen.udfps_burn_in_offset_x);
+ mMaxBurnInOffsetY = resources.getDimensionPixelSize(R.dimen.udfps_burn_in_offset_y);
+
+ mUnlockIcon = resources.getDrawable(R.drawable.ic_unlock, mView.getContext().getTheme());
+ mLockIcon = resources.getDrawable(R.anim.lock_to_unlock, mView.getContext().getTheme());
+ mFpToUnlockIcon = (AnimatedVectorDrawable) resources.getDrawable(
R.anim.fp_to_unlock, mView.getContext().getTheme());
- mLockToUnlockIcon = (AnimatedVectorDrawable) mView.getContext().getResources().getDrawable(
- R.anim.lock_to_unlock,
+ mLockToUnlockIcon = (AnimatedVectorDrawable) resources.getDrawable(R.anim.lock_to_unlock,
mView.getContext().getTheme());
- mUnlockedLabel = context.getResources().getString(R.string.accessibility_unlock_button);
- mLockedLabel = context.getResources().getString(R.string.accessibility_lock_icon);
+ mUnlockedLabel = resources.getString(R.string.accessibility_unlock_button);
+ mLockedLabel = resources.getString(R.string.accessibility_lock_icon);
dumpManager.registerDumpable("LockIconViewController", this);
}
@@ -264,8 +260,8 @@ public class LockIconViewController extends ViewController<LockIconView> impleme
boolean wasShowingLockIcon = mShowLockIcon;
boolean wasShowingUnlockIcon = mShowUnlockIcon;
mShowLockIcon = !mCanDismissLockScreen && !mUserUnlockedWithBiometric && isLockScreen()
- && (!mUdfpsEnrolled || !mRunningFPS);
- mShowUnlockIcon = mCanDismissLockScreen && isLockScreen();
+ && (!mUdfpsEnrolled || !mRunningFPS);
+ mShowUnlockIcon = (mCanDismissLockScreen || mUserUnlockedWithBiometric) && isLockScreen();
mShowAODFpIcon = mIsDozing && mUdfpsEnrolled && !mRunningFPS;
final CharSequence prevContentDescription = mView.getContentDescription();
@@ -300,7 +296,7 @@ public class LockIconViewController extends ViewController<LockIconView> impleme
mView.setContentDescription(null);
}
- if (!mShowAODFpIcon) {
+ if (!mShowAODFpIcon && mAodFp != null) {
mAodFp.setVisibility(View.INVISIBLE);
mAodFp.setContentDescription(null);
}
@@ -416,10 +412,12 @@ public class LockIconViewController extends ViewController<LockIconView> impleme
- mMaxBurnInOffsetY, mInterpolatedDarkAmount);
float progress = MathUtils.lerp(0f, getBurnInProgressOffset(), mInterpolatedDarkAmount);
- mAodFp.setTranslationX(offsetX);
- mAodFp.setTranslationY(offsetY);
- mAodFp.setProgress(progress);
- mAodFp.setAlpha(255 * mInterpolatedDarkAmount);
+ if (mAodFp != null) {
+ mAodFp.setTranslationX(offsetX);
+ mAodFp.setTranslationY(offsetY);
+ mAodFp.setProgress(progress);
+ mAodFp.setAlpha(255 * mInterpolatedDarkAmount);
+ }
}
private void updateIsUdfpsEnrolled() {
@@ -430,6 +428,10 @@ public class LockIconViewController extends ViewController<LockIconView> impleme
mView.setUseBackground(mUdfpsSupported);
mUdfpsEnrolled = mKeyguardUpdateMonitor.isUdfpsEnrolled();
+ if (!wasUdfpsEnrolled && mUdfpsEnrolled && mAodFp == null) {
+ mLayoutInflater.inflate(R.layout.udfps_aod_lock_icon, mView);
+ mAodFp = mView.findViewById(R.id.lock_udfps_aod_fp);
+ }
if (wasUdfpsSupported != mUdfpsSupported || wasUdfpsEnrolled != mUdfpsEnrolled) {
updateVisibility();
}
@@ -477,13 +479,15 @@ public class LockIconViewController extends ViewController<LockIconView> impleme
@Override
public void onBiometricRunningStateChanged(boolean running,
BiometricSourceType biometricSourceType) {
+ final boolean wasRunningFps = mRunningFPS;
+ final boolean wasUserUnlockedWithBiometric = mUserUnlockedWithBiometric;
mUserUnlockedWithBiometric =
mKeyguardUpdateMonitor.getUserUnlockedWithBiometric(
KeyguardUpdateMonitor.getCurrentUser());
if (biometricSourceType == FINGERPRINT) {
mRunningFPS = running;
- if (!mRunningFPS) {
+ if (wasRunningFps && !mRunningFPS) {
if (mCancelDelayedUpdateVisibilityRunnable != null) {
mCancelDelayedUpdateVisibilityRunnable.run();
}
@@ -493,10 +497,14 @@ public class LockIconViewController extends ViewController<LockIconView> impleme
// button in this case, so we delay updating the visibility by 50ms.
mCancelDelayedUpdateVisibilityRunnable =
mExecutor.executeDelayed(() -> updateVisibility(), 50);
- } else {
- updateVisibility();
+ return;
}
}
+
+ if (wasUserUnlockedWithBiometric != mUserUnlockedWithBiometric
+ || wasRunningFps != mRunningFPS) {
+ updateVisibility();
+ }
}
};
@@ -545,11 +553,6 @@ public class LockIconViewController extends ViewController<LockIconView> impleme
}
@Override
- public void onOverlayChanged() {
- updateColors();
- }
-
- @Override
public void onConfigChanged(Configuration newConfig) {
updateConfiguration();
updateColors();
@@ -650,7 +653,7 @@ public class LockIconViewController extends ViewController<LockIconView> impleme
public boolean onTouchEvent(MotionEvent event, Runnable onGestureDetectedRunnable) {
if (mSensorTouchLocation.contains((int) event.getX(), (int) event.getY())
&& (mView.getVisibility() == View.VISIBLE
- || mAodFp.getVisibility() == View.VISIBLE)) {
+ || (mAodFp != null && mAodFp.getVisibility() == View.VISIBLE))) {
mOnGestureDetectedRunnable = onGestureDetectedRunnable;
mGestureDetector.onTouchEvent(event);
}
diff --git a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusViewModule.java b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusViewModule.java
index 1d51e5925de8..b8841eda1de4 100644
--- a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusViewModule.java
+++ b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusViewModule.java
@@ -34,6 +34,6 @@ public abstract class KeyguardStatusViewModule {
@Provides
static KeyguardSliceView getKeyguardSliceView(KeyguardClockSwitch keyguardClockSwitch) {
- return keyguardClockSwitch.findViewById(R.id.keyguard_status_area);
+ return keyguardClockSwitch.findViewById(R.id.keyguard_slice_view);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/AutoReinflateContainer.java b/packages/SystemUI/src/com/android/systemui/AutoReinflateContainer.java
index 5ed9eaad0a00..12dd8f06de17 100644
--- a/packages/SystemUI/src/com/android/systemui/AutoReinflateContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/AutoReinflateContainer.java
@@ -86,7 +86,7 @@ public class AutoReinflateContainer extends FrameLayout implements
}
@Override
- public void onOverlayChanged() {
+ public void onThemeChanged() {
inflateLayout();
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
index 0932a8ce757e..8b04bf59658a 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
@@ -272,9 +272,6 @@ class AuthRippleController @Inject constructor(
override fun onThemeChanged() {
updateRippleColor()
}
- override fun onOverlayChanged() {
- updateRippleColor()
- }
}
private val udfpsControllerCallback =
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index 594a642f33bb..b2eaa2b8a821 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -256,6 +256,13 @@ public class UdfpsController implements DozeReceiver {
@Override
public void hideUdfpsOverlay(int sensorId) {
mFgExecutor.execute(() -> {
+ if (mKeyguardUpdateMonitor.isFingerprintDetectionRunning()) {
+ // if we get here, we expect keyguardUpdateMonitor's fingerprintRunningState
+ // to be updated shortly afterwards
+ Log.d(TAG, "hiding udfps overlay when "
+ + "mKeyguardUpdateMonitor.isFingerprintDetectionRunning()=true");
+ }
+
mServerRequest = null;
updateOverlay();
});
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsHapticsSimulator.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsHapticsSimulator.kt
index ea2bbfad1b74..e23131069eab 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsHapticsSimulator.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsHapticsSimulator.kt
@@ -58,9 +58,6 @@ class UdfpsHapticsSimulator @Inject constructor(
"start" -> {
udfpsController?.playStartHaptic()
}
- "acquired" -> {
- keyguardUpdateMonitor.playAcquiredHaptic()
- }
"success" -> {
// needs to be kept up to date with AcquisitionClient#SUCCESS_VIBRATION_EFFECT
vibrator?.vibrate(
@@ -82,7 +79,6 @@ class UdfpsHapticsSimulator @Inject constructor(
pw.println("Usage: adb shell cmd statusbar udfps-haptic <haptic>")
pw.println("Available commands:")
pw.println(" start")
- pw.println(" acquired")
pw.println(" success, always plays CLICK haptic")
pw.println(" error, always plays DOUBLE_CLICK haptic")
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java
index db93b26d99e5..7a28c9d52260 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java
@@ -398,11 +398,6 @@ public class UdfpsKeyguardViewController extends UdfpsAnimationViewController<Ud
}
@Override
- public void onOverlayChanged() {
- mView.updateColor();
- }
-
- @Override
public void onConfigChanged(Configuration newConfig) {
mView.updateColor();
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index 3b459d1427d5..f9331280ece3 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -38,7 +38,10 @@ import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.demomode.dagger.DemoModeModule;
import com.android.systemui.doze.dagger.DozeComponent;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlagManager;
import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.FlagReader;
+import com.android.systemui.flags.FlagWriter;
import com.android.systemui.fragments.FragmentService;
import com.android.systemui.log.dagger.LogModule;
import com.android.systemui.model.SysUiState;
@@ -149,6 +152,12 @@ public abstract class SystemUIModule {
return state;
}
+ @Binds
+ abstract FlagReader provideFlagReader(FeatureFlagManager impl);
+
+ @Binds
+ abstract FlagWriter provideFlagWriter(FeatureFlagManager impl);
+
@BindsOptionalOf
abstract CommandQueue optionalCommandQueue();
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagManager.java b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagManager.java
new file mode 100644
index 000000000000..85baed4a221c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagManager.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.flags;
+
+import com.android.systemui.dagger.SysUISingleton;
+
+import javax.inject.Inject;
+
+/**
+ * Default implementation of the a Flag manager that returns default values for release builds
+ */
+@SysUISingleton
+public class FeatureFlagManager implements FlagReader, FlagWriter {
+ @Inject
+ public FeatureFlagManager() {}
+ public boolean isEnabled(String key, boolean defaultValue) {
+ return defaultValue;
+ }
+ public boolean isEnabled(int key, boolean defaultValue) {
+ return defaultValue;
+ }
+ public void setEnabled(String key, boolean value) {}
+ public void setEnabled(int key, boolean value) {}
+}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagReader.java b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagReader.java
index d4d01c8d97b5..e78646a4839d 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagReader.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagReader.java
@@ -16,26 +16,29 @@
package com.android.systemui.flags;
-import android.content.Context;
import android.content.res.Resources;
import android.util.SparseArray;
import androidx.annotation.BoolRes;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import com.android.systemui.Dumpable;
import com.android.systemui.R;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.plugins.FlagReaderPlugin;
-import com.android.systemui.plugins.PluginListener;
-import com.android.systemui.shared.plugins.PluginManager;
+import com.android.systemui.dump.DumpManager;
import com.android.systemui.util.wrapper.BuildInfo;
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+
import javax.inject.Inject;
/**
* Reads and caches feature flags for quick access
*
- * Feature flags must be defined as boolean resources. For example:
+ * Feature flags must be defined as boolean resources. For example:t
*
* {@code
* <bool name="flag_foo_bar_baz">false</bool>
@@ -55,71 +58,39 @@ import javax.inject.Inject;
* Calls to this class should probably be wrapped by a method in {@link FeatureFlags}.
*/
@SysUISingleton
-public class FeatureFlagReader {
+public class FeatureFlagReader implements Dumpable {
private final Resources mResources;
private final boolean mAreFlagsOverrideable;
- private final PluginManager mPluginManager;
private final SystemPropertiesHelper mSystemPropertiesHelper;
private final SparseArray<CachedFlag> mCachedFlags = new SparseArray<>();
- private FlagReaderPlugin mPlugin = new FlagReaderPlugin(){};
+ private final FlagReader mFlagReader;
@Inject
public FeatureFlagReader(
@Main Resources resources,
BuildInfo build,
- PluginManager pluginManager,
- SystemPropertiesHelper systemPropertiesHelper) {
+ DumpManager dumpManager,
+ SystemPropertiesHelper systemPropertiesHelper,
+ FlagReader reader) {
mResources = resources;
- mPluginManager = pluginManager;
+ mFlagReader = reader;
mSystemPropertiesHelper = systemPropertiesHelper;
mAreFlagsOverrideable =
build.isDebuggable() && mResources.getBoolean(R.bool.are_flags_overrideable);
-
- mPluginManager.addPluginListener(mPluginListener, FlagReaderPlugin.class);
+ dumpManager.registerDumpable("FeatureFlags", this);
}
- private final PluginListener<FlagReaderPlugin> mPluginListener =
- new PluginListener<FlagReaderPlugin>() {
- public void onPluginConnected(FlagReaderPlugin plugin, Context context) {
- mPlugin = plugin;
- }
-
- public void onPluginDisconnected(FlagReaderPlugin plugin) {
- mPlugin = new FlagReaderPlugin() {};
- }
- };
-
boolean isEnabled(BooleanFlag flag) {
- return mPlugin.isEnabled(flag.getId(), flag.getDefault());
- }
-
- String getValue(StringFlag flag) {
- return mPlugin.getValue(flag.getId(), flag.getDefault());
- }
-
- int getValue(IntFlag flag) {
- return mPlugin.getValue(flag.getId(), flag.getDefault());
+ return mFlagReader.isEnabled(flag.getId(), flag.getDefault());
}
- long getValue(LongFlag flag) {
- return mPlugin.getValue(flag.getId(), flag.getDefault());
+ void addListener(FlagReader.Listener listener) {
+ mFlagReader.addListener(listener);
}
- float getValue(FloatFlag flag) {
- return mPlugin.getValue(flag.getId(), flag.getDefault());
- }
-
- double getValue(DoubleFlag flag) {
- return mPlugin.getValue(flag.getId(), flag.getDefault());
- }
-
- void addListener(FlagReaderPlugin.Listener listener) {
- mPlugin.addListener(listener);
- }
-
- void removeListener(FlagReaderPlugin.Listener listener) {
- mPlugin.removeListener(listener);
+ void removeListener(FlagReader.Listener listener) {
+ mFlagReader.removeListener(listener);
}
/**
@@ -172,6 +143,23 @@ public class FeatureFlagReader {
}
}
+ @Override
+ public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) {
+ ArrayList<String> flagStrings = new ArrayList<>(mCachedFlags.size());
+ for (int i = 0; i < mCachedFlags.size(); i++) {
+ int key = mCachedFlags.keyAt(i);
+ // get the object by the key.
+ CachedFlag flag = mCachedFlags.get(key);
+ flagStrings.add(" " + RESNAME_PREFIX + flag.name + ": " + flag.value + "\n");
+ }
+ flagStrings.sort(String.CASE_INSENSITIVE_ORDER);
+ pw.println("AreFlagsOverrideable: " + mAreFlagsOverrideable);
+ pw.println("Cached FeatureFlags:");
+ for (String flagString : flagStrings) {
+ pw.print(flagString);
+ }
+ }
+
private static class CachedFlag {
public final String name;
public final boolean value;
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.java b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.java
index f81811ac0ffb..105e7f514dc4 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.java
@@ -22,7 +22,6 @@ import android.util.FeatureFlagUtils;
import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.R;
import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.plugins.FlagReaderPlugin;
import java.util.ArrayList;
import java.util.HashMap;
@@ -51,7 +50,7 @@ public class FeatureFlags {
flagReader.addListener(mListener);
}
- private final FlagReaderPlugin.Listener mListener = id -> {
+ private final FlagReader.Listener mListener = id -> {
if (mListeners.containsKey(id) && mFlagMap.containsKey(id)) {
mListeners.get(id).forEach(listener -> listener.onFlagChanged(mFlagMap.get(id)));
}
@@ -71,44 +70,7 @@ public class FeatureFlags {
}
/**
- * @param flag The {@link StringFlag} of interest.
- * @return The value of the flag.
- */
- public String getValue(StringFlag flag) {
- return mFlagReader.getValue(flag);
- }
-
- /**
* @param flag The {@link IntFlag} of interest.
- * @return The value of the flag.
- */
- public int getValue(IntFlag flag) {
- return mFlagReader.getValue(flag);
- }
-
- /**
- * @param flag The {@link LongFlag} of interest.
- * @return The value of the flag.
- */
- public long getValue(LongFlag flag) {
- return mFlagReader.getValue(flag);
- }
-
- /**
- * @param flag The {@link FloatFlag} of interest.
- * @return The value of the flag.
- */
- public float getValue(FloatFlag flag) {
- return mFlagReader.getValue(flag);
- }
-
- /**
- * @param flag The {@link DoubleFlag} of interest.
- * @return The value of the flag.
- */
- public double getValue(DoubleFlag flag) {
- return mFlagReader.getValue(flag);
- }
/** Add a listener for a specific flag. */
public void addFlagListener(Flag<?> flag, Listener listener) {
@@ -132,10 +94,6 @@ public class FeatureFlags {
return mFlagReader.isEnabled(R.bool.flag_notification_pipeline2_rendering);
}
- public boolean isKeyguardLayoutEnabled() {
- return mFlagReader.isEnabled(R.bool.flag_keyguard_layout);
- }
-
/** */
public boolean useNewLockscreenAnimations() {
return mFlagReader.isEnabled(R.bool.flag_lockscreen_animations);
@@ -166,6 +124,11 @@ public class FeatureFlags {
&& mFlagReader.isEnabled(R.bool.flag_ongoing_call_in_immersive);
}
+ public boolean isOngoingCallInImmersiveChipTapEnabled() {
+ return isOngoingCallInImmersiveEnabled()
+ && mFlagReader.isEnabled(R.bool.flag_ongoing_call_in_immersive_chip_tap);
+ }
+
public boolean isSmartspaceEnabled() {
return mFlagReader.isEnabled(R.bool.flag_smartspace);
}
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/FlagReaderPlugin.java b/packages/SystemUI/src/com/android/systemui/flags/FlagReader.java
index ab1749978653..1ae8c1f2a712 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/FlagReaderPlugin.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/FlagReader.java
@@ -14,49 +14,18 @@
* limitations under the License.
*/
-package com.android.systemui.plugins;
-
-import com.android.systemui.plugins.annotations.ProvidesInterface;
+package com.android.systemui.flags;
/**
- * Plugin for loading flag values from an alternate source of truth.
+ * Plugin for loading flag values
*/
-@ProvidesInterface(action = FlagReaderPlugin.ACTION, version = FlagReaderPlugin.VERSION)
-public interface FlagReaderPlugin extends Plugin {
- int VERSION = 1;
- String ACTION = "com.android.systemui.flags.FLAG_READER_PLUGIN";
-
+public interface FlagReader {
/** Returns a boolean value for the given flag. */
default boolean isEnabled(int id, boolean def) {
return def;
}
- /** Returns a string value for the given flag id. */
- default String getValue(int id, String def) {
- return def;
- }
-
- /** Returns a int value for the given flag. */
- default int getValue(int id, int def) {
- return def;
- }
-
- /** Returns a long value for the given flag. */
- default long getValue(int id, long def) {
- return def;
- }
-
- /** Returns a float value for the given flag. */
- default float getValue(int id, float def) {
- return def;
- }
-
- /** Returns a double value for the given flag. */
- default double getValue(int id, double def) {
- return def;
- }
-
/** Add a listener to be alerted when any flag changes. */
default void addListener(Listener listener) {}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FlagWriter.kt b/packages/SystemUI/src/com/android/systemui/flags/FlagWriter.kt
new file mode 100644
index 000000000000..bacc66b39c58
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/flags/FlagWriter.kt
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.flags
+
+interface FlagWriter {
+ fun setEnabled(key: Int, value: Boolean) {}
+} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/flags/SystemPropertiesHelper.kt b/packages/SystemUI/src/com/android/systemui/flags/SystemPropertiesHelper.kt
index 6561bd5a5323..1dc5a9f3adc5 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/SystemPropertiesHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/SystemPropertiesHelper.kt
@@ -26,11 +26,19 @@ import javax.inject.Inject
*/
@SysUISingleton
open class SystemPropertiesHelper @Inject constructor() {
+ fun get(name: String): String {
+ return SystemProperties.get(name)
+ }
+
fun getBoolean(name: String, default: Boolean): Boolean {
return SystemProperties.getBoolean(name, default)
}
+ fun set(name: String, value: String) {
+ SystemProperties.set(name, value)
+ }
+
fun set(name: String, value: Int) {
- SystemProperties.set(name, value.toString())
+ set(name, value.toString())
}
} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index c5694398cdc6..784346e59721 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -670,13 +670,6 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable,
}
}
}
-
- @Override
- public void onHasLockscreenWallpaperChanged(boolean hasLockscreenWallpaper) {
- synchronized (KeyguardViewMediator.this) {
- notifyHasLockscreenWallpaperChanged(hasLockscreenWallpaper);
- }
- }
};
ViewMediatorCallback mViewMediatorCallback = new ViewMediatorCallback() {
@@ -2867,21 +2860,6 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable,
}
}
- private void notifyHasLockscreenWallpaperChanged(boolean hasLockscreenWallpaper) {
- int size = mKeyguardStateCallbacks.size();
- for (int i = size - 1; i >= 0; i--) {
- try {
- mKeyguardStateCallbacks.get(i).onHasLockscreenWallpaperChanged(
- hasLockscreenWallpaper);
- } catch (RemoteException e) {
- Slog.w(TAG, "Failed to call onHasLockscreenWallpaperChanged", e);
- if (e instanceof DeadObjectException) {
- mKeyguardStateCallbacks.remove(i);
- }
- }
- }
- }
-
public void addStateMonitorCallback(IKeyguardStateCallback callback) {
synchronized (this) {
mKeyguardStateCallbacks.add(callback);
@@ -2891,7 +2869,6 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable,
callback.onInputRestrictedStateChanged(mInputRestricted);
callback.onTrustedChanged(mUpdateMonitor.getUserHasTrust(
KeyguardUpdateMonitor.getCurrentUser()));
- callback.onHasLockscreenWallpaperChanged(mUpdateMonitor.hasLockscreenWallpaper());
} catch (RemoteException e) {
Slog.w(TAG, "Failed to call to IKeyguardStateCallback", e);
}
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
index 84c5a571c857..72601e9816f9 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -111,6 +111,17 @@ public class LogModule {
return factory.create("CollapsedSbFragmentLog", 20);
}
+ /**
+ * Provides a logging buffer for logs related to swiping away the status bar while in immersive
+ * mode. See {@link com.android.systemui.statusbar.gesture.SwipeStatusBarAwayGestureLogger}.
+ */
+ @Provides
+ @SysUISingleton
+ @SwipeStatusBarAwayLog
+ public static LogBuffer provideSwipeAwayGestureLogBuffer(LogBufferFactory factory) {
+ return factory.create("SwipeStatusBarAwayLog", 30);
+ }
+
/** Allows logging buffers to be tweaked via adb on debug builds but not on prod builds. */
@Provides
@SysUISingleton
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/SwipeStatusBarAwayLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/SwipeStatusBarAwayLog.java
new file mode 100644
index 000000000000..dd6837563a74
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/SwipeStatusBarAwayLog.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2020 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.log.dagger;
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import com.android.systemui.log.LogBuffer;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+
+import javax.inject.Qualifier;
+
+/**
+ * A {@link LogBuffer} for
+ * {@link com.android.systemui.statusbar.gesture.SwipeStatusBarAwayGestureLogger}.
+ */
+@Qualifier
+@Documented
+@Retention(RUNTIME)
+public @interface SwipeStatusBarAwayLog {
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
index 0e70945be225..e87558ebee27 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
@@ -148,7 +148,7 @@ class MediaCarouselController @Inject constructor(
inflateSettingsButton()
}
- override fun onOverlayChanged() {
+ override fun onThemeChanged() {
recreatePlayers()
inflateSettingsButton()
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/SeekBarObserver.kt b/packages/SystemUI/src/com/android/systemui/media/SeekBarObserver.kt
index f17ad6f09128..33ef19ad040f 100644
--- a/packages/SystemUI/src/com/android/systemui/media/SeekBarObserver.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/SeekBarObserver.kt
@@ -50,6 +50,7 @@ class SeekBarObserver(private val holder: PlayerViewHolder) : Observer<SeekBarVi
holder.seekBar.setProgress(0)
holder.elapsedTimeView.setText("")
holder.totalTimeView.setText("")
+ holder.seekBar.contentDescription = ""
return
}
@@ -61,16 +62,22 @@ class SeekBarObserver(private val holder: PlayerViewHolder) : Observer<SeekBarVi
setVerticalPadding(seekBarEnabledVerticalPadding)
}
- data.duration?.let {
- holder.seekBar.setMax(it)
- holder.totalTimeView.setText(DateUtils.formatElapsedTime(
- it / DateUtils.SECOND_IN_MILLIS))
- }
+ holder.seekBar.setMax(data.duration)
+ val totalTimeString = DateUtils.formatElapsedTime(
+ data.duration / DateUtils.SECOND_IN_MILLIS)
+ holder.totalTimeView.setText(totalTimeString)
data.elapsedTime?.let {
holder.seekBar.setProgress(it)
- holder.elapsedTimeView.setText(DateUtils.formatElapsedTime(
- it / DateUtils.SECOND_IN_MILLIS))
+ val elapsedTimeString = DateUtils.formatElapsedTime(
+ it / DateUtils.SECOND_IN_MILLIS)
+ holder.elapsedTimeView.setText(elapsedTimeString)
+
+ holder.seekBar.contentDescription = holder.seekBar.context.getString(
+ R.string.controls_media_seekbar_description,
+ elapsedTimeString,
+ totalTimeString
+ )
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
index d1b6548132a6..125b87b31e63 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
@@ -135,14 +135,11 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter {
if (currentlyConnected && mController.isActiveRemoteDevice(device)
&& mController.getSelectableMediaDevice().size() > 0) {
// Init active device layout
- mDivider.setVisibility(View.VISIBLE);
- mDivider.setTransitionAlpha(1);
mAddIcon.setVisibility(View.VISIBLE);
mAddIcon.setTransitionAlpha(1);
mAddIcon.setOnClickListener(this::onEndItemClick);
} else {
// Init non-active device layout
- mDivider.setVisibility(View.GONE);
mAddIcon.setVisibility(View.GONE);
}
if (mCurrentActivePosition == position) {
@@ -181,7 +178,6 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter {
super.onBind(customizedItem, topMargin, bottomMargin);
if (customizedItem == CUSTOMIZED_ITEM_PAIR_NEW) {
mCheckBox.setVisibility(View.GONE);
- mDivider.setVisibility(View.GONE);
mAddIcon.setVisibility(View.GONE);
mBottomDivider.setVisibility(View.GONE);
setSingleLineLayout(mContext.getText(R.string.media_output_dialog_pairing_new),
@@ -196,13 +192,10 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter {
mBottomDivider.setVisibility(View.GONE);
mCheckBox.setVisibility(View.GONE);
if (mController.getSelectableMediaDevice().size() > 0) {
- mDivider.setVisibility(View.VISIBLE);
- mDivider.setTransitionAlpha(1);
mAddIcon.setVisibility(View.VISIBLE);
mAddIcon.setTransitionAlpha(1);
mAddIcon.setOnClickListener(this::onEndItemClick);
} else {
- mDivider.setVisibility(View.GONE);
mAddIcon.setVisibility(View.GONE);
}
mTitleIcon.setImageDrawable(getSpeakerDrawable());
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
index 0890841eb4fb..1ffc2c4c35ba 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
@@ -121,7 +121,6 @@ public abstract class MediaOutputBaseAdapter extends
final ProgressBar mProgressBar;
final SeekBar mSeekBar;
final RelativeLayout mTwoLineLayout;
- final View mDivider;
final View mBottomDivider;
final CheckBox mCheckBox;
private String mDeviceId;
@@ -136,7 +135,6 @@ public abstract class MediaOutputBaseAdapter extends
mTitleIcon = view.requireViewById(R.id.title_icon);
mProgressBar = view.requireViewById(R.id.volume_indeterminate_progress);
mSeekBar = view.requireViewById(R.id.volume_seekbar);
- mDivider = view.requireViewById(R.id.end_divider);
mBottomDivider = view.requireViewById(R.id.bottom_divider);
mAddIcon = view.requireViewById(R.id.add_icon);
mCheckBox = view.requireViewById(R.id.check_box);
@@ -151,21 +149,12 @@ public abstract class MediaOutputBaseAdapter extends
return;
}
mTitleIcon.setImageIcon(icon);
- setMargin(topMargin, bottomMargin);
});
});
}
void onBind(int customizedItem, boolean topMargin, boolean bottomMargin) {
- setMargin(topMargin, bottomMargin);
- }
-
- private void setMargin(boolean topMargin, boolean bottomMargin) {
- ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) mContainerLayout
- .getLayoutParams();
- params.topMargin = topMargin ? mMargin : 0;
- params.bottomMargin = bottomMargin ? mMargin : 0;
- mContainerLayout.setLayoutParams(params);
+ // TODO (b/201718621): clean up method after adjustment
}
void setSingleLineLayout(CharSequence title, boolean bFocused) {
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputGroupAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputGroupAdapter.java
index 968c3506f39f..11d76dba1423 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputGroupAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputGroupAdapter.java
@@ -96,7 +96,6 @@ public class MediaOutputGroupAdapter extends MediaOutputBaseAdapter {
@Override
void onBind(MediaDevice device, boolean topMargin, boolean bottomMargin, int position) {
super.onBind(device, topMargin, bottomMargin, position);
- mDivider.setVisibility(View.GONE);
mAddIcon.setVisibility(View.GONE);
mBottomDivider.setVisibility(View.GONE);
mCheckBox.setVisibility(View.VISIBLE);
@@ -135,7 +134,6 @@ public class MediaOutputGroupAdapter extends MediaOutputBaseAdapter {
mTitleIcon.setImageDrawable(getSpeakerDrawable());
mBottomDivider.setVisibility(View.VISIBLE);
mCheckBox.setVisibility(View.GONE);
- mDivider.setVisibility(View.GONE);
mAddIcon.setVisibility(View.GONE);
initSessionSeekbar();
}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
index 7622d663301d..2badbfa31979 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
@@ -61,6 +61,7 @@ import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.CommandQueue.Callbacks;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.NotificationShadeDepthController;
+import com.android.systemui.statusbar.phone.AutoHideController;
import com.android.systemui.statusbar.phone.BarTransitions.TransitionMode;
import com.android.systemui.statusbar.phone.ShadeController;
import com.android.systemui.statusbar.phone.StatusBar;
@@ -160,7 +161,8 @@ public class NavigationBarController implements
NavigationBarA11yHelper navigationBarA11yHelper,
TaskbarDelegate taskbarDelegate,
UserTracker userTracker,
- DumpManager dumpManager) {
+ DumpManager dumpManager,
+ AutoHideController autoHideController) {
mContext = context;
mWindowManager = windowManager;
mAssistManagerLazy = assistManagerLazy;
@@ -194,9 +196,9 @@ public class NavigationBarController implements
mNavMode = mNavigationModeController.addListener(this);
mNavigationModeController.addListener(this);
mTaskbarDelegate = taskbarDelegate;
- mTaskbarDelegate.setOverviewProxyService(commandQueue, overviewProxyService,
+ mTaskbarDelegate.setDependencies(commandQueue, overviewProxyService,
navigationBarA11yHelper, navigationModeController, sysUiFlagsContainer,
- dumpManager);
+ dumpManager, autoHideController);
mIsTablet = isTablet(mContext);
mUserTracker = userTracker;
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationModeController.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationModeController.java
index 0603bb7f02fa..73a0c542fb09 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationModeController.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationModeController.java
@@ -118,7 +118,7 @@ public class NavigationModeController implements Dumpable {
configurationController.addCallback(new ConfigurationController.ConfigurationListener() {
@Override
- public void onOverlayChanged() {
+ public void onThemeChanged() {
if (DEBUG) {
Log.d(TAG, "onOverlayChanged");
}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
index 4d29612951cc..d707dbdf5cba 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
@@ -19,6 +19,8 @@ package com.android.systemui.navigationbar;
import static android.app.StatusBarManager.NAVIGATION_HINT_BACK_ALT;
import static android.app.StatusBarManager.NAVIGATION_HINT_IME_SHOWN;
import static android.app.StatusBarManager.WINDOW_STATE_SHOWING;
+import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
+import static android.view.InsetsState.containsType;
import static android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
@@ -57,7 +59,9 @@ import com.android.systemui.navigationbar.gestural.EdgeBackGestureHandler;
import com.android.systemui.recents.OverviewProxyService;
import com.android.systemui.shared.recents.utilities.Utilities;
import com.android.systemui.shared.system.ActivityManagerWrapper;
+import com.android.systemui.statusbar.AutoHideUiElement;
import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.phone.AutoHideController;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -77,6 +81,7 @@ public class TaskbarDelegate implements CommandQueue.Callbacks,
private NavigationBarA11yHelper mNavigationBarA11yHelper;
private NavigationModeController mNavigationModeController;
private SysUiState mSysUiState;
+ private AutoHideController mAutoHideController;
private int mDisplayId;
private int mNavigationIconHints;
private final NavigationBarA11yHelper.NavA11yEventListener mNavA11yEventListener =
@@ -87,6 +92,28 @@ public class TaskbarDelegate implements CommandQueue.Callbacks,
private final Context mContext;
private final DisplayManager mDisplayManager;
private Context mWindowContext;
+ /**
+ * Tracks the system calls for when taskbar should transiently show or hide so we can return
+ * this value in {@link AutoHideUiElement#isVisible()} below.
+ *
+ * This also gets set by {@link #onTaskbarAutohideSuspend(boolean)} to force show the transient
+ * taskbar if launcher has requested to suspend auto-hide behavior.
+ */
+ private boolean mTaskbarTransientShowing;
+ private final AutoHideUiElement mAutoHideUiElement = new AutoHideUiElement() {
+ @Override
+ public void synchronizeState() {
+ }
+
+ @Override
+ public boolean isVisible() {
+ return mTaskbarTransientShowing;
+ }
+
+ @Override
+ public void hide() {
+ }
+ };
@Inject
public TaskbarDelegate(Context context) {
@@ -96,11 +123,12 @@ public class TaskbarDelegate implements CommandQueue.Callbacks,
mDisplayManager = mContext.getSystemService(DisplayManager.class);
}
- public void setOverviewProxyService(CommandQueue commandQueue,
+ public void setDependencies(CommandQueue commandQueue,
OverviewProxyService overviewProxyService,
NavigationBarA11yHelper navigationBarA11yHelper,
NavigationModeController navigationModeController,
- SysUiState sysUiState, DumpManager dumpManager) {
+ SysUiState sysUiState, DumpManager dumpManager,
+ AutoHideController autoHideController) {
// TODO: adding this in the ctor results in a dagger dependency cycle :(
mCommandQueue = commandQueue;
mOverviewProxyService = overviewProxyService;
@@ -108,18 +136,7 @@ public class TaskbarDelegate implements CommandQueue.Callbacks,
mNavigationModeController = navigationModeController;
mSysUiState = sysUiState;
dumpManager.registerDumpable(this);
- }
-
- public void destroy() {
- mCommandQueue.removeCallback(this);
- mOverviewProxyService.removeCallback(this);
- mNavigationModeController.removeListener(this);
- mNavigationBarA11yHelper.removeA11yEventListener(mNavA11yEventListener);
- mEdgeBackGestureHandler.onNavBarDetached();
- if (mWindowContext != null) {
- mWindowContext.unregisterComponentCallbacks(this);
- mWindowContext = null;
- }
+ mAutoHideController = autoHideController;
}
public void init(int displayId) {
@@ -136,6 +153,20 @@ public class TaskbarDelegate implements CommandQueue.Callbacks,
mWindowContext.registerComponentCallbacks(this);
// Set initial state for any listeners
updateSysuiFlags();
+ mAutoHideController.setNavigationBar(mAutoHideUiElement);
+ }
+
+ public void destroy() {
+ mCommandQueue.removeCallback(this);
+ mOverviewProxyService.removeCallback(this);
+ mNavigationModeController.removeListener(this);
+ mNavigationBarA11yHelper.removeA11yEventListener(mNavA11yEventListener);
+ mEdgeBackGestureHandler.onNavBarDetached();
+ if (mWindowContext != null) {
+ mWindowContext.unregisterComponentCallbacks(this);
+ mWindowContext = null;
+ }
+ mAutoHideController.setNavigationBar(null);
}
private void updateSysuiFlags() {
@@ -209,6 +240,38 @@ public class TaskbarDelegate implements CommandQueue.Callbacks,
}
@Override
+ public void showTransient(int displayId, int[] types) {
+ if (displayId != mDisplayId) {
+ return;
+ }
+ if (!containsType(types, ITYPE_NAVIGATION_BAR)) {
+ return;
+ }
+ mTaskbarTransientShowing = true;
+ }
+
+ @Override
+ public void abortTransient(int displayId, int[] types) {
+ if (displayId != mDisplayId) {
+ return;
+ }
+ if (!containsType(types, ITYPE_NAVIGATION_BAR)) {
+ return;
+ }
+ mTaskbarTransientShowing = false;
+ }
+
+ @Override
+ public void onTaskbarAutohideSuspend(boolean suspend) {
+ mTaskbarTransientShowing = suspend;
+ if (suspend) {
+ mAutoHideController.suspendAutoHide();
+ } else {
+ mAutoHideController.resumeSuspendedAutoHide();
+ }
+ }
+
+ @Override
public void onNavigationModeChanged(int mode) {
mEdgeBackGestureHandler.onNavigationModeChanged(mode);
}
@@ -236,6 +299,7 @@ public class TaskbarDelegate implements CommandQueue.Callbacks,
pw.println(" mDisabledFlags=" + mDisabledFlags);
pw.println(" mTaskBarWindowState=" + mTaskBarWindowState);
pw.println(" mBehavior=" + mBehavior);
+ pw.println(" mTaskbarTransientShowing=" + mTaskbarTransientShowing);
mEdgeBackGestureHandler.dump(pw);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
index bfb63ea5e7c3..90d3448ac1ef 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
@@ -35,7 +35,6 @@ import com.android.systemui.qs.TouchAnimator.Builder;
import com.android.systemui.qs.TouchAnimator.Listener;
import com.android.systemui.qs.dagger.QSScope;
import com.android.systemui.qs.tileimpl.HeightOverrideable;
-import com.android.systemui.statusbar.CrossFadeHelper;
import com.android.systemui.tuner.TunerService;
import com.android.systemui.tuner.TunerService.Tunable;
import com.android.wm.shell.animation.Interpolators;
@@ -170,19 +169,6 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha
}
}
- void startAlphaAnimation(boolean show) {
- if (show == mToShowing) {
- return;
- }
- mToShowing = show;
- if (show) {
- CrossFadeHelper.fadeIn(mQs.getView(), QQS_FADE_IN_DURATION, 0 /* delay */);
- } else {
- CrossFadeHelper.fadeOut(mQs.getView(), QQS_FADE_OUT_DURATION, 0 /* delay */,
- null /* endRunnable */);
- }
- }
-
/**
* Sets whether or not the keyguard is currently being shown with a collapsed header.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
index b8376da8a885..f300efca8a23 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
@@ -89,6 +89,7 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
private int mLayoutDirection;
private QSFooter mFooter;
private float mLastQSExpansion = -1;
+ private float mLastPanelFraction;
private boolean mQsDisabled;
private ImageView mQsDragHandler;
@@ -120,7 +121,7 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
* When true, QS will translate from outside the screen. It will be clipped with parallax
* otherwise.
*/
- private boolean mTranslateWhileExpanding;
+ private boolean mInSplitShade;
private boolean mPulseExpanding;
/**
@@ -135,6 +136,12 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
private DumpManager mDumpManager;
+ /**
+ * Progress of pull down from the center of the lock screen.
+ * @see com.android.systemui.statusbar.LockscreenShadeTransitionController
+ */
+ private float mFullShadeProgress;
+
@Inject
public QSFragment(RemoteInputQuickSettingsDisabler remoteInputQsDisabler,
InjectionInflationController injectionInflater, QSTileHost qsTileHost,
@@ -226,7 +233,8 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
(v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
boolean sizeChanged = (oldTop - oldBottom) != (top - bottom);
if (sizeChanged) {
- setQsExpansion(mLastQSExpansion, mLastHeaderTranslation);
+ setQsExpansion(mLastQSExpansion, mLastPanelFraction,
+ mLastHeaderTranslation);
}
});
mQSPanelController.setUsingHorizontalLayoutChangeListener(
@@ -408,7 +416,7 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
mQSAnimator.setShowCollapsedOnKeyguard(showCollapsed);
}
if (!showCollapsed && isKeyguardState()) {
- setQsExpansion(mLastQSExpansion, 0);
+ setQsExpansion(mLastQSExpansion, mLastPanelFraction, 0);
}
}
}
@@ -476,33 +484,30 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
}
@Override
- public void setTranslateWhileExpanding(boolean shouldTranslate) {
- mTranslateWhileExpanding = shouldTranslate;
- mQSAnimator.setTranslateWhileExpanding(shouldTranslate);
+ public void setInSplitShade(boolean inSplitShade) {
+ mInSplitShade = inSplitShade;
+ mQSAnimator.setTranslateWhileExpanding(inSplitShade);
}
@Override
- public void setTransitionToFullShadeAmount(float pxAmount, boolean animated) {
+ public void setTransitionToFullShadeAmount(float pxAmount, float progress) {
boolean isTransitioningToFullShade = pxAmount > 0;
if (isTransitioningToFullShade != mTransitioningToFullShade) {
mTransitioningToFullShade = isTransitioningToFullShade;
updateShowCollapsedOnKeyguard();
- setQsExpansion(mLastQSExpansion, mLastHeaderTranslation);
}
+ mFullShadeProgress = progress;
+ setQsExpansion(mLastQSExpansion, mLastPanelFraction, mLastHeaderTranslation);
}
@Override
- public void setQsExpansion(float expansion, float proposedTranslation) {
- if (DEBUG) Log.d(TAG, "setQSExpansion " + expansion + " " + proposedTranslation);
+ public void setQsExpansion(float expansion, float panelExpansionFraction,
+ float proposedTranslation) {
float headerTranslation = mTransitioningToFullShade ? 0 : proposedTranslation;
- if (mQSAnimator != null) {
- final boolean showQSOnLockscreen = expansion > 0;
- final boolean showQSUnlocked = headerTranslation == 0 || !mTranslateWhileExpanding;
- mQSAnimator.startAlphaAnimation(showQSOnLockscreen || showQSUnlocked
- || mTransitioningToFullShade);
- }
+ float progress = mTransitioningToFullShade ? mFullShadeProgress : panelExpansionFraction;
+ setAlphaAnimationProgress(mInSplitShade ? progress : 1);
mContainer.setExpansion(expansion);
- final float translationScaleY = (mTranslateWhileExpanding
+ final float translationScaleY = (mInSplitShade
? 1 : QSAnimator.SHORT_PARALLAX_AMOUNT) * (expansion - 1);
boolean onKeyguardAndExpanded = isKeyguardState() && !mShowCollapsedOnKeyguard;
if (!mHeaderAnimating && !headerWillBeAnimating()) {
@@ -519,6 +524,7 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
return;
}
mLastHeaderTranslation = headerTranslation;
+ mLastPanelFraction = panelExpansionFraction;
mLastQSExpansion = expansion;
mLastKeyguardAndExpanded = onKeyguardAndExpanded;
mLastViewHeight = currentHeight;
@@ -560,6 +566,17 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
updateMediaPositions();
}
+ private void setAlphaAnimationProgress(float progress) {
+ final View view = getView();
+ if (progress == 0 && view.getVisibility() != View.INVISIBLE) {
+ view.setVisibility(View.INVISIBLE);
+ } else if (progress > 0 && view.getVisibility() != View.VISIBLE) {
+ view.setVisibility((View.VISIBLE));
+ }
+ float alpha = Interpolators.getNotificationScrimAlpha(progress, true /* uiContent */);
+ view.setAlpha(alpha);
+ }
+
private void updateQsBounds() {
if (mLastQSExpansion == 1.0f) {
// Fully expanded, let's set the layout bounds as clip bounds. This is necessary because
diff --git a/packages/SystemUI/src/com/android/systemui/qs/user/UserDialog.kt b/packages/SystemUI/src/com/android/systemui/qs/user/UserDialog.kt
index 2ad06c1c172c..01afa56fc496 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/user/UserDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/user/UserDialog.kt
@@ -32,7 +32,7 @@ import com.android.systemui.R
*/
class UserDialog(
context: Context
-) : SystemUIDialog(context, R.style.Theme_SystemUI_Dialog_QSDialog) {
+) : SystemUIDialog(context) {
// create() is no-op after creation
private lateinit var _doneButton: View
@@ -72,7 +72,7 @@ class UserDialog(
attributes.fitInsetsTypes = attributes.fitInsetsTypes or WindowInsets.Type.statusBars()
attributes.receiveInsetsIgnoringZOrder = true
setLayout(
- context.resources.getDimensionPixelSize(R.dimen.qs_panel_width),
+ context.resources.getDimensionPixelSize(R.dimen.notification_panel_width),
ViewGroup.LayoutParams.WRAP_CONTENT
)
setGravity(Gravity.CENTER)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt b/packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt
index a5e4ba1b5b4b..bae7996517c5 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt
@@ -21,6 +21,7 @@ import android.content.Intent
import android.provider.Settings
import android.view.View
import androidx.annotation.VisibleForTesting
+import com.android.systemui.animation.DialogLaunchAnimator
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.FalsingManager
@@ -36,6 +37,7 @@ class UserSwitchDialogController @VisibleForTesting constructor(
private val userDetailViewAdapterProvider: Provider<UserDetailView.Adapter>,
private val activityStarter: ActivityStarter,
private val falsingManager: FalsingManager,
+ private val dialogLaunchAnimator: DialogLaunchAnimator,
private val dialogFactory: (Context) -> UserDialog
) {
@@ -43,11 +45,13 @@ class UserSwitchDialogController @VisibleForTesting constructor(
constructor(
userDetailViewAdapterProvider: Provider<UserDetailView.Adapter>,
activityStarter: ActivityStarter,
- falsingManager: FalsingManager
+ falsingManager: FalsingManager,
+ dialogLaunchAnimator: DialogLaunchAnimator
) : this(
userDetailViewAdapterProvider,
activityStarter,
falsingManager,
+ dialogLaunchAnimator,
{ UserDialog(it) }
)
@@ -69,7 +73,11 @@ class UserSwitchDialogController @VisibleForTesting constructor(
settingsButton.setOnClickListener {
if (!falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
- activityStarter.postStartActivityDismissingKeyguard(USER_SETTINGS_INTENT, 0)
+ dialogLaunchAnimator.disableAllCurrentDialogsExitAnimations()
+ activityStarter.postStartActivityDismissingKeyguard(
+ USER_SETTINGS_INTENT,
+ 0
+ )
}
dismiss()
}
@@ -81,7 +89,7 @@ class UserSwitchDialogController @VisibleForTesting constructor(
}
adapter.linkToViewGroup(grid)
- show()
+ dialogLaunchAnimator.showFromView(this, view)
}
}
} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index c76f01b9f192..721a6af37e21 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -248,6 +248,12 @@ public class OverviewProxyService extends CurrentUserTracker implements
onTaskbarStatusUpdated(visible, stashed));
}
+ @Override
+ public void notifyTaskbarAutohideSuspend(boolean suspend) {
+ verifyCallerAndClearCallingIdentityPostMain("notifyTaskbarAutohideSuspend", () ->
+ onTaskbarAutohideSuspend(suspend));
+ }
+
private boolean sendEvent(int action, int code) {
long when = SystemClock.uptimeMillis();
final KeyEvent ev = new KeyEvent(when, when, action, code, 0 /* repeat */,
@@ -818,6 +824,12 @@ public class OverviewProxyService extends CurrentUserTracker implements
}
}
+ private void onTaskbarAutohideSuspend(boolean suspend) {
+ for (int i = mConnectionCallbacks.size() - 1; i >= 0; --i) {
+ mConnectionCallbacks.get(i).onTaskbarAutohideSuspend(suspend);
+ }
+ }
+
private void notifyConnectionChanged() {
for (int i = mConnectionCallbacks.size() - 1; i >= 0; --i) {
mConnectionCallbacks.get(i).onConnectionChanged(mOverviewProxy != null);
@@ -1000,6 +1012,7 @@ public class OverviewProxyService extends CurrentUserTracker implements
default void onNavBarButtonAlphaChanged(float alpha, boolean animate) {}
default void onHomeRotationEnabled(boolean enabled) {}
default void onTaskbarStatusUpdated(boolean visible, boolean stashed) {}
+ default void onTaskbarAutohideSuspend(boolean suspend) {}
default void onSystemUiStateChanged(int sysuiStateFlags) {}
default void onAssistantProgress(@FloatRange(from = 0.0, to = 1.0) float progress) {}
default void onAssistantGestureCompletion(float velocity) {}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index 4f932a36504c..74ebfe5ad5e4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -147,7 +147,6 @@ public class KeyguardIndicationController {
private boolean mBatteryPresent = true;
private long mChargingTimeRemaining;
private String mMessageToShowOnScreenOn;
- protected int mLockScreenMode;
private boolean mInited;
private KeyguardUpdateMonitorCallback mUpdateMonitorCallback;
@@ -600,10 +599,6 @@ public class KeyguardIndicationController {
mHideTransientMessageOnScreenOff = hideOnScreenOff && transientIndication != null;
mHandler.removeMessages(MSG_HIDE_TRANSIENT);
mHandler.removeMessages(MSG_SHOW_ACTION_TO_UNLOCK);
- if (mDozing && !TextUtils.isEmpty(mTransientIndication)) {
- // Make sure this doesn't get stuck and burns in. Acquire wakelock until its cleared.
- mWakeLock.setAcquired(true);
- }
hideTransientIndicationDelayed(BaseKeyguardCallback.HIDE_DELAY_MS);
updateIndication(false);
@@ -623,10 +618,6 @@ public class KeyguardIndicationController {
}
protected final void updateIndication(boolean animate) {
- if (TextUtils.isEmpty(mTransientIndication)) {
- mWakeLock.setAcquired(false);
- }
-
if (!mVisible) {
return;
}
@@ -644,24 +635,31 @@ public class KeyguardIndicationController {
// colors can be hard to read in low brightness.
mTopIndicationView.setTextColor(Color.WHITE);
if (!TextUtils.isEmpty(mTransientIndication)) {
- mTopIndicationView.switchIndication(mTransientIndication, null);
+ mWakeLock.setAcquired(true);
+ mTopIndicationView.switchIndication(mTransientIndication, null,
+ true, () -> mWakeLock.setAcquired(false));
} else if (!mBatteryPresent) {
// If there is no battery detected, hide the indication and bail
mIndicationArea.setVisibility(GONE);
} else if (!TextUtils.isEmpty(mAlignmentIndication)) {
- mTopIndicationView.switchIndication(mAlignmentIndication, null);
+ mTopIndicationView.switchIndication(mAlignmentIndication, null,
+ false /* animate */, null /* onAnimationEndCallback */);
mTopIndicationView.setTextColor(mContext.getColor(R.color.misalignment_text_color));
} else if (mPowerPluggedIn || mEnableBatteryDefender) {
String indication = computePowerIndication();
if (animate) {
- animateText(mTopIndicationView, indication);
+ mWakeLock.setAcquired(true);
+ mTopIndicationView.switchIndication(indication, null, true /* animate */,
+ () -> mWakeLock.setAcquired(false));
} else {
- mTopIndicationView.switchIndication(indication, null);
+ mTopIndicationView.switchIndication(indication, null, false /* animate */,
+ null /* onAnimationEndCallback */);
}
} else {
String percentage = NumberFormat.getPercentInstance()
.format(mBatteryLevel / 100f);
- mTopIndicationView.switchIndication(percentage, null);
+ mTopIndicationView.switchIndication(percentage, null /* indication */,
+ false /* animate */, null /* onAnimationEnd*/);
}
return;
}
@@ -863,11 +861,6 @@ public class KeyguardIndicationController {
public static final int HIDE_DELAY_MS = 5000;
@Override
- public void onLockScreenModeChanged(int mode) {
- mLockScreenMode = mode;
- }
-
- @Override
public void onRefreshBatteryInfo(BatteryStatus status) {
boolean isChargingOrFull = status.status == BatteryManager.BATTERY_STATUS_CHARGING
|| status.status == BatteryManager.BATTERY_STATUS_FULL;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt
index 03d8e7e03c0f..77e329f94a36 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt
@@ -208,6 +208,11 @@ class LightRevealScrim(context: Context?, attrs: AttributeSet?) : View(context,
lateinit var isScrimOpaqueChangedListener: Consumer<Boolean>
/**
+ * A runnable to call when the scrim has been fully revealed. This is only invoked once
+ */
+ var fullyRevealedRunnable: Runnable? = null
+
+ /**
* How much of the underlying views are revealed, in percent. 0 means they will be completely
* obscured and 1 means they'll be fully visible.
*/
@@ -218,10 +223,20 @@ class LightRevealScrim(context: Context?, attrs: AttributeSet?) : View(context,
revealEffect.setRevealAmountOnScrim(value, this)
updateScrimOpaque()
+ maybeTriggerFullyRevealedRunnable()
invalidate()
}
}
+ private fun maybeTriggerFullyRevealedRunnable() {
+ if (revealAmount == 1.0f) {
+ fullyRevealedRunnable?.let {
+ it.run()
+ fullyRevealedRunnable = null
+ }
+ }
+ }
+
/**
* The [LightRevealEffect] used to manipulate the radial gradient whenever [revealAmount]
* changes.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
index ca18b076b1e9..dca7f70d3470 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
@@ -298,9 +298,8 @@ class LockscreenShadeTransitionController @Inject constructor(
nsslController.setTransitionToFullShadeAmount(field)
notificationPanelController.setTransitionToFullShadeAmount(field,
false /* animate */, 0 /* delay */)
- // TODO: appear qs also in split shade
- val qsAmount = if (useSplitShade) 0f else field
- qS.setTransitionToFullShadeAmount(qsAmount, false /* animate */)
+ val progress = MathUtils.saturate(dragDownAmount / scrimTransitionDistance)
+ qS.setTransitionToFullShadeAmount(field, progress)
// TODO: appear media also in split shade
val mediaAmount = if (useSplitShade) 0f else field
mediaHierarchyManager.setTransitionToFullShadeAmount(mediaAmount)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
index 7aa2dc7e0785..d4f54e1cd7b1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
@@ -36,7 +36,6 @@ import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dump.DumpManager
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.phone.BiometricUnlockController
-import com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK
import com.android.systemui.statusbar.phone.DozeParameters
import com.android.systemui.statusbar.phone.PanelExpansionListener
import com.android.systemui.statusbar.phone.ScrimController
@@ -73,6 +72,10 @@ class NotificationShadeDepthController @Inject constructor(
private const val TAG = "DepthController"
}
+ /**
+ * Did we already unblur while dozing?
+ */
+ private var alreadyUnblurredWhileDozing = false
lateinit var root: View
private var blurRoot: View? = null
private var keyguardAnimator: Animator? = null
@@ -229,9 +232,11 @@ class NotificationShadeDepthController @Inject constructor(
private val keyguardStateCallback = object : KeyguardStateController.Callback {
override fun onKeyguardFadingAwayChanged() {
if (!keyguardStateController.isKeyguardFadingAway ||
- biometricUnlockController.mode != MODE_WAKE_AND_UNLOCK) {
+ !biometricUnlockController.isWakeAndUnlock) {
return
}
+ // When wakeAndUnlocking the screen remains dozing, so we have to manually trigger
+ // the unblur earlier
keyguardAnimator?.cancel()
keyguardAnimator = ValueAnimator.ofFloat(1f, 0f).apply {
@@ -253,6 +258,7 @@ class NotificationShadeDepthController @Inject constructor(
})
start()
}
+ alreadyUnblurredWhileDozing = statusBarStateController.dozeAmount != 0.0f
}
override fun onKeyguardShowingChanged() {
@@ -274,10 +280,24 @@ class NotificationShadeDepthController @Inject constructor(
if (isDozing) {
shadeAnimation.finishIfRunning()
brightnessMirrorSpring.finishIfRunning()
+
+ // unset this for safety, to be ready for the next wakeup
+ alreadyUnblurredWhileDozing = false
}
}
override fun onDozeAmountChanged(linear: Float, eased: Float) {
+ if (alreadyUnblurredWhileDozing) {
+ if (linear == 0.0f) {
+ // We finished waking up, let's reset
+ alreadyUnblurredWhileDozing = false
+ } else {
+ // We've already handled the unbluring from the keyguardAnimator above.
+ // if we would continue, we'd play another unzoom / blur animation from the
+ // dozing changing.
+ return
+ }
+ }
wakeAndUnlockBlurRadius = blurUtils.blurRadiusOfRatio(eased)
scheduleUpdate()
}
@@ -435,6 +455,7 @@ class NotificationShadeDepthController @Inject constructor(
it.println("blursDisabledForAppLaunch: $blursDisabledForAppLaunch")
it.println("qsPanelExpansion: $qsPanelExpansion")
it.println("transitionToFullShadeProgress: $transitionToFullShadeProgress")
+ it.println("alreadyUnblurredWhileDozing: $alreadyUnblurredWhileDozing")
it.println("lastAppliedBlur: $lastAppliedBlur")
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/charging/WiredChargingRippleController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/charging/WiredChargingRippleController.kt
index d74297ee8b76..04c60fc197d8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/charging/WiredChargingRippleController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/charging/WiredChargingRippleController.kt
@@ -114,9 +114,6 @@ class WiredChargingRippleController @Inject constructor(
override fun onThemeChanged() {
updateRippleColor()
}
- override fun onOverlayChanged() {
- updateRippleColor()
- }
override fun onConfigChanged(newConfig: Configuration?) {
normalizedPortPosX = context.resources.getFloat(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java
index 0352212ced1b..d297d9581d6a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java
@@ -47,6 +47,7 @@ import com.android.systemui.statusbar.SmartReplyController;
import com.android.systemui.statusbar.StatusBarStateControllerImpl;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.commandline.CommandRegistry;
+import com.android.systemui.statusbar.gesture.SwipeStatusBarAwayGestureHandler;
import com.android.systemui.statusbar.notification.AssistantFeedbackController;
import com.android.systemui.statusbar.notification.DynamicChildBindController;
import com.android.systemui.statusbar.notification.DynamicPrivacyController;
@@ -255,15 +256,30 @@ public interface StatusBarDependenciesModule {
IActivityManager iActivityManager,
OngoingCallLogger logger,
DumpManager dumpManager,
- StatusBarWindowController statusBarWindowController) {
+ StatusBarWindowController statusBarWindowController,
+ SwipeStatusBarAwayGestureHandler swipeStatusBarAwayGestureHandler,
+ StatusBarStateController statusBarStateController) {
Optional<StatusBarWindowController> windowController =
featureFlags.isOngoingCallInImmersiveEnabled()
? Optional.of(statusBarWindowController)
: Optional.empty();
+ Optional<SwipeStatusBarAwayGestureHandler> gestureHandler =
+ featureFlags.isOngoingCallInImmersiveEnabled()
+ ? Optional.of(swipeStatusBarAwayGestureHandler)
+ : Optional.empty();
OngoingCallController ongoingCallController =
new OngoingCallController(
- notifCollection, featureFlags, systemClock, activityStarter, mainExecutor,
- iActivityManager, logger, dumpManager, windowController);
+ notifCollection,
+ featureFlags,
+ systemClock,
+ activityStarter,
+ mainExecutor,
+ iActivityManager,
+ logger,
+ dumpManager,
+ windowController,
+ gestureHandler,
+ statusBarStateController);
ongoingCallController.init();
return ongoingCallController;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeStatusBarAwayGestureHandler.kt b/packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeStatusBarAwayGestureHandler.kt
new file mode 100644
index 000000000000..80577ee24317
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeStatusBarAwayGestureHandler.kt
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.gesture
+
+import android.content.Context
+import android.os.Looper
+import android.view.Choreographer
+import android.view.Display
+import android.view.InputEvent
+import android.view.MotionEvent
+import android.view.MotionEvent.*
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.shared.system.InputChannelCompat
+import com.android.systemui.shared.system.InputMonitorCompat
+import com.android.systemui.statusbar.phone.StatusBarWindowController
+import javax.inject.Inject
+
+/**
+ * A class to detect when a user swipes away the status bar. To be notified when the swipe away
+ * gesture is detected, add a callback via [addOnGestureDetectedCallback].
+ */
+@SysUISingleton
+open class SwipeStatusBarAwayGestureHandler @Inject constructor(
+ context: Context,
+ private val statusBarWindowController: StatusBarWindowController,
+ private val logger: SwipeStatusBarAwayGestureLogger
+) {
+
+ /**
+ * Active callbacks, each associated with a tag. Gestures will only be monitored if
+ * [callbacks.size] > 0.
+ */
+ private val callbacks: MutableMap<String, () -> Unit> = mutableMapOf()
+
+ private var startY: Float = 0f
+ private var startTime: Long = 0L
+ private var monitoringCurrentTouch: Boolean = false
+
+ private var inputMonitor: InputMonitorCompat? = null
+ private var inputReceiver: InputChannelCompat.InputEventReceiver? = null
+
+ private var swipeDistanceThreshold: Int = context.resources.getDimensionPixelSize(
+ com.android.internal.R.dimen.system_gestures_start_threshold
+ )
+
+ /** Adds a callback that will be triggered when the swipe away gesture is detected. */
+ fun addOnGestureDetectedCallback(tag: String, callback: () -> Unit) {
+ val callbacksWasEmpty = callbacks.isEmpty()
+ callbacks[tag] = callback
+ if (callbacksWasEmpty) {
+ startGestureListening()
+ }
+ }
+
+ /** Removes the callback. */
+ fun removeOnGestureDetectedCallback(tag: String) {
+ callbacks.remove(tag)
+ if (callbacks.isEmpty()) {
+ stopGestureListening()
+ }
+ }
+
+ private fun onInputEvent(ev: InputEvent) {
+ if (ev !is MotionEvent) {
+ return
+ }
+
+ when (ev.actionMasked) {
+ ACTION_DOWN -> {
+ if (
+ // Gesture starts just below the status bar
+ ev.y >= statusBarWindowController.statusBarHeight
+ && ev.y <= 3 * statusBarWindowController.statusBarHeight
+ ) {
+ logger.logGestureDetectionStarted(ev.y.toInt())
+ startY = ev.y
+ startTime = ev.eventTime
+ monitoringCurrentTouch = true
+ } else {
+ monitoringCurrentTouch = false
+ }
+ }
+ ACTION_MOVE -> {
+ if (!monitoringCurrentTouch) {
+ return
+ }
+ if (
+ // Gesture is up
+ ev.y < startY
+ // Gesture went far enough
+ && (startY - ev.y) >= swipeDistanceThreshold
+ // Gesture completed quickly enough
+ && (ev.eventTime - startTime) < SWIPE_TIMEOUT_MS
+ ) {
+ monitoringCurrentTouch = false
+ logger.logGestureDetected(ev.y.toInt())
+ callbacks.values.forEach { it.invoke() }
+ }
+ }
+ ACTION_CANCEL, ACTION_UP -> {
+ if (monitoringCurrentTouch) {
+ logger.logGestureDetectionEndedWithoutTriggering(ev.y.toInt())
+ }
+ monitoringCurrentTouch = false
+ }
+ }
+ }
+
+ /** Start listening for the swipe gesture. */
+ private fun startGestureListening() {
+ stopGestureListening()
+
+ logger.logInputListeningStarted()
+ inputMonitor = InputMonitorCompat(TAG, Display.DEFAULT_DISPLAY).also {
+ inputReceiver = it.getInputReceiver(
+ Looper.getMainLooper(),
+ Choreographer.getInstance(),
+ this::onInputEvent
+ )
+ }
+ }
+
+ /** Stop listening for the swipe gesture. */
+ private fun stopGestureListening() {
+ inputMonitor?.let {
+ logger.logInputListeningStopped()
+ inputMonitor = null
+ it.dispose()
+ }
+ inputReceiver?.let {
+ inputReceiver = null
+ it.dispose()
+ }
+ }
+}
+
+private const val SWIPE_TIMEOUT_MS: Long = 500
+private val TAG = SwipeStatusBarAwayGestureHandler::class.simpleName
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeStatusBarAwayGestureLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeStatusBarAwayGestureLogger.kt
new file mode 100644
index 000000000000..17feaa842165
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeStatusBarAwayGestureLogger.kt
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.gesture
+
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
+import com.android.systemui.log.dagger.SwipeStatusBarAwayLog
+import javax.inject.Inject
+
+/** Log messages for [SwipeStatusBarAwayGestureHandler]. */
+class SwipeStatusBarAwayGestureLogger @Inject constructor(
+ @SwipeStatusBarAwayLog private val buffer: LogBuffer
+) {
+ fun logGestureDetectionStarted(y: Int) {
+ buffer.log(
+ TAG,
+ LogLevel.DEBUG,
+ { int1 = y },
+ { "Beginning gesture detection. y=$int1" }
+ )
+ }
+
+ fun logGestureDetectionEndedWithoutTriggering(y: Int) {
+ buffer.log(
+ TAG,
+ LogLevel.DEBUG,
+ { int1 = y },
+ { "Gesture finished; no swipe up gesture detected. Final y=$int1" }
+ )
+ }
+
+ fun logGestureDetected(y: Int) {
+ buffer.log(
+ TAG,
+ LogLevel.INFO,
+ { int1 = y },
+ { "Gesture detected; notifying callbacks. y=$int1" }
+ )
+ }
+
+ fun logInputListeningStarted() {
+ buffer.log(TAG, LogLevel.VERBOSE, {}, { "Input listening started "})
+ }
+
+ fun logInputListeningStopped() {
+ buffer.log(TAG, LogLevel.VERBOSE, {}, { "Input listening stopped "})
+ }
+}
+
+private const val TAG = "SwipeStatusBarAwayGestureHandler" \ No newline at end of file
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 bdade2c6c2c9..bacb85ae89df 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
@@ -29,6 +29,7 @@ import android.net.Uri
import android.os.Handler
import android.os.UserHandle
import android.provider.Settings
+import android.util.Log
import android.view.View
import android.view.ViewGroup
import com.android.settingslib.Utils
@@ -73,6 +74,10 @@ class LockscreenSmartspaceController @Inject constructor(
@Main private val handler: Handler,
optionalPlugin: Optional<BcSmartspaceDataPlugin>
) {
+ companion object {
+ private const val TAG = "LockscreenSmartspaceController"
+ }
+
private var session: SmartspaceSession? = null
private val plugin: BcSmartspaceDataPlugin? = optionalPlugin.orElse(null)
@@ -210,6 +215,7 @@ class LockscreenSmartspaceController @Inject constructor(
val newSession = smartspaceManager.createSmartspaceSession(
SmartspaceConfig.Builder(context, "lockscreen").build())
+ Log.d(TAG, "Starting smartspace session for lockscreen")
newSession.addOnTargetsAvailableListener(uiExecutor, sessionListener)
this.session = newSession
@@ -231,6 +237,8 @@ class LockscreenSmartspaceController @Inject constructor(
* Disconnects the smartspace view from the smartspace service and cleans up any resources.
*/
fun disconnect() {
+ if (!smartspaceViews.isEmpty()) return
+
execution.assertIsMainThread()
if (session == null) {
@@ -248,6 +256,7 @@ class LockscreenSmartspaceController @Inject constructor(
session = null
plugin?.onTargetsAvailable(emptyList())
+ Log.d(TAG, "Ending smartspace session for lockscreen")
}
fun addListener(listener: SmartspaceTargetListener) {
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 9fe06a0ddbdc..b226aec88cd8 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
@@ -272,15 +272,6 @@ public class NotificationStackScrollLayoutController {
}
@Override
- public void onOverlayChanged() {
- updateShowEmptyShadeView();
- mView.updateCornerRadius();
- mView.updateBgColor();
- mView.updateDecorViews();
- mView.reinflateViews();
- }
-
- @Override
public void onUiModeChanged() {
mView.updateBgColor();
mView.updateDecorViews();
@@ -288,6 +279,11 @@ public class NotificationStackScrollLayoutController {
@Override
public void onThemeChanged() {
+ updateShowEmptyShadeView();
+ mView.updateCornerRadius();
+ mView.updateBgColor();
+ mView.updateDecorViews();
+ mView.reinflateViews();
updateFooter();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoHideController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoHideController.java
index aeb2efd2026a..e12009e655b5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoHideController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoHideController.java
@@ -37,6 +37,7 @@ public class AutoHideController {
private final Handler mHandler;
private AutoHideUiElement mStatusBar;
+ /** For tablets, this will represent the Taskbar */
private AutoHideUiElement mNavigationBar;
private int mDisplayId;
@@ -89,7 +90,7 @@ public class AutoHideController {
}
}
- void resumeSuspendedAutoHide() {
+ public void resumeSuspendedAutoHide() {
if (mAutoHideSuspended) {
scheduleAutoHide();
Runnable checkBarModesRunnable = getCheckBarModesRunnable();
@@ -99,7 +100,7 @@ public class AutoHideController {
}
}
- void suspendAutoHide() {
+ public void suspendAutoHide() {
mHandler.removeCallbacks(mAutoHide);
Runnable checkBarModesRunnable = getCheckBarModesRunnable();
if (checkBarModesRunnable != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt
index 12ae3f1d66cb..96fa8a5cfd71 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt
@@ -123,7 +123,7 @@ class ConfigurationControllerImpl(context: Context) : ConfigurationController {
if (lastConfig.updateFrom(newConfig) and ActivityInfo.CONFIG_ASSETS_PATHS != 0) {
listeners.filterForEach({ this.listeners.contains(it) }) {
- it.onOverlayChanged()
+ it.onThemeChanged()
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
index 4b545ebf2a05..5f402d0d861d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
@@ -121,7 +121,7 @@ public class HeadsUpManagerPhone extends HeadsUpManager implements Dumpable,
}
@Override
- public void onOverlayChanged() {
+ public void onThemeChanged() {
updateResources();
}
});
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextView.java
index a5b5f1cbf1e7..3a68b9c3d1b3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextView.java
@@ -95,44 +95,85 @@ public class KeyguardIndicationTextView extends TextView {
}
/**
- * Changes the text with an animation and makes sure a single indication is shown long enough.
+ * Changes the text with an animation. Makes sure a single indication is shown long enough.
+ */
+ public void switchIndication(CharSequence text, KeyguardIndication indication) {
+ switchIndication(text, indication, true, null);
+ }
+
+ /**
+ * Changes the text with an optional animation. For animating text, makes sure a single
+ * indication is shown long enough.
*
* @param text The text to show.
* @param indication optional display information for the text
+ * @param animate whether to animate this indication in - we may not want this on AOD
+ * @param onAnimationEndCallback runnable called after this indication is animated in
*/
- public void switchIndication(CharSequence text, KeyguardIndication indication) {
+ public void switchIndication(CharSequence text, KeyguardIndication indication,
+ boolean animate, Runnable onAnimationEndCallback) {
if (text == null) text = "";
CharSequence lastPendingMessage = mMessages.peekLast();
if (TextUtils.equals(lastPendingMessage, text)
|| (lastPendingMessage == null && TextUtils.equals(text, getText()))) {
+ if (onAnimationEndCallback != null) {
+ onAnimationEndCallback.run();
+ }
return;
}
mMessages.add(text);
mKeyguardIndicationInfo.add(indication);
- final boolean hasIcon = indication != null && indication.getIcon() != null;
- final AnimatorSet animSet = new AnimatorSet();
- final AnimatorSet.Builder animSetBuilder = animSet.play(getOutAnimator());
-
- // Make sure each animation is visible for a minimum amount of time, while not worrying
- // about fading in blank text
- long timeInMillis = System.currentTimeMillis();
- long delay = Math.max(0, mNextAnimationTime - timeInMillis);
- setNextAnimationTime(timeInMillis + delay + getFadeOutDuration());
-
- final long minDurationMillis =
- (indication != null && indication.getMinVisibilityMillis() != null)
- ? indication.getMinVisibilityMillis()
- : MSG_MIN_DURATION_MILLIS_DEFAULT;
+ if (animate) {
+ final boolean hasIcon = indication != null && indication.getIcon() != null;
+ final AnimatorSet animator = new AnimatorSet();
+ // Make sure each animation is visible for a minimum amount of time, while not worrying
+ // about fading in blank text
+ long timeInMillis = System.currentTimeMillis();
+ long delay = Math.max(0, mNextAnimationTime - timeInMillis);
+ setNextAnimationTime(timeInMillis + delay + getFadeOutDuration());
+ final long minDurationMillis =
+ (indication != null && indication.getMinVisibilityMillis() != null)
+ ? indication.getMinVisibilityMillis()
+ : MSG_MIN_DURATION_MILLIS_DEFAULT;
+ if (!text.equals("") || hasIcon) {
+ setNextAnimationTime(mNextAnimationTime + minDurationMillis);
+ Animator inAnimator = getInAnimator();
+ inAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ super.onAnimationEnd(animation);
+ if (onAnimationEndCallback != null) {
+ onAnimationEndCallback.run();
+ }
+ }
+ });
+ animator.playSequentially(getOutAnimator(), inAnimator);
+ } else {
+ Animator outAnimator = getOutAnimator();
+ outAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ super.onAnimationEnd(animation);
+ if (onAnimationEndCallback != null) {
+ onAnimationEndCallback.run();
+ }
+ }
+ });
+ animator.play(outAnimator);
+ }
- if (!text.equals("") || hasIcon) {
- setNextAnimationTime(mNextAnimationTime + minDurationMillis);
- animSetBuilder.before(getInAnimator());
+ animator.setStartDelay(delay);
+ animator.start();
+ } else {
+ setAlpha(1f);
+ setTranslationY(0f);
+ setNextIndication();
+ if (onAnimationEndCallback != null) {
+ onAnimationEndCallback.run();
+ }
}
-
- animSet.setStartDelay(delay);
- animSet.start();
}
private AnimatorSet getOutAnimator() {
@@ -143,29 +184,8 @@ public class KeyguardIndicationTextView extends TextView {
fadeOut.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animator) {
- KeyguardIndication info = mKeyguardIndicationInfo.poll();
- if (info != null) {
- // First, update the style.
- // If a background is set on the text, we don't want shadow on the text
- if (info.getBackground() != null) {
- setTextAppearance(sButtonStyleId);
- } else {
- setTextAppearance(sStyleId);
- }
- setBackground(info.getBackground());
- setTextColor(info.getTextColor());
- setOnClickListener(info.getClickListener());
- setClickable(info.getClickListener() != null);
- final Drawable icon = info.getIcon();
- if (icon != null) {
- icon.setTint(getCurrentTextColor());
- if (icon instanceof AnimatedVectorDrawable) {
- ((AnimatedVectorDrawable) icon).start();
- }
- }
- setCompoundDrawablesRelativeWithIntrinsicBounds(icon, null, null, null);
- }
- setText(mMessages.poll());
+ super.onAnimationEnd(animator);
+ setNextIndication();
}
});
@@ -177,6 +197,32 @@ public class KeyguardIndicationTextView extends TextView {
return animatorSet;
}
+ private void setNextIndication() {
+ KeyguardIndication info = mKeyguardIndicationInfo.poll();
+ if (info != null) {
+ // First, update the style.
+ // If a background is set on the text, we don't want shadow on the text
+ if (info.getBackground() != null) {
+ setTextAppearance(sButtonStyleId);
+ } else {
+ setTextAppearance(sStyleId);
+ }
+ setBackground(info.getBackground());
+ setTextColor(info.getTextColor());
+ setOnClickListener(info.getClickListener());
+ setClickable(info.getClickListener() != null);
+ final Drawable icon = info.getIcon();
+ if (icon != null) {
+ icon.setTint(getCurrentTextColor());
+ if (icon instanceof AnimatedVectorDrawable) {
+ ((AnimatedVectorDrawable) icon).start();
+ }
+ }
+ setCompoundDrawablesRelativeWithIntrinsicBounds(icon, null, null, null);
+ }
+ setText(mMessages.poll());
+ }
+
private AnimatorSet getInAnimator() {
AnimatorSet animatorSet = new AnimatorSet();
ObjectAnimator fadeIn = ObjectAnimator.ofFloat(this, View.ALPHA, 1f);
@@ -190,6 +236,7 @@ public class KeyguardIndicationTextView extends TextView {
yTranslate.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationCancel(Animator animation) {
+ super.onAnimationCancel(animation);
setTranslationY(0);
}
});
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
index 5feb4053f833..90550818bbdd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
@@ -100,13 +100,8 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat
}
@Override
- public void onOverlayChanged() {
- mView.onOverlayChanged();
- KeyguardStatusBarViewController.this.onThemeChanged();
- }
-
- @Override
public void onThemeChanged() {
+ mView.onOverlayChanged();
KeyguardStatusBarViewController.this.onThemeChanged();
}
};
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java
index 78fcd82dc1f5..2a13e6bbd37e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java
@@ -119,7 +119,6 @@ public class LockscreenWallpaper extends IWallpaperManagerCallback.Stub implemen
LoaderResult result = loadBitmap(mCurrentUserId, mSelectedUser);
if (result.success) {
mCached = true;
- mUpdateMonitor.setHasLockscreenWallpaper(result.bitmap != null);
mCache = result.bitmap;
}
return mCache;
@@ -235,7 +234,6 @@ public class LockscreenWallpaper extends IWallpaperManagerCallback.Stub implemen
if (result.success) {
mCached = true;
mCache = result.bitmap;
- mUpdateMonitor.setHasLockscreenWallpaper(result.bitmap != null);
mMediaManager.updateMediaMetaData(
true /* metaDataChanged */, true /* allowEnterAnimation */);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
index 7b3f34a4a510..8c8e4e7e8631 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
@@ -983,7 +983,7 @@ public class NotificationPanelViewController extends PanelViewController {
Utils.shouldUseSplitNotificationShade(mResources);
mScrimController.setClipsQsScrim(!mShouldUseSplitNotificationShade);
if (mQs != null) {
- mQs.setTranslateWhileExpanding(mShouldUseSplitNotificationShade);
+ mQs.setInSplitShade(mShouldUseSplitNotificationShade);
}
int topMargin = mShouldUseSplitNotificationShade ? mSplitShadeStatusBarHeight :
@@ -2201,7 +2201,7 @@ public class NotificationPanelViewController extends PanelViewController {
private void updateQsExpansion() {
if (mQs == null) return;
float qsExpansionFraction = computeQsExpansionFraction();
- mQs.setQsExpansion(qsExpansionFraction, getHeaderTranslation());
+ mQs.setQsExpansion(qsExpansionFraction, getExpandedFraction(), getHeaderTranslation());
mMediaHierarchyManager.setQsExpansion(qsExpansionFraction);
int qsPanelBottomY = calculateQsBottomPosition(qsExpansionFraction);
mScrimController.setQsPosition(qsExpansionFraction, qsPanelBottomY);
@@ -3295,9 +3295,9 @@ public class NotificationPanelViewController extends PanelViewController {
* cases, such as if there's a heads-up notification.
*/
public void setPanelScrimMinFraction(float minFraction) {
- mBar.onPanelMinFractionChanged(minFraction);
mMinFraction = minFraction;
mDepthController.setPanelPullDownMinFraction(mMinFraction);
+ mScrimController.setPanelScrimMinFraction(mMinFraction);
}
public void clearNotificationEffects() {
@@ -3413,7 +3413,7 @@ public class NotificationPanelViewController extends PanelViewController {
mQs.setExpandClickListener(mOnClickListener);
mQs.setHeaderClickable(isQsExpansionEnabled());
mQs.setOverscrolling(mStackScrollerOverscrolling);
- mQs.setTranslateWhileExpanding(mShouldUseSplitNotificationShade);
+ mQs.setInSplitShade(mShouldUseSplitNotificationShade);
// recompute internal state when qspanel height changes
mQs.getView().addOnLayoutChangeListener(
@@ -4211,12 +4211,7 @@ public class NotificationPanelViewController extends PanelViewController {
@Override
public void onThemeChanged() {
if (DEBUG) Log.d(TAG, "onThemeChanged");
- final int themeResId = mView.getContext().getThemeResId();
- if (mThemeResId == themeResId) {
- return;
- }
- mThemeResId = themeResId;
-
+ mThemeResId = mView.getContext().getThemeResId();
reInflateViews();
}
@@ -4230,12 +4225,6 @@ public class NotificationPanelViewController extends PanelViewController {
}
@Override
- public void onOverlayChanged() {
- if (DEBUG) Log.d(TAG, "onOverlayChanged");
- reInflateViews();
- }
-
- @Override
public void onDensityOrFontScaleChanged() {
if (DEBUG) Log.d(TAG, "onDensityOrFontScaleChanged");
reInflateViews();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java
index 1f1090d7168b..310fe73df1ea 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java
@@ -146,11 +146,6 @@ public abstract class PanelBar extends FrameLayout {
}
/**
- * Percentage of panel expansion offset, caused by pulling down on a heads-up.
- */
- abstract void onPanelMinFractionChanged(float minFraction);
-
- /**
* @param frac the fraction from the expansion in [0, 1]
* @param expanded whether the panel is currently expanded; this is independent from the
* fraction as the panel also might be expanded if the fraction is 0
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
index 768567b8b474..c23577c523e3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
@@ -385,11 +385,16 @@ public abstract class PanelViewController {
final boolean expand;
if (event.getActionMasked() == MotionEvent.ACTION_CANCEL || forceCancel) {
- // If we get a cancel, put the shade back to the state it was in when the gesture
- // started
- if (onKeyguard) {
+ // If the keyguard is fading away, don't expand it again. This can happen if you're
+ // swiping to unlock, the app below the keyguard is in landscape, and the screen
+ // rotates while your finger is still down after the swipe to unlock.
+ if (mKeyguardStateController.isKeyguardFadingAway()) {
+ expand = false;
+ } else if (onKeyguard) {
expand = true;
} else {
+ // If we get a cancel, put the shade back to the state it was in when the
+ // gesture started
expand = !mPanelClosedOnDown;
}
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
index 1cca4777da0a..150d9c8fcce6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
@@ -18,8 +18,6 @@ package com.android.systemui.statusbar.phone;
import static com.android.systemui.ScreenDecorations.DisplayCutoutView.boundsFromDirection;
-import static java.lang.Float.isNaN;
-
import android.annotation.Nullable;
import android.content.Context;
import android.content.res.Configuration;
@@ -57,7 +55,6 @@ public class PhoneStatusBarView extends PanelBar {
StatusBar mBar;
private ScrimController mScrimController;
- private float mMinFraction;
private Runnable mHideExpandedRunnable = new Runnable() {
@Override
public void run() {
@@ -268,20 +265,8 @@ public class PhoneStatusBarView extends PanelBar {
}
@Override
- public void onPanelMinFractionChanged(float minFraction) {
- if (isNaN(minFraction)) {
- throw new IllegalArgumentException("minFraction cannot be NaN");
- }
- if (mMinFraction != minFraction) {
- mMinFraction = minFraction;
- updateScrimFraction();
- }
- }
-
- @Override
public void panelExpansionChanged(float frac, boolean expanded) {
super.panelExpansionChanged(frac, expanded);
- updateScrimFraction();
if ((frac == 0 || frac == 1)) {
if (mPanelExpansionStateChangedListener != null) {
mPanelExpansionStateChangedListener.onPanelExpansionStateChanged();
@@ -302,15 +287,6 @@ public class PhoneStatusBarView extends PanelBar {
mPanelEnabledProvider = panelEnabledProvider;
}
- private void updateScrimFraction() {
- float scrimFraction = mPanelFraction;
- if (mMinFraction < 1.0f) {
- scrimFraction = Math.max((mPanelFraction - mMinFraction) / (1.0f - mMinFraction),
- 0);
- }
- mScrimController.setPanelExpansion(scrimFraction);
- }
-
public void updateResources() {
mCutoutSideNudge = getResources().getDimensionPixelSize(
R.dimen.display_cutout_margin_consumption);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index a564637aa510..a5cea06ad893 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -34,6 +34,7 @@ import android.view.ViewTreeObserver;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.Interpolator;
+import androidx.annotation.FloatRange;
import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
@@ -183,8 +184,10 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump
private float mScrimBehindAlphaKeyguard = KEYGUARD_SCRIM_ALPHA;
private final float mDefaultScrimAlpha;
- // Assuming the shade is expanded during initialization
- private float mPanelExpansion = 1f;
+ private float mRawPanelExpansionFraction;
+ private float mPanelScrimMinFraction;
+ // Calculated based on mRawPanelExpansionFraction and mPanelScrimMinFraction
+ private float mPanelExpansionFraction = 1f; // Assume shade is expanded during initialization
private float mQsExpansion;
private boolean mQsBottomVisible;
@@ -262,11 +265,6 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump
}
@Override
- public void onOverlayChanged() {
- ScrimController.this.onThemeChanged();
- }
-
- @Override
public void onUiModeChanged() {
ScrimController.this.onThemeChanged();
}
@@ -483,14 +481,39 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump
*
* The expansion fraction is tied to the scrim opacity.
*
- * @param fraction From 0 to 1 where 0 means collapsed and 1 expanded.
+ * See {@link PanelBar#panelExpansionChanged}.
+ *
+ * @param rawPanelExpansionFraction From 0 to 1 where 0 means collapsed and 1 expanded.
*/
- public void setPanelExpansion(float fraction) {
- if (isNaN(fraction)) {
- throw new IllegalArgumentException("Fraction should not be NaN");
+ public void setRawPanelExpansionFraction(
+ @FloatRange(from = 0.0, to = 1.0) float rawPanelExpansionFraction) {
+ if (isNaN(rawPanelExpansionFraction)) {
+ throw new IllegalArgumentException("rawPanelExpansionFraction should not be NaN");
+ }
+ mRawPanelExpansionFraction = rawPanelExpansionFraction;
+ calculateAndUpdatePanelExpansion();
+ }
+
+ /** See {@link NotificationPanelViewController#setPanelScrimMinFraction(float)}. */
+ public void setPanelScrimMinFraction(float minFraction) {
+ if (isNaN(minFraction)) {
+ throw new IllegalArgumentException("minFraction should not be NaN");
+ }
+ mPanelScrimMinFraction = minFraction;
+ calculateAndUpdatePanelExpansion();
+ }
+
+ private void calculateAndUpdatePanelExpansion() {
+ float panelExpansionFraction = mRawPanelExpansionFraction;
+ if (mPanelScrimMinFraction < 1.0f) {
+ panelExpansionFraction = Math.max(
+ (mRawPanelExpansionFraction - mPanelScrimMinFraction)
+ / (1.0f - mPanelScrimMinFraction),
+ 0);
}
- if (mPanelExpansion != fraction) {
- mPanelExpansion = fraction;
+
+ if (mPanelExpansionFraction != panelExpansionFraction) {
+ mPanelExpansionFraction = panelExpansionFraction;
boolean relevantState = (mState == ScrimState.UNLOCKED
|| mState == ScrimState.KEYGUARD
@@ -892,7 +915,8 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump
}
private float getInterpolatedFraction() {
- return Interpolators.getNotificationScrimAlpha(mPanelExpansion, false /* notification */);
+ return Interpolators.getNotificationScrimAlpha(
+ mPanelExpansionFraction, false /* notification */);
}
private void setScrimAlpha(ScrimView scrim, float alpha) {
@@ -1228,8 +1252,8 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump
pw.println(mTracking);
pw.print(" mDefaultScrimAlpha=");
pw.println(mDefaultScrimAlpha);
- pw.print(" mExpansionFraction=");
- pw.println(mPanelExpansion);
+ pw.print(" mPanelExpansionFraction=");
+ pw.println(mPanelExpansionFraction);
pw.print(" mExpansionAffectsAlpha=");
pw.println(mExpansionAffectsAlpha);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index 7b3fc849df03..16f67bcf5f90 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -336,6 +336,7 @@ public class StatusBar extends SystemUI implements
}
private final LockscreenShadeTransitionController mLockscreenShadeTransitionController;
+ private boolean mCallingFadingAwayAfterReveal;
private StatusBarCommandQueueCallbacks mCommandQueueCallbacks;
void setWindowState(int state) {
@@ -899,6 +900,8 @@ public class StatusBar extends SystemUI implements
lockscreenShadeTransitionController.setStatusbar(this);
mExpansionChangedListeners = new ArrayList<>();
+ addExpansionChangedListener(
+ (expansion, expanded) -> mScrimController.setRawPanelExpansionFraction(expansion));
mBubbleExpandListener =
(isExpanding, key) -> mContext.getMainExecutor().execute(() -> {
@@ -1404,6 +1407,12 @@ public class StatusBar extends SystemUI implements
mDeviceProvisionedController.addCallback(mUserSetupObserver);
mUserSetupObserver.onUserSetupChanged();
+ for (ExpansionChangedListener listener : mExpansionChangedListeners) {
+ // The initial expansion amount comes from mNotificationPanelViewController, so we
+ // should send the amount once we've fully set up that controller.
+ sendInitialExpansionAmount(listener);
+ }
+
// disable profiling bars, since they overlap and clutter the output on app windows
ThreadedRenderer.overrideProperty("disableProfileBars", "true");
@@ -3131,8 +3140,20 @@ public class StatusBar extends SystemUI implements
public void fadeKeyguardWhilePulsing() {
mNotificationPanelViewController.fadeOut(0, FADE_KEYGUARD_DURATION_PULSING,
()-> {
- hideKeyguard();
- mStatusBarKeyguardViewManager.onKeyguardFadedAway();
+ Runnable finishFading = () -> {
+ mCallingFadingAwayAfterReveal = false;
+ hideKeyguard();
+ mStatusBarKeyguardViewManager.onKeyguardFadedAway();
+ };
+ if (mLightRevealScrim.getRevealAmount() != 1.0f) {
+ mCallingFadingAwayAfterReveal = true;
+ // We're still revealing the Light reveal, let's only go to keyguard once
+ // that has finished and nothing moves anymore.
+ // Going there introduces lots of jank
+ mLightRevealScrim.setFullyRevealedRunnable(finishFading);
+ } else {
+ finishFading.run();
+ }
}).start();
}
@@ -3266,16 +3287,13 @@ public class StatusBar extends SystemUI implements
* Switches theme from light to dark and vice-versa.
*/
protected void updateTheme() {
-
// Lock wallpaper defines the color of the majority of the views, hence we'll use it
// to set our default theme.
final boolean lockDarkText = mColorExtractor.getNeutralColors().supportsDarkText();
final int themeResId = lockDarkText ? R.style.Theme_SystemUI_LightWallpaper
: R.style.Theme_SystemUI;
- if (mContext.getThemeResId() != themeResId) {
- mContext.setTheme(themeResId);
- mConfigurationController.notifyThemeChanged();
- }
+ mContext.setTheme(themeResId);
+ mConfigurationController.notifyThemeChanged();
}
private void updateDozingState() {
@@ -4233,9 +4251,11 @@ public class StatusBar extends SystemUI implements
}
private void sendInitialExpansionAmount(ExpansionChangedListener expansionChangedListener) {
- expansionChangedListener.onExpansionChanged(
- mNotificationPanelViewController.getExpandedFraction(),
- mNotificationPanelViewController.isExpanded());
+ if (mNotificationPanelViewController != null) {
+ expansionChangedListener.onExpansionChanged(
+ mNotificationPanelViewController.getExpandedFraction(),
+ mNotificationPanelViewController.isExpanded());
+ }
}
public void removeExpansionChangedListener(@NonNull ExpansionChangedListener listener) {
@@ -4291,7 +4311,7 @@ public class StatusBar extends SystemUI implements
+ "mStatusBarKeyguardViewManager was null");
return;
}
- if (mKeyguardStateController.isKeyguardFadingAway()) {
+ if (mKeyguardStateController.isKeyguardFadingAway() && !mCallingFadingAwayAfterReveal) {
mStatusBarKeyguardViewManager.onKeyguardFadedAway();
}
}
@@ -4386,6 +4406,13 @@ public class StatusBar extends SystemUI implements
@Override
public void onThemeChanged() {
+ if (mBrightnessMirrorController != null) {
+ mBrightnessMirrorController.onOverlayChanged();
+ }
+ // We need the new R.id.keyguard_indication_area before recreating
+ // mKeyguardIndicationController
+ mNotificationPanelViewController.onThemeChanged();
+
if (mStatusBarKeyguardViewManager != null) {
mStatusBarKeyguardViewManager.onThemeChanged();
}
@@ -4396,17 +4423,6 @@ public class StatusBar extends SystemUI implements
}
@Override
- public void onOverlayChanged() {
- if (mBrightnessMirrorController != null) {
- mBrightnessMirrorController.onOverlayChanged();
- }
- // We need the new R.id.keyguard_indication_area before recreating
- // mKeyguardIndicationController
- mNotificationPanelViewController.onThemeChanged();
- onThemeChanged();
- }
-
- @Override
public void onUiModeChanged() {
if (mBrightnessMirrorController != null) {
mBrightnessMirrorController.onUiModeChanged();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt
index 61552f065bc4..98be77dfa421 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt
@@ -91,7 +91,7 @@ class StatusBarContentInsetsProvider @Inject constructor(
clearCachedInsets()
}
- override fun onOverlayChanged() {
+ override fun onThemeChanged() {
clearCachedInsets()
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
index 832f317d5783..c655964e64bc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
@@ -25,7 +25,6 @@ import android.content.Context;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemClock;
-import android.service.notification.NotificationListenerService;
import android.service.notification.StatusBarNotification;
import android.service.vr.IVrManager;
import android.service.vr.IVrStateCallbacks;
@@ -248,7 +247,7 @@ public class StatusBarNotificationPresenter implements NotificationPresenter,
}
@Override
- public void onOverlayChanged() {
+ public void onThemeChanged() {
onDensityOrFontScaleChanged();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java
index d3d90639546a..eb405e9bdea3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java
@@ -83,7 +83,7 @@ public final class StatusBarTouchableRegionManager implements Dumpable {
}
@Override
- public void onOverlayChanged() {
+ public void onThemeChanged() {
initResources();
}
});
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowController.java
index b708861ecc74..235a8e85a223 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowController.java
@@ -208,9 +208,18 @@ public class StatusBarWindowController {
apply(mCurrentState);
}
- /** Sets whether there is currently an ongoing call. */
- public void setIsCallOngoing(boolean isCallOngoing) {
- mCurrentState.mIsCallOngoing = isCallOngoing;
+ /**
+ * Sets whether an ongoing process requires the status bar to be forced visible.
+ *
+ * This method is separate from {@link this#setForceStatusBarVisible} because the ongoing
+ * process **takes priority**. For example, if {@link this#setForceStatusBarVisible} is set to
+ * false but this method is set to true, then the status bar **will** be visible.
+ *
+ * TODO(b/195839150): We should likely merge this method and
+ * {@link this#setForceStatusBarVisible} together and use some sort of ranking system instead.
+ */
+ public void setOngoingProcessRequiresStatusBarVisible(boolean visible) {
+ mCurrentState.mOngoingProcessRequiresStatusBarVisible = visible;
apply(mCurrentState);
}
@@ -254,13 +263,14 @@ public class StatusBarWindowController {
private static class State {
boolean mForceStatusBarVisible;
boolean mIsLaunchAnimationRunning;
- boolean mIsCallOngoing;
+ boolean mOngoingProcessRequiresStatusBarVisible;
}
private void applyForceStatusBarVisibleFlag(State state) {
if (state.mForceStatusBarVisible
|| state.mIsLaunchAnimationRunning
- || state.mIsCallOngoing) {
+ // Don't force-show the status bar if the user has already dismissed it.
+ || state.mOngoingProcessRequiresStatusBarVisible) {
mLpChanged.privateFlags |= PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR;
} else {
mLpChanged.privateFlags &= ~PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/TapAgainViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/TapAgainViewController.java
index 0c5502bac8fc..26ba31c6f526 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/TapAgainViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/TapAgainViewController.java
@@ -43,11 +43,6 @@ public class TapAgainViewController extends ViewController<TapAgainView> {
@VisibleForTesting
final ConfigurationListener mConfigurationListener = new ConfigurationListener() {
@Override
- public void onOverlayChanged() {
- mView.updateColor();
- }
-
- @Override
public void onUiModeChanged() {
mView.updateColor();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
index 143aaba648da..f3f3325f49d7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
@@ -4,10 +4,10 @@ import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.animation.ValueAnimator
import android.content.Context
-import android.content.res.Configuration
import android.database.ContentObserver
import android.os.Handler
import android.provider.Settings
+import android.view.Surface
import android.view.View
import com.android.systemui.animation.Interpolators
import com.android.systemui.dagger.SysUISingleton
@@ -239,10 +239,11 @@ class UnlockedScreenOffAnimationController @Inject constructor(
return false
}
- // If we're not allowed to rotate the keyguard, then only do the screen off animation if
- // we're in portrait. Otherwise, AOD will animate in sideways, which looks weird.
+ // If we're not allowed to rotate the keyguard, it can only be displayed in zero-degree
+ // portrait. If we're in another orientation, disable the screen off animation so we don't
+ // animate in the keyguard AOD UI sideways or upside down.
if (!keyguardStateController.isKeyguardScreenRotationAllowed &&
- context.resources.configuration.orientation != Configuration.ORIENTATION_PORTRAIT) {
+ context.display.rotation != Surface.ROTATION_0) {
return false
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
index b7c80def7dad..3806d9a2925c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
@@ -34,6 +34,8 @@ import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.dump.DumpManager
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.statusbar.gesture.SwipeStatusBarAwayGestureHandler
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
@@ -60,8 +62,11 @@ class OngoingCallController @Inject constructor(
private val logger: OngoingCallLogger,
private val dumpManager: DumpManager,
private val statusBarWindowController: Optional<StatusBarWindowController>,
+ private val swipeStatusBarAwayGestureHandler: Optional<SwipeStatusBarAwayGestureHandler>,
+ private val statusBarStateController: StatusBarStateController,
) : CallbackController<OngoingCallListener>, Dumpable {
+ private var isFullscreen: Boolean = false
/** Non-null if there's an active call notification. */
private var callNotificationInfo: CallNotificationInfo? = null
/** True if the application managing the call is visible to the user. */
@@ -96,7 +101,8 @@ class OngoingCallController @Inject constructor(
entry.sbn.notification.contentIntent?.intent,
entry.sbn.uid,
entry.sbn.notification.extras.getInt(
- Notification.EXTRA_CALL_TYPE, -1) == CALL_TYPE_ONGOING
+ Notification.EXTRA_CALL_TYPE, -1) == CALL_TYPE_ONGOING,
+ statusBarSwipedAway = callNotificationInfo?.statusBarSwipedAway ?: false
)
if (newOngoingCallInfo == callNotificationInfo) {
return
@@ -122,6 +128,7 @@ class OngoingCallController @Inject constructor(
dumpManager.registerDumpable(this)
if (featureFlags.isOngoingCallStatusBarChipEnabled) {
notifCollection.addCollectionListener(notifListener)
+ statusBarStateController.addCallback(statusBarStateListener)
}
}
@@ -175,10 +182,8 @@ class OngoingCallController @Inject constructor(
val currentChipView = chipView
val timeView = currentChipView?.getTimeView()
- val backgroundView =
- currentChipView?.findViewById<View>(R.id.ongoing_call_chip_background)
- if (currentChipView != null && timeView != null && backgroundView != null) {
+ if (currentChipView != null && timeView != null) {
if (currentCallNotificationInfo.hasValidStartTime()) {
timeView.setShouldHideText(false)
timeView.base = currentCallNotificationInfo.callStartTime -
@@ -189,22 +194,15 @@ class OngoingCallController @Inject constructor(
timeView.setShouldHideText(true)
timeView.stop()
}
+ updateChipClickListener()
- currentCallNotificationInfo.intent?.let { intent ->
- currentChipView.setOnClickListener {
- logger.logChipClicked()
- activityStarter.postStartActivityDismissingKeyguard(
- intent,
- 0,
- ActivityLaunchAnimator.Controller.fromView(
- backgroundView,
- InteractionJankMonitor.CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP)
- )
+ setUpUidObserver(currentCallNotificationInfo)
+ if (!currentCallNotificationInfo.statusBarSwipedAway) {
+ statusBarWindowController.ifPresent {
+ it.setOngoingProcessRequiresStatusBarVisible(true)
}
}
-
- setUpUidObserver(currentCallNotificationInfo)
- statusBarWindowController.ifPresent { it.setIsCallOngoing(true) }
+ updateGestureListening()
mListeners.forEach { l -> l.onOngoingCallStateChanged(animate = true) }
} else {
// If we failed to update the chip, don't store the call info. Then [hasOngoingCall]
@@ -218,6 +216,30 @@ class OngoingCallController @Inject constructor(
}
}
+ private fun updateChipClickListener() {
+ if (callNotificationInfo == null) { return }
+ if (isFullscreen && !featureFlags.isOngoingCallInImmersiveChipTapEnabled) {
+ chipView?.setOnClickListener(null)
+ } else {
+ val currentChipView = chipView
+ val backgroundView =
+ currentChipView?.findViewById<View>(R.id.ongoing_call_chip_background)
+ val intent = callNotificationInfo?.intent
+ if (currentChipView != null && backgroundView != null && intent != null) {
+ currentChipView.setOnClickListener {
+ logger.logChipClicked()
+ activityStarter.postStartActivityDismissingKeyguard(
+ intent,
+ 0,
+ ActivityLaunchAnimator.Controller.fromView(
+ backgroundView,
+ InteractionJankMonitor.CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP)
+ )
+ }
+ }
+ }
+ }
+
/**
* Sets up an [IUidObserver] to monitor the status of the application managing the ongoing call.
*/
@@ -268,10 +290,23 @@ class OngoingCallController @Inject constructor(
return procState <= ActivityManager.PROCESS_STATE_TOP
}
+ private fun updateGestureListening() {
+ if (callNotificationInfo == null
+ || callNotificationInfo?.statusBarSwipedAway == true
+ || !isFullscreen) {
+ swipeStatusBarAwayGestureHandler.ifPresent { it.removeOnGestureDetectedCallback(TAG) }
+ } else {
+ swipeStatusBarAwayGestureHandler.ifPresent {
+ it.addOnGestureDetectedCallback(TAG, this::onSwipeAwayGestureDetected)
+ }
+ }
+ }
+
private fun removeChip() {
callNotificationInfo = null
tearDownChipView()
- statusBarWindowController.ifPresent { it.setIsCallOngoing(false) }
+ statusBarWindowController.ifPresent { it.setOngoingProcessRequiresStatusBarVisible(false) }
+ swipeStatusBarAwayGestureHandler.ifPresent { it.removeOnGestureDetectedCallback(TAG) }
mListeners.forEach { l -> l.onOngoingCallStateChanged(animate = true) }
if (uidObserver != null) {
iActivityManager.unregisterUidObserver(uidObserver)
@@ -286,13 +321,42 @@ class OngoingCallController @Inject constructor(
return this.findViewById(R.id.ongoing_call_chip_time)
}
+ /**
+ * If there's an active ongoing call, then we will force the status bar to always show, even if
+ * the user is in immersive mode. However, we also want to give users the ability to swipe away
+ * the status bar if they need to access the area under the status bar.
+ *
+ * This method updates the status bar window appropriately when the swipe away gesture is
+ * detected.
+ */
+ private fun onSwipeAwayGestureDetected() {
+ if (DEBUG) { Log.d(TAG, "Swipe away gesture detected") }
+ callNotificationInfo = callNotificationInfo?.copy(statusBarSwipedAway = true)
+ statusBarWindowController.ifPresent {
+ it.setOngoingProcessRequiresStatusBarVisible(false)
+ }
+ swipeStatusBarAwayGestureHandler.ifPresent {
+ it.removeOnGestureDetectedCallback(TAG)
+ }
+ }
+
+ private val statusBarStateListener = object : StatusBarStateController.StateListener {
+ override fun onFullscreenStateChanged(isFullscreen: Boolean) {
+ this@OngoingCallController.isFullscreen = isFullscreen
+ updateChipClickListener()
+ updateGestureListening()
+ }
+ }
+
private data class CallNotificationInfo(
val key: String,
val callStartTime: Long,
val intent: Intent?,
val uid: Int,
/** True if the call is currently ongoing (as opposed to incoming, screening, etc.). */
- val isOngoing: Boolean
+ val isOngoing: Boolean,
+ /** True if the user has swiped away the status bar while in this phone call. */
+ val statusBarSwipedAway: Boolean
) {
/**
* Returns true if the notification information has a valid call start time.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ConfigurationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ConfigurationController.java
index e679c4c97f18..6b80a9dab7cf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ConfigurationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ConfigurationController.java
@@ -38,7 +38,6 @@ public interface ConfigurationController extends CallbackController<Configuratio
default void onDensityOrFontScaleChanged() {}
default void onSmallestScreenWidthChanged() {}
default void onMaxBoundsChanged() {}
- default void onOverlayChanged() {}
default void onUiModeChanged() {}
default void onThemeChanged() {}
default void onLocaleListChanged() {}
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
index db965db13833..c776ab9bda1d 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
@@ -212,7 +212,7 @@ public final class WMShell extends SystemUI
}
@Override
- public void onOverlayChanged() {
+ public void onThemeChanged() {
pip.onOverlayChanged();
}
});
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
index 5c7885f47c59..fe0a2a4bbb14 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
@@ -19,7 +19,6 @@ package com.android.keyguard;
import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
@@ -30,6 +29,7 @@ import android.content.res.Resources;
import android.testing.AndroidTestingRunner;
import android.view.View;
import android.widget.FrameLayout;
+import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import androidx.test.filters.SmallTest;
@@ -108,7 +108,7 @@ public class KeyguardClockSwitchControllerTest extends SysuiTestCase {
private final View mFakeSmartspaceView = new View(mContext);
private KeyguardClockSwitchController mController;
- private View mStatusArea;
+ private View mSliceView;
@Before
public void setup() {
@@ -149,8 +149,10 @@ public class KeyguardClockSwitchControllerTest extends SysuiTestCase {
when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE);
when(mColorExtractor.getColors(anyInt())).thenReturn(mGradientColors);
- mStatusArea = new View(getContext());
- when(mView.findViewById(R.id.keyguard_status_area)).thenReturn(mStatusArea);
+ mSliceView = new View(getContext());
+ when(mView.findViewById(R.id.keyguard_slice_view)).thenReturn(mSliceView);
+ when(mView.findViewById(R.id.keyguard_status_area)).thenReturn(
+ new LinearLayout(getContext()));
}
@Test
@@ -215,7 +217,7 @@ public class KeyguardClockSwitchControllerTest extends SysuiTestCase {
when(mSmartspaceController.buildAndConnectView(any())).thenReturn(mFakeSmartspaceView);
mController.init();
- assertEquals(View.GONE, mStatusArea.getVisibility());
+ assertEquals(View.GONE, mSliceView.getVisibility());
}
@Test
@@ -223,22 +225,7 @@ public class KeyguardClockSwitchControllerTest extends SysuiTestCase {
when(mSmartspaceController.isEnabled()).thenReturn(false);
mController.init();
- assertEquals(View.VISIBLE, mStatusArea.getVisibility());
- }
-
- @Test
- public void testDetachDisconnectsSmartspace() {
- when(mSmartspaceController.isEnabled()).thenReturn(true);
- when(mSmartspaceController.buildAndConnectView(any())).thenReturn(mFakeSmartspaceView);
- mController.init();
- verify(mView).addView(eq(mFakeSmartspaceView), anyInt(), any());
-
- ArgumentCaptor<View.OnAttachStateChangeListener> listenerArgumentCaptor =
- ArgumentCaptor.forClass(View.OnAttachStateChangeListener.class);
- verify(mView).addOnAttachStateChangeListener(listenerArgumentCaptor.capture());
-
- listenerArgumentCaptor.getValue().onViewDetachedFromWindow(mView);
- verify(mSmartspaceController).disconnect();
+ assertEquals(View.VISIBLE, mSliceView.getVisibility());
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewTest.java
index 1ab08c27088a..77302ce30f09 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewTest.java
@@ -54,7 +54,7 @@ public class KeyguardSliceViewTest extends SysuiTestCase {
MockitoAnnotations.initMocks(this);
LayoutInflater layoutInflater = LayoutInflater.from(getContext());
mKeyguardSliceView = (KeyguardSliceView) layoutInflater
- .inflate(R.layout.keyguard_status_area, null);
+ .inflate(R.layout.keyguard_slice_view, null);
mSliceUri = Uri.parse(KeyguardSliceProvider.KEYGUARD_SLICE_URI);
SliceProvider.setSpecs(new HashSet<>(Collections.singletonList(SliceSpecs.LIST)));
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index 0772b2098565..3edfd03dfc70 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -64,7 +64,6 @@ import android.os.Handler;
import android.os.IRemoteCallback;
import android.os.UserHandle;
import android.os.UserManager;
-import android.os.Vibrator;
import android.telephony.ServiceState;
import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
@@ -173,8 +172,6 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
private FeatureFlags mFeatureFlags;
@Mock
private InteractionJankMonitor mInteractionJankMonitor;
- @Mock
- private Vibrator mVibrator;
@Captor
private ArgumentCaptor<StatusBarStateController.StateListener> mStatusBarStateListenerCaptor;
// Direct executor
@@ -242,8 +239,6 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
when(mRingerModeTracker.getRingerMode()).thenReturn(mRingerModeLiveData);
- when(mFeatureFlags.isKeyguardLayoutEnabled()).thenReturn(false);
-
mMockitoSession = ExtendedMockito.mockitoSession()
.spyStatic(SubscriptionManager.class).startMocking();
ExtendedMockito.doReturn(SubscriptionManager.INVALID_SUBSCRIPTION_ID)
@@ -1064,7 +1059,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
mRingerModeTracker, mBackgroundExecutor,
mStatusBarStateController, mLockPatternUtils,
mAuthController, mTelephonyListenerManager, mFeatureFlags,
- mInteractionJankMonitor, mVibrator);
+ mInteractionJankMonitor);
setStrongAuthTracker(KeyguardUpdateMonitorTest.this.mStrongAuthTracker);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagManagerTest.java
new file mode 100644
index 000000000000..172dcda5321f
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagManagerTest.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.flags;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+public class FeatureFlagManagerTest extends SysuiTestCase {
+ FeatureFlagManager mFeatureFlagManager;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+
+ mFeatureFlagManager = new FeatureFlagManager();
+ }
+
+ @Test
+ public void testIsEnabled() {
+ mFeatureFlagManager.setEnabled(1, true);
+ // Again, nothing changes.
+ assertThat(mFeatureFlagManager.isEnabled(1, false)).isFalse();
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagReaderTest.java b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagReaderTest.java
index 7bc5f86510a1..fc6f3fd1d9c6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagReaderTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagReaderTest.java
@@ -32,6 +32,7 @@ import androidx.test.filters.SmallTest;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.dump.DumpManager;
import com.android.systemui.shared.plugins.PluginManager;
import com.android.systemui.util.wrapper.BuildInfo;
@@ -44,8 +45,9 @@ import org.mockito.MockitoAnnotations;
public class FeatureFlagReaderTest extends SysuiTestCase {
@Mock private Resources mResources;
@Mock private BuildInfo mBuildInfo;
- @Mock private PluginManager mPluginManager;
+ @Mock private DumpManager mDumpManager;
@Mock private SystemPropertiesHelper mSystemPropertiesHelper;
+ @Mock private FlagReader mFlagReader;
private FeatureFlagReader mReader;
@@ -66,7 +68,7 @@ public class FeatureFlagReaderTest extends SysuiTestCase {
when(mBuildInfo.isDebuggable()).thenReturn(isDebuggable);
when(mResources.getBoolean(R.bool.are_flags_overrideable)).thenReturn(isOverrideable);
mReader = new FeatureFlagReader(
- mResources, mBuildInfo, mPluginManager, mSystemPropertiesHelper);
+ mResources, mBuildInfo, mDumpManager, mSystemPropertiesHelper, mFlagReader);
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsTest.java b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsTest.java
index 1a961787ee79..a850f70ae318 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsTest.java
@@ -23,7 +23,6 @@ import static org.mockito.Mockito.verify;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.plugins.FlagReaderPlugin;
import org.junit.Before;
import org.junit.Test;
@@ -51,11 +50,10 @@ public class FeatureFlagsTest extends SysuiTestCase {
mFeatureFlags.addFlag(flag);
// Assert and capture that a plugin listener was added.
- ArgumentCaptor<FlagReaderPlugin.Listener> pluginListenerCaptor =
- ArgumentCaptor.forClass(FlagReaderPlugin.Listener.class);
-
+ ArgumentCaptor<FlagReader.Listener> pluginListenerCaptor =
+ ArgumentCaptor.forClass(FlagReader.Listener.class);
verify(mFeatureFlagReader).addListener(pluginListenerCaptor.capture());
- FlagReaderPlugin.Listener pluginListener = pluginListenerCaptor.getValue();
+ FlagReader.Listener pluginListener = pluginListenerCaptor.getValue();
// Signal a change. No listeners, so no real effect.
pluginListener.onFlagChanged(flag.getId());
@@ -81,11 +79,10 @@ public class FeatureFlagsTest extends SysuiTestCase {
mFeatureFlags.addFlag(flag);
// Assert and capture that a plugin listener was added.
- ArgumentCaptor<FlagReaderPlugin.Listener> pluginListenerCaptor =
- ArgumentCaptor.forClass(FlagReaderPlugin.Listener.class);
-
+ ArgumentCaptor<FlagReader.Listener> pluginListenerCaptor =
+ ArgumentCaptor.forClass(FlagReader.Listener.class);
verify(mFeatureFlagReader).addListener(pluginListenerCaptor.capture());
- FlagReaderPlugin.Listener pluginListener = pluginListenerCaptor.getValue();
+ FlagReader.Listener pluginListener = pluginListenerCaptor.getValue();
// Add a listener for the flag
final Flag<?>[] changedFlag = {null};
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java
index c464cad3e0b2..90e3db7750bc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java
@@ -18,14 +18,19 @@ package com.android.systemui.keyguard;
import static junit.framework.Assert.assertEquals;
+import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.PointF;
+import android.graphics.drawable.AnimatedVectorDrawable;
+import android.hardware.biometrics.BiometricSourceType;
import android.hardware.biometrics.SensorLocationInternal;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.os.Vibrator;
@@ -33,21 +38,25 @@ import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.util.DisplayMetrics;
import android.util.Pair;
+import android.view.LayoutInflater;
import android.view.View;
import android.view.accessibility.AccessibilityManager;
import androidx.test.filters.SmallTest;
import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.keyguard.KeyguardUpdateMonitorCallback;
import com.android.keyguard.KeyguardViewController;
import com.android.keyguard.LockIconView;
import com.android.keyguard.LockIconViewController;
+import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.biometrics.AuthController;
import com.android.systemui.biometrics.AuthRippleController;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.concurrency.DelayableExecutor;
@@ -69,7 +78,10 @@ import java.util.List;
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
public class LockIconViewControllerTest extends SysuiTestCase {
+ private static final String UNLOCKED_LABEL = "unlocked";
+
private @Mock LockIconView mLockIconView;
+ private @Mock AnimatedVectorDrawable mIconDrawable;
private @Mock Context mContext;
private @Mock Resources mResources;
private @Mock DisplayMetrics mDisplayMetrics;
@@ -86,6 +98,7 @@ public class LockIconViewControllerTest extends SysuiTestCase {
private @Mock Vibrator mVibrator;
private @Mock AuthRippleController mAuthRippleController;
private @Mock LottieAnimationView mAodFp;
+ private @Mock LayoutInflater mLayoutInflater;
private LockIconViewController mLockIconViewController;
@@ -97,6 +110,11 @@ public class LockIconViewControllerTest extends SysuiTestCase {
@Captor private ArgumentCaptor<AuthController.Callback> mAuthControllerCallbackCaptor;
private AuthController.Callback mAuthControllerCallback;
+ @Captor private ArgumentCaptor<KeyguardUpdateMonitorCallback>
+ mKeyguardUpdateMonitorCallbackCaptor =
+ ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback.class);
+ private KeyguardUpdateMonitorCallback mKeyguardUpdateMonitorCallback;
+
@Captor private ArgumentCaptor<PointF> mPointCaptor;
@Before
@@ -105,9 +123,16 @@ public class LockIconViewControllerTest extends SysuiTestCase {
when(mLockIconView.getResources()).thenReturn(mResources);
when(mLockIconView.getContext()).thenReturn(mContext);
+ when(mLockIconView.findViewById(R.layout.udfps_aod_lock_icon)).thenReturn(mAodFp);
when(mContext.getResources()).thenReturn(mResources);
when(mResources.getDisplayMetrics()).thenReturn(mDisplayMetrics);
- when(mLockIconView.findViewById(anyInt())).thenReturn(mAodFp);
+ when(mResources.getString(R.string.accessibility_unlock_button)).thenReturn(UNLOCKED_LABEL);
+ when(mResources.getDrawable(anyInt(), any())).thenReturn(mIconDrawable);
+
+ when(mKeyguardStateController.isShowing()).thenReturn(true);
+ when(mKeyguardStateController.isKeyguardGoingAway()).thenReturn(false);
+ when(mStatusBarStateController.isDozing()).thenReturn(false);
+ when(mStatusBarStateController.getState()).thenReturn(StatusBarState.KEYGUARD);
mLockIconViewController = new LockIconViewController(
mLockIconView,
@@ -122,11 +147,42 @@ public class LockIconViewControllerTest extends SysuiTestCase {
mConfigurationController,
mDelayableExecutor,
mVibrator,
- mAuthRippleController
+ mAuthRippleController,
+ mResources,
+ mLayoutInflater
);
}
@Test
+ public void testIgnoreUdfpsWhenNotSupported() {
+ // GIVEN Udpfs sensor is NOT available
+ mLockIconViewController.init();
+ captureAttachListener();
+
+ // WHEN the view is attached
+ mAttachListener.onViewAttachedToWindow(mLockIconView);
+
+ // THEN lottie animation should NOT be inflated
+ verify(mLayoutInflater, never()).inflate(eq(R.layout.udfps_aod_lock_icon), any());
+ }
+
+ @Test
+ public void testInflateUdfpsWhenSupported() {
+ // GIVEN Udpfs sensor is available
+ setupUdfps();
+ when(mKeyguardUpdateMonitor.isUdfpsEnrolled()).thenReturn(true);
+
+ mLockIconViewController.init();
+ captureAttachListener();
+
+ // WHEN the view is attached
+ mAttachListener.onViewAttachedToWindow(mLockIconView);
+
+ // THEN lottie animation should be inflated
+ verify(mLayoutInflater).inflate(eq(R.layout.udfps_aod_lock_icon), any());
+ }
+
+ @Test
public void testUpdateFingerprintLocationOnInit() {
// GIVEN fp sensor location is available pre-attached
Pair<Integer, PointF> udfps = setupUdfps();
@@ -192,6 +248,29 @@ public class LockIconViewControllerTest extends SysuiTestCase {
verify(mLockIconView).setUseBackground(false);
}
+ @Test
+ public void testUnlockIconShows_biometricUnlockedTrue() {
+ // GIVEN UDFPS sensor location is available
+ setupUdfps();
+
+ // GIVEN lock icon controller is initialized and view is attached
+ mLockIconViewController.init();
+ captureAttachListener();
+ mAttachListener.onViewAttachedToWindow(mLockIconView);
+ captureKeyguardUpdateMonitorCallback();
+
+ // GIVEN user has unlocked with a biometric auth (ie: face auth)
+ when(mKeyguardUpdateMonitor.getUserUnlockedWithBiometric(anyInt())).thenReturn(true);
+ reset(mLockIconView);
+
+ // WHEN face auth's biometric running state changes
+ mKeyguardUpdateMonitorCallback.onBiometricRunningStateChanged(false,
+ BiometricSourceType.FACE);
+
+ // THEN the unlock icon is shown
+ verify(mLockIconView).setContentDescription(UNLOCKED_LABEL);
+ }
+
private Pair<Integer, PointF> setupUdfps() {
final PointF udfpsLocation = new PointF(50, 75);
final int radius = 33;
@@ -220,4 +299,10 @@ public class LockIconViewControllerTest extends SysuiTestCase {
verify(mLockIconView).addOnAttachStateChangeListener(mAttachCaptor.capture());
mAttachListener = mAttachCaptor.getValue();
}
+
+ private void captureKeyguardUpdateMonitorCallback() {
+ verify(mKeyguardUpdateMonitor).registerCallback(
+ mKeyguardUpdateMonitorCallbackCaptor.capture());
+ mKeyguardUpdateMonitorCallback = mKeyguardUpdateMonitorCallbackCaptor.getValue();
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarObserverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarObserverTest.kt
index 7d8728e4acab..e77802f8db32 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarObserverTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarObserverTest.kt
@@ -76,6 +76,7 @@ public class SeekBarObserverTest : SysuiTestCase() {
assertThat(seekBarView.getThumb().getAlpha()).isEqualTo(0)
assertThat(elapsedTimeView.getText()).isEqualTo("")
assertThat(totalTimeView.getText()).isEqualTo("")
+ assertThat(seekBarView.contentDescription).isEqualTo("")
assertThat(seekBarView.maxHeight).isEqualTo(disabledHeight)
}
@@ -102,6 +103,9 @@ public class SeekBarObserverTest : SysuiTestCase() {
assertThat(seekBarView.max).isEqualTo(120000)
assertThat(elapsedTimeView.getText()).isEqualTo("00:03")
assertThat(totalTimeView.getText()).isEqualTo("02:00")
+
+ val desc = context.getString(R.string.controls_media_seekbar_description, "00:03", "02:00")
+ assertThat(seekBarView.contentDescription).isEqualTo(desc)
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java
index 25ca8c95500b..750600ad1387 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java
@@ -119,7 +119,6 @@ public class MediaOutputAdapterTest extends SysuiTestCase {
assertThat(mViewHolder.mTitleText.getVisibility()).isEqualTo(View.VISIBLE);
assertThat(mViewHolder.mTwoLineLayout.getVisibility()).isEqualTo(View.GONE);
assertThat(mViewHolder.mProgressBar.getVisibility()).isEqualTo(View.GONE);
- assertThat(mViewHolder.mDivider.getVisibility()).isEqualTo(View.GONE);
assertThat(mViewHolder.mAddIcon.getVisibility()).isEqualTo(View.GONE);
assertThat(mViewHolder.mCheckBox.getVisibility()).isEqualTo(View.GONE);
assertThat(mViewHolder.mBottomDivider.getVisibility()).isEqualTo(View.GONE);
@@ -181,7 +180,6 @@ public class MediaOutputAdapterTest extends SysuiTestCase {
when(mMediaOutputController.getSelectableMediaDevice()).thenReturn(mMediaDevices);
mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0);
- assertThat(mViewHolder.mDivider.getVisibility()).isEqualTo(View.VISIBLE);
assertThat(mViewHolder.mAddIcon.getVisibility()).isEqualTo(View.VISIBLE);
}
@@ -190,7 +188,6 @@ public class MediaOutputAdapterTest extends SysuiTestCase {
when(mMediaOutputController.getSelectableMediaDevice()).thenReturn(new ArrayList<>());
mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0);
- assertThat(mViewHolder.mDivider.getVisibility()).isEqualTo(View.GONE);
assertThat(mViewHolder.mAddIcon.getVisibility()).isEqualTo(View.GONE);
}
@@ -198,7 +195,6 @@ public class MediaOutputAdapterTest extends SysuiTestCase {
public void onBindViewHolder_bindNonActiveConnectedDevice_verifyView() {
mMediaOutputAdapter.onBindViewHolder(mViewHolder, 1);
- assertThat(mViewHolder.mDivider.getVisibility()).isEqualTo(View.GONE);
assertThat(mViewHolder.mAddIcon.getVisibility()).isEqualTo(View.GONE);
assertThat(mViewHolder.mTwoLineLayout.getVisibility()).isEqualTo(View.GONE);
assertThat(mViewHolder.mProgressBar.getVisibility()).isEqualTo(View.GONE);
@@ -215,7 +211,6 @@ public class MediaOutputAdapterTest extends SysuiTestCase {
when(mMediaDevice2.isConnected()).thenReturn(false);
mMediaOutputAdapter.onBindViewHolder(mViewHolder, 1);
- assertThat(mViewHolder.mDivider.getVisibility()).isEqualTo(View.GONE);
assertThat(mViewHolder.mAddIcon.getVisibility()).isEqualTo(View.GONE);
assertThat(mViewHolder.mTwoLineLayout.getVisibility()).isEqualTo(View.GONE);
assertThat(mViewHolder.mProgressBar.getVisibility()).isEqualTo(View.GONE);
@@ -231,7 +226,6 @@ public class MediaOutputAdapterTest extends SysuiTestCase {
LocalMediaManager.MediaDeviceState.STATE_CONNECTING_FAILED);
mMediaOutputAdapter.onBindViewHolder(mViewHolder, 1);
- assertThat(mViewHolder.mDivider.getVisibility()).isEqualTo(View.GONE);
assertThat(mViewHolder.mAddIcon.getVisibility()).isEqualTo(View.GONE);
assertThat(mViewHolder.mTitleText.getVisibility()).isEqualTo(View.GONE);
assertThat(mViewHolder.mSeekBar.getVisibility()).isEqualTo(View.GONE);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputGroupAdapterTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputGroupAdapterTest.java
index 1f85112dfb74..ca5d570969c8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputGroupAdapterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputGroupAdapterTest.java
@@ -99,7 +99,6 @@ public class MediaOutputGroupAdapterTest extends SysuiTestCase {
assertThat(mGroupViewHolder.mCheckBox.getVisibility()).isEqualTo(View.GONE);
assertThat(mGroupViewHolder.mSubTitleText.getVisibility()).isEqualTo(View.GONE);
assertThat(mGroupViewHolder.mProgressBar.getVisibility()).isEqualTo(View.GONE);
- assertThat(mGroupViewHolder.mDivider.getVisibility()).isEqualTo(View.GONE);
assertThat(mGroupViewHolder.mAddIcon.getVisibility()).isEqualTo(View.GONE);
assertThat(mGroupViewHolder.mBottomDivider.getVisibility()).isEqualTo(View.VISIBLE);
assertThat(mGroupViewHolder.mSeekBar.getVisibility()).isEqualTo(View.VISIBLE);
@@ -114,7 +113,6 @@ public class MediaOutputGroupAdapterTest extends SysuiTestCase {
assertThat(mGroupViewHolder.mTitleText.getVisibility()).isEqualTo(View.GONE);
assertThat(mGroupViewHolder.mProgressBar.getVisibility()).isEqualTo(View.GONE);
- assertThat(mGroupViewHolder.mDivider.getVisibility()).isEqualTo(View.GONE);
assertThat(mGroupViewHolder.mAddIcon.getVisibility()).isEqualTo(View.GONE);
assertThat(mGroupViewHolder.mBottomDivider.getVisibility()).isEqualTo(View.GONE);
assertThat(mGroupViewHolder.mSubTitleText.getVisibility()).isEqualTo(View.GONE);
@@ -141,7 +139,6 @@ public class MediaOutputGroupAdapterTest extends SysuiTestCase {
assertThat(mGroupViewHolder.mTitleText.getVisibility()).isEqualTo(View.GONE);
assertThat(mGroupViewHolder.mProgressBar.getVisibility()).isEqualTo(View.GONE);
- assertThat(mGroupViewHolder.mDivider.getVisibility()).isEqualTo(View.GONE);
assertThat(mGroupViewHolder.mAddIcon.getVisibility()).isEqualTo(View.GONE);
assertThat(mGroupViewHolder.mBottomDivider.getVisibility()).isEqualTo(View.GONE);
assertThat(mGroupViewHolder.mSubTitleText.getVisibility()).isEqualTo(View.GONE);
@@ -167,7 +164,6 @@ public class MediaOutputGroupAdapterTest extends SysuiTestCase {
assertThat(mGroupViewHolder.mTitleText.getVisibility()).isEqualTo(View.GONE);
assertThat(mGroupViewHolder.mProgressBar.getVisibility()).isEqualTo(View.GONE);
- assertThat(mGroupViewHolder.mDivider.getVisibility()).isEqualTo(View.GONE);
assertThat(mGroupViewHolder.mAddIcon.getVisibility()).isEqualTo(View.GONE);
assertThat(mGroupViewHolder.mBottomDivider.getVisibility()).isEqualTo(View.GONE);
assertThat(mGroupViewHolder.mSubTitleText.getVisibility()).isEqualTo(View.GONE);
@@ -186,7 +182,6 @@ public class MediaOutputGroupAdapterTest extends SysuiTestCase {
assertThat(mGroupViewHolder.mTitleText.getVisibility()).isEqualTo(View.GONE);
assertThat(mGroupViewHolder.mProgressBar.getVisibility()).isEqualTo(View.GONE);
- assertThat(mGroupViewHolder.mDivider.getVisibility()).isEqualTo(View.GONE);
assertThat(mGroupViewHolder.mAddIcon.getVisibility()).isEqualTo(View.GONE);
assertThat(mGroupViewHolder.mBottomDivider.getVisibility()).isEqualTo(View.GONE);
assertThat(mGroupViewHolder.mSubTitleText.getVisibility()).isEqualTo(View.GONE);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java
index c1a9739cb232..2a8840b503ad 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java
@@ -57,6 +57,7 @@ import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.NotificationShadeDepthController;
+import com.android.systemui.statusbar.phone.AutoHideController;
import com.android.systemui.statusbar.phone.ShadeController;
import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
@@ -118,7 +119,8 @@ public class NavigationBarControllerTest extends SysuiTestCase {
mock(NavigationBarA11yHelper.class),
mock(TaskbarDelegate.class),
mock(UserTracker.class),
- mock(DumpManager.class)));
+ mock(DumpManager.class),
+ mock(AutoHideController.class)));
initializeNavigationBars();
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/user/UserSwitchDialogControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/user/UserSwitchDialogControllerTest.kt
index a1760a79417f..7e900c843cc3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/user/UserSwitchDialogControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/user/UserSwitchDialogControllerTest.kt
@@ -22,6 +22,7 @@ import android.testing.AndroidTestingRunner
import android.view.View
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.animation.DialogLaunchAnimator
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.qs.PseudoGridView
@@ -68,6 +69,8 @@ class UserSwitchDialogControllerTest : SysuiTestCase() {
private lateinit var launchView: View
@Mock
private lateinit var gridView: PseudoGridView
+ @Mock
+ private lateinit var dialogLaunchAnimator: DialogLaunchAnimator
@Captor
private lateinit var clickCaptor: ArgumentCaptor<View.OnClickListener>
@@ -87,6 +90,7 @@ class UserSwitchDialogControllerTest : SysuiTestCase() {
{ userDetailViewAdapter },
activityStarter,
falsingManager,
+ dialogLaunchAnimator,
{ dialog }
)
}
@@ -94,7 +98,7 @@ class UserSwitchDialogControllerTest : SysuiTestCase() {
@Test
fun showDialog_callsDialogShow() {
controller.showDialog(launchView)
- verify(dialog).show()
+ verify(dialogLaunchAnimator).showFromView(dialog, launchView)
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
index f5cab1df9fa2..01f7fae05f76 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
@@ -166,7 +166,7 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase {
private BroadcastReceiver mBroadcastReceiver;
private FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock());
- private KeyguardIndicationTextView mTextView;
+ private KeyguardIndicationTextView mTextView; // AOD text
private KeyguardIndicationController mController;
private WakeLockFake.Builder mWakeLockBuilder;
@@ -412,41 +412,32 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase {
@Test
public void transientIndication_holdsWakeLock_whenDozing() {
+ // GIVEN animations are enabled and text is visible
+ mTextView.setAnimationsEnabled(true);
createController();
+ mController.setVisible(true);
+ // WHEN transient text is shown
mStatusBarStateListener.onDozingChanged(true);
mController.showTransientIndication("Test");
- assertTrue(mWakeLock.isHeld());
+ // THEN wake lock is held while the animation is running
+ assertTrue("WakeLock expected: HELD, was: RELEASED", mWakeLock.isHeld());
}
@Test
- public void transientIndication_releasesWakeLock_afterHiding() {
+ public void transientIndication_releasesWakeLock_whenDozing() {
+ // GIVEN animations aren't enabled
+ mTextView.setAnimationsEnabled(false);
createController();
+ mController.setVisible(true);
+ // WHEN we show the transient indication
mStatusBarStateListener.onDozingChanged(true);
mController.showTransientIndication("Test");
- mController.hideTransientIndication();
-
- assertFalse(mWakeLock.isHeld());
- }
-
- @Test
- public void transientIndication_releasesWakeLock_afterHidingDelayed() throws Throwable {
- mInstrumentation.runOnMainSync(() -> {
- createController();
-
- mStatusBarStateListener.onDozingChanged(true);
- mController.showTransientIndication("Test");
- mController.hideTransientIndicationDelayed(0);
- });
- mInstrumentation.waitForIdleSync();
- Boolean[] held = new Boolean[1];
- mInstrumentation.runOnMainSync(() -> {
- held[0] = mWakeLock.isHeld();
- });
- assertFalse("WakeLock expected: RELEASED, was: HELD", held[0]);
+ // THEN wake lock is RELEASED, not held
+ assertFalse("WakeLock expected: RELEASED, was: HELD", mWakeLock.isHeld());
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
index c50296be94f3..793851160dc2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
@@ -209,7 +209,7 @@ class LockscreenShadeTransitionControllerTest : SysuiTestCase() {
verify(scrimController, never()).setTransitionToFullShadeProgress(anyFloat())
verify(notificationPanelController, never()).setTransitionToFullShadeAmount(anyFloat(),
anyBoolean(), anyLong())
- verify(qS, never()).setTransitionToFullShadeAmount(anyFloat(), anyBoolean())
+ verify(qS, never()).setTransitionToFullShadeAmount(anyFloat(), anyFloat())
}
@Test
@@ -220,7 +220,7 @@ class LockscreenShadeTransitionControllerTest : SysuiTestCase() {
verify(scrimController).setTransitionToFullShadeProgress(anyFloat())
verify(notificationPanelController).setTransitionToFullShadeAmount(anyFloat(),
anyBoolean(), anyLong())
- verify(qS).setTransitionToFullShadeAmount(anyFloat(), anyBoolean())
+ verify(qS).setTransitionToFullShadeAmount(anyFloat(), anyFloat())
verify(depthController).transitionToFullShadeProgress = anyFloat()
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt
index ee9c2b82c283..ff91978c54bb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt
@@ -246,6 +246,7 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() {
clearInvocations(plugin)
// WHEN the session is closed
+ controller.stateChangeListener.onViewDetachedFromWindow(smartspaceView as View)
controller.disconnect()
// THEN the listener receives an empty list of targets
@@ -417,6 +418,7 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() {
connectSession()
// WHEN we are told to cleanup
+ controller.stateChangeListener.onViewDetachedFromWindow(smartspaceView as View)
controller.disconnect()
// THEN we disconnect from the session and unregister any listeners
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
index 5ebe900b52d4..705112afa93f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
@@ -623,7 +623,7 @@ public class ScrimControllerTest extends SysuiTestCase {
@Test
public void transitionToUnlocked() {
- mScrimController.setPanelExpansion(0f);
+ mScrimController.setRawPanelExpansionFraction(0f);
mScrimController.transitionTo(ScrimState.UNLOCKED);
finishAnimationsImmediately();
assertScrimAlpha(Map.of(
@@ -638,7 +638,7 @@ public class ScrimControllerTest extends SysuiTestCase {
));
// Back scrim should be visible after start dragging
- mScrimController.setPanelExpansion(0.3f);
+ mScrimController.setRawPanelExpansionFraction(0.3f);
assertScrimAlpha(Map.of(
mScrimInFront, TRANSPARENT,
mNotificationsScrim, SEMI_TRANSPARENT,
@@ -663,20 +663,20 @@ public class ScrimControllerTest extends SysuiTestCase {
@Test
public void panelExpansion() {
- mScrimController.setPanelExpansion(0f);
- mScrimController.setPanelExpansion(0.5f);
+ mScrimController.setRawPanelExpansionFraction(0f);
+ mScrimController.setRawPanelExpansionFraction(0.5f);
mScrimController.transitionTo(ScrimState.UNLOCKED);
finishAnimationsImmediately();
reset(mScrimBehind);
- mScrimController.setPanelExpansion(0f);
- mScrimController.setPanelExpansion(1.0f);
+ mScrimController.setRawPanelExpansionFraction(0f);
+ mScrimController.setRawPanelExpansionFraction(1.0f);
finishAnimationsImmediately();
assertEquals("Scrim alpha should change after setPanelExpansion",
mScrimBehindAlpha, mScrimBehind.getViewAlpha(), 0.01f);
- mScrimController.setPanelExpansion(0f);
+ mScrimController.setRawPanelExpansionFraction(0f);
finishAnimationsImmediately();
assertEquals("Scrim alpha should change after setPanelExpansion",
@@ -723,21 +723,21 @@ public class ScrimControllerTest extends SysuiTestCase {
@Test
public void panelExpansionAffectsAlpha() {
- mScrimController.setPanelExpansion(0f);
- mScrimController.setPanelExpansion(0.5f);
+ mScrimController.setRawPanelExpansionFraction(0f);
+ mScrimController.setRawPanelExpansionFraction(0.5f);
mScrimController.transitionTo(ScrimState.UNLOCKED);
finishAnimationsImmediately();
final float scrimAlpha = mScrimBehind.getViewAlpha();
reset(mScrimBehind);
mScrimController.setExpansionAffectsAlpha(false);
- mScrimController.setPanelExpansion(0.8f);
+ mScrimController.setRawPanelExpansionFraction(0.8f);
verifyZeroInteractions(mScrimBehind);
assertEquals("Scrim opacity shouldn't change when setExpansionAffectsAlpha "
+ "is false", scrimAlpha, mScrimBehind.getViewAlpha(), 0.01f);
mScrimController.setExpansionAffectsAlpha(true);
- mScrimController.setPanelExpansion(0.1f);
+ mScrimController.setRawPanelExpansionFraction(0.1f);
finishAnimationsImmediately();
Assert.assertNotEquals("Scrim opacity should change when setExpansionAffectsAlpha "
+ "is true", scrimAlpha, mScrimBehind.getViewAlpha(), 0.01f);
@@ -747,7 +747,7 @@ public class ScrimControllerTest extends SysuiTestCase {
public void transitionToUnlockedFromOff() {
// Simulate unlock with fingerprint without AOD
mScrimController.transitionTo(ScrimState.OFF);
- mScrimController.setPanelExpansion(0f);
+ mScrimController.setRawPanelExpansionFraction(0f);
finishAnimationsImmediately();
mScrimController.transitionTo(ScrimState.UNLOCKED);
@@ -769,7 +769,7 @@ public class ScrimControllerTest extends SysuiTestCase {
public void transitionToUnlockedFromAod() {
// Simulate unlock with fingerprint
mScrimController.transitionTo(ScrimState.AOD);
- mScrimController.setPanelExpansion(0f);
+ mScrimController.setRawPanelExpansionFraction(0f);
finishAnimationsImmediately();
mScrimController.transitionTo(ScrimState.UNLOCKED);
@@ -948,7 +948,7 @@ public class ScrimControllerTest extends SysuiTestCase {
@Test
public void testConservesExpansionOpacityAfterTransition() {
mScrimController.transitionTo(ScrimState.UNLOCKED);
- mScrimController.setPanelExpansion(0.5f);
+ mScrimController.setRawPanelExpansionFraction(0.5f);
finishAnimationsImmediately();
final float expandedAlpha = mScrimBehind.getViewAlpha();
@@ -1075,7 +1075,7 @@ public class ScrimControllerTest extends SysuiTestCase {
@Test
public void testScrimsOpaque_whenShadeFullyExpanded() {
mScrimController.transitionTo(ScrimState.UNLOCKED);
- mScrimController.setPanelExpansion(1);
+ mScrimController.setRawPanelExpansionFraction(1);
// notifications scrim alpha change require calling setQsPosition
mScrimController.setQsPosition(0, 300);
finishAnimationsImmediately();
@@ -1089,7 +1089,7 @@ public class ScrimControllerTest extends SysuiTestCase {
@Test
public void testScrimsVisible_whenShadeVisible() {
mScrimController.transitionTo(ScrimState.UNLOCKED);
- mScrimController.setPanelExpansion(0.3f);
+ mScrimController.setRawPanelExpansionFraction(0.3f);
// notifications scrim alpha change require calling setQsPosition
mScrimController.setQsPosition(0, 300);
finishAnimationsImmediately();
@@ -1124,7 +1124,7 @@ public class ScrimControllerTest extends SysuiTestCase {
public void testScrimsVisible_whenShadeVisible_clippingQs() {
mScrimController.setClipsQsScrim(true);
mScrimController.transitionTo(ScrimState.UNLOCKED);
- mScrimController.setPanelExpansion(0.3f);
+ mScrimController.setRawPanelExpansionFraction(0.3f);
// notifications scrim alpha change require calling setQsPosition
mScrimController.setQsPosition(0.5f, 300);
finishAnimationsImmediately();
@@ -1150,7 +1150,7 @@ public class ScrimControllerTest extends SysuiTestCase {
public void testNotificationScrimTransparent_whenOnLockscreen() {
mScrimController.transitionTo(ScrimState.KEYGUARD);
// even if shade is not pulled down, panel has expansion of 1 on the lockscreen
- mScrimController.setPanelExpansion(1);
+ mScrimController.setRawPanelExpansionFraction(1);
mScrimController.setQsPosition(0f, /*qs panel bottom*/ 0);
assertScrimAlpha(Map.of(
@@ -1160,7 +1160,7 @@ public class ScrimControllerTest extends SysuiTestCase {
@Test
public void testNotificationScrimVisible_afterOpeningShadeFromLockscreen() {
- mScrimController.setPanelExpansion(1);
+ mScrimController.setRawPanelExpansionFraction(1);
mScrimController.transitionTo(ScrimState.SHADE_LOCKED);
finishAnimationsImmediately();
@@ -1203,11 +1203,11 @@ public class ScrimControllerTest extends SysuiTestCase {
@Test
public void testNotificationTransparency_followsTransitionToFullShade() {
mScrimController.transitionTo(ScrimState.SHADE_LOCKED);
- mScrimController.setPanelExpansion(1.0f);
+ mScrimController.setRawPanelExpansionFraction(1.0f);
finishAnimationsImmediately();
float shadeLockedAlpha = mNotificationsScrim.getViewAlpha();
mScrimController.transitionTo(ScrimState.KEYGUARD);
- mScrimController.setPanelExpansion(1.0f);
+ mScrimController.setRawPanelExpansionFraction(1.0f);
finishAnimationsImmediately();
float keyguardAlpha = mNotificationsScrim.getViewAlpha();
@@ -1227,7 +1227,7 @@ public class ScrimControllerTest extends SysuiTestCase {
}
private void assertAlphaAfterExpansion(ScrimView scrim, float expectedAlpha, float expansion) {
- mScrimController.setPanelExpansion(expansion);
+ mScrimController.setRawPanelExpansionFraction(expansion);
finishAnimationsImmediately();
// alpha is not changing linearly thus 0.2 of leeway when asserting
assertEquals(expectedAlpha, mNotificationsScrim.getViewAlpha(), 0.2);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt
index e97aba2816e1..3d2ff47e3cdd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt
@@ -36,6 +36,8 @@ import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.statusbar.gesture.SwipeStatusBarAwayGestureHandler
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection
@@ -50,8 +52,7 @@ import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
-import org.mockito.ArgumentMatchers.anyBoolean
-import org.mockito.ArgumentMatchers.nullable
+import org.mockito.ArgumentMatchers.*
import org.mockito.Mock
import org.mockito.Mockito.`when`
import org.mockito.Mockito.eq
@@ -83,10 +84,13 @@ class OngoingCallControllerTest : SysuiTestCase() {
private lateinit var controller: OngoingCallController
private lateinit var notifCollectionListener: NotifCollectionListener
+ @Mock private lateinit var mockFeatureFlags: FeatureFlags
+ @Mock private lateinit var mockSwipeStatusBarAwayGestureHandler: SwipeStatusBarAwayGestureHandler
@Mock private lateinit var mockOngoingCallListener: OngoingCallListener
@Mock private lateinit var mockActivityStarter: ActivityStarter
@Mock private lateinit var mockIActivityManager: IActivityManager
@Mock private lateinit var mockStatusBarWindowController: StatusBarWindowController
+ @Mock private lateinit var mockStatusBarStateController: StatusBarStateController
private lateinit var chipView: View
@@ -98,13 +102,12 @@ class OngoingCallControllerTest : SysuiTestCase() {
}
MockitoAnnotations.initMocks(this)
- val featureFlags = mock(FeatureFlags::class.java)
- `when`(featureFlags.isOngoingCallStatusBarChipEnabled).thenReturn(true)
+ `when`(mockFeatureFlags.isOngoingCallStatusBarChipEnabled).thenReturn(true)
val notificationCollection = mock(CommonNotifCollection::class.java)
controller = OngoingCallController(
notificationCollection,
- featureFlags,
+ mockFeatureFlags,
clock,
mockActivityStarter,
mainExecutor,
@@ -112,7 +115,9 @@ class OngoingCallControllerTest : SysuiTestCase() {
OngoingCallLogger(uiEventLoggerFake),
DumpManager(),
Optional.of(mockStatusBarWindowController),
- )
+ Optional.of(mockSwipeStatusBarAwayGestureHandler),
+ mockStatusBarStateController,
+ )
controller.init()
controller.addCallback(mockOngoingCallListener)
controller.setChipView(chipView)
@@ -141,7 +146,7 @@ class OngoingCallControllerTest : SysuiTestCase() {
fun onEntryUpdated_isOngoingCallNotif_windowControllerUpdated() {
notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry())
- verify(mockStatusBarWindowController).setIsCallOngoing(true)
+ verify(mockStatusBarWindowController).setOngoingProcessRequiresStatusBarVisible(true)
}
@Test
@@ -242,7 +247,7 @@ class OngoingCallControllerTest : SysuiTestCase() {
notifCollectionListener.onEntryRemoved(ongoingCallNotifEntry, REASON_USER_STOPPED)
- verify(mockStatusBarWindowController).setIsCallOngoing(false)
+ verify(mockStatusBarWindowController).setOngoingProcessRequiresStatusBarVisible(false)
}
/** Regression test for b/188491504. */
@@ -435,6 +440,120 @@ class OngoingCallControllerTest : SysuiTestCase() {
// Other tests for notifyChipVisibilityChanged are in [OngoingCallLogger], since
// [OngoingCallController.notifyChipVisibilityChanged] just delegates to that class.
+ @Test
+ fun callNotificationAdded_chipIsClickable() {
+ notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry())
+
+ assertThat(chipView.hasOnClickListeners()).isTrue()
+ }
+
+ @Test
+ fun fullscreenIsTrue_thenCallNotificationAdded_chipNotClickable() {
+ `when`(mockFeatureFlags.isOngoingCallInImmersiveChipTapEnabled).thenReturn(false)
+
+ getStateListener().onFullscreenStateChanged(/* isFullscreen= */ true)
+ notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry())
+
+ assertThat(chipView.hasOnClickListeners()).isFalse()
+ }
+
+ @Test
+ fun callNotificationAdded_thenFullscreenIsTrue_chipNotClickable() {
+ `when`(mockFeatureFlags.isOngoingCallInImmersiveChipTapEnabled).thenReturn(false)
+
+ notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry())
+ getStateListener().onFullscreenStateChanged(/* isFullscreen= */ true)
+
+ assertThat(chipView.hasOnClickListeners()).isFalse()
+ }
+
+ @Test
+ fun fullscreenChangesToFalse_chipClickable() {
+ `when`(mockFeatureFlags.isOngoingCallInImmersiveChipTapEnabled).thenReturn(false)
+
+ notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry())
+ // First, update to true
+ getStateListener().onFullscreenStateChanged(/* isFullscreen= */ true)
+ // Then, update to false
+ getStateListener().onFullscreenStateChanged(/* isFullscreen= */ false)
+
+ assertThat(chipView.hasOnClickListeners()).isTrue()
+ }
+
+ @Test
+ fun fullscreenIsTrue_butChipClickInImmersiveEnabled_chipClickable() {
+ `when`(mockFeatureFlags.isOngoingCallInImmersiveChipTapEnabled).thenReturn(true)
+
+ notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry())
+ getStateListener().onFullscreenStateChanged(/* isFullscreen= */ true)
+
+ assertThat(chipView.hasOnClickListeners()).isTrue()
+ }
+
+ // Swipe gesture tests
+
+ @Test
+ fun callStartedInImmersiveMode_swipeGestureCallbackAdded() {
+ getStateListener().onFullscreenStateChanged(true)
+
+ notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry())
+
+ verify(mockSwipeStatusBarAwayGestureHandler)
+ .addOnGestureDetectedCallback(anyString(), any())
+ }
+
+ @Test
+ fun callStartedNotInImmersiveMode_swipeGestureCallbackNotAdded() {
+ getStateListener().onFullscreenStateChanged(false)
+
+ notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry())
+
+ verify(mockSwipeStatusBarAwayGestureHandler, never())
+ .addOnGestureDetectedCallback(anyString(), any())
+ }
+
+ @Test
+ fun transitionToImmersiveMode_swipeGestureCallbackAdded() {
+ notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry())
+
+ getStateListener().onFullscreenStateChanged(true)
+
+ verify(mockSwipeStatusBarAwayGestureHandler)
+ .addOnGestureDetectedCallback(anyString(), any())
+ }
+
+ @Test
+ fun transitionOutOfImmersiveMode_swipeGestureCallbackRemoved() {
+ getStateListener().onFullscreenStateChanged(true)
+ notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry())
+ reset(mockSwipeStatusBarAwayGestureHandler)
+
+ getStateListener().onFullscreenStateChanged(false)
+
+ verify(mockSwipeStatusBarAwayGestureHandler)
+ .removeOnGestureDetectedCallback(anyString())
+ }
+
+ @Test
+ fun callEndedWhileInImmersiveMode_swipeGestureCallbackRemoved() {
+ getStateListener().onFullscreenStateChanged(true)
+ val ongoingCallNotifEntry = createOngoingCallNotifEntry()
+ notifCollectionListener.onEntryAdded(ongoingCallNotifEntry)
+ reset(mockSwipeStatusBarAwayGestureHandler)
+
+ notifCollectionListener.onEntryRemoved(ongoingCallNotifEntry, REASON_USER_STOPPED)
+
+ verify(mockSwipeStatusBarAwayGestureHandler)
+ .removeOnGestureDetectedCallback(anyString())
+ }
+
+ // TODO(b/195839150): Add test
+ // swipeGesturedTriggeredPreviously_entersImmersiveModeAgain_callbackNotAdded(). That's
+ // difficult to add now because we have no way to trigger [SwipeStatusBarAwayGestureHandler]'s
+ // callbacks in test.
+
+ // END swipe gesture tests
+
private fun createOngoingCallNotifEntry() = createCallNotifEntry(ongoingCallStyle)
private fun createScreeningCallNotifEntry() = createCallNotifEntry(screeningCallStyle)
@@ -459,6 +578,13 @@ class OngoingCallControllerTest : SysuiTestCase() {
}
private fun createNotCallNotifEntry() = NotificationEntryBuilder().build()
+
+ private fun getStateListener(): StatusBarStateController.StateListener {
+ val statusBarStateListenerCaptor = ArgumentCaptor.forClass(
+ StatusBarStateController.StateListener::class.java)
+ verify(mockStatusBarStateController).addCallback(statusBarStateListenerCaptor.capture())
+ return statusBarStateListenerCaptor.value!!
+ }
}
private val person = Person.Builder().setName("name").build()
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index e09ba346f8d8..655278657b83 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -96,6 +96,7 @@ import android.media.IRingtonePlayer;
import android.media.ISpatializerCallback;
import android.media.ISpatializerHeadToSoundStagePoseCallback;
import android.media.ISpatializerHeadTrackingModeCallback;
+import android.media.ISpatializerOutputCallback;
import android.media.IStrategyPreferredDevicesDispatcher;
import android.media.IVolumeController;
import android.media.MediaMetrics;
@@ -8498,6 +8499,26 @@ public class AudioService extends IAudioService.Stub
mSpatializerHelper.getEffectParameter(key, value);
}
+ /** @see Spatializer#getOutput */
+ public int getSpatializerOutput() {
+ enforceModifyDefaultAudioEffectsPermission();
+ return mSpatializerHelper.getOutput();
+ }
+
+ /** @see Spatializer#setOnSpatializerOutputChangedListener */
+ public void registerSpatializerOutputCallback(ISpatializerOutputCallback cb) {
+ enforceModifyDefaultAudioEffectsPermission();
+ Objects.requireNonNull(cb);
+ mSpatializerHelper.registerSpatializerOutputCallback(cb);
+ }
+
+ /** @see Spatializer#clearOnSpatializerOutputChangedListener */
+ public void unregisterSpatializerOutputCallback(ISpatializerOutputCallback cb) {
+ enforceModifyDefaultAudioEffectsPermission();
+ Objects.requireNonNull(cb);
+ mSpatializerHelper.unregisterSpatializerOutputCallback(cb);
+ }
+
/**
* post a message to schedule init/release of head tracking sensors
* @param init initialization if true, release if false
diff --git a/services/core/java/com/android/server/audio/SpatializerHelper.java b/services/core/java/com/android/server/audio/SpatializerHelper.java
index b2fa86b69cfc..7cd027c7550f 100644
--- a/services/core/java/com/android/server/audio/SpatializerHelper.java
+++ b/services/core/java/com/android/server/audio/SpatializerHelper.java
@@ -31,6 +31,7 @@ import android.media.ISpatializerCallback;
import android.media.ISpatializerHeadToSoundStagePoseCallback;
import android.media.ISpatializerHeadTrackingCallback;
import android.media.ISpatializerHeadTrackingModeCallback;
+import android.media.ISpatializerOutputCallback;
import android.media.SpatializationLevel;
import android.media.Spatializer;
import android.media.SpatializerHeadTrackingMode;
@@ -76,6 +77,7 @@ public class SpatializerHelper {
private int mCapableSpatLevel = Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE;
private int mActualHeadTrackingMode = Spatializer.HEAD_TRACKING_MODE_UNSUPPORTED;
private int mDesiredHeadTrackingMode = Spatializer.HEAD_TRACKING_MODE_UNSUPPORTED;
+ private int mSpatOutput = 0;
private @Nullable ISpatializer mSpat;
private @Nullable SpatializerCallback mSpatCallback;
private @Nullable SpatializerHeadTrackingCallback mSpatHeadTrackingCallback;
@@ -213,6 +215,18 @@ public class SpatializerHelper {
postInitSensors(true);
}
}
+
+ public void onOutputChanged(int output) {
+ logd("SpatializerCallback.onOutputChanged output:" + output);
+ int oldOutput;
+ synchronized (SpatializerHelper.this) {
+ oldOutput = mSpatOutput;
+ mSpatOutput = output;
+ }
+ if (oldOutput != output) {
+ dispatchOutputUpdate(output);
+ }
+ }
};
// spatializer head tracking callback from native
@@ -782,6 +796,60 @@ public class SpatializerHelper {
}
//------------------------------------------------------
+ // output
+
+ /** @see Spatializer#getOutput */
+ synchronized int getOutput() {
+ switch (mState) {
+ case STATE_UNINITIALIZED:
+ case STATE_NOT_SUPPORTED:
+ throw (new IllegalStateException(
+ "Can't get output without a spatializer"));
+ case STATE_ENABLED_UNAVAILABLE:
+ case STATE_DISABLED_UNAVAILABLE:
+ case STATE_DISABLED_AVAILABLE:
+ case STATE_ENABLED_AVAILABLE:
+ if (mSpat == null) {
+ throw (new IllegalStateException(
+ "null Spatializer for getOutput"));
+ }
+ break;
+ }
+ // mSpat != null
+ try {
+ return mSpat.getOutput();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error in getOutput", e);
+ return 0;
+ }
+ }
+
+ final RemoteCallbackList<ISpatializerOutputCallback> mOutputCallbacks =
+ new RemoteCallbackList<ISpatializerOutputCallback>();
+
+ synchronized void registerSpatializerOutputCallback(
+ @NonNull ISpatializerOutputCallback callback) {
+ mOutputCallbacks.register(callback);
+ }
+
+ synchronized void unregisterSpatializerOutputCallback(
+ @NonNull ISpatializerOutputCallback callback) {
+ mOutputCallbacks.unregister(callback);
+ }
+
+ private void dispatchOutputUpdate(int output) {
+ final int nbCallbacks = mOutputCallbacks.beginBroadcast();
+ for (int i = 0; i < nbCallbacks; i++) {
+ try {
+ mOutputCallbacks.getBroadcastItem(i).dispatchSpatializerOutputChanged(output);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error in dispatchOutputUpdate", e);
+ }
+ }
+ mOutputCallbacks.finishBroadcast();
+ }
+
+ //------------------------------------------------------
// sensors
private void initSensors(boolean init) {
if (mSensorManager == null) {
@@ -825,9 +893,18 @@ public class SpatializerHelper {
}
synchronized void onInitSensors(boolean init) {
- final int[] modes = getSupportedHeadTrackingModes();
- if (modes.length == 0) {
- Log.i(TAG, "not initializing sensors, no headtracking supported");
+ final String action = init ? "initializing" : "releasing";
+ if (mSpat == null) {
+ Log.e(TAG, "not " + action + " sensors, null spatializer");
+ return;
+ }
+ try {
+ if (!mSpat.isHeadTrackingSupported()) {
+ Log.e(TAG, "not " + action + " sensors, spatializer doesn't support headtracking");
+ return;
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "not " + action + " sensors, error querying headtracking", e);
return;
}
initSensors(init);
diff --git a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
index 301914615562..7096f6f419b7 100644
--- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
+++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
@@ -913,6 +913,11 @@ final class DefaultPermissionGrantPolicy {
}
grantPermissionsToSystemPackage(pm, dialerPackage, userId,
CONTACTS_PERMISSIONS, SMS_PERMISSIONS, MICROPHONE_PERMISSIONS, CAMERA_PERMISSIONS);
+ boolean isAndroidAutomotive =
+ mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE, 0);
+ if (isAndroidAutomotive) {
+ grantPermissionsToSystemPackage(pm, dialerPackage, userId, NEARBY_DEVICES_PERMISSIONS);
+ }
}
private void grantDefaultPermissionsToDefaultSystemSmsApp(PackageManagerWrapper pm,
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 5acff2b6c743..5b6bf1510bf2 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -47,7 +47,6 @@ import static android.view.KeyEvent.KEYCODE_VOLUME_UP;
import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW;
import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW;
import static android.view.WindowManager.LayoutParams.FIRST_SYSTEM_WINDOW;
-import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
import static android.view.WindowManager.LayoutParams.LAST_APPLICATION_WINDOW;
import static android.view.WindowManager.LayoutParams.LAST_SUB_WINDOW;
@@ -3289,18 +3288,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
final boolean showing = mKeyguardDelegate.isShowing();
final boolean animate = showing && !isOccluded;
mKeyguardDelegate.setOccluded(isOccluded, animate);
-
- if (!showing) {
- return false;
- }
- if (mKeyguardCandidate != null) {
- if (isOccluded) {
- mKeyguardCandidate.getAttrs().flags &= ~FLAG_SHOW_WALLPAPER;
- } else if (!mKeyguardDelegate.hasLockscreenWallpaper()) {
- mKeyguardCandidate.getAttrs().flags |= FLAG_SHOW_WALLPAPER;
- }
- }
- return true;
+ return showing;
}
/** {@inheritDoc} */
diff --git a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java
index 86ff33e8cc42..cdd36f7e25dd 100644
--- a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java
+++ b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java
@@ -235,13 +235,6 @@ public class KeyguardServiceDelegate {
return false;
}
- public boolean hasLockscreenWallpaper() {
- if (mKeyguardService != null) {
- return mKeyguardService.hasLockscreenWallpaper();
- }
- return false;
- }
-
public boolean hasKeyguard() {
return mKeyguardState.deviceHasKeyguard;
}
diff --git a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceWrapper.java b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceWrapper.java
index 051f555fab95..ac650ec0f564 100644
--- a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceWrapper.java
+++ b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceWrapper.java
@@ -261,10 +261,6 @@ public class KeyguardServiceWrapper implements IKeyguardService {
return mKeyguardStateMonitor.isTrusted();
}
- public boolean hasLockscreenWallpaper() {
- return mKeyguardStateMonitor.hasLockscreenWallpaper();
- }
-
public boolean isSecure(int userId) {
return mKeyguardStateMonitor.isSecure(userId);
}
diff --git a/services/core/java/com/android/server/policy/keyguard/KeyguardStateMonitor.java b/services/core/java/com/android/server/policy/keyguard/KeyguardStateMonitor.java
index add0b01f1879..e6511372d62c 100644
--- a/services/core/java/com/android/server/policy/keyguard/KeyguardStateMonitor.java
+++ b/services/core/java/com/android/server/policy/keyguard/KeyguardStateMonitor.java
@@ -44,7 +44,6 @@ public class KeyguardStateMonitor extends IKeyguardStateCallback.Stub {
private volatile boolean mSimSecure = true;
private volatile boolean mInputRestricted = true;
private volatile boolean mTrusted = false;
- private volatile boolean mHasLockscreenWallpaper = false;
private int mCurrentUserId;
@@ -79,10 +78,6 @@ public class KeyguardStateMonitor extends IKeyguardStateCallback.Stub {
return mTrusted;
}
- public boolean hasLockscreenWallpaper() {
- return mHasLockscreenWallpaper;
- }
-
@Override // Binder interface
public void onShowingStateChanged(boolean showing) {
mIsShowing = showing;
@@ -110,11 +105,6 @@ public class KeyguardStateMonitor extends IKeyguardStateCallback.Stub {
mCallback.onTrustedChanged();
}
- @Override // Binder interface
- public void onHasLockscreenWallpaperChanged(boolean hasLockscreenWallpaper) {
- mHasLockscreenWallpaper = hasLockscreenWallpaper;
- }
-
public interface StateCallback {
void onTrustedChanged();
void onShowingChanged();
diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
index 2a47512bb147..730766275f4a 100644
--- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java
+++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
@@ -1319,24 +1319,18 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
return IExternalVibratorService.SCALE_MUTE;
}
- int mode = checkAppOpModeLocked(vib.getUid(), vib.getPackage(),
- vib.getVibrationAttributes());
- if (mode != AppOpsManager.MODE_ALLOWED) {
- ExternalVibrationHolder vibHolder = new ExternalVibrationHolder(vib);
- vibHolder.scale = IExternalVibratorService.SCALE_MUTE;
- if (mode == AppOpsManager.MODE_ERRORED) {
- Slog.w(TAG, "Would be an error: external vibrate from uid " + vib.getUid());
- endVibrationLocked(vibHolder, Vibration.Status.IGNORED_ERROR_APP_OPS);
- } else {
- endVibrationLocked(vibHolder, Vibration.Status.IGNORED_APP_OPS);
- }
- return vibHolder.scale;
- }
-
ExternalVibrationHolder cancelingExternalVibration = null;
VibrationThread cancelingVibration = null;
int scale;
synchronized (mLock) {
+ Vibration.Status ignoreStatus = shouldIgnoreVibrationLocked(
+ vib.getUid(), vib.getPackage(), vib.getVibrationAttributes());
+ if (ignoreStatus != null) {
+ ExternalVibrationHolder vibHolder = new ExternalVibrationHolder(vib);
+ vibHolder.scale = IExternalVibratorService.SCALE_MUTE;
+ endVibrationLocked(vibHolder, ignoreStatus);
+ return vibHolder.scale;
+ }
if (mCurrentExternalVibration != null
&& mCurrentExternalVibration.externalVibration.equals(vib)) {
// We are already playing this external vibration, so we can return the same
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 394ff755d252..8ef973dbcfae 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -46,8 +46,6 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.WindowConfiguration.activityTypeToString;
import static android.app.WindowConfiguration.isSplitScreenWindowingMode;
-import static android.app.servertransaction.TransferSplashScreenViewStateItem.ATTACH_TO;
-import static android.app.servertransaction.TransferSplashScreenViewStateItem.HANDOVER_TO;
import static android.content.Intent.ACTION_MAIN;
import static android.content.Intent.CATEGORY_HOME;
import static android.content.Intent.CATEGORY_LAUNCHER;
@@ -2322,7 +2320,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
// unable to copy from shell, maybe it's not a splash screen. or something went wrong.
// either way, abort and reset the sequence.
if (parcelable == null
- || mTransferringSplashScreenState != TRANSFER_SPLASH_SCREEN_COPYING) {
+ || mTransferringSplashScreenState != TRANSFER_SPLASH_SCREEN_COPYING
+ || mStartingWindow == null) {
if (parcelable != null) {
parcelable.clearIfNeeded();
}
@@ -2331,13 +2330,17 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
return;
}
// schedule attach splashScreen to client
+ final SurfaceControl windowAnimationLeash = TaskOrganizerController
+ .applyStartingWindowAnimation(mStartingWindow);
try {
mTransferringSplashScreenState = TRANSFER_SPLASH_SCREEN_ATTACH_TO_CLIENT;
mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), appToken,
- TransferSplashScreenViewStateItem.obtain(ATTACH_TO, parcelable));
+ TransferSplashScreenViewStateItem.obtain(parcelable,
+ windowAnimationLeash));
scheduleTransferSplashScreenTimeout();
} catch (Exception e) {
Slog.w(TAG, "onCopySplashScreenComplete fail: " + this);
+ mStartingWindow.cancelAnimation();
parcelable.clearIfNeeded();
mTransferringSplashScreenState = TRANSFER_SPLASH_SCREEN_FINISH;
}
@@ -2347,14 +2350,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
removeTransferSplashScreenTimeout();
// Client has draw the splash screen, so we can remove the starting window.
if (mStartingWindow != null) {
+ mStartingWindow.cancelAnimation();
mStartingWindow.hide(false, false);
}
- try {
- mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), appToken,
- TransferSplashScreenViewStateItem.obtain(HANDOVER_TO, null));
- } catch (Exception e) {
- Slog.w(TAG, "onSplashScreenAttachComplete fail: " + this);
- }
// no matter what, remove the starting window.
mTransferringSplashScreenState = TRANSFER_SPLASH_SCREEN_FINISH;
removeStartingWindowAnimation(false /* prepareAnimation */);
diff --git a/services/core/java/com/android/server/wm/ActivityStartController.java b/services/core/java/com/android/server/wm/ActivityStartController.java
index b71ad2ea7102..6ad2f7cad3c2 100644
--- a/services/core/java/com/android/server/wm/ActivityStartController.java
+++ b/services/core/java/com/android/server/wm/ActivityStartController.java
@@ -501,6 +501,8 @@ public class ActivityStartController {
int startActivityInTaskFragment(@NonNull TaskFragment taskFragment,
@NonNull Intent activityIntent, @Nullable Bundle activityOptions,
@Nullable IBinder resultTo) {
+ final ActivityRecord caller =
+ resultTo != null ? ActivityRecord.forTokenLocked(resultTo) : null;
return obtainStarter(activityIntent, "startActivityInTaskFragment")
.setActivityOptions(activityOptions)
.setInTaskFragment(taskFragment)
@@ -508,6 +510,7 @@ public class ActivityStartController {
.setRequestCode(-1)
.setCallingUid(Binder.getCallingUid())
.setCallingPid(Binder.getCallingPid())
+ .setUserId(caller != null ? caller.mUserId : mService.getCurrentUserId())
.execute();
}
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 170789569b3f..47467abf459b 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -1979,7 +1979,7 @@ class ActivityStarter {
// Allowing the embedding if the task is owned by system.
final int hostUid = hostTask.effectiveUid;
- if (hostUid == Process.SYSTEM_UID) {
+ if (UserHandle.getAppId(hostUid) == Process.SYSTEM_UID) {
return true;
}
diff --git a/services/core/java/com/android/server/wm/AppTransitionController.java b/services/core/java/com/android/server/wm/AppTransitionController.java
index ffaf710523ee..535a061ee4ab 100644
--- a/services/core/java/com/android/server/wm/AppTransitionController.java
+++ b/services/core/java/com/android/server/wm/AppTransitionController.java
@@ -124,6 +124,7 @@ public class AppTransitionController {
@interface TransitContainerType {}
private final ArrayMap<WindowContainer, Integer> mTempTransitionReasons = new ArrayMap<>();
+ private final ArrayList<WindowContainer> mTempTransitionWindows = new ArrayList<>();
AppTransitionController(WindowManagerService service, DisplayContent displayContent) {
mService = service;
@@ -523,26 +524,44 @@ public class AppTransitionController {
}
}
+ private boolean transitionMayContainNonAppWindows(@TransitionOldType int transit) {
+ // We don't want to have the client to animate any non-app windows.
+ // Having {@code transit} of those types doesn't mean it will contain non-app windows, but
+ // non-app windows will only be included with those transition types. And we don't currently
+ // have any use case of those for TaskFragment transition.
+ // @see NonAppWindowAnimationAdapter#startNonAppWindowAnimations
+ if (transit == TRANSIT_OLD_KEYGUARD_GOING_AWAY
+ || transit == TRANSIT_OLD_KEYGUARD_GOING_AWAY_ON_WALLPAPER
+ || transit == TRANSIT_OLD_TASK_OPEN || transit == TRANSIT_OLD_TASK_TO_FRONT
+ || transit == TRANSIT_OLD_WALLPAPER_CLOSE) {
+ return true;
+ }
+
+ // Check if the wallpaper is going to participate in the transition. We don't want to have
+ // the client to animate the wallpaper windows.
+ // @see WallpaperAnimationAdapter#startWallpaperAnimations
+ return mDisplayContent.mWallpaperController.isWallpaperVisible();
+ }
+
/**
- * Overrides the pending transition with the remote animation defined by the
- * {@link ITaskFragmentOrganizer} if all windows in the transition are children of
- * {@link TaskFragment} that are organized by the same organizer.
- *
- * @return {@code true} if the transition is overridden.
+ * Finds the common {@link android.window.TaskFragmentOrganizer} that organizes all app windows
+ * in the current transition.
+ * @return {@code null} if there is no such organizer, or if there are more than one.
*/
- private boolean overrideWithTaskFragmentRemoteAnimation(@TransitionOldType int transit,
- ArraySet<Integer> activityTypes) {
- final ArrayList<WindowContainer> allWindows = new ArrayList<>();
- allWindows.addAll(mDisplayContent.mClosingApps);
- allWindows.addAll(mDisplayContent.mOpeningApps);
- allWindows.addAll(mDisplayContent.mChangingContainers);
+ @Nullable
+ private ITaskFragmentOrganizer findTaskFragmentOrganizerForAllWindows() {
+ mTempTransitionWindows.clear();
+ mTempTransitionWindows.addAll(mDisplayContent.mClosingApps);
+ mTempTransitionWindows.addAll(mDisplayContent.mOpeningApps);
+ mTempTransitionWindows.addAll(mDisplayContent.mChangingContainers);
// It should only animated by the organizer if all windows are below the same leaf Task.
Task leafTask = null;
- for (int i = allWindows.size() - 1; i >= 0; i--) {
- final ActivityRecord r = getAppFromContainer(allWindows.get(i));
+ for (int i = mTempTransitionWindows.size() - 1; i >= 0; i--) {
+ final ActivityRecord r = getAppFromContainer(mTempTransitionWindows.get(i));
if (r == null) {
- return false;
+ leafTask = null;
+ break;
}
// The activity may be a child of embedded Task, but we want to find the owner Task.
// As a result, find the organized TaskFragment first.
@@ -561,26 +580,31 @@ public class AppTransitionController {
? organizedTaskFragment.getTask()
: r.getTask();
if (task == null) {
- return false;
+ leafTask = null;
+ break;
}
// We don't want the organizer to handle transition of other non-embedded Task.
if (leafTask != null && leafTask != task) {
- return false;
+ leafTask = null;
+ break;
}
final ActivityRecord rootActivity = task.getRootActivity();
// We don't want the organizer to handle transition when the whole app is closing.
if (rootActivity == null) {
- return false;
+ leafTask = null;
+ break;
}
// We don't want the organizer to handle transition of non-embedded activity of other
// app.
if (r.getUid() != rootActivity.getUid() && !r.isEmbedded()) {
- return false;
+ leafTask = null;
+ break;
}
leafTask = task;
}
+ mTempTransitionWindows.clear();
if (leafTask == null) {
- return false;
+ return null;
}
// We don't support remote animation for Task with multiple TaskFragmentOrganizers.
@@ -599,12 +623,28 @@ public class AppTransitionController {
if (hasMultipleOrganizers) {
ProtoLog.e(WM_DEBUG_APP_TRANSITIONS, "We don't support remote animation for"
+ " Task with multiple TaskFragmentOrganizers.");
+ return null;
+ }
+ return organizer[0];
+ }
+
+ /**
+ * Overrides the pending transition with the remote animation defined by the
+ * {@link ITaskFragmentOrganizer} if all windows in the transition are children of
+ * {@link TaskFragment} that are organized by the same organizer.
+ *
+ * @return {@code true} if the transition is overridden.
+ */
+ private boolean overrideWithTaskFragmentRemoteAnimation(@TransitionOldType int transit,
+ ArraySet<Integer> activityTypes) {
+ if (transitionMayContainNonAppWindows(transit)) {
return false;
}
- final RemoteAnimationDefinition definition = organizer[0] != null
+ final ITaskFragmentOrganizer organizer = findTaskFragmentOrganizerForAllWindows();
+ final RemoteAnimationDefinition definition = organizer != null
? mDisplayContent.mAtmService.mTaskFragmentOrganizerController
- .getRemoteAnimationDefinition(organizer[0])
+ .getRemoteAnimationDefinition(organizer)
: null;
final RemoteAnimationAdapter adapter = definition != null
? definition.getAdapter(transit, activityTypes)
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index ff4dd46b1f18..aaca58ef4ac0 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -91,6 +91,7 @@ import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_IME;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_LAYER_MIRRORING;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_SCREEN_ON;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WALLPAPER;
import static com.android.internal.protolog.ProtoLogGroup.WM_SHOW_TRANSACTIONS;
import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM;
import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_CONFIG;
@@ -127,7 +128,6 @@ import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_INPUT_METHOD;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYOUT;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYOUT_REPEATS;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_SCREENSHOT;
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WALLPAPER_LIGHT;
import static com.android.server.wm.WindowManagerDebugConfig.SHOW_STACK_CRAWLS;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
@@ -588,7 +588,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
/** Caches the value whether told display manager that we have content. */
private boolean mLastHasContent;
- private DisplayRotationUtil mRotationUtil = new DisplayRotationUtil();
+ private static DisplayRotationUtil sRotationUtil = new DisplayRotationUtil();
/**
* The input method window for this display.
@@ -1003,8 +1003,8 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
final boolean committed = winAnimator.commitFinishDrawingLocked();
if (isDefaultDisplay && committed) {
if (w.hasWallpaper()) {
- if (DEBUG_WALLPAPER_LIGHT) Slog.v(TAG,
- "First draw done in potential wallpaper target " + w);
+ ProtoLog.v(WM_DEBUG_WALLPAPER,
+ "First draw done in potential wallpaper target %s", w);
mWallpaperMayChange = true;
pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
if (DEBUG_LAYOUT_REPEATS) {
@@ -2053,29 +2053,35 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
return mDisplayCutoutCache.getOrCompute(mInitialDisplayCutout, rotation);
}
- private WmDisplayCutout calculateDisplayCutoutForRotationUncached(
- DisplayCutout cutout, int rotation) {
+ static WmDisplayCutout calculateDisplayCutoutForRotationAndDisplaySizeUncached(
+ DisplayCutout cutout, int rotation, int displayWidth, int displayHeight) {
if (cutout == null || cutout == DisplayCutout.NO_CUTOUT) {
return WmDisplayCutout.NO_CUTOUT;
}
if (rotation == ROTATION_0) {
return WmDisplayCutout.computeSafeInsets(
- cutout, mInitialDisplayWidth, mInitialDisplayHeight);
+ cutout, displayWidth, displayHeight);
}
final Insets waterfallInsets =
RotationUtils.rotateInsets(cutout.getWaterfallInsets(), rotation);
final boolean rotated = (rotation == ROTATION_90 || rotation == ROTATION_270);
- final Rect[] newBounds = mRotationUtil.getRotatedBounds(
+ final Rect[] newBounds = sRotationUtil.getRotatedBounds(
cutout.getBoundingRectsAll(),
- rotation, mInitialDisplayWidth, mInitialDisplayHeight);
+ rotation, displayWidth, displayHeight);
final CutoutPathParserInfo info = cutout.getCutoutPathParserInfo();
final CutoutPathParserInfo newInfo = new CutoutPathParserInfo(
info.getDisplayWidth(), info.getDisplayHeight(), info.getDensity(),
info.getCutoutSpec(), rotation, info.getScale());
return WmDisplayCutout.computeSafeInsets(
DisplayCutout.constructDisplayCutout(newBounds, waterfallInsets, newInfo),
- rotated ? mInitialDisplayHeight : mInitialDisplayWidth,
- rotated ? mInitialDisplayWidth : mInitialDisplayHeight);
+ rotated ? displayHeight : displayWidth,
+ rotated ? displayWidth : displayHeight);
+ }
+
+ private WmDisplayCutout calculateDisplayCutoutForRotationUncached(
+ DisplayCutout cutout, int rotation) {
+ return calculateDisplayCutoutForRotationAndDisplaySizeUncached(cutout, rotation,
+ mInitialDisplayWidth, mInitialDisplayHeight);
}
RoundedCorners calculateRoundedCornersForRotation(int rotation) {
@@ -5156,9 +5162,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
onAppTransitionDone();
changes |= FINISH_LAYOUT_REDO_LAYOUT;
- if (DEBUG_WALLPAPER_LIGHT) {
- Slog.v(TAG_WM, "Wallpaper layer changed: assigning layers + relayout");
- }
+ ProtoLog.v(WM_DEBUG_WALLPAPER, "Wallpaper layer changed: assigning layers + relayout");
computeImeTarget(true /* updateImeTarget */);
mWallpaperMayChange = true;
// Since the window list has been rebuilt, focus might have to be recomputed since the
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index ccfb174e3d16..c09db1e6fa98 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -898,15 +898,6 @@ public class DisplayPolicy {
// letterboxed. Hence always let them extend under the cutout.
attrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
break;
- case TYPE_NOTIFICATION_SHADE:
- // If the Keyguard is in a hidden state (occluded by another window), we force to
- // remove the wallpaper and keyguard flag so that any change in-flight after setting
- // the keyguard as occluded wouldn't set these flags again.
- // See {@link #processKeyguardSetHiddenResultLw}.
- if (mService.mPolicy.isKeyguardOccluded()) {
- attrs.flags &= ~WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
- }
- break;
case TYPE_TOAST:
// While apps should use the dedicated toast APIs to add such windows
@@ -1530,30 +1521,50 @@ public class DisplayPolicy {
* some temporal states, but doesn't change the window frames used to show on screen.
*/
void simulateLayoutDisplay(DisplayFrames displayFrames, SparseArray<Rect> barContentFrames) {
- if (mNavigationBar != null) {
- final WindowFrames simulatedWindowFrames = new WindowFrames();
- if (INSETS_LAYOUT_GENERALIZATION) {
- simulateLayoutDecorWindow(mNavigationBar, displayFrames, simulatedWindowFrames,
+ if (INSETS_LAYOUT_GENERALIZATION) {
+ final InsetsStateController insetsStateController =
+ mDisplayContent.getInsetsStateController();
+ for (int type = 0; type < InsetsState.SIZE; type++) {
+ final InsetsSourceProvider provider =
+ insetsStateController.peekSourceProvider(type);
+ if (provider == null || !provider.hasWindow()
+ || provider.mWin.getControllableInsetProvider() != provider) {
+ continue;
+ }
+ final WindowFrames simulatedWindowFrames = new WindowFrames();
+ simulateLayoutDecorWindow(provider.mWin, displayFrames, simulatedWindowFrames,
barContentFrames,
contentFrame -> simulateLayoutForContentFrame(displayFrames,
- mNavigationBar, contentFrame));
- } else {
+ provider.mWin, contentFrame));
+ }
+ } else {
+ if (mNavigationBar != null) {
+ final WindowFrames simulatedWindowFrames = new WindowFrames();
simulateLayoutDecorWindow(mNavigationBar, displayFrames, simulatedWindowFrames,
barContentFrames, contentFrame -> layoutNavigationBar(displayFrames,
contentFrame));
}
- }
- if (mStatusBar != null) {
- final WindowFrames simulatedWindowFrames = new WindowFrames();
- if (INSETS_LAYOUT_GENERALIZATION) {
+ if (mStatusBar != null) {
+ final WindowFrames simulatedWindowFrames = new WindowFrames();
simulateLayoutDecorWindow(mStatusBar, displayFrames, simulatedWindowFrames,
barContentFrames,
+ contentFrame -> layoutStatusBar(displayFrames, contentFrame));
+ }
+ if (mExtraNavBarAlt != null) {
+ // There's no pre-defined behavior for the extra navigation bar, we need to use the
+ // new flexible insets logic anyway.
+ final WindowFrames simulatedWindowFrames = new WindowFrames();
+ simulateLayoutDecorWindow(mExtraNavBarAlt, displayFrames, simulatedWindowFrames,
+ barContentFrames,
contentFrame -> simulateLayoutForContentFrame(displayFrames,
- mStatusBar, contentFrame));
- } else {
- simulateLayoutDecorWindow(mStatusBar, displayFrames, simulatedWindowFrames,
+ mExtraNavBarAlt, contentFrame));
+ }
+ if (mClimateBarAlt != null) {
+ final WindowFrames simulatedWindowFrames = new WindowFrames();
+ simulateLayoutDecorWindow(mClimateBarAlt, displayFrames, simulatedWindowFrames,
barContentFrames,
- contentFrame -> layoutStatusBar(displayFrames, contentFrame));
+ contentFrame -> simulateLayoutForContentFrame(displayFrames,
+ mClimateBarAlt, contentFrame));
}
}
}
diff --git a/services/core/java/com/android/server/wm/KeyguardController.java b/services/core/java/com/android/server/wm/KeyguardController.java
index c630e91ee306..bd41de3a9509 100644
--- a/services/core/java/com/android/server/wm/KeyguardController.java
+++ b/services/core/java/com/android/server/wm/KeyguardController.java
@@ -194,8 +194,7 @@ class KeyguardController {
if (keyguardChanged) {
// Irrelevant to AOD.
- dismissMultiWindowModeForTaskIfNeeded(null /* currentTaskControllsingOcclusion */,
- false /* turningScreenOn */);
+ dismissMultiWindowModeForTaskIfNeeded(null /* currentTaskControllsingOcclusion */);
mKeyguardGoingAway = false;
if (keyguardShowing) {
mDismissalRequested = false;
@@ -396,6 +395,8 @@ class KeyguardController {
mService.continueWindowLayout();
}
}
+ dismissMultiWindowModeForTaskIfNeeded(topActivity != null
+ ? topActivity.getRootTask() : null);
}
/**
@@ -421,21 +422,6 @@ class KeyguardController {
}
}
- /**
- * Called when somebody wants to turn screen on.
- */
- private void handleTurnScreenOn(int displayId) {
- if (displayId != DEFAULT_DISPLAY) {
- return;
- }
-
- mTaskSupervisor.wakeUp("handleTurnScreenOn");
- if (mKeyguardShowing && canDismissKeyguard()) {
- mWindowManager.dismissKeyguard(null /* callback */, null /* message */);
- mDismissalRequested = true;
- }
- }
-
boolean isDisplayOccluded(int displayId) {
return getDisplayState(displayId).mOccluded;
}
@@ -449,11 +435,9 @@ class KeyguardController {
}
private void dismissMultiWindowModeForTaskIfNeeded(
- @Nullable Task currentTaskControllingOcclusion, boolean turningScreenOn) {
- // If turningScreenOn is true, it means that the visibility state has changed from
- // currentTaskControllingOcclusion and we should update windowing mode.
+ @Nullable Task currentTaskControllingOcclusion) {
// TODO(b/113840485): Handle docked stack for individual display.
- if (!turningScreenOn && (!mKeyguardShowing || !isDisplayOccluded(DEFAULT_DISPLAY))) {
+ if (!mKeyguardShowing || !isDisplayOccluded(DEFAULT_DISPLAY)) {
return;
}
@@ -592,26 +576,17 @@ class KeyguardController {
&& controller.mWindowManager.isKeyguardSecure(
controller.mService.getCurrentUserId());
- boolean occludingChange = false;
- boolean turningScreenOn = false;
if (mTopTurnScreenOnActivity != lastTurnScreenOnActivity
&& mTopTurnScreenOnActivity != null
&& !mService.mWindowManager.mPowerManager.isInteractive()
- && (mRequestDismissKeyguard || occludedByActivity
- || controller.canDismissKeyguard())) {
- turningScreenOn = true;
- controller.handleTurnScreenOn(mDisplayId);
+ && (mRequestDismissKeyguard || occludedByActivity)) {
+ controller.mTaskSupervisor.wakeUp("handleTurnScreenOn");
mTopTurnScreenOnActivity.setCurrentLaunchCanTurnScreenOn(false);
}
if (lastOccluded != mOccluded) {
- occludingChange = true;
controller.handleOccludedChanged(mDisplayId, mTopOccludesActivity);
}
-
- if (occludingChange || turningScreenOn) {
- controller.dismissMultiWindowModeForTaskIfNeeded(task, turningScreenOn);
- }
}
/**
diff --git a/services/core/java/com/android/server/wm/PossibleDisplayInfoMapper.java b/services/core/java/com/android/server/wm/PossibleDisplayInfoMapper.java
index ef8dee401b05..11a27c593d9e 100644
--- a/services/core/java/com/android/server/wm/PossibleDisplayInfoMapper.java
+++ b/services/core/java/com/android/server/wm/PossibleDisplayInfoMapper.java
@@ -120,8 +120,15 @@ public class PossibleDisplayInfoMapper {
@Surface.Rotation int rotation) {
DisplayInfo updatedDisplayInfo = new DisplayInfo();
updatedDisplayInfo.copyFrom(displayInfo);
- updatedDisplayInfo.rotation = rotation;
+ // Apply rotations before updating width and height
+ updatedDisplayInfo.roundedCorners = updatedDisplayInfo.roundedCorners.rotate(rotation,
+ updatedDisplayInfo.logicalWidth, updatedDisplayInfo.logicalHeight);
+ updatedDisplayInfo.displayCutout =
+ DisplayContent.calculateDisplayCutoutForRotationAndDisplaySizeUncached(
+ updatedDisplayInfo.displayCutout, rotation, updatedDisplayInfo.logicalWidth,
+ updatedDisplayInfo.logicalHeight).getDisplayCutout();
+ updatedDisplayInfo.rotation = rotation;
final int naturalWidth = updatedDisplayInfo.getNaturalWidth();
final int naturalHeight = updatedDisplayInfo.getNaturalHeight();
updatedDisplayInfo.logicalWidth = naturalWidth;
diff --git a/services/core/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java
index a663c62b40e5..22c84590fcfc 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimationController.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java
@@ -793,7 +793,7 @@ public class RecentsAnimationController implements DeathRecipient {
private RemoteAnimationTarget[] createWallpaperAnimations() {
ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS, "createWallpaperAnimations()");
- return WallpaperAnimationAdapter.startWallpaperAnimations(mService, 0L, 0L,
+ return WallpaperAnimationAdapter.startWallpaperAnimations(mDisplayContent, 0L, 0L,
adapter -> {
synchronized (mService.mGlobalLock) {
// If the wallpaper animation is canceled, continue with the recents
diff --git a/services/core/java/com/android/server/wm/RemoteAnimationController.java b/services/core/java/com/android/server/wm/RemoteAnimationController.java
index 16a45fe7cec7..ca1aed52fe04 100644
--- a/services/core/java/com/android/server/wm/RemoteAnimationController.java
+++ b/services/core/java/com/android/server/wm/RemoteAnimationController.java
@@ -207,7 +207,7 @@ class RemoteAnimationController implements DeathRecipient {
if (wrappers.mThumbnailAdapter != null
&& wrappers.mThumbnailAdapter.mCapturedFinishCallback != null) {
wrappers.mThumbnailAdapter.mCapturedFinishCallback
- .onAnimationFinished(wrappers.mAdapter.mAnimationType,
+ .onAnimationFinished(wrappers.mThumbnailAdapter.mAnimationType,
wrappers.mThumbnailAdapter);
}
mPendingAnimations.remove(i);
@@ -218,7 +218,7 @@ class RemoteAnimationController implements DeathRecipient {
private RemoteAnimationTarget[] createWallpaperAnimations() {
ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS, "createWallpaperAnimations()");
- return WallpaperAnimationAdapter.startWallpaperAnimations(mService,
+ return WallpaperAnimationAdapter.startWallpaperAnimations(mDisplayContent,
mRemoteAnimationAdapter.getDuration(),
mRemoteAnimationAdapter.getStatusBarTransitionDelay(),
adapter -> {
@@ -260,7 +260,7 @@ class RemoteAnimationController implements DeathRecipient {
}
if (adapters.mThumbnailAdapter != null) {
adapters.mThumbnailAdapter.mCapturedFinishCallback
- .onAnimationFinished(adapters.mAdapter.mAnimationType,
+ .onAnimationFinished(adapters.mThumbnailAdapter.mAnimationType,
adapters.mThumbnailAdapter);
}
mPendingAnimations.remove(i);
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 1a881f7a6227..0f7c6497ff72 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -48,6 +48,7 @@ import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_KEEP_SCREEN_O
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_STATES;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_TASKS;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WALLPAPER;
import static com.android.internal.protolog.ProtoLogGroup.WM_SHOW_SURFACE_ALLOC;
import static com.android.internal.protolog.ProtoLogGroup.WM_SHOW_TRANSACTIONS;
import static com.android.server.policy.PhoneWindowManager.SYSTEM_DIALOG_REASON_ASSIST;
@@ -79,7 +80,6 @@ import static com.android.server.wm.Task.REPARENT_LEAVE_ROOT_TASK_IN_PLACE;
import static com.android.server.wm.Task.REPARENT_MOVE_ROOT_TASK_TO_FRONT;
import static com.android.server.wm.TaskFragment.TASK_FRAGMENT_VISIBILITY_INVISIBLE;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYOUT_REPEATS;
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WALLPAPER_LIGHT;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WINDOW_TRACE;
import static com.android.server.wm.WindowManagerDebugConfig.SHOW_LIGHT_TRANSACTIONS;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
@@ -894,7 +894,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
for (int displayNdx = 0; displayNdx < mChildren.size(); ++displayNdx) {
final DisplayContent displayContent = mChildren.get(displayNdx);
if (displayContent.mWallpaperMayChange) {
- if (DEBUG_WALLPAPER_LIGHT) Slog.v(TAG, "Wallpaper may change! Adjusting");
+ ProtoLog.v(WM_DEBUG_WALLPAPER, "Wallpaper may change! Adjusting");
displayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
if (DEBUG_LAYOUT_REPEATS) {
surfacePlacer.debugLayoutRepeats("WallpaperMayChange",
@@ -2151,6 +2151,9 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
final Task rootTask;
if (singleActivity) {
rootTask = task;
+
+ // Apply the last recents animation leash transform to the task entering PIP
+ rootTask.maybeApplyLastRecentsAnimationTransaction();
} else {
// In the case of multiple activities, we will create a new task for it and then
// move the PIP activity into the task. Note that we explicitly defer the task
diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java
index f1345e8ee61e..731036b3d9a4 100644
--- a/services/core/java/com/android/server/wm/TaskOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java
@@ -422,8 +422,8 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub {
}
// Capture the animation surface control for activity's main window
- private static class StartingWindowAnimationAdaptor implements AnimationAdapter {
- private SurfaceControl mAnimationLeash;
+ static class StartingWindowAnimationAdaptor implements AnimationAdapter {
+ SurfaceControl mAnimationLeash;
@Override
public boolean getShowWallpaper() {
return false;
@@ -464,6 +464,13 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub {
}
}
+ static SurfaceControl applyStartingWindowAnimation(WindowContainer window) {
+ final StartingWindowAnimationAdaptor adaptor = new StartingWindowAnimationAdaptor();
+ window.startAnimation(window.getPendingTransaction(), adaptor, false,
+ ANIMATION_TYPE_STARTING_REVEAL);
+ return adaptor.mAnimationLeash;
+ }
+
boolean addStartingWindow(Task task, ActivityRecord activity, int launchTheme,
TaskSnapshot taskSnapshot) {
final Task rootTask = task.getRootTask();
@@ -510,12 +517,8 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub {
final WindowState mainWindow =
topActivity.findMainWindow(false/* includeStartingApp */);
if (mainWindow != null) {
- final StartingWindowAnimationAdaptor adaptor =
- new StartingWindowAnimationAdaptor();
final SurfaceControl.Transaction t = mainWindow.getPendingTransaction();
- mainWindow.startAnimation(t, adaptor, false,
- ANIMATION_TYPE_STARTING_REVEAL);
- removalInfo.windowAnimationLeash = adaptor.mAnimationLeash;
+ removalInfo.windowAnimationLeash = applyStartingWindowAnimation(mainWindow);
removalInfo.mainFrame = mainWindow.getRelativeFrame();
t.setPosition(removalInfo.windowAnimationLeash,
removalInfo.mainFrame.left, removalInfo.mainFrame.top);
diff --git a/services/core/java/com/android/server/wm/WallpaperAnimationAdapter.java b/services/core/java/com/android/server/wm/WallpaperAnimationAdapter.java
index 25f7269effe8..0b20f37eea0f 100644
--- a/services/core/java/com/android/server/wm/WallpaperAnimationAdapter.java
+++ b/services/core/java/com/android/server/wm/WallpaperAnimationAdapter.java
@@ -64,18 +64,17 @@ class WallpaperAnimationAdapter implements AnimationAdapter {
*
* @return RemoteAnimationTarget[] targets for all the visible wallpaper windows
*/
- public static RemoteAnimationTarget[] startWallpaperAnimations(WindowManagerService service,
+ public static RemoteAnimationTarget[] startWallpaperAnimations(DisplayContent displayContent,
long durationHint, long statusBarTransitionDelay,
Consumer<WallpaperAnimationAdapter> animationCanceledRunnable,
ArrayList<WallpaperAnimationAdapter> adaptersOut) {
+ if (!displayContent.mWallpaperController.isWallpaperVisible()) {
+ ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS,
+ "\tWallpaper of display=%s is not visible", displayContent);
+ return new RemoteAnimationTarget[0];
+ }
final ArrayList<RemoteAnimationTarget> targets = new ArrayList<>();
- service.mRoot.forAllWallpaperWindows(wallpaperWindow -> {
- if (!wallpaperWindow.getDisplayContent().mWallpaperController.isWallpaperVisible()) {
- ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS, "\tNot visible=%s", wallpaperWindow);
- return;
- }
-
- ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS, "\tvisible=%s", wallpaperWindow);
+ displayContent.forAllWallpaperWindows(wallpaperWindow -> {
final WallpaperAnimationAdapter wallpaperAdapter = new WallpaperAnimationAdapter(
wallpaperWindow, durationHint, statusBarTransitionDelay,
animationCanceledRunnable);
diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java
index 0909462585af..a92e0883db37 100644
--- a/services/core/java/com/android/server/wm/WallpaperController.java
+++ b/services/core/java/com/android/server/wm/WallpaperController.java
@@ -24,12 +24,12 @@ import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WALLPAPER;
import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS;
import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_SCREENSHOT;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WALLPAPER;
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WALLPAPER_LIGHT;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
import static com.android.server.wm.WindowManagerService.H.WALLPAPER_DRAW_PENDING_TIMEOUT;
@@ -49,6 +49,8 @@ import android.view.WindowManager;
import android.view.animation.Animation;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.protolog.ProtoLogImpl;
+import com.android.internal.protolog.common.ProtoLog;
import com.android.internal.util.ToBooleanFunction;
import java.io.PrintWriter;
@@ -291,10 +293,11 @@ class WallpaperController {
for (int i = mWallpaperTokens.size() - 1; i >= 0; i--) {
final WallpaperWindowToken token = mWallpaperTokens.get(i);
token.setVisibility(false);
- if (DEBUG_WALLPAPER_LIGHT && token.isVisible()) {
- Slog.d(TAG, "Hiding wallpaper " + token
- + " from " + winGoingAway + " target=" + mWallpaperTarget + " prev="
- + mPrevWallpaperTarget + "\n" + Debug.getCallers(5, " "));
+ if (ProtoLogImpl.isEnabled(WM_DEBUG_WALLPAPER) && token.isVisible()) {
+ ProtoLog.d(WM_DEBUG_WALLPAPER,
+ "Hiding wallpaper %s from %s target=%s prev=%s callers=%s",
+ token, winGoingAway, mWallpaperTarget, mPrevWallpaperTarget,
+ Debug.getCallers(5));
}
}
}
@@ -544,15 +547,15 @@ class WallpaperController {
// Is it time to stop animating?
if (!mPrevWallpaperTarget.isAnimatingLw()) {
- if (DEBUG_WALLPAPER_LIGHT) Slog.v(TAG, "No longer animating wallpaper targets!");
+ ProtoLog.v(WM_DEBUG_WALLPAPER, "No longer animating wallpaper targets!");
mPrevWallpaperTarget = null;
mWallpaperTarget = wallpaperTarget;
}
return;
}
- if (DEBUG_WALLPAPER_LIGHT) Slog.v(TAG,
- "New wallpaper target: " + wallpaperTarget + " prevTarget: " + mWallpaperTarget);
+ ProtoLog.v(WM_DEBUG_WALLPAPER, "New wallpaper target: %s prevTarget: %s caller=%s",
+ wallpaperTarget, mWallpaperTarget, Debug.getCallers(5));
mPrevWallpaperTarget = null;
@@ -570,8 +573,8 @@ class WallpaperController {
// then we are in our super special mode!
boolean oldAnim = prevWallpaperTarget.isAnimatingLw();
boolean foundAnim = wallpaperTarget.isAnimatingLw();
- if (DEBUG_WALLPAPER_LIGHT) Slog.v(TAG,
- "New animation: " + foundAnim + " old animation: " + oldAnim);
+ ProtoLog.v(WM_DEBUG_WALLPAPER, "New animation: %s old animation: %s",
+ foundAnim, oldAnim);
if (!foundAnim || !oldAnim) {
return;
@@ -586,14 +589,14 @@ class WallpaperController {
final boolean oldTargetHidden = prevWallpaperTarget.mActivityRecord != null
&& !prevWallpaperTarget.mActivityRecord.mVisibleRequested;
- if (DEBUG_WALLPAPER_LIGHT) Slog.v(TAG, "Animating wallpapers:" + " old: "
- + prevWallpaperTarget + " hidden=" + oldTargetHidden + " new: " + wallpaperTarget
- + " hidden=" + newTargetHidden);
+ ProtoLog.v(WM_DEBUG_WALLPAPER, "Animating wallpapers: "
+ + "old: %s hidden=%b new: %s hidden=%b",
+ prevWallpaperTarget, oldTargetHidden, wallpaperTarget, newTargetHidden);
mPrevWallpaperTarget = prevWallpaperTarget;
if (newTargetHidden && !oldTargetHidden) {
- if (DEBUG_WALLPAPER_LIGHT) Slog.v(TAG, "Old wallpaper still the target.");
+ ProtoLog.v(WM_DEBUG_WALLPAPER, "Old wallpaper still the target.");
// Use the old target if new target is hidden but old target
// is not. If they're both hidden, still use the new target.
mWallpaperTarget = prevWallpaperTarget;
@@ -661,8 +664,8 @@ class WallpaperController {
/* x= */ 0, /* y= */ 0, /* z= */ 0, /* extras= */ null, /* sync= */ false);
}
- if (DEBUG_WALLPAPER_LIGHT) Slog.d(TAG, "New wallpaper: target=" + mWallpaperTarget
- + " prev=" + mPrevWallpaperTarget);
+ ProtoLog.d(WM_DEBUG_WALLPAPER, "New wallpaper: target=%s prev=%s",
+ mWallpaperTarget, mPrevWallpaperTarget);
}
boolean processWallpaperDrawPendingTimeout() {
diff --git a/services/core/java/com/android/server/wm/WallpaperWindowToken.java b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
index 75c84c44c48e..3a639f50603f 100644
--- a/services/core/java/com/android/server/wm/WallpaperWindowToken.java
+++ b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
@@ -20,7 +20,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS;
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WALLPAPER_LIGHT;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WALLPAPER;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
@@ -28,7 +28,6 @@ import android.annotation.Nullable;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
-import android.util.Slog;
import android.view.DisplayInfo;
import android.view.ViewGroup;
import android.view.WindowManager;
@@ -107,8 +106,8 @@ class WallpaperWindowToken extends WindowToken {
void updateWallpaperWindows(boolean visible) {
if (isVisible() != visible) {
- if (DEBUG_WALLPAPER_LIGHT) Slog.d(TAG,
- "Wallpaper token " + token + " visible=" + visible);
+ ProtoLog.d(WM_DEBUG_WALLPAPER, "Wallpaper token %s visible=%b",
+ token, visible);
setVisibility(visible);
}
final WallpaperController wallpaperController = mDisplayContent.mWallpaperController;
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index e90290383a17..51ecce0ec9ec 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -111,6 +111,7 @@ import java.util.ArrayList;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
@@ -2835,8 +2836,18 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
taskDisplayArea.setBackgroundColor(backgroundColor);
}
+ // Atomic counter to make sure the clearColor callback is only called one.
+ // It will be called twice in the case we cancel the animation without restart
+ // (in that case it will run as the cancel and finished callbacks).
+ final AtomicInteger callbackCounter = new AtomicInteger(0);
+ final Runnable clearBackgroundColorHandler = () -> {
+ if (callbackCounter.getAndIncrement() == 0) {
+ taskDisplayArea.clearBackgroundColor();
+ }
+ };
+
final Runnable cleanUpCallback = isSettingBackgroundColor
- ? taskDisplayArea::clearBackgroundColor : () -> {};
+ ? clearBackgroundColorHandler : () -> {};
startAnimation(getPendingTransaction(), adapter, !isVisible(),
ANIMATION_TYPE_APP_TRANSITION, (type, anim) -> cleanUpCallback.run(),
diff --git a/services/core/java/com/android/server/wm/WindowManagerDebugConfig.java b/services/core/java/com/android/server/wm/WindowManagerDebugConfig.java
index 08404411c02b..c95470071e2d 100644
--- a/services/core/java/com/android/server/wm/WindowManagerDebugConfig.java
+++ b/services/core/java/com/android/server/wm/WindowManagerDebugConfig.java
@@ -43,7 +43,6 @@ public class WindowManagerDebugConfig {
static final boolean DEBUG_CONFIGURATION = false;
static final boolean DEBUG_STARTING_WINDOW_VERBOSE = false;
static final boolean DEBUG_WALLPAPER = false;
- static final boolean DEBUG_WALLPAPER_LIGHT = false || DEBUG_WALLPAPER;
static final boolean DEBUG_DRAG = true;
static final boolean DEBUG_SCREENSHOT = false;
static final boolean DEBUG_LAYOUT_REPEATS = false;
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 776a65db365f..17297bd0ba45 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -2472,7 +2472,8 @@ public class WindowManagerService extends IWindowManager.Stub
if (isPrimaryDisplay) {
transformHint = (transformHint + mPrimaryDisplayOrientation) % 4;
}
- outSurfaceControl.setTransformHint(transformHint);
+ outSurfaceControl.setTransformHint(
+ SurfaceControl.rotationToBufferTransform(transformHint));
ProtoLog.v(WM_DEBUG_ORIENTATION,
"Passing transform hint %d for window %s%s",
transformHint, win,
@@ -2582,13 +2583,17 @@ public class WindowManagerService extends IWindowManager.Stub
// an exit.
win.mAnimatingExit = true;
} else if (win.mDisplayContent.okToAnimate()
- && win.mDisplayContent.mWallpaperController.isWallpaperTarget(win)) {
- // If the wallpaper is currently behind this
- // window, we need to change both of them inside
- // of a transaction to avoid artifacts.
+ && win.mDisplayContent.mWallpaperController.isWallpaperTarget(win)
+ && win.mAttrs.type == TYPE_NOTIFICATION_SHADE) {
+ // If the wallpaper is currently behind this app window, we need to change both of them
+ // inside of a transaction to avoid artifacts.
+ // For NotificationShade, sysui is in charge of running window animation and it updates
+ // the client view visibility only after both NotificationShade and the wallpaper are
+ // hidden. So we don't need to care about exit animation, but can destroy its surface
+ // immediately.
win.mAnimatingExit = true;
} else {
- boolean stopped = win.mActivityRecord != null ? win.mActivityRecord.mAppStopped : true;
+ boolean stopped = win.mActivityRecord == null || win.mActivityRecord.mAppStopped;
// We set mDestroying=true so ActivityRecord#notifyAppStopped in-to destroy surfaces
// will later actually destroy the surface if we do not do so here. Normally we leave
// this to the exit animation.
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java
index 5a00e0d6530d..62e0a19abd7f 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java
@@ -998,6 +998,8 @@ public class VibratorManagerServiceTest {
throws Exception {
mockVibrators(1);
mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_EXTERNAL_CONTROL);
+ setRingerMode(AudioManager.RINGER_MODE_NORMAL);
+ setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 1);
createSystemReadyService();
IExternalVibrationController firstController = mock(IExternalVibrationController.class);
@@ -1006,8 +1008,11 @@ public class VibratorManagerServiceTest {
firstController);
int firstScale = mExternalVibratorService.onExternalVibrationStart(firstVibration);
- ExternalVibration secondVibration = new ExternalVibration(UID, PACKAGE_NAME, AUDIO_ATTRS,
- secondController);
+ AudioAttributes ringtoneAudioAttrs = new AudioAttributes.Builder()
+ .setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE)
+ .build();
+ ExternalVibration secondVibration = new ExternalVibration(UID, PACKAGE_NAME,
+ ringtoneAudioAttrs, secondController);
int secondScale = mExternalVibratorService.onExternalVibrationStart(secondVibration);
assertEquals(IExternalVibratorService.SCALE_NONE, firstScale);
@@ -1040,6 +1045,37 @@ public class VibratorManagerServiceTest {
assertEquals(Arrays.asList(true), mVibratorProviders.get(1).getExternalControlStates());
}
+ @Test
+ public void onExternalVibration_withRingtone_usesRingerModeSettings() {
+ mockVibrators(1);
+ mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_EXTERNAL_CONTROL);
+ mVibrator.setDefaultRingVibrationIntensity(Vibrator.VIBRATION_INTENSITY_MEDIUM);
+ AudioAttributes audioAttrs = new AudioAttributes.Builder()
+ .setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE)
+ .build();
+ ExternalVibration externalVibration = new ExternalVibration(UID, PACKAGE_NAME, audioAttrs,
+ mock(IExternalVibrationController.class));
+
+ setRingerMode(AudioManager.RINGER_MODE_NORMAL);
+ setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 0);
+ setGlobalSetting(Settings.Global.APPLY_RAMPING_RINGER, 0);
+ createSystemReadyService();
+ int scale = mExternalVibratorService.onExternalVibrationStart(externalVibration);
+ assertEquals(IExternalVibratorService.SCALE_MUTE, scale);
+
+ setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 0);
+ setGlobalSetting(Settings.Global.APPLY_RAMPING_RINGER, 1);
+ createSystemReadyService();
+ scale = mExternalVibratorService.onExternalVibrationStart(externalVibration);
+ assertEquals(IExternalVibratorService.SCALE_NONE, scale);
+
+ setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 1);
+ setGlobalSetting(Settings.Global.APPLY_RAMPING_RINGER, 0);
+ createSystemReadyService();
+ scale = mExternalVibratorService.onExternalVibrationStart(externalVibration);
+ assertEquals(IExternalVibratorService.SCALE_NONE, scale);
+ }
+
private VibrationEffectSegment expectedPrebaked(int effectId) {
return new PrebakedSegment(effectId, false, VibrationEffect.EFFECT_STRENGTH_MEDIUM);
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
index 82140f4d965c..5d0e34a80f3f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
@@ -43,6 +43,7 @@ import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.doCallRealMethod;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
@@ -893,6 +894,33 @@ public class AppTransitionControllerTest extends WindowTestsBase {
}
@Test
+ public void testOverrideTaskFragmentAdapter_noOverrideWithWallpaper() {
+ final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
+ final RemoteAnimationAdapter adapter = new RemoteAnimationAdapter(
+ new TestRemoteAnimationRunner(), 10, 1);
+ setupTaskFragmentRemoteAnimation(organizer, adapter);
+
+ // Create a TaskFragment with embedded activity.
+ final TaskFragment taskFragment = createTaskFragmentWithEmbeddedActivity(
+ createTask(mDisplayContent), organizer);
+ final ActivityRecord activity = taskFragment.getTopMostActivity();
+ activity.allDrawn = true;
+ // Set wallpaper as visible.
+ final WallpaperWindowToken wallpaperWindowToken = new WallpaperWindowToken(mWm,
+ mock(IBinder.class), true, mDisplayContent, true /* ownerCanManageAppTokens */);
+ spyOn(mDisplayContent.mWallpaperController);
+ doReturn(true).when(mDisplayContent.mWallpaperController).isWallpaperVisible();
+ spyOn(mDisplayContent.mAppTransition);
+
+ // Prepare a transition.
+ prepareAndTriggerAppTransition(activity, null /* closingActivity */, taskFragment);
+
+ // Should not be overridden when there is wallpaper in the transition.
+ verify(mDisplayContent.mAppTransition, never())
+ .overridePendingAppTransitionRemote(adapter, false /* sync */);
+ }
+
+ @Test
public void testTransitionGoodToGoForTaskFragments() {
final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
final Task task = createTask(mDisplayContent);