summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/android/app/IActivityTaskManager.aidl3
-rw-r--r--core/java/android/appwidget/AppWidgetHost.java124
-rw-r--r--core/java/android/appwidget/AppWidgetHostView.java13
-rw-r--r--core/java/android/net/vcn/persistablebundleutils/IkeSessionParamsUtils.java40
-rw-r--r--core/java/android/os/BatteryStats.java13
-rw-r--r--core/java/android/os/PowerManager.java38
-rw-r--r--core/java/android/service/notification/NotificationAssistantService.java16
-rw-r--r--core/java/android/view/IWindowManager.aidl5
-rw-r--r--core/java/android/view/InsetsSourceConsumer.java4
-rw-r--r--core/java/android/view/InsetsState.java14
-rw-r--r--core/java/android/view/WindowLayout.java18
-rw-r--r--core/java/android/widget/TextView.java63
-rw-r--r--core/java/android/window/BackAnimationAdaptor.aidl22
-rw-r--r--core/java/android/window/BackAnimationAdaptor.java72
-rw-r--r--core/java/android/window/BackNavigationInfo.java27
-rw-r--r--core/java/android/window/IBackAnimationRunner.aidl45
-rw-r--r--core/java/android/window/IBackNaviAnimationController.aidl27
-rw-r--r--core/java/android/window/TransitionInfo.java11
-rw-r--r--core/java/com/android/internal/app/ResolverActivity.java15
-rw-r--r--core/java/com/android/internal/app/chooser/DisplayResolveInfo.java9
-rw-r--r--core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java6
-rw-r--r--core/java/com/android/internal/os/BatteryStatsImpl.java11
-rw-r--r--core/res/res/values/attrs.xml16
-rw-r--r--core/res/res/values/config.xml5
-rw-r--r--core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java47
-rw-r--r--core/tests/coretests/src/android/window/BackNavigationTest.java2
-rw-r--r--data/etc/platform.xml3
-rw-r--r--data/etc/services.core.protolog.json42
-rw-r--r--ktfmt_includes.txt10
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java142
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/util/BaseDataProducer.java44
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/util/ExtensionHelper.java20
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java274
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java59
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizer.java12
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java55
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java14
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestShellExecutor.java5
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java43
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java3
-rw-r--r--packages/SettingsLib/MainSwitchPreference/res/layout-v33/settingslib_main_switch_bar.xml6
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/MainSwitchBarTest.java12
-rw-r--r--packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java4
-rw-r--r--packages/SystemUI/Android.bp1
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt3
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt35
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/Expandable.kt52
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/LaunchableView.kt68
-rw-r--r--packages/SystemUI/compose/gallery/Android.bp5
-rw-r--r--packages/SystemUI/compose/gallery/src/com/android/systemui/qs/footer/Fakes.kt160
-rw-r--r--packages/SystemUI/res-keyguard/layout/fgs_footer.xml1
-rw-r--r--packages/SystemUI/res-keyguard/layout/footer_actions.xml5
-rw-r--r--packages/SystemUI/res-keyguard/layout/footer_actions_icon_button.xml28
-rw-r--r--packages/SystemUI/res-keyguard/layout/footer_actions_number_button.xml39
-rw-r--r--packages/SystemUI/res-keyguard/layout/footer_actions_text_button.xml66
-rw-r--r--packages/SystemUI/res/layout/dream_overlay_complication_clock_time.xml4
-rw-r--r--packages/SystemUI/res/layout/notification_stack_scroll_layout.xml31
-rw-r--r--packages/SystemUI/res/layout/qs_user_detail_item.xml3
-rw-r--r--packages/SystemUI/res/layout/quick_settings_security_footer.xml1
-rw-r--r--packages/SystemUI/res/layout/status_bar_expanded.xml15
-rw-r--r--packages/SystemUI/res/raw/fingerprint_dialogue_error_to_success_lottie.json1
-rw-r--r--packages/SystemUI/res/raw/fingerprint_dialogue_fingerprint_to_success_lottie.json1
-rw-r--r--packages/SystemUI/res/values/colors.xml2
-rw-r--r--packages/SystemUI/res/values/dimens.xml7
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/regionsampling/RegionSamplingInstance.kt18
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintView.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt53
-rw-r--r--packages/SystemUI/src/com/android/systemui/camera/CameraGestureHelper.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/common/shared/model/ContentDescription.kt33
-rw-r--r--packages/SystemUI/src/com/android/systemui/common/shared/model/Icon.kt34
-rw-r--r--packages/SystemUI/src/com/android/systemui/common/ui/binder/ContentDescriptionViewBinder.kt34
-rw-r--r--packages/SystemUI/src/com/android/systemui/common/ui/binder/IconViewBinder.kt32
-rw-r--r--packages/SystemUI/src/com/android/systemui/common/ui/view/LaunchableLinearLayout.kt60
-rw-r--r--packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/controls/management/ControlsRequestDialog.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/complication/DoubleShadowTextClock.java83
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/BouncerSwipeModule.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/Flags.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt177
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt1
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/FooterActionsView.kt1
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/NewFooterActionsController.kt33
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSFgsManagerFooter.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSFragment.java123
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java775
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooterUtils.java793
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/footer/dagger/FooterActionsModule.kt39
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/footer/data/model/UserSwitcherStatusModel.kt32
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/footer/data/repository/ForegroundServicesRepository.kt121
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/footer/data/repository/UserSwitcherRepository.kt155
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractor.kt211
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/footer/domain/model/SecurityButtonConfig.kt26
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/footer/ui/binder/FooterActionsViewBinder.kt321
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsButtonViewModel.kt36
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsForegroundServicesButtonViewModel.kt28
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsSecurityButtonViewModel.kt27
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt310
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt32
-rw-r--r--packages/SystemUI/src/com/android/systemui/security/data/model/SecurityModel.kt89
-rw-r--r--packages/SystemUI/src/com/android/systemui/security/data/repository/SecurityRepository.kt58
-rw-r--r--packages/SystemUI/src/com/android/systemui/security/data/repository/SecurityRepositoryModule.kt26
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java1580
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/PanelView.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/PanelViewController.java1489
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/AlphaOptimizedFrameLayout.java33
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListAttachState.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java51
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitch.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitchController.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/LifecycleActivity.kt72
-rw-r--r--packages/SystemUI/src/com/android/systemui/wallet/ui/WalletActivity.java7
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt10
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceViewTest.kt7
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFingerprintViewTest.kt24
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/broadcast/BroadcastDispatcherTest.kt43
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/FgsManagerControllerTest.java43
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java10
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java89
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractorTest.kt212
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt408
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java8
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt12
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt6
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/flags/FakeFeatureFlags.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/flags/FakeFeatureFlags.kt)1
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeFgsManagerController.kt80
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/qs/footer/FooterActionsTestUtils.kt172
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeUserTracker.kt58
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/FakeSecurityController.kt118
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/FakeUserInfoController.kt58
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/MockUserSwitcherControllerWrapper.kt59
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/truth/correspondence/FakeUiEvent.kt30
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/truth/correspondence/LogMaker.kt29
-rw-r--r--services/core/java/com/android/server/am/UidObserverController.java16
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/BiometricStateCallback.java23
-rw-r--r--services/core/java/com/android/server/broadcastradio/hal2/RadioEventLogger.java44
-rw-r--r--services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java18
-rw-r--r--services/core/java/com/android/server/broadcastradio/hal2/TunerSession.java42
-rw-r--r--services/core/java/com/android/server/connectivity/Vpn.java1
-rw-r--r--services/core/java/com/android/server/display/BrightnessMappingStrategy.java6
-rw-r--r--services/core/java/com/android/server/display/DisplayDeviceConfig.java113
-rw-r--r--services/core/java/com/android/server/notification/ZenModeFiltering.java22
-rw-r--r--services/core/java/com/android/server/power/Notifier.java3
-rw-r--r--services/core/java/com/android/server/power/PowerGroup.java27
-rw-r--r--services/core/java/com/android/server/power/PowerManagerService.java44
-rw-r--r--services/core/java/com/android/server/wm/ActivityRecord.java7
-rw-r--r--services/core/java/com/android/server/wm/ActivityTaskManagerService.java8
-rw-r--r--services/core/java/com/android/server/wm/BLASTSyncEngine.java32
-rw-r--r--services/core/java/com/android/server/wm/BackNaviAnimationController.java418
-rw-r--r--services/core/java/com/android/server/wm/BackNavigationController.java140
-rw-r--r--services/core/java/com/android/server/wm/DisplayContent.java167
-rw-r--r--services/core/java/com/android/server/wm/DisplayPolicy.java253
-rw-r--r--services/core/java/com/android/server/wm/RootWindowContainer.java4
-rw-r--r--services/core/java/com/android/server/wm/Task.java11
-rw-r--r--services/core/java/com/android/server/wm/TaskFragment.java11
-rw-r--r--services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java35
-rw-r--r--services/core/java/com/android/server/wm/Transition.java197
-rw-r--r--services/core/java/com/android/server/wm/TransitionController.java45
-rw-r--r--services/core/java/com/android/server/wm/WallpaperController.java9
-rw-r--r--services/core/java/com/android/server/wm/WallpaperWindowToken.java3
-rw-r--r--services/core/java/com/android/server/wm/WindowContainer.java12
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerService.java37
-rw-r--r--services/core/java/com/android/server/wm/WindowOrganizerController.java54
-rw-r--r--services/core/java/com/android/server/wm/WindowState.java50
-rw-r--r--services/core/xsd/display-device-config/autobrightness.xsd33
-rw-r--r--services/core/xsd/display-device-config/display-device-config.xsd71
-rw-r--r--services/core/xsd/display-device-config/schema/current.txt15
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java6
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/BiometricStateCallbackTest.java25
-rw-r--r--services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java105
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java7
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java10
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java10
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DisplayPolicyInsetsTests.java91
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/SyncEngineTests.java50
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java129
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java34
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TransitionTests.java57
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java4
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java7
-rw-r--r--telephony/java/android/telephony/ims/ImsMmTelManager.java7
-rw-r--r--tests/testables/src/android/testing/TestableLooper.java44
-rw-r--r--tests/testables/tests/src/android/testing/TestableLooperTest.java65
-rw-r--r--tests/vcn/java/android/net/vcn/persistablebundleutils/IkeSessionParamsUtilsTest.java47
191 files changed, 9423 insertions, 3757 deletions
diff --git a/core/java/android/app/IActivityTaskManager.aidl b/core/java/android/app/IActivityTaskManager.aidl
index 02be051d973a..52732d3eba78 100644
--- a/core/java/android/app/IActivityTaskManager.aidl
+++ b/core/java/android/app/IActivityTaskManager.aidl
@@ -72,6 +72,7 @@ import android.view.IRemoteAnimationRunner;
import android.view.IWindowFocusObserver;
import android.view.RemoteAnimationDefinition;
import android.view.RemoteAnimationAdapter;
+import android.window.BackAnimationAdaptor;
import android.window.IWindowOrganizerController;
import android.window.BackNavigationInfo;
import android.window.SplashScreenView;
@@ -356,5 +357,5 @@ interface IActivityTaskManager {
* @param focusObserver a remote callback to nofify shell when the focused window lost focus.
*/
android.window.BackNavigationInfo startBackNavigation(in boolean requestAnimation,
- in IWindowFocusObserver focusObserver);
+ in IWindowFocusObserver focusObserver, in BackAnimationAdaptor adaptor);
}
diff --git a/core/java/android/appwidget/AppWidgetHost.java b/core/java/android/appwidget/AppWidgetHost.java
index fe81df0b6b3b..cc303fb1f413 100644
--- a/core/java/android/appwidget/AppWidgetHost.java
+++ b/core/java/android/appwidget/AppWidgetHost.java
@@ -70,7 +70,7 @@ public class AppWidgetHost {
private final Handler mHandler;
private final int mHostId;
private final Callbacks mCallbacks;
- private final SparseArray<AppWidgetHostView> mViews = new SparseArray<>();
+ private final SparseArray<AppWidgetHostListener> mListeners = new SparseArray<>();
private InteractionHandler mInteractionHandler;
static class Callbacks extends IAppWidgetHost.Stub {
@@ -171,6 +171,15 @@ public class AppWidgetHost {
this(context, hostId, null, context.getMainLooper());
}
+ @Nullable
+ private AppWidgetHostListener getListener(final int appWidgetId) {
+ AppWidgetHostListener tempListener = null;
+ synchronized (mListeners) {
+ tempListener = mListeners.get(appWidgetId);
+ }
+ return tempListener;
+ }
+
/**
* @hide
*/
@@ -210,11 +219,11 @@ public class AppWidgetHost {
return;
}
final int[] idsToUpdate;
- synchronized (mViews) {
- int N = mViews.size();
- idsToUpdate = new int[N];
- for (int i = 0; i < N; i++) {
- idsToUpdate[i] = mViews.keyAt(i);
+ synchronized (mListeners) {
+ int n = mListeners.size();
+ idsToUpdate = new int[n];
+ for (int i = 0; i < n; i++) {
+ idsToUpdate[i] = mListeners.keyAt(i);
}
}
List<PendingHostUpdate> updates;
@@ -349,14 +358,11 @@ public class AppWidgetHost {
if (sService == null) {
return;
}
- synchronized (mViews) {
- mViews.remove(appWidgetId);
- try {
- sService.deleteAppWidgetId(mContextOpPackageName, appWidgetId);
- }
- catch (RemoteException e) {
- throw new RuntimeException("system server dead?", e);
- }
+ removeListener(appWidgetId);
+ try {
+ sService.deleteAppWidgetId(mContextOpPackageName, appWidgetId);
+ } catch (RemoteException e) {
+ throw new RuntimeException("system server dead?", e);
}
}
@@ -412,9 +418,7 @@ public class AppWidgetHost {
AppWidgetHostView view = onCreateView(context, appWidgetId, appWidget);
view.setInteractionHandler(mInteractionHandler);
view.setAppWidget(appWidgetId, appWidget);
- synchronized (mViews) {
- mViews.put(appWidgetId, view);
- }
+ addListener(appWidgetId, view);
RemoteViews views;
try {
views = sService.getAppWidgetViews(mContextOpPackageName, appWidgetId);
@@ -439,24 +443,52 @@ public class AppWidgetHost {
* Called when the AppWidget provider for a AppWidget has been upgraded to a new apk.
*/
protected void onProviderChanged(int appWidgetId, AppWidgetProviderInfo appWidget) {
- AppWidgetHostView v;
+ AppWidgetHostListener v = getListener(appWidgetId);
// Convert complex to dp -- we are getting the AppWidgetProviderInfo from the
// AppWidgetService, which doesn't have our context, hence we need to do the
// conversion here.
appWidget.updateDimensions(mDisplayMetrics);
- synchronized (mViews) {
- v = mViews.get(appWidgetId);
- }
if (v != null) {
- v.resetAppWidget(appWidget);
+ v.onUpdateProviderInfo(appWidget);
}
}
+ /**
+ * This interface specifies the actions to be performed on the app widget based on the calls
+ * from the service
+ *
+ * @hide
+ */
+ public interface AppWidgetHostListener {
+
+ /**
+ * This function is called when the service want to reset the app widget provider info
+ * @param appWidget The new app widget provider info
+ *
+ * @hide
+ */
+ void onUpdateProviderInfo(@Nullable AppWidgetProviderInfo appWidget);
+
+ /**
+ * This function is called when the RemoteViews of the app widget is updated
+ * @param views The new RemoteViews to be set for the app widget
+ *
+ * @hide
+ */
+ void updateAppWidget(@Nullable RemoteViews views);
+
+ /**
+ * This function is called when the view ID is changed for the app widget
+ * @param viewId The new view ID to be be set for the widget
+ *
+ * @hide
+ */
+ void onViewDataChanged(int viewId);
+ }
+
void dispatchOnAppWidgetRemoved(int appWidgetId) {
- synchronized (mViews) {
- mViews.remove(appWidgetId);
- }
+ removeListener(appWidgetId);
onAppWidgetRemoved(appWidgetId);
}
@@ -476,23 +508,43 @@ public class AppWidgetHost {
// Does nothing
}
- void updateAppWidgetView(int appWidgetId, RemoteViews views) {
- AppWidgetHostView v;
- synchronized (mViews) {
- v = mViews.get(appWidgetId);
+ /**
+ * Create an AppWidgetHostListener for the given widget.
+ * The AppWidgetHost retains a pointer to the newly-created listener.
+ * @param appWidgetId The ID of the app widget for which to add the listener
+ * @param listener The listener interface that deals with actions towards the widget view
+ *
+ * @hide
+ */
+ public void addListener(int appWidgetId, @NonNull AppWidgetHostListener listener) {
+ synchronized (mListeners) {
+ mListeners.put(appWidgetId, listener);
+ }
+ }
+
+ /**
+ * Delete the listener for the given widget
+ * @param appWidgetId The ID of the app widget for which the listener is to be deleted
+
+ * @hide
+ */
+ public void removeListener(int appWidgetId) {
+ synchronized (mListeners) {
+ mListeners.remove(appWidgetId);
}
+ }
+
+ void updateAppWidgetView(int appWidgetId, RemoteViews views) {
+ AppWidgetHostListener v = getListener(appWidgetId);
if (v != null) {
v.updateAppWidget(views);
}
}
void viewDataChanged(int appWidgetId, int viewId) {
- AppWidgetHostView v;
- synchronized (mViews) {
- v = mViews.get(appWidgetId);
- }
+ AppWidgetHostListener v = getListener(appWidgetId);
if (v != null) {
- v.viewDataChanged(viewId);
+ v.onViewDataChanged(viewId);
}
}
@@ -500,8 +552,8 @@ public class AppWidgetHost {
* Clear the list of Views that have been created by this AppWidgetHost.
*/
protected void clearViews() {
- synchronized (mViews) {
- mViews.clear();
+ synchronized (mListeners) {
+ mListeners.clear();
}
}
}
diff --git a/core/java/android/appwidget/AppWidgetHostView.java b/core/java/android/appwidget/AppWidgetHostView.java
index e3bca9c9aadb..fe10b7f8b3f4 100644
--- a/core/java/android/appwidget/AppWidgetHostView.java
+++ b/core/java/android/appwidget/AppWidgetHostView.java
@@ -66,7 +66,7 @@ import java.util.concurrent.Executor;
* between updates, and will try recycling old views for each incoming
* {@link RemoteViews}.
*/
-public class AppWidgetHostView extends FrameLayout {
+public class AppWidgetHostView extends FrameLayout implements AppWidgetHost.AppWidgetHostListener {
static final String TAG = "AppWidgetHostView";
private static final String KEY_JAILED_ARRAY = "jail";
@@ -492,8 +492,11 @@ public class AppWidgetHostView extends FrameLayout {
/**
* Update the AppWidgetProviderInfo for this view, and reset it to the
* initial layout.
+ *
+ * @hide
*/
- void resetAppWidget(AppWidgetProviderInfo info) {
+ @Override
+ public void onUpdateProviderInfo(@Nullable AppWidgetProviderInfo info) {
setAppWidget(mAppWidgetId, info);
mViewMode = VIEW_MODE_NOINIT;
updateAppWidget(null);
@@ -503,6 +506,7 @@ public class AppWidgetHostView extends FrameLayout {
* Process a set of {@link RemoteViews} coming in as an update from the
* AppWidget provider. Will animate into these new views as needed
*/
+ @Override
public void updateAppWidget(RemoteViews remoteViews) {
mLastInflatedRemoteViews = remoteViews;
applyRemoteViews(remoteViews, true);
@@ -693,8 +697,11 @@ public class AppWidgetHostView extends FrameLayout {
/**
* Process data-changed notifications for the specified view in the specified
* set of {@link RemoteViews} views.
+ *
+ * @hide
*/
- void viewDataChanged(int viewId) {
+ @Override
+ public void onViewDataChanged(int viewId) {
View v = findViewById(viewId);
if ((v != null) && (v instanceof AdapterView<?>)) {
AdapterView<?> adapterView = (AdapterView<?>) v;
diff --git a/core/java/android/net/vcn/persistablebundleutils/IkeSessionParamsUtils.java b/core/java/android/net/vcn/persistablebundleutils/IkeSessionParamsUtils.java
index be573721936b..d6f191e31182 100644
--- a/core/java/android/net/vcn/persistablebundleutils/IkeSessionParamsUtils.java
+++ b/core/java/android/net/vcn/persistablebundleutils/IkeSessionParamsUtils.java
@@ -37,6 +37,7 @@ import android.net.ipsec.ike.IkeSessionParams.IkeAuthPskConfig;
import android.net.ipsec.ike.IkeSessionParams.IkeConfigRequest;
import android.os.PersistableBundle;
import android.util.ArraySet;
+import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.vcn.util.PersistableBundleUtils;
@@ -58,6 +59,8 @@ import java.util.Set;
*/
@VisibleForTesting(visibility = Visibility.PRIVATE)
public final class IkeSessionParamsUtils {
+ private static final String TAG = IkeSessionParamsUtils.class.getSimpleName();
+
private static final String SERVER_HOST_NAME_KEY = "SERVER_HOST_NAME_KEY";
private static final String SA_PROPOSALS_KEY = "SA_PROPOSALS_KEY";
private static final String LOCAL_ID_KEY = "LOCAL_ID_KEY";
@@ -72,6 +75,13 @@ public final class IkeSessionParamsUtils {
private static final String NATT_KEEPALIVE_DELAY_SEC_KEY = "NATT_KEEPALIVE_DELAY_SEC_KEY";
private static final String IKE_OPTIONS_KEY = "IKE_OPTIONS_KEY";
+ // TODO: b/243181760 Use the IKE API when they are exposed
+ @VisibleForTesting(visibility = Visibility.PRIVATE)
+ public static final int IKE_OPTION_AUTOMATIC_ADDRESS_FAMILY_SELECTION = 6;
+
+ @VisibleForTesting(visibility = Visibility.PRIVATE)
+ public static final int IKE_OPTION_AUTOMATIC_NATT_KEEPALIVES = 7;
+
private static final Set<Integer> IKE_OPTIONS = new ArraySet<>();
static {
@@ -80,6 +90,26 @@ public final class IkeSessionParamsUtils {
IKE_OPTIONS.add(IkeSessionParams.IKE_OPTION_MOBIKE);
IKE_OPTIONS.add(IkeSessionParams.IKE_OPTION_FORCE_PORT_4500);
IKE_OPTIONS.add(IkeSessionParams.IKE_OPTION_INITIAL_CONTACT);
+ IKE_OPTIONS.add(IkeSessionParams.IKE_OPTION_REKEY_MOBILITY);
+ IKE_OPTIONS.add(IKE_OPTION_AUTOMATIC_ADDRESS_FAMILY_SELECTION);
+ IKE_OPTIONS.add(IKE_OPTION_AUTOMATIC_NATT_KEEPALIVES);
+ }
+
+ /**
+ * Check if an IKE option is supported in the IPsec module installed on the device
+ *
+ * <p>This method ensures caller to safely access options that are added between dessert
+ * releases.
+ */
+ @VisibleForTesting(visibility = Visibility.PRIVATE)
+ public static boolean isIkeOptionValid(int option) {
+ try {
+ new IkeSessionParams.Builder().addIkeOption(option);
+ return true;
+ } catch (IllegalArgumentException e) {
+ Log.d(TAG, "Option not supported; discarding: " + option);
+ return false;
+ }
}
/** Serializes an IkeSessionParams to a PersistableBundle. */
@@ -130,7 +160,7 @@ public final class IkeSessionParamsUtils {
// IKE_OPTION is defined in IKE module and added in the IkeSessionParams
final List<Integer> enabledIkeOptions = new ArrayList<>();
for (int option : IKE_OPTIONS) {
- if (params.hasIkeOption(option)) {
+ if (isIkeOptionValid(option) && params.hasIkeOption(option)) {
enabledIkeOptions.add(option);
}
}
@@ -205,12 +235,16 @@ public final class IkeSessionParamsUtils {
// Clear IKE Options that are by default enabled
for (int option : IKE_OPTIONS) {
- builder.removeIkeOption(option);
+ if (isIkeOptionValid(option)) {
+ builder.removeIkeOption(option);
+ }
}
final int[] optionArray = in.getIntArray(IKE_OPTIONS_KEY);
for (int option : optionArray) {
- builder.addIkeOption(option);
+ if (isIkeOptionValid(option)) {
+ builder.addIkeOption(option);
+ }
}
return builder.build();
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index 801d34d9d884..06c35b5bec5d 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -956,7 +956,16 @@ public abstract class BatteryStats implements Parcelable {
public static final int NUM_WIFI_BATCHED_SCAN_BINS = 5;
- public static final int NUM_USER_ACTIVITY_TYPES = PowerManager.USER_ACTIVITY_EVENT_MAX + 1;
+ /**
+ * Note that these must match the constants in android.os.PowerManager.
+ * Also, if the user activity types change, the BatteryStatsImpl.VERSION must
+ * also be bumped.
+ */
+ static final String[] USER_ACTIVITY_TYPES = {
+ "other", "button", "touch", "accessibility", "attention"
+ };
+
+ public static final int NUM_USER_ACTIVITY_TYPES = USER_ACTIVITY_TYPES.length;
public abstract void noteUserActivityLocked(int type);
public abstract boolean hasUserActivity();
@@ -6168,7 +6177,7 @@ public abstract class BatteryStats implements Parcelable {
}
sb.append(val);
sb.append(" ");
- sb.append(PowerManager.userActivityEventToString(i));
+ sb.append(Uid.USER_ACTIVITY_TYPES[i]);
}
}
if (hasData) {
diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java
index 8a203e07ae6d..13ca2c34b27e 100644
--- a/core/java/android/os/PowerManager.java
+++ b/core/java/android/os/PowerManager.java
@@ -345,44 +345,6 @@ public final class PowerManager {
public static final int USER_ACTIVITY_EVENT_DEVICE_STATE = 6;
/**
- * @hide
- */
- public static final int USER_ACTIVITY_EVENT_MAX = USER_ACTIVITY_EVENT_DEVICE_STATE;
-
- /**
- * @hide
- */
- @IntDef(prefix = { "USER_ACTIVITY_EVENT_" }, value = {
- USER_ACTIVITY_EVENT_OTHER,
- USER_ACTIVITY_EVENT_BUTTON,
- USER_ACTIVITY_EVENT_TOUCH,
- USER_ACTIVITY_EVENT_ACCESSIBILITY,
- USER_ACTIVITY_EVENT_ATTENTION,
- USER_ACTIVITY_EVENT_FACE_DOWN,
- USER_ACTIVITY_EVENT_DEVICE_STATE,
- })
- @Retention(RetentionPolicy.SOURCE)
- public @interface UserActivityEvent{}
-
- /**
- *
- * Convert the user activity event to a string for debugging purposes.
- * @hide
- */
- public static String userActivityEventToString(@UserActivityEvent int userActivityEvent) {
- switch (userActivityEvent) {
- case USER_ACTIVITY_EVENT_OTHER: return "other";
- case USER_ACTIVITY_EVENT_BUTTON: return "button";
- case USER_ACTIVITY_EVENT_TOUCH: return "touch";
- case USER_ACTIVITY_EVENT_ACCESSIBILITY: return "accessibility";
- case USER_ACTIVITY_EVENT_ATTENTION: return "attention";
- case USER_ACTIVITY_EVENT_FACE_DOWN: return "faceDown";
- case USER_ACTIVITY_EVENT_DEVICE_STATE: return "deviceState";
- default: return Integer.toString(userActivityEvent);
- }
- }
-
- /**
* User activity flag: If already dimmed, extend the dim timeout
* but do not brighten. This flag is useful for keeping the screen on
* a little longer without causing a visible change such as when
diff --git a/core/java/android/service/notification/NotificationAssistantService.java b/core/java/android/service/notification/NotificationAssistantService.java
index 91042bfa3402..a9c2ad1ce915 100644
--- a/core/java/android/service/notification/NotificationAssistantService.java
+++ b/core/java/android/service/notification/NotificationAssistantService.java
@@ -91,6 +91,22 @@ public abstract class NotificationAssistantService extends NotificationListenerS
= "android.service.notification.NotificationAssistantService";
/**
+ * Activity Action: Show notification assistant detail setting page in NAS app.
+ * <p>
+ * In some cases, a matching Activity may not exist, so ensure you
+ * safeguard against this.
+ * <p>
+ * Input: Nothing.
+ * <p>
+ * Output: Nothing.
+ * @hide
+ */
+ @SdkConstant(SdkConstant.SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_NOTIFICATION_ASSISTANT_DETAIL_SETTINGS =
+ "android.service.notification.action.NOTIFICATION_ASSISTANT_DETAIL_SETTINGS";
+
+
+ /**
* Data type: int, the feedback rating score provided by user. The score can be any integer
* value depends on the experimental and feedback UX design.
*/
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index 0ef23bbfc8c9..bb26c46142d2 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -413,11 +413,6 @@ interface IWindowManager
boolean hasNavigationBar(int displayId);
/**
- * Get the position of the nav bar
- */
- int getNavBarPosition(int displayId);
-
- /**
* Lock the device immediately with the specified options (can be null).
*/
@UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553)
diff --git a/core/java/android/view/InsetsSourceConsumer.java b/core/java/android/view/InsetsSourceConsumer.java
index d63c25a09382..5236fe772a7b 100644
--- a/core/java/android/view/InsetsSourceConsumer.java
+++ b/core/java/android/view/InsetsSourceConsumer.java
@@ -176,7 +176,9 @@ public class InsetsSourceConsumer {
// If we have a new leash, make sure visibility is up-to-date, even though we
// didn't want to run an animation above.
- applyRequestedVisibilityToControl();
+ if (mController.getAnimationType(control.getType()) == ANIMATION_TYPE_NONE) {
+ applyRequestedVisibilityToControl();
+ }
// Remove the surface that owned by last control when it lost.
if (!requestedVisible && lastControl == null) {
diff --git a/core/java/android/view/InsetsState.java b/core/java/android/view/InsetsState.java
index c198098cb6ff..c102ad3a3ace 100644
--- a/core/java/android/view/InsetsState.java
+++ b/core/java/android/view/InsetsState.java
@@ -349,6 +349,20 @@ public class InsetsState implements Parcelable {
return insets;
}
+ // TODO: Remove this once the task bar is treated as navigation bar.
+ public Insets calculateInsetsWithInternalTypes(Rect frame, @InternalInsetsType int[] types,
+ boolean ignoreVisibility) {
+ Insets insets = Insets.NONE;
+ for (int i = types.length - 1; i >= 0; i--) {
+ InsetsSource source = mSources[types[i]];
+ if (source == null) {
+ continue;
+ }
+ insets = Insets.max(source.calculateInsets(frame, ignoreVisibility), insets);
+ }
+ return insets;
+ }
+
public Insets calculateInsets(Rect frame, @InsetsType int types,
InsetsVisibilities overrideVisibilities) {
Insets insets = Insets.NONE;
diff --git a/core/java/android/view/WindowLayout.java b/core/java/android/view/WindowLayout.java
index 57a0330e3c18..5ed9d2f90a72 100644
--- a/core/java/android/view/WindowLayout.java
+++ b/core/java/android/view/WindowLayout.java
@@ -118,11 +118,11 @@ public class WindowLayout {
}
if (cutoutMode == LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES) {
if (displayFrame.width() < displayFrame.height()) {
- displayCutoutSafeExceptMaybeBars.top = Integer.MIN_VALUE;
- displayCutoutSafeExceptMaybeBars.bottom = Integer.MAX_VALUE;
+ displayCutoutSafeExceptMaybeBars.top = MIN_Y;
+ displayCutoutSafeExceptMaybeBars.bottom = MAX_Y;
} else {
- displayCutoutSafeExceptMaybeBars.left = Integer.MIN_VALUE;
- displayCutoutSafeExceptMaybeBars.right = Integer.MAX_VALUE;
+ displayCutoutSafeExceptMaybeBars.left = MIN_X;
+ displayCutoutSafeExceptMaybeBars.right = MAX_X;
}
}
final boolean layoutInsetDecor = (attrs.flags & FLAG_LAYOUT_INSET_DECOR) != 0;
@@ -132,23 +132,23 @@ public class WindowLayout {
final Insets systemBarsInsets = state.calculateInsets(
displayFrame, WindowInsets.Type.systemBars(), requestedVisibilities);
if (systemBarsInsets.left > 0) {
- displayCutoutSafeExceptMaybeBars.left = Integer.MIN_VALUE;
+ displayCutoutSafeExceptMaybeBars.left = MIN_X;
}
if (systemBarsInsets.top > 0) {
- displayCutoutSafeExceptMaybeBars.top = Integer.MIN_VALUE;
+ displayCutoutSafeExceptMaybeBars.top = MIN_Y;
}
if (systemBarsInsets.right > 0) {
- displayCutoutSafeExceptMaybeBars.right = Integer.MAX_VALUE;
+ displayCutoutSafeExceptMaybeBars.right = MAX_X;
}
if (systemBarsInsets.bottom > 0) {
- displayCutoutSafeExceptMaybeBars.bottom = Integer.MAX_VALUE;
+ displayCutoutSafeExceptMaybeBars.bottom = MAX_Y;
}
}
if (type == TYPE_INPUT_METHOD) {
final InsetsSource navSource = state.peekSource(ITYPE_NAVIGATION_BAR);
if (navSource != null && navSource.calculateInsets(displayFrame, true).bottom > 0) {
// The IME can always extend under the bottom cutout if the navbar is there.
- displayCutoutSafeExceptMaybeBars.bottom = Integer.MAX_VALUE;
+ displayCutoutSafeExceptMaybeBars.bottom = MAX_Y;
}
}
final boolean attachedInParent = attachedWindowFrame != null && !layoutInScreen;
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 2268bef2c1d9..450bb1e77ec8 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -4877,20 +4877,28 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
/**
- * Set the line break style for text wrapping.
+ * Sets the line-break style for text wrapping.
*
- * The line break style to indicates the line break strategies can be used when
- * calculating the text wrapping. The line break style affects rule-based breaking. It
- * specifies the strictness of line-breaking rules.
- * There are several types for the line break style:
- * {@link LineBreakConfig#LINE_BREAK_STYLE_LOOSE},
- * {@link LineBreakConfig#LINE_BREAK_STYLE_NORMAL} and
- * {@link LineBreakConfig#LINE_BREAK_STYLE_STRICT}. The default values of the line break style
- * is {@link LineBreakConfig#LINE_BREAK_STYLE_NONE}, indicating no breaking rule is specified.
- * See <a href="https://www.w3.org/TR/css-text-3/#line-break-property">
- * the line-break property</a>
+ * <p>Line-break style specifies the line-break strategies that can be used
+ * for text wrapping. The line-break style affects rule-based line breaking
+ * by specifying the strictness of line-breaking rules.
*
- * @param lineBreakStyle the line break style for the text.
+ * <p>The following are types of line-break styles:
+ * <ul>
+ * <li>{@link LineBreakConfig#LINE_BREAK_STYLE_LOOSE}
+ * <li>{@link LineBreakConfig#LINE_BREAK_STYLE_NORMAL}
+ * <li>{@link LineBreakConfig#LINE_BREAK_STYLE_STRICT}
+ * </ul>
+ *
+ * <p>The default line-break style is
+ * {@link LineBreakConfig#LINE_BREAK_STYLE_NONE}, which specifies that no
+ * line-breaking rules are used.
+ *
+ * <p>See the
+ * <a href="https://www.w3.org/TR/css-text-3/#line-break-property" class="external">
+ * line-break property</a> for more information.
+ *
+ * @param lineBreakStyle The line-break style for the text.
*/
public void setLineBreakStyle(@LineBreakConfig.LineBreakStyle int lineBreakStyle) {
if (mLineBreakStyle != lineBreakStyle) {
@@ -4904,17 +4912,22 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
/**
- * Set the line break word style for text wrapping.
+ * Sets the line-break word style for text wrapping.
+ *
+ * <p>The line-break word style affects dictionary-based line breaking by
+ * providing phrase-based line-breaking opportunities. Use
+ * {@link LineBreakConfig#LINE_BREAK_WORD_STYLE_PHRASE} to specify
+ * phrase-based line breaking.
+ *
+ * <p>The default line-break word style is
+ * {@link LineBreakConfig#LINE_BREAK_WORD_STYLE_NONE}, which specifies that
+ * no line-breaking word style is used.
*
- * The line break word style affects dictionary-based breaking and provide phrase-based
- * breaking opportunities. The type for the line break word style is
- * {@link LineBreakConfig#LINE_BREAK_WORD_STYLE_PHRASE}. The default values of the line break
- * word style is {@link LineBreakConfig#LINE_BREAK_WORD_STYLE_NONE}, indicating no breaking rule
- * is specified.
- * See <a href="https://www.w3.org/TR/css-text-3/#word-break-property">
- * the word-break property</a>
+ * <p>See the
+ * <a href="https://www.w3.org/TR/css-text-3/#word-break-property" class="external">
+ * word-break property</a> for more information.
*
- * @param lineBreakWordStyle the line break word style for the tet
+ * @param lineBreakWordStyle The line-break word style for the text.
*/
public void setLineBreakWordStyle(@LineBreakConfig.LineBreakWordStyle int lineBreakWordStyle) {
if (mLineBreakWordStyle != lineBreakWordStyle) {
@@ -4928,18 +4941,18 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
/**
- * Get the current line break style for text wrapping.
+ * Gets the current line-break style for text wrapping.
*
- * @return the current line break style to be used for text wrapping.
+ * @return The line-break style to be used for text wrapping.
*/
public @LineBreakConfig.LineBreakStyle int getLineBreakStyle() {
return mLineBreakStyle;
}
/**
- * Get the current line word break style for text wrapping.
+ * Gets the current line-break word style for text wrapping.
*
- * @return the current line break word style to be used for text wrapping.
+ * @return The line-break word style to be used for text wrapping.
*/
public @LineBreakConfig.LineBreakWordStyle int getLineBreakWordStyle() {
return mLineBreakWordStyle;
diff --git a/core/java/android/window/BackAnimationAdaptor.aidl b/core/java/android/window/BackAnimationAdaptor.aidl
new file mode 100644
index 000000000000..1082d0ace1ae
--- /dev/null
+++ b/core/java/android/window/BackAnimationAdaptor.aidl
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2022 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.window;
+
+/**
+ * @hide
+ */
+parcelable BackAnimationAdaptor; \ No newline at end of file
diff --git a/core/java/android/window/BackAnimationAdaptor.java b/core/java/android/window/BackAnimationAdaptor.java
new file mode 100644
index 000000000000..cf82046e7e55
--- /dev/null
+++ b/core/java/android/window/BackAnimationAdaptor.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2022 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.window;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Object that describes how to run a remote back animation.
+ *
+ * @hide
+ */
+public class BackAnimationAdaptor implements Parcelable {
+
+ private final IBackAnimationRunner mRunner;
+ @BackNavigationInfo.BackTargetType
+ private final int mSupportType;
+
+ public BackAnimationAdaptor(IBackAnimationRunner runner, int supportType) {
+ mRunner = runner;
+ mSupportType = supportType;
+ }
+
+ public BackAnimationAdaptor(Parcel in) {
+ mRunner = IBackAnimationRunner.Stub.asInterface(in.readStrongBinder());
+ mSupportType = in.readInt();
+ }
+
+ public IBackAnimationRunner getRunner() {
+ return mRunner;
+ }
+
+ @BackNavigationInfo.BackTargetType public int getSupportType() {
+ return mSupportType;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeStrongInterface(mRunner);
+ dest.writeInt(mSupportType);
+ }
+
+ public static final @android.annotation.NonNull Creator<BackAnimationAdaptor> CREATOR =
+ new Creator<BackAnimationAdaptor>() {
+ public BackAnimationAdaptor createFromParcel(Parcel in) {
+ return new BackAnimationAdaptor(in);
+ }
+
+ public BackAnimationAdaptor[] newArray(int size) {
+ return new BackAnimationAdaptor[size];
+ }
+ };
+}
diff --git a/core/java/android/window/BackNavigationInfo.java b/core/java/android/window/BackNavigationInfo.java
index dd4901417671..941511ec33be 100644
--- a/core/java/android/window/BackNavigationInfo.java
+++ b/core/java/android/window/BackNavigationInfo.java
@@ -101,6 +101,8 @@ public final class BackNavigationInfo implements Parcelable {
@Nullable
private final IOnBackInvokedCallback mOnBackInvokedCallback;
+ private final boolean mIsPrepareRemoteAnimation;
+
/**
* Create a new {@link BackNavigationInfo} instance.
*
@@ -117,6 +119,9 @@ public final class BackNavigationInfo implements Parcelable {
* @param onBackNavigationDone The callback to be called once the client is done with the
* back preview.
* @param onBackInvokedCallback The back callback registered by the current top level window.
+ * @param isPrepareRemoteAnimation Return whether the core is preparing a back gesture
+ * animation, if true, the caller of startBackNavigation should
+ * be expected to receive an animation start callback.
*/
private BackNavigationInfo(@BackTargetType int type,
@Nullable RemoteAnimationTarget departingAnimationTarget,
@@ -124,7 +129,8 @@ public final class BackNavigationInfo implements Parcelable {
@Nullable HardwareBuffer screenshotBuffer,
@Nullable WindowConfiguration taskWindowConfiguration,
@Nullable RemoteCallback onBackNavigationDone,
- @Nullable IOnBackInvokedCallback onBackInvokedCallback) {
+ @Nullable IOnBackInvokedCallback onBackInvokedCallback,
+ boolean isPrepareRemoteAnimation) {
mType = type;
mDepartingAnimationTarget = departingAnimationTarget;
mScreenshotSurface = screenshotSurface;
@@ -132,6 +138,7 @@ public final class BackNavigationInfo implements Parcelable {
mTaskWindowConfiguration = taskWindowConfiguration;
mOnBackNavigationDone = onBackNavigationDone;
mOnBackInvokedCallback = onBackInvokedCallback;
+ mIsPrepareRemoteAnimation = isPrepareRemoteAnimation;
}
private BackNavigationInfo(@NonNull Parcel in) {
@@ -142,6 +149,7 @@ public final class BackNavigationInfo implements Parcelable {
mTaskWindowConfiguration = in.readTypedObject(WindowConfiguration.CREATOR);
mOnBackNavigationDone = in.readTypedObject(RemoteCallback.CREATOR);
mOnBackInvokedCallback = IOnBackInvokedCallback.Stub.asInterface(in.readStrongBinder());
+ mIsPrepareRemoteAnimation = in.readBoolean();
}
@Override
@@ -153,6 +161,7 @@ public final class BackNavigationInfo implements Parcelable {
dest.writeTypedObject(mTaskWindowConfiguration, flags);
dest.writeTypedObject(mOnBackNavigationDone, flags);
dest.writeStrongInterface(mOnBackInvokedCallback);
+ dest.writeBoolean(mIsPrepareRemoteAnimation);
}
/**
@@ -221,6 +230,10 @@ public final class BackNavigationInfo implements Parcelable {
return mOnBackInvokedCallback;
}
+ public boolean isPrepareRemoteAnimation() {
+ return mIsPrepareRemoteAnimation;
+ }
+
/**
* Callback to be called when the back preview is finished in order to notify the server that
* it can clean up the resources created for the animation.
@@ -306,6 +319,8 @@ public final class BackNavigationInfo implements Parcelable {
@Nullable
private IOnBackInvokedCallback mOnBackInvokedCallback = null;
+ private boolean mPrepareAnimation;
+
/**
* @see BackNavigationInfo#getType()
*/
@@ -366,12 +381,20 @@ public final class BackNavigationInfo implements Parcelable {
}
/**
+ * @param prepareAnimation Whether core prepare animation for shell.
+ */
+ public Builder setPrepareAnimation(boolean prepareAnimation) {
+ mPrepareAnimation = prepareAnimation;
+ return this;
+ }
+
+ /**
* Builds and returns an instance of {@link BackNavigationInfo}
*/
public BackNavigationInfo build() {
return new BackNavigationInfo(mType, mDepartingAnimationTarget, mScreenshotSurface,
mScreenshotBuffer, mTaskWindowConfiguration, mOnBackNavigationDone,
- mOnBackInvokedCallback);
+ mOnBackInvokedCallback, mPrepareAnimation);
}
}
}
diff --git a/core/java/android/window/IBackAnimationRunner.aidl b/core/java/android/window/IBackAnimationRunner.aidl
new file mode 100644
index 000000000000..ca04b9d358b8
--- /dev/null
+++ b/core/java/android/window/IBackAnimationRunner.aidl
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2022 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.window;
+
+import android.view.RemoteAnimationTarget;
+import android.window.IBackNaviAnimationController;
+
+/**
+ * Interface that is used to callback from window manager to the process that runs a back gesture
+ * animation to start or cancel it.
+ *
+ * {@hide}
+ */
+oneway interface IBackAnimationRunner {
+
+ /**
+ * Called when the system needs to cancel the current animation. This can be due to the
+ * wallpaper not drawing in time, or the handler not finishing the animation within a predefined
+ * amount of time.
+ *
+ */
+ void onAnimationCancelled() = 1;
+
+ /**
+ * Called when the system is ready for the handler to start animating all the visible tasks.
+ *
+ */
+ void onAnimationStart(in IBackNaviAnimationController controller, in int type,
+ in RemoteAnimationTarget[] apps, in RemoteAnimationTarget[] wallpapers,
+ in RemoteAnimationTarget[] nonApps) = 2;
+}
diff --git a/core/java/android/window/IBackNaviAnimationController.aidl b/core/java/android/window/IBackNaviAnimationController.aidl
new file mode 100644
index 000000000000..bba223ea339b
--- /dev/null
+++ b/core/java/android/window/IBackNaviAnimationController.aidl
@@ -0,0 +1,27 @@
+/*
+ * 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 android.window;
+
+/**
+ * Interface to be invoked by the controlling process when a back animation has finished.
+ *
+ * @param trigger Whether the back gesture has passed the triggering threshold.
+ * {@hide}
+ */
+interface IBackNaviAnimationController {
+ void finish(in boolean triggerBack);
+}
diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java
index b263b08d6abc..dc1f612534e2 100644
--- a/core/java/android/window/TransitionInfo.java
+++ b/core/java/android/window/TransitionInfo.java
@@ -119,6 +119,12 @@ public final class TransitionInfo implements Parcelable {
/** The container is going to show IME on its task after the transition. */
public static final int FLAG_WILL_IME_SHOWN = 1 << 11;
+ /** The container attaches owner profile thumbnail for cross profile animation. */
+ public static final int FLAG_CROSS_PROFILE_OWNER_THUMBNAIL = 1 << 12;
+
+ /** The container attaches work profile thumbnail for cross profile animation. */
+ public static final int FLAG_CROSS_PROFILE_WORK_THUMBNAIL = 1 << 13;
+
/** @hide */
@IntDef(prefix = { "FLAG_" }, value = {
FLAG_NONE,
@@ -508,6 +514,11 @@ public final class TransitionInfo implements Parcelable {
return mFlags;
}
+ /** Whether the given change flags has included in this change. */
+ public boolean hasFlags(@ChangeFlags int flags) {
+ return (mFlags & flags) != 0;
+ }
+
/**
* @return the bounds of the container before the change. It may be empty if the container
* is coming into existence.
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index e9e437fda8f2..0e1ed7bd0550 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -1283,9 +1283,6 @@ public class ResolverActivity extends Activity implements
}
if (target != null) {
- if (intent != null && isLaunchingTargetInOtherProfile()) {
- prepareIntentForCrossProfileLaunch(intent);
- }
safelyStartActivity(target);
// Rely on the ActivityManager to pop up a dialog regarding app suspension
@@ -1298,15 +1295,6 @@ public class ResolverActivity extends Activity implements
return true;
}
- private void prepareIntentForCrossProfileLaunch(Intent intent) {
- intent.fixUris(UserHandle.myUserId());
- }
-
- private boolean isLaunchingTargetInOtherProfile() {
- return mMultiProfilePagerAdapter.getCurrentUserHandle().getIdentifier()
- != UserHandle.myUserId();
- }
-
@VisibleForTesting
public void safelyStartActivity(TargetInfo cti) {
// We're dispatching intents that might be coming from legacy apps, so
@@ -1513,9 +1501,6 @@ public class ResolverActivity extends Activity implements
findViewById(R.id.button_open).setOnClickListener(v -> {
Intent intent = otherProfileResolveInfo.getResolvedIntent();
- if (intent != null) {
- prepareIntentForCrossProfileLaunch(intent);
- }
safelyStartActivityAsUser(otherProfileResolveInfo,
inactiveAdapter.mResolverListController.getUserHandle());
finish();
diff --git a/core/java/com/android/internal/app/chooser/DisplayResolveInfo.java b/core/java/com/android/internal/app/chooser/DisplayResolveInfo.java
index 96cc5e1bd7d2..5f4a9cd5141e 100644
--- a/core/java/com/android/internal/app/chooser/DisplayResolveInfo.java
+++ b/core/java/com/android/internal/app/chooser/DisplayResolveInfo.java
@@ -172,12 +172,14 @@ public class DisplayResolveInfo implements TargetInfo, Parcelable {
@Override
public boolean startAsCaller(ResolverActivity activity, Bundle options, int userId) {
+ prepareIntentForCrossProfileLaunch(mResolvedIntent, userId);
activity.startActivityAsCaller(mResolvedIntent, options, false, userId);
return true;
}
@Override
public boolean startAsUser(Activity activity, Bundle options, UserHandle user) {
+ prepareIntentForCrossProfileLaunch(mResolvedIntent, user.getIdentifier());
activity.startActivityAsUser(mResolvedIntent, options, user);
return false;
}
@@ -222,6 +224,13 @@ public class DisplayResolveInfo implements TargetInfo, Parcelable {
}
};
+ private static void prepareIntentForCrossProfileLaunch(Intent intent, int targetUserId) {
+ final int currentUserId = UserHandle.myUserId();
+ if (targetUserId != currentUserId) {
+ intent.fixUris(currentUserId);
+ }
+ }
+
private DisplayResolveInfo(Parcel in) {
mDisplayLabel = in.readCharSequence();
mExtendedInfo = in.readCharSequence();
diff --git a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
index 7c08a7bbc826..6c689ff2b725 100644
--- a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
+++ b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
@@ -548,6 +548,12 @@ public final class SystemUiDeviceConfigFlags {
*/
public static final String TASK_MANAGER_SHOW_FOOTER_DOT = "task_manager_show_footer_dot";
+ /**
+ * (boolean) Whether the task manager should show a stop button if the app is allowlisted
+ * by the user.
+ */
+ public static final String TASK_MANAGER_SHOW_STOP_BUTTON_FOR_USER_ALLOWLISTED_APPS =
+ "show_stop_button_for_user_allowlisted_apps";
/**
* (boolean) Whether the clipboard overlay is enabled.
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 06473b5ee8de..98d4c5976adc 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -6144,8 +6144,7 @@ public class BatteryStatsImpl extends BatteryStats {
}
@GuardedBy("this")
- public void noteUserActivityLocked(int uid, @PowerManager.UserActivityEvent int event,
- long elapsedRealtimeMs, long uptimeMs) {
+ public void noteUserActivityLocked(int uid, int event, long elapsedRealtimeMs, long uptimeMs) {
if (mOnBatteryInternal) {
uid = mapUid(uid);
getUidStatsLocked(uid, elapsedRealtimeMs, uptimeMs).noteUserActivityLocked(event);
@@ -9957,14 +9956,14 @@ public class BatteryStatsImpl extends BatteryStats {
}
@Override
- public void noteUserActivityLocked(@PowerManager.UserActivityEvent int event) {
+ public void noteUserActivityLocked(int type) {
if (mUserActivityCounters == null) {
initUserActivityLocked();
}
- if (event >= 0 && event < NUM_USER_ACTIVITY_TYPES) {
- mUserActivityCounters[event].stepAtomic();
+ if (type >= 0 && type < NUM_USER_ACTIVITY_TYPES) {
+ mUserActivityCounters[type].stepAtomic();
} else {
- Slog.w(TAG, "Unknown user activity event " + event + " was specified.",
+ Slog.w(TAG, "Unknown user activity type " + type + " was specified.",
new Throwable());
}
}
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 515ea5006667..004b5f6a3ea4 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -5484,22 +5484,22 @@
ignores some hyphen character related typographic features, e.g. kerning. -->
<enum name="fullFast" value="4" />
</attr>
- <!-- Indicates the line break strategies can be used when calculating the text wrapping. -->
+ <!-- Specifies the line-break strategies for text wrapping. -->
<attr name="lineBreakStyle">
- <!-- No line break style specific. -->
+ <!-- No line-break rules are used for line breaking. -->
<enum name="none" value="0" />
- <!-- Use the least restrictive rule for line-breaking. -->
+ <!-- The least restrictive line-break rules are used for line breaking. -->
<enum name="loose" value="1" />
- <!-- Indicates breaking text with the most comment set of line-breaking rules. -->
+ <!-- The most common line-break rules are used for line breaking. -->
<enum name="normal" value="2" />
- <!-- Indicates breaking text with the most strictest line-breaking rules. -->
+ <!-- The most strict line-break rules are used for line breaking. -->
<enum name="strict" value="3" />
</attr>
- <!-- Specify the phrase-based line break can be used when calculating the text wrapping.-->
+ <!-- Specifies the line-break word strategies for text wrapping.-->
<attr name="lineBreakWordStyle">
- <!-- No line break word style specific. -->
+ <!-- No line-break word style is used for line breaking. -->
<enum name="none" value="0" />
- <!-- Specify the phrase based breaking. -->
+ <!-- Line breaking is based on phrases, which results in text wrapping only on meaningful words. -->
<enum name="phrase" value="1" />
</attr>
<!-- Specify the type of auto-size. Note that this feature is not supported by EditText,
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index eef2ff627c79..d441b31a0996 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -1562,8 +1562,7 @@
<bool name="config_enableIdleScreenBrightnessMode">false</bool>
<!-- Array of desired screen brightness in nits corresponding to the lux values
- in the config_autoBrightnessLevels array. As with config_screenBrightnessMinimumNits and
- config_screenBrightnessMaximumNits, the display brightness is defined as the measured
+ in the config_autoBrightnessLevels array. The display brightness is defined as the measured
brightness of an all-white image.
If this is defined then:
@@ -1584,7 +1583,7 @@
<array name="config_autoBrightnessDisplayValuesNitsIdle">
</array>
- <!-- Array of output values for button backlight corresponding to the luX values
+ <!-- Array of output values for button backlight corresponding to the lux values
in the config_autoBrightnessLevels array. This array should have size one greater
than the size of the config_autoBrightnessLevels array.
The brightness values must be between 0 and 255 and be non-decreasing.
diff --git a/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java b/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java
index 2054b4fe9a35..8cf118c4b79a 100644
--- a/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java
+++ b/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java
@@ -18,8 +18,10 @@ package android.view;
import static android.view.InsetsController.ANIMATION_TYPE_NONE;
import static android.view.InsetsController.ANIMATION_TYPE_USER;
+import static android.view.InsetsSourceConsumer.ShowResult.SHOW_IMMEDIATELY;
import static android.view.InsetsState.ITYPE_IME;
import static android.view.InsetsState.ITYPE_STATUS_BAR;
+import static android.view.WindowInsets.Type.ime;
import static android.view.WindowInsets.Type.statusBars;
import static junit.framework.Assert.assertEquals;
@@ -28,6 +30,7 @@ import static junit.framework.TestCase.assertTrue;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;
@@ -75,6 +78,7 @@ public class InsetsSourceConsumerTest {
private boolean mRemoveSurfaceCalled = false;
private InsetsController mController;
private InsetsState mState;
+ private ViewRootImpl mViewRoot;
@Before
public void setup() {
@@ -86,10 +90,9 @@ public class InsetsSourceConsumerTest {
instrumentation.runOnMainSync(() -> {
final Context context = instrumentation.getTargetContext();
// cannot mock ViewRootImpl since it's final.
- final ViewRootImpl viewRootImpl = new ViewRootImpl(context,
- context.getDisplayNoVerify());
+ mViewRoot = new ViewRootImpl(context, context.getDisplayNoVerify());
try {
- viewRootImpl.setView(new TextView(context), new LayoutParams(), null);
+ mViewRoot.setView(new TextView(context), new LayoutParams(), null);
} catch (BadTokenException e) {
// activity isn't running, lets ignore BadTokenException.
}
@@ -97,7 +100,7 @@ public class InsetsSourceConsumerTest {
mSpyInsetsSource = Mockito.spy(new InsetsSource(ITYPE_STATUS_BAR));
mState.addSource(mSpyInsetsSource);
- mController = new InsetsController(new ViewRootInsetsControllerHost(viewRootImpl));
+ mController = new InsetsController(new ViewRootInsetsControllerHost(mViewRoot));
mConsumer = new InsetsSourceConsumer(ITYPE_STATUS_BAR, mState,
() -> mMockTransaction, mController) {
@Override
@@ -207,4 +210,40 @@ public class InsetsSourceConsumerTest {
});
}
+
+ @Test
+ public void testWontUpdateImeLeashVisibility_whenAnimation() {
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+ InsetsState state = new InsetsState();
+ ViewRootInsetsControllerHost host = new ViewRootInsetsControllerHost(mViewRoot);
+ InsetsController insetsController = new InsetsController(host, (controller, type) -> {
+ if (type == ITYPE_IME) {
+ return new InsetsSourceConsumer(ITYPE_IME, state,
+ () -> mMockTransaction, controller) {
+ @Override
+ public int requestShow(boolean fromController) {
+ return SHOW_IMMEDIATELY;
+ }
+ };
+ }
+ return new InsetsSourceConsumer(type, controller.getState(), Transaction::new,
+ controller);
+ }, host.getHandler());
+ InsetsSourceConsumer imeConsumer = insetsController.getSourceConsumer(ITYPE_IME);
+
+ // Initial IME insets source control with its leash.
+ imeConsumer.setControl(new InsetsSourceControl(ITYPE_IME, mLeash,
+ false /* initialVisible */, new Point(), Insets.NONE), new int[1], new int[1]);
+ reset(mMockTransaction);
+
+ // Verify when the app requests controlling show IME animation, the IME leash
+ // visibility won't be updated when the consumer received the same leash in setControl.
+ insetsController.controlWindowInsetsAnimation(ime(), 0L,
+ null /* interpolator */, null /* cancellationSignal */, null /* listener */);
+ assertTrue(insetsController.getAnimationType(ITYPE_IME) == ANIMATION_TYPE_USER);
+ imeConsumer.setControl(new InsetsSourceControl(ITYPE_IME, mLeash,
+ true /* initialVisible */, new Point(), Insets.NONE), new int[1], new int[1]);
+ verify(mMockTransaction, never()).show(mLeash);
+ });
+ }
}
diff --git a/core/tests/coretests/src/android/window/BackNavigationTest.java b/core/tests/coretests/src/android/window/BackNavigationTest.java
index bbbc4230903a..77d61d589015 100644
--- a/core/tests/coretests/src/android/window/BackNavigationTest.java
+++ b/core/tests/coretests/src/android/window/BackNavigationTest.java
@@ -92,7 +92,7 @@ public class BackNavigationTest {
try {
mInstrumentation.getUiAutomation().waitForIdle(500, 1000);
BackNavigationInfo info = ActivityTaskManager.getService()
- .startBackNavigation(true, null);
+ .startBackNavigation(true, null, null);
assertNotNull("BackNavigationInfo is null", info);
assertNotNull("OnBackInvokedCallback is null", info.getOnBackInvokedCallback());
info.getOnBackInvokedCallback().onBackInvoked();
diff --git a/data/etc/platform.xml b/data/etc/platform.xml
index 6897c01844a8..9a1b8a90dbfd 100644
--- a/data/etc/platform.xml
+++ b/data/etc/platform.xml
@@ -171,10 +171,11 @@
<assign-permission name="android.permission.UPDATE_DEVICE_STATS" uid="audioserver" />
<assign-permission name="android.permission.UPDATE_APP_OPS_STATS" uid="audioserver" />
<assign-permission name="android.permission.PACKAGE_USAGE_STATS" uid="audioserver" />
- <assign-permission name="android.permission.INTERACT_ACROSS_USERS" uid="audioserver" />
+ <assign-permission name="android.permission.INTERACT_ACROSS_USERS_FULL" uid="audioserver" />
<assign-permission name="android.permission.OBSERVE_SENSOR_PRIVACY" uid="audioserver" />
<assign-permission name="android.permission.MODIFY_AUDIO_SETTINGS" uid="cameraserver" />
+ <assign-permission name="android.permission.INTERACT_ACROSS_USERS_FULL" uid="cameraserver" />
<assign-permission name="android.permission.ACCESS_SURFACE_FLINGER" uid="cameraserver" />
<assign-permission name="android.permission.WAKE_LOCK" uid="cameraserver" />
<assign-permission name="android.permission.UPDATE_DEVICE_STATS" uid="cameraserver" />
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index 4976784416ad..c2074da55c9a 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -2041,12 +2041,6 @@
"group": "WM_DEBUG_CONFIGURATION",
"at": "com\/android\/server\/wm\/ActivityRecord.java"
},
- "-108248992": {
- "message": "Defer transition ready for TaskFragmentTransaction=%s",
- "level": "VERBOSE",
- "group": "WM_DEBUG_WINDOW_TRANSITIONS",
- "at": "com\/android\/server\/wm\/TaskFragmentOrganizerController.java"
- },
"-106400104": {
"message": "Preload recents with %s",
"level": "DEBUG",
@@ -2095,12 +2089,6 @@
"group": "WM_DEBUG_STATES",
"at": "com\/android\/server\/wm\/TaskFragment.java"
},
- "-79016993": {
- "message": "Continue transition ready for TaskFragmentTransaction=%s",
- "level": "VERBOSE",
- "group": "WM_DEBUG_WINDOW_TRANSITIONS",
- "at": "com\/android\/server\/wm\/TaskFragmentOrganizerController.java"
- },
"-70719599": {
"message": "Unregister remote animations for organizer=%s uid=%d pid=%d",
"level": "VERBOSE",
@@ -2647,6 +2635,12 @@
"group": "WM_DEBUG_ANIM",
"at": "com\/android\/server\/wm\/WindowContainer.java"
},
+ "390947100": {
+ "message": "Screenshotting %s [%s]",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+ "at": "com\/android\/server\/wm\/Transition.java"
+ },
"397382873": {
"message": "Moving to PAUSED: %s %s",
"level": "VERBOSE",
@@ -3079,6 +3073,12 @@
"group": "WM_DEBUG_REMOTE_ANIMATIONS",
"at": "com\/android\/server\/wm\/RemoteAnimationController.java"
},
+ "851368695": {
+ "message": "Deferred transition id=%d has been continued before the TaskFragmentTransaction=%s is finished",
+ "level": "WARN",
+ "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+ "at": "com\/android\/server\/wm\/TaskFragmentOrganizerController.java"
+ },
"872933199": {
"message": "Changing focus from %s to %s displayId=%d Callers=%s",
"level": "DEBUG",
@@ -3271,6 +3271,12 @@
"group": "WM_DEBUG_CONFIGURATION",
"at": "com\/android\/server\/wm\/ActivityRecord.java"
},
+ "1046228706": {
+ "message": "Defer transition id=%d for TaskFragmentTransaction=%s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+ "at": "com\/android\/server\/wm\/TaskFragmentOrganizerController.java"
+ },
"1046922686": {
"message": "requestScrollCapture: caught exception dispatching callback: %s",
"level": "WARN",
@@ -3313,6 +3319,12 @@
"group": "WM_DEBUG_REMOTE_ANIMATIONS",
"at": "com\/android\/server\/wm\/WallpaperAnimationAdapter.java"
},
+ "1075460705": {
+ "message": "Continue transition id=%d for TaskFragmentTransaction=%s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+ "at": "com\/android\/server\/wm\/TaskFragmentOrganizerController.java"
+ },
"1087494661": {
"message": "Clear window stuck on animatingExit status: %s",
"level": "WARN",
@@ -4171,6 +4183,12 @@
"group": "WM_DEBUG_FOCUS_LIGHT",
"at": "com\/android\/server\/wm\/InputMonitor.java"
},
+ "2004282287": {
+ "message": "Override sync-method for %s because seamless rotating",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+ "at": "com\/android\/server\/wm\/Transition.java"
+ },
"2010476671": {
"message": "Animation done in %s: reportedVisible=%b okToDisplay=%b okToAnimate=%b startingDisplayed=%b",
"level": "VERBOSE",
diff --git a/ktfmt_includes.txt b/ktfmt_includes.txt
index 96da8c9c803b..c7062e093135 100644
--- a/ktfmt_includes.txt
+++ b/ktfmt_includes.txt
@@ -6,4 +6,12 @@ packages/SystemUI/src/com/android/systemui/keyguard/data
packages/SystemUI/src/com/android/systemui/keyguard/dagger
packages/SystemUI/src/com/android/systemui/keyguard/domain
packages/SystemUI/src/com/android/systemui/keyguard/shared
-packages/SystemUI/src/com/android/systemui/keyguard/ui \ No newline at end of file
+packages/SystemUI/src/com/android/systemui/keyguard/ui
+packages/SystemUI/src/com/android/systemui/qs/footer
+packages/SystemUI/src/com/android/systemui/security
+packages/SystemUI/src/com/android/systemui/common/
+packages/SystemUI/tests/utils/src/com/android/systemui/qs/
+packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/FakeSecurityController.kt
+packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/FakeUserInfoController.kt
+packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/MockUserSwitcherControllerWrapper.kt
+packages/SystemUI/tests/src/com/android/systemui/qs/footer/ \ No newline at end of file
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
index 6bfb16a3c22d..f24401f0cd53 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
@@ -20,21 +20,26 @@ import static android.view.Display.DEFAULT_DISPLAY;
import static androidx.window.common.CommonFoldingFeature.COMMON_STATE_FLAT;
import static androidx.window.common.CommonFoldingFeature.COMMON_STATE_HALF_OPENED;
+import static androidx.window.util.ExtensionHelper.isZero;
import static androidx.window.util.ExtensionHelper.rotateRectToDisplayRotation;
import static androidx.window.util.ExtensionHelper.transformToWindowSpaceRect;
-import android.annotation.Nullable;
import android.app.Activity;
import android.app.ActivityClient;
import android.app.Application;
import android.app.WindowConfiguration;
+import android.content.ComponentCallbacks;
import android.content.Context;
+import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.IBinder;
import android.util.ArrayMap;
+import android.window.WindowContext;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.UiContext;
import androidx.window.common.CommonFoldingFeature;
import androidx.window.common.DeviceStateManagerFoldingFeatureProducer;
import androidx.window.common.EmptyLifecycleCallbacksAdapter;
@@ -58,11 +63,14 @@ import java.util.function.Consumer;
public class WindowLayoutComponentImpl implements WindowLayoutComponent {
private static final String TAG = "SampleExtension";
- private final Map<Activity, Consumer<WindowLayoutInfo>> mWindowLayoutChangeListeners =
+ private final Map<Context, Consumer<WindowLayoutInfo>> mWindowLayoutChangeListeners =
new ArrayMap<>();
private final DataProducer<List<CommonFoldingFeature>> mFoldingFeatureProducer;
+ private final Map<IBinder, WindowContextConfigListener> mWindowContextConfigListeners =
+ new ArrayMap<>();
+
public WindowLayoutComponentImpl(@NonNull Context context) {
((Application) context.getApplicationContext())
.registerActivityLifecycleCallbacks(new NotifyOnConfigurationChanged());
@@ -78,14 +86,42 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent {
* @param activity hosting a {@link android.view.Window}
* @param consumer interested in receiving updates to {@link WindowLayoutInfo}
*/
+ @Override
public void addWindowLayoutInfoListener(@NonNull Activity activity,
@NonNull Consumer<WindowLayoutInfo> consumer) {
+ addWindowLayoutInfoListener((Context) activity, consumer);
+ }
+
+ /**
+ * Similar to {@link #addWindowLayoutInfoListener(Activity, Consumer)}, but takes a UI Context
+ * as a parameter.
+ */
+ // TODO(b/204073440): Add @Override to hook the API in WM extensions library.
+ public void addWindowLayoutInfoListener(@NonNull @UiContext Context context,
+ @NonNull Consumer<WindowLayoutInfo> consumer) {
+ if (mWindowLayoutChangeListeners.containsKey(context)
+ || mWindowLayoutChangeListeners.containsValue(consumer)) {
+ // Early return if the listener or consumer has been registered.
+ return;
+ }
+ if (!context.isUiContext()) {
+ throw new IllegalArgumentException("Context must be a UI Context, which should be"
+ + " an Activity or a WindowContext");
+ }
mFoldingFeatureProducer.getData((features) -> {
// Get the WindowLayoutInfo from the activity and pass the value to the layoutConsumer.
- WindowLayoutInfo newWindowLayout = getWindowLayoutInfo(activity, features);
+ WindowLayoutInfo newWindowLayout = getWindowLayoutInfo(context, features);
consumer.accept(newWindowLayout);
});
- mWindowLayoutChangeListeners.put(activity, consumer);
+ mWindowLayoutChangeListeners.put(context, consumer);
+
+ if (context instanceof WindowContext) {
+ final IBinder windowContextToken = context.getWindowContextToken();
+ final WindowContextConfigListener listener =
+ new WindowContextConfigListener(windowContextToken);
+ context.registerComponentCallbacks(listener);
+ mWindowContextConfigListeners.put(windowContextToken, listener);
+ }
}
/**
@@ -93,18 +129,30 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent {
*
* @param consumer no longer interested in receiving updates to {@link WindowLayoutInfo}
*/
+ @Override
public void removeWindowLayoutInfoListener(@NonNull Consumer<WindowLayoutInfo> consumer) {
+ for (Context context : mWindowLayoutChangeListeners.keySet()) {
+ if (!mWindowLayoutChangeListeners.get(context).equals(consumer)) {
+ continue;
+ }
+ if (context instanceof WindowContext) {
+ final IBinder token = context.getWindowContextToken();
+ context.unregisterComponentCallbacks(mWindowContextConfigListeners.get(token));
+ mWindowContextConfigListeners.remove(token);
+ }
+ break;
+ }
mWindowLayoutChangeListeners.values().remove(consumer);
}
@NonNull
- Set<Activity> getActivitiesListeningForLayoutChanges() {
+ Set<Context> getContextsListeningForLayoutChanges() {
return mWindowLayoutChangeListeners.keySet();
}
private boolean isListeningForLayoutChanges(IBinder token) {
- for (Activity activity: getActivitiesListeningForLayoutChanges()) {
- if (token.equals(activity.getWindow().getAttributes().token)) {
+ for (Context context: getContextsListeningForLayoutChanges()) {
+ if (token.equals(Context.getToken(context))) {
return true;
}
}
@@ -138,10 +186,10 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent {
}
private void onDisplayFeaturesChanged(List<CommonFoldingFeature> storedFeatures) {
- for (Activity activity : getActivitiesListeningForLayoutChanges()) {
+ for (Context context : getContextsListeningForLayoutChanges()) {
// Get the WindowLayoutInfo from the activity and pass the value to the layoutConsumer.
- Consumer<WindowLayoutInfo> layoutConsumer = mWindowLayoutChangeListeners.get(activity);
- WindowLayoutInfo newWindowLayout = getWindowLayoutInfo(activity, storedFeatures);
+ Consumer<WindowLayoutInfo> layoutConsumer = mWindowLayoutChangeListeners.get(context);
+ WindowLayoutInfo newWindowLayout = getWindowLayoutInfo(context, storedFeatures);
layoutConsumer.accept(newWindowLayout);
}
}
@@ -149,11 +197,12 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent {
/**
* Translates the {@link DisplayFeature} into a {@link WindowLayoutInfo} when a
* valid state is found.
- * @param activity a proxy for the {@link android.view.Window} that contains the
+ * @param context a proxy for the {@link android.view.Window} that contains the
+ * {@link DisplayFeature}.
*/
- private WindowLayoutInfo getWindowLayoutInfo(
- @NonNull Activity activity, List<CommonFoldingFeature> storedFeatures) {
- List<DisplayFeature> displayFeatureList = getDisplayFeatures(activity, storedFeatures);
+ private WindowLayoutInfo getWindowLayoutInfo(@NonNull @UiContext Context context,
+ List<CommonFoldingFeature> storedFeatures) {
+ List<DisplayFeature> displayFeatureList = getDisplayFeatures(context, storedFeatures);
return new WindowLayoutInfo(displayFeatureList);
}
@@ -170,18 +219,18 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent {
* bounds are not valid, constructing a {@link FoldingFeature} will throw an
* {@link IllegalArgumentException} since this can cause negative UI effects down stream.
*
- * @param activity a proxy for the {@link android.view.Window} that contains the
+ * @param context a proxy for the {@link android.view.Window} that contains the
* {@link DisplayFeature}.
* are within the {@link android.view.Window} of the {@link Activity}
*/
private List<DisplayFeature> getDisplayFeatures(
- @NonNull Activity activity, List<CommonFoldingFeature> storedFeatures) {
+ @NonNull @UiContext Context context, List<CommonFoldingFeature> storedFeatures) {
List<DisplayFeature> features = new ArrayList<>();
- if (!shouldReportDisplayFeatures(activity)) {
+ if (!shouldReportDisplayFeatures(context)) {
return features;
}
- int displayId = activity.getDisplay().getDisplayId();
+ int displayId = context.getDisplay().getDisplayId();
for (CommonFoldingFeature baseFeature : storedFeatures) {
Integer state = convertToExtensionState(baseFeature.getState());
if (state == null) {
@@ -189,9 +238,9 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent {
}
Rect featureRect = baseFeature.getRect();
rotateRectToDisplayRotation(displayId, featureRect);
- transformToWindowSpaceRect(activity, featureRect);
+ transformToWindowSpaceRect(context, featureRect);
- if (!isRectZero(featureRect)) {
+ if (!isZero(featureRect)) {
// TODO(b/228641877): Remove guarding when fixed.
features.add(new FoldingFeature(featureRect, baseFeature.getType(), state));
}
@@ -203,15 +252,21 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent {
* Checks whether display features should be reported for the activity.
* TODO(b/238948678): Support reporting display features in all windowing modes.
*/
- private boolean shouldReportDisplayFeatures(@NonNull Activity activity) {
- int displayId = activity.getDisplay().getDisplayId();
+ private boolean shouldReportDisplayFeatures(@NonNull @UiContext Context context) {
+ int displayId = context.getDisplay().getDisplayId();
if (displayId != DEFAULT_DISPLAY) {
// Display features are not supported on secondary displays.
return false;
}
- final int taskWindowingMode = ActivityClient.getInstance().getTaskWindowingMode(
- activity.getActivityToken());
- if (taskWindowingMode == -1) {
+ final int windowingMode;
+ if (context instanceof Activity) {
+ windowingMode = ActivityClient.getInstance().getTaskWindowingMode(
+ context.getActivityToken());
+ } else {
+ windowingMode = context.getResources().getConfiguration().windowConfiguration
+ .getWindowingMode();
+ }
+ if (windowingMode == -1) {
// If we cannot determine the task windowing mode for any reason, it is likely that we
// won't be able to determine its position correctly as well. DisplayFeatures' bounds
// in this case can't be computed correctly, so we should skip.
@@ -219,36 +274,43 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent {
}
// It is recommended not to report any display features in multi-window mode, since it
// won't be possible to synchronize the display feature positions with window movement.
- return !WindowConfiguration.inMultiWindowMode(taskWindowingMode);
+ return !WindowConfiguration.inMultiWindowMode(windowingMode);
}
- /**
- * Returns {@link true} if a {@link Rect} has zero width and zero height,
- * {@code false} otherwise.
- */
- private boolean isRectZero(Rect rect) {
- return rect.width() == 0 && rect.height() == 0;
+ private void onDisplayFeaturesChangedIfListening(@NonNull IBinder token) {
+ if (isListeningForLayoutChanges(token)) {
+ mFoldingFeatureProducer.getData(
+ WindowLayoutComponentImpl.this::onDisplayFeaturesChanged);
+ }
}
private final class NotifyOnConfigurationChanged extends EmptyLifecycleCallbacksAdapter {
@Override
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
super.onActivityCreated(activity, savedInstanceState);
- onDisplayFeaturesChangedIfListening(activity);
+ onDisplayFeaturesChangedIfListening(activity.getActivityToken());
}
@Override
public void onActivityConfigurationChanged(Activity activity) {
super.onActivityConfigurationChanged(activity);
- onDisplayFeaturesChangedIfListening(activity);
+ onDisplayFeaturesChangedIfListening(activity.getActivityToken());
+ }
+ }
+
+ private final class WindowContextConfigListener implements ComponentCallbacks {
+ final IBinder mToken;
+
+ WindowContextConfigListener(IBinder token) {
+ mToken = token;
}
- private void onDisplayFeaturesChangedIfListening(Activity activity) {
- IBinder token = activity.getWindow().getAttributes().token;
- if (token == null || isListeningForLayoutChanges(token)) {
- mFoldingFeatureProducer.getData(
- WindowLayoutComponentImpl.this::onDisplayFeaturesChanged);
- }
+ @Override
+ public void onConfigurationChanged(@NonNull Configuration newConfig) {
+ onDisplayFeaturesChangedIfListening(mToken);
}
+
+ @Override
+ public void onLowMemory() {}
}
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/util/BaseDataProducer.java b/libs/WindowManager/Jetpack/src/androidx/window/util/BaseDataProducer.java
index 0da44ac36a6e..cbaa27712015 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/util/BaseDataProducer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/util/BaseDataProducer.java
@@ -16,6 +16,7 @@
package androidx.window.util;
+import androidx.annotation.GuardedBy;
import androidx.annotation.NonNull;
import java.util.LinkedHashSet;
@@ -25,25 +26,45 @@ import java.util.function.Consumer;
/**
* Base class that provides the implementation for the callback mechanism of the
- * {@link DataProducer} API.
+ * {@link DataProducer} API. This class is thread safe for adding, removing, and notifying
+ * consumers.
*
* @param <T> The type of data this producer returns through {@link DataProducer#getData}.
*/
public abstract class BaseDataProducer<T> implements DataProducer<T> {
+
+ private final Object mLock = new Object();
+ @GuardedBy("mLock")
private final Set<Consumer<T>> mCallbacks = new LinkedHashSet<>();
+ /**
+ * Adds a callback to the set of callbacks listening for data. Data is delivered through
+ * {@link BaseDataProducer#notifyDataChanged(Object)}. This method is thread safe. Callers
+ * should ensure that callbacks are thread safe.
+ * @param callback that will receive data from the producer.
+ */
@Override
public final void addDataChangedCallback(@NonNull Consumer<T> callback) {
- mCallbacks.add(callback);
- Optional<T> currentData = getCurrentData();
- currentData.ifPresent(callback);
- onListenersChanged(mCallbacks);
+ synchronized (mLock) {
+ mCallbacks.add(callback);
+ Optional<T> currentData = getCurrentData();
+ currentData.ifPresent(callback);
+ onListenersChanged(mCallbacks);
+ }
}
+ /**
+ * Removes a callback to the set of callbacks listening for data. This method is thread safe
+ * for adding.
+ * @param callback that was registered in
+ * {@link BaseDataProducer#addDataChangedCallback(Consumer)}.
+ */
@Override
public final void removeDataChangedCallback(@NonNull Consumer<T> callback) {
- mCallbacks.remove(callback);
- onListenersChanged(mCallbacks);
+ synchronized (mLock) {
+ mCallbacks.remove(callback);
+ onListenersChanged(mCallbacks);
+ }
}
protected void onListenersChanged(Set<Consumer<T>> callbacks) {}
@@ -56,11 +77,14 @@ public abstract class BaseDataProducer<T> implements DataProducer<T> {
/**
* Called to notify all registered consumers that the data provided
- * by {@link DataProducer#getData} has changed.
+ * by {@link DataProducer#getData} has changed. Calls to this are thread save but callbacks need
+ * to ensure thread safety.
*/
protected void notifyDataChanged(T value) {
- for (Consumer<T> callback : mCallbacks) {
- callback.accept(value);
+ synchronized (mLock) {
+ for (Consumer<T> callback : mCallbacks) {
+ callback.accept(value);
+ }
}
}
} \ No newline at end of file
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/util/ExtensionHelper.java b/libs/WindowManager/Jetpack/src/androidx/window/util/ExtensionHelper.java
index 2a593f15a9de..31bf96313a95 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/util/ExtensionHelper.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/util/ExtensionHelper.java
@@ -21,14 +21,15 @@ import static android.view.Surface.ROTATION_180;
import static android.view.Surface.ROTATION_270;
import static android.view.Surface.ROTATION_90;
-import android.app.Activity;
+import android.content.Context;
import android.graphics.Rect;
import android.hardware.display.DisplayManagerGlobal;
import android.view.DisplayInfo;
import android.view.Surface;
+import android.view.WindowManager;
import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
+import androidx.annotation.UiContext;
/**
* Util class for both Sidecar and Extensions.
@@ -86,12 +87,9 @@ public final class ExtensionHelper {
}
/** Transforms rectangle from absolute coordinate space to the window coordinate space. */
- public static void transformToWindowSpaceRect(Activity activity, Rect inOutRect) {
- Rect windowRect = getWindowBounds(activity);
- if (windowRect == null) {
- inOutRect.setEmpty();
- return;
- }
+ public static void transformToWindowSpaceRect(@NonNull @UiContext Context context,
+ Rect inOutRect) {
+ Rect windowRect = getWindowBounds(context);
if (!Rect.intersects(inOutRect, windowRect)) {
inOutRect.setEmpty();
return;
@@ -103,9 +101,9 @@ public final class ExtensionHelper {
/**
* Gets the current window bounds in absolute coordinates.
*/
- @Nullable
- private static Rect getWindowBounds(@NonNull Activity activity) {
- return activity.getWindowManager().getCurrentWindowMetrics().getBounds();
+ @NonNull
+ private static Rect getWindowBounds(@NonNull @UiContext Context context) {
+ return context.getSystemService(WindowManager.class).getCurrentWindowMetrics().getBounds();
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
index d3e46f82efe5..33ecdd88fad3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
@@ -16,6 +16,9 @@
package com.android.wm.shell.back;
+import static android.view.RemoteAnimationTarget.MODE_CLOSING;
+import static android.view.RemoteAnimationTarget.MODE_OPENING;
+
import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BACK_PREVIEW;
@@ -27,8 +30,6 @@ import android.app.WindowConfiguration;
import android.content.ContentResolver;
import android.content.Context;
import android.database.ContentObserver;
-import android.graphics.Point;
-import android.graphics.PointF;
import android.hardware.HardwareBuffer;
import android.hardware.input.InputManager;
import android.net.Uri;
@@ -47,8 +48,11 @@ import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.RemoteAnimationTarget;
import android.view.SurfaceControl;
+import android.window.BackAnimationAdaptor;
import android.window.BackEvent;
import android.window.BackNavigationInfo;
+import android.window.IBackAnimationRunner;
+import android.window.IBackNaviAnimationController;
import android.window.IOnBackInvokedCallback;
import com.android.internal.annotations.VisibleForTesting;
@@ -76,22 +80,15 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
private static final int PROGRESS_THRESHOLD = SystemProperties
.getInt(PREDICTIVE_BACK_PROGRESS_THRESHOLD_PROP, -1);
private final AtomicBoolean mEnableAnimations = new AtomicBoolean(false);
+ // TODO (b/241808055) Find a appropriate time to remove during refactor
+ private static final boolean USE_TRANSITION =
+ SystemProperties.getInt("persist.wm.debug.predictive_back_ani_trans", 1) != 0;
/**
* Max duration to wait for a transition to finish before accepting another gesture start
* request.
*/
private static final long MAX_TRANSITION_DURATION = 2000;
- /**
- * Location of the initial touch event of the back gesture.
- */
- private final PointF mInitTouchLocation = new PointF();
-
- /**
- * Raw delta between {@link #mInitTouchLocation} and the last touch location.
- */
- private final Point mTouchEventDelta = new Point();
-
/** True when a back gesture is ongoing */
private boolean mBackGestureStarted = false;
@@ -119,6 +116,15 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
mTransitionInProgress = false;
};
+ private RemoteAnimationTarget mAnimationTarget;
+ IBackAnimationRunner mIBackAnimationRunner;
+ private IBackNaviAnimationController mBackAnimationController;
+ private BackAnimationAdaptor mBackAnimationAdaptor;
+
+ private boolean mWaitingAnimationStart;
+ private final TouchTracker mTouchTracker = new TouchTracker();
+ private final CachingBackDispatcher mCachingBackDispatcher = new CachingBackDispatcher();
+
@VisibleForTesting
final IWindowFocusObserver mFocusObserver = new IWindowFocusObserver.Stub() {
@Override
@@ -137,6 +143,92 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
}
};
+ /**
+ * Helper class to record the touch location for gesture start and latest.
+ */
+ private static class TouchTracker {
+ /**
+ * Location of the latest touch event
+ */
+ private float mLatestTouchX;
+ private float mLatestTouchY;
+ private int mSwipeEdge;
+
+ /**
+ * Location of the initial touch event of the back gesture.
+ */
+ private float mInitTouchX;
+ private float mInitTouchY;
+
+ void update(float touchX, float touchY, int swipeEdge) {
+ mLatestTouchX = touchX;
+ mLatestTouchY = touchY;
+ mSwipeEdge = swipeEdge;
+ }
+
+ void setGestureStartLocation(float touchX, float touchY) {
+ mInitTouchX = touchX;
+ mInitTouchY = touchY;
+ }
+
+ int getDeltaFromGestureStart(float touchX) {
+ return Math.round(touchX - mInitTouchX);
+ }
+
+ void reset() {
+ mInitTouchX = 0;
+ mInitTouchY = 0;
+ }
+ }
+
+ /**
+ * Cache the temporary callback and trigger result if gesture was finish before received
+ * BackAnimationRunner#onAnimationStart/cancel, so there can continue play the animation.
+ */
+ private class CachingBackDispatcher {
+ private IOnBackInvokedCallback mOnBackCallback;
+ private boolean mTriggerBack;
+ // Whether we are waiting to receive onAnimationStart
+ private boolean mWaitingAnimation;
+
+ void startWaitingAnimation() {
+ mWaitingAnimation = true;
+ }
+
+ boolean set(IOnBackInvokedCallback callback, boolean triggerBack) {
+ if (mWaitingAnimation) {
+ mOnBackCallback = callback;
+ mTriggerBack = triggerBack;
+ return true;
+ }
+ return false;
+ }
+
+ boolean consume() {
+ boolean consumed = false;
+ if (mWaitingAnimation && mOnBackCallback != null) {
+ if (mTriggerBack) {
+ final BackEvent backFinish = new BackEvent(
+ mTouchTracker.mLatestTouchX, mTouchTracker.mLatestTouchY, 1,
+ mTouchTracker.mSwipeEdge, mAnimationTarget);
+ dispatchOnBackProgressed(mBackToLauncherCallback, backFinish);
+ dispatchOnBackInvoked(mOnBackCallback);
+ } else {
+ final BackEvent backFinish = new BackEvent(
+ mTouchTracker.mLatestTouchX, mTouchTracker.mLatestTouchY, 0,
+ mTouchTracker.mSwipeEdge, mAnimationTarget);
+ dispatchOnBackProgressed(mBackToLauncherCallback, backFinish);
+ dispatchOnBackCancelled(mOnBackCallback);
+ }
+ startTransition();
+ consumed = true;
+ }
+ mOnBackCallback = null;
+ mWaitingAnimation = false;
+ return consumed;
+ }
+ }
+
public BackAnimationController(
@NonNull ShellInit shellInit,
@NonNull @ShellMainThread ShellExecutor shellExecutor,
@@ -272,6 +364,9 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
@VisibleForTesting
void setBackToLauncherCallback(IOnBackInvokedCallback callback) {
mBackToLauncherCallback = callback;
+ if (USE_TRANSITION) {
+ createAdaptor();
+ }
}
private void clearBackToLauncherCallback() {
@@ -280,15 +375,18 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
@VisibleForTesting
void onBackToLauncherAnimationFinished() {
- if (mBackNavigationInfo != null) {
- IOnBackInvokedCallback callback = mBackNavigationInfo.getOnBackInvokedCallback();
- if (mTriggerBack) {
+ final boolean triggerBack = mTriggerBack;
+ IOnBackInvokedCallback callback = mBackNavigationInfo != null
+ ? mBackNavigationInfo.getOnBackInvokedCallback() : null;
+ // Make sure the notification sequence should be controller > client.
+ finishAnimation();
+ if (callback != null) {
+ if (triggerBack) {
dispatchOnBackInvoked(callback);
} else {
dispatchOnBackCancelled(callback);
}
}
- finishAnimation();
}
/**
@@ -300,6 +398,8 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
if (mTransitionInProgress) {
return;
}
+
+ mTouchTracker.update(touchX, touchY, swipeEdge);
if (keyAction == MotionEvent.ACTION_DOWN) {
if (!mBackGestureStarted) {
mShouldStartOnNextMoveEvent = true;
@@ -330,13 +430,13 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
finishAnimation();
}
- mInitTouchLocation.set(touchX, touchY);
+ mTouchTracker.setGestureStartLocation(touchX, touchY);
mBackGestureStarted = true;
try {
boolean requestAnimation = mEnableAnimations.get();
- mBackNavigationInfo =
- mActivityTaskManager.startBackNavigation(requestAnimation, mFocusObserver);
+ mBackNavigationInfo = mActivityTaskManager.startBackNavigation(requestAnimation,
+ mFocusObserver, mBackAnimationAdaptor);
onBackNavigationInfoReceived(mBackNavigationInfo);
} catch (RemoteException remoteException) {
Log.e(TAG, "Failed to initAnimation", remoteException);
@@ -352,6 +452,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
}
int backType = backNavigationInfo.getType();
IOnBackInvokedCallback targetCallback = null;
+ final boolean dispatchToLauncher = shouldDispatchToLauncher(backType);
if (backType == BackNavigationInfo.TYPE_CROSS_ACTIVITY) {
HardwareBuffer hardwareBuffer = backNavigationInfo.getScreenshotHardwareBuffer();
if (hardwareBuffer != null) {
@@ -359,12 +460,17 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
backNavigationInfo.getTaskWindowConfiguration());
}
mTransaction.apply();
- } else if (shouldDispatchToLauncher(backType)) {
+ } else if (dispatchToLauncher) {
targetCallback = mBackToLauncherCallback;
+ if (USE_TRANSITION) {
+ mCachingBackDispatcher.startWaitingAnimation();
+ }
} else if (backType == BackNavigationInfo.TYPE_CALLBACK) {
targetCallback = mBackNavigationInfo.getOnBackInvokedCallback();
}
- dispatchOnBackStarted(targetCallback);
+ if (!USE_TRANSITION || !dispatchToLauncher) {
+ dispatchOnBackStarted(targetCallback);
+ }
}
/**
@@ -403,24 +509,33 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
if (!mBackGestureStarted || mBackNavigationInfo == null) {
return;
}
- int deltaX = Math.round(touchX - mInitTouchLocation.x);
+ int deltaX = mTouchTracker.getDeltaFromGestureStart(touchX);
float progressThreshold = PROGRESS_THRESHOLD >= 0 ? PROGRESS_THRESHOLD : mProgressThreshold;
float progress = Math.min(Math.max(Math.abs(deltaX) / progressThreshold, 0), 1);
- int backType = mBackNavigationInfo.getType();
- RemoteAnimationTarget animationTarget = mBackNavigationInfo.getDepartingAnimationTarget();
-
- BackEvent backEvent = new BackEvent(
- touchX, touchY, progress, swipeEdge, animationTarget);
- IOnBackInvokedCallback targetCallback = null;
- if (shouldDispatchToLauncher(backType)) {
- targetCallback = mBackToLauncherCallback;
- } else if (backType == BackNavigationInfo.TYPE_CROSS_TASK
- || backType == BackNavigationInfo.TYPE_CROSS_ACTIVITY) {
- // TODO(208427216) Run the actual animation
- } else if (backType == BackNavigationInfo.TYPE_CALLBACK) {
- targetCallback = mBackNavigationInfo.getOnBackInvokedCallback();
+ if (USE_TRANSITION) {
+ if (mBackAnimationController != null && mAnimationTarget != null) {
+ final BackEvent backEvent = new BackEvent(
+ touchX, touchY, progress, swipeEdge, mAnimationTarget);
+ dispatchOnBackProgressed(mBackToLauncherCallback, backEvent);
+ }
+ } else {
+ int backType = mBackNavigationInfo.getType();
+ RemoteAnimationTarget animationTarget =
+ mBackNavigationInfo.getDepartingAnimationTarget();
+
+ BackEvent backEvent = new BackEvent(
+ touchX, touchY, progress, swipeEdge, animationTarget);
+ IOnBackInvokedCallback targetCallback = null;
+ if (shouldDispatchToLauncher(backType)) {
+ targetCallback = mBackToLauncherCallback;
+ } else if (backType == BackNavigationInfo.TYPE_CROSS_TASK
+ || backType == BackNavigationInfo.TYPE_CROSS_ACTIVITY) {
+ // TODO(208427216) Run the actual animation
+ } else if (backType == BackNavigationInfo.TYPE_CALLBACK) {
+ targetCallback = mBackNavigationInfo.getOnBackInvokedCallback();
+ }
+ dispatchOnBackProgressed(targetCallback, backEvent);
}
- dispatchOnBackProgressed(targetCallback, backEvent);
}
private void injectBackKey() {
@@ -474,6 +589,9 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
IOnBackInvokedCallback targetCallback = shouldDispatchToLauncher
? mBackToLauncherCallback
: mBackNavigationInfo.getOnBackInvokedCallback();
+ if (mCachingBackDispatcher.set(targetCallback, mTriggerBack)) {
+ return;
+ }
if (shouldDispatchToLauncher) {
startTransition();
}
@@ -493,7 +611,8 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
&& mBackToLauncherCallback != null
&& mEnableAnimations.get()
&& mBackNavigationInfo != null
- && mBackNavigationInfo.getDepartingAnimationTarget() != null;
+ && ((USE_TRANSITION && mBackNavigationInfo.isPrepareRemoteAnimation())
+ || mBackNavigationInfo.getDepartingAnimationTarget() != null);
}
private static void dispatchOnBackStarted(IOnBackInvokedCallback callback) {
@@ -558,8 +677,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
private void finishAnimation() {
ProtoLog.d(WM_SHELL_BACK_PREVIEW, "BackAnimationController: finishAnimation()");
- mTouchEventDelta.set(0, 0);
- mInitTouchLocation.set(0, 0);
+ mTouchTracker.reset();
BackNavigationInfo backNavigationInfo = mBackNavigationInfo;
boolean triggerBack = mTriggerBack;
mBackNavigationInfo = null;
@@ -569,19 +687,33 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
return;
}
- RemoteAnimationTarget animationTarget = backNavigationInfo.getDepartingAnimationTarget();
- if (animationTarget != null) {
- if (animationTarget.leash != null && animationTarget.leash.isValid()) {
- mTransaction.remove(animationTarget.leash);
+ if (!USE_TRANSITION) {
+ RemoteAnimationTarget animationTarget = backNavigationInfo
+ .getDepartingAnimationTarget();
+ if (animationTarget != null) {
+ if (animationTarget.leash != null && animationTarget.leash.isValid()) {
+ mTransaction.remove(animationTarget.leash);
+ }
}
+ SurfaceControl screenshotSurface = backNavigationInfo.getScreenshotSurface();
+ if (screenshotSurface != null && screenshotSurface.isValid()) {
+ mTransaction.remove(screenshotSurface);
+ }
+ mTransaction.apply();
}
- SurfaceControl screenshotSurface = backNavigationInfo.getScreenshotSurface();
- if (screenshotSurface != null && screenshotSurface.isValid()) {
- mTransaction.remove(screenshotSurface);
- }
- mTransaction.apply();
stopTransition();
backNavigationInfo.onBackNavigationFinished(triggerBack);
+ if (USE_TRANSITION) {
+ final IBackNaviAnimationController controller = mBackAnimationController;
+ if (controller != null) {
+ try {
+ controller.finish(triggerBack);
+ } catch (RemoteException r) {
+ // Oh no!
+ }
+ }
+ mBackAnimationController = null;
+ }
}
private void startTransition() {
@@ -599,4 +731,50 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
mShellExecutor.removeCallbacks(mResetTransitionRunnable);
mTransitionInProgress = false;
}
+
+ private void createAdaptor() {
+ mIBackAnimationRunner = new IBackAnimationRunner.Stub() {
+ @Override
+ public void onAnimationCancelled() {
+ // no op for now
+ }
+ @Override // Binder interface
+ public void onAnimationStart(IBackNaviAnimationController controller, int type,
+ RemoteAnimationTarget[] apps, RemoteAnimationTarget[] wallpapers,
+ RemoteAnimationTarget[] nonApps) {
+ mShellExecutor.execute(() -> {
+ mBackAnimationController = controller;
+ for (int i = 0; i < apps.length; i++) {
+ final RemoteAnimationTarget target = apps[i];
+ if (MODE_CLOSING == target.mode) {
+ mAnimationTarget = target;
+ } else if (MODE_OPENING == target.mode) {
+ // TODO Home activity should handle the visibility for itself
+ // once it finish relayout for orientation change
+ SurfaceControl.Transaction tx =
+ new SurfaceControl.Transaction();
+ tx.setAlpha(target.leash, 1);
+ tx.apply();
+ }
+ }
+ // TODO animation target should be passed at onBackStarted
+ dispatchOnBackStarted(mBackToLauncherCallback);
+ // TODO This is Workaround for LauncherBackAnimationController, there will need
+ // to dispatch onBackProgressed twice(startBack & updateBackProgress) to
+ // initialize the animation data, for now that would happen when onMove
+ // called, but there will no expected animation if the down -> up gesture
+ // happen very fast which ACTION_MOVE only happen once.
+ final BackEvent backInit = new BackEvent(
+ mTouchTracker.mLatestTouchX, mTouchTracker.mLatestTouchY, 0,
+ mTouchTracker.mSwipeEdge, mAnimationTarget);
+ dispatchOnBackProgressed(mBackToLauncherCallback, backInit);
+ if (!mCachingBackDispatcher.consume()) {
+ dispatchOnBackProgressed(mBackToLauncherCallback, backInit);
+ }
+ });
+ }
+ };
+ mBackAnimationAdaptor = new BackAnimationAdaptor(mIBackAnimationRunner,
+ BackNavigationInfo.TYPE_RETURN_TO_HOME);
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
index 83ba909e712d..b7959eb629c1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
@@ -80,10 +80,7 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
public static final int PARALLAX_DISMISSING = 1;
public static final int PARALLAX_ALIGN_CENTER = 2;
- private static final int FLING_RESIZE_DURATION = 250;
- private static final int FLING_SWITCH_DURATION = 350;
- private static final int FLING_ENTER_DURATION = 350;
- private static final int FLING_EXIT_DURATION = 350;
+ private static final int FLING_ANIMATION_DURATION = 250;
private final int mDividerWindowWidth;
private final int mDividerInsets;
@@ -96,9 +93,6 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
private final Rect mBounds1 = new Rect();
// Bounds2 final position should be always at bottom or right
private final Rect mBounds2 = new Rect();
- // The temp bounds outside of display bounds for side stage when split screen inactive to avoid
- // flicker next time active split screen.
- private final Rect mInvisibleBounds = new Rect();
private final Rect mWinBounds1 = new Rect();
private final Rect mWinBounds2 = new Rect();
private final SplitLayoutHandler mSplitLayoutHandler;
@@ -147,10 +141,6 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
resetDividerPosition();
mDimNonImeSide = resources.getBoolean(R.bool.config_dimNonImeAttachedSide);
-
- mInvisibleBounds.set(mRootBounds);
- mInvisibleBounds.offset(isLandscape() ? mRootBounds.right : 0,
- isLandscape() ? 0 : mRootBounds.bottom);
}
private int getDividerInsets(Resources resources, Display display) {
@@ -249,12 +239,6 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
rect.offset(-mRootBounds.left, -mRootBounds.top);
}
- /** Gets bounds size equal to root bounds but outside of screen, used for position side stage
- * when split inactive to avoid flicker when next time active. */
- public void getInvisibleBounds(Rect rect) {
- rect.set(mInvisibleBounds);
- }
-
/** Returns leash of the current divider bar. */
@Nullable
public SurfaceControl getDividerLeash() {
@@ -300,10 +284,6 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
mDividerSnapAlgorithm = getSnapAlgorithm(mContext, mRootBounds, null);
initDividerPosition(mTempRect);
- mInvisibleBounds.set(mRootBounds);
- mInvisibleBounds.offset(isLandscape() ? mRootBounds.right : 0,
- isLandscape() ? 0 : mRootBounds.bottom);
-
return true;
}
@@ -425,13 +405,6 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
mFreezeDividerWindow = freezeDividerWindow;
}
- /** Update current layout as divider put on start or end position. */
- public void setDividerAtBorder(boolean start) {
- final int pos = start ? mDividerSnapAlgorithm.getDismissStartTarget().position
- : mDividerSnapAlgorithm.getDismissEndTarget().position;
- setDividePosition(pos, false /* applyLayoutChange */);
- }
-
/**
* Updates bounds with the passing position. Usually used to update recording bounds while
* performing animation or dragging divider bar to resize the splits.
@@ -476,17 +449,17 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
public void snapToTarget(int currentPosition, DividerSnapAlgorithm.SnapTarget snapTarget) {
switch (snapTarget.flag) {
case FLAG_DISMISS_START:
- flingDividePosition(currentPosition, snapTarget.position, FLING_RESIZE_DURATION,
+ flingDividePosition(currentPosition, snapTarget.position,
() -> mSplitLayoutHandler.onSnappedToDismiss(false /* bottomOrRight */,
EXIT_REASON_DRAG_DIVIDER));
break;
case FLAG_DISMISS_END:
- flingDividePosition(currentPosition, snapTarget.position, FLING_RESIZE_DURATION,
+ flingDividePosition(currentPosition, snapTarget.position,
() -> mSplitLayoutHandler.onSnappedToDismiss(true /* bottomOrRight */,
EXIT_REASON_DRAG_DIVIDER));
break;
default:
- flingDividePosition(currentPosition, snapTarget.position, FLING_RESIZE_DURATION,
+ flingDividePosition(currentPosition, snapTarget.position,
() -> setDividePosition(snapTarget.position, true /* applyLayoutChange */));
break;
}
@@ -520,11 +493,9 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
final Rect insets = stableInsets != null ? stableInsets : getDisplayInsets(context);
// Make split axis insets value same as the larger one to avoid bounds1 and bounds2
- // have difference after split switching for solving issues on non-resizable app case.
- if (isLandscape) {
- final int largerInsets = Math.max(insets.left, insets.right);
- insets.set(largerInsets, insets.top, largerInsets, insets.bottom);
- } else {
+ // have difference for avoiding size-compat mode when switching unresizable apps in
+ // landscape while they are letterboxed.
+ if (!isLandscape) {
final int largerInsets = Math.max(insets.top, insets.bottom);
insets.set(insets.left, largerInsets, insets.right, largerInsets);
}
@@ -543,20 +514,12 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
public void flingDividerToDismiss(boolean toEnd, int reason) {
final int target = toEnd ? mDividerSnapAlgorithm.getDismissEndTarget().position
: mDividerSnapAlgorithm.getDismissStartTarget().position;
- flingDividePosition(getDividePosition(), target, FLING_EXIT_DURATION,
+ flingDividePosition(getDividePosition(), target,
() -> mSplitLayoutHandler.onSnappedToDismiss(toEnd, reason));
}
- /** Fling divider from current position to center position. */
- public void flingDividerToCenter() {
- final int pos = mDividerSnapAlgorithm.getMiddleTarget().position;
- flingDividePosition(getDividePosition(), pos, FLING_ENTER_DURATION,
- () -> setDividePosition(pos, true /* applyLayoutChange */));
- }
-
@VisibleForTesting
- void flingDividePosition(int from, int to, int duration,
- @Nullable Runnable flingFinishedCallback) {
+ void flingDividePosition(int from, int to, @Nullable Runnable flingFinishedCallback) {
if (from == to) {
// No animation run, still callback to stop resizing.
mSplitLayoutHandler.onLayoutSizeChanged(this);
@@ -566,7 +529,7 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
}
ValueAnimator animator = ValueAnimator
.ofInt(from, to)
- .setDuration(duration);
+ .setDuration(FLING_ANIMATION_DURATION);
animator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
animator.addUpdateListener(
animation -> updateDivideBounds((int) animation.getAnimatedValue()));
@@ -623,7 +586,7 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
AnimatorSet set = new AnimatorSet();
set.playTogether(animator1, animator2, animator3);
- set.setDuration(FLING_SWITCH_DURATION);
+ set.setDuration(FLING_ANIMATION_DURATION);
set.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizer.java
index 2fdd12185551..e91987dab972 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizer.java
@@ -316,11 +316,13 @@ public class KidsModeTaskOrganizer extends ShellTaskOrganizer {
true /* onTop */);
wct.reorder(rootToken, mEnabled /* onTop */);
mSyncQueue.queue(wct);
- final SurfaceControl rootLeash = mLaunchRootLeash;
- mSyncQueue.runInSync(t -> {
- t.setPosition(rootLeash, taskBounds.left, taskBounds.top);
- t.setWindowCrop(rootLeash, taskBounds.width(), taskBounds.height());
- });
+ if (mEnabled) {
+ final SurfaceControl rootLeash = mLaunchRootLeash;
+ mSyncQueue.runInSync(t -> {
+ t.setPosition(rootLeash, taskBounds.left, taskBounds.top);
+ t.setWindowCrop(rootLeash, taskBounds.width(), taskBounds.height());
+ });
+ }
}
private Rect calculateBounds() {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index 21fc01e554c8..7e83d2fa0a0b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -25,7 +25,6 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
-import static android.content.res.Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.RemoteAnimationTarget.MODE_OPENING;
import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
@@ -489,6 +488,13 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
final WindowContainerTransaction wct = new WindowContainerTransaction();
options = resolveStartStage(STAGE_TYPE_UNDEFINED, position, options, wct);
+ // If split still not active, apply windows bounds first to avoid surface reset to
+ // wrong pos by SurfaceAnimator from wms.
+ // TODO(b/223325631): check is it still necessary after improve enter transition done.
+ if (!mMainStage.isActive()) {
+ updateWindowBounds(mSplitLayout, wct);
+ }
+
wct.sendPendingIntent(intent, fillInIntent, options);
mSyncQueue.queue(transition, WindowManager.TRANSIT_OPEN, wct);
}
@@ -635,7 +641,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
wct.startTask(sideTaskId, sideOptions);
}
// Using legacy transitions, so we can't use blast sync since it conflicts.
- mSyncQueue.queue(wct);
+ mTaskOrganizer.applyTransaction(wct);
mSyncQueue.runInSync(t -> {
setDividerVisibility(true, t);
updateSurfaceBounds(mSplitLayout, t, false /* applyResizingOffset */);
@@ -887,13 +893,10 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mShouldUpdateRecents = false;
mIsDividerRemoteAnimating = false;
- mSplitLayout.getInvisibleBounds(mTempRect1);
if (childrenToTop == null) {
mSideStage.removeAllTasks(wct, false /* toTop */);
mMainStage.deactivate(wct, false /* toTop */);
wct.reorder(mRootTaskInfo.token, false /* onTop */);
- wct.setForceTranslucent(mRootTaskInfo.token, true);
- wct.setBounds(mSideStage.mRootTaskInfo.token, mTempRect1);
onTransitionAnimationComplete();
} else {
// Expand to top side split as full screen for fading out decor animation and dismiss
@@ -904,32 +907,27 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
? mSideStage : mMainStage;
tempFullStage.resetBounds(wct);
wct.setSmallestScreenWidthDp(tempFullStage.mRootTaskInfo.token,
- SMALLEST_SCREEN_WIDTH_DP_UNDEFINED);
+ mRootTaskInfo.configuration.smallestScreenWidthDp);
dismissStage.dismiss(wct, false /* toTop */);
}
mSyncQueue.queue(wct);
mSyncQueue.runInSync(t -> {
t.setWindowCrop(mMainStage.mRootLeash, null)
.setWindowCrop(mSideStage.mRootLeash, null);
+ t.setPosition(mMainStage.mRootLeash, 0, 0)
+ .setPosition(mSideStage.mRootLeash, 0, 0);
t.hide(mMainStage.mDimLayer).hide(mSideStage.mDimLayer);
setDividerVisibility(false, t);
- if (childrenToTop == null) {
- t.setPosition(mSideStage.mRootLeash, mTempRect1.left, mTempRect1.right);
- } else {
- // In this case, exit still under progress, fade out the split decor after first WCT
- // done and do remaining WCT after animation finished.
+ // In this case, exit still under progress, fade out the split decor after first WCT
+ // done and do remaining WCT after animation finished.
+ if (childrenToTop != null) {
childrenToTop.fadeOutDecor(() -> {
WindowContainerTransaction finishedWCT = new WindowContainerTransaction();
mIsExiting = false;
childrenToTop.dismiss(finishedWCT, true /* toTop */);
finishedWCT.reorder(mRootTaskInfo.token, false /* toTop */);
- finishedWCT.setForceTranslucent(mRootTaskInfo.token, true);
- finishedWCT.setBounds(mSideStage.mRootTaskInfo.token, mTempRect1);
- mSyncQueue.queue(finishedWCT);
- mSyncQueue.runInSync(at -> {
- at.setPosition(mSideStage.mRootLeash, mTempRect1.left, mTempRect1.right);
- });
+ mTaskOrganizer.applyTransaction(finishedWCT);
onTransitionAnimationComplete();
});
}
@@ -998,7 +996,6 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mMainStage.activate(wct, true /* includingTopTask */);
updateWindowBounds(mSplitLayout, wct);
wct.reorder(mRootTaskInfo.token, true);
- wct.setForceTranslucent(mRootTaskInfo.token, false);
}
void finishEnterSplitScreen(SurfaceControl.Transaction t) {
@@ -1224,13 +1221,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
// Make the stages adjacent to each other so they occlude what's behind them.
wct.setAdjacentRoots(mMainStage.mRootTaskInfo.token, mSideStage.mRootTaskInfo.token);
wct.setLaunchAdjacentFlagRoot(mSideStage.mRootTaskInfo.token);
- wct.setForceTranslucent(mRootTaskInfo.token, true);
- mSplitLayout.getInvisibleBounds(mTempRect1);
- wct.setBounds(mSideStage.mRootTaskInfo.token, mTempRect1);
- mSyncQueue.queue(wct);
- mSyncQueue.runInSync(t -> {
- t.setPosition(mSideStage.mRootLeash, mTempRect1.left, mTempRect1.top);
- });
+ mTaskOrganizer.applyTransaction(wct);
}
private void onRootTaskVanished() {
@@ -1386,17 +1377,10 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
// TODO (b/238697912) : Add the validation to prevent entering non-recovered status
final WindowContainerTransaction wct = new WindowContainerTransaction();
mSplitLayout.init();
- mSplitLayout.setDividerAtBorder(mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT);
- mMainStage.activate(wct, true /* includingTopTask */);
- updateWindowBounds(mSplitLayout, wct);
- wct.reorder(mRootTaskInfo.token, true);
- wct.setForceTranslucent(mRootTaskInfo.token, false);
+ prepareEnterSplitScreen(wct);
mSyncQueue.queue(wct);
- mSyncQueue.runInSync(t -> {
- updateSurfaceBounds(mSplitLayout, t, false /* applyResizingOffset */);
-
- mSplitLayout.flingDividerToCenter();
- });
+ mSyncQueue.runInSync(t ->
+ updateSurfaceBounds(mSplitLayout, t, false /* applyResizingOffset */));
}
if (mMainStageListener.mHasChildren && mSideStageListener.mHasChildren) {
mShouldUpdateRecents = true;
@@ -1838,7 +1822,6 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
// properly for the animation itself.
mSplitLayout.release();
mSplitLayout.resetDividerPosition();
- mSideStagePosition = SPLIT_POSITION_BOTTOM_OR_RIGHT;
mTopStageAfterFoldDismiss = STAGE_TYPE_UNDEFINED;
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
index 6c659667a4a7..cff60f5e5b6c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
@@ -44,6 +44,8 @@ import static android.view.WindowManager.TRANSIT_RELAUNCH;
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.window.TransitionInfo.FLAG_CROSS_PROFILE_OWNER_THUMBNAIL;
+import static android.window.TransitionInfo.FLAG_CROSS_PROFILE_WORK_THUMBNAIL;
import static android.window.TransitionInfo.FLAG_DISPLAY_HAS_ALERT_WINDOWS;
import static android.window.TransitionInfo.FLAG_IS_DISPLAY;
import static android.window.TransitionInfo.FLAG_IS_VOICE_INTERACTION;
@@ -903,11 +905,10 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
private void attachThumbnail(@NonNull ArrayList<Animator> animations,
@NonNull Runnable finishCallback, TransitionInfo.Change change,
TransitionInfo.AnimationOptions options, float cornerRadius) {
- final boolean isTask = change.getTaskInfo() != null;
final boolean isOpen = Transitions.isOpeningType(change.getMode());
final boolean isClose = Transitions.isClosingType(change.getMode());
if (isOpen) {
- if (options.getType() == ANIM_OPEN_CROSS_PROFILE_APPS && isTask) {
+ if (options.getType() == ANIM_OPEN_CROSS_PROFILE_APPS) {
attachCrossProfileThumbnailAnimation(animations, finishCallback, change,
cornerRadius);
} else if (options.getType() == ANIM_THUMBNAIL_SCALE_UP) {
@@ -922,8 +923,13 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
@NonNull Runnable finishCallback, TransitionInfo.Change change, float cornerRadius) {
final Rect bounds = change.getEndAbsBounds();
// Show the right drawable depending on the user we're transitioning to.
- final Drawable thumbnailDrawable = change.getTaskInfo().userId == mCurrentUserId
- ? mContext.getDrawable(R.drawable.ic_account_circle) : mEnterpriseThumbnailDrawable;
+ final Drawable thumbnailDrawable = change.hasFlags(FLAG_CROSS_PROFILE_OWNER_THUMBNAIL)
+ ? mContext.getDrawable(R.drawable.ic_account_circle)
+ : change.hasFlags(FLAG_CROSS_PROFILE_WORK_THUMBNAIL)
+ ? mEnterpriseThumbnailDrawable : null;
+ if (thumbnailDrawable == null) {
+ return;
+ }
final HardwareBuffer thumbnail = mTransitionAnimation.createCrossProfileAppsThumbnail(
thumbnailDrawable, bounds);
if (thumbnail == null) {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestShellExecutor.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestShellExecutor.java
index da95c77d2b89..fe8b305093d7 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestShellExecutor.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestShellExecutor.java
@@ -48,9 +48,10 @@ public class TestShellExecutor implements ShellExecutor {
}
public void flushAll() {
- for (Runnable r : mRunnables) {
+ final ArrayList<Runnable> tmpRunnable = new ArrayList<>(mRunnables);
+ mRunnables.clear();
+ for (Runnable r : tmpRunnable) {
r.run();
}
- mRunnables.clear();
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
index 5b3b8fd7ad71..90a377309edd 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
@@ -54,6 +54,7 @@ import android.view.RemoteAnimationTarget;
import android.view.SurfaceControl;
import android.window.BackEvent;
import android.window.BackNavigationInfo;
+import android.window.IBackNaviAnimationController;
import android.window.IOnBackInvokedCallback;
import androidx.test.filters.SmallTest;
@@ -98,6 +99,9 @@ public class BackAnimationControllerTest extends ShellTestCase {
@Mock
private IOnBackInvokedCallback mIOnBackInvokedCallback;
+ @Mock
+ private IBackNaviAnimationController mIBackNaviAnimationController;
+
private BackAnimationController mController;
private int mEventTime = 0;
@@ -127,7 +131,7 @@ public class BackAnimationControllerTest extends ShellTestCase {
SurfaceControl screenshotSurface,
HardwareBuffer hardwareBuffer,
int backType,
- IOnBackInvokedCallback onBackInvokedCallback) {
+ IOnBackInvokedCallback onBackInvokedCallback, boolean prepareAnimation) {
BackNavigationInfo.Builder builder = new BackNavigationInfo.Builder()
.setType(backType)
.setDepartingAnimationTarget(topAnimationTarget)
@@ -135,7 +139,8 @@ public class BackAnimationControllerTest extends ShellTestCase {
.setScreenshotBuffer(hardwareBuffer)
.setTaskWindowConfiguration(new WindowConfiguration())
.setOnBackNavigationDone(new RemoteCallback((bundle) -> {}))
- .setOnBackInvokedCallback(onBackInvokedCallback);
+ .setOnBackInvokedCallback(onBackInvokedCallback)
+ .setPrepareAnimation(prepareAnimation);
createNavigationInfo(builder);
}
@@ -143,7 +148,7 @@ public class BackAnimationControllerTest extends ShellTestCase {
private void createNavigationInfo(BackNavigationInfo.Builder builder) {
try {
doReturn(builder.build()).when(mActivityTaskManager)
- .startBackNavigation(anyBoolean(), any());
+ .startBackNavigation(anyBoolean(), any(), any());
} catch (RemoteException ex) {
ex.rethrowFromSystemServer();
}
@@ -175,7 +180,7 @@ public class BackAnimationControllerTest extends ShellTestCase {
SurfaceControl screenshotSurface = new SurfaceControl();
HardwareBuffer hardwareBuffer = mock(HardwareBuffer.class);
createNavigationInfo(createAnimationTarget(), screenshotSurface, hardwareBuffer,
- BackNavigationInfo.TYPE_CROSS_ACTIVITY, null);
+ BackNavigationInfo.TYPE_CROSS_ACTIVITY, null, true);
doMotionEvent(MotionEvent.ACTION_DOWN, 0);
verify(mTransaction).setBuffer(screenshotSurface, hardwareBuffer);
verify(mTransaction).setVisibility(screenshotSurface, true);
@@ -188,7 +193,7 @@ public class BackAnimationControllerTest extends ShellTestCase {
HardwareBuffer hardwareBuffer = mock(HardwareBuffer.class);
RemoteAnimationTarget animationTarget = createAnimationTarget();
createNavigationInfo(animationTarget, screenshotSurface, hardwareBuffer,
- BackNavigationInfo.TYPE_CROSS_ACTIVITY, null);
+ BackNavigationInfo.TYPE_CROSS_ACTIVITY, null, true);
doMotionEvent(MotionEvent.ACTION_DOWN, 0);
doMotionEvent(MotionEvent.ACTION_MOVE, 100);
// b/207481538, we check that the surface is not moved for now, we can re-enable this once
@@ -222,15 +227,16 @@ public class BackAnimationControllerTest extends ShellTestCase {
mController.setBackToLauncherCallback(mIOnBackInvokedCallback);
RemoteAnimationTarget animationTarget = createAnimationTarget();
createNavigationInfo(animationTarget, null, null,
- BackNavigationInfo.TYPE_RETURN_TO_HOME, null);
+ BackNavigationInfo.TYPE_RETURN_TO_HOME, null, true);
doMotionEvent(MotionEvent.ACTION_DOWN, 0);
// Check that back start and progress is dispatched when first move.
doMotionEvent(MotionEvent.ACTION_MOVE, 100);
+ simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME, animationTarget);
verify(mIOnBackInvokedCallback).onBackStarted();
ArgumentCaptor<BackEvent> backEventCaptor = ArgumentCaptor.forClass(BackEvent.class);
- verify(mIOnBackInvokedCallback).onBackProgressed(backEventCaptor.capture());
+ verify(mIOnBackInvokedCallback, atLeastOnce()).onBackProgressed(backEventCaptor.capture());
assertEquals(animationTarget, backEventCaptor.getValue().getDepartingAnimationTarget());
// Check that back invocation is dispatched.
@@ -255,7 +261,7 @@ public class BackAnimationControllerTest extends ShellTestCase {
IOnBackInvokedCallback appCallback = mock(IOnBackInvokedCallback.class);
ArgumentCaptor<BackEvent> backEventCaptor = ArgumentCaptor.forClass(BackEvent.class);
createNavigationInfo(animationTarget, null, null,
- BackNavigationInfo.TYPE_RETURN_TO_HOME, appCallback);
+ BackNavigationInfo.TYPE_RETURN_TO_HOME, appCallback, false);
triggerBackGesture();
@@ -273,9 +279,10 @@ public class BackAnimationControllerTest extends ShellTestCase {
mController.setBackToLauncherCallback(mIOnBackInvokedCallback);
RemoteAnimationTarget animationTarget = createAnimationTarget();
createNavigationInfo(animationTarget, null, null,
- BackNavigationInfo.TYPE_RETURN_TO_HOME, null);
+ BackNavigationInfo.TYPE_RETURN_TO_HOME, null, true);
triggerBackGesture();
+ simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME, animationTarget);
// Check that back invocation is dispatched.
verify(mIOnBackInvokedCallback).onBackInvoked();
@@ -294,6 +301,7 @@ public class BackAnimationControllerTest extends ShellTestCase {
// Verify that we start accepting gestures again once transition finishes.
doMotionEvent(MotionEvent.ACTION_DOWN, 0);
doMotionEvent(MotionEvent.ACTION_MOVE, 100);
+ simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME, animationTarget);
verify(mIOnBackInvokedCallback).onBackStarted();
}
@@ -302,15 +310,17 @@ public class BackAnimationControllerTest extends ShellTestCase {
mController.setBackToLauncherCallback(mIOnBackInvokedCallback);
RemoteAnimationTarget animationTarget = createAnimationTarget();
createNavigationInfo(animationTarget, null, null,
- BackNavigationInfo.TYPE_RETURN_TO_HOME, null);
+ BackNavigationInfo.TYPE_RETURN_TO_HOME, null, true);
triggerBackGesture();
+ simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME, animationTarget);
reset(mIOnBackInvokedCallback);
// Simulate transition timeout.
mShellExecutor.flushAll();
doMotionEvent(MotionEvent.ACTION_DOWN, 0);
doMotionEvent(MotionEvent.ACTION_MOVE, 100);
+ simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME, animationTarget);
verify(mIOnBackInvokedCallback).onBackStarted();
}
@@ -321,11 +331,12 @@ public class BackAnimationControllerTest extends ShellTestCase {
RemoteAnimationTarget animationTarget = createAnimationTarget();
createNavigationInfo(animationTarget, null, null,
- BackNavigationInfo.TYPE_RETURN_TO_HOME, null);
+ BackNavigationInfo.TYPE_RETURN_TO_HOME, null, true);
doMotionEvent(MotionEvent.ACTION_DOWN, 0);
// Check that back start and progress is dispatched when first move.
doMotionEvent(MotionEvent.ACTION_MOVE, 100);
+ simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME, animationTarget);
verify(mIOnBackInvokedCallback).onBackStarted();
// Check that back invocation is dispatched.
@@ -349,4 +360,14 @@ public class BackAnimationControllerTest extends ShellTestCase {
BackEvent.EDGE_LEFT);
mEventTime += 10;
}
+
+ private void simulateRemoteAnimationStart(int type, RemoteAnimationTarget animationTarget)
+ throws RemoteException {
+ if (mController.mIBackAnimationRunner != null) {
+ final RemoteAnimationTarget[] targets = new RemoteAnimationTarget[]{animationTarget};
+ mController.mIBackAnimationRunner.onAnimationStart(mIBackNaviAnimationController, type,
+ targets, null, null);
+ mShellExecutor.flushAll();
+ }
+ }
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
index 695550dd8fa5..95725bbfd855 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
@@ -159,8 +159,7 @@ public class SplitLayoutTests extends ShellTestCase {
}
private void waitDividerFlingFinished() {
- verify(mSplitLayout).flingDividePosition(anyInt(), anyInt(), anyInt(),
- mRunnableCaptor.capture());
+ verify(mSplitLayout).flingDividePosition(anyInt(), anyInt(), mRunnableCaptor.capture());
mRunnableCaptor.getValue().run();
}
diff --git a/packages/SettingsLib/MainSwitchPreference/res/layout-v33/settingslib_main_switch_bar.xml b/packages/SettingsLib/MainSwitchPreference/res/layout-v33/settingslib_main_switch_bar.xml
index 35d13230a3b7..2aa26e321a91 100644
--- a/packages/SettingsLib/MainSwitchPreference/res/layout-v33/settingslib_main_switch_bar.xml
+++ b/packages/SettingsLib/MainSwitchPreference/res/layout-v33/settingslib_main_switch_bar.xml
@@ -20,6 +20,11 @@
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:background="?android:attr/colorBackground"
+ android:minHeight="?android:attr/listPreferredItemHeight"
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+ android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+ android:paddingTop="@dimen/settingslib_switchbar_margin"
+ android:paddingBottom="@dimen/settingslib_switchbar_margin"
android:orientation="vertical">
<LinearLayout
@@ -27,7 +32,6 @@
android:minHeight="@dimen/settingslib_min_switch_bar_height"
android:layout_height="wrap_content"
android:layout_width="match_parent"
- android:layout_margin="@dimen/settingslib_switchbar_margin"
android:paddingStart="@dimen/settingslib_switchbar_padding_left"
android:paddingEnd="@dimen/settingslib_switchbar_padding_right">
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/MainSwitchBarTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/MainSwitchBarTest.java
index d86bd014988e..24037caf4e6c 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/MainSwitchBarTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/MainSwitchBarTest.java
@@ -16,6 +16,8 @@
package com.android.settingslib.widget;
+import static android.graphics.text.LineBreakConfig.LINE_BREAK_WORD_STYLE_PHRASE;
+
import static com.google.common.truth.Truth.assertThat;
import android.content.Context;
@@ -97,4 +99,14 @@ public class MainSwitchBarTest {
assertThat(mBar.getVisibility()).isEqualTo(View.GONE);
}
+
+ @Test
+ public void setTitle_shouldSetCorrectLineBreakStyle() {
+ final String title = "title";
+
+ mBar.setTitle(title);
+ final TextView textView = ((TextView) mBar.findViewById(R.id.switch_text));
+
+ assertThat(textView.getLineBreakWordStyle()).isEqualTo(LINE_BREAK_WORD_STYLE_PHRASE);
+ }
}
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
index 86d2eb85479f..36fbf8f850d3 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
@@ -210,6 +210,8 @@ public class SecureSettings {
Settings.Secure.LOCKSCREEN_SHOW_WALLET,
Settings.Secure.LOCK_SCREEN_SHOW_QR_CODE_SCANNER,
Settings.Secure.LOCKSCREEN_USE_DOUBLE_LINE_CLOCK,
- Settings.Secure.STATUS_BAR_SHOW_VIBRATE_ICON
+ Settings.Secure.STATUS_BAR_SHOW_VIBRATE_ICON,
+ Settings.Secure.ASSIST_TOUCH_GESTURE_ENABLED,
+ Settings.Secure.ASSIST_LONG_PRESS_HOME_ENABLED
};
}
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index cd3a72271603..ff4c748f4946 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -230,6 +230,7 @@ android_library {
libs: [
"android.test.runner",
"android.test.base",
+ "android.test.mock",
],
kotlincflags: ["-Xjvm-default=enable"],
aaptflags: [
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
index 8ddd430dadbc..7d4dcf88542b 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
@@ -311,8 +311,7 @@ class ActivityLaunchAnimator(
@JvmStatic
fun fromView(view: View, cujType: Int? = null): Controller? {
if (view.parent !is ViewGroup) {
- // TODO(b/192194319): Throw instead of just logging.
- Log.wtf(
+ Log.e(
TAG,
"Skipping animation as view $view is not attached to a ViewGroup",
Exception()
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
index 8f9ced6956ca..eac5d275092a 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
@@ -113,6 +113,19 @@ constructor(
}
val animateFrom = animatedParent?.dialogContentWithBackground ?: view
+ if (animatedParent == null && animateFrom !is LaunchableView) {
+ // Make sure the View we launch from implements LaunchableView to avoid visibility
+ // issues. Given that we don't own dialog decorViews so we can't enforce it for launches
+ // from a dialog.
+ // TODO(b/243636422): Throw instead of logging to enforce this.
+ Log.w(
+ TAG,
+ "A dialog was launched from a View that does not implement LaunchableView. This " +
+ "can lead to subtle bugs where the visibility of the View we are " +
+ "launching from is not what we expected."
+ )
+ }
+
// Make sure we don't run the launch animation from the same view twice at the same time.
if (animateFrom.getTag(TAG_LAUNCH_ANIMATION_RUNNING) != null) {
Log.e(TAG, "Not running dialog launch animation as there is already one running")
@@ -156,9 +169,14 @@ constructor(
openedDialogs.firstOrNull { it.dialog == animateFrom }?.dialogContentWithBackground
?: throw IllegalStateException(
"The animateFrom dialog was not animated using " +
- "DialogLaunchAnimator.showFrom(View|Dialog)")
+ "DialogLaunchAnimator.showFrom(View|Dialog)"
+ )
showFromView(
- dialog, view, animateBackgroundBoundsChange = animateBackgroundBoundsChange, cuj = cuj)
+ dialog,
+ view,
+ animateBackgroundBoundsChange = animateBackgroundBoundsChange,
+ cuj = cuj
+ )
}
/**
@@ -197,7 +215,7 @@ constructor(
// bouncer.
if (
!dialog.isShowing ||
- (!callback.isUnlocked() && !callback.isShowingAlternateAuthOnUnlock())
+ (!callback.isUnlocked() && !callback.isShowingAlternateAuthOnUnlock())
) {
return null
}
@@ -556,11 +574,12 @@ private class AnimatedDialog(
window.setDecorFitsSystemWindows(false)
val viewWithInsets = (dialogContentWithBackground.parent as ViewGroup)
viewWithInsets.setOnApplyWindowInsetsListener { view, windowInsets ->
- val type = if (wasFittingNavigationBars) {
- WindowInsets.Type.displayCutout() or WindowInsets.Type.navigationBars()
- } else {
- WindowInsets.Type.displayCutout()
- }
+ val type =
+ if (wasFittingNavigationBars) {
+ WindowInsets.Type.displayCutout() or WindowInsets.Type.navigationBars()
+ } else {
+ WindowInsets.Type.displayCutout()
+ }
val insets = windowInsets.getInsets(type)
view.setPadding(insets.left, insets.top, insets.right, insets.bottom)
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/Expandable.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/Expandable.kt
new file mode 100644
index 000000000000..8ce372dbb278
--- /dev/null
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/Expandable.kt
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2022 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.animation
+
+import android.view.View
+
+/** A piece of UI that can be expanded into a Dialog or an Activity. */
+interface Expandable {
+ /**
+ * Create an [ActivityLaunchAnimator.Controller] that can be used to expand this [Expandable]
+ * into an Activity, or return `null` if this [Expandable] should not be animated (e.g. if it is
+ * currently not attached or visible).
+ *
+ * @param cujType the CUJ type from the [com.android.internal.jank.InteractionJankMonitor]
+ * associated to the launch that will use this controller.
+ */
+ fun activityLaunchController(cujType: Int? = null): ActivityLaunchAnimator.Controller?
+
+ // TODO(b/230830644): Introduce DialogLaunchAnimator and a function to expose it here.
+
+ companion object {
+ /**
+ * Create an [Expandable] that will animate [view] when expanded.
+ *
+ * Note: The background of [view] should be a (rounded) rectangle so that it can be properly
+ * animated.
+ */
+ fun fromView(view: View): Expandable {
+ return object : Expandable {
+ override fun activityLaunchController(
+ cujType: Int?,
+ ): ActivityLaunchAnimator.Controller? {
+ return ActivityLaunchAnimator.Controller.fromView(view, cujType)
+ }
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchableView.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchableView.kt
index 7499302c06b2..67b59e0e9928 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchableView.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchableView.kt
@@ -16,15 +16,79 @@
package com.android.systemui.animation
+import android.view.View
+
/** A view that can expand/launch into an app or a dialog. */
interface LaunchableView {
/**
- * Set whether this view should block/prevent all visibility changes. This ensures that this
- * view remains invisible during the launch animation given that it is ghosted and already drawn
+ * Set whether this view should block/postpone all visibility changes. This ensures that this
+ * view:
+ * - remains invisible during the launch animation given that it is ghosted and already drawn
* somewhere else.
+ * - remains invisible as long as a dialog expanded from it is shown.
+ * - restores its expected visibility once the dialog expanded from it is dismissed.
*
* Note that when this is set to true, both the [normal][android.view.View.setVisibility] and
* [transition][android.view.View.setTransitionVisibility] visibility changes must be blocked.
+ *
+ * @param block whether we should block/postpone all calls to `setVisibility` and
+ * `setTransitionVisibility`.
*/
fun setShouldBlockVisibilityChanges(block: Boolean)
}
+
+/** A delegate that can be used by views to make the implementation of [LaunchableView] easier. */
+class LaunchableViewDelegate(
+ private val view: View,
+
+ /**
+ * The lambda that should set the actual visibility of [view], usually by calling
+ * super.setVisibility(visibility).
+ */
+ private val superSetVisibility: (Int) -> Unit,
+
+ /**
+ * The lambda that should set the actual transition visibility of [view], usually by calling
+ * super.setTransitionVisibility(visibility).
+ */
+ private val superSetTransitionVisibility: (Int) -> Unit,
+) {
+ private var blockVisibilityChanges = false
+ private var lastVisibility = view.visibility
+
+ /** Call this when [LaunchableView.setShouldBlockVisibilityChanges] is called. */
+ fun setShouldBlockVisibilityChanges(block: Boolean) {
+ if (block == blockVisibilityChanges) {
+ return
+ }
+
+ blockVisibilityChanges = block
+ if (block) {
+ lastVisibility = view.visibility
+ } else {
+ superSetVisibility(lastVisibility)
+ }
+ }
+
+ /** Call this when [View.setVisibility] is called. */
+ fun setVisibility(visibility: Int) {
+ if (blockVisibilityChanges) {
+ lastVisibility = visibility
+ return
+ }
+
+ superSetVisibility(visibility)
+ }
+
+ /** Call this when [View.setTransitionVisibility] is called. */
+ fun setTransitionVisibility(visibility: Int) {
+ if (blockVisibilityChanges) {
+ // View.setTransitionVisibility just sets the visibility flag, so we don't have to save
+ // the transition visibility separately from the normal visibility.
+ lastVisibility = visibility
+ return
+ }
+
+ superSetTransitionVisibility(visibility)
+ }
+}
diff --git a/packages/SystemUI/compose/gallery/Android.bp b/packages/SystemUI/compose/gallery/Android.bp
index b0f5cc112120..5a7a1e1807a3 100644
--- a/packages/SystemUI/compose/gallery/Android.bp
+++ b/packages/SystemUI/compose/gallery/Android.bp
@@ -54,6 +54,11 @@ android_library {
"testables",
"truth-prebuilt",
"androidx.test.uiautomator",
+ "kotlinx_coroutines_test",
+ ],
+
+ libs: [
+ "android.test.mock",
],
kotlincflags: ["-Xjvm-default=all"],
diff --git a/packages/SystemUI/compose/gallery/src/com/android/systemui/qs/footer/Fakes.kt b/packages/SystemUI/compose/gallery/src/com/android/systemui/qs/footer/Fakes.kt
new file mode 100644
index 000000000000..11477f9d833b
--- /dev/null
+++ b/packages/SystemUI/compose/gallery/src/com/android/systemui/qs/footer/Fakes.kt
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2022 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.qs.footer
+
+import android.content.Context
+import android.os.UserHandle
+import android.view.View
+import com.android.internal.util.UserIcons
+import com.android.systemui.R
+import com.android.systemui.animation.Expandable
+import com.android.systemui.classifier.FalsingManagerFake
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.globalactions.GlobalActionsDialogLite
+import com.android.systemui.qs.footer.data.model.UserSwitcherStatusModel
+import com.android.systemui.qs.footer.domain.interactor.FooterActionsInteractor
+import com.android.systemui.qs.footer.domain.model.SecurityButtonConfig
+import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
+import com.android.systemui.util.mockito.mock
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flowOf
+
+/** A list of fake [FooterActionsViewModel] to be used in screenshot tests and the gallery. */
+fun fakeFooterActionsViewModels(
+ @Application context: Context,
+): List<FooterActionsViewModel> {
+ return listOf(
+ fakeFooterActionsViewModel(context),
+ fakeFooterActionsViewModel(context, showPowerButton = false, isGuestUser = true),
+ fakeFooterActionsViewModel(context, showUserSwitcher = false),
+ fakeFooterActionsViewModel(context, showUserSwitcher = false, foregroundServices = 4),
+ fakeFooterActionsViewModel(
+ context,
+ foregroundServices = 4,
+ hasNewForegroundServices = true,
+ userId = 1,
+ ),
+ fakeFooterActionsViewModel(
+ context,
+ securityText = "Security",
+ foregroundServices = 4,
+ showUserSwitcher = false,
+ ),
+ fakeFooterActionsViewModel(
+ context,
+ securityText = "Security (not clickable)",
+ securityClickable = false,
+ foregroundServices = 4,
+ hasNewForegroundServices = true,
+ userId = 2,
+ ),
+ )
+}
+
+private fun fakeFooterActionsViewModel(
+ @Application context: Context,
+ securityText: String? = null,
+ securityClickable: Boolean = true,
+ foregroundServices: Int = 0,
+ hasNewForegroundServices: Boolean = false,
+ showUserSwitcher: Boolean = true,
+ showPowerButton: Boolean = true,
+ userId: Int = UserHandle.USER_OWNER,
+ isGuestUser: Boolean = false,
+): FooterActionsViewModel {
+ val interactor =
+ FakeFooterActionsInteractor(
+ securityButtonConfig =
+ flowOf(
+ securityText?.let { text ->
+ SecurityButtonConfig(
+ icon = Icon.Resource(R.drawable.ic_info_outline),
+ text = text,
+ isClickable = securityClickable,
+ )
+ }
+ ),
+ foregroundServicesCount = flowOf(foregroundServices),
+ hasNewForegroundServices = flowOf(hasNewForegroundServices),
+ userSwitcherStatus =
+ flowOf(
+ if (showUserSwitcher) {
+ UserSwitcherStatusModel.Enabled(
+ currentUserName = "foo",
+ currentUserImage =
+ UserIcons.getDefaultUserIcon(
+ context.resources,
+ userId,
+ /* light= */ false,
+ ),
+ isGuestUser = isGuestUser,
+ )
+ } else {
+ UserSwitcherStatusModel.Disabled
+ }
+ ),
+ deviceMonitoringDialogRequests = flowOf(),
+ )
+
+ return FooterActionsViewModel(
+ context,
+ interactor,
+ FalsingManagerFake(),
+ globalActionsDialogLite = mock(),
+ showPowerButton = showPowerButton,
+ )
+}
+
+private class FakeFooterActionsInteractor(
+ override val securityButtonConfig: Flow<SecurityButtonConfig?> = flowOf(null),
+ override val foregroundServicesCount: Flow<Int> = flowOf(0),
+ override val hasNewForegroundServices: Flow<Boolean> = flowOf(false),
+ override val userSwitcherStatus: Flow<UserSwitcherStatusModel> =
+ flowOf(UserSwitcherStatusModel.Disabled),
+ override val deviceMonitoringDialogRequests: Flow<Unit> = flowOf(),
+ private val onShowDeviceMonitoringDialogFromView: (View) -> Unit = {},
+ private val onShowDeviceMonitoringDialog: (Context) -> Unit = {},
+ private val onShowForegroundServicesDialog: (View) -> Unit = {},
+ private val onShowPowerMenuDialog: (GlobalActionsDialogLite, View) -> Unit = { _, _ -> },
+ private val onShowSettings: (Expandable) -> Unit = {},
+ private val onShowUserSwitcher: (View) -> Unit = {},
+) : FooterActionsInteractor {
+ override fun showDeviceMonitoringDialog(view: View) {
+ onShowDeviceMonitoringDialogFromView(view)
+ }
+
+ override fun showDeviceMonitoringDialog(quickSettingsContext: Context) {
+ onShowDeviceMonitoringDialog(quickSettingsContext)
+ }
+
+ override fun showForegroundServicesDialog(view: View) {
+ onShowForegroundServicesDialog(view)
+ }
+
+ override fun showPowerMenuDialog(globalActionsDialogLite: GlobalActionsDialogLite, view: View) {
+ onShowPowerMenuDialog(globalActionsDialogLite, view)
+ }
+
+ override fun showSettings(expandable: Expandable) {
+ onShowSettings(expandable)
+ }
+
+ override fun showUserSwitcher(view: View) {
+ onShowUserSwitcher(view)
+ }
+}
diff --git a/packages/SystemUI/res-keyguard/layout/fgs_footer.xml b/packages/SystemUI/res-keyguard/layout/fgs_footer.xml
index 6757acf7014a..ee588f997ab8 100644
--- a/packages/SystemUI/res-keyguard/layout/fgs_footer.xml
+++ b/packages/SystemUI/res-keyguard/layout/fgs_footer.xml
@@ -14,6 +14,7 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
+<!-- TODO(b/242040009): Remove this file. -->
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="0dp"
diff --git a/packages/SystemUI/res-keyguard/layout/footer_actions.xml b/packages/SystemUI/res-keyguard/layout/footer_actions.xml
index a7e61029bfdb..1ce106ed2156 100644
--- a/packages/SystemUI/res-keyguard/layout/footer_actions.xml
+++ b/packages/SystemUI/res-keyguard/layout/footer_actions.xml
@@ -16,16 +16,17 @@
-->
<!-- Action buttons for footer in QS/QQS, containing settings button, power off button etc -->
+<!-- TODO(b/242040009): Clean up this file. -->
<com.android.systemui.qs.FooterActionsView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
android:layout_width="match_parent"
android:layout_height="@dimen/footer_actions_height"
android:elevation="@dimen/qs_panel_elevation"
- android:paddingTop="8dp"
+ android:paddingTop="@dimen/qs_footer_actions_top_padding"
android:paddingBottom="@dimen/qs_footer_actions_bottom_padding"
android:background="@drawable/qs_footer_actions_background"
- android:gravity="center_vertical"
+ android:gravity="center_vertical|end"
android:layout_gravity="bottom"
>
diff --git a/packages/SystemUI/res-keyguard/layout/footer_actions_icon_button.xml b/packages/SystemUI/res-keyguard/layout/footer_actions_icon_button.xml
new file mode 100644
index 000000000000..fad41c822ec0
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/layout/footer_actions_icon_button.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2022 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.android.systemui.statusbar.AlphaOptimizedFrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="@dimen/qs_footer_action_button_size"
+ android:layout_height="@dimen/qs_footer_action_button_size"
+ android:visibility="gone">
+ <ImageView
+ android:id="@+id/icon"
+ android:layout_width="@dimen/qs_footer_icon_size"
+ android:layout_height="@dimen/qs_footer_icon_size"
+ android:layout_gravity="center"
+ android:scaleType="centerInside" />
+</com.android.systemui.statusbar.AlphaOptimizedFrameLayout> \ No newline at end of file
diff --git a/packages/SystemUI/res-keyguard/layout/footer_actions_number_button.xml b/packages/SystemUI/res-keyguard/layout/footer_actions_number_button.xml
new file mode 100644
index 000000000000..a7ffe9ca256f
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/layout/footer_actions_number_button.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2022 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.android.systemui.statusbar.AlphaOptimizedFrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="@dimen/qs_footer_action_button_size"
+ android:layout_height="@dimen/qs_footer_action_button_size"
+ android:background="@drawable/qs_footer_action_circle"
+ android:visibility="gone">
+ <TextView
+ android:id="@+id/number"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textAppearance="@style/TextAppearance.QS.SecurityFooter"
+ android:layout_gravity="center"
+ android:textColor="?android:attr/textColorPrimary"
+ android:textSize="18sp"/>
+ <ImageView
+ android:id="@+id/new_dot"
+ android:layout_width="12dp"
+ android:layout_height="12dp"
+ android:scaleType="fitCenter"
+ android:layout_gravity="bottom|end"
+ android:src="@drawable/fgs_dot"
+ android:contentDescription="@string/fgs_dot_content_description" />
+</com.android.systemui.statusbar.AlphaOptimizedFrameLayout> \ No newline at end of file
diff --git a/packages/SystemUI/res-keyguard/layout/footer_actions_text_button.xml b/packages/SystemUI/res-keyguard/layout/footer_actions_text_button.xml
new file mode 100644
index 000000000000..fc18132d4dc3
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/layout/footer_actions_text_button.xml
@@ -0,0 +1,66 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2022 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.android.systemui.common.ui.view.LaunchableLinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="0dp"
+ android:layout_height="@dimen/qs_security_footer_single_line_height"
+ android:layout_weight="1"
+ android:orientation="horizontal"
+ android:paddingHorizontal="@dimen/qs_footer_padding"
+ android:gravity="center_vertical"
+ android:layout_marginEnd="@dimen/qs_footer_action_inset"
+ android:background="@drawable/qs_security_footer_background"
+ android:visibility="gone">
+ <ImageView
+ android:id="@+id/icon"
+ android:layout_width="@dimen/qs_footer_icon_size"
+ android:layout_height="@dimen/qs_footer_icon_size"
+ android:gravity="start"
+ android:layout_marginEnd="12dp"
+ android:contentDescription="@null"
+ android:src="@drawable/ic_info_outline"
+ android:tint="?android:attr/textColorSecondary" />
+
+ <TextView
+ android:id="@+id/text"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:maxLines="1"
+ android:ellipsize="end"
+ android:textAppearance="@style/TextAppearance.QS.SecurityFooter"
+ android:textColor="?android:attr/textColorSecondary"/>
+
+ <ImageView
+ android:id="@+id/new_dot"
+ android:layout_width="12dp"
+ android:layout_height="12dp"
+ android:scaleType="fitCenter"
+ android:src="@drawable/fgs_dot"
+ android:contentDescription="@string/fgs_dot_content_description"
+ />
+
+ <ImageView
+ android:id="@+id/chevron_icon"
+ android:layout_width="@dimen/qs_footer_icon_size"
+ android:layout_height="@dimen/qs_footer_icon_size"
+ android:layout_marginStart="8dp"
+ android:contentDescription="@null"
+ android:src="@*android:drawable/ic_chevron_end"
+ android:autoMirrored="true"
+ android:tint="?android:attr/textColorSecondary" />
+</com.android.systemui.common.ui.view.LaunchableLinearLayout> \ No newline at end of file
diff --git a/packages/SystemUI/res/layout/dream_overlay_complication_clock_time.xml b/packages/SystemUI/res/layout/dream_overlay_complication_clock_time.xml
index 3bf44a4b85a4..6ba88a66977a 100644
--- a/packages/SystemUI/res/layout/dream_overlay_complication_clock_time.xml
+++ b/packages/SystemUI/res/layout/dream_overlay_complication_clock_time.xml
@@ -14,7 +14,7 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-<TextClock
+<com.android.systemui.dreams.complication.DoubleShadowTextClock
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/time_view"
android:layout_width="wrap_content"
@@ -23,8 +23,6 @@
android:textColor="@android:color/white"
android:format12Hour="@string/dream_time_complication_12_hr_time_format"
android:format24Hour="@string/dream_time_complication_24_hr_time_format"
- android:shadowColor="@color/keyguard_shadow_color"
- android:shadowRadius="?attr/shadowRadius"
android:fontFeatureSettings="pnum, lnum"
android:letterSpacing="0.02"
android:textSize="@dimen/dream_overlay_complication_clock_time_text_size"/>
diff --git a/packages/SystemUI/res/layout/notification_stack_scroll_layout.xml b/packages/SystemUI/res/layout/notification_stack_scroll_layout.xml
new file mode 100644
index 000000000000..65cf81ea416b
--- /dev/null
+++ b/packages/SystemUI/res/layout/notification_stack_scroll_layout.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 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.
+-->
+
+<!-- This XML is served to be overridden by other OEMs/device types. -->
+<com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:systemui="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/notification_stack_scroller"
+ android:layout_marginTop="@dimen/notification_panel_margin_top"
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_marginHorizontal="@dimen/notification_panel_margin_horizontal"
+ android:layout_marginBottom="@dimen/notification_panel_margin_bottom"
+ android:importantForAccessibility="no"
+ systemui:layout_constraintStart_toStartOf="parent"
+ systemui:layout_constraintEnd_toEndOf="parent"
+/>
diff --git a/packages/SystemUI/res/layout/qs_user_detail_item.xml b/packages/SystemUI/res/layout/qs_user_detail_item.xml
index 0c847ed588e8..7c86bc77aa95 100644
--- a/packages/SystemUI/res/layout/qs_user_detail_item.xml
+++ b/packages/SystemUI/res/layout/qs_user_detail_item.xml
@@ -49,7 +49,8 @@
android:id="@+id/user_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:gravity="center_horizontal" />
+ android:gravity="center_horizontal"
+ android:hyphenationFrequency="full"/>
<ImageView
android:id="@+id/restricted_padlock"
android:layout_width="@dimen/qs_tile_text_size"
diff --git a/packages/SystemUI/res/layout/quick_settings_security_footer.xml b/packages/SystemUI/res/layout/quick_settings_security_footer.xml
index 1b11816465ac..194f3dd5dc26 100644
--- a/packages/SystemUI/res/layout/quick_settings_security_footer.xml
+++ b/packages/SystemUI/res/layout/quick_settings_security_footer.xml
@@ -14,6 +14,7 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
+<!-- TODO(b/242040009): Remove this file. -->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="0dp"
diff --git a/packages/SystemUI/res/layout/status_bar_expanded.xml b/packages/SystemUI/res/layout/status_bar_expanded.xml
index 6423a50fc107..f0e49d5c2011 100644
--- a/packages/SystemUI/res/layout/status_bar_expanded.xml
+++ b/packages/SystemUI/res/layout/status_bar_expanded.xml
@@ -109,17 +109,10 @@
systemui:layout_constraintGuide_percent="0.5"
android:orientation="vertical"/>
- <com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout
- android:id="@+id/notification_stack_scroller"
- android:layout_marginTop="@dimen/notification_panel_margin_top"
- android:layout_width="0dp"
- android:layout_height="match_parent"
- android:layout_marginHorizontal="@dimen/notification_panel_margin_horizontal"
- android:layout_marginBottom="@dimen/notification_panel_margin_bottom"
- android:importantForAccessibility="no"
- systemui:layout_constraintStart_toStartOf="parent"
- systemui:layout_constraintEnd_toEndOf="parent"
- />
+ <!-- This layout should always include a version of
+ NotificationStackScrollLayout, as it is expected from
+ NotificationPanelViewController. -->
+ <include layout="@layout/notification_stack_scroll_layout" />
<include layout="@layout/photo_preview_overlay" />
diff --git a/packages/SystemUI/res/raw/fingerprint_dialogue_error_to_success_lottie.json b/packages/SystemUI/res/raw/fingerprint_dialogue_error_to_success_lottie.json
new file mode 100644
index 000000000000..c5ed827de0d1
--- /dev/null
+++ b/packages/SystemUI/res/raw/fingerprint_dialogue_error_to_success_lottie.json
@@ -0,0 +1 @@
+{"v":"5.9.0","fr":60,"ip":0,"op":21,"w":80,"h":80,"nm":"RearFPS_error_to_success","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":".green200","cl":"green200","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[28,47,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-10.556,-9.889],[7.444,6.555],[34.597,-20.486]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.658823529412,0.854901960784,0.709803921569,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":4,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":10,"s":[0]},{"t":20,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":10,"op":910,"st":10,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".red200","cl":"red200","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[39.95,40,0],"ix":2,"l":2},"a":{"a":0,"k":[30,30,0],"ix":1,"l":2},"s":{"a":0,"k":[120,120,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-1.721,-7.982],[1.721,-7.982],[1.721,7.5],[-1.721,7.5]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.949019607843,0.721568627451,0.709803921569,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[30.002,32.488],"ix":2},"a":{"a":0,"k":[0.002,7.488],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.659,0.6],"y":[1,1]},"o":{"x":[0.8,0.8],"y":[0,0]},"t":0,"s":[100,100]},{"i":{"x":[0.6,0.92],"y":[1,1.096]},"o":{"x":[0.8,0.8],"y":[0,0]},"t":4,"s":[100,110]},{"t":10,"s":[100,0]}],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Top!","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-1.681,-1.25],[1.681,-1.25],[1.681,2.213],[-1.681,2.213]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.949019607843,0.721568627451,0.709803921569,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[30,38.75],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.6,0.6],"y":[1,1]},"o":{"x":[0.853,0.853],"y":[0,0]},"t":0,"s":[100,100]},{"i":{"x":[0.92,0.92],"y":[1.06,1.06]},"o":{"x":[0.8,0.8],"y":[0,0]},"t":4,"s":[110,110]},{"t":10,"s":[0,0]}],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Bottom!","np":2,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":86,"st":-30,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".green200","cl":"green200","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":10,"s":[0]},{"t":15,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[40,40,0],"ix":2,"l":2},"a":{"a":0,"k":[40.25,40.25,0],"ix":1,"l":2},"s":{"a":0,"k":[93.5,93.5,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[22.12,0],[0,-22.08],[-22.08,0],[0,22.08]],"o":[[-22.08,0],[0,22.08],[22.12,0],[0,-22.08]],"v":[[-0.04,-40],[-40,0],[-0.04,40],[40,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[40.25,40.25],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.658823529412,0.854901960784,0.709803921569,1],"ix":3},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":10,"s":[0]},{"t":15,"s":[100]}],"ix":4},"w":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":10,"s":[0]},{"t":20,"s":[4]}],"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false}],"ip":10,"op":21,"st":10,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":".red200","cl":"red200","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":5,"s":[100]},{"t":10,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[40,40,0],"ix":2,"l":2},"a":{"a":0,"k":[40.25,40.25,0],"ix":1,"l":2},"s":{"a":0,"k":[93.5,93.5,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[22.12,0],[0,-22.08],[-22.08,0],[0,22.08]],"o":[[-22.08,0],[0,22.08],[22.12,0],[0,-22.08]],"v":[[-0.04,-40],[-40,0],[-0.04,40],[40,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[40.25,40.25],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":5,"s":[100]},{"t":10,"s":[0]}],"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.949019607843,0.721568627451,0.709803921569,1],"ix":3},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":5,"s":[100]},{"t":10,"s":[0]}],"ix":4},"w":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":0,"s":[4]},{"t":10,"s":[0]}],"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false}],"ip":0,"op":10,"st":0,"bm":0}],"markers":[]} \ No newline at end of file
diff --git a/packages/SystemUI/res/raw/fingerprint_dialogue_fingerprint_to_success_lottie.json b/packages/SystemUI/res/raw/fingerprint_dialogue_fingerprint_to_success_lottie.json
new file mode 100644
index 000000000000..3eb95ef1a718
--- /dev/null
+++ b/packages/SystemUI/res/raw/fingerprint_dialogue_fingerprint_to_success_lottie.json
@@ -0,0 +1 @@
+{"v":"5.9.0","fr":60,"ip":0,"op":21,"w":80,"h":80,"nm":"RearFPS_fingerprint_to_success","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":".green200","cl":"green200","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[28,47,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-10.556,-9.889],[7.444,6.555],[34.597,-20.486]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.658823529412,0.854901960784,0.709803921569,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":4,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":10,"s":[0]},{"t":20,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":10,"op":910,"st":10,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".green200","cl":"green200","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":10,"s":[0]},{"t":15,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[40,40,0],"ix":2,"l":2},"a":{"a":0,"k":[40.25,40.25,0],"ix":1,"l":2},"s":{"a":0,"k":[93.5,93.5,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[22.12,0],[0,-22.08],[-22.08,0],[0,22.08]],"o":[[-22.08,0],[0,22.08],[22.12,0],[0,-22.08]],"v":[[-0.04,-40],[-40,0],[-0.04,40],[40,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[40.25,40.25],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.658823529412,0.854901960784,0.709803921569,1],"ix":3},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":10,"s":[0]},{"t":15,"s":[100]}],"ix":4},"w":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":10,"s":[0]},{"t":20,"s":[4]}],"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false}],"ip":10,"op":21,"st":10,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".colorAccentPrimary","cl":"colorAccentPrimary","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":0,"s":[100]},{"t":10,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[40.091,40,0],"ix":2,"l":2},"a":{"a":0,"k":[19.341,24.25,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[-1.701,0.42],[-1.757,0],[-1.577,-0.381],[-1.485,-0.816]],"o":[[1.455,-0.799],[1.608,-0.397],[1.719,0],[1.739,0.42],[0,0]],"v":[[-9.818,1.227],[-5.064,-0.618],[0,-1.227],[4.96,-0.643],[9.818,1.227]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.827450980392,0.890196078431,0.992156862745,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":3,"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[19.341,7.477],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Top","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[-2.446,1.161],[-1.168,0.275],[-1.439,0],[-1.301,-0.304],[-1.225,-0.66],[-1.11,-1.844]],"o":[[1.23,-2.044],[1.024,-0.486],[1.312,-0.31],[1.425,0],[1.454,0.34],[2.122,1.143],[0,0]],"v":[[-13.091,3.273],[-7.438,-1.646],[-4.14,-2.797],[0,-3.273],[4.104,-2.805],[8.141,-1.29],[13.091,3.273]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.827450980392,0.890196078431,0.992156862745,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":3,"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[19.341,16.069],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Mid Top","np":2,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[-6.53,0],[0,-5.793],[0,0],[2.159,0],[0.59,1.489],[0,0],[1.587,0],[0,-2.16],[-0.81,-1.363],[-0.844,-0.674],[0,0]],"o":[[-0.753,-2.095],[0,-5.793],[6.529,0],[0,0],[0,2.16],[-1.604,0],[0,0],[-0.589,-1.489],[-2.161,0],[0,1.62],[0.54,0.909],[0,0],[0,0]],"v":[[-10.702,5.728],[-11.454,1.506],[0.001,-9],[11.454,1.506],[11.454,1.817],[7.544,5.728],[3.926,3.273],[2.618,0],[-0.997,-2.454],[-4.91,1.457],[-3.657,6.014],[-1.57,8.412],[-0.818,9]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.827450980392,0.890196078431,0.992156862745,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":3,"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[19.341,28.341],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Inside to dot ","np":2,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[1.307,-0.561],[0.894,-0.16],[0.706,0],[0.844,0.193],[0.728,0.334],[0.967,0.901]],"o":[[-1.038,0.967],[-0.817,0.351],[-0.673,0.12],[-0.9,0],[-0.794,-0.182],[-1.203,-0.551],[0,0]],"v":[[8.182,-0.386],[4.642,1.931],[2.07,2.703],[-0.001,2.886],[-2.621,2.591],[-4.909,1.813],[-8.182,-0.386]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.827450980392,0.890196078431,0.992156862745,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":3,"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[19.341,40.614],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Bottom","np":2,"cix":2,"bm":0,"ix":4,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":".grey700","cl":"grey700","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[40,40,0],"ix":2,"l":2},"a":{"a":0,"k":[40.25,40.25,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[22.12,0],[0,-22.08],[-22.08,0],[0,22.08]],"o":[[-22.08,0],[0,22.08],[22.12,0],[0,-22.08]],"v":[[-0.04,-40],[-40,0],[-0.04,40],[40,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.278431385756,0.278431385756,0.278431385756,1],"ix":4},"o":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":0,"s":[100]},{"t":20,"s":[0]}],"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[40.25,40.25],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0}],"markers":[]} \ No newline at end of file
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index 1eece4cee179..db2ac436229f 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -241,4 +241,6 @@
<color name="dream_overlay_aqi_very_unhealthy">#AD1457</color>
<color name="dream_overlay_aqi_hazardous">#880E4F</color>
<color name="dream_overlay_aqi_unknown">#BDC1C6</color>
+ <color name="dream_overlay_clock_key_text_shadow_color">#4D000000</color>
+ <color name="dream_overlay_clock_ambient_text_shadow_color">#4D000000</color>
</resources>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 229858413f99..c9776dd58788 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -380,6 +380,7 @@
<!-- (48dp - 40dp) / 2 -->
<dimen name="qs_footer_action_inset">4dp</dimen>
+ <dimen name="qs_footer_actions_top_padding">8dp</dimen>
<dimen name="qs_footer_actions_bottom_padding">4dp</dimen>
<dimen name="qs_footer_action_inset_negative">-4dp</dimen>
@@ -1539,4 +1540,10 @@
<dimen name="broadcast_dialog_btn_text_size">16sp</dimen>
<dimen name="broadcast_dialog_btn_minHeight">44dp</dimen>
<dimen name="broadcast_dialog_margin">16dp</dimen>
+ <dimen name="dream_overlay_clock_key_text_shadow_dx">0dp</dimen>
+ <dimen name="dream_overlay_clock_key_text_shadow_dy">0dp</dimen>
+ <dimen name="dream_overlay_clock_key_text_shadow_radius">5dp</dimen>
+ <dimen name="dream_overlay_clock_ambient_text_shadow_dx">0dp</dimen>
+ <dimen name="dream_overlay_clock_ambient_text_shadow_dy">0dp</dimen>
+ <dimen name="dream_overlay_clock_ambient_text_shadow_radius">1dp</dimen>
</resources>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/regionsampling/RegionSamplingInstance.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/regionsampling/RegionSamplingInstance.kt
index 0146795f4988..dd2e55d4e7d7 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/regionsampling/RegionSamplingInstance.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/regionsampling/RegionSamplingInstance.kt
@@ -35,6 +35,7 @@ open class RegionSamplingInstance(
) {
private var isDark = RegionDarkness.DEFAULT
private var samplingBounds = Rect()
+ private val tmpScreenLocation = IntArray(2)
@VisibleForTesting var regionSampler: RegionSamplingHelper? = null
/**
@@ -99,10 +100,21 @@ open class RegionSamplingInstance(
isDark = convertToClockDarkness(isRegionDark)
updateFun.updateColors()
}
-
+ /**
+ * The method getLocationOnScreen is used to obtain the view coordinates
+ * relative to its left and top edges on the device screen.
+ * Directly accessing the X and Y coordinates of the view returns the
+ * location relative to its parent view instead.
+ */
override fun getSampledRegion(sampledView: View): Rect {
- samplingBounds = Rect(sampledView.left, sampledView.top,
- sampledView.right, sampledView.bottom)
+ val screenLocation = tmpScreenLocation
+ sampledView.getLocationOnScreen(screenLocation)
+ val left = screenLocation[0]
+ val top = screenLocation[1]
+ samplingBounds.left = left
+ samplingBounds.top = top
+ samplingBounds.right = left + sampledView.width
+ samplingBounds.bottom = top + sampledView.height
return samplingBounds
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt
index 589ec0e72b3b..9b5f54a0a91d 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt
@@ -92,7 +92,7 @@ open class AuthBiometricFingerprintIconController(
STATE_ERROR -> true
STATE_AUTHENTICATING_ANIMATING_IN,
STATE_AUTHENTICATING -> oldState == STATE_ERROR || oldState == STATE_HELP
- STATE_AUTHENTICATED -> false
+ STATE_AUTHENTICATED -> true
else -> false
}
@@ -114,7 +114,13 @@ open class AuthBiometricFingerprintIconController(
R.raw.fingerprint_dialogue_fingerprint_to_error_lottie
}
}
- STATE_AUTHENTICATED -> R.raw.fingerprint_dialogue_fingerprint_to_error_lottie
+ STATE_AUTHENTICATED -> {
+ if (oldState == STATE_ERROR || oldState == STATE_HELP) {
+ R.raw.fingerprint_dialogue_error_to_success_lottie
+ } else {
+ R.raw.fingerprint_dialogue_fingerprint_to_success_lottie
+ }
+ }
else -> return null
}
return if (id != null) return id else null
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintView.kt
index 31baa0ff1154..9cce066afe9d 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintView.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintView.kt
@@ -75,7 +75,7 @@ open class AuthBiometricFingerprintView(
}
}
- override fun getDelayAfterAuthenticatedDurationMs() = 0
+ override fun getDelayAfterAuthenticatedDurationMs() = 500
override fun getStateForAfterError() = STATE_AUTHENTICATING
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java
index e866b9c0bb25..fc5cf9f005ed 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java
@@ -468,6 +468,7 @@ public abstract class AuthBiometricView extends LinearLayout {
break;
case STATE_AUTHENTICATED:
+ removePendingAnimations();
if (mSize != AuthDialog.SIZE_SMALL) {
mConfirmButton.setVisibility(View.GONE);
mNegativeButton.setVisibility(View.GONE);
diff --git a/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt b/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt
index d757b629c829..eb8cb47c2671 100644
--- a/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt
+++ b/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt
@@ -31,6 +31,8 @@ import android.util.SparseArray
import com.android.internal.annotations.VisibleForTesting
import com.android.systemui.Dumpable
import com.android.systemui.broadcast.logging.BroadcastDispatcherLogger
+import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dump.DumpManager
@@ -38,6 +40,8 @@ import com.android.systemui.settings.UserTracker
import java.io.PrintWriter
import java.util.concurrent.Executor
import javax.inject.Inject
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
data class ReceiverData(
val receiver: BroadcastReceiver,
@@ -153,6 +157,55 @@ open class BroadcastDispatcher @Inject constructor(
.sendToTarget()
}
+ /**
+ * Returns a [Flow] that, when collected, emits a new value whenever a broadcast matching
+ * [filter] is received. The value will be computed from the intent and the registered receiver
+ * using [map].
+ *
+ * @see registerReceiver
+ */
+ @JvmOverloads
+ fun <T> broadcastFlow(
+ filter: IntentFilter,
+ user: UserHandle? = null,
+ @Context.RegisterReceiverFlags flags: Int = Context.RECEIVER_EXPORTED,
+ permission: String? = null,
+ map: (Intent, BroadcastReceiver) -> T,
+ ): Flow<T> = conflatedCallbackFlow {
+ val receiver = object : BroadcastReceiver() {
+ override fun onReceive(context: Context, intent: Intent) {
+ trySendWithFailureLogging(map(intent, this), TAG)
+ }
+ }
+
+ registerReceiver(
+ receiver,
+ filter,
+ bgExecutor,
+ user,
+ flags,
+ permission,
+ )
+
+ awaitClose {
+ unregisterReceiver(receiver)
+ }
+ }
+
+ /**
+ * Returns a [Flow] that, when collected, emits `Unit` whenever a broadcast matching [filter] is
+ * received.
+ *
+ * @see registerReceiver
+ */
+ @JvmOverloads
+ fun broadcastFlow(
+ filter: IntentFilter,
+ user: UserHandle? = null,
+ @Context.RegisterReceiverFlags flags: Int = Context.RECEIVER_EXPORTED,
+ permission: String? = null,
+ ): Flow<Unit> = broadcastFlow(filter, user, flags, permission) { _, _ -> Unit }
+
private fun checkFilter(filter: IntentFilter) {
val sb = StringBuilder()
if (filter.countActions() == 0) sb.append("Filter must contain at least one action. ")
diff --git a/packages/SystemUI/src/com/android/systemui/camera/CameraGestureHelper.kt b/packages/SystemUI/src/com/android/systemui/camera/CameraGestureHelper.kt
index 81da80233d42..4fe2dd810a08 100644
--- a/packages/SystemUI/src/com/android/systemui/camera/CameraGestureHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/camera/CameraGestureHelper.kt
@@ -33,10 +33,10 @@ import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.ActivityIntentHelper
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.shade.NotificationPanelViewController
import com.android.systemui.shared.system.ActivityManagerKt.isInForeground
import com.android.systemui.statusbar.StatusBarState
import com.android.systemui.statusbar.phone.CentralSurfaces
-import com.android.systemui.shade.PanelViewController
import com.android.systemui.statusbar.policy.KeyguardStateController
import java.util.concurrent.Executor
import javax.inject.Inject
@@ -117,7 +117,7 @@ class CameraGestureHelper @Inject constructor(
)
} catch (e: RemoteException) {
Log.w(
- PanelViewController.TAG,
+ NotificationPanelViewController.TAG,
"Unable to start camera activity",
e
)
diff --git a/packages/SystemUI/src/com/android/systemui/common/shared/model/ContentDescription.kt b/packages/SystemUI/src/com/android/systemui/common/shared/model/ContentDescription.kt
new file mode 100644
index 000000000000..bebade0cc484
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/common/shared/model/ContentDescription.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2022 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.common.shared.model
+
+import android.annotation.StringRes
+
+/**
+ * Models a content description, that can either be already [loaded][ContentDescription.Loaded] or
+ * be a [reference][ContentDescription.Resource] to a resource.
+ */
+sealed class ContentDescription {
+ data class Loaded(
+ val description: String?,
+ ) : ContentDescription()
+
+ data class Resource(
+ @StringRes val res: Int,
+ ) : ContentDescription()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/common/shared/model/Icon.kt b/packages/SystemUI/src/com/android/systemui/common/shared/model/Icon.kt
new file mode 100644
index 000000000000..0b65966ca109
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/common/shared/model/Icon.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2022 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.common.shared.model
+
+import android.annotation.DrawableRes
+import android.graphics.drawable.Drawable
+
+/**
+ * Models an icon, that can either be already [loaded][Icon.Loaded] or be a [reference]
+ * [Icon.Resource] to a resource.
+ */
+sealed class Icon {
+ data class Loaded(
+ val drawable: Drawable,
+ ) : Icon()
+
+ data class Resource(
+ @DrawableRes val res: Int,
+ ) : Icon()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/binder/ContentDescriptionViewBinder.kt b/packages/SystemUI/src/com/android/systemui/common/ui/binder/ContentDescriptionViewBinder.kt
new file mode 100644
index 000000000000..d6433aae9845
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/common/ui/binder/ContentDescriptionViewBinder.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2022 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.common.ui.binder
+
+import android.view.View
+import com.android.systemui.common.shared.model.ContentDescription
+
+object ContentDescriptionViewBinder {
+ fun bind(
+ contentDescription: ContentDescription,
+ view: View,
+ ) {
+ when (contentDescription) {
+ is ContentDescription.Loaded -> view.contentDescription = contentDescription.description
+ is ContentDescription.Resource -> {
+ view.contentDescription = view.context.resources.getString(contentDescription.res)
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/binder/IconViewBinder.kt b/packages/SystemUI/src/com/android/systemui/common/ui/binder/IconViewBinder.kt
new file mode 100644
index 000000000000..aecee2afc9d2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/common/ui/binder/IconViewBinder.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2022 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.common.ui.binder
+
+import android.widget.ImageView
+import com.android.systemui.common.shared.model.Icon
+
+object IconViewBinder {
+ fun bind(
+ icon: Icon,
+ view: ImageView,
+ ) {
+ when (icon) {
+ is Icon.Loaded -> view.setImageDrawable(icon.drawable)
+ is Icon.Resource -> view.setImageResource(icon.res)
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/view/LaunchableLinearLayout.kt b/packages/SystemUI/src/com/android/systemui/common/ui/view/LaunchableLinearLayout.kt
new file mode 100644
index 000000000000..c27b82aeeb47
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/common/ui/view/LaunchableLinearLayout.kt
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2022 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.common.ui.view
+
+import android.content.Context
+import android.util.AttributeSet
+import android.widget.LinearLayout
+import com.android.systemui.animation.LaunchableView
+import com.android.systemui.animation.LaunchableViewDelegate
+
+/** A [LinearLayout] that also implements [LaunchableView]. */
+class LaunchableLinearLayout : LinearLayout, LaunchableView {
+ private val delegate =
+ LaunchableViewDelegate(
+ this,
+ superSetVisibility = { super.setVisibility(it) },
+ superSetTransitionVisibility = { super.setTransitionVisibility(it) },
+ )
+
+ constructor(context: Context?) : super(context)
+ constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)
+ constructor(
+ context: Context?,
+ attrs: AttributeSet?,
+ defStyleAttr: Int,
+ ) : super(context, attrs, defStyleAttr)
+
+ constructor(
+ context: Context?,
+ attrs: AttributeSet?,
+ defStyleAttr: Int,
+ defStyleRes: Int,
+ ) : super(context, attrs, defStyleAttr, defStyleRes)
+
+ override fun setShouldBlockVisibilityChanges(block: Boolean) {
+ delegate.setShouldBlockVisibilityChanges(block)
+ }
+
+ override fun setVisibility(visibility: Int) {
+ delegate.setVisibility(visibility)
+ }
+
+ override fun setTransitionVisibility(visibility: Int) {
+ delegate.setTransitionVisibility(visibility)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt
index f611c3ef966d..5e8ce6db971c 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt
@@ -25,6 +25,7 @@ import android.view.ViewGroup
import android.view.ViewStub
import android.widget.Button
import android.widget.TextView
+import androidx.activity.ComponentActivity
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.RecyclerView
@@ -36,7 +37,6 @@ import com.android.systemui.controls.controller.StructureInfo
import com.android.systemui.controls.ui.ControlsActivity
import com.android.systemui.controls.ui.ControlsUiController
import com.android.systemui.settings.CurrentUserTracker
-import com.android.systemui.util.LifecycleActivity
import javax.inject.Inject
/**
@@ -47,7 +47,7 @@ class ControlsEditingActivity @Inject constructor(
private val broadcastDispatcher: BroadcastDispatcher,
private val customIconCache: CustomIconCache,
private val uiController: ControlsUiController
-) : LifecycleActivity() {
+) : ComponentActivity() {
companion object {
private const val TAG = "ControlsEditingActivity"
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt
index dca52a9678b9..be572c503bda 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt
@@ -32,6 +32,7 @@ import android.widget.Button
import android.widget.FrameLayout
import android.widget.TextView
import android.widget.Toast
+import androidx.activity.ComponentActivity
import androidx.viewpager2.widget.ViewPager2
import com.android.systemui.Prefs
import com.android.systemui.R
@@ -44,7 +45,6 @@ import com.android.systemui.controls.ui.ControlsActivity
import com.android.systemui.controls.ui.ControlsUiController
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.settings.CurrentUserTracker
-import com.android.systemui.util.LifecycleActivity
import java.text.Collator
import java.util.concurrent.Executor
import java.util.function.Consumer
@@ -56,7 +56,7 @@ class ControlsFavoritingActivity @Inject constructor(
private val listingController: ControlsListingController,
private val broadcastDispatcher: BroadcastDispatcher,
private val uiController: ControlsUiController
-) : LifecycleActivity() {
+) : ComponentActivity() {
companion object {
private const val TAG = "ControlsFavoritingActivity"
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt
index 8ad5099dc42d..b26615fe4702 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt
@@ -26,6 +26,7 @@ import android.view.ViewGroup
import android.view.ViewStub
import android.widget.Button
import android.widget.TextView
+import androidx.activity.ComponentActivity
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.AdapterDataObserver
@@ -37,7 +38,6 @@ import com.android.systemui.controls.ui.ControlsUiController
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.settings.CurrentUserTracker
-import com.android.systemui.util.LifecycleActivity
import java.util.concurrent.Executor
import javax.inject.Inject
@@ -51,7 +51,7 @@ class ControlsProviderSelectorActivity @Inject constructor(
private val controlsController: ControlsController,
private val broadcastDispatcher: BroadcastDispatcher,
private val uiController: ControlsUiController
-) : LifecycleActivity() {
+) : ComponentActivity() {
companion object {
private const val TAG = "ControlsProviderSelectorActivity"
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsRequestDialog.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsRequestDialog.kt
index f9e7f0e921f3..b376455ee815 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsRequestDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsRequestDialog.kt
@@ -30,6 +30,7 @@ import android.view.LayoutInflater
import android.view.View
import android.widget.ImageView
import android.widget.TextView
+import androidx.activity.ComponentActivity
import com.android.systemui.R
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.controls.ControlsServiceInfo
@@ -38,14 +39,13 @@ import com.android.systemui.controls.controller.ControlsController
import com.android.systemui.controls.ui.RenderInfo
import com.android.systemui.settings.CurrentUserTracker
import com.android.systemui.statusbar.phone.SystemUIDialog
-import com.android.systemui.util.LifecycleActivity
import javax.inject.Inject
open class ControlsRequestDialog @Inject constructor(
private val controller: ControlsController,
private val broadcastDispatcher: BroadcastDispatcher,
private val controlsListingController: ControlsListingController
-) : LifecycleActivity(), DialogInterface.OnClickListener, DialogInterface.OnCancelListener {
+) : ComponentActivity(), DialogInterface.OnClickListener, DialogInterface.OnCancelListener {
companion object {
private const val TAG = "ControlsRequestDialog"
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt
index 49f758405fbd..77b65233c112 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt
@@ -25,11 +25,10 @@ import android.view.View
import android.view.ViewGroup
import android.view.WindowInsets
import android.view.WindowInsets.Type
-
+import androidx.activity.ComponentActivity
import com.android.systemui.R
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.controls.management.ControlsAnimations
-import com.android.systemui.util.LifecycleActivity
import javax.inject.Inject
/**
@@ -42,7 +41,7 @@ import javax.inject.Inject
class ControlsActivity @Inject constructor(
private val uiController: ControlsUiController,
private val broadcastDispatcher: BroadcastDispatcher
-) : LifecycleActivity() {
+) : ComponentActivity() {
private lateinit var parent: ViewGroup
private lateinit var broadcastReceiver: BroadcastReceiver
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index e549a96079bd..1b060e209579 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -49,8 +49,12 @@ import com.android.systemui.navigationbar.NavigationBarComponent;
import com.android.systemui.people.PeopleModule;
import com.android.systemui.plugins.BcSmartspaceDataPlugin;
import com.android.systemui.privacy.PrivacyModule;
+import com.android.systemui.qs.FgsManagerController;
+import com.android.systemui.qs.FgsManagerControllerImpl;
+import com.android.systemui.qs.footer.dagger.FooterActionsModule;
import com.android.systemui.recents.Recents;
import com.android.systemui.screenshot.dagger.ScreenshotModule;
+import com.android.systemui.security.data.repository.SecurityRepositoryModule;
import com.android.systemui.settings.dagger.MultiUserUtilsModule;
import com.android.systemui.shade.ShadeController;
import com.android.systemui.smartspace.dagger.SmartspaceModule;
@@ -122,6 +126,7 @@ import dagger.Provides;
DemoModeModule.class,
FalsingModule.class,
FlagsModule.class,
+ FooterActionsModule.class,
LogModule.class,
MediaProjectionModule.class,
PeopleHubModule.class,
@@ -132,6 +137,7 @@ import dagger.Provides;
ScreenshotModule.class,
SensorModule.class,
MultiUserUtilsModule.class,
+ SecurityRepositoryModule.class,
SettingsUtilModule.class,
SmartRepliesInflationModule.class,
SmartspaceModule.class,
@@ -258,4 +264,7 @@ public abstract class SystemUIModule {
return Optional.empty();
}
}
+
+ @Binds
+ abstract FgsManagerController bindFgsManagerController(FgsManagerControllerImpl impl);
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/DoubleShadowTextClock.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/DoubleShadowTextClock.java
new file mode 100644
index 000000000000..653f4dc66200
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/DoubleShadowTextClock.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2022 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.dreams.complication;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.util.AttributeSet;
+import android.widget.TextClock;
+
+import com.android.systemui.R;
+
+/**
+ * Extension of {@link TextClock} which draws two shadows on the text (ambient and key shadows)
+ */
+public class DoubleShadowTextClock extends TextClock {
+ private final float mAmbientShadowBlur;
+ private final int mAmbientShadowColor;
+ private final float mKeyShadowBlur;
+ private final float mKeyShadowOffsetX;
+ private final float mKeyShadowOffsetY;
+ private final int mKeyShadowColor;
+ private final float mAmbientShadowOffsetX;
+ private final float mAmbientShadowOffsetY;
+
+ public DoubleShadowTextClock(Context context) {
+ this(context, null);
+ }
+
+ public DoubleShadowTextClock(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public DoubleShadowTextClock(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ mKeyShadowBlur = context.getResources()
+ .getDimensionPixelSize(R.dimen.dream_overlay_clock_key_text_shadow_radius);
+ mKeyShadowOffsetX = context.getResources()
+ .getDimensionPixelSize(R.dimen.dream_overlay_clock_key_text_shadow_dx);
+ mKeyShadowOffsetY = context.getResources()
+ .getDimensionPixelSize(R.dimen.dream_overlay_clock_key_text_shadow_dy);
+ mKeyShadowColor = context.getResources().getColor(
+ R.color.dream_overlay_clock_key_text_shadow_color);
+ mAmbientShadowBlur = context.getResources()
+ .getDimensionPixelSize(R.dimen.dream_overlay_clock_ambient_text_shadow_radius);
+ mAmbientShadowColor = context.getResources().getColor(
+ R.color.dream_overlay_clock_ambient_text_shadow_color);
+ mAmbientShadowOffsetX = context.getResources()
+ .getDimensionPixelSize(R.dimen.dream_overlay_clock_ambient_text_shadow_dx);
+ mAmbientShadowOffsetY = context.getResources()
+ .getDimensionPixelSize(R.dimen.dream_overlay_clock_ambient_text_shadow_dy);
+ }
+
+ @Override
+ public void onDraw(Canvas canvas) {
+ // We enhance the shadow by drawing the shadow twice
+ getPaint().setShadowLayer(mAmbientShadowBlur, mAmbientShadowOffsetX, mAmbientShadowOffsetY,
+ mAmbientShadowColor);
+ super.onDraw(canvas);
+ canvas.save();
+ canvas.clipRect(getScrollX(), getScrollY() + getExtendedPaddingTop(),
+ getScrollX() + getWidth(),
+ getScrollY() + getHeight());
+
+ getPaint().setShadowLayer(
+ mKeyShadowBlur, mKeyShadowOffsetX, mKeyShadowOffsetY, mKeyShadowColor);
+ super.onDraw(canvas);
+ canvas.restore();
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/BouncerSwipeModule.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/BouncerSwipeModule.java
index 9c22dc61e67b..081bab085843 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/BouncerSwipeModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/BouncerSwipeModule.java
@@ -25,7 +25,7 @@ import com.android.systemui.R;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dreams.touch.BouncerSwipeTouchHandler;
import com.android.systemui.dreams.touch.DreamTouchHandler;
-import com.android.systemui.shade.PanelViewController;
+import com.android.systemui.shade.NotificationPanelViewController;
import com.android.wm.shell.animation.FlingAnimationUtils;
import javax.inject.Named;
@@ -77,8 +77,9 @@ public class BouncerSwipeModule {
Provider<FlingAnimationUtils.Builder> flingAnimationUtilsBuilderProvider) {
return flingAnimationUtilsBuilderProvider.get()
.reset()
- .setMaxLengthSeconds(PanelViewController.FLING_CLOSING_MAX_LENGTH_SECONDS)
- .setSpeedUpFactor(PanelViewController.FLING_SPEED_UP_FACTOR)
+ .setMaxLengthSeconds(
+ NotificationPanelViewController.FLING_CLOSING_MAX_LENGTH_SECONDS)
+ .setSpeedUpFactor(NotificationPanelViewController.FLING_SPEED_UP_FACTOR)
.build();
}
@@ -91,8 +92,8 @@ public class BouncerSwipeModule {
Provider<FlingAnimationUtils.Builder> flingAnimationUtilsBuilderProvider) {
return flingAnimationUtilsBuilderProvider.get()
.reset()
- .setMaxLengthSeconds(PanelViewController.FLING_MAX_LENGTH_SECONDS)
- .setSpeedUpFactor(PanelViewController.FLING_SPEED_UP_FACTOR)
+ .setMaxLengthSeconds(NotificationPanelViewController.FLING_MAX_LENGTH_SECONDS)
+ .setSpeedUpFactor(NotificationPanelViewController.FLING_SPEED_UP_FACTOR)
.build();
}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.java b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
index 0ee53cd68dbe..d8423c3c530c 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
@@ -103,6 +103,10 @@ public class Flags {
*/
public static final ReleasedFlag MODERN_BOUNCER = new ReleasedFlag(208);
+ /** Whether UserSwitcherActivity should use modern architecture. */
+ public static final UnreleasedFlag MODERN_USER_SWITCHER_ACTIVITY =
+ new UnreleasedFlag(209, true);
+
/***************************************/
// 300 - power menu
public static final ReleasedFlag POWER_MENU_LITE =
@@ -147,6 +151,8 @@ public class Flags {
public static final ResourceBooleanFlag FULL_SCREEN_USER_SWITCHER =
new ResourceBooleanFlag(506, R.bool.config_enableFullscreenUserSwitcher);
+ public static final UnreleasedFlag NEW_FOOTER_ACTIONS = new UnreleasedFlag(507, true);
+
/***************************************/
// 600- status bar
public static final ResourceBooleanFlag STATUS_BAR_USER_SWITCHER =
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
index 6ac3eadb838d..7c4c64c20089 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -814,8 +814,10 @@ public class EdgeBackGestureHandler extends CurrentUserTracker
}
mLogGesture = false;
String logPackageName = "";
+ Map<String, Integer> vocab = mVocab;
// Due to privacy, only top 100 most used apps by all users can be logged.
- if (mUseMLModel && mVocab.containsKey(mPackageName) && mVocab.get(mPackageName) < 100) {
+ if (mUseMLModel && vocab != null && vocab.containsKey(mPackageName)
+ && vocab.get(mPackageName) < 100) {
logPackageName = mPackageName;
}
SysUiStatsLog.write(SysUiStatsLog.BACK_GESTURE_REPORTED_REPORTED, backType,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt b/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt
index 0288c9fce64a..482a1397642b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt
@@ -40,11 +40,13 @@ import android.widget.Button
import android.widget.ImageView
import android.widget.TextView
import androidx.annotation.GuardedBy
+import androidx.annotation.VisibleForTesting
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.android.internal.config.sysui.SystemUiDeviceConfigFlags.TASK_MANAGER_ENABLED
import com.android.internal.config.sysui.SystemUiDeviceConfigFlags.TASK_MANAGER_SHOW_FOOTER_DOT
+import com.android.internal.config.sysui.SystemUiDeviceConfigFlags.TASK_MANAGER_SHOW_STOP_BUTTON_FOR_USER_ALLOWLISTED_APPS
import com.android.internal.jank.InteractionJankMonitor
import com.android.systemui.Dumpable
import com.android.systemui.R
@@ -66,9 +68,73 @@ import java.util.Objects
import java.util.concurrent.Executor
import javax.inject.Inject
import kotlin.math.max
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+/** A controller for the dealing with services running in the foreground. */
+interface FgsManagerController {
+ /** Whether the TaskManager (and therefore this controller) is actually available. */
+ val isAvailable: StateFlow<Boolean>
+
+ /** The number of packages with a service running in the foreground. */
+ val numRunningPackages: Int
+
+ /**
+ * Whether there were new changes to the foreground services since the last [shown][showDialog]
+ * dialog was dismissed.
+ */
+ val newChangesSinceDialogWasDismissed: Boolean
+
+ /**
+ * Whether we should show a dot to indicate when [newChangesSinceDialogWasDismissed] is true.
+ */
+ val showFooterDot: StateFlow<Boolean>
+
+ /**
+ * Initialize this controller. This should be called once, before this controller is used for
+ * the first time.
+ */
+ fun init()
+
+ /**
+ * Show the foreground services dialog. The dialog will be expanded from [viewLaunchedFrom] if
+ * it's not `null`.
+ */
+ fun showDialog(viewLaunchedFrom: View?)
+
+ /** Add a [OnNumberOfPackagesChangedListener]. */
+ fun addOnNumberOfPackagesChangedListener(listener: OnNumberOfPackagesChangedListener)
+
+ /** Remove a [OnNumberOfPackagesChangedListener]. */
+ fun removeOnNumberOfPackagesChangedListener(listener: OnNumberOfPackagesChangedListener)
+
+ /** Add a [OnDialogDismissedListener]. */
+ fun addOnDialogDismissedListener(listener: OnDialogDismissedListener)
+
+ /** Remove a [OnDialogDismissedListener]. */
+ fun removeOnDialogDismissedListener(listener: OnDialogDismissedListener)
+
+ /** Whether we should update the footer visibility. */
+ // TODO(b/242040009): Remove this.
+ fun shouldUpdateFooterVisibility(): Boolean
+
+ @VisibleForTesting
+ fun visibleButtonsCount(): Int
+
+ interface OnNumberOfPackagesChangedListener {
+ /** Called when [numRunningPackages] changed. */
+ fun onNumberOfPackagesChanged(numPackages: Int)
+ }
+
+ interface OnDialogDismissedListener {
+ /** Called when a dialog shown using [showDialog] was dismissed. */
+ fun onDialogDismissed()
+ }
+}
@SysUISingleton
-class FgsManagerController @Inject constructor(
+class FgsManagerControllerImpl @Inject constructor(
private val context: Context,
@Main private val mainExecutor: Executor,
@Background private val backgroundExecutor: Executor,
@@ -80,22 +146,32 @@ class FgsManagerController @Inject constructor(
private val dialogLaunchAnimator: DialogLaunchAnimator,
private val broadcastDispatcher: BroadcastDispatcher,
private val dumpManager: DumpManager
-) : IForegroundServiceObserver.Stub(), Dumpable {
+) : IForegroundServiceObserver.Stub(), Dumpable, FgsManagerController {
companion object {
private const val INTERACTION_JANK_TAG = "active_background_apps"
- private val LOG_TAG = FgsManagerController::class.java.simpleName
private const val DEFAULT_TASK_MANAGER_ENABLED = true
private const val DEFAULT_TASK_MANAGER_SHOW_FOOTER_DOT = false
+ private const val DEFAULT_TASK_MANAGER_SHOW_STOP_BUTTON_FOR_USER_ALLOWLISTED_APPS = true
}
- var changesSinceDialog = false
+ override var newChangesSinceDialogWasDismissed = false
private set
- var isAvailable = false
- private set
- var showFooterDot = false
- private set
+ val _isAvailable = MutableStateFlow(false)
+ override val isAvailable: StateFlow<Boolean> = _isAvailable.asStateFlow()
+
+ val _showFooterDot = MutableStateFlow(false)
+ override val showFooterDot: StateFlow<Boolean> = _showFooterDot.asStateFlow()
+
+ private var showStopBtnForUserAllowlistedApps = false
+
+ override val numRunningPackages: Int
+ get() {
+ synchronized(lock) {
+ return getNumVisiblePackagesLocked()
+ }
+ }
private val lock = Any()
@@ -133,15 +209,7 @@ class FgsManagerController @Inject constructor(
}
}
- interface OnNumberOfPackagesChangedListener {
- fun onNumberOfPackagesChanged(numPackages: Int)
- }
-
- interface OnDialogDismissedListener {
- fun onDialogDismissed()
- }
-
- fun init() {
+ override fun init() {
synchronized(lock) {
if (initialized) {
return
@@ -160,19 +228,26 @@ class FgsManagerController @Inject constructor(
NAMESPACE_SYSTEMUI,
backgroundExecutor
) {
- isAvailable = it.getBoolean(TASK_MANAGER_ENABLED, isAvailable)
- showFooterDot =
- it.getBoolean(TASK_MANAGER_SHOW_FOOTER_DOT, showFooterDot)
+ _isAvailable.value = it.getBoolean(TASK_MANAGER_ENABLED, _isAvailable.value)
+ _showFooterDot.value =
+ it.getBoolean(TASK_MANAGER_SHOW_FOOTER_DOT, _showFooterDot.value)
+ showStopBtnForUserAllowlistedApps = it.getBoolean(
+ TASK_MANAGER_SHOW_STOP_BUTTON_FOR_USER_ALLOWLISTED_APPS,
+ showStopBtnForUserAllowlistedApps)
}
- isAvailable = deviceConfigProxy.getBoolean(
+ _isAvailable.value = deviceConfigProxy.getBoolean(
NAMESPACE_SYSTEMUI,
TASK_MANAGER_ENABLED, DEFAULT_TASK_MANAGER_ENABLED
)
- showFooterDot = deviceConfigProxy.getBoolean(
+ _showFooterDot.value = deviceConfigProxy.getBoolean(
NAMESPACE_SYSTEMUI,
TASK_MANAGER_SHOW_FOOTER_DOT, DEFAULT_TASK_MANAGER_SHOW_FOOTER_DOT
)
+ showStopBtnForUserAllowlistedApps = deviceConfigProxy.getBoolean(
+ NAMESPACE_SYSTEMUI,
+ TASK_MANAGER_SHOW_STOP_BUTTON_FOR_USER_ALLOWLISTED_APPS,
+ DEFAULT_TASK_MANAGER_SHOW_STOP_BUTTON_FOR_USER_ALLOWLISTED_APPS)
dumpManager.registerDumpable(this)
@@ -220,42 +295,45 @@ class FgsManagerController @Inject constructor(
}
@GuardedBy("lock")
- val onNumberOfPackagesChangedListeners: MutableSet<OnNumberOfPackagesChangedListener> =
- mutableSetOf()
+ private val onNumberOfPackagesChangedListeners =
+ mutableSetOf<FgsManagerController.OnNumberOfPackagesChangedListener>()
@GuardedBy("lock")
- val onDialogDismissedListeners: MutableSet<OnDialogDismissedListener> = mutableSetOf()
+ private val onDialogDismissedListeners =
+ mutableSetOf<FgsManagerController.OnDialogDismissedListener>()
- fun addOnNumberOfPackagesChangedListener(listener: OnNumberOfPackagesChangedListener) {
+ override fun addOnNumberOfPackagesChangedListener(
+ listener: FgsManagerController.OnNumberOfPackagesChangedListener
+ ) {
synchronized(lock) {
onNumberOfPackagesChangedListeners.add(listener)
}
}
- fun removeOnNumberOfPackagesChangedListener(listener: OnNumberOfPackagesChangedListener) {
+ override fun removeOnNumberOfPackagesChangedListener(
+ listener: FgsManagerController.OnNumberOfPackagesChangedListener
+ ) {
synchronized(lock) {
onNumberOfPackagesChangedListeners.remove(listener)
}
}
- fun addOnDialogDismissedListener(listener: OnDialogDismissedListener) {
+ override fun addOnDialogDismissedListener(
+ listener: FgsManagerController.OnDialogDismissedListener
+ ) {
synchronized(lock) {
onDialogDismissedListeners.add(listener)
}
}
- fun removeOnDialogDismissedListener(listener: OnDialogDismissedListener) {
+ override fun removeOnDialogDismissedListener(
+ listener: FgsManagerController.OnDialogDismissedListener
+ ) {
synchronized(lock) {
onDialogDismissedListeners.remove(listener)
}
}
- fun getNumRunningPackages(): Int {
- synchronized(lock) {
- return getNumVisiblePackagesLocked()
- }
- }
-
private fun getNumVisiblePackagesLocked(): Int {
return runningServiceTokens.keys.count {
it.uiControl != UIControl.HIDE_ENTRY && currentProfileIds.contains(it.userId)
@@ -266,7 +344,7 @@ class FgsManagerController @Inject constructor(
val num = getNumVisiblePackagesLocked()
if (num != lastNumberOfVisiblePackages) {
lastNumberOfVisiblePackages = num
- changesSinceDialog = true
+ newChangesSinceDialogWasDismissed = true
onNumberOfPackagesChangedListeners.forEach {
backgroundExecutor.execute {
it.onNumberOfPackagesChanged(num)
@@ -275,9 +353,21 @@ class FgsManagerController @Inject constructor(
}
}
- fun shouldUpdateFooterVisibility() = dialog == null
+ override fun visibleButtonsCount(): Int {
+ synchronized(lock) {
+ return getNumVisibleButtonsLocked()
+ }
+ }
+
+ private fun getNumVisibleButtonsLocked(): Int {
+ return runningServiceTokens.keys.count {
+ it.uiControl != UIControl.HIDE_BUTTON && currentProfileIds.contains(it.userId)
+ }
+ }
- fun showDialog(viewLaunchedFrom: View?) {
+ override fun shouldUpdateFooterVisibility() = dialog == null
+
+ override fun showDialog(viewLaunchedFrom: View?) {
synchronized(lock) {
if (dialog == null) {
@@ -302,7 +392,7 @@ class FgsManagerController @Inject constructor(
this.dialog = dialog
dialog.setOnDismissListener {
- changesSinceDialog = false
+ newChangesSinceDialogWasDismissed = false
synchronized(lock) {
this.dialog = null
updateAppItemsLocked()
@@ -505,6 +595,13 @@ class FgsManagerController @Inject constructor(
PowerExemptionManager.REASON_PROC_STATE_PERSISTENT_UI,
PowerExemptionManager.REASON_ROLE_DIALER,
PowerExemptionManager.REASON_SYSTEM_MODULE -> UIControl.HIDE_BUTTON
+
+ PowerExemptionManager.REASON_ALLOWLISTED_PACKAGE ->
+ if (showStopBtnForUserAllowlistedApps) {
+ UIControl.NORMAL
+ } else {
+ UIControl.HIDE_BUTTON
+ }
else -> UIControl.NORMAL
}
uiControlInitialized = true
@@ -623,7 +720,7 @@ class FgsManagerController @Inject constructor(
val pw = IndentingPrintWriter(printwriter)
synchronized(lock) {
pw.println("current user profiles = $currentProfileIds")
- pw.println("changesSinceDialog=$changesSinceDialog")
+ pw.println("newChangesSinceDialogWasShown=$newChangesSinceDialogWasDismissed")
pw.println("Running service tokens: [")
pw.indentIfPossible {
runningServiceTokens.forEach { (userPackage, startTimeAndTokens) ->
diff --git a/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt b/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt
index c790cfe7b7b7..9d64781ef2e9 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt
@@ -56,6 +56,7 @@ import javax.inject.Provider
* determined by [buttonsVisibleState]
*/
@QSScope
+// TODO(b/242040009): Remove this file.
internal class FooterActionsController @Inject constructor(
view: FooterActionsView,
multiUserSwitchControllerFactory: MultiUserSwitchController.Factory,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/FooterActionsView.kt b/packages/SystemUI/src/com/android/systemui/qs/FooterActionsView.kt
index 309ac2a66e6b..d602b0b27977 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/FooterActionsView.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/FooterActionsView.kt
@@ -38,6 +38,7 @@ import com.android.systemui.statusbar.phone.MultiUserSwitch
* in split shade mode visible also in collapsed state. May contain up to 5 buttons: settings,
* edit tiles, power off and conditionally: user switch and tuner
*/
+// TODO(b/242040009): Remove this file.
class FooterActionsView(context: Context?, attrs: AttributeSet?) : LinearLayout(context, attrs) {
private lateinit var settingsContainer: View
private lateinit var multiUserSwitch: MultiUserSwitch
diff --git a/packages/SystemUI/src/com/android/systemui/qs/NewFooterActionsController.kt b/packages/SystemUI/src/com/android/systemui/qs/NewFooterActionsController.kt
new file mode 100644
index 000000000000..7c67d9f42b55
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/NewFooterActionsController.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2022 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.qs
+
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+
+/** Controller for the footer actions. This manages the initialization of its dependencies. */
+@SysUISingleton
+class NewFooterActionsController
+@Inject
+// TODO(b/242040009): Rename this to FooterActionsController.
+constructor(
+ private val fgsManagerController: FgsManagerController,
+) {
+ fun init() {
+ fgsManagerController.init()
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFgsManagerFooter.java b/packages/SystemUI/src/com/android/systemui/qs/QSFgsManagerFooter.java
index 875493d73f9e..7511278e0919 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFgsManagerFooter.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFgsManagerFooter.java
@@ -41,6 +41,7 @@ import javax.inject.Named;
/**
* Footer entry point for the foreground service manager
*/
+// TODO(b/242040009): Remove this file.
@QSScope
public class QSFgsManagerFooter implements View.OnClickListener,
FgsManagerController.OnDialogDismissedListener,
@@ -149,9 +150,11 @@ public class QSFgsManagerFooter implements View.OnClickListener,
mNumberView.setContentDescription(text);
if (mFgsManagerController.shouldUpdateFooterVisibility()) {
mRootView.setVisibility(mNumPackages > 0
- && mFgsManagerController.isAvailable() ? View.VISIBLE : View.GONE);
- int dotVis = mFgsManagerController.getShowFooterDot()
- && mFgsManagerController.getChangesSinceDialog() ? View.VISIBLE : View.GONE;
+ && mFgsManagerController.isAvailable().getValue() ? View.VISIBLE
+ : View.GONE);
+ int dotVis = mFgsManagerController.getShowFooterDot().getValue()
+ && mFgsManagerController.getNewChangesSinceDialogWasDismissed()
+ ? View.VISIBLE : View.GONE;
mDotView.setVisibility(dotVis);
mCollapsedDotView.setVisibility(dotVis);
if (mVisibilityChangedListener != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
index 139fb8b0bc14..05b3eae1d2f2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
@@ -36,6 +36,9 @@ import android.view.ViewTreeObserver;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
+import androidx.lifecycle.Lifecycle;
+import androidx.lifecycle.LifecycleOwner;
+import androidx.lifecycle.LifecycleRegistry;
import com.android.keyguard.BouncerPanelExpansionCalculator;
import com.android.systemui.Dumpable;
@@ -43,6 +46,8 @@ import com.android.systemui.R;
import com.android.systemui.animation.Interpolators;
import com.android.systemui.animation.ShadeInterpolation;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
import com.android.systemui.media.MediaHost;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.qs.QS;
@@ -50,6 +55,8 @@ import com.android.systemui.plugins.qs.QSContainerController;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.qs.customize.QSCustomizerController;
import com.android.systemui.qs.dagger.QSFragmentComponent;
+import com.android.systemui.qs.footer.ui.binder.FooterActionsViewBinder;
+import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
@@ -104,6 +111,10 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
private final QSFragmentComponent.Factory mQsComponentFactory;
private final QSFragmentDisableFlagsLogger mQsFragmentDisableFlagsLogger;
private final QSTileHost mHost;
+ private final FeatureFlags mFeatureFlags;
+ private final NewFooterActionsController mNewFooterActionsController;
+ private final FooterActionsViewModel.Factory mFooterActionsViewModelFactory;
+ private final ListeningAndVisibilityLifecycleOwner mListeningAndVisibilityLifecycleOwner;
private boolean mShowCollapsedOnKeyguard;
private boolean mLastKeyguardAndExpanded;
/**
@@ -119,8 +130,11 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
private QSPanelController mQSPanelController;
private QuickQSPanelController mQuickQSPanelController;
private QSCustomizerController mQSCustomizerController;
+ @Nullable
private FooterActionsController mQSFooterActionController;
@Nullable
+ private FooterActionsViewModel mQSFooterActionsViewModel;
+ @Nullable
private ScrollListener mScrollListener;
/**
* When true, QS will translate from outside the screen. It will be clipped with parallax
@@ -161,7 +175,9 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
KeyguardBypassController keyguardBypassController,
QSFragmentComponent.Factory qsComponentFactory,
QSFragmentDisableFlagsLogger qsFragmentDisableFlagsLogger,
- FalsingManager falsingManager, DumpManager dumpManager) {
+ FalsingManager falsingManager, DumpManager dumpManager, FeatureFlags featureFlags,
+ NewFooterActionsController newFooterActionsController,
+ FooterActionsViewModel.Factory footerActionsViewModelFactory) {
mRemoteInputQuickSettingsDisabler = remoteInputQsDisabler;
mQsMediaHost = qsMediaHost;
mQqsMediaHost = qqsMediaHost;
@@ -173,6 +189,10 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
mBypassController = keyguardBypassController;
mStatusBarStateController = statusBarStateController;
mDumpManager = dumpManager;
+ mFeatureFlags = featureFlags;
+ mNewFooterActionsController = newFooterActionsController;
+ mFooterActionsViewModelFactory = footerActionsViewModelFactory;
+ mListeningAndVisibilityLifecycleOwner = new ListeningAndVisibilityLifecycleOwner();
}
@Override
@@ -193,11 +213,22 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
QSFragmentComponent qsFragmentComponent = mQsComponentFactory.create(this);
mQSPanelController = qsFragmentComponent.getQSPanelController();
mQuickQSPanelController = qsFragmentComponent.getQuickQSPanelController();
- mQSFooterActionController = qsFragmentComponent.getQSFooterActionController();
mQSPanelController.init();
mQuickQSPanelController.init();
- mQSFooterActionController.init();
+
+ if (mFeatureFlags.isEnabled(Flags.NEW_FOOTER_ACTIONS)) {
+ mQSFooterActionsViewModel = mFooterActionsViewModelFactory.create(/* lifecycleOwner */
+ this);
+ FooterActionsView footerActionsView = view.findViewById(R.id.qs_footer_actions);
+ FooterActionsViewBinder.bind(footerActionsView, mQSFooterActionsViewModel,
+ mListeningAndVisibilityLifecycleOwner);
+
+ mNewFooterActionsController.init();
+ } else {
+ mQSFooterActionController = qsFragmentComponent.getQSFooterActionController();
+ mQSFooterActionController.init();
+ }
mQSPanelScrollView = view.findViewById(R.id.expanded_qs_scroll_view);
mQSPanelScrollView.addOnLayoutChangeListener(
@@ -283,6 +314,7 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
mDumpManager.unregisterDumpable(mContainer.getClass().getName());
}
mDumpManager.unregisterDumpable(getClass().getName());
+ mListeningAndVisibilityLifecycleOwner.destroy();
}
@Override
@@ -395,7 +427,9 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
mContainer.disable(state1, state2, animate);
mHeader.disable(state1, state2, animate);
mFooter.disable(state1, state2, animate);
- mQSFooterActionController.disable(state2);
+ if (mQSFooterActionController != null) {
+ mQSFooterActionController.disable(state2);
+ }
updateQsState();
}
@@ -415,7 +449,11 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
boolean footerVisible = qsPanelVisible && (expanded || !keyguardShowing || mHeaderAnimating
|| mShowCollapsedOnKeyguard);
mFooter.setVisibility(footerVisible ? View.VISIBLE : View.INVISIBLE);
- mQSFooterActionController.setVisible(footerVisible);
+ if (mQSFooterActionController != null) {
+ mQSFooterActionController.setVisible(footerVisible);
+ } else {
+ mQSFooterActionsViewModel.onVisibilityChangeRequested(footerVisible);
+ }
mFooter.setExpanded((keyguardShowing && !mHeaderAnimating && !mShowCollapsedOnKeyguard)
|| (expanded && !mStackScrollerOverscrolling));
mQSPanelController.setVisibility(qsPanelVisible ? View.VISIBLE : View.INVISIBLE);
@@ -482,7 +520,9 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
}
mFooter.setKeyguardShowing(keyguardShowing);
- mQSFooterActionController.setKeyguardShowing(keyguardShowing);
+ if (mQSFooterActionController != null) {
+ mQSFooterActionController.setKeyguardShowing(keyguardShowing);
+ }
updateQsState();
}
@@ -498,7 +538,10 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
if (DEBUG) Log.d(TAG, "setListening " + listening);
mListening = listening;
mQSContainerImplController.setListening(listening && mQsVisible);
- mQSFooterActionController.setListening(listening && mQsVisible);
+ if (mQSFooterActionController != null) {
+ mQSFooterActionController.setListening(listening && mQsVisible);
+ }
+ mListeningAndVisibilityLifecycleOwner.updateState();
updateQsPanelControllerListening();
}
@@ -511,6 +554,7 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
if (DEBUG) Log.d(TAG, "setQsVisible " + visible);
mQsVisible = visible;
setListening(mListening);
+ mListeningAndVisibilityLifecycleOwner.updateState();
}
@Override
@@ -602,7 +646,12 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
mFooter.setExpansion(onKeyguardAndExpanded ? 1 : expansion);
float footerActionsExpansion =
onKeyguardAndExpanded ? 1 : mInSplitShade ? alphaProgress : expansion;
- mQSFooterActionController.setExpansion(footerActionsExpansion);
+ if (mQSFooterActionController != null) {
+ mQSFooterActionController.setExpansion(footerActionsExpansion);
+ } else {
+ mQSFooterActionsViewModel.onQuickSettingsExpansionChanged(footerActionsExpansion,
+ mInSplitShade);
+ }
mQSPanelController.setRevealExpansion(expansion);
mQSPanelController.getTileLayout().setExpansion(expansion, proposedTranslation);
mQuickQSPanelController.getTileLayout().setExpansion(expansion, proposedTranslation);
@@ -714,7 +763,11 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
boolean customizing = isCustomizing();
mQSPanelScrollView.setVisibility(!customizing ? View.VISIBLE : View.INVISIBLE);
mFooter.setVisibility(!customizing ? View.VISIBLE : View.INVISIBLE);
- mQSFooterActionController.setVisible(!customizing);
+ if (mQSFooterActionController != null) {
+ mQSFooterActionController.setVisible(!customizing);
+ } else {
+ mQSFooterActionsViewModel.onVisibilityChangeRequested(!customizing);
+ }
mHeader.setVisibility(!customizing ? View.VISIBLE : View.INVISIBLE);
// Let the panel know the position changed and it needs to update where notifications
// and whatnot are.
@@ -860,4 +913,56 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
}
return "GONE";
}
+
+ /**
+ * A {@link LifecycleOwner} whose state is driven by the current state of this fragment:
+ *
+ * - DESTROYED when the fragment is destroyed.
+ * - CREATED when mListening == mQsVisible == false.
+ * - STARTED when mListening == true && mQsVisible == false.
+ * - RESUMED when mListening == true && mQsVisible == true.
+ */
+ private class ListeningAndVisibilityLifecycleOwner implements LifecycleOwner {
+ private final LifecycleRegistry mLifecycleRegistry = new LifecycleRegistry(this);
+ private boolean mDestroyed = false;
+
+ {
+ updateState();
+ }
+
+ @Override
+ public Lifecycle getLifecycle() {
+ return mLifecycleRegistry;
+ }
+
+ /**
+ * Update the state of the associated lifecycle. This should be called whenever
+ * {@code mListening} or {@code mQsVisible} is changed.
+ */
+ public void updateState() {
+ if (mDestroyed) {
+ mLifecycleRegistry.setCurrentState(Lifecycle.State.DESTROYED);
+ return;
+ }
+
+ if (!mListening) {
+ mLifecycleRegistry.setCurrentState(Lifecycle.State.CREATED);
+ return;
+ }
+
+ // mListening && !mQsVisible.
+ if (!mQsVisible) {
+ mLifecycleRegistry.setCurrentState(Lifecycle.State.STARTED);
+ return;
+ }
+
+ // mListening && mQsVisible.
+ mLifecycleRegistry.setCurrentState(Lifecycle.State.RESUMED);
+ }
+
+ public void destroy() {
+ mDestroyed = true;
+ updateState();
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java b/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java
index 87fcce455ea6..b20d7ba33397 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java
@@ -15,126 +15,66 @@
*/
package com.android.systemui.qs;
-import static android.app.admin.DevicePolicyManager.DEVICE_OWNER_TYPE_FINANCED;
-import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_DIALOG_MANAGEMENT;
-import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_DIALOG_MANAGEMENT_CA_CERT;
-import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_DIALOG_MANAGEMENT_NAMED_VPN;
-import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_DIALOG_MANAGEMENT_NETWORK;
-import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_DIALOG_MANAGEMENT_TITLE;
-import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_DIALOG_MANAGEMENT_TWO_NAMED_VPN;
-import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_DIALOG_MONITORING_CA_CERT_SUBTITLE;
-import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_DIALOG_MONITORING_NETWORK_SUBTITLE;
-import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_DIALOG_MONITORING_VPN_SUBTITLE;
-import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_DIALOG_NAMED_MANAGEMENT;
-import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_DIALOG_PERSONAL_PROFILE_NAMED_VPN;
-import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_DIALOG_VIEW_POLICIES;
-import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_DIALOG_WORK_PROFILE_CA_CERT;
-import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_DIALOG_WORK_PROFILE_NAMED_VPN;
-import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_DIALOG_WORK_PROFILE_NETWORK;
-import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_MSG_MANAGEMENT;
-import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_MSG_MANAGEMENT_MONITORING;
-import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_MSG_MANAGEMENT_MULTIPLE_VPNS;
-import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_MSG_MANAGEMENT_NAMED_VPN;
-import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_MSG_NAMED_MANAGEMENT;
-import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_MSG_NAMED_MANAGEMENT_MONITORING;
-import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_MSG_NAMED_MANAGEMENT_MULTIPLE_VPNS;
-import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_MSG_NAMED_MANAGEMENT_NAMED_VPN;
-import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_MSG_NAMED_WORK_PROFILE_MONITORING;
-import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_MSG_PERSONAL_PROFILE_NAMED_VPN;
-import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_MSG_WORK_PROFILE_MONITORING;
-import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_MSG_WORK_PROFILE_NAMED_VPN;
-import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_MSG_WORK_PROFILE_NETWORK;
-
import static com.android.systemui.qs.dagger.QSFragmentModule.QS_SECURITY_FOOTER_VIEW;
-import android.app.AlertDialog;
-import android.app.Dialog;
-import android.app.admin.DeviceAdminInfo;
import android.app.admin.DevicePolicyEventLogger;
import android.app.admin.DevicePolicyManager;
import android.content.BroadcastReceiver;
import android.content.Context;
-import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
-import android.content.pm.UserInfo;
import android.content.res.Resources;
-import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.UserHandle;
-import android.os.UserManager;
-import android.provider.Settings;
-import android.text.SpannableStringBuilder;
-import android.text.method.LinkMovementMethod;
-import android.text.style.ClickableSpan;
import android.util.Log;
-import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
-import android.view.Window;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.Nullable;
-import androidx.annotation.VisibleForTesting;
-import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.util.FrameworkStatsLog;
import com.android.systemui.FontSizeUtils;
import com.android.systemui.R;
-import com.android.systemui.animation.DialogCuj;
-import com.android.systemui.animation.DialogLaunchAnimator;
import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.common.shared.model.Icon;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.qs.dagger.QSScope;
-import com.android.systemui.settings.UserTracker;
-import com.android.systemui.statusbar.phone.SystemUIDialog;
+import com.android.systemui.qs.footer.domain.model.SecurityButtonConfig;
+import com.android.systemui.security.data.model.SecurityModel;
import com.android.systemui.statusbar.policy.SecurityController;
import com.android.systemui.util.ViewController;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.function.Supplier;
-
import javax.inject.Inject;
import javax.inject.Named;
+/** ViewController for the footer actions. */
+// TODO(b/242040009): Remove this class.
@QSScope
-class QSSecurityFooter extends ViewController<View>
- implements OnClickListener, DialogInterface.OnClickListener,
- VisibilityChangedDispatcher {
+public class QSSecurityFooter extends ViewController<View>
+ implements OnClickListener, VisibilityChangedDispatcher {
protected static final String TAG = "QSSecurityFooter";
- protected static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
- private static final boolean DEBUG_FORCE_VISIBLE = false;
-
- private static final String INTERACTION_JANK_TAG = "managed_device_info";
private final TextView mFooterText;
private final ImageView mPrimaryFooterIcon;
private Context mContext;
- private final DevicePolicyManager mDpm;
private final Callback mCallback = new Callback();
private final SecurityController mSecurityController;
- private final ActivityStarter mActivityStarter;
private final Handler mMainHandler;
- private final UserTracker mUserTracker;
- private final DialogLaunchAnimator mDialogLaunchAnimator;
private final BroadcastDispatcher mBroadcastDispatcher;
+ private final QSSecurityFooterUtils mQSSecurityFooterUtils;
- private final AtomicBoolean mShouldUseSettingsButton = new AtomicBoolean(false);
-
- private AlertDialog mDialog;
protected H mHandler;
private boolean mIsVisible;
+ private boolean mIsClickable;
@Nullable
private CharSequence mFooterTextContent = null;
- private int mFooterIconId;
- @Nullable
- private Drawable mPrimaryFooterIconDrawable;
+ private Icon mFooterIcon;
@Nullable
private VisibilityChangedDispatcher.OnVisibilityChangedListener mVisibilityChangedListener;
@@ -149,82 +89,21 @@ class QSSecurityFooter extends ViewController<View>
}
};
- private Supplier<String> mManagementTitleSupplier = () ->
- mContext == null ? null : mContext.getString(R.string.monitoring_title_device_owned);
-
- private Supplier<String> mManagementMessageSupplier = () ->
- mContext == null ? null : mContext.getString(
- R.string.quick_settings_disclosure_management);
-
- private Supplier<String> mManagementMonitoringStringSupplier = () ->
- mContext == null ? null : mContext.getString(
- R.string.quick_settings_disclosure_management_monitoring);
-
- private Supplier<String> mManagementMultipleVpnStringSupplier = () ->
- mContext == null ? null : mContext.getString(
- R.string.quick_settings_disclosure_management_vpns);
-
- private Supplier<String> mWorkProfileMonitoringStringSupplier = () ->
- mContext == null ? null : mContext.getString(
- R.string.quick_settings_disclosure_managed_profile_monitoring);
-
- private Supplier<String> mWorkProfileNetworkStringSupplier = () ->
- mContext == null ? null : mContext.getString(
- R.string.quick_settings_disclosure_managed_profile_network_activity);
-
- private Supplier<String> mMonitoringSubtitleCaCertStringSupplier = () ->
- mContext == null ? null : mContext.getString(
- R.string.monitoring_subtitle_ca_certificate);
-
- private Supplier<String> mMonitoringSubtitleNetworkStringSupplier = () ->
- mContext == null ? null : mContext.getString(
- R.string.monitoring_subtitle_network_logging);
-
- private Supplier<String> mMonitoringSubtitleVpnStringSupplier = () ->
- mContext == null ? null : mContext.getString(R.string.monitoring_subtitle_vpn);
-
- private Supplier<String> mViewPoliciesButtonStringSupplier = () ->
- mContext == null ? null : mContext.getString(R.string.monitoring_button_view_policies);
-
- private Supplier<String> mManagementDialogStringSupplier = () ->
- mContext == null ? null : mContext.getString(
- R.string.monitoring_description_management);
-
- private Supplier<String> mManagementDialogCaCertStringSupplier = () ->
- mContext == null ? null : mContext.getString(
- R.string.monitoring_description_management_ca_certificate);
-
- private Supplier<String> mWorkProfileDialogCaCertStringSupplier = () ->
- mContext == null ? null : mContext.getString(
- R.string.monitoring_description_managed_profile_ca_certificate);
-
- private Supplier<String> mManagementDialogNetworkStringSupplier = () ->
- mContext == null ? null : mContext.getString(
- R.string.monitoring_description_management_network_logging);
-
- private Supplier<String> mWorkProfileDialogNetworkStringSupplier = () ->
- mContext == null ? null : mContext.getString(
- R.string.monitoring_description_managed_profile_network_logging);
-
@Inject
QSSecurityFooter(@Named(QS_SECURITY_FOOTER_VIEW) View rootView,
- UserTracker userTracker, @Main Handler mainHandler,
- ActivityStarter activityStarter, SecurityController securityController,
- DialogLaunchAnimator dialogLaunchAnimator, @Background Looper bgLooper,
- BroadcastDispatcher broadcastDispatcher) {
+ @Main Handler mainHandler, SecurityController securityController,
+ @Background Looper bgLooper, BroadcastDispatcher broadcastDispatcher,
+ QSSecurityFooterUtils qSSecurityFooterUtils) {
super(rootView);
mFooterText = mView.findViewById(R.id.footer_text);
mPrimaryFooterIcon = mView.findViewById(R.id.primary_footer_icon);
- mFooterIconId = R.drawable.ic_info_outline;
+ mFooterIcon = new Icon.Resource(R.drawable.ic_info_outline);
mContext = rootView.getContext();
- mDpm = rootView.getContext().getSystemService(DevicePolicyManager.class);
- mMainHandler = mainHandler;
- mActivityStarter = activityStarter;
mSecurityController = securityController;
+ mMainHandler = mainHandler;
mHandler = new H(bgLooper);
- mUserTracker = userTracker;
- mDialogLaunchAnimator = dialogLaunchAnimator;
mBroadcastDispatcher = broadcastDispatcher;
+ mQSSecurityFooterUtils = qSSecurityFooterUtils;
}
@Override
@@ -287,8 +166,9 @@ class QSSecurityFooter extends ViewController<View>
.write();
}
+ // TODO(b/242040009): Remove this.
public void showDeviceMonitoringDialog() {
- createDialog();
+ mQSSecurityFooterUtils.showDeviceMonitoringDialog(mContext, mView);
}
public void refreshState() {
@@ -296,590 +176,30 @@ class QSSecurityFooter extends ViewController<View>
}
private void handleRefreshState() {
- final boolean isDeviceManaged = mSecurityController.isDeviceManaged();
- final UserInfo currentUser = mUserTracker.getUserInfo();
- final boolean isDemoDevice = UserManager.isDeviceInDemoMode(mContext) && currentUser != null
- && currentUser.isDemo();
- final boolean hasWorkProfile = mSecurityController.hasWorkProfile();
- final boolean hasCACerts = mSecurityController.hasCACertInCurrentUser();
- final boolean hasCACertsInWorkProfile = mSecurityController.hasCACertInWorkProfile();
- final boolean isNetworkLoggingEnabled = mSecurityController.isNetworkLoggingEnabled();
- final String vpnName = mSecurityController.getPrimaryVpnName();
- final String vpnNameWorkProfile = mSecurityController.getWorkProfileVpnName();
- final CharSequence organizationName = mSecurityController.getDeviceOwnerOrganizationName();
- final CharSequence workProfileOrganizationName =
- mSecurityController.getWorkProfileOrganizationName();
- final boolean isProfileOwnerOfOrganizationOwnedDevice =
- mSecurityController.isProfileOwnerOfOrganizationOwnedDevice();
- final boolean isParentalControlsEnabled = mSecurityController.isParentalControlsEnabled();
- final boolean isWorkProfileOn = mSecurityController.isWorkProfileOn();
- final boolean hasDisclosableWorkProfilePolicy = hasCACertsInWorkProfile
- || vpnNameWorkProfile != null || (hasWorkProfile && isNetworkLoggingEnabled);
- // Update visibility of footer
- mIsVisible = (isDeviceManaged && !isDemoDevice)
- || hasCACerts
- || vpnName != null
- || isProfileOwnerOfOrganizationOwnedDevice
- || isParentalControlsEnabled
- || (hasDisclosableWorkProfilePolicy && isWorkProfileOn);
- // Update the view to be untappable if the device is an organization-owned device with a
- // managed profile and there is either:
- // a) no policy set which requires a privacy disclosure.
- // b) a specific work policy set but the work profile is turned off.
- if (mIsVisible && isProfileOwnerOfOrganizationOwnedDevice
- && (!hasDisclosableWorkProfilePolicy || !isWorkProfileOn)) {
- mView.setClickable(false);
- mView.findViewById(R.id.footer_icon).setVisibility(View.GONE);
- } else {
- mView.setClickable(true);
- mView.findViewById(R.id.footer_icon).setVisibility(View.VISIBLE);
- }
- // Update the string
- mFooterTextContent = getFooterText(isDeviceManaged, hasWorkProfile,
- hasCACerts, hasCACertsInWorkProfile, isNetworkLoggingEnabled, vpnName,
- vpnNameWorkProfile, organizationName, workProfileOrganizationName,
- isProfileOwnerOfOrganizationOwnedDevice, isParentalControlsEnabled,
- isWorkProfileOn);
- // Update the icon
- int footerIconId = R.drawable.ic_info_outline;
- if (vpnName != null || vpnNameWorkProfile != null) {
- if (mSecurityController.isVpnBranded()) {
- footerIconId = R.drawable.stat_sys_branded_vpn;
- } else {
- footerIconId = R.drawable.stat_sys_vpn_ic;
- }
- }
- if (mFooterIconId != footerIconId) {
- mFooterIconId = footerIconId;
- }
+ SecurityModel securityModel = SecurityModel.create(mSecurityController);
+ SecurityButtonConfig buttonConfig = mQSSecurityFooterUtils.getButtonConfig(securityModel);
- // Update the primary icon
- if (isParentalControlsEnabled) {
- if (mPrimaryFooterIconDrawable == null) {
- DeviceAdminInfo info = mSecurityController.getDeviceAdminInfo();
- mPrimaryFooterIconDrawable = mSecurityController.getIcon(info);
- }
+ if (buttonConfig == null) {
+ mIsVisible = false;
} else {
- mPrimaryFooterIconDrawable = null;
+ mIsVisible = true;
+ mIsClickable = buttonConfig.isClickable();
+ mFooterTextContent = buttonConfig.getText();
+ mFooterIcon = buttonConfig.getIcon();
}
- mMainHandler.post(mUpdatePrimaryIcon);
+ // Update the UI.
+ mMainHandler.post(mUpdatePrimaryIcon);
mMainHandler.post(mUpdateDisplayState);
}
- @Nullable
- protected CharSequence getFooterText(boolean isDeviceManaged, boolean hasWorkProfile,
- boolean hasCACerts, boolean hasCACertsInWorkProfile, boolean isNetworkLoggingEnabled,
- String vpnName, String vpnNameWorkProfile, CharSequence organizationName,
- CharSequence workProfileOrganizationName,
- boolean isProfileOwnerOfOrganizationOwnedDevice, boolean isParentalControlsEnabled,
- boolean isWorkProfileOn) {
- if (isParentalControlsEnabled) {
- return mContext.getString(R.string.quick_settings_disclosure_parental_controls);
- }
- if (isDeviceManaged || DEBUG_FORCE_VISIBLE) {
- return getManagedDeviceFooterText(hasCACerts, hasCACertsInWorkProfile,
- isNetworkLoggingEnabled, vpnName, vpnNameWorkProfile, organizationName);
- }
- return getManagedAndPersonalProfileFooterText(hasWorkProfile, hasCACerts,
- hasCACertsInWorkProfile, isNetworkLoggingEnabled, vpnName, vpnNameWorkProfile,
- workProfileOrganizationName, isProfileOwnerOfOrganizationOwnedDevice,
- isWorkProfileOn);
- }
-
- private String getManagedDeviceFooterText(
- boolean hasCACerts, boolean hasCACertsInWorkProfile, boolean isNetworkLoggingEnabled,
- String vpnName, String vpnNameWorkProfile, CharSequence organizationName) {
- if (hasCACerts || hasCACertsInWorkProfile || isNetworkLoggingEnabled) {
- return getManagedDeviceMonitoringText(organizationName);
- }
- if (vpnName != null || vpnNameWorkProfile != null) {
- return getManagedDeviceVpnText(vpnName, vpnNameWorkProfile, organizationName);
- }
- return getMangedDeviceGeneralText(organizationName);
- }
-
- private String getManagedDeviceMonitoringText(CharSequence organizationName) {
- if (organizationName == null) {
- return mDpm.getResources().getString(
- QS_MSG_MANAGEMENT_MONITORING, mManagementMonitoringStringSupplier);
- }
- return mDpm.getResources().getString(
- QS_MSG_NAMED_MANAGEMENT_MONITORING,
- () -> mContext.getString(
- R.string.quick_settings_disclosure_named_management_monitoring,
- organizationName),
- organizationName);
- }
-
- private String getManagedDeviceVpnText(
- String vpnName, String vpnNameWorkProfile, CharSequence organizationName) {
- if (vpnName != null && vpnNameWorkProfile != null) {
- if (organizationName == null) {
- return mDpm.getResources().getString(
- QS_MSG_MANAGEMENT_MULTIPLE_VPNS, mManagementMultipleVpnStringSupplier);
- }
- return mDpm.getResources().getString(
- QS_MSG_NAMED_MANAGEMENT_MULTIPLE_VPNS,
- () -> mContext.getString(
- R.string.quick_settings_disclosure_named_management_vpns,
- organizationName),
- organizationName);
- }
- String name = vpnName != null ? vpnName : vpnNameWorkProfile;
- if (organizationName == null) {
- return mDpm.getResources().getString(
- QS_MSG_MANAGEMENT_NAMED_VPN,
- () -> mContext.getString(
- R.string.quick_settings_disclosure_management_named_vpn,
- name),
- name);
- }
- return mDpm.getResources().getString(
- QS_MSG_NAMED_MANAGEMENT_NAMED_VPN,
- () -> mContext.getString(
- R.string.quick_settings_disclosure_named_management_named_vpn,
- organizationName,
- name),
- organizationName,
- name);
- }
-
- private String getMangedDeviceGeneralText(CharSequence organizationName) {
- if (organizationName == null) {
- return mDpm.getResources().getString(QS_MSG_MANAGEMENT, mManagementMessageSupplier);
- }
- if (isFinancedDevice()) {
- return mContext.getString(
- R.string.quick_settings_financed_disclosure_named_management,
- organizationName);
- } else {
- return mDpm.getResources().getString(
- QS_MSG_NAMED_MANAGEMENT,
- () -> mContext.getString(
- R.string.quick_settings_disclosure_named_management,
- organizationName),
- organizationName);
- }
- }
-
- private String getManagedAndPersonalProfileFooterText(boolean hasWorkProfile,
- boolean hasCACerts, boolean hasCACertsInWorkProfile, boolean isNetworkLoggingEnabled,
- String vpnName, String vpnNameWorkProfile, CharSequence workProfileOrganizationName,
- boolean isProfileOwnerOfOrganizationOwnedDevice, boolean isWorkProfileOn) {
- if (hasCACerts || (hasCACertsInWorkProfile && isWorkProfileOn)) {
- return getMonitoringText(
- hasCACerts, hasCACertsInWorkProfile, workProfileOrganizationName,
- isWorkProfileOn);
- }
- if (vpnName != null || (vpnNameWorkProfile != null && isWorkProfileOn)) {
- return getVpnText(hasWorkProfile, vpnName, vpnNameWorkProfile, isWorkProfileOn);
- }
- if (hasWorkProfile && isNetworkLoggingEnabled && isWorkProfileOn) {
- return getManagedProfileNetworkActivityText();
- }
- if (isProfileOwnerOfOrganizationOwnedDevice) {
- return getMangedDeviceGeneralText(workProfileOrganizationName);
- }
- return null;
- }
-
- private String getMonitoringText(boolean hasCACerts, boolean hasCACertsInWorkProfile,
- CharSequence workProfileOrganizationName, boolean isWorkProfileOn) {
- if (hasCACertsInWorkProfile && isWorkProfileOn) {
- if (workProfileOrganizationName == null) {
- return mDpm.getResources().getString(
- QS_MSG_WORK_PROFILE_MONITORING, mWorkProfileMonitoringStringSupplier);
- }
- return mDpm.getResources().getString(
- QS_MSG_NAMED_WORK_PROFILE_MONITORING,
- () -> mContext.getString(
- R.string.quick_settings_disclosure_named_managed_profile_monitoring,
- workProfileOrganizationName),
- workProfileOrganizationName);
- }
- if (hasCACerts) {
- return mContext.getString(R.string.quick_settings_disclosure_monitoring);
- }
- return null;
- }
-
- private String getVpnText(boolean hasWorkProfile, String vpnName, String vpnNameWorkProfile,
- boolean isWorkProfileOn) {
- if (vpnName != null && vpnNameWorkProfile != null) {
- return mContext.getString(R.string.quick_settings_disclosure_vpns);
- }
- if (vpnNameWorkProfile != null && isWorkProfileOn) {
- return mDpm.getResources().getString(
- QS_MSG_WORK_PROFILE_NAMED_VPN,
- () -> mContext.getString(
- R.string.quick_settings_disclosure_managed_profile_named_vpn,
- vpnNameWorkProfile),
- vpnNameWorkProfile);
- }
- if (vpnName != null) {
- if (hasWorkProfile) {
- return mDpm.getResources().getString(
- QS_MSG_PERSONAL_PROFILE_NAMED_VPN,
- () -> mContext.getString(
- R.string.quick_settings_disclosure_personal_profile_named_vpn,
- vpnName),
- vpnName);
- }
- return mContext.getString(R.string.quick_settings_disclosure_named_vpn,
- vpnName);
- }
- return null;
- }
-
- private String getManagedProfileNetworkActivityText() {
- return mDpm.getResources().getString(
- QS_MSG_WORK_PROFILE_NETWORK, mWorkProfileNetworkStringSupplier);
- }
-
- @Override
- public void onClick(DialogInterface dialog, int which) {
- if (which == DialogInterface.BUTTON_NEGATIVE) {
- final Intent intent = new Intent(Settings.ACTION_ENTERPRISE_PRIVACY_SETTINGS);
- dialog.dismiss();
- // This dismisses the shade on opening the activity
- mActivityStarter.postStartActivityDismissingKeyguard(intent, 0);
- }
- }
-
- private void createDialog() {
- mShouldUseSettingsButton.set(false);
- mHandler.post(() -> {
- String settingsButtonText = getSettingsButton();
- final View view = createDialogView();
- mMainHandler.post(() -> {
- mDialog = new SystemUIDialog(mContext, 0); // Use mContext theme
- mDialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
- mDialog.setButton(DialogInterface.BUTTON_POSITIVE, getPositiveButton(), this);
- mDialog.setButton(DialogInterface.BUTTON_NEGATIVE, mShouldUseSettingsButton.get()
- ? settingsButtonText : getNegativeButton(), this);
-
- mDialog.setView(view);
- if (mView.isAggregatedVisible()) {
- mDialogLaunchAnimator.showFromView(mDialog, mView, new DialogCuj(
- InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN, INTERACTION_JANK_TAG));
- } else {
- mDialog.show();
- }
- });
- });
- }
-
- @VisibleForTesting
- Dialog getDialog() {
- return mDialog;
- }
-
- @VisibleForTesting
- View createDialogView() {
- if (mSecurityController.isParentalControlsEnabled()) {
- return createParentalControlsDialogView();
- }
- return createOrganizationDialogView();
- }
-
- private View createOrganizationDialogView() {
- final boolean isDeviceManaged = mSecurityController.isDeviceManaged();
- final boolean hasWorkProfile = mSecurityController.hasWorkProfile();
- final CharSequence deviceOwnerOrganization =
- mSecurityController.getDeviceOwnerOrganizationName();
- final boolean hasCACerts = mSecurityController.hasCACertInCurrentUser();
- final boolean hasCACertsInWorkProfile = mSecurityController.hasCACertInWorkProfile();
- final boolean isNetworkLoggingEnabled = mSecurityController.isNetworkLoggingEnabled();
- final String vpnName = mSecurityController.getPrimaryVpnName();
- final String vpnNameWorkProfile = mSecurityController.getWorkProfileVpnName();
-
- View dialogView = LayoutInflater.from(mContext)
- .inflate(R.layout.quick_settings_footer_dialog, null, false);
-
- // device management section
- TextView deviceManagementSubtitle =
- dialogView.findViewById(R.id.device_management_subtitle);
- deviceManagementSubtitle.setText(getManagementTitle(deviceOwnerOrganization));
-
- CharSequence managementMessage = getManagementMessage(isDeviceManaged,
- deviceOwnerOrganization);
- if (managementMessage == null) {
- dialogView.findViewById(R.id.device_management_disclosures).setVisibility(View.GONE);
- } else {
- dialogView.findViewById(R.id.device_management_disclosures).setVisibility(View.VISIBLE);
- TextView deviceManagementWarning =
- (TextView) dialogView.findViewById(R.id.device_management_warning);
- deviceManagementWarning.setText(managementMessage);
- mShouldUseSettingsButton.set(true);
- }
-
- // ca certificate section
- CharSequence caCertsMessage = getCaCertsMessage(isDeviceManaged, hasCACerts,
- hasCACertsInWorkProfile);
- if (caCertsMessage == null) {
- dialogView.findViewById(R.id.ca_certs_disclosures).setVisibility(View.GONE);
- } else {
- dialogView.findViewById(R.id.ca_certs_disclosures).setVisibility(View.VISIBLE);
- TextView caCertsWarning = (TextView) dialogView.findViewById(R.id.ca_certs_warning);
- caCertsWarning.setText(caCertsMessage);
- // Make "Open trusted credentials"-link clickable
- caCertsWarning.setMovementMethod(new LinkMovementMethod());
-
- TextView caCertsSubtitle = (TextView) dialogView.findViewById(R.id.ca_certs_subtitle);
- String caCertsSubtitleMessage = mDpm.getResources().getString(
- QS_DIALOG_MONITORING_CA_CERT_SUBTITLE, mMonitoringSubtitleCaCertStringSupplier);
- caCertsSubtitle.setText(caCertsSubtitleMessage);
-
- }
-
- // network logging section
- CharSequence networkLoggingMessage = getNetworkLoggingMessage(isDeviceManaged,
- isNetworkLoggingEnabled);
- if (networkLoggingMessage == null) {
- dialogView.findViewById(R.id.network_logging_disclosures).setVisibility(View.GONE);
- } else {
- dialogView.findViewById(R.id.network_logging_disclosures).setVisibility(View.VISIBLE);
- TextView networkLoggingWarning =
- (TextView) dialogView.findViewById(R.id.network_logging_warning);
- networkLoggingWarning.setText(networkLoggingMessage);
-
- TextView networkLoggingSubtitle = (TextView) dialogView.findViewById(
- R.id.network_logging_subtitle);
- String networkLoggingSubtitleMessage = mDpm.getResources().getString(
- QS_DIALOG_MONITORING_NETWORK_SUBTITLE,
- mMonitoringSubtitleNetworkStringSupplier);
- networkLoggingSubtitle.setText(networkLoggingSubtitleMessage);
- }
-
- // vpn section
- CharSequence vpnMessage = getVpnMessage(isDeviceManaged, hasWorkProfile, vpnName,
- vpnNameWorkProfile);
- if (vpnMessage == null) {
- dialogView.findViewById(R.id.vpn_disclosures).setVisibility(View.GONE);
- } else {
- dialogView.findViewById(R.id.vpn_disclosures).setVisibility(View.VISIBLE);
- TextView vpnWarning = (TextView) dialogView.findViewById(R.id.vpn_warning);
- vpnWarning.setText(vpnMessage);
- // Make "Open VPN Settings"-link clickable
- vpnWarning.setMovementMethod(new LinkMovementMethod());
-
- TextView vpnSubtitle = (TextView) dialogView.findViewById(R.id.vpn_subtitle);
- String vpnSubtitleMessage = mDpm.getResources().getString(
- QS_DIALOG_MONITORING_VPN_SUBTITLE, mMonitoringSubtitleVpnStringSupplier);
- vpnSubtitle.setText(vpnSubtitleMessage);
- }
-
- // Note: if a new section is added, should update configSubtitleVisibility to include
- // the handling of the subtitle
- configSubtitleVisibility(managementMessage != null,
- caCertsMessage != null,
- networkLoggingMessage != null,
- vpnMessage != null,
- dialogView);
-
- return dialogView;
- }
-
- private View createParentalControlsDialogView() {
- View dialogView = LayoutInflater.from(mContext)
- .inflate(R.layout.quick_settings_footer_dialog_parental_controls, null, false);
-
- DeviceAdminInfo info = mSecurityController.getDeviceAdminInfo();
- Drawable icon = mSecurityController.getIcon(info);
- if (icon != null) {
- ImageView imageView = (ImageView) dialogView.findViewById(R.id.parental_controls_icon);
- imageView.setImageDrawable(icon);
- }
-
- TextView parentalControlsTitle =
- (TextView) dialogView.findViewById(R.id.parental_controls_title);
- parentalControlsTitle.setText(mSecurityController.getLabel(info));
-
- return dialogView;
- }
-
- protected void configSubtitleVisibility(boolean showDeviceManagement, boolean showCaCerts,
- boolean showNetworkLogging, boolean showVpn, View dialogView) {
- // Device Management title should always been shown
- // When there is a Device Management message, all subtitles should be shown
- if (showDeviceManagement) {
- return;
- }
- // Hide the subtitle if there is only 1 message shown
- int mSectionCountExcludingDeviceMgt = 0;
- if (showCaCerts) { mSectionCountExcludingDeviceMgt++; }
- if (showNetworkLogging) { mSectionCountExcludingDeviceMgt++; }
- if (showVpn) { mSectionCountExcludingDeviceMgt++; }
-
- // No work needed if there is no sections or more than 1 section
- if (mSectionCountExcludingDeviceMgt != 1) {
- return;
- }
- if (showCaCerts) {
- dialogView.findViewById(R.id.ca_certs_subtitle).setVisibility(View.GONE);
- }
- if (showNetworkLogging) {
- dialogView.findViewById(R.id.network_logging_subtitle).setVisibility(View.GONE);
- }
- if (showVpn) {
- dialogView.findViewById(R.id.vpn_subtitle).setVisibility(View.GONE);
- }
- }
-
- // This should not be called on the main thread to avoid making an IPC.
- @VisibleForTesting
- String getSettingsButton() {
- return mDpm.getResources().getString(
- QS_DIALOG_VIEW_POLICIES, mViewPoliciesButtonStringSupplier);
- }
-
- private String getPositiveButton() {
- return mContext.getString(R.string.ok);
- }
-
- @Nullable
- private String getNegativeButton() {
- if (mSecurityController.isParentalControlsEnabled()) {
- return mContext.getString(R.string.monitoring_button_view_controls);
- }
- return null;
- }
-
- @Nullable
- protected CharSequence getManagementMessage(boolean isDeviceManaged,
- CharSequence organizationName) {
- if (!isDeviceManaged) {
- return null;
- }
- if (organizationName != null) {
- if (isFinancedDevice()) {
- return mContext.getString(R.string.monitoring_financed_description_named_management,
- organizationName, organizationName);
- } else {
- return mDpm.getResources().getString(
- QS_DIALOG_NAMED_MANAGEMENT,
- () -> mContext.getString(
- R.string.monitoring_description_named_management,
- organizationName),
- organizationName);
- }
- }
- return mDpm.getResources().getString(QS_DIALOG_MANAGEMENT, mManagementDialogStringSupplier);
- }
-
- @Nullable
- protected CharSequence getCaCertsMessage(boolean isDeviceManaged, boolean hasCACerts,
- boolean hasCACertsInWorkProfile) {
- if (!(hasCACerts || hasCACertsInWorkProfile)) return null;
- if (isDeviceManaged) {
- return mDpm.getResources().getString(
- QS_DIALOG_MANAGEMENT_CA_CERT, mManagementDialogCaCertStringSupplier);
- }
- if (hasCACertsInWorkProfile) {
- return mDpm.getResources().getString(
- QS_DIALOG_WORK_PROFILE_CA_CERT, mWorkProfileDialogCaCertStringSupplier);
- }
- return mContext.getString(R.string.monitoring_description_ca_certificate);
- }
-
- @Nullable
- protected CharSequence getNetworkLoggingMessage(boolean isDeviceManaged,
- boolean isNetworkLoggingEnabled) {
- if (!isNetworkLoggingEnabled) return null;
- if (isDeviceManaged) {
- return mDpm.getResources().getString(
- QS_DIALOG_MANAGEMENT_NETWORK, mManagementDialogNetworkStringSupplier);
- } else {
- return mDpm.getResources().getString(
- QS_DIALOG_WORK_PROFILE_NETWORK, mWorkProfileDialogNetworkStringSupplier);
- }
- }
-
- @Nullable
- protected CharSequence getVpnMessage(boolean isDeviceManaged, boolean hasWorkProfile,
- String vpnName, String vpnNameWorkProfile) {
- if (vpnName == null && vpnNameWorkProfile == null) return null;
- final SpannableStringBuilder message = new SpannableStringBuilder();
- if (isDeviceManaged) {
- if (vpnName != null && vpnNameWorkProfile != null) {
- String namedVpns = mDpm.getResources().getString(
- QS_DIALOG_MANAGEMENT_TWO_NAMED_VPN,
- () -> mContext.getString(
- R.string.monitoring_description_two_named_vpns,
- vpnName, vpnNameWorkProfile),
- vpnName, vpnNameWorkProfile);
- message.append(namedVpns);
- } else {
- String name = vpnName != null ? vpnName : vpnNameWorkProfile;
- String namedVp = mDpm.getResources().getString(
- QS_DIALOG_MANAGEMENT_NAMED_VPN,
- () -> mContext.getString(R.string.monitoring_description_named_vpn, name),
- name);
- message.append(namedVp);
- }
- } else {
- if (vpnName != null && vpnNameWorkProfile != null) {
- String namedVpns = mDpm.getResources().getString(
- QS_DIALOG_MANAGEMENT_TWO_NAMED_VPN,
- () -> mContext.getString(
- R.string.monitoring_description_two_named_vpns,
- vpnName, vpnNameWorkProfile),
- vpnName, vpnNameWorkProfile);
- message.append(namedVpns);
- } else if (vpnNameWorkProfile != null) {
- String namedVpn = mDpm.getResources().getString(
- QS_DIALOG_WORK_PROFILE_NAMED_VPN,
- () -> mContext.getString(
- R.string.monitoring_description_managed_profile_named_vpn,
- vpnNameWorkProfile),
- vpnNameWorkProfile);
- message.append(namedVpn);
- } else if (hasWorkProfile) {
- String namedVpn = mDpm.getResources().getString(
- QS_DIALOG_PERSONAL_PROFILE_NAMED_VPN,
- () -> mContext.getString(
- R.string.monitoring_description_personal_profile_named_vpn,
- vpnName),
- vpnName);
- message.append(namedVpn);
- } else {
- message.append(mContext.getString(R.string.monitoring_description_named_vpn,
- vpnName));
- }
- }
- message.append(mContext.getString(R.string.monitoring_description_vpn_settings_separator));
- message.append(mContext.getString(R.string.monitoring_description_vpn_settings),
- new VpnSpan(), 0);
- return message;
- }
-
- @VisibleForTesting
- CharSequence getManagementTitle(CharSequence deviceOwnerOrganization) {
- if (deviceOwnerOrganization != null && isFinancedDevice()) {
- return mContext.getString(R.string.monitoring_title_financed_device,
- deviceOwnerOrganization);
- } else {
- return mDpm.getResources().getString(
- QS_DIALOG_MANAGEMENT_TITLE,
- mManagementTitleSupplier);
- }
- }
-
- private boolean isFinancedDevice() {
- return mSecurityController.isDeviceManaged()
- && mSecurityController.getDeviceOwnerType(
- mSecurityController.getDeviceOwnerComponentOnAnyUser())
- == DEVICE_OWNER_TYPE_FINANCED;
- }
-
private final Runnable mUpdatePrimaryIcon = new Runnable() {
@Override
public void run() {
- if (mPrimaryFooterIconDrawable != null) {
- mPrimaryFooterIcon.setImageDrawable(mPrimaryFooterIconDrawable);
- } else {
- mPrimaryFooterIcon.setImageResource(mFooterIconId);
+ if (mFooterIcon instanceof Icon.Loaded) {
+ mPrimaryFooterIcon.setImageDrawable(((Icon.Loaded) mFooterIcon).getDrawable());
+ } else if (mFooterIcon instanceof Icon.Resource) {
+ mPrimaryFooterIcon.setImageResource(((Icon.Resource) mFooterIcon).getRes());
}
}
};
@@ -890,10 +210,18 @@ class QSSecurityFooter extends ViewController<View>
if (mFooterTextContent != null) {
mFooterText.setText(mFooterTextContent);
}
- mView.setVisibility(mIsVisible || DEBUG_FORCE_VISIBLE ? View.VISIBLE : View.GONE);
+ mView.setVisibility(mIsVisible ? View.VISIBLE : View.GONE);
if (mVisibilityChangedListener != null) {
mVisibilityChangedListener.onVisibilityChanged(mView.getVisibility());
}
+
+ if (mIsVisible && mIsClickable) {
+ mView.setClickable(true);
+ mView.findViewById(R.id.footer_icon).setVisibility(View.VISIBLE);
+ } else {
+ mView.setClickable(false);
+ mView.findViewById(R.id.footer_icon).setVisibility(View.GONE);
+ }
}
};
@@ -929,25 +257,4 @@ class QSSecurityFooter extends ViewController<View>
}
}
}
-
- protected class VpnSpan extends ClickableSpan {
- @Override
- public void onClick(View widget) {
- final Intent intent = new Intent(Settings.ACTION_VPN_SETTINGS);
- mDialog.dismiss();
- // This dismisses the shade on opening the activity
- mActivityStarter.postStartActivityDismissingKeyguard(intent, 0);
- }
-
- // for testing, to compare two CharSequences containing VpnSpans
- @Override
- public boolean equals(Object object) {
- return object instanceof VpnSpan;
- }
-
- @Override
- public int hashCode() {
- return 314159257; // prime
- }
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooterUtils.java b/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooterUtils.java
new file mode 100644
index 000000000000..f6322743eaa1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooterUtils.java
@@ -0,0 +1,793 @@
+/*
+ * Copyright (C) 2014 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.qs;
+
+import static android.app.admin.DevicePolicyManager.DEVICE_OWNER_TYPE_FINANCED;
+import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_DIALOG_MANAGEMENT;
+import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_DIALOG_MANAGEMENT_CA_CERT;
+import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_DIALOG_MANAGEMENT_NAMED_VPN;
+import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_DIALOG_MANAGEMENT_NETWORK;
+import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_DIALOG_MANAGEMENT_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_DIALOG_MANAGEMENT_TWO_NAMED_VPN;
+import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_DIALOG_MONITORING_CA_CERT_SUBTITLE;
+import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_DIALOG_MONITORING_NETWORK_SUBTITLE;
+import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_DIALOG_MONITORING_VPN_SUBTITLE;
+import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_DIALOG_NAMED_MANAGEMENT;
+import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_DIALOG_PERSONAL_PROFILE_NAMED_VPN;
+import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_DIALOG_VIEW_POLICIES;
+import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_DIALOG_WORK_PROFILE_CA_CERT;
+import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_DIALOG_WORK_PROFILE_NAMED_VPN;
+import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_DIALOG_WORK_PROFILE_NETWORK;
+import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_MSG_MANAGEMENT;
+import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_MSG_MANAGEMENT_MONITORING;
+import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_MSG_MANAGEMENT_MULTIPLE_VPNS;
+import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_MSG_MANAGEMENT_NAMED_VPN;
+import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_MSG_NAMED_MANAGEMENT;
+import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_MSG_NAMED_MANAGEMENT_MONITORING;
+import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_MSG_NAMED_MANAGEMENT_MULTIPLE_VPNS;
+import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_MSG_NAMED_MANAGEMENT_NAMED_VPN;
+import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_MSG_NAMED_WORK_PROFILE_MONITORING;
+import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_MSG_PERSONAL_PROFILE_NAMED_VPN;
+import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_MSG_WORK_PROFILE_MONITORING;
+import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_MSG_WORK_PROFILE_NAMED_VPN;
+import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_MSG_WORK_PROFILE_NETWORK;
+
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.app.admin.DeviceAdminInfo;
+import android.app.admin.DevicePolicyManager;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.pm.UserInfo;
+import android.graphics.drawable.Drawable;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.UserManager;
+import android.provider.Settings;
+import android.text.SpannableStringBuilder;
+import android.text.method.LinkMovementMethod;
+import android.text.style.ClickableSpan;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.Window;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+
+import com.android.internal.jank.InteractionJankMonitor;
+import com.android.systemui.R;
+import com.android.systemui.animation.DialogCuj;
+import com.android.systemui.animation.DialogLaunchAnimator;
+import com.android.systemui.common.shared.model.Icon;
+import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Application;
+import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.qs.footer.domain.model.SecurityButtonConfig;
+import com.android.systemui.security.data.model.SecurityModel;
+import com.android.systemui.settings.UserTracker;
+import com.android.systemui.statusbar.phone.SystemUIDialog;
+import com.android.systemui.statusbar.policy.SecurityController;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.Supplier;
+
+import javax.inject.Inject;
+
+/** Helper class for the configuration of the QS security footer button. */
+@SysUISingleton
+public class QSSecurityFooterUtils implements DialogInterface.OnClickListener {
+ protected static final String TAG = "QSSecurityFooterUtils";
+ protected static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+ private static final boolean DEBUG_FORCE_VISIBLE = false;
+
+ private static final String INTERACTION_JANK_TAG = "managed_device_info";
+
+ @Application private Context mContext;
+ private final DevicePolicyManager mDpm;
+
+ private final SecurityController mSecurityController;
+ private final ActivityStarter mActivityStarter;
+ private final Handler mMainHandler;
+ private final UserTracker mUserTracker;
+ private final DialogLaunchAnimator mDialogLaunchAnimator;
+
+ private final AtomicBoolean mShouldUseSettingsButton = new AtomicBoolean(false);
+
+ protected Handler mBgHandler;
+ private AlertDialog mDialog;
+
+ private Supplier<String> mManagementTitleSupplier = () ->
+ mContext == null ? null : mContext.getString(R.string.monitoring_title_device_owned);
+
+ private Supplier<String> mManagementMessageSupplier = () ->
+ mContext == null ? null : mContext.getString(
+ R.string.quick_settings_disclosure_management);
+
+ private Supplier<String> mManagementMonitoringStringSupplier = () ->
+ mContext == null ? null : mContext.getString(
+ R.string.quick_settings_disclosure_management_monitoring);
+
+ private Supplier<String> mManagementMultipleVpnStringSupplier = () ->
+ mContext == null ? null : mContext.getString(
+ R.string.quick_settings_disclosure_management_vpns);
+
+ private Supplier<String> mWorkProfileMonitoringStringSupplier = () ->
+ mContext == null ? null : mContext.getString(
+ R.string.quick_settings_disclosure_managed_profile_monitoring);
+
+ private Supplier<String> mWorkProfileNetworkStringSupplier = () ->
+ mContext == null ? null : mContext.getString(
+ R.string.quick_settings_disclosure_managed_profile_network_activity);
+
+ private Supplier<String> mMonitoringSubtitleCaCertStringSupplier = () ->
+ mContext == null ? null : mContext.getString(
+ R.string.monitoring_subtitle_ca_certificate);
+
+ private Supplier<String> mMonitoringSubtitleNetworkStringSupplier = () ->
+ mContext == null ? null : mContext.getString(
+ R.string.monitoring_subtitle_network_logging);
+
+ private Supplier<String> mMonitoringSubtitleVpnStringSupplier = () ->
+ mContext == null ? null : mContext.getString(R.string.monitoring_subtitle_vpn);
+
+ private Supplier<String> mViewPoliciesButtonStringSupplier = () ->
+ mContext == null ? null : mContext.getString(R.string.monitoring_button_view_policies);
+
+ private Supplier<String> mManagementDialogStringSupplier = () ->
+ mContext == null ? null : mContext.getString(
+ R.string.monitoring_description_management);
+
+ private Supplier<String> mManagementDialogCaCertStringSupplier = () ->
+ mContext == null ? null : mContext.getString(
+ R.string.monitoring_description_management_ca_certificate);
+
+ private Supplier<String> mWorkProfileDialogCaCertStringSupplier = () ->
+ mContext == null ? null : mContext.getString(
+ R.string.monitoring_description_managed_profile_ca_certificate);
+
+ private Supplier<String> mManagementDialogNetworkStringSupplier = () ->
+ mContext == null ? null : mContext.getString(
+ R.string.monitoring_description_management_network_logging);
+
+ private Supplier<String> mWorkProfileDialogNetworkStringSupplier = () ->
+ mContext == null ? null : mContext.getString(
+ R.string.monitoring_description_managed_profile_network_logging);
+
+ @Inject
+ QSSecurityFooterUtils(
+ @Application Context context, DevicePolicyManager devicePolicyManager,
+ UserTracker userTracker, @Main Handler mainHandler, ActivityStarter activityStarter,
+ SecurityController securityController, @Background Looper bgLooper,
+ DialogLaunchAnimator dialogLaunchAnimator) {
+ mContext = context;
+ mDpm = devicePolicyManager;
+ mUserTracker = userTracker;
+ mMainHandler = mainHandler;
+ mActivityStarter = activityStarter;
+ mSecurityController = securityController;
+ mBgHandler = new Handler(bgLooper);
+ mDialogLaunchAnimator = dialogLaunchAnimator;
+ }
+
+ /** Show the device monitoring dialog. */
+ public void showDeviceMonitoringDialog(Context quickSettingsContext, @Nullable View view) {
+ createDialog(quickSettingsContext, view);
+ }
+
+ /**
+ * Return the {@link SecurityButtonConfig} of the security button, or {@code null} if no
+ * security button should be shown.
+ */
+ @Nullable
+ public SecurityButtonConfig getButtonConfig(SecurityModel securityModel) {
+ final boolean isDeviceManaged = securityModel.isDeviceManaged();
+ final UserInfo currentUser = mUserTracker.getUserInfo();
+ final boolean isDemoDevice = UserManager.isDeviceInDemoMode(mContext) && currentUser != null
+ && currentUser.isDemo();
+ final boolean hasWorkProfile = securityModel.getHasWorkProfile();
+ final boolean hasCACerts = securityModel.getHasCACertInCurrentUser();
+ final boolean hasCACertsInWorkProfile = securityModel.getHasCACertInWorkProfile();
+ final boolean isNetworkLoggingEnabled = securityModel.isNetworkLoggingEnabled();
+ final String vpnName = securityModel.getPrimaryVpnName();
+ final String vpnNameWorkProfile = securityModel.getWorkProfileVpnName();
+ final CharSequence organizationName = securityModel.getDeviceOwnerOrganizationName();
+ final CharSequence workProfileOrganizationName =
+ securityModel.getWorkProfileOrganizationName();
+ final boolean isProfileOwnerOfOrganizationOwnedDevice =
+ securityModel.isProfileOwnerOfOrganizationOwnedDevice();
+ final boolean isParentalControlsEnabled = securityModel.isParentalControlsEnabled();
+ final boolean isWorkProfileOn = securityModel.isWorkProfileOn();
+ final boolean hasDisclosableWorkProfilePolicy = hasCACertsInWorkProfile
+ || vpnNameWorkProfile != null || (hasWorkProfile && isNetworkLoggingEnabled);
+ // Update visibility of footer
+ boolean isVisible = (isDeviceManaged && !isDemoDevice)
+ || hasCACerts
+ || vpnName != null
+ || isProfileOwnerOfOrganizationOwnedDevice
+ || isParentalControlsEnabled
+ || (hasDisclosableWorkProfilePolicy && isWorkProfileOn);
+ if (!isVisible && !DEBUG_FORCE_VISIBLE) {
+ return null;
+ }
+
+ // Update the view to be untappable if the device is an organization-owned device with a
+ // managed profile and there is either:
+ // a) no policy set which requires a privacy disclosure.
+ // b) a specific work policy set but the work profile is turned off.
+ boolean isClickable = !(isProfileOwnerOfOrganizationOwnedDevice
+ && (!hasDisclosableWorkProfilePolicy || !isWorkProfileOn));
+
+ String text = getFooterText(isDeviceManaged, hasWorkProfile,
+ hasCACerts, hasCACertsInWorkProfile, isNetworkLoggingEnabled, vpnName,
+ vpnNameWorkProfile, organizationName, workProfileOrganizationName,
+ isProfileOwnerOfOrganizationOwnedDevice, isParentalControlsEnabled,
+ isWorkProfileOn).toString();
+
+ Icon icon;
+ if (isParentalControlsEnabled) {
+ icon = new Icon.Loaded(securityModel.getDeviceAdminIcon());
+ } else if (vpnName != null || vpnNameWorkProfile != null) {
+ if (securityModel.isVpnBranded()) {
+ icon = new Icon.Resource(R.drawable.stat_sys_branded_vpn);
+ } else {
+ icon = new Icon.Resource(R.drawable.stat_sys_vpn_ic);
+ }
+ } else {
+ icon = new Icon.Resource(R.drawable.ic_info_outline);
+ }
+
+ return new SecurityButtonConfig(icon, text, isClickable);
+ }
+
+ @Nullable
+ protected CharSequence getFooterText(boolean isDeviceManaged, boolean hasWorkProfile,
+ boolean hasCACerts, boolean hasCACertsInWorkProfile, boolean isNetworkLoggingEnabled,
+ String vpnName, String vpnNameWorkProfile, CharSequence organizationName,
+ CharSequence workProfileOrganizationName,
+ boolean isProfileOwnerOfOrganizationOwnedDevice, boolean isParentalControlsEnabled,
+ boolean isWorkProfileOn) {
+ if (isParentalControlsEnabled) {
+ return mContext.getString(R.string.quick_settings_disclosure_parental_controls);
+ }
+ if (isDeviceManaged || DEBUG_FORCE_VISIBLE) {
+ return getManagedDeviceFooterText(hasCACerts, hasCACertsInWorkProfile,
+ isNetworkLoggingEnabled, vpnName, vpnNameWorkProfile, organizationName);
+ }
+ return getManagedAndPersonalProfileFooterText(hasWorkProfile, hasCACerts,
+ hasCACertsInWorkProfile, isNetworkLoggingEnabled, vpnName, vpnNameWorkProfile,
+ workProfileOrganizationName, isProfileOwnerOfOrganizationOwnedDevice,
+ isWorkProfileOn);
+ }
+
+ private String getManagedDeviceFooterText(
+ boolean hasCACerts, boolean hasCACertsInWorkProfile, boolean isNetworkLoggingEnabled,
+ String vpnName, String vpnNameWorkProfile, CharSequence organizationName) {
+ if (hasCACerts || hasCACertsInWorkProfile || isNetworkLoggingEnabled) {
+ return getManagedDeviceMonitoringText(organizationName);
+ }
+ if (vpnName != null || vpnNameWorkProfile != null) {
+ return getManagedDeviceVpnText(vpnName, vpnNameWorkProfile, organizationName);
+ }
+ return getMangedDeviceGeneralText(organizationName);
+ }
+
+ private String getManagedDeviceMonitoringText(CharSequence organizationName) {
+ if (organizationName == null) {
+ return mDpm.getResources().getString(
+ QS_MSG_MANAGEMENT_MONITORING, mManagementMonitoringStringSupplier);
+ }
+ return mDpm.getResources().getString(
+ QS_MSG_NAMED_MANAGEMENT_MONITORING,
+ () -> mContext.getString(
+ R.string.quick_settings_disclosure_named_management_monitoring,
+ organizationName),
+ organizationName);
+ }
+
+ private String getManagedDeviceVpnText(
+ String vpnName, String vpnNameWorkProfile, CharSequence organizationName) {
+ if (vpnName != null && vpnNameWorkProfile != null) {
+ if (organizationName == null) {
+ return mDpm.getResources().getString(
+ QS_MSG_MANAGEMENT_MULTIPLE_VPNS, mManagementMultipleVpnStringSupplier);
+ }
+ return mDpm.getResources().getString(
+ QS_MSG_NAMED_MANAGEMENT_MULTIPLE_VPNS,
+ () -> mContext.getString(
+ R.string.quick_settings_disclosure_named_management_vpns,
+ organizationName),
+ organizationName);
+ }
+ String name = vpnName != null ? vpnName : vpnNameWorkProfile;
+ if (organizationName == null) {
+ return mDpm.getResources().getString(
+ QS_MSG_MANAGEMENT_NAMED_VPN,
+ () -> mContext.getString(
+ R.string.quick_settings_disclosure_management_named_vpn,
+ name),
+ name);
+ }
+ return mDpm.getResources().getString(
+ QS_MSG_NAMED_MANAGEMENT_NAMED_VPN,
+ () -> mContext.getString(
+ R.string.quick_settings_disclosure_named_management_named_vpn,
+ organizationName,
+ name),
+ organizationName,
+ name);
+ }
+
+ private String getMangedDeviceGeneralText(CharSequence organizationName) {
+ if (organizationName == null) {
+ return mDpm.getResources().getString(QS_MSG_MANAGEMENT, mManagementMessageSupplier);
+ }
+ if (isFinancedDevice()) {
+ return mContext.getString(
+ R.string.quick_settings_financed_disclosure_named_management,
+ organizationName);
+ } else {
+ return mDpm.getResources().getString(
+ QS_MSG_NAMED_MANAGEMENT,
+ () -> mContext.getString(
+ R.string.quick_settings_disclosure_named_management,
+ organizationName),
+ organizationName);
+ }
+ }
+
+ private String getManagedAndPersonalProfileFooterText(boolean hasWorkProfile,
+ boolean hasCACerts, boolean hasCACertsInWorkProfile, boolean isNetworkLoggingEnabled,
+ String vpnName, String vpnNameWorkProfile, CharSequence workProfileOrganizationName,
+ boolean isProfileOwnerOfOrganizationOwnedDevice, boolean isWorkProfileOn) {
+ if (hasCACerts || (hasCACertsInWorkProfile && isWorkProfileOn)) {
+ return getMonitoringText(
+ hasCACerts, hasCACertsInWorkProfile, workProfileOrganizationName,
+ isWorkProfileOn);
+ }
+ if (vpnName != null || (vpnNameWorkProfile != null && isWorkProfileOn)) {
+ return getVpnText(hasWorkProfile, vpnName, vpnNameWorkProfile, isWorkProfileOn);
+ }
+ if (hasWorkProfile && isNetworkLoggingEnabled && isWorkProfileOn) {
+ return getManagedProfileNetworkActivityText();
+ }
+ if (isProfileOwnerOfOrganizationOwnedDevice) {
+ return getMangedDeviceGeneralText(workProfileOrganizationName);
+ }
+ return null;
+ }
+
+ private String getMonitoringText(boolean hasCACerts, boolean hasCACertsInWorkProfile,
+ CharSequence workProfileOrganizationName, boolean isWorkProfileOn) {
+ if (hasCACertsInWorkProfile && isWorkProfileOn) {
+ if (workProfileOrganizationName == null) {
+ return mDpm.getResources().getString(
+ QS_MSG_WORK_PROFILE_MONITORING, mWorkProfileMonitoringStringSupplier);
+ }
+ return mDpm.getResources().getString(
+ QS_MSG_NAMED_WORK_PROFILE_MONITORING,
+ () -> mContext.getString(
+ R.string.quick_settings_disclosure_named_managed_profile_monitoring,
+ workProfileOrganizationName),
+ workProfileOrganizationName);
+ }
+ if (hasCACerts) {
+ return mContext.getString(R.string.quick_settings_disclosure_monitoring);
+ }
+ return null;
+ }
+
+ private String getVpnText(boolean hasWorkProfile, String vpnName, String vpnNameWorkProfile,
+ boolean isWorkProfileOn) {
+ if (vpnName != null && vpnNameWorkProfile != null) {
+ return mContext.getString(R.string.quick_settings_disclosure_vpns);
+ }
+ if (vpnNameWorkProfile != null && isWorkProfileOn) {
+ return mDpm.getResources().getString(
+ QS_MSG_WORK_PROFILE_NAMED_VPN,
+ () -> mContext.getString(
+ R.string.quick_settings_disclosure_managed_profile_named_vpn,
+ vpnNameWorkProfile),
+ vpnNameWorkProfile);
+ }
+ if (vpnName != null) {
+ if (hasWorkProfile) {
+ return mDpm.getResources().getString(
+ QS_MSG_PERSONAL_PROFILE_NAMED_VPN,
+ () -> mContext.getString(
+ R.string.quick_settings_disclosure_personal_profile_named_vpn,
+ vpnName),
+ vpnName);
+ }
+ return mContext.getString(R.string.quick_settings_disclosure_named_vpn,
+ vpnName);
+ }
+ return null;
+ }
+
+ private String getManagedProfileNetworkActivityText() {
+ return mDpm.getResources().getString(
+ QS_MSG_WORK_PROFILE_NETWORK, mWorkProfileNetworkStringSupplier);
+ }
+
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ if (which == DialogInterface.BUTTON_NEGATIVE) {
+ final Intent intent = new Intent(Settings.ACTION_ENTERPRISE_PRIVACY_SETTINGS);
+ dialog.dismiss();
+ // This dismisses the shade on opening the activity
+ mActivityStarter.postStartActivityDismissingKeyguard(intent, 0);
+ }
+ }
+
+ private void createDialog(Context quickSettingsContext, @Nullable View view) {
+ mShouldUseSettingsButton.set(false);
+ mBgHandler.post(() -> {
+ String settingsButtonText = getSettingsButton();
+ final View dialogView = createDialogView();
+ mMainHandler.post(() -> {
+ mDialog = new SystemUIDialog(quickSettingsContext, 0);
+ mDialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
+ mDialog.setButton(DialogInterface.BUTTON_POSITIVE, getPositiveButton(), this);
+ mDialog.setButton(DialogInterface.BUTTON_NEGATIVE, mShouldUseSettingsButton.get()
+ ? settingsButtonText : getNegativeButton(), this);
+
+ mDialog.setView(dialogView);
+ if (view != null && view.isAggregatedVisible()) {
+ mDialogLaunchAnimator.showFromView(mDialog, view, new DialogCuj(
+ InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN, INTERACTION_JANK_TAG));
+ } else {
+ mDialog.show();
+ }
+ });
+ });
+ }
+
+ @VisibleForTesting
+ Dialog getDialog() {
+ return mDialog;
+ }
+
+ @VisibleForTesting
+ View createDialogView() {
+ if (mSecurityController.isParentalControlsEnabled()) {
+ return createParentalControlsDialogView();
+ }
+ return createOrganizationDialogView();
+ }
+
+ private View createOrganizationDialogView() {
+ final boolean isDeviceManaged = mSecurityController.isDeviceManaged();
+ final boolean hasWorkProfile = mSecurityController.hasWorkProfile();
+ final CharSequence deviceOwnerOrganization =
+ mSecurityController.getDeviceOwnerOrganizationName();
+ final boolean hasCACerts = mSecurityController.hasCACertInCurrentUser();
+ final boolean hasCACertsInWorkProfile = mSecurityController.hasCACertInWorkProfile();
+ final boolean isNetworkLoggingEnabled = mSecurityController.isNetworkLoggingEnabled();
+ final String vpnName = mSecurityController.getPrimaryVpnName();
+ final String vpnNameWorkProfile = mSecurityController.getWorkProfileVpnName();
+
+ View dialogView = LayoutInflater.from(mContext)
+ .inflate(R.layout.quick_settings_footer_dialog, null, false);
+
+ // device management section
+ TextView deviceManagementSubtitle =
+ dialogView.findViewById(R.id.device_management_subtitle);
+ deviceManagementSubtitle.setText(getManagementTitle(deviceOwnerOrganization));
+
+ CharSequence managementMessage = getManagementMessage(isDeviceManaged,
+ deviceOwnerOrganization);
+ if (managementMessage == null) {
+ dialogView.findViewById(R.id.device_management_disclosures).setVisibility(View.GONE);
+ } else {
+ dialogView.findViewById(R.id.device_management_disclosures).setVisibility(View.VISIBLE);
+ TextView deviceManagementWarning =
+ (TextView) dialogView.findViewById(R.id.device_management_warning);
+ deviceManagementWarning.setText(managementMessage);
+ mShouldUseSettingsButton.set(true);
+ }
+
+ // ca certificate section
+ CharSequence caCertsMessage = getCaCertsMessage(isDeviceManaged, hasCACerts,
+ hasCACertsInWorkProfile);
+ if (caCertsMessage == null) {
+ dialogView.findViewById(R.id.ca_certs_disclosures).setVisibility(View.GONE);
+ } else {
+ dialogView.findViewById(R.id.ca_certs_disclosures).setVisibility(View.VISIBLE);
+ TextView caCertsWarning = (TextView) dialogView.findViewById(R.id.ca_certs_warning);
+ caCertsWarning.setText(caCertsMessage);
+ // Make "Open trusted credentials"-link clickable
+ caCertsWarning.setMovementMethod(new LinkMovementMethod());
+
+ TextView caCertsSubtitle = (TextView) dialogView.findViewById(R.id.ca_certs_subtitle);
+ String caCertsSubtitleMessage = mDpm.getResources().getString(
+ QS_DIALOG_MONITORING_CA_CERT_SUBTITLE, mMonitoringSubtitleCaCertStringSupplier);
+ caCertsSubtitle.setText(caCertsSubtitleMessage);
+
+ }
+
+ // network logging section
+ CharSequence networkLoggingMessage = getNetworkLoggingMessage(isDeviceManaged,
+ isNetworkLoggingEnabled);
+ if (networkLoggingMessage == null) {
+ dialogView.findViewById(R.id.network_logging_disclosures).setVisibility(View.GONE);
+ } else {
+ dialogView.findViewById(R.id.network_logging_disclosures).setVisibility(View.VISIBLE);
+ TextView networkLoggingWarning =
+ (TextView) dialogView.findViewById(R.id.network_logging_warning);
+ networkLoggingWarning.setText(networkLoggingMessage);
+
+ TextView networkLoggingSubtitle = (TextView) dialogView.findViewById(
+ R.id.network_logging_subtitle);
+ String networkLoggingSubtitleMessage = mDpm.getResources().getString(
+ QS_DIALOG_MONITORING_NETWORK_SUBTITLE,
+ mMonitoringSubtitleNetworkStringSupplier);
+ networkLoggingSubtitle.setText(networkLoggingSubtitleMessage);
+ }
+
+ // vpn section
+ CharSequence vpnMessage = getVpnMessage(isDeviceManaged, hasWorkProfile, vpnName,
+ vpnNameWorkProfile);
+ if (vpnMessage == null) {
+ dialogView.findViewById(R.id.vpn_disclosures).setVisibility(View.GONE);
+ } else {
+ dialogView.findViewById(R.id.vpn_disclosures).setVisibility(View.VISIBLE);
+ TextView vpnWarning = (TextView) dialogView.findViewById(R.id.vpn_warning);
+ vpnWarning.setText(vpnMessage);
+ // Make "Open VPN Settings"-link clickable
+ vpnWarning.setMovementMethod(new LinkMovementMethod());
+
+ TextView vpnSubtitle = (TextView) dialogView.findViewById(R.id.vpn_subtitle);
+ String vpnSubtitleMessage = mDpm.getResources().getString(
+ QS_DIALOG_MONITORING_VPN_SUBTITLE, mMonitoringSubtitleVpnStringSupplier);
+ vpnSubtitle.setText(vpnSubtitleMessage);
+ }
+
+ // Note: if a new section is added, should update configSubtitleVisibility to include
+ // the handling of the subtitle
+ configSubtitleVisibility(managementMessage != null,
+ caCertsMessage != null,
+ networkLoggingMessage != null,
+ vpnMessage != null,
+ dialogView);
+
+ return dialogView;
+ }
+
+ private View createParentalControlsDialogView() {
+ View dialogView = LayoutInflater.from(mContext)
+ .inflate(R.layout.quick_settings_footer_dialog_parental_controls, null, false);
+
+ DeviceAdminInfo info = mSecurityController.getDeviceAdminInfo();
+ Drawable icon = mSecurityController.getIcon(info);
+ if (icon != null) {
+ ImageView imageView = (ImageView) dialogView.findViewById(R.id.parental_controls_icon);
+ imageView.setImageDrawable(icon);
+ }
+
+ TextView parentalControlsTitle =
+ (TextView) dialogView.findViewById(R.id.parental_controls_title);
+ parentalControlsTitle.setText(mSecurityController.getLabel(info));
+
+ return dialogView;
+ }
+
+ protected void configSubtitleVisibility(boolean showDeviceManagement, boolean showCaCerts,
+ boolean showNetworkLogging, boolean showVpn, View dialogView) {
+ // Device Management title should always been shown
+ // When there is a Device Management message, all subtitles should be shown
+ if (showDeviceManagement) {
+ return;
+ }
+ // Hide the subtitle if there is only 1 message shown
+ int mSectionCountExcludingDeviceMgt = 0;
+ if (showCaCerts) {
+ mSectionCountExcludingDeviceMgt++;
+ }
+ if (showNetworkLogging) {
+ mSectionCountExcludingDeviceMgt++;
+ }
+ if (showVpn) {
+ mSectionCountExcludingDeviceMgt++;
+ }
+
+ // No work needed if there is no sections or more than 1 section
+ if (mSectionCountExcludingDeviceMgt != 1) {
+ return;
+ }
+ if (showCaCerts) {
+ dialogView.findViewById(R.id.ca_certs_subtitle).setVisibility(View.GONE);
+ }
+ if (showNetworkLogging) {
+ dialogView.findViewById(R.id.network_logging_subtitle).setVisibility(View.GONE);
+ }
+ if (showVpn) {
+ dialogView.findViewById(R.id.vpn_subtitle).setVisibility(View.GONE);
+ }
+ }
+
+ // This should not be called on the main thread to avoid making an IPC.
+ @VisibleForTesting
+ String getSettingsButton() {
+ return mDpm.getResources().getString(
+ QS_DIALOG_VIEW_POLICIES, mViewPoliciesButtonStringSupplier);
+ }
+
+ private String getPositiveButton() {
+ return mContext.getString(R.string.ok);
+ }
+
+ @Nullable
+ private String getNegativeButton() {
+ if (mSecurityController.isParentalControlsEnabled()) {
+ return mContext.getString(R.string.monitoring_button_view_controls);
+ }
+ return null;
+ }
+
+ @Nullable
+ protected CharSequence getManagementMessage(boolean isDeviceManaged,
+ CharSequence organizationName) {
+ if (!isDeviceManaged) {
+ return null;
+ }
+ if (organizationName != null) {
+ if (isFinancedDevice()) {
+ return mContext.getString(R.string.monitoring_financed_description_named_management,
+ organizationName, organizationName);
+ } else {
+ return mDpm.getResources().getString(
+ QS_DIALOG_NAMED_MANAGEMENT,
+ () -> mContext.getString(
+ R.string.monitoring_description_named_management,
+ organizationName),
+ organizationName);
+ }
+ }
+ return mDpm.getResources().getString(QS_DIALOG_MANAGEMENT, mManagementDialogStringSupplier);
+ }
+
+ @Nullable
+ protected CharSequence getCaCertsMessage(boolean isDeviceManaged, boolean hasCACerts,
+ boolean hasCACertsInWorkProfile) {
+ if (!(hasCACerts || hasCACertsInWorkProfile)) return null;
+ if (isDeviceManaged) {
+ return mDpm.getResources().getString(
+ QS_DIALOG_MANAGEMENT_CA_CERT, mManagementDialogCaCertStringSupplier);
+ }
+ if (hasCACertsInWorkProfile) {
+ return mDpm.getResources().getString(
+ QS_DIALOG_WORK_PROFILE_CA_CERT, mWorkProfileDialogCaCertStringSupplier);
+ }
+ return mContext.getString(R.string.monitoring_description_ca_certificate);
+ }
+
+ @Nullable
+ protected CharSequence getNetworkLoggingMessage(boolean isDeviceManaged,
+ boolean isNetworkLoggingEnabled) {
+ if (!isNetworkLoggingEnabled) return null;
+ if (isDeviceManaged) {
+ return mDpm.getResources().getString(
+ QS_DIALOG_MANAGEMENT_NETWORK, mManagementDialogNetworkStringSupplier);
+ } else {
+ return mDpm.getResources().getString(
+ QS_DIALOG_WORK_PROFILE_NETWORK, mWorkProfileDialogNetworkStringSupplier);
+ }
+ }
+
+ @Nullable
+ protected CharSequence getVpnMessage(boolean isDeviceManaged, boolean hasWorkProfile,
+ String vpnName, String vpnNameWorkProfile) {
+ if (vpnName == null && vpnNameWorkProfile == null) return null;
+ final SpannableStringBuilder message = new SpannableStringBuilder();
+ if (isDeviceManaged) {
+ if (vpnName != null && vpnNameWorkProfile != null) {
+ String namedVpns = mDpm.getResources().getString(
+ QS_DIALOG_MANAGEMENT_TWO_NAMED_VPN,
+ () -> mContext.getString(
+ R.string.monitoring_description_two_named_vpns,
+ vpnName, vpnNameWorkProfile),
+ vpnName, vpnNameWorkProfile);
+ message.append(namedVpns);
+ } else {
+ String name = vpnName != null ? vpnName : vpnNameWorkProfile;
+ String namedVp = mDpm.getResources().getString(
+ QS_DIALOG_MANAGEMENT_NAMED_VPN,
+ () -> mContext.getString(R.string.monitoring_description_named_vpn, name),
+ name);
+ message.append(namedVp);
+ }
+ } else {
+ if (vpnName != null && vpnNameWorkProfile != null) {
+ String namedVpns = mDpm.getResources().getString(
+ QS_DIALOG_MANAGEMENT_TWO_NAMED_VPN,
+ () -> mContext.getString(
+ R.string.monitoring_description_two_named_vpns,
+ vpnName, vpnNameWorkProfile),
+ vpnName, vpnNameWorkProfile);
+ message.append(namedVpns);
+ } else if (vpnNameWorkProfile != null) {
+ String namedVpn = mDpm.getResources().getString(
+ QS_DIALOG_WORK_PROFILE_NAMED_VPN,
+ () -> mContext.getString(
+ R.string.monitoring_description_managed_profile_named_vpn,
+ vpnNameWorkProfile),
+ vpnNameWorkProfile);
+ message.append(namedVpn);
+ } else if (hasWorkProfile) {
+ String namedVpn = mDpm.getResources().getString(
+ QS_DIALOG_PERSONAL_PROFILE_NAMED_VPN,
+ () -> mContext.getString(
+ R.string.monitoring_description_personal_profile_named_vpn,
+ vpnName),
+ vpnName);
+ message.append(namedVpn);
+ } else {
+ message.append(mContext.getString(R.string.monitoring_description_named_vpn,
+ vpnName));
+ }
+ }
+ message.append(mContext.getString(R.string.monitoring_description_vpn_settings_separator));
+ message.append(mContext.getString(R.string.monitoring_description_vpn_settings),
+ new VpnSpan(), 0);
+ return message;
+ }
+
+ @VisibleForTesting
+ CharSequence getManagementTitle(CharSequence deviceOwnerOrganization) {
+ if (deviceOwnerOrganization != null && isFinancedDevice()) {
+ return mContext.getString(R.string.monitoring_title_financed_device,
+ deviceOwnerOrganization);
+ } else {
+ return mDpm.getResources().getString(
+ QS_DIALOG_MANAGEMENT_TITLE,
+ mManagementTitleSupplier);
+ }
+ }
+
+ private boolean isFinancedDevice() {
+ return mSecurityController.isDeviceManaged()
+ && mSecurityController.getDeviceOwnerType(
+ mSecurityController.getDeviceOwnerComponentOnAnyUser())
+ == DEVICE_OWNER_TYPE_FINANCED;
+ }
+
+ protected class VpnSpan extends ClickableSpan {
+ @Override
+ public void onClick(View widget) {
+ final Intent intent = new Intent(Settings.ACTION_VPN_SETTINGS);
+ mDialog.dismiss();
+ // This dismisses the shade on opening the activity
+ mActivityStarter.postStartActivityDismissingKeyguard(intent, 0);
+ }
+
+ // for testing, to compare two CharSequences containing VpnSpans
+ @Override
+ public boolean equals(Object object) {
+ return object instanceof VpnSpan;
+ }
+
+ @Override
+ public int hashCode() {
+ return 314159257; // prime
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/footer/dagger/FooterActionsModule.kt b/packages/SystemUI/src/com/android/systemui/qs/footer/dagger/FooterActionsModule.kt
new file mode 100644
index 000000000000..38fe34eb8f9f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/footer/dagger/FooterActionsModule.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2022 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.qs.footer.dagger
+
+import com.android.systemui.qs.footer.data.repository.ForegroundServicesRepository
+import com.android.systemui.qs.footer.data.repository.ForegroundServicesRepositoryImpl
+import com.android.systemui.qs.footer.data.repository.UserSwitcherRepository
+import com.android.systemui.qs.footer.data.repository.UserSwitcherRepositoryImpl
+import com.android.systemui.qs.footer.domain.interactor.FooterActionsInteractor
+import com.android.systemui.qs.footer.domain.interactor.FooterActionsInteractorImpl
+import dagger.Binds
+import dagger.Module
+
+/** Dagger module to provide/bind footer actions singletons. */
+@Module
+interface FooterActionsModule {
+ @Binds fun userSwitcherRepository(impl: UserSwitcherRepositoryImpl): UserSwitcherRepository
+
+ @Binds
+ fun foregroundServicesRepository(
+ impl: ForegroundServicesRepositoryImpl
+ ): ForegroundServicesRepository
+
+ @Binds fun footerActionsInteractor(impl: FooterActionsInteractorImpl): FooterActionsInteractor
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/footer/data/model/UserSwitcherStatusModel.kt b/packages/SystemUI/src/com/android/systemui/qs/footer/data/model/UserSwitcherStatusModel.kt
new file mode 100644
index 000000000000..4ca229ab4e61
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/footer/data/model/UserSwitcherStatusModel.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2022 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.qs.footer.data.model
+
+import android.graphics.drawable.Drawable
+
+/** The current status of the User Switcher. */
+sealed class UserSwitcherStatusModel {
+ /** The user switcher is disabled. */
+ object Disabled : UserSwitcherStatusModel()
+
+ /** The user switcher is enabled. */
+ data class Enabled(
+ val currentUserName: String?,
+ val currentUserImage: Drawable?,
+ val isGuestUser: Boolean,
+ ) : UserSwitcherStatusModel()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/footer/data/repository/ForegroundServicesRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/footer/data/repository/ForegroundServicesRepository.kt
new file mode 100644
index 000000000000..37a9c40ffacf
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/footer/data/repository/ForegroundServicesRepository.kt
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2022 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.qs.footer.data.repository
+
+import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.qs.FgsManagerController
+import javax.inject.Inject
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.merge
+
+interface ForegroundServicesRepository {
+ /**
+ * The number of packages with a service running in the foreground.
+ *
+ * Note that this will be equal to 0 if [FgsManagerController.isAvailable] is false.
+ */
+ val foregroundServicesCount: Flow<Int>
+
+ /**
+ * Whether there were new changes to the foreground packages since a dialog was last shown.
+ *
+ * Note that this will be equal to `false` if [FgsManagerController.showFooterDot] is false.
+ */
+ val hasNewChanges: Flow<Boolean>
+}
+
+@SysUISingleton
+class ForegroundServicesRepositoryImpl
+@Inject
+constructor(
+ fgsManagerController: FgsManagerController,
+) : ForegroundServicesRepository {
+ override val foregroundServicesCount: Flow<Int> =
+ fgsManagerController.isAvailable
+ .flatMapLatest { isAvailable ->
+ if (!isAvailable) {
+ return@flatMapLatest flowOf(0)
+ }
+
+ conflatedCallbackFlow {
+ fun updateState(numberOfPackages: Int) {
+ trySendWithFailureLogging(numberOfPackages, TAG)
+ }
+
+ val listener =
+ object : FgsManagerController.OnNumberOfPackagesChangedListener {
+ override fun onNumberOfPackagesChanged(numberOfPackages: Int) {
+ updateState(numberOfPackages)
+ }
+ }
+
+ fgsManagerController.addOnNumberOfPackagesChangedListener(listener)
+ updateState(fgsManagerController.numRunningPackages)
+ awaitClose {
+ fgsManagerController.removeOnNumberOfPackagesChangedListener(listener)
+ }
+ }
+ }
+ .distinctUntilChanged()
+
+ override val hasNewChanges: Flow<Boolean> =
+ fgsManagerController.showFooterDot.flatMapLatest { showFooterDot ->
+ if (!showFooterDot) {
+ return@flatMapLatest flowOf(false)
+ }
+
+ // A flow that emits whenever the FGS dialog is dismissed.
+ val dialogDismissedEvents = conflatedCallbackFlow {
+ fun updateState() {
+ trySendWithFailureLogging(
+ Unit,
+ TAG,
+ )
+ }
+
+ val listener =
+ object : FgsManagerController.OnDialogDismissedListener {
+ override fun onDialogDismissed() {
+ updateState()
+ }
+ }
+
+ fgsManagerController.addOnDialogDismissedListener(listener)
+ awaitClose { fgsManagerController.removeOnDialogDismissedListener(listener) }
+ }
+
+ // Query [fgsManagerController.newChangesSinceDialogWasDismissed] everytime the dialog
+ // is dismissed or when [foregroundServices] is changing.
+ merge(
+ foregroundServicesCount,
+ dialogDismissedEvents,
+ )
+ .map { fgsManagerController.newChangesSinceDialogWasDismissed }
+ .distinctUntilChanged()
+ }
+
+ companion object {
+ private const val TAG = "ForegroundServicesRepositoryImpl"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/footer/data/repository/UserSwitcherRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/footer/data/repository/UserSwitcherRepository.kt
new file mode 100644
index 000000000000..e969d4c6e08a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/footer/data/repository/UserSwitcherRepository.kt
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2022 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.qs.footer.data.repository
+
+import android.content.Context
+import android.graphics.drawable.Drawable
+import android.os.Handler
+import android.os.UserManager
+import android.provider.Settings.Global.USER_SWITCHER_ENABLED
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.systemui.R
+import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.qs.SettingObserver
+import com.android.systemui.qs.footer.data.model.UserSwitcherStatusModel
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.statusbar.policy.UserInfoController
+import com.android.systemui.statusbar.policy.UserSwitcherController
+import com.android.systemui.util.settings.GlobalSettings
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+
+interface UserSwitcherRepository {
+ /** The current [UserSwitcherStatusModel]. */
+ val userSwitcherStatus: Flow<UserSwitcherStatusModel>
+}
+
+@SysUISingleton
+class UserSwitcherRepositoryImpl
+@Inject
+constructor(
+ @Application private val context: Context,
+ @Background private val bgHandler: Handler,
+ @Background private val bgDispatcher: CoroutineDispatcher,
+ private val userManager: UserManager,
+ private val userTracker: UserTracker,
+ private val userSwitcherController: UserSwitcherController,
+ private val userInfoController: UserInfoController,
+ private val globalSetting: GlobalSettings,
+) : UserSwitcherRepository {
+ private val showUserSwitcherForSingleUser =
+ context.resources.getBoolean(R.bool.qs_show_user_switcher_for_single_user)
+
+ /** Whether the user switcher is currently enabled. */
+ private val isEnabled: Flow<Boolean> = conflatedCallbackFlow {
+ suspend fun updateState() {
+ trySendWithFailureLogging(isUserSwitcherEnabled(), TAG)
+ }
+
+ val observer =
+ object :
+ SettingObserver(
+ globalSetting,
+ bgHandler,
+ USER_SWITCHER_ENABLED,
+ userTracker.userId,
+ ) {
+ override fun handleValueChanged(value: Int, observedChange: Boolean) {
+ if (observedChange) {
+ launch { updateState() }
+ }
+ }
+ }
+
+ observer.isListening = true
+ updateState()
+ awaitClose { observer.isListening = false }
+ }
+
+ /** The current user name. */
+ private val currentUserName: Flow<String?> = conflatedCallbackFlow {
+ suspend fun updateState() {
+ trySendWithFailureLogging(getCurrentUser(), TAG)
+ }
+
+ val callback = UserSwitcherController.UserSwitchCallback { launch { updateState() } }
+
+ userSwitcherController.addUserSwitchCallback(callback)
+ updateState()
+ awaitClose { userSwitcherController.removeUserSwitchCallback(callback) }
+ }
+
+ /** The current (icon, isGuestUser) values. */
+ // TODO(b/242040009): Could we only use this callback to get the user name and remove
+ // currentUsername above?
+ private val currentUserInfo: Flow<Pair<Drawable?, Boolean>> = conflatedCallbackFlow {
+ val listener =
+ UserInfoController.OnUserInfoChangedListener { _, picture, _ ->
+ launch { trySendWithFailureLogging(picture to isGuestUser(), TAG) }
+ }
+
+ // This will automatically call the listener when attached, so no need to update the state
+ // here.
+ userInfoController.addCallback(listener)
+ awaitClose { userInfoController.removeCallback(listener) }
+ }
+
+ override val userSwitcherStatus: Flow<UserSwitcherStatusModel> =
+ isEnabled
+ .flatMapLatest { enabled ->
+ if (enabled) {
+ combine(currentUserName, currentUserInfo) { name, (icon, isGuest) ->
+ UserSwitcherStatusModel.Enabled(name, icon, isGuest)
+ }
+ } else {
+ flowOf(UserSwitcherStatusModel.Disabled)
+ }
+ }
+ .distinctUntilChanged()
+
+ private suspend fun isUserSwitcherEnabled(): Boolean {
+ return withContext(bgDispatcher) {
+ userManager.isUserSwitcherEnabled(showUserSwitcherForSingleUser)
+ }
+ }
+
+ private suspend fun getCurrentUser(): String? {
+ return withContext(bgDispatcher) { userSwitcherController.currentUserName }
+ }
+
+ private suspend fun isGuestUser(): Boolean {
+ return withContext(bgDispatcher) {
+ userManager.isGuestUser(KeyguardUpdateMonitor.getCurrentUser())
+ }
+ }
+
+ companion object {
+ private const val TAG = "UserSwitcherRepositoryImpl"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractor.kt
new file mode 100644
index 000000000000..cf9b41c25388
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractor.kt
@@ -0,0 +1,211 @@
+/*
+ * Copyright (C) 2022 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.qs.footer.domain.interactor
+
+import android.app.admin.DevicePolicyEventLogger
+import android.app.admin.DevicePolicyManager
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.os.UserHandle
+import android.provider.Settings
+import android.view.View
+import com.android.internal.jank.InteractionJankMonitor
+import com.android.internal.logging.MetricsLogger
+import com.android.internal.logging.UiEventLogger
+import com.android.internal.logging.nano.MetricsProto
+import com.android.internal.util.FrameworkStatsLog
+import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.animation.Expandable
+import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.globalactions.GlobalActionsDialogLite
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.qs.FgsManagerController
+import com.android.systemui.qs.QSSecurityFooterUtils
+import com.android.systemui.qs.footer.data.model.UserSwitcherStatusModel
+import com.android.systemui.qs.footer.data.repository.ForegroundServicesRepository
+import com.android.systemui.qs.footer.data.repository.UserSwitcherRepository
+import com.android.systemui.qs.footer.domain.model.SecurityButtonConfig
+import com.android.systemui.qs.user.UserSwitchDialogController
+import com.android.systemui.security.data.repository.SecurityRepository
+import com.android.systemui.statusbar.policy.DeviceProvisionedController
+import com.android.systemui.user.UserSwitcherActivity
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.withContext
+
+/** Interactor for the footer actions business logic. */
+interface FooterActionsInteractor {
+ /** The current [SecurityButtonConfig]. */
+ val securityButtonConfig: Flow<SecurityButtonConfig?>
+
+ /** The number of packages with a service running in the foreground. */
+ val foregroundServicesCount: Flow<Int>
+
+ /** Whether there are new packages with a service running in the foreground. */
+ val hasNewForegroundServices: Flow<Boolean>
+
+ /** The current [UserSwitcherStatusModel]. */
+ val userSwitcherStatus: Flow<UserSwitcherStatusModel>
+
+ /**
+ * The flow emitting `Unit` whenever a request to show the device monitoring dialog is fired.
+ */
+ val deviceMonitoringDialogRequests: Flow<Unit>
+
+ /**
+ * Show the device monitoring dialog, expanded from [view].
+ *
+ * Important: [view] must be associated to the same [Context] as the [Quick Settings fragment]
+ * [com.android.systemui.qs.QSFragment].
+ */
+ // TODO(b/230830644): Replace view by Expandable interface.
+ fun showDeviceMonitoringDialog(view: View)
+
+ /**
+ * Show the device monitoring dialog.
+ *
+ * Important: [quickSettingsContext] *must* be the [Context] associated to the [Quick Settings
+ * fragment][com.android.systemui.qs.QSFragment].
+ */
+ // TODO(b/230830644): Replace view by Expandable interface.
+ fun showDeviceMonitoringDialog(quickSettingsContext: Context)
+
+ /** Show the foreground services dialog. */
+ // TODO(b/230830644): Replace view by Expandable interface.
+ fun showForegroundServicesDialog(view: View)
+
+ /** Show the power menu dialog. */
+ // TODO(b/230830644): Replace view by Expandable interface.
+ fun showPowerMenuDialog(globalActionsDialogLite: GlobalActionsDialogLite, view: View)
+
+ /** Show the settings. */
+ fun showSettings(expandable: Expandable)
+
+ /** Show the user switcher. */
+ // TODO(b/230830644): Replace view by Expandable interface.
+ fun showUserSwitcher(view: View)
+}
+
+@SysUISingleton
+class FooterActionsInteractorImpl
+@Inject
+constructor(
+ private val activityStarter: ActivityStarter,
+ private val featureFlags: FeatureFlags,
+ private val metricsLogger: MetricsLogger,
+ private val uiEventLogger: UiEventLogger,
+ private val deviceProvisionedController: DeviceProvisionedController,
+ private val qsSecurityFooterUtils: QSSecurityFooterUtils,
+ private val fgsManagerController: FgsManagerController,
+ private val userSwitchDialogController: UserSwitchDialogController,
+ securityRepository: SecurityRepository,
+ foregroundServicesRepository: ForegroundServicesRepository,
+ userSwitcherRepository: UserSwitcherRepository,
+ broadcastDispatcher: BroadcastDispatcher,
+ @Background bgDispatcher: CoroutineDispatcher,
+) : FooterActionsInteractor {
+ override val securityButtonConfig: Flow<SecurityButtonConfig?> =
+ securityRepository.security.map { security ->
+ withContext(bgDispatcher) { qsSecurityFooterUtils.getButtonConfig(security) }
+ }
+
+ override val foregroundServicesCount: Flow<Int> =
+ foregroundServicesRepository.foregroundServicesCount
+
+ override val hasNewForegroundServices: Flow<Boolean> =
+ foregroundServicesRepository.hasNewChanges
+
+ override val userSwitcherStatus: Flow<UserSwitcherStatusModel> =
+ userSwitcherRepository.userSwitcherStatus
+
+ override val deviceMonitoringDialogRequests: Flow<Unit> =
+ broadcastDispatcher.broadcastFlow(
+ IntentFilter(DevicePolicyManager.ACTION_SHOW_DEVICE_MONITORING_DIALOG),
+ UserHandle.ALL,
+ Context.RECEIVER_EXPORTED,
+ null,
+ )
+
+ override fun showDeviceMonitoringDialog(view: View) {
+ qsSecurityFooterUtils.showDeviceMonitoringDialog(view.context, view)
+ DevicePolicyEventLogger.createEvent(
+ FrameworkStatsLog.DEVICE_POLICY_EVENT__EVENT_ID__DO_USER_INFO_CLICKED
+ )
+ .write()
+ }
+
+ override fun showDeviceMonitoringDialog(quickSettingsContext: Context) {
+ qsSecurityFooterUtils.showDeviceMonitoringDialog(quickSettingsContext, /* view= */ null)
+ }
+
+ override fun showForegroundServicesDialog(view: View) {
+ fgsManagerController.showDialog(view)
+ }
+
+ override fun showPowerMenuDialog(globalActionsDialogLite: GlobalActionsDialogLite, view: View) {
+ uiEventLogger.log(GlobalActionsDialogLite.GlobalActionsEvent.GA_OPEN_QS)
+ globalActionsDialogLite.showOrHideDialog(
+ /* keyguardShowing= */ false,
+ /* isDeviceProvisioned= */ true,
+ view,
+ )
+ }
+
+ override fun showSettings(expandable: Expandable) {
+ if (!deviceProvisionedController.isCurrentUserSetup) {
+ // If user isn't setup just unlock the device and dump them back at SUW.
+ activityStarter.postQSRunnableDismissingKeyguard {}
+ return
+ }
+
+ metricsLogger.action(MetricsProto.MetricsEvent.ACTION_QS_EXPANDED_SETTINGS_LAUNCH)
+ activityStarter.startActivity(
+ Intent(Settings.ACTION_SETTINGS),
+ true /* dismissShade */,
+ expandable.activityLaunchController(
+ InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_SETTINGS_BUTTON
+ ),
+ )
+ }
+
+ override fun showUserSwitcher(view: View) {
+ if (!featureFlags.isEnabled(Flags.FULL_SCREEN_USER_SWITCHER)) {
+ userSwitchDialogController.showDialog(view)
+ return
+ }
+
+ val intent =
+ Intent(view.context, UserSwitcherActivity::class.java).apply {
+ addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK)
+ }
+
+ activityStarter.startActivity(
+ intent,
+ true /* dismissShade */,
+ ActivityLaunchAnimator.Controller.fromView(view, null),
+ true /* showOverlockscreenwhenlocked */,
+ UserHandle.SYSTEM,
+ )
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/footer/domain/model/SecurityButtonConfig.kt b/packages/SystemUI/src/com/android/systemui/qs/footer/domain/model/SecurityButtonConfig.kt
new file mode 100644
index 000000000000..be9c0c1de799
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/footer/domain/model/SecurityButtonConfig.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2022 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.qs.footer.domain.model
+
+import com.android.systemui.common.shared.model.Icon
+
+/** The config for the security button. */
+data class SecurityButtonConfig(
+ val icon: Icon,
+ val text: String,
+ val isClickable: Boolean,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/binder/FooterActionsViewBinder.kt b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/binder/FooterActionsViewBinder.kt
new file mode 100644
index 000000000000..8dd506ec8775
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/binder/FooterActionsViewBinder.kt
@@ -0,0 +1,321 @@
+/*
+ * Copyright (C) 2022 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.qs.footer.ui.binder
+
+import android.content.Context
+import android.graphics.PorterDuff
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.ImageView
+import android.widget.LinearLayout
+import android.widget.TextView
+import androidx.core.view.isInvisible
+import androidx.core.view.isVisible
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.lifecycleScope
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.R
+import com.android.systemui.common.ui.binder.ContentDescriptionViewBinder
+import com.android.systemui.common.ui.binder.IconViewBinder
+import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.people.ui.view.PeopleViewBinder.bind
+import com.android.systemui.qs.FooterActionsView
+import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsButtonViewModel
+import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsForegroundServicesButtonViewModel
+import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsSecurityButtonViewModel
+import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.launch
+
+/** A ViewBinder for [FooterActionsViewBinder]. */
+object FooterActionsViewBinder {
+ /**
+ * Create a [FooterActionsView] that can later be [bound][bind] to a [FooterActionsViewModel].
+ */
+ @JvmStatic
+ fun create(context: Context): FooterActionsView {
+ return LayoutInflater.from(context).inflate(R.layout.footer_actions, /* root= */ null)
+ as FooterActionsView
+ }
+
+ /** Bind [view] to [viewModel]. */
+ @JvmStatic
+ fun bind(
+ view: FooterActionsView,
+ viewModel: FooterActionsViewModel,
+ qsVisibilityLifecycleOwner: LifecycleOwner,
+ ) {
+ // Remove all children of the FooterActionsView that are used by the old implementation.
+ // TODO(b/242040009): Clean up the XML once the old implementation is removed.
+ view.removeAllViews()
+
+ // Add the views used by this new implementation.
+ val context = view.context
+ val inflater = LayoutInflater.from(context)
+
+ val securityHolder = TextButtonViewHolder.createAndAdd(inflater, view)
+ val foregroundServicesWithTextHolder = TextButtonViewHolder.createAndAdd(inflater, view)
+ val foregroundServicesWithNumberHolder = NumberButtonViewHolder.createAndAdd(inflater, view)
+ val userSwitcherHolder = IconButtonViewHolder.createAndAdd(inflater, view, isLast = false)
+ val settingsHolder =
+ IconButtonViewHolder.createAndAdd(inflater, view, isLast = viewModel.power == null)
+
+ // Bind the static power and settings buttons.
+ bindButton(settingsHolder, viewModel.settings)
+
+ if (viewModel.power != null) {
+ val powerHolder = IconButtonViewHolder.createAndAdd(inflater, view, isLast = true)
+ bindButton(powerHolder, viewModel.power)
+ }
+
+ // There are 2 lifecycle scopes we are using here:
+ // 1) The scope created by [repeatWhenAttached] when [view] is attached, and destroyed
+ // when the [view] is detached. We use this as the parent scope for all our [viewModel]
+ // state collection, given that we don't want to do any work when [view] is detached.
+ // 2) The scope owned by [lifecycleOwner], which should be RESUMED only when Quick
+ // Settings are visible. We use this to make sure we collect UI state only when the
+ // View is visible.
+ //
+ // Given that we start our collection when the Quick Settings become visible, which happens
+ // every time the user swipes down the shade, we remember our previous UI state already
+ // bound to the UI to avoid binding the same values over and over for nothing.
+
+ // TODO(b/242040009): Look into using only a single scope.
+
+ var previousSecurity: FooterActionsSecurityButtonViewModel? = null
+ var previousForegroundServices: FooterActionsForegroundServicesButtonViewModel? = null
+ var previousUserSwitcher: FooterActionsButtonViewModel? = null
+
+ view.repeatWhenAttached {
+ val attachedScope = this.lifecycleScope
+
+ attachedScope.launch {
+ // Listen for dialog requests as soon as we are attached, even when not visible.
+ // TODO(b/242040009): Should this move somewhere else?
+ launch { viewModel.observeDeviceMonitoringDialogRequests(view.context) }
+
+ // Make sure we set the correct visibility and alpha even when QS are not currently
+ // shown.
+ launch {
+ viewModel.isVisible.collect { isVisible -> view.isInvisible = !isVisible }
+ }
+
+ launch { viewModel.alpha.collect { view.alpha = it } }
+ launch { viewModel.backgroundAlpha.collect { view.backgroundAlpha = it } }
+ }
+
+ // Listen for model changes only when QS are visible.
+ qsVisibilityLifecycleOwner.repeatOnLifecycle(Lifecycle.State.RESUMED) {
+ // Security.
+ launch {
+ viewModel.security.collect { security ->
+ if (previousSecurity != security) {
+ bindSecurity(securityHolder, security)
+ previousSecurity = security
+ }
+ }
+ }
+
+ // Foreground services.
+ launch {
+ viewModel.foregroundServices.collect { foregroundServices ->
+ if (previousForegroundServices != foregroundServices) {
+ bindForegroundService(
+ foregroundServicesWithNumberHolder,
+ foregroundServicesWithTextHolder,
+ foregroundServices,
+ )
+ previousForegroundServices = foregroundServices
+ }
+ }
+ }
+
+ // User switcher.
+ launch {
+ viewModel.userSwitcher.collect { userSwitcher ->
+ if (previousUserSwitcher != userSwitcher) {
+ bindButton(userSwitcherHolder, userSwitcher)
+ previousUserSwitcher = userSwitcher
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private fun bindSecurity(
+ securityHolder: TextButtonViewHolder,
+ security: FooterActionsSecurityButtonViewModel?,
+ ) {
+ val securityView = securityHolder.view
+ securityView.isVisible = security != null
+ if (security == null) {
+ return
+ }
+
+ // Make sure that the chevron is visible and that the button is clickable if there is a
+ // listener.
+ val chevron = securityHolder.chevron
+ if (security.onClick != null) {
+ securityView.isClickable = true
+ securityView.setOnClickListener(security.onClick)
+ chevron.isVisible = true
+ } else {
+ securityView.isClickable = false
+ securityView.setOnClickListener(null)
+ chevron.isVisible = false
+ }
+
+ securityHolder.text.text = security.text
+ securityHolder.newDot.isVisible = false
+ IconViewBinder.bind(security.icon, securityHolder.icon)
+ }
+
+ private fun bindForegroundService(
+ foregroundServicesWithNumberHolder: NumberButtonViewHolder,
+ foregroundServicesWithTextHolder: TextButtonViewHolder,
+ foregroundServices: FooterActionsForegroundServicesButtonViewModel?,
+ ) {
+ val foregroundServicesWithNumberView = foregroundServicesWithNumberHolder.view
+ val foregroundServicesWithTextView = foregroundServicesWithTextHolder.view
+ if (foregroundServices == null) {
+ foregroundServicesWithNumberView.isVisible = false
+ foregroundServicesWithTextView.isVisible = false
+ return
+ }
+
+ val foregroundServicesCount = foregroundServices.foregroundServicesCount
+ if (foregroundServices.displayText) {
+ // Button with text, icon and chevron.
+ foregroundServicesWithNumberView.isVisible = false
+
+ foregroundServicesWithTextView.isVisible = true
+ foregroundServicesWithTextView.setOnClickListener(foregroundServices.onClick)
+ foregroundServicesWithTextHolder.text.text = foregroundServices.text
+ foregroundServicesWithTextHolder.newDot.isVisible = foregroundServices.hasNewChanges
+ } else {
+ // Small button with the number only.
+ foregroundServicesWithTextView.isVisible = false
+
+ foregroundServicesWithNumberView.visibility = View.VISIBLE
+ foregroundServicesWithNumberView.setOnClickListener(foregroundServices.onClick)
+ foregroundServicesWithNumberHolder.number.text = foregroundServicesCount.toString()
+ foregroundServicesWithNumberHolder.number.contentDescription = foregroundServices.text
+ foregroundServicesWithNumberHolder.newDot.isVisible = foregroundServices.hasNewChanges
+ }
+ }
+
+ private fun bindButton(button: IconButtonViewHolder, model: FooterActionsButtonViewModel?) {
+ val buttonView = button.view
+ buttonView.isVisible = model != null
+ if (model == null) {
+ return
+ }
+
+ buttonView.setBackgroundResource(model.background)
+ buttonView.setOnClickListener(model.onClick)
+
+ val icon = model.icon
+ val iconView = button.icon
+ val contentDescription = model.contentDescription
+
+ IconViewBinder.bind(icon, iconView)
+ ContentDescriptionViewBinder.bind(contentDescription, iconView)
+ if (model.iconTint != null) {
+ iconView.setColorFilter(model.iconTint, PorterDuff.Mode.SRC_IN)
+ } else {
+ iconView.clearColorFilter()
+ }
+ }
+}
+
+private class TextButtonViewHolder(val view: View) {
+ val icon = view.requireViewById<ImageView>(R.id.icon)
+ val text = view.requireViewById<TextView>(R.id.text)
+ val newDot = view.requireViewById<ImageView>(R.id.new_dot)
+ val chevron = view.requireViewById<ImageView>(R.id.chevron_icon)
+
+ companion object {
+ fun createAndAdd(inflater: LayoutInflater, root: ViewGroup): TextButtonViewHolder {
+ val view =
+ inflater.inflate(
+ R.layout.footer_actions_text_button,
+ /* root= */ root,
+ /* attachToRoot= */ false,
+ )
+ root.addView(view)
+ return TextButtonViewHolder(view)
+ }
+ }
+}
+
+private class NumberButtonViewHolder(val view: View) {
+ val number = view.requireViewById<TextView>(R.id.number)
+ val newDot = view.requireViewById<ImageView>(R.id.new_dot)
+
+ companion object {
+ fun createAndAdd(inflater: LayoutInflater, root: ViewGroup): NumberButtonViewHolder {
+ val view =
+ inflater.inflate(
+ R.layout.footer_actions_number_button,
+ /* root= */ root,
+ /* attachToRoot= */ false,
+ )
+ root.addView(view)
+ return NumberButtonViewHolder(view)
+ }
+ }
+}
+
+private class IconButtonViewHolder(val view: View) {
+ val icon = view.requireViewById<ImageView>(R.id.icon)
+
+ companion object {
+ fun createAndAdd(
+ inflater: LayoutInflater,
+ root: ViewGroup,
+ isLast: Boolean,
+ ): IconButtonViewHolder {
+ val view =
+ inflater.inflate(
+ R.layout.footer_actions_icon_button,
+ /* root= */ root,
+ /* attachToRoot= */ false,
+ )
+
+ // All buttons have a background with an inset of qs_footer_action_inset, so the last
+ // button must have a negative inset of -qs_footer_action_inset to compensate and be
+ // aligned with its parent.
+ val marginEnd =
+ if (isLast) {
+ -view.context.resources.getDimensionPixelSize(R.dimen.qs_footer_action_inset)
+ } else {
+ 0
+ }
+
+ val size =
+ view.context.resources.getDimensionPixelSize(R.dimen.qs_footer_action_button_size)
+ root.addView(
+ view,
+ LinearLayout.LayoutParams(size, size).apply { this.marginEnd = marginEnd },
+ )
+ return IconButtonViewHolder(view)
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsButtonViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsButtonViewModel.kt
new file mode 100644
index 000000000000..4c0879e225c1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsButtonViewModel.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2022 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.qs.footer.ui.viewmodel
+
+import android.annotation.DrawableRes
+import android.view.View
+import com.android.systemui.common.shared.model.ContentDescription
+import com.android.systemui.common.shared.model.Icon
+
+/**
+ * A ViewModel for a simple footer actions button. This is used for the user switcher, settings and
+ * power buttons.
+ */
+data class FooterActionsButtonViewModel(
+ val icon: Icon,
+ val iconTint: Int?,
+ @DrawableRes val background: Int,
+ val contentDescription: ContentDescription,
+ // TODO(b/230830644): Replace View by an Expandable interface that can expand in either dialog
+ // or activity.
+ val onClick: (View) -> Unit,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsForegroundServicesButtonViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsForegroundServicesButtonViewModel.kt
new file mode 100644
index 000000000000..98b53cb0ed5a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsForegroundServicesButtonViewModel.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2022 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.qs.footer.ui.viewmodel
+
+import android.view.View
+
+/** A ViewModel for the foreground services button. */
+data class FooterActionsForegroundServicesButtonViewModel(
+ val foregroundServicesCount: Int,
+ val text: String,
+ val displayText: Boolean,
+ val hasNewChanges: Boolean,
+ val onClick: (View) -> Unit,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsSecurityButtonViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsSecurityButtonViewModel.kt
new file mode 100644
index 000000000000..98ab129fc9de
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsSecurityButtonViewModel.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2022 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.qs.footer.ui.viewmodel
+
+import android.view.View
+import com.android.systemui.common.shared.model.Icon
+
+/** A ViewModel for the security button. */
+data class FooterActionsSecurityButtonViewModel(
+ val icon: Icon,
+ val text: String,
+ val onClick: ((View) -> Unit)?,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt
new file mode 100644
index 000000000000..b556a3e0d66b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt
@@ -0,0 +1,310 @@
+/*
+ * Copyright (C) 2022 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.qs.footer.ui.viewmodel
+
+import android.content.Context
+import android.util.Log
+import android.view.View
+import androidx.lifecycle.DefaultLifecycleObserver
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleOwner
+import com.android.settingslib.Utils
+import com.android.settingslib.drawable.UserIconDrawable
+import com.android.systemui.R
+import com.android.systemui.animation.Expandable
+import com.android.systemui.common.shared.model.ContentDescription
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.globalactions.GlobalActionsDialogLite
+import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.qs.dagger.QSFlagsModule.PM_LITE_ENABLED
+import com.android.systemui.qs.footer.data.model.UserSwitcherStatusModel
+import com.android.systemui.qs.footer.domain.interactor.FooterActionsInteractor
+import com.android.systemui.util.icuMessageFormat
+import javax.inject.Inject
+import javax.inject.Named
+import javax.inject.Provider
+import kotlin.math.max
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.map
+
+/** A ViewModel for the footer actions. */
+class FooterActionsViewModel(
+ @Application private val context: Context,
+ private val footerActionsInteractor: FooterActionsInteractor,
+ private val falsingManager: FalsingManager,
+ private val globalActionsDialogLite: GlobalActionsDialogLite,
+ showPowerButton: Boolean,
+) {
+ /**
+ * Whether the UI rendering this ViewModel should be visible. Note that even when this is false,
+ * the UI should still participate to the layout it is included in (i.e. in the View world it
+ * should be INVISIBLE, not GONE).
+ */
+ private val _isVisible = MutableStateFlow(true)
+ val isVisible: StateFlow<Boolean> = _isVisible.asStateFlow()
+
+ /** The alpha the UI rendering this ViewModel should have. */
+ private val _alpha = MutableStateFlow(1f)
+ val alpha: StateFlow<Float> = _alpha.asStateFlow()
+
+ /** The alpha the background of the UI rendering this ViewModel should have. */
+ private val _backgroundAlpha = MutableStateFlow(1f)
+ val backgroundAlpha: StateFlow<Float> = _backgroundAlpha.asStateFlow()
+
+ /** The model for the security button. */
+ val security: Flow<FooterActionsSecurityButtonViewModel?> =
+ footerActionsInteractor.securityButtonConfig
+ .map { config ->
+ val (icon, text, isClickable) = config ?: return@map null
+ FooterActionsSecurityButtonViewModel(
+ icon,
+ text,
+ if (isClickable) this::onSecurityButtonClicked else null,
+ )
+ }
+ .distinctUntilChanged()
+
+ /** The model for the foreground services button. */
+ val foregroundServices: Flow<FooterActionsForegroundServicesButtonViewModel?> =
+ combine(
+ footerActionsInteractor.foregroundServicesCount,
+ footerActionsInteractor.hasNewForegroundServices,
+ security,
+ ) { foregroundServicesCount, hasNewChanges, securityModel ->
+ if (foregroundServicesCount <= 0) {
+ return@combine null
+ }
+
+ val text =
+ icuMessageFormat(
+ context.resources,
+ R.string.fgs_manager_footer_label,
+ foregroundServicesCount,
+ )
+ FooterActionsForegroundServicesButtonViewModel(
+ foregroundServicesCount,
+ text = text,
+ displayText = securityModel == null,
+ hasNewChanges = hasNewChanges,
+ this::onForegroundServiceButtonClicked,
+ )
+ }
+ .distinctUntilChanged()
+
+ /** The model for the user switcher button. */
+ val userSwitcher: Flow<FooterActionsButtonViewModel?> =
+ footerActionsInteractor.userSwitcherStatus
+ .map { userSwitcherStatus ->
+ when (userSwitcherStatus) {
+ UserSwitcherStatusModel.Disabled -> null
+ is UserSwitcherStatusModel.Enabled -> {
+ if (userSwitcherStatus.currentUserImage == null) {
+ Log.e(
+ TAG,
+ "Skipped the addition of user switcher button because " +
+ "currentUserImage is missing",
+ )
+ return@map null
+ }
+
+ userSwitcherButton(userSwitcherStatus)
+ }
+ }
+ }
+ .distinctUntilChanged()
+
+ /** The model for the settings button. */
+ val settings: FooterActionsButtonViewModel =
+ FooterActionsButtonViewModel(
+ Icon.Resource(R.drawable.ic_settings),
+ iconTint = null,
+ R.drawable.qs_footer_action_circle,
+ ContentDescription.Resource(R.string.accessibility_quick_settings_settings),
+ this::onSettingsButtonClicked,
+ )
+
+ /** The model for the power button. */
+ val power: FooterActionsButtonViewModel? =
+ if (showPowerButton) {
+ FooterActionsButtonViewModel(
+ Icon.Resource(android.R.drawable.ic_lock_power_off),
+ iconTint =
+ Utils.getColorAttrDefaultColor(
+ context,
+ com.android.internal.R.attr.textColorOnAccent,
+ ),
+ R.drawable.qs_footer_action_circle_color,
+ ContentDescription.Resource(R.string.accessibility_quick_settings_power_menu),
+ this::onPowerButtonClicked,
+ )
+ } else {
+ null
+ }
+
+ /** Called when the visibility of the UI rendering this model should be changed. */
+ fun onVisibilityChangeRequested(visible: Boolean) {
+ _isVisible.value = visible
+ }
+
+ /** Called when the expansion of the Quick Settings changed. */
+ fun onQuickSettingsExpansionChanged(expansion: Float, isInSplitShade: Boolean) {
+ if (isInSplitShade) {
+ // In split shade, we want to fade in the background only at the very end (see
+ // b/240563302).
+ val delay = 0.99f
+ _alpha.value = expansion
+ _backgroundAlpha.value = max(0f, expansion - delay) / (1f - delay)
+ } else {
+ // Only start fading in the footer actions when we are at least 90% expanded.
+ val delay = 0.9f
+ _alpha.value = max(0f, expansion - delay) / (1 - delay)
+ _backgroundAlpha.value = 1f
+ }
+ }
+
+ /**
+ * Observe the device monitoring dialog requests and show the dialog accordingly. This function
+ * will suspend indefinitely and will need to be cancelled to stop observing.
+ *
+ * Important: [quickSettingsContext] must be the [Context] associated to the [Quick Settings
+ * fragment][com.android.systemui.qs.QSFragment], and the call to this function must be
+ * cancelled when that fragment is destroyed.
+ */
+ suspend fun observeDeviceMonitoringDialogRequests(quickSettingsContext: Context) {
+ footerActionsInteractor.deviceMonitoringDialogRequests.collect {
+ footerActionsInteractor.showDeviceMonitoringDialog(quickSettingsContext)
+ }
+ }
+
+ private fun onSecurityButtonClicked(view: View) {
+ if (falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
+ return
+ }
+
+ footerActionsInteractor.showDeviceMonitoringDialog(view)
+ }
+
+ private fun onForegroundServiceButtonClicked(view: View) {
+ if (falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
+ return
+ }
+
+ footerActionsInteractor.showForegroundServicesDialog(view)
+ }
+
+ private fun onUserSwitcherClicked(view: View) {
+ if (falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
+ return
+ }
+
+ footerActionsInteractor.showUserSwitcher(view)
+ }
+
+ // TODO(b/230830644): Replace View by an Expandable interface that can expand in either dialog
+ // or activity.
+ private fun onSettingsButtonClicked(view: View) {
+ if (falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
+ return
+ }
+
+ footerActionsInteractor.showSettings(Expandable.fromView(view))
+ }
+
+ private fun onPowerButtonClicked(view: View) {
+ if (falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
+ return
+ }
+
+ footerActionsInteractor.showPowerMenuDialog(globalActionsDialogLite, view)
+ }
+
+ private fun userSwitcherButton(
+ status: UserSwitcherStatusModel.Enabled
+ ): FooterActionsButtonViewModel {
+ val icon = status.currentUserImage!!
+ val iconTint =
+ if (status.isGuestUser && icon !is UserIconDrawable) {
+ Utils.getColorAttrDefaultColor(context, android.R.attr.colorForeground)
+ } else {
+ null
+ }
+
+ return FooterActionsButtonViewModel(
+ Icon.Loaded(icon),
+ iconTint,
+ R.drawable.qs_footer_action_circle,
+ ContentDescription.Loaded(userSwitcherContentDescription(status.currentUserName)),
+ this::onUserSwitcherClicked,
+ )
+ }
+
+ private fun userSwitcherContentDescription(currentUser: String?): String? {
+ return currentUser?.let { user ->
+ context.getString(R.string.accessibility_quick_settings_user, user)
+ }
+ }
+
+ @SysUISingleton
+ class Factory
+ @Inject
+ constructor(
+ @Application private val context: Context,
+ private val falsingManager: FalsingManager,
+ private val footerActionsInteractor: FooterActionsInteractor,
+ private val globalActionsDialogLiteProvider: Provider<GlobalActionsDialogLite>,
+ @Named(PM_LITE_ENABLED) private val showPowerButton: Boolean,
+ ) {
+ /** Create a [FooterActionsViewModel] bound to the lifecycle of [lifecycleOwner]. */
+ fun create(lifecycleOwner: LifecycleOwner): FooterActionsViewModel {
+ val globalActionsDialogLite = globalActionsDialogLiteProvider.get()
+ if (lifecycleOwner.lifecycle.currentState == Lifecycle.State.DESTROYED) {
+ // This should usually not happen, but let's make sure we already destroy
+ // globalActionsDialogLite.
+ globalActionsDialogLite.destroy()
+ } else {
+ // Destroy globalActionsDialogLite when the lifecycle is destroyed.
+ lifecycleOwner.lifecycle.addObserver(
+ object : DefaultLifecycleObserver {
+ override fun onDestroy(owner: LifecycleOwner) {
+ globalActionsDialogLite.destroy()
+ }
+ }
+ )
+ }
+
+ return FooterActionsViewModel(
+ context,
+ footerActionsInteractor,
+ falsingManager,
+ globalActionsDialogLite,
+ showPowerButton,
+ )
+ }
+ }
+
+ companion object {
+ private const val TAG = "FooterActionsViewModel"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
index 5147d5934039..2731d64ee4e7 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
@@ -44,6 +44,7 @@ import com.android.settingslib.Utils
import com.android.systemui.FontSizeUtils
import com.android.systemui.R
import com.android.systemui.animation.LaunchableView
+import com.android.systemui.animation.LaunchableViewDelegate
import com.android.systemui.plugins.qs.QSIconView
import com.android.systemui.plugins.qs.QSTile
import com.android.systemui.plugins.qs.QSTile.BooleanState
@@ -138,8 +139,11 @@ open class QSTileViewImpl @JvmOverloads constructor(
private var lastStateDescription: CharSequence? = null
private var tileState = false
private var lastState = INVALID
- private var blockVisibilityChanges = false
- private var lastVisibility = View.VISIBLE
+ private val launchableViewDelegate = LaunchableViewDelegate(
+ this,
+ superSetVisibility = { super.setVisibility(it) },
+ superSetTransitionVisibility = { super.setTransitionVisibility(it) },
+ )
private val locInScreen = IntArray(2)
@@ -343,33 +347,15 @@ open class QSTileViewImpl @JvmOverloads constructor(
}
override fun setShouldBlockVisibilityChanges(block: Boolean) {
- blockVisibilityChanges = block
-
- if (block) {
- lastVisibility = visibility
- } else {
- visibility = lastVisibility
- }
+ launchableViewDelegate.setShouldBlockVisibilityChanges(block)
}
override fun setVisibility(visibility: Int) {
- if (blockVisibilityChanges) {
- lastVisibility = visibility
- return
- }
-
- super.setVisibility(visibility)
+ launchableViewDelegate.setVisibility(visibility)
}
override fun setTransitionVisibility(visibility: Int) {
- if (blockVisibilityChanges) {
- // View.setTransitionVisibility just sets the visibility flag, so we don't have to save
- // the transition visibility separately from the normal visibility.
- lastVisibility = visibility
- return
- }
-
- super.setTransitionVisibility(visibility)
+ launchableViewDelegate.setTransitionVisibility(visibility)
}
// Accessibility
diff --git a/packages/SystemUI/src/com/android/systemui/security/data/model/SecurityModel.kt b/packages/SystemUI/src/com/android/systemui/security/data/model/SecurityModel.kt
new file mode 100644
index 000000000000..50af260684f8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/security/data/model/SecurityModel.kt
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2022 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.security.data.model
+
+import android.graphics.drawable.Drawable
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.statusbar.policy.SecurityController
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.withContext
+
+/** The security info exposed by [com.android.systemui.statusbar.policy.SecurityController]. */
+// TODO(b/242040009): Consider splitting this model into smaller submodels.
+data class SecurityModel(
+ val isDeviceManaged: Boolean,
+ val hasWorkProfile: Boolean,
+ val isWorkProfileOn: Boolean,
+ val isProfileOwnerOfOrganizationOwnedDevice: Boolean,
+ val deviceOwnerOrganizationName: String?,
+ val workProfileOrganizationName: String?,
+ val isNetworkLoggingEnabled: Boolean,
+ val isVpnBranded: Boolean,
+ val primaryVpnName: String?,
+ val workProfileVpnName: String?,
+ val hasCACertInCurrentUser: Boolean,
+ val hasCACertInWorkProfile: Boolean,
+ val isParentalControlsEnabled: Boolean,
+ val deviceAdminIcon: Drawable?,
+) {
+ companion object {
+ /** Create a [SecurityModel] from the current [securityController] state. */
+ suspend fun create(
+ securityController: SecurityController,
+ @Background bgDispatcher: CoroutineDispatcher,
+ ): SecurityModel {
+ return withContext(bgDispatcher) { create(securityController) }
+ }
+
+ /**
+ * Create a [SecurityModel] from the current [securityController] state.
+ *
+ * Important: This method should be called from a background thread as this will do a lot of
+ * binder calls.
+ */
+ // TODO(b/242040009): Remove this.
+ @JvmStatic
+ fun create(securityController: SecurityController): SecurityModel {
+ val deviceAdminInfo =
+ if (securityController.isParentalControlsEnabled) {
+ securityController.deviceAdminInfo
+ } else {
+ null
+ }
+
+ return SecurityModel(
+ isDeviceManaged = securityController.isDeviceManaged,
+ hasWorkProfile = securityController.hasWorkProfile(),
+ isWorkProfileOn = securityController.isWorkProfileOn,
+ isProfileOwnerOfOrganizationOwnedDevice =
+ securityController.isProfileOwnerOfOrganizationOwnedDevice,
+ deviceOwnerOrganizationName =
+ securityController.deviceOwnerOrganizationName?.toString(),
+ workProfileOrganizationName =
+ securityController.workProfileOrganizationName?.toString(),
+ isNetworkLoggingEnabled = securityController.isNetworkLoggingEnabled,
+ isVpnBranded = securityController.isVpnBranded,
+ primaryVpnName = securityController.primaryVpnName,
+ workProfileVpnName = securityController.workProfileVpnName,
+ hasCACertInCurrentUser = securityController.hasCACertInCurrentUser(),
+ hasCACertInWorkProfile = securityController.hasCACertInWorkProfile(),
+ isParentalControlsEnabled = securityController.isParentalControlsEnabled,
+ deviceAdminIcon = securityController.getIcon(deviceAdminInfo),
+ )
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/security/data/repository/SecurityRepository.kt b/packages/SystemUI/src/com/android/systemui/security/data/repository/SecurityRepository.kt
new file mode 100644
index 000000000000..8f4402eaa406
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/security/data/repository/SecurityRepository.kt
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2022 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.security.data.repository
+
+import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.security.data.model.SecurityModel
+import com.android.systemui.statusbar.policy.SecurityController
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.launch
+
+interface SecurityRepository {
+ /** The current [SecurityModel]. */
+ val security: Flow<SecurityModel>
+}
+
+@SysUISingleton
+class SecurityRepositoryImpl
+@Inject
+constructor(
+ private val securityController: SecurityController,
+ @Background private val bgDispatcher: CoroutineDispatcher,
+) : SecurityRepository {
+ override val security: Flow<SecurityModel> = conflatedCallbackFlow {
+ suspend fun updateState() {
+ trySendWithFailureLogging(SecurityModel.create(securityController, bgDispatcher), TAG)
+ }
+
+ val callback = SecurityController.SecurityControllerCallback { launch { updateState() } }
+
+ securityController.addCallback(callback)
+ updateState()
+ awaitClose { securityController.removeCallback(callback) }
+ }
+
+ companion object {
+ private const val TAG = "SecurityRepositoryImpl"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/security/data/repository/SecurityRepositoryModule.kt b/packages/SystemUI/src/com/android/systemui/security/data/repository/SecurityRepositoryModule.kt
new file mode 100644
index 000000000000..39a57cafaecc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/security/data/repository/SecurityRepositoryModule.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2022 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.security.data.repository
+
+import dagger.Binds
+import dagger.Module
+
+/** Dagger module to provide/bind security repositories. */
+@Module
+interface SecurityRepositoryModule {
+ @Binds fun securityRepository(impl: SecurityRepositoryImpl): SecurityRepository
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index e6d10228dc55..3b7c7ce359b3 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -17,6 +17,8 @@
package com.android.systemui.shade;
import static android.app.StatusBarManager.WINDOW_STATE_SHOWING;
+import static android.view.View.INVISIBLE;
+import static android.view.View.VISIBLE;
import static androidx.constraintlayout.widget.ConstraintSet.END;
import static androidx.constraintlayout.widget.ConstraintSet.PARENT_ID;
@@ -26,8 +28,12 @@ import static com.android.keyguard.KeyguardClockSwitch.LARGE;
import static com.android.keyguard.KeyguardClockSwitch.SMALL;
import static com.android.systemui.animation.Interpolators.EMPHASIZED_ACCELERATE;
import static com.android.systemui.animation.Interpolators.EMPHASIZED_DECELERATE;
+import static com.android.systemui.classifier.Classifier.BOUNCER_UNLOCK;
+import static com.android.systemui.classifier.Classifier.GENERIC;
import static com.android.systemui.classifier.Classifier.QS_COLLAPSE;
import static com.android.systemui.classifier.Classifier.QUICK_SETTINGS;
+import static com.android.systemui.classifier.Classifier.UNLOCK;
+import static com.android.systemui.shade.PanelView.DEBUG;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_QUICK_SETTINGS_EXPANDED;
import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
@@ -40,6 +46,8 @@ import static com.android.systemui.statusbar.phone.panelstate.PanelExpansionStat
import static com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManagerKt.STATE_OPENING;
import static com.android.systemui.util.DumpUtilsKt.asIndenting;
+import static java.lang.Float.isNaN;
+
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
@@ -47,6 +55,8 @@ import android.annotation.NonNull;
import android.app.Fragment;
import android.app.StatusBarManager;
import android.content.ContentResolver;
+import android.content.res.Configuration;
+import android.content.res.Resources;
import android.database.ContentObserver;
import android.graphics.Canvas;
import android.graphics.Color;
@@ -71,11 +81,13 @@ import android.transition.TransitionManager;
import android.util.IndentingPrintWriter;
import android.util.Log;
import android.util.MathUtils;
+import android.view.InputDevice;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.View.AccessibilityDelegate;
+import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.ViewPropertyAnimator;
import android.view.ViewStub;
@@ -84,6 +96,7 @@ import android.view.WindowInsets;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.animation.Interpolator;
import android.widget.FrameLayout;
import androidx.annotation.Nullable;
@@ -178,6 +191,7 @@ import com.android.systemui.statusbar.notification.stack.NotificationStackScroll
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
import com.android.systemui.statusbar.notification.stack.NotificationStackSizeCalculator;
import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
+import com.android.systemui.statusbar.phone.BounceInterpolator;
import com.android.systemui.statusbar.phone.CentralSurfaces;
import com.android.systemui.statusbar.phone.DozeParameters;
import com.android.systemui.statusbar.phone.HeadsUpAppearanceController;
@@ -233,12 +247,25 @@ import javax.inject.Inject;
import javax.inject.Provider;
@CentralSurfacesComponent.CentralSurfacesScope
-public final class NotificationPanelViewController extends PanelViewController {
+public final class NotificationPanelViewController {
+ public static final String TAG = PanelView.class.getSimpleName();
+ public static final float FLING_MAX_LENGTH_SECONDS = 0.6f;
+ public static final float FLING_SPEED_UP_FACTOR = 0.6f;
+ public static final float FLING_CLOSING_MAX_LENGTH_SECONDS = 0.6f;
+ public static final float FLING_CLOSING_SPEED_UP_FACTOR = 0.6f;
+ private static final int NO_FIXED_DURATION = -1;
+ private static final long SHADE_OPEN_SPRING_OUT_DURATION = 350L;
+ private static final long SHADE_OPEN_SPRING_BACK_DURATION = 400L;
+
+ /**
+ * The factor of the usual high velocity that is needed in order to reach the maximum overshoot
+ * when flinging. A low value will make it that most flings will reach the maximum overshoot.
+ */
+ private static final float FACTOR_OF_HIGH_VELOCITY_FOR_MAX_OVERSHOOT = 0.5f;
private static final boolean DEBUG_LOGCAT = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.DEBUG);
private static final boolean SPEW_LOGCAT = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.VERBOSE);
private static final boolean DEBUG_DRAWABLE = false;
-
/**
* The parallax amount of the quick settings translation when dragging down the panel
*/
@@ -263,6 +290,16 @@ public final class NotificationPanelViewController extends PanelViewController {
- CollapsedStatusBarFragment.FADE_IN_DURATION
- CollapsedStatusBarFragment.FADE_IN_DELAY - 48;
+ private final StatusBarTouchableRegionManager mStatusBarTouchableRegionManager;
+ private final Resources mResources;
+ private final KeyguardStateController mKeyguardStateController;
+ private final SysuiStatusBarStateController mStatusBarStateController;
+ private final AmbientState mAmbientState;
+ private final LockscreenGestureLogger mLockscreenGestureLogger;
+ private final SystemClock mSystemClock;
+
+ private final ShadeLogger mShadeLog;
+
private final DozeParameters mDozeParameters;
private final OnHeightChangedListener mOnHeightChangedListener = new OnHeightChangedListener();
private final Runnable mCollapseExpandAction = new CollapseExpandAction();
@@ -341,6 +378,28 @@ public final class NotificationPanelViewController extends PanelViewController {
private final LargeScreenShadeHeaderController mLargeScreenShadeHeaderController;
private final RecordingController mRecordingController;
private final PanelEventsEmitter mPanelEventsEmitter;
+ private final boolean mVibrateOnOpening;
+ private final VelocityTracker mVelocityTracker = VelocityTracker.obtain();
+ private final FlingAnimationUtils mFlingAnimationUtilsClosing;
+ private final FlingAnimationUtils mFlingAnimationUtilsDismissing;
+ private final LatencyTracker mLatencyTracker;
+ private final DozeLog mDozeLog;
+ /** Whether or not the PanelView can be expanded or collapsed with a drag. */
+ private final boolean mNotificationsDragEnabled;
+ private final Interpolator mBounceInterpolator;
+ private final NotificationShadeWindowController mNotificationShadeWindowController;
+ private final PanelExpansionStateManager mPanelExpansionStateManager;
+ private long mDownTime;
+ private boolean mTouchSlopExceededBeforeDown;
+ private boolean mIsLaunchAnimationRunning;
+ private float mOverExpansion;
+ private CentralSurfaces mCentralSurfaces;
+ private HeadsUpManagerPhone mHeadsUpManager;
+ private float mExpandedHeight = 0;
+ private boolean mTracking;
+ private boolean mHintAnimationRunning;
+ private KeyguardBottomAreaView mKeyguardBottomArea;
+ private boolean mExpanding;
private boolean mSplitShadeEnabled;
/** The bottom padding reserved for elements of the keyguard measuring notifications. */
private float mKeyguardNotificationBottomPadding;
@@ -371,7 +430,7 @@ public final class NotificationPanelViewController extends PanelViewController {
private final ScreenOffAnimationController mScreenOffAnimationController;
private final UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController;
- private int mTrackingPointer;
+ private int mQsTrackingPointer;
private VelocityTracker mQsVelocityTracker;
private boolean mQsTracking;
@@ -703,6 +762,51 @@ public final class NotificationPanelViewController extends PanelViewController {
private final CameraGestureHelper mCameraGestureHelper;
private final Provider<KeyguardBottomAreaViewModel> mKeyguardBottomAreaViewModelProvider;
private final Provider<KeyguardBottomAreaInteractor> mKeyguardBottomAreaInteractorProvider;
+ private float mMinExpandHeight;
+ private boolean mPanelUpdateWhenAnimatorEnds;
+ private int mFixedDuration = NO_FIXED_DURATION;
+ /** The overshoot amount when the panel flings open */
+ private float mPanelFlingOvershootAmount;
+ /** The amount of pixels that we have overexpanded the last time with a gesture */
+ private float mLastGesturedOverExpansion = -1;
+ /** Is the current animator the spring back animation? */
+ private boolean mIsSpringBackAnimation;
+ private boolean mInSplitShade;
+ private float mHintDistance;
+ private float mInitialOffsetOnTouch;
+ private boolean mCollapsedAndHeadsUpOnDown;
+ private float mExpandedFraction = 0;
+ private float mExpansionDragDownAmountPx = 0;
+ private boolean mPanelClosedOnDown;
+ private boolean mHasLayoutedSinceDown;
+ private float mUpdateFlingVelocity;
+ private boolean mUpdateFlingOnLayout;
+ private boolean mClosing;
+ private boolean mTouchSlopExceeded;
+ private int mTrackingPointer;
+ private int mTouchSlop;
+ private float mSlopMultiplier;
+ private boolean mTouchAboveFalsingThreshold;
+ private boolean mTouchStartedInEmptyArea;
+ private boolean mMotionAborted;
+ private boolean mUpwardsWhenThresholdReached;
+ private boolean mAnimatingOnDown;
+ private boolean mHandlingPointerUp;
+ private ValueAnimator mHeightAnimator;
+ /** Whether instant expand request is currently pending and we are just waiting for layout. */
+ private boolean mInstantExpanding;
+ private boolean mAnimateAfterExpanding;
+ private boolean mIsFlinging;
+ private String mViewName;
+ private float mInitialExpandY;
+ private float mInitialExpandX;
+ private boolean mTouchDisabled;
+ private boolean mInitialTouchFromKeyguard;
+ /** Speed-up factor to be used when {@link #mFlingCollapseRunnable} runs the next time. */
+ private float mNextCollapseSpeedUpFactor = 1.0f;
+ private boolean mGestureWaitForTouchSlop;
+ private boolean mIgnoreXTouchSlop;
+ private boolean mExpandLatencyTracking;
@Inject
public NotificationPanelViewController(NotificationPanelView view,
@@ -726,7 +830,7 @@ public final class NotificationPanelViewController extends PanelViewController {
MetricsLogger metricsLogger,
ShadeLogger shadeLogger,
ConfigurationController configurationController,
- Provider<FlingAnimationUtils.Builder> flingAnimationUtilsBuilder,
+ Provider<FlingAnimationUtils.Builder> flingAnimationUtilsBuilderProvider,
StatusBarTouchableRegionManager statusBarTouchableRegionManager,
ConversationNotificationManager conversationNotificationManager,
MediaHierarchyManager mediaHierarchyManager,
@@ -776,25 +880,68 @@ public final class NotificationPanelViewController extends PanelViewController {
CameraGestureHelper cameraGestureHelper,
Provider<KeyguardBottomAreaViewModel> keyguardBottomAreaViewModelProvider,
Provider<KeyguardBottomAreaInteractor> keyguardBottomAreaInteractorProvider) {
- super(view,
- falsingManager,
- dozeLog,
- keyguardStateController,
- (SysuiStatusBarStateController) statusBarStateController,
- notificationShadeWindowController,
- vibratorHelper,
- statusBarKeyguardViewManager,
- latencyTracker,
- flingAnimationUtilsBuilder.get(),
- statusBarTouchableRegionManager,
- lockscreenGestureLogger,
- panelExpansionStateManager,
- ambientState,
- interactionJankMonitor,
- shadeLogger,
- systemClock);
+ keyguardStateController.addCallback(new KeyguardStateController.Callback() {
+ @Override
+ public void onKeyguardFadingAwayChanged() {
+ requestPanelHeightUpdate();
+ }
+ });
+ mAmbientState = ambientState;
mView = view;
+ mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
+ mLockscreenGestureLogger = lockscreenGestureLogger;
+ mPanelExpansionStateManager = panelExpansionStateManager;
+ mShadeLog = shadeLogger;
+ TouchHandler touchHandler = createTouchHandler();
+ mView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
+ @Override
+ public void onViewAttachedToWindow(View v) {
+ mViewName = mResources.getResourceName(mView.getId());
+ }
+
+ @Override
+ public void onViewDetachedFromWindow(View v) {
+ }
+ });
+
+ mView.addOnLayoutChangeListener(createLayoutChangeListener());
+ mView.setOnTouchListener(touchHandler);
+ mView.setOnConfigurationChangedListener(createOnConfigurationChangedListener());
+
+ mResources = mView.getResources();
+ mKeyguardStateController = keyguardStateController;
+ mStatusBarStateController = (SysuiStatusBarStateController) statusBarStateController;
+ mNotificationShadeWindowController = notificationShadeWindowController;
+ FlingAnimationUtils.Builder flingAnimationUtilsBuilder =
+ flingAnimationUtilsBuilderProvider.get();
+ mFlingAnimationUtils = flingAnimationUtilsBuilder
+ .reset()
+ .setMaxLengthSeconds(FLING_MAX_LENGTH_SECONDS)
+ .setSpeedUpFactor(FLING_SPEED_UP_FACTOR)
+ .build();
+ mFlingAnimationUtilsClosing = flingAnimationUtilsBuilder
+ .reset()
+ .setMaxLengthSeconds(FLING_CLOSING_MAX_LENGTH_SECONDS)
+ .setSpeedUpFactor(FLING_CLOSING_SPEED_UP_FACTOR)
+ .build();
+ mFlingAnimationUtilsDismissing = flingAnimationUtilsBuilder
+ .reset()
+ .setMaxLengthSeconds(0.5f)
+ .setSpeedUpFactor(0.6f)
+ .setX2(0.6f)
+ .setY2(0.84f)
+ .build();
+ mLatencyTracker = latencyTracker;
+ mBounceInterpolator = new BounceInterpolator();
+ mFalsingManager = falsingManager;
+ mDozeLog = dozeLog;
+ mNotificationsDragEnabled = mResources.getBoolean(
+ R.bool.config_enableNotificationShadeDrag);
mVibratorHelper = vibratorHelper;
+ mVibrateOnOpening = mResources.getBoolean(R.bool.config_vibrateOnIconAnimation);
+ mStatusBarTouchableRegionManager = statusBarTouchableRegionManager;
+ mInteractionJankMonitor = interactionJankMonitor;
+ mSystemClock = systemClock;
mKeyguardMediaController = keyguardMediaController;
mPrivacyDotViewController = privacyDotViewController;
mQuickAccessWalletController = quickAccessWalletController;
@@ -802,9 +949,8 @@ public final class NotificationPanelViewController extends PanelViewController {
mControlsComponent = controlsComponent;
mMetricsLogger = metricsLogger;
mConfigurationController = configurationController;
- mFlingAnimationUtilsBuilder = flingAnimationUtilsBuilder;
+ mFlingAnimationUtilsBuilder = flingAnimationUtilsBuilderProvider;
mMediaHierarchyManager = mediaHierarchyManager;
- mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
mNotificationsQSContainerController = notificationsQSContainerController;
mNotificationListContainer = notificationListContainer;
mNotificationStackSizeCalculator = notificationStackSizeCalculator;
@@ -826,7 +972,6 @@ public final class NotificationPanelViewController extends PanelViewController {
mLargeScreenShadeHeaderController = largeScreenShadeHeaderController;
mLayoutInflater = layoutInflater;
mFeatureFlags = featureFlags;
- mFalsingManager = falsingManager;
mFalsingCollector = falsingCollector;
mPowerManager = powerManager;
mWakeUpCoordinator = coordinator;
@@ -842,7 +987,6 @@ public final class NotificationPanelViewController extends PanelViewController {
mUserManager = userManager;
mMediaDataManager = mediaDataManager;
mTapAgainViewController = tapAgainViewController;
- mInteractionJankMonitor = interactionJankMonitor;
mSysUiState = sysUiState;
mPanelEventsEmitter = panelEventsEmitter;
pulseExpansionHandler.setPulseExpandAbortListener(() -> {
@@ -1045,9 +1189,14 @@ public final class NotificationPanelViewController extends PanelViewController {
controller.setup(mNotificationContainerParent));
}
- @Override
- protected void loadDimens() {
- super.loadDimens();
+ @VisibleForTesting
+ void loadDimens() {
+ final ViewConfiguration configuration = ViewConfiguration.get(this.mView.getContext());
+ mTouchSlop = configuration.getScaledTouchSlop();
+ mSlopMultiplier = configuration.getScaledAmbiguousGestureMultiplier();
+ mHintDistance = mResources.getDimension(R.dimen.hint_move_distance);
+ mPanelFlingOvershootAmount = mResources.getDimension(R.dimen.panel_overshoot_amount);
+ mInSplitShade = mResources.getBoolean(R.bool.config_use_split_notification_shade);
mFlingAnimationUtils = mFlingAnimationUtilsBuilder.get()
.setMaxLengthSeconds(0.4f).build();
mStatusBarMinHeight = SystemBarUtils.getStatusBarHeight(mView.getContext());
@@ -1738,7 +1887,6 @@ public final class NotificationPanelViewController extends PanelViewController {
}
}
- @Override
public void collapse(boolean delayed, float speedUpFactor) {
if (!canPanelBeCollapsed()) {
return;
@@ -1748,7 +1896,20 @@ public final class NotificationPanelViewController extends PanelViewController {
setQsExpandImmediate(true);
setShowShelfOnly(true);
}
- super.collapse(delayed, speedUpFactor);
+ if (DEBUG) this.logf("collapse: " + this);
+ if (canPanelBeCollapsed()) {
+ cancelHeightAnimator();
+ notifyExpandingStarted();
+
+ // Set after notifyExpandingStarted, as notifyExpandingStarted resets the closing state.
+ setIsClosing(true);
+ if (delayed) {
+ mNextCollapseSpeedUpFactor = speedUpFactor;
+ this.mView.postDelayed(mFlingCollapseRunnable, 120);
+ } else {
+ fling(0, false /* expand */, speedUpFactor, false /* expandBecauseOfFalsing */);
+ }
+ }
}
private void setQsExpandImmediate(boolean expandImmediate) {
@@ -1766,10 +1927,15 @@ public final class NotificationPanelViewController extends PanelViewController {
setQsExpansion(mQsMinExpansionHeight);
}
- @Override
@VisibleForTesting
- protected void cancelHeightAnimator() {
- super.cancelHeightAnimator();
+ void cancelHeightAnimator() {
+ if (mHeightAnimator != null) {
+ if (mHeightAnimator.isRunning()) {
+ mPanelUpdateWhenAnimatorEnds = false;
+ }
+ mHeightAnimator.cancel();
+ }
+ endClosing();
}
public void cancelAnimation() {
@@ -1837,37 +2003,132 @@ public final class NotificationPanelViewController extends PanelViewController {
}
}
- @Override
public void fling(float vel, boolean expand) {
GestureRecorder gr = mCentralSurfaces.getGestureRecorder();
if (gr != null) {
gr.tag("fling " + ((vel > 0) ? "open" : "closed"), "notifications,v=" + vel);
}
- super.fling(vel, expand);
+ fling(vel, expand, 1.0f /* collapseSpeedUpFactor */, false);
}
- @Override
- protected void flingToHeight(float vel, boolean expand, float target,
+ @VisibleForTesting
+ void flingToHeight(float vel, boolean expand, float target,
float collapseSpeedUpFactor, boolean expandBecauseOfFalsing) {
mHeadsUpTouchHelper.notifyFling(!expand);
mKeyguardStateController.notifyPanelFlingStart(!expand /* flingingToDismiss */);
setClosingWithAlphaFadeout(!expand && !isOnKeyguard() && getFadeoutAlpha() == 1.0f);
mNotificationStackScrollLayoutController.setPanelFlinging(true);
- super.flingToHeight(vel, expand, target, collapseSpeedUpFactor, expandBecauseOfFalsing);
+ if (target == mExpandedHeight && mOverExpansion == 0.0f) {
+ // We're at the target and didn't fling and there's no overshoot
+ onFlingEnd(false /* cancelled */);
+ return;
+ }
+ mIsFlinging = true;
+ // we want to perform an overshoot animation when flinging open
+ final boolean addOverscroll =
+ expand
+ && !mInSplitShade // Split shade has its own overscroll logic
+ && mStatusBarStateController.getState() != KEYGUARD
+ && mOverExpansion == 0.0f
+ && vel >= 0;
+ final boolean shouldSpringBack = addOverscroll || (mOverExpansion != 0.0f && expand);
+ float overshootAmount = 0.0f;
+ if (addOverscroll) {
+ // Let's overshoot depending on the amount of velocity
+ overshootAmount = MathUtils.lerp(
+ 0.2f,
+ 1.0f,
+ MathUtils.saturate(vel
+ / (this.mFlingAnimationUtils.getHighVelocityPxPerSecond()
+ * FACTOR_OF_HIGH_VELOCITY_FOR_MAX_OVERSHOOT)));
+ overshootAmount += mOverExpansion / mPanelFlingOvershootAmount;
+ }
+ ValueAnimator animator = createHeightAnimator(target, overshootAmount);
+ if (expand) {
+ if (expandBecauseOfFalsing && vel < 0) {
+ vel = 0;
+ }
+ this.mFlingAnimationUtils.apply(animator, mExpandedHeight,
+ target + overshootAmount * mPanelFlingOvershootAmount, vel,
+ this.mView.getHeight());
+ if (vel == 0) {
+ animator.setDuration(SHADE_OPEN_SPRING_OUT_DURATION);
+ }
+ } else {
+ if (shouldUseDismissingAnimation()) {
+ if (vel == 0) {
+ animator.setInterpolator(Interpolators.PANEL_CLOSE_ACCELERATED);
+ long duration = (long) (200 + mExpandedHeight / this.mView.getHeight() * 100);
+ animator.setDuration(duration);
+ } else {
+ mFlingAnimationUtilsDismissing.apply(animator, mExpandedHeight, target, vel,
+ this.mView.getHeight());
+ }
+ } else {
+ mFlingAnimationUtilsClosing.apply(
+ animator, mExpandedHeight, target, vel, this.mView.getHeight());
+ }
+
+ // Make it shorter if we run a canned animation
+ if (vel == 0) {
+ animator.setDuration((long) (animator.getDuration() / collapseSpeedUpFactor));
+ }
+ if (mFixedDuration != NO_FIXED_DURATION) {
+ animator.setDuration(mFixedDuration);
+ }
+ }
+ animator.addListener(new AnimatorListenerAdapter() {
+ private boolean mCancelled;
+
+ @Override
+ public void onAnimationStart(Animator animation) {
+ if (!mStatusBarStateController.isDozing()) {
+ beginJankMonitoring();
+ }
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ mCancelled = true;
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ if (shouldSpringBack && !mCancelled) {
+ // After the shade is flinged open to an overscrolled state, spring back
+ // the shade by reducing section padding to 0.
+ springBack();
+ } else {
+ onFlingEnd(mCancelled);
+ }
+ }
+ });
+ setAnimator(animator);
+ animator.start();
}
- @Override
- protected void onFlingEnd(boolean cancelled) {
- super.onFlingEnd(cancelled);
+ private void onFlingEnd(boolean cancelled) {
+ mIsFlinging = false;
+ // No overshoot when the animation ends
+ setOverExpansionInternal(0, false /* isFromGesture */);
+ setAnimator(null);
+ mKeyguardStateController.notifyPanelFlingEnd();
+ if (!cancelled) {
+ endJankMonitoring();
+ notifyExpandingFinished();
+ } else {
+ cancelJankMonitoring();
+ }
+ updatePanelExpansionAndVisibility();
mNotificationStackScrollLayoutController.setPanelFlinging(false);
}
private boolean onQsIntercept(MotionEvent event) {
if (DEBUG_LOGCAT) Log.d(TAG, "onQsIntercept");
- int pointerIndex = event.findPointerIndex(mTrackingPointer);
+ int pointerIndex = event.findPointerIndex(mQsTrackingPointer);
if (pointerIndex < 0) {
pointerIndex = 0;
- mTrackingPointer = event.getPointerId(pointerIndex);
+ mQsTrackingPointer = event.getPointerId(pointerIndex);
}
final float x = event.getX(pointerIndex);
final float y = event.getY(pointerIndex);
@@ -1896,10 +2157,10 @@ public final class NotificationPanelViewController extends PanelViewController {
break;
case MotionEvent.ACTION_POINTER_UP:
final int upPointer = event.getPointerId(event.getActionIndex());
- if (mTrackingPointer == upPointer) {
+ if (mQsTrackingPointer == upPointer) {
// gesture is ongoing, find a new pointer to track
final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1;
- mTrackingPointer = event.getPointerId(newIndex);
+ mQsTrackingPointer = event.getPointerId(newIndex);
mInitialTouchX = event.getX(newIndex);
mInitialTouchY = event.getY(newIndex);
}
@@ -1953,8 +2214,7 @@ public final class NotificationPanelViewController extends PanelViewController {
return mQsTracking;
}
- @Override
- protected boolean isInContentBounds(float x, float y) {
+ private boolean isInContentBounds(float x, float y) {
float stackScrollerX = mNotificationStackScrollLayoutController.getX();
return !mNotificationStackScrollLayoutController
.isBelowLastNotification(x - stackScrollerX, y)
@@ -2087,9 +2347,8 @@ public final class NotificationPanelViewController extends PanelViewController {
- mQsMinExpansionHeight));
}
- @Override
- protected boolean shouldExpandWhenNotFlinging() {
- if (super.shouldExpandWhenNotFlinging()) {
+ private boolean shouldExpandWhenNotFlinging() {
+ if (getExpandedFraction() > 0.5f) {
return true;
}
if (mAllowExpandForSmallExpansion) {
@@ -2101,8 +2360,7 @@ public final class NotificationPanelViewController extends PanelViewController {
return false;
}
- @Override
- protected float getOpeningHeight() {
+ private float getOpeningHeight() {
return mNotificationStackScrollLayoutController.getOpeningHeight();
}
@@ -2252,9 +2510,20 @@ public final class NotificationPanelViewController extends PanelViewController {
}
}
- @Override
- protected boolean flingExpands(float vel, float vectorVel, float x, float y) {
- boolean expands = super.flingExpands(vel, vectorVel, x, y);
+ private boolean flingExpands(float vel, float vectorVel, float x, float y) {
+ boolean expands = true;
+ if (!this.mFalsingManager.isUnlockingDisabled()) {
+ @Classifier.InteractionType int interactionType = y - mInitialExpandY > 0
+ ? QUICK_SETTINGS : (
+ mKeyguardStateController.canDismissLockScreen() ? UNLOCK : BOUNCER_UNLOCK);
+ if (!isFalseTouch(x, y, interactionType)) {
+ if (Math.abs(vectorVel) < this.mFlingAnimationUtils.getMinVelocityPxPerSecond()) {
+ expands = shouldExpandWhenNotFlinging();
+ } else {
+ expands = vel > 0;
+ }
+ }
+ }
// If we are already running a QS expansion, make sure that we keep the panel open.
if (mQsExpansionAnimator != null) {
@@ -2263,8 +2532,7 @@ public final class NotificationPanelViewController extends PanelViewController {
return expands;
}
- @Override
- protected boolean shouldGestureWaitForTouchSlop() {
+ private boolean shouldGestureWaitForTouchSlop() {
if (mExpectingSynthesizedDown) {
mExpectingSynthesizedDown = false;
return false;
@@ -2273,10 +2541,10 @@ public final class NotificationPanelViewController extends PanelViewController {
}
private void onQsTouch(MotionEvent event) {
- int pointerIndex = event.findPointerIndex(mTrackingPointer);
+ int pointerIndex = event.findPointerIndex(mQsTrackingPointer);
if (pointerIndex < 0) {
pointerIndex = 0;
- mTrackingPointer = event.getPointerId(pointerIndex);
+ mQsTrackingPointer = event.getPointerId(pointerIndex);
}
final float y = event.getY(pointerIndex);
final float x = event.getX(pointerIndex);
@@ -2297,12 +2565,12 @@ public final class NotificationPanelViewController extends PanelViewController {
case MotionEvent.ACTION_POINTER_UP:
final int upPointer = event.getPointerId(event.getActionIndex());
- if (mTrackingPointer == upPointer) {
+ if (mQsTrackingPointer == upPointer) {
// gesture is ongoing, find a new pointer to track
final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1;
final float newY = event.getY(newIndex);
final float newX = event.getX(newIndex);
- mTrackingPointer = event.getPointerId(newIndex);
+ mQsTrackingPointer = event.getPointerId(newIndex);
mInitialHeightOnTouch = mQsExpansionHeight;
mInitialTouchY = newY;
mInitialTouchX = newX;
@@ -2324,7 +2592,7 @@ public final class NotificationPanelViewController extends PanelViewController {
mShadeLog.logMotionEvent(event,
"onQsTouch: up/cancel action, QS tracking disabled");
mQsTracking = false;
- mTrackingPointer = -1;
+ mQsTrackingPointer = -1;
trackMovement(event);
float fraction = computeQsExpansionFraction();
if (fraction != 0f || y >= mInitialTouchY) {
@@ -3076,8 +3344,8 @@ public final class NotificationPanelViewController extends PanelViewController {
}
}
- @Override
- protected boolean canCollapsePanelOnTouch() {
+ @VisibleForTesting
+ boolean canCollapsePanelOnTouch() {
if (!isInSettings() && mBarState == KEYGUARD) {
return true;
}
@@ -3089,7 +3357,6 @@ public final class NotificationPanelViewController extends PanelViewController {
return !mSplitShadeEnabled && (isInSettings() || mIsPanelCollapseOnQQS);
}
- @Override
public int getMaxPanelHeight() {
int min = mStatusBarMinHeight;
if (!(mBarState == KEYGUARD)
@@ -3132,8 +3399,7 @@ public final class NotificationPanelViewController extends PanelViewController {
return mIsExpanding;
}
- @Override
- protected void onHeightUpdated(float expandedHeight) {
+ private void onHeightUpdated(float expandedHeight) {
if (!mQsExpanded || mQsExpandImmediate || mIsExpanding && mQsExpandedWhenExpandingStarted) {
// Updating the clock position will set the top padding which might
// trigger a new panel height and re-position the clock.
@@ -3315,9 +3581,7 @@ public final class NotificationPanelViewController extends PanelViewController {
mLockIconViewController.setAlpha(alpha);
}
- @Override
- protected void onExpandingStarted() {
- super.onExpandingStarted();
+ private void onExpandingStarted() {
mNotificationStackScrollLayoutController.onExpansionStarted();
mIsExpanding = true;
mQsExpandedWhenExpandingStarted = mQsFullyExpanded;
@@ -3333,8 +3597,7 @@ public final class NotificationPanelViewController extends PanelViewController {
mQs.setHeaderListening(true);
}
- @Override
- protected void onExpandingFinished() {
+ private void onExpandingFinished() {
mScrimController.onExpandingFinished();
mNotificationStackScrollLayoutController.onExpansionStopped();
mHeadsUpManager.onExpandingFinished();
@@ -3382,18 +3645,54 @@ public final class NotificationPanelViewController extends PanelViewController {
mQs.setListening(listening);
}
- @Override
public void expand(boolean animate) {
- super.expand(animate);
+ if (isFullyCollapsed() || isCollapsing()) {
+ mInstantExpanding = true;
+ mAnimateAfterExpanding = animate;
+ mUpdateFlingOnLayout = false;
+ abortAnimations();
+ if (mTracking) {
+ onTrackingStopped(true /* expands */); // The panel is expanded after this call.
+ }
+ if (mExpanding) {
+ notifyExpandingFinished();
+ }
+ updatePanelExpansionAndVisibility();// Wait for window manager to pickup the change,
+ // so we know the maximum height of the panel then.
+ this.mView.getViewTreeObserver().addOnGlobalLayoutListener(
+ new ViewTreeObserver.OnGlobalLayoutListener() {
+ @Override
+ public void onGlobalLayout() {
+ if (!mInstantExpanding) {
+ mView.getViewTreeObserver().removeOnGlobalLayoutListener(
+ this);
+ return;
+ }
+ if (mCentralSurfaces.getNotificationShadeWindowView().isVisibleToUser()) {
+ mView.getViewTreeObserver().removeOnGlobalLayoutListener(
+ this);
+ if (mAnimateAfterExpanding) {
+ notifyExpandingStarted();
+ beginJankMonitoring();
+ fling(0, true /* expand */);
+ } else {
+ setExpandedFraction(1f);
+ }
+ mInstantExpanding = false;
+ }
+ }
+ });// Make sure a layout really happens.
+ this.mView.requestLayout();
+ }
+
setListening(true);
}
- @Override
public void setOverExpansion(float overExpansion) {
if (overExpansion == mOverExpansion) {
return;
}
- super.setOverExpansion(overExpansion);
+ mOverExpansion = overExpansion;
// Translating the quick settings by half the overexpansion to center it in the background
// frame
updateQsFrameTranslation();
@@ -3405,10 +3704,13 @@ public final class NotificationPanelViewController extends PanelViewController {
mQsTranslationForFullShadeTransition);
}
- @Override
- protected void onTrackingStarted() {
+ private void onTrackingStarted() {
mFalsingCollector.onTrackingStarted(!mKeyguardStateController.canDismissLockScreen());
- super.onTrackingStarted();
+ endClosing();
+ mTracking = true;
+ mCentralSurfaces.onTrackingStarted();
+ notifyExpandingStarted();
+ updatePanelExpansionAndVisibility();
mScrimController.onTrackingStarted();
if (mQsFullyExpanded) {
setQsExpandImmediate(true);
@@ -3418,10 +3720,11 @@ public final class NotificationPanelViewController extends PanelViewController {
cancelPendingPanelCollapse();
}
- @Override
- protected void onTrackingStopped(boolean expand) {
+ private void onTrackingStopped(boolean expand) {
mFalsingCollector.onTrackingStopped();
- super.onTrackingStopped(expand);
+ mTracking = false;
+ mCentralSurfaces.onTrackingStopped(expand);
+ updatePanelExpansionAndVisibility();
if (expand) {
mNotificationStackScrollLayoutController.setOverScrollAmount(0.0f, true /* onTop */,
true /* animate */);
@@ -3438,38 +3741,50 @@ public final class NotificationPanelViewController extends PanelViewController {
getHeight(), mNavigationBarBottomHeight);
}
- @Override
- protected void startUnlockHintAnimation() {
+ @VisibleForTesting
+ void startUnlockHintAnimation() {
if (mPowerManager.isPowerSaveMode() || mAmbientState.getDozeAmount() > 0f) {
onUnlockHintStarted();
onUnlockHintFinished();
return;
}
- super.startUnlockHintAnimation();
+
+ // We don't need to hint the user if an animation is already running or the user is changing
+ // the expansion.
+ if (mHeightAnimator != null || mTracking) {
+ return;
+ }
+ notifyExpandingStarted();
+ startUnlockHintAnimationPhase1(() -> {
+ notifyExpandingFinished();
+ onUnlockHintFinished();
+ mHintAnimationRunning = false;
+ });
+ onUnlockHintStarted();
+ mHintAnimationRunning = true;
}
- @Override
- protected void onUnlockHintFinished() {
- super.onUnlockHintFinished();
+ @VisibleForTesting
+ void onUnlockHintFinished() {
+ mCentralSurfaces.onHintFinished();
mScrimController.setExpansionAffectsAlpha(true);
mNotificationStackScrollLayoutController.setUnlockHintRunning(false);
}
- @Override
- protected void onUnlockHintStarted() {
- super.onUnlockHintStarted();
+ @VisibleForTesting
+ void onUnlockHintStarted() {
+ mCentralSurfaces.onUnlockHintStarted();
mScrimController.setExpansionAffectsAlpha(false);
mNotificationStackScrollLayoutController.setUnlockHintRunning(true);
}
- @Override
- protected boolean shouldUseDismissingAnimation() {
+ private boolean shouldUseDismissingAnimation() {
return mBarState != StatusBarState.SHADE && (mKeyguardStateController.canDismissLockScreen()
|| !isTracking());
}
- @Override
- protected boolean isTrackingBlocked() {
+ @VisibleForTesting
+ boolean isTrackingBlocked() {
return mConflictingQsExpansionGesture && mQsExpanded || mBlockingExpansionForCurrentTouch;
}
@@ -3491,19 +3806,17 @@ public final class NotificationPanelViewController extends PanelViewController {
return mIsLaunchTransitionFinished;
}
- @Override
public void setIsLaunchAnimationRunning(boolean running) {
boolean wasRunning = mIsLaunchAnimationRunning;
- super.setIsLaunchAnimationRunning(running);
+ mIsLaunchAnimationRunning = running;
if (wasRunning != mIsLaunchAnimationRunning) {
mPanelEventsEmitter.notifyLaunchingActivityChanged(running);
}
}
- @Override
- protected void setIsClosing(boolean isClosing) {
+ private void setIsClosing(boolean isClosing) {
boolean wasClosing = isClosing();
- super.setIsClosing(isClosing);
+ mClosing = isClosing;
if (wasClosing != isClosing) {
mPanelEventsEmitter.notifyPanelCollapsingChanged(isClosing);
}
@@ -3517,7 +3830,6 @@ public final class NotificationPanelViewController extends PanelViewController {
}
}
- @Override
public boolean isDozing() {
return mDozing;
}
@@ -3534,8 +3846,7 @@ public final class NotificationPanelViewController extends PanelViewController {
mKeyguardStatusViewController.dozeTimeTick();
}
- @Override
- protected boolean onMiddleClicked() {
+ private boolean onMiddleClicked() {
switch (mBarState) {
case KEYGUARD:
if (!mDozingOnDown) {
@@ -3593,15 +3904,13 @@ public final class NotificationPanelViewController extends PanelViewController {
updateVisibility();
}
- @Override
- protected boolean shouldPanelBeVisible() {
+ private boolean shouldPanelBeVisible() {
boolean headsUpVisible = mHeadsUpAnimatingAway || mHeadsUpPinnedMode;
return headsUpVisible || isExpanded() || mBouncerShowing;
}
- @Override
public void setHeadsUpManager(HeadsUpManagerPhone headsUpManager) {
- super.setHeadsUpManager(headsUpManager);
+ mHeadsUpManager = headsUpManager;
mHeadsUpTouchHelper = new HeadsUpTouchHelper(headsUpManager,
mNotificationStackScrollLayoutController.getHeadsUpCallback(),
NotificationPanelViewController.this);
@@ -3615,8 +3924,7 @@ public final class NotificationPanelViewController extends PanelViewController {
// otherwise we update the state when the expansion is finished
}
- @Override
- protected void onClosingFinished() {
+ private void onClosingFinished() {
mCentralSurfaces.onClosingFinished();
setClosingWithAlphaFadeout(false);
mMediaHierarchyManager.closeGuts();
@@ -3680,8 +3988,7 @@ public final class NotificationPanelViewController extends PanelViewController {
mCentralSurfaces.clearNotificationEffects();
}
- @Override
- protected boolean isPanelVisibleBecauseOfHeadsUp() {
+ private boolean isPanelVisibleBecauseOfHeadsUp() {
return (mHeadsUpManager.hasPinnedHeadsUp() || mHeadsUpAnimatingAway)
&& mBarState == StatusBarState.SHADE;
}
@@ -3795,9 +4102,15 @@ public final class NotificationPanelViewController extends PanelViewController {
mNotificationBoundsAnimationDelay = delay;
}
- @Override
public void setTouchAndAnimationDisabled(boolean disabled) {
- super.setTouchAndAnimationDisabled(disabled);
+ mTouchDisabled = disabled;
+ if (mTouchDisabled) {
+ cancelHeightAnimator();
+ if (mTracking) {
+ onTrackingStopped(true /* expanded */);
+ }
+ notifyExpandingFinished();
+ }
mNotificationStackScrollLayoutController.setAnimationsEnabled(!disabled);
}
@@ -3999,9 +4312,14 @@ public final class NotificationPanelViewController extends PanelViewController {
mBlockingExpansionForCurrentTouch = mTracking;
}
- @Override
public void dump(PrintWriter pw, String[] args) {
- super.dump(pw, args);
+ pw.println(String.format("[PanelView(%s): expandedHeight=%f maxPanelHeight=%d closing=%s"
+ + " tracking=%s timeAnim=%s%s "
+ + "touchDisabled=%s" + "]",
+ this.getClass().getSimpleName(), getExpandedHeight(), getMaxPanelHeight(),
+ mClosing ? "T" : "f", mTracking ? "T" : "f", mHeightAnimator,
+ ((mHeightAnimator != null && mHeightAnimator.isStarted()) ? " (started)" : ""),
+ mTouchDisabled ? "T" : "f"));
IndentingPrintWriter ipw = asIndenting(pw);
ipw.increaseIndent();
ipw.println("gestureExclusionRect:" + calculateGestureExclusionRect());
@@ -4144,127 +4462,359 @@ public final class NotificationPanelViewController extends PanelViewController {
mConfigurationListener.onThemeChanged();
}
- @Override
- public OnLayoutChangeListener createLayoutChangeListener() {
+ private OnLayoutChangeListener createLayoutChangeListener() {
return new OnLayoutChangeListener();
}
- @Override
- protected TouchHandler createTouchHandler() {
- return new TouchHandler() {
+ @VisibleForTesting
+ TouchHandler createTouchHandler() {
+ return new TouchHandler();
+ }
- private long mLastTouchDownTime = -1L;
+ public class TouchHandler implements View.OnTouchListener {
- @Override
- public boolean onInterceptTouchEvent(MotionEvent event) {
- if (SPEW_LOGCAT) {
- Log.v(TAG,
- "NPVC onInterceptTouchEvent (" + event.getId() + "): (" + event.getX()
- + "," + event.getY() + ")");
- }
- if (mBlockTouches || mQs.disallowPanelTouches()) {
- return false;
- }
- initDownStates(event);
- // Do not let touches go to shade or QS if the bouncer is visible,
- // but still let user swipe down to expand the panel, dismissing the bouncer.
- if (mCentralSurfaces.isBouncerShowing()) {
- return true;
- }
- if (mCommandQueue.panelsEnabled()
- && !mNotificationStackScrollLayoutController.isLongPressInProgress()
- && mHeadsUpTouchHelper.onInterceptTouchEvent(event)) {
- mMetricsLogger.count(COUNTER_PANEL_OPEN, 1);
- mMetricsLogger.count(COUNTER_PANEL_OPEN_PEEK, 1);
- return true;
- }
- if (!shouldQuickSettingsIntercept(mDownX, mDownY, 0)
- && mPulseExpansionHandler.onInterceptTouchEvent(event)) {
- return true;
- }
+ private long mLastTouchDownTime = -1L;
- if (!isFullyCollapsed() && onQsIntercept(event)) {
- if (DEBUG_LOGCAT) Log.d(TAG, "onQsIntercept true");
- return true;
- }
- return super.onInterceptTouchEvent(event);
+ public boolean onInterceptTouchEvent(MotionEvent event) {
+ if (SPEW_LOGCAT) {
+ Log.v(TAG,
+ "NPVC onInterceptTouchEvent (" + event.getId() + "): (" + event.getX()
+ + "," + event.getY() + ")");
+ }
+ if (mBlockTouches || mQs.disallowPanelTouches()) {
+ return false;
+ }
+ initDownStates(event);
+ // Do not let touches go to shade or QS if the bouncer is visible,
+ // but still let user swipe down to expand the panel, dismissing the bouncer.
+ if (mCentralSurfaces.isBouncerShowing()) {
+ return true;
+ }
+ if (mCommandQueue.panelsEnabled()
+ && !mNotificationStackScrollLayoutController.isLongPressInProgress()
+ && mHeadsUpTouchHelper.onInterceptTouchEvent(event)) {
+ mMetricsLogger.count(COUNTER_PANEL_OPEN, 1);
+ mMetricsLogger.count(COUNTER_PANEL_OPEN_PEEK, 1);
+ return true;
+ }
+ if (!shouldQuickSettingsIntercept(mDownX, mDownY, 0)
+ && mPulseExpansionHandler.onInterceptTouchEvent(event)) {
+ return true;
}
- @Override
- public boolean onTouch(View v, MotionEvent event) {
- if (event.getAction() == MotionEvent.ACTION_DOWN) {
- if (event.getDownTime() == mLastTouchDownTime) {
- // An issue can occur when swiping down after unlock, where multiple down
- // events are received in this handler with identical downTimes. Until the
- // source of the issue can be located, detect this case and ignore.
- // see b/193350347
- Log.w(TAG, "Duplicate down event detected... ignoring");
+ if (!isFullyCollapsed() && onQsIntercept(event)) {
+ if (DEBUG_LOGCAT) Log.d(TAG, "onQsIntercept true");
+ return true;
+ }
+ if (mInstantExpanding || !mNotificationsDragEnabled || mTouchDisabled || (mMotionAborted
+ && event.getActionMasked() != MotionEvent.ACTION_DOWN)) {
+ return false;
+ }
+
+ /*
+ * If the user drags anywhere inside the panel we intercept it if the movement is
+ * upwards. This allows closing the shade from anywhere inside the panel.
+ *
+ * We only do this if the current content is scrolled to the bottom,
+ * i.e. canCollapsePanelOnTouch() is true and therefore there is no conflicting
+ * scrolling gesture possible.
+ */
+ int pointerIndex = event.findPointerIndex(mTrackingPointer);
+ if (pointerIndex < 0) {
+ pointerIndex = 0;
+ mTrackingPointer = event.getPointerId(pointerIndex);
+ }
+ final float x = event.getX(pointerIndex);
+ final float y = event.getY(pointerIndex);
+ boolean canCollapsePanel = canCollapsePanelOnTouch();
+
+ switch (event.getActionMasked()) {
+ case MotionEvent.ACTION_DOWN:
+ mCentralSurfaces.userActivity();
+ mAnimatingOnDown = mHeightAnimator != null && !mIsSpringBackAnimation;
+ mMinExpandHeight = 0.0f;
+ mDownTime = mSystemClock.uptimeMillis();
+ if (mAnimatingOnDown && mClosing && !mHintAnimationRunning) {
+ cancelHeightAnimator();
+ mTouchSlopExceeded = true;
return true;
}
- mLastTouchDownTime = event.getDownTime();
+ mInitialExpandY = y;
+ mInitialExpandX = x;
+ mTouchStartedInEmptyArea = !isInContentBounds(x, y);
+ mTouchSlopExceeded = mTouchSlopExceededBeforeDown;
+ mMotionAborted = false;
+ mPanelClosedOnDown = isFullyCollapsed();
+ mCollapsedAndHeadsUpOnDown = false;
+ mHasLayoutedSinceDown = false;
+ mUpdateFlingOnLayout = false;
+ mTouchAboveFalsingThreshold = false;
+ addMovement(event);
+ break;
+ case MotionEvent.ACTION_POINTER_UP:
+ final int upPointer = event.getPointerId(event.getActionIndex());
+ if (mTrackingPointer == upPointer) {
+ // gesture is ongoing, find a new pointer to track
+ final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1;
+ mTrackingPointer = event.getPointerId(newIndex);
+ mInitialExpandX = event.getX(newIndex);
+ mInitialExpandY = event.getY(newIndex);
+ }
+ break;
+ case MotionEvent.ACTION_POINTER_DOWN:
+ if (mStatusBarStateController.getState() == StatusBarState.KEYGUARD) {
+ mMotionAborted = true;
+ mVelocityTracker.clear();
+ }
+ break;
+ case MotionEvent.ACTION_MOVE:
+ final float h = y - mInitialExpandY;
+ addMovement(event);
+ final boolean openShadeWithoutHun =
+ mPanelClosedOnDown && !mCollapsedAndHeadsUpOnDown;
+ if (canCollapsePanel || mTouchStartedInEmptyArea || mAnimatingOnDown
+ || openShadeWithoutHun) {
+ float hAbs = Math.abs(h);
+ float touchSlop = getTouchSlop(event);
+ if ((h < -touchSlop
+ || ((openShadeWithoutHun || mAnimatingOnDown) && hAbs > touchSlop))
+ && hAbs > Math.abs(x - mInitialExpandX)) {
+ cancelHeightAnimator();
+ startExpandMotion(x, y, true /* startTracking */, mExpandedHeight);
+ return true;
+ }
+ }
+ break;
+ case MotionEvent.ACTION_CANCEL:
+ case MotionEvent.ACTION_UP:
+ mVelocityTracker.clear();
+ break;
+ }
+ return false;
+ }
+
+ @Override
+ public boolean onTouch(View v, MotionEvent event) {
+ if (event.getAction() == MotionEvent.ACTION_DOWN) {
+ if (event.getDownTime() == mLastTouchDownTime) {
+ // An issue can occur when swiping down after unlock, where multiple down
+ // events are received in this handler with identical downTimes. Until the
+ // source of the issue can be located, detect this case and ignore.
+ // see b/193350347
+ Log.w(TAG, "Duplicate down event detected... ignoring");
+ return true;
}
+ mLastTouchDownTime = event.getDownTime();
+ }
- if (mBlockTouches || (mQsFullyExpanded && mQs != null
- && mQs.disallowPanelTouches())) {
- return false;
- }
+ if (mBlockTouches || (mQsFullyExpanded && mQs != null && mQs.disallowPanelTouches())) {
+ return false;
+ }
- // Do not allow panel expansion if bouncer is scrimmed or showing over a dream,
- // otherwise user would be able to pull down QS or expand the shade.
- if (mCentralSurfaces.isBouncerShowingScrimmed()
- || mCentralSurfaces.isBouncerShowingOverDream()) {
- return false;
- }
+ // Do not allow panel expansion if bouncer is scrimmed or showing over a dream,
+ // otherwise user would be able to pull down QS or expand the shade.
+ if (mCentralSurfaces.isBouncerShowingScrimmed()
+ || mCentralSurfaces.isBouncerShowingOverDream()) {
+ return false;
+ }
- // Make sure the next touch won't the blocked after the current ends.
- if (event.getAction() == MotionEvent.ACTION_UP
- || event.getAction() == MotionEvent.ACTION_CANCEL) {
- mBlockingExpansionForCurrentTouch = false;
- }
- // When touch focus transfer happens, ACTION_DOWN->ACTION_UP may happen immediately
- // without any ACTION_MOVE event.
- // In such case, simply expand the panel instead of being stuck at the bottom bar.
- if (mLastEventSynthesizedDown && event.getAction() == MotionEvent.ACTION_UP) {
- expand(true /* animate */);
- }
- initDownStates(event);
-
- // If pulse is expanding already, let's give it the touch. There are situations
- // where the panel starts expanding even though we're also pulsing
- boolean pulseShouldGetTouch = (!mIsExpanding
- && !shouldQuickSettingsIntercept(mDownX, mDownY, 0))
- || mPulseExpansionHandler.isExpanding();
- if (pulseShouldGetTouch && mPulseExpansionHandler.onTouchEvent(event)) {
- // We're expanding all the other ones shouldn't get this anymore
- mShadeLog.logMotionEvent(event, "onTouch: PulseExpansionHandler handled event");
- return true;
- }
- if (mListenForHeadsUp && !mHeadsUpTouchHelper.isTrackingHeadsUp()
- && !mNotificationStackScrollLayoutController.isLongPressInProgress()
- && mHeadsUpTouchHelper.onInterceptTouchEvent(event)) {
- mMetricsLogger.count(COUNTER_PANEL_OPEN_PEEK, 1);
- }
- boolean handled = mHeadsUpTouchHelper.onTouchEvent(event);
+ // Make sure the next touch won't the blocked after the current ends.
+ if (event.getAction() == MotionEvent.ACTION_UP
+ || event.getAction() == MotionEvent.ACTION_CANCEL) {
+ mBlockingExpansionForCurrentTouch = false;
+ }
+ // When touch focus transfer happens, ACTION_DOWN->ACTION_UP may happen immediately
+ // without any ACTION_MOVE event.
+ // In such case, simply expand the panel instead of being stuck at the bottom bar.
+ if (mLastEventSynthesizedDown && event.getAction() == MotionEvent.ACTION_UP) {
+ expand(true /* animate */);
+ }
+ initDownStates(event);
+
+ // If pulse is expanding already, let's give it the touch. There are situations
+ // where the panel starts expanding even though we're also pulsing
+ boolean pulseShouldGetTouch = (!mIsExpanding
+ && !shouldQuickSettingsIntercept(mDownX, mDownY, 0))
+ || mPulseExpansionHandler.isExpanding();
+ if (pulseShouldGetTouch && mPulseExpansionHandler.onTouchEvent(event)) {
+ // We're expanding all the other ones shouldn't get this anymore
+ mShadeLog.logMotionEvent(event, "onTouch: PulseExpansionHandler handled event");
+ return true;
+ }
+ if (mListenForHeadsUp && !mHeadsUpTouchHelper.isTrackingHeadsUp()
+ && !mNotificationStackScrollLayoutController.isLongPressInProgress()
+ && mHeadsUpTouchHelper.onInterceptTouchEvent(event)) {
+ mMetricsLogger.count(COUNTER_PANEL_OPEN_PEEK, 1);
+ }
+ boolean handled = mHeadsUpTouchHelper.onTouchEvent(event);
- if (!mHeadsUpTouchHelper.isTrackingHeadsUp() && handleQsTouch(event)) {
- mShadeLog.logMotionEvent(event, "onTouch: handleQsTouch handled event");
- return true;
- }
- if (event.getActionMasked() == MotionEvent.ACTION_DOWN && isFullyCollapsed()) {
- mMetricsLogger.count(COUNTER_PANEL_OPEN, 1);
- handled = true;
+ if (!mHeadsUpTouchHelper.isTrackingHeadsUp() && handleQsTouch(event)) {
+ mShadeLog.logMotionEvent(event, "onTouch: handleQsTouch handled event");
+ return true;
+ }
+ if (event.getActionMasked() == MotionEvent.ACTION_DOWN && isFullyCollapsed()) {
+ mMetricsLogger.count(COUNTER_PANEL_OPEN, 1);
+ handled = true;
+ }
+
+ if (event.getActionMasked() == MotionEvent.ACTION_DOWN && isFullyExpanded()
+ && mStatusBarKeyguardViewManager.isShowing()) {
+ mStatusBarKeyguardViewManager.updateKeyguardPosition(event.getX());
+ }
+ handled |= handleTouch(v, event);
+ return !mDozing || mPulsing || handled;
+ }
+
+ public boolean handleTouch(View v, MotionEvent event) {
+ if (mInstantExpanding) {
+ mShadeLog.logMotionEvent(event, "onTouch: touch ignored due to instant expanding");
+ return false;
+ }
+ if (mTouchDisabled && event.getActionMasked() != MotionEvent.ACTION_CANCEL) {
+ mShadeLog.logMotionEvent(event, "onTouch: non-cancel action, touch disabled");
+ return false;
+ }
+ if (mMotionAborted && event.getActionMasked() != MotionEvent.ACTION_DOWN) {
+ mShadeLog.logMotionEvent(event, "onTouch: non-down action, motion was aborted");
+ return false;
+ }
+
+ // If dragging should not expand the notifications shade, then return false.
+ if (!mNotificationsDragEnabled) {
+ if (mTracking) {
+ // Turn off tracking if it's on or the shade can get stuck in the down position.
+ onTrackingStopped(true /* expand */);
}
+ mShadeLog.logMotionEvent(event, "onTouch: drag not enabled");
+ return false;
+ }
- if (event.getActionMasked() == MotionEvent.ACTION_DOWN && isFullyExpanded()
- && mStatusBarKeyguardViewManager.isShowing()) {
- mStatusBarKeyguardViewManager.updateKeyguardPosition(event.getX());
+ // On expanding, single mouse click expands the panel instead of dragging.
+ if (isFullyCollapsed() && event.isFromSource(InputDevice.SOURCE_MOUSE)) {
+ if (event.getAction() == MotionEvent.ACTION_UP) {
+ expand(true);
}
+ return true;
+ }
+
+ /*
+ * We capture touch events here and update the expand height here in case according to
+ * the users fingers. This also handles multi-touch.
+ *
+ * Flinging is also enabled in order to open or close the shade.
+ */
+
+ int pointerIndex = event.findPointerIndex(mTrackingPointer);
+ if (pointerIndex < 0) {
+ pointerIndex = 0;
+ mTrackingPointer = event.getPointerId(pointerIndex);
+ }
+ final float x = event.getX(pointerIndex);
+ final float y = event.getY(pointerIndex);
+
+ if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
+ mGestureWaitForTouchSlop = shouldGestureWaitForTouchSlop();
+ mIgnoreXTouchSlop = true;
+ }
- handled |= super.onTouch(v, event);
- return !mDozing || mPulsing || handled;
+ switch (event.getActionMasked()) {
+ case MotionEvent.ACTION_DOWN:
+ startExpandMotion(x, y, false /* startTracking */, mExpandedHeight);
+ mMinExpandHeight = 0.0f;
+ mPanelClosedOnDown = isFullyCollapsed();
+ mHasLayoutedSinceDown = false;
+ mUpdateFlingOnLayout = false;
+ mMotionAborted = false;
+ mDownTime = mSystemClock.uptimeMillis();
+ mTouchAboveFalsingThreshold = false;
+ mCollapsedAndHeadsUpOnDown =
+ isFullyCollapsed() && mHeadsUpManager.hasPinnedHeadsUp();
+ addMovement(event);
+ boolean regularHeightAnimationRunning = mHeightAnimator != null
+ && !mHintAnimationRunning && !mIsSpringBackAnimation;
+ if (!mGestureWaitForTouchSlop || regularHeightAnimationRunning) {
+ mTouchSlopExceeded = regularHeightAnimationRunning
+ || mTouchSlopExceededBeforeDown;
+ cancelHeightAnimator();
+ onTrackingStarted();
+ }
+ if (isFullyCollapsed() && !mHeadsUpManager.hasPinnedHeadsUp()
+ && !mCentralSurfaces.isBouncerShowing()) {
+ startOpening(event);
+ }
+ break;
+
+ case MotionEvent.ACTION_POINTER_UP:
+ final int upPointer = event.getPointerId(event.getActionIndex());
+ if (mTrackingPointer == upPointer) {
+ // gesture is ongoing, find a new pointer to track
+ final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1;
+ final float newY = event.getY(newIndex);
+ final float newX = event.getX(newIndex);
+ mTrackingPointer = event.getPointerId(newIndex);
+ mHandlingPointerUp = true;
+ startExpandMotion(newX, newY, true /* startTracking */, mExpandedHeight);
+ mHandlingPointerUp = false;
+ }
+ break;
+ case MotionEvent.ACTION_POINTER_DOWN:
+ if (mStatusBarStateController.getState() == StatusBarState.KEYGUARD) {
+ mMotionAborted = true;
+ endMotionEvent(event, x, y, true /* forceCancel */);
+ return false;
+ }
+ break;
+ case MotionEvent.ACTION_MOVE:
+ addMovement(event);
+ float h = y - mInitialTouchY;
+
+ // If the panel was collapsed when touching, we only need to check for the
+ // y-component of the gesture, as we have no conflicting horizontal gesture.
+ if (Math.abs(h) > getTouchSlop(event)
+ && (Math.abs(h) > Math.abs(x - mInitialTouchX)
+ || mIgnoreXTouchSlop)) {
+ mTouchSlopExceeded = true;
+ if (mGestureWaitForTouchSlop && !mTracking && !mCollapsedAndHeadsUpOnDown) {
+ if (mInitialOffsetOnTouch != 0f) {
+ startExpandMotion(x, y, false /* startTracking */, mExpandedHeight);
+ h = 0;
+ }
+ cancelHeightAnimator();
+ onTrackingStarted();
+ }
+ }
+ float newHeight = Math.max(0, h + mInitialOffsetOnTouch);
+ newHeight = Math.max(newHeight, mMinExpandHeight);
+ if (-h >= getFalsingThreshold()) {
+ mTouchAboveFalsingThreshold = true;
+ mUpwardsWhenThresholdReached = isDirectionUpwards(x, y);
+ }
+ if ((!mGestureWaitForTouchSlop || mTracking) && !isTrackingBlocked()) {
+ // Count h==0 as part of swipe-up,
+ // otherwise {@link NotificationStackScrollLayout}
+ // wrongly enables stack height updates at the start of lockscreen swipe-up
+ mAmbientState.setSwipingUp(h <= 0);
+ setExpandedHeightInternal(newHeight);
+ }
+ break;
+
+ case MotionEvent.ACTION_UP:
+ case MotionEvent.ACTION_CANCEL:
+ addMovement(event);
+ endMotionEvent(event, x, y, false /* forceCancel */);
+ // mHeightAnimator is null, there is no remaining frame, ends instrumenting.
+ if (mHeightAnimator == null) {
+ if (event.getActionMasked() == MotionEvent.ACTION_UP) {
+ endJankMonitoring();
+ } else {
+ cancelJankMonitoring();
+ }
+ }
+ break;
}
- };
+ return !mGestureWaitForTouchSlop || mTracking;
+ }
}
private final PhoneStatusBarView.TouchEventHandler mStatusBarViewTouchEventHandler =
@@ -4316,8 +4866,7 @@ public final class NotificationPanelViewController extends PanelViewController {
}
};
- @Override
- protected OnConfigurationChangedListener createOnConfigurationChangedListener() {
+ private OnConfigurationChangedListener createOnConfigurationChangedListener() {
return new OnConfigurationChangedListener();
}
@@ -4379,6 +4928,585 @@ public final class NotificationPanelViewController extends PanelViewController {
.commitUpdate(mDisplayId);
}
+ private void logf(String fmt, Object... args) {
+ Log.v(TAG, (mViewName != null ? (mViewName + ": ") : "") + String.format(fmt, args));
+ }
+
+ private void notifyExpandingStarted() {
+ if (!mExpanding) {
+ mExpanding = true;
+ onExpandingStarted();
+ }
+ }
+
+ private void notifyExpandingFinished() {
+ endClosing();
+ if (mExpanding) {
+ mExpanding = false;
+ onExpandingFinished();
+ }
+ }
+
+ private float getTouchSlop(MotionEvent event) {
+ // Adjust the touch slop if another gesture may be being performed.
+ return event.getClassification() == MotionEvent.CLASSIFICATION_AMBIGUOUS_GESTURE
+ ? mTouchSlop * mSlopMultiplier
+ : mTouchSlop;
+ }
+
+ private void addMovement(MotionEvent event) {
+ // Add movement to velocity tracker using raw screen X and Y coordinates instead
+ // of window coordinates because the window frame may be moving at the same time.
+ float deltaX = event.getRawX() - event.getX();
+ float deltaY = event.getRawY() - event.getY();
+ event.offsetLocation(deltaX, deltaY);
+ mVelocityTracker.addMovement(event);
+ event.offsetLocation(-deltaX, -deltaY);
+ }
+
+ public void startExpandLatencyTracking() {
+ if (mLatencyTracker.isEnabled()) {
+ mLatencyTracker.onActionStart(LatencyTracker.ACTION_EXPAND_PANEL);
+ mExpandLatencyTracking = true;
+ }
+ }
+
+ private void startOpening(MotionEvent event) {
+ updatePanelExpansionAndVisibility();
+ maybeVibrateOnOpening();
+
+ //TODO: keyguard opens QS a different way; log that too?
+
+ // Log the position of the swipe that opened the panel
+ float width = mCentralSurfaces.getDisplayWidth();
+ float height = mCentralSurfaces.getDisplayHeight();
+ int rot = mCentralSurfaces.getRotation();
+
+ mLockscreenGestureLogger.writeAtFractionalPosition(MetricsEvent.ACTION_PANEL_VIEW_EXPAND,
+ (int) (event.getX() / width * 100), (int) (event.getY() / height * 100), rot);
+ mLockscreenGestureLogger
+ .log(LockscreenUiEvent.LOCKSCREEN_UNLOCKED_NOTIFICATION_PANEL_EXPAND);
+ }
+
+ private void maybeVibrateOnOpening() {
+ if (mVibrateOnOpening) {
+ mVibratorHelper.vibrate(VibrationEffect.EFFECT_TICK);
+ }
+ }
+
+ /**
+ * @return whether the swiping direction is upwards and above a 45 degree angle compared to the
+ * horizontal direction
+ */
+ private boolean isDirectionUpwards(float x, float y) {
+ float xDiff = x - mInitialExpandX;
+ float yDiff = y - mInitialExpandY;
+ if (yDiff >= 0) {
+ return false;
+ }
+ return Math.abs(yDiff) >= Math.abs(xDiff);
+ }
+
+ public void startExpandMotion(float newX, float newY, boolean startTracking,
+ float expandedHeight) {
+ if (!mHandlingPointerUp && !mStatusBarStateController.isDozing()) {
+ beginJankMonitoring();
+ }
+ mInitialOffsetOnTouch = expandedHeight;
+ mInitialExpandY = newY;
+ mInitialExpandX = newX;
+ mInitialTouchFromKeyguard = mStatusBarStateController.getState() == StatusBarState.KEYGUARD;
+ if (startTracking) {
+ mTouchSlopExceeded = true;
+ setExpandedHeight(mInitialOffsetOnTouch);
+ onTrackingStarted();
+ }
+ }
+
+ private void endMotionEvent(MotionEvent event, float x, float y, boolean forceCancel) {
+ mTrackingPointer = -1;
+ mAmbientState.setSwipingUp(false);
+ if ((mTracking && mTouchSlopExceeded) || Math.abs(x - mInitialExpandX) > mTouchSlop
+ || Math.abs(y - mInitialExpandY) > mTouchSlop
+ || event.getActionMasked() == MotionEvent.ACTION_CANCEL || forceCancel) {
+ mVelocityTracker.computeCurrentVelocity(1000);
+ float vel = mVelocityTracker.getYVelocity();
+ float vectorVel = (float) Math.hypot(
+ mVelocityTracker.getXVelocity(), mVelocityTracker.getYVelocity());
+
+ final boolean onKeyguard =
+ mStatusBarStateController.getState() == StatusBarState.KEYGUARD;
+
+ final boolean expand;
+ if (mKeyguardStateController.isKeyguardFadingAway()
+ || (mInitialTouchFromKeyguard && !onKeyguard)) {
+ // Don't expand for any touches that started from the keyguard and ended after the
+ // keyguard is gone.
+ expand = false;
+ } else if (event.getActionMasked() == MotionEvent.ACTION_CANCEL || forceCancel) {
+ if (onKeyguard) {
+ expand = true;
+ } else if (mCentralSurfaces.isBouncerShowingOverDream()) {
+ expand = false;
+ } else {
+ // If we get a cancel, put the shade back to the state it was in when the
+ // gesture started
+ expand = !mPanelClosedOnDown;
+ }
+ } else {
+ expand = flingExpands(vel, vectorVel, x, y);
+ }
+
+ mDozeLog.traceFling(expand, mTouchAboveFalsingThreshold,
+ mCentralSurfaces.isFalsingThresholdNeeded(),
+ mCentralSurfaces.isWakeUpComingFromTouch());
+ // Log collapse gesture if on lock screen.
+ if (!expand && onKeyguard) {
+ float displayDensity = mCentralSurfaces.getDisplayDensity();
+ int heightDp = (int) Math.abs((y - mInitialExpandY) / displayDensity);
+ int velocityDp = (int) Math.abs(vel / displayDensity);
+ mLockscreenGestureLogger.write(MetricsEvent.ACTION_LS_UNLOCK, heightDp, velocityDp);
+ mLockscreenGestureLogger.log(LockscreenUiEvent.LOCKSCREEN_UNLOCK);
+ }
+ @Classifier.InteractionType int interactionType = vel == 0 ? GENERIC
+ : y - mInitialExpandY > 0 ? QUICK_SETTINGS
+ : (mKeyguardStateController.canDismissLockScreen()
+ ? UNLOCK : BOUNCER_UNLOCK);
+
+ fling(vel, expand, isFalseTouch(x, y, interactionType));
+ onTrackingStopped(expand);
+ mUpdateFlingOnLayout = expand && mPanelClosedOnDown && !mHasLayoutedSinceDown;
+ if (mUpdateFlingOnLayout) {
+ mUpdateFlingVelocity = vel;
+ }
+ } else if (!mCentralSurfaces.isBouncerShowing()
+ && !mStatusBarKeyguardViewManager.isShowingAlternateAuthOrAnimating()
+ && !mKeyguardStateController.isKeyguardGoingAway()) {
+ boolean expands = onEmptySpaceClick();
+ onTrackingStopped(expands);
+ }
+ mVelocityTracker.clear();
+ }
+
+ private float getCurrentExpandVelocity() {
+ mVelocityTracker.computeCurrentVelocity(1000);
+ return mVelocityTracker.getYVelocity();
+ }
+
+ private void endClosing() {
+ if (mClosing) {
+ setIsClosing(false);
+ onClosingFinished();
+ }
+ }
+
+ /**
+ * @param x the final x-coordinate when the finger was lifted
+ * @param y the final y-coordinate when the finger was lifted
+ * @return whether this motion should be regarded as a false touch
+ */
+ private boolean isFalseTouch(float x, float y,
+ @Classifier.InteractionType int interactionType) {
+ if (!mCentralSurfaces.isFalsingThresholdNeeded()) {
+ return false;
+ }
+ if (mFalsingManager.isClassifierEnabled()) {
+ return mFalsingManager.isFalseTouch(interactionType);
+ }
+ if (!mTouchAboveFalsingThreshold) {
+ return true;
+ }
+ if (mUpwardsWhenThresholdReached) {
+ return false;
+ }
+ return !isDirectionUpwards(x, y);
+ }
+
+ private void fling(float vel, boolean expand, boolean expandBecauseOfFalsing) {
+ fling(vel, expand, 1.0f /* collapseSpeedUpFactor */, expandBecauseOfFalsing);
+ }
+
+ private void fling(float vel, boolean expand, float collapseSpeedUpFactor,
+ boolean expandBecauseOfFalsing) {
+ float target = expand ? getMaxPanelHeight() : 0;
+ if (!expand) {
+ setIsClosing(true);
+ }
+ flingToHeight(vel, expand, target, collapseSpeedUpFactor, expandBecauseOfFalsing);
+ }
+
+ private void springBack() {
+ if (mOverExpansion == 0) {
+ onFlingEnd(false /* cancelled */);
+ return;
+ }
+ mIsSpringBackAnimation = true;
+ ValueAnimator animator = ValueAnimator.ofFloat(mOverExpansion, 0);
+ animator.addUpdateListener(
+ animation -> setOverExpansionInternal((float) animation.getAnimatedValue(),
+ false /* isFromGesture */));
+ animator.setDuration(SHADE_OPEN_SPRING_BACK_DURATION);
+ animator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
+ animator.addListener(new AnimatorListenerAdapter() {
+ private boolean mCancelled;
+
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ mCancelled = true;
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mIsSpringBackAnimation = false;
+ onFlingEnd(mCancelled);
+ }
+ });
+ setAnimator(animator);
+ animator.start();
+ }
+
+ public String getName() {
+ return mViewName;
+ }
+
+ public void setExpandedHeight(float height) {
+ if (DEBUG) logf("setExpandedHeight(%.1f)", height);
+ setExpandedHeightInternal(height);
+ }
+
+ private void requestPanelHeightUpdate() {
+ float currentMaxPanelHeight = getMaxPanelHeight();
+
+ if (isFullyCollapsed()) {
+ return;
+ }
+
+ if (currentMaxPanelHeight == mExpandedHeight) {
+ return;
+ }
+
+ if (mTracking && !isTrackingBlocked()) {
+ return;
+ }
+
+ if (mHeightAnimator != null && !mIsSpringBackAnimation) {
+ mPanelUpdateWhenAnimatorEnds = true;
+ return;
+ }
+
+ setExpandedHeight(currentMaxPanelHeight);
+ }
+
+ public void setExpandedHeightInternal(float h) {
+ if (isNaN(h)) {
+ Log.wtf(TAG, "ExpandedHeight set to NaN");
+ }
+ mNotificationShadeWindowController.batchApplyWindowLayoutParams(() -> {
+ if (mExpandLatencyTracking && h != 0f) {
+ DejankUtils.postAfterTraversal(
+ () -> mLatencyTracker.onActionEnd(LatencyTracker.ACTION_EXPAND_PANEL));
+ mExpandLatencyTracking = false;
+ }
+ float maxPanelHeight = getMaxPanelHeight();
+ if (mHeightAnimator == null) {
+ // Split shade has its own overscroll logic
+ if (mTracking && !mInSplitShade) {
+ float overExpansionPixels = Math.max(0, h - maxPanelHeight);
+ setOverExpansionInternal(overExpansionPixels, true /* isFromGesture */);
+ }
+ mExpandedHeight = Math.min(h, maxPanelHeight);
+ } else {
+ mExpandedHeight = h;
+ }
+
+ // If we are closing the panel and we are almost there due to a slow decelerating
+ // interpolator, abort the animation.
+ if (mExpandedHeight < 1f && mExpandedHeight != 0f && mClosing) {
+ mExpandedHeight = 0f;
+ if (mHeightAnimator != null) {
+ mHeightAnimator.end();
+ }
+ }
+ mExpansionDragDownAmountPx = h;
+ mExpandedFraction = Math.min(1f,
+ maxPanelHeight == 0 ? 0 : mExpandedHeight / maxPanelHeight);
+ mAmbientState.setExpansionFraction(mExpandedFraction);
+ onHeightUpdated(mExpandedHeight);
+ updatePanelExpansionAndVisibility();
+ });
+ }
+
+ /**
+ * Set the current overexpansion
+ *
+ * @param overExpansion the amount of overexpansion to apply
+ * @param isFromGesture is this amount from a gesture and needs to be rubberBanded?
+ */
+ private void setOverExpansionInternal(float overExpansion, boolean isFromGesture) {
+ if (!isFromGesture) {
+ mLastGesturedOverExpansion = -1;
+ setOverExpansion(overExpansion);
+ } else if (mLastGesturedOverExpansion != overExpansion) {
+ mLastGesturedOverExpansion = overExpansion;
+ final float heightForFullOvershoot = mView.getHeight() / 3.0f;
+ float newExpansion = MathUtils.saturate(overExpansion / heightForFullOvershoot);
+ newExpansion = Interpolators.getOvershootInterpolation(newExpansion);
+ setOverExpansion(newExpansion * mPanelFlingOvershootAmount * 2.0f);
+ }
+ }
+
+ public void setExpandedFraction(float frac) {
+ setExpandedHeight(getMaxPanelHeight() * frac);
+ }
+
+ public float getExpandedHeight() {
+ return mExpandedHeight;
+ }
+
+ public float getExpandedFraction() {
+ return mExpandedFraction;
+ }
+
+ public boolean isFullyExpanded() {
+ return mExpandedHeight >= getMaxPanelHeight();
+ }
+
+ public boolean isFullyCollapsed() {
+ return mExpandedFraction <= 0.0f;
+ }
+
+ public boolean isCollapsing() {
+ return mClosing || mIsLaunchAnimationRunning;
+ }
+
+ public boolean isFlinging() {
+ return mIsFlinging;
+ }
+
+ public boolean isTracking() {
+ return mTracking;
+ }
+
+ public boolean canPanelBeCollapsed() {
+ return !isFullyCollapsed() && !mTracking && !mClosing;
+ }
+
+ private final Runnable mFlingCollapseRunnable = () -> fling(0, false /* expand */,
+ mNextCollapseSpeedUpFactor, false /* expandBecauseOfFalsing */);
+
+ public void instantCollapse() {
+ abortAnimations();
+ setExpandedFraction(0f);
+ if (mExpanding) {
+ notifyExpandingFinished();
+ }
+ if (mInstantExpanding) {
+ mInstantExpanding = false;
+ updatePanelExpansionAndVisibility();
+ }
+ }
+
+ private void abortAnimations() {
+ cancelHeightAnimator();
+ mView.removeCallbacks(mFlingCollapseRunnable);
+ }
+
+ public boolean isUnlockHintRunning() {
+ return mHintAnimationRunning;
+ }
+
+ /**
+ * Phase 1: Move everything upwards.
+ */
+ private void startUnlockHintAnimationPhase1(final Runnable onAnimationFinished) {
+ float target = Math.max(0, getMaxPanelHeight() - mHintDistance);
+ ValueAnimator animator = createHeightAnimator(target);
+ animator.setDuration(250);
+ animator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
+ animator.addListener(new AnimatorListenerAdapter() {
+ private boolean mCancelled;
+
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ mCancelled = true;
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ if (mCancelled) {
+ setAnimator(null);
+ onAnimationFinished.run();
+ } else {
+ startUnlockHintAnimationPhase2(onAnimationFinished);
+ }
+ }
+ });
+ animator.start();
+ setAnimator(animator);
+
+ final List<ViewPropertyAnimator> indicationAnimators =
+ mKeyguardBottomArea.getIndicationAreaAnimators();
+ for (final ViewPropertyAnimator indicationAreaAnimator : indicationAnimators) {
+ indicationAreaAnimator
+ .translationY(-mHintDistance)
+ .setDuration(250)
+ .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
+ .withEndAction(() -> indicationAreaAnimator
+ .translationY(0)
+ .setDuration(450)
+ .setInterpolator(mBounceInterpolator)
+ .start())
+ .start();
+ }
+ }
+
+ private void setAnimator(ValueAnimator animator) {
+ mHeightAnimator = animator;
+ if (animator == null && mPanelUpdateWhenAnimatorEnds) {
+ mPanelUpdateWhenAnimatorEnds = false;
+ requestPanelHeightUpdate();
+ }
+ }
+
+ /**
+ * Phase 2: Bounce down.
+ */
+ private void startUnlockHintAnimationPhase2(final Runnable onAnimationFinished) {
+ ValueAnimator animator = createHeightAnimator(getMaxPanelHeight());
+ animator.setDuration(450);
+ animator.setInterpolator(mBounceInterpolator);
+ animator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ setAnimator(null);
+ onAnimationFinished.run();
+ updatePanelExpansionAndVisibility();
+ }
+ });
+ animator.start();
+ setAnimator(animator);
+ }
+
+ private ValueAnimator createHeightAnimator(float targetHeight) {
+ return createHeightAnimator(targetHeight, 0.0f /* performOvershoot */);
+ }
+
+ /**
+ * Create an animator that can also overshoot
+ *
+ * @param targetHeight the target height
+ * @param overshootAmount the amount of overshoot desired
+ */
+ private ValueAnimator createHeightAnimator(float targetHeight, float overshootAmount) {
+ float startExpansion = mOverExpansion;
+ ValueAnimator animator = ValueAnimator.ofFloat(mExpandedHeight, targetHeight);
+ animator.addUpdateListener(
+ animation -> {
+ if (overshootAmount > 0.0f
+ // Also remove the overExpansion when collapsing
+ || (targetHeight == 0.0f && startExpansion != 0)) {
+ final float expansion = MathUtils.lerp(
+ startExpansion,
+ mPanelFlingOvershootAmount * overshootAmount,
+ Interpolators.FAST_OUT_SLOW_IN.getInterpolation(
+ animator.getAnimatedFraction()));
+ setOverExpansionInternal(expansion, false /* isFromGesture */);
+ }
+ setExpandedHeightInternal((float) animation.getAnimatedValue());
+ });
+ return animator;
+ }
+
+ /** Update the visibility of {@link PanelView} if necessary. */
+ public void updateVisibility() {
+ mView.setVisibility(shouldPanelBeVisible() ? VISIBLE : INVISIBLE);
+ }
+
+ /**
+ * Updates the panel expansion and {@link PanelView} visibility if necessary.
+ *
+ * TODO(b/200063118): Could public calls to this method be replaced with calls to
+ * {@link #updateVisibility()}? That would allow us to make this method private.
+ */
+ public void updatePanelExpansionAndVisibility() {
+ mPanelExpansionStateManager.onPanelExpansionChanged(
+ mExpandedFraction, isExpanded(), mTracking, mExpansionDragDownAmountPx);
+ updateVisibility();
+ }
+
+ public boolean isExpanded() {
+ return mExpandedFraction > 0f
+ || mInstantExpanding
+ || isPanelVisibleBecauseOfHeadsUp()
+ || mTracking
+ || mHeightAnimator != null
+ && !mIsSpringBackAnimation;
+ }
+
+ /**
+ * Gets called when the user performs a click anywhere in the empty area of the panel.
+ *
+ * @return whether the panel will be expanded after the action performed by this method
+ */
+ private boolean onEmptySpaceClick() {
+ if (mHintAnimationRunning) {
+ return true;
+ }
+ return onMiddleClicked();
+ }
+
+ @VisibleForTesting
+ boolean isClosing() {
+ return mClosing;
+ }
+
+ public void collapseWithDuration(int animationDuration) {
+ mFixedDuration = animationDuration;
+ collapse(false /* delayed */, 1.0f /* speedUpFactor */);
+ mFixedDuration = NO_FIXED_DURATION;
+ }
+
+ public ViewGroup getView() {
+ // TODO: remove this method, or at least reduce references to it.
+ return mView;
+ }
+
+ private void beginJankMonitoring() {
+ if (mInteractionJankMonitor == null) {
+ return;
+ }
+ InteractionJankMonitor.Configuration.Builder builder =
+ InteractionJankMonitor.Configuration.Builder.withView(
+ InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE,
+ mView)
+ .setTag(isFullyCollapsed() ? "Expand" : "Collapse");
+ mInteractionJankMonitor.begin(builder);
+ }
+
+ private void endJankMonitoring() {
+ if (mInteractionJankMonitor == null) {
+ return;
+ }
+ InteractionJankMonitor.getInstance().end(
+ InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE);
+ }
+
+ private void cancelJankMonitoring() {
+ if (mInteractionJankMonitor == null) {
+ return;
+ }
+ InteractionJankMonitor.getInstance().cancel(
+ InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE);
+ }
+
+ private float getExpansionFraction() {
+ return mExpandedFraction;
+ }
+
+ private PanelExpansionStateManager getPanelExpansionStateManager() {
+ return mPanelExpansionStateManager;
+ }
+
private class OnHeightChangedListener implements ExpandableView.OnHeightChangedListener {
@Override
public void onHeightChanged(ExpandableView view, boolean needsAnimation) {
@@ -4785,13 +5913,19 @@ public final class NotificationPanelViewController extends PanelViewController {
}
}
- private class OnLayoutChangeListener extends PanelViewController.OnLayoutChangeListener {
+ private class OnLayoutChangeListener implements View.OnLayoutChangeListener {
@Override
public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft,
int oldTop, int oldRight, int oldBottom) {
DejankUtils.startDetectingBlockingIpcs("NVP#onLayout");
- super.onLayoutChange(v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom);
+ requestPanelHeightUpdate();
+ mHasLayoutedSinceDown = true;
+ if (mUpdateFlingOnLayout) {
+ abortAnimations();
+ fling(mUpdateFlingVelocity, true /* expands */);
+ mUpdateFlingOnLayout = false;
+ }
updateMaxDisplayedNotifications(!shouldAvoidChangingNotificationsCount());
setIsFullWidth(mNotificationStackScrollLayoutController.getWidth() == mView.getWidth());
@@ -5050,4 +6184,12 @@ public final class NotificationPanelViewController extends PanelViewController {
}
}
}
+
+ public class OnConfigurationChangedListener implements
+ PanelView.OnConfigurationChangedListener {
+ @Override
+ public void onConfigurationChanged(Configuration newConfig) {
+ loadDimens();
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/PanelView.java b/packages/SystemUI/src/com/android/systemui/shade/PanelView.java
index efff0db742d7..4349d816b3c3 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/PanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/PanelView.java
@@ -29,7 +29,7 @@ import com.android.systemui.statusbar.phone.KeyguardBottomAreaView;
public abstract class PanelView extends FrameLayout {
public static final boolean DEBUG = false;
public static final String TAG = PanelView.class.getSimpleName();
- private PanelViewController.TouchHandler mTouchHandler;
+ private NotificationPanelViewController.TouchHandler mTouchHandler;
protected CentralSurfaces mCentralSurfaces;
protected HeadsUpManagerPhone mHeadsUpManager;
@@ -49,7 +49,7 @@ public abstract class PanelView extends FrameLayout {
super(context, attrs, defStyleAttr);
}
- public void setOnTouchListener(PanelViewController.TouchHandler touchHandler) {
+ public void setOnTouchListener(NotificationPanelViewController.TouchHandler touchHandler) {
super.setOnTouchListener(touchHandler);
mTouchHandler = touchHandler;
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/PanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/PanelViewController.java
deleted file mode 100644
index 73eaa852e345..000000000000
--- a/packages/SystemUI/src/com/android/systemui/shade/PanelViewController.java
+++ /dev/null
@@ -1,1489 +0,0 @@
-/*
- * Copyright (C) 2019 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.shade;
-
-import static android.view.View.INVISIBLE;
-import static android.view.View.VISIBLE;
-
-import static com.android.systemui.classifier.Classifier.BOUNCER_UNLOCK;
-import static com.android.systemui.classifier.Classifier.GENERIC;
-import static com.android.systemui.classifier.Classifier.QUICK_SETTINGS;
-import static com.android.systemui.classifier.Classifier.UNLOCK;
-import static com.android.systemui.shade.PanelView.DEBUG;
-
-import static java.lang.Float.isNaN;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ValueAnimator;
-import android.content.res.Configuration;
-import android.content.res.Resources;
-import android.os.VibrationEffect;
-import android.util.Log;
-import android.util.MathUtils;
-import android.view.InputDevice;
-import android.view.MotionEvent;
-import android.view.VelocityTracker;
-import android.view.View;
-import android.view.ViewConfiguration;
-import android.view.ViewGroup;
-import android.view.ViewPropertyAnimator;
-import android.view.ViewTreeObserver;
-import android.view.animation.Interpolator;
-
-import com.android.internal.jank.InteractionJankMonitor;
-import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.android.internal.util.LatencyTracker;
-import com.android.systemui.DejankUtils;
-import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
-import com.android.systemui.classifier.Classifier;
-import com.android.systemui.doze.DozeLog;
-import com.android.systemui.plugins.FalsingManager;
-import com.android.systemui.statusbar.NotificationShadeWindowController;
-import com.android.systemui.statusbar.StatusBarState;
-import com.android.systemui.statusbar.SysuiStatusBarStateController;
-import com.android.systemui.statusbar.VibratorHelper;
-import com.android.systemui.statusbar.notification.stack.AmbientState;
-import com.android.systemui.statusbar.phone.BounceInterpolator;
-import com.android.systemui.statusbar.phone.CentralSurfaces;
-import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
-import com.android.systemui.statusbar.phone.KeyguardBottomAreaView;
-import com.android.systemui.statusbar.phone.LockscreenGestureLogger;
-import com.android.systemui.statusbar.phone.LockscreenGestureLogger.LockscreenUiEvent;
-import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
-import com.android.systemui.statusbar.phone.StatusBarTouchableRegionManager;
-import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager;
-import com.android.systemui.statusbar.policy.KeyguardStateController;
-import com.android.systemui.util.time.SystemClock;
-import com.android.wm.shell.animation.FlingAnimationUtils;
-
-import java.io.PrintWriter;
-import java.util.List;
-
-public abstract class PanelViewController {
- public static final String TAG = PanelView.class.getSimpleName();
- public static final float FLING_MAX_LENGTH_SECONDS = 0.6f;
- public static final float FLING_SPEED_UP_FACTOR = 0.6f;
- public static final float FLING_CLOSING_MAX_LENGTH_SECONDS = 0.6f;
- public static final float FLING_CLOSING_SPEED_UP_FACTOR = 0.6f;
- private static final int NO_FIXED_DURATION = -1;
- private static final long SHADE_OPEN_SPRING_OUT_DURATION = 350L;
- private static final long SHADE_OPEN_SPRING_BACK_DURATION = 400L;
-
- /**
- * The factor of the usual high velocity that is needed in order to reach the maximum overshoot
- * when flinging. A low value will make it that most flings will reach the maximum overshoot.
- */
- private static final float FACTOR_OF_HIGH_VELOCITY_FOR_MAX_OVERSHOOT = 0.5f;
-
- protected long mDownTime;
- protected boolean mTouchSlopExceededBeforeDown;
- private float mMinExpandHeight;
- private boolean mPanelUpdateWhenAnimatorEnds;
- private final boolean mVibrateOnOpening;
- protected boolean mIsLaunchAnimationRunning;
- private int mFixedDuration = NO_FIXED_DURATION;
- protected float mOverExpansion;
-
- /**
- * The overshoot amount when the panel flings open
- */
- private float mPanelFlingOvershootAmount;
-
- /**
- * The amount of pixels that we have overexpanded the last time with a gesture
- */
- private float mLastGesturedOverExpansion = -1;
-
- /**
- * Is the current animator the spring back animation?
- */
- private boolean mIsSpringBackAnimation;
-
- private boolean mInSplitShade;
-
- private void logf(String fmt, Object... args) {
- Log.v(TAG, (mViewName != null ? (mViewName + ": ") : "") + String.format(fmt, args));
- }
-
- protected CentralSurfaces mCentralSurfaces;
- protected HeadsUpManagerPhone mHeadsUpManager;
- protected final StatusBarTouchableRegionManager mStatusBarTouchableRegionManager;
-
- private float mHintDistance;
- private float mInitialOffsetOnTouch;
- private boolean mCollapsedAndHeadsUpOnDown;
- private float mExpandedFraction = 0;
- private float mExpansionDragDownAmountPx = 0;
- protected float mExpandedHeight = 0;
- private boolean mPanelClosedOnDown;
- private boolean mHasLayoutedSinceDown;
- private float mUpdateFlingVelocity;
- private boolean mUpdateFlingOnLayout;
- private boolean mClosing;
- protected boolean mTracking;
- private boolean mTouchSlopExceeded;
- private int mTrackingPointer;
- private int mTouchSlop;
- private float mSlopMultiplier;
- protected boolean mHintAnimationRunning;
- private boolean mTouchAboveFalsingThreshold;
- private int mUnlockFalsingThreshold;
- private boolean mTouchStartedInEmptyArea;
- private boolean mMotionAborted;
- private boolean mUpwardsWhenThresholdReached;
- private boolean mAnimatingOnDown;
- private boolean mHandlingPointerUp;
-
- private ValueAnimator mHeightAnimator;
- private final VelocityTracker mVelocityTracker = VelocityTracker.obtain();
- private final FlingAnimationUtils mFlingAnimationUtils;
- private final FlingAnimationUtils mFlingAnimationUtilsClosing;
- private final FlingAnimationUtils mFlingAnimationUtilsDismissing;
- private final LatencyTracker mLatencyTracker;
- private final FalsingManager mFalsingManager;
- private final DozeLog mDozeLog;
- private final VibratorHelper mVibratorHelper;
-
- /**
- * Whether an instant expand request is currently pending and we are just waiting for layout.
- */
- private boolean mInstantExpanding;
- private boolean mAnimateAfterExpanding;
- private boolean mIsFlinging;
-
- private String mViewName;
- private float mInitialTouchY;
- private float mInitialTouchX;
- private boolean mTouchDisabled;
- private boolean mInitialTouchFromKeyguard;
-
- /**
- * Whether or not the PanelView can be expanded or collapsed with a drag.
- */
- private final boolean mNotificationsDragEnabled;
-
- private final Interpolator mBounceInterpolator;
- protected KeyguardBottomAreaView mKeyguardBottomArea;
-
- /**
- * Speed-up factor to be used when {@link #mFlingCollapseRunnable} runs the next time.
- */
- private float mNextCollapseSpeedUpFactor = 1.0f;
-
- protected boolean mExpanding;
- private boolean mGestureWaitForTouchSlop;
- private boolean mIgnoreXTouchSlop;
- private boolean mExpandLatencyTracking;
- private final PanelView mView;
- private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
- private final NotificationShadeWindowController mNotificationShadeWindowController;
- protected final Resources mResources;
- protected final KeyguardStateController mKeyguardStateController;
- protected final SysuiStatusBarStateController mStatusBarStateController;
- protected final AmbientState mAmbientState;
- protected final LockscreenGestureLogger mLockscreenGestureLogger;
- private final PanelExpansionStateManager mPanelExpansionStateManager;
- private final InteractionJankMonitor mInteractionJankMonitor;
- protected final SystemClock mSystemClock;
-
- protected final ShadeLogger mShadeLog;
-
- protected abstract void onExpandingFinished();
-
- protected void onExpandingStarted() {
- }
-
- protected void notifyExpandingStarted() {
- if (!mExpanding) {
- mExpanding = true;
- onExpandingStarted();
- }
- }
-
- protected final void notifyExpandingFinished() {
- endClosing();
- if (mExpanding) {
- mExpanding = false;
- onExpandingFinished();
- }
- }
-
- protected AmbientState getAmbientState() {
- return mAmbientState;
- }
-
- public PanelViewController(
- PanelView view,
- FalsingManager falsingManager,
- DozeLog dozeLog,
- KeyguardStateController keyguardStateController,
- SysuiStatusBarStateController statusBarStateController,
- NotificationShadeWindowController notificationShadeWindowController,
- VibratorHelper vibratorHelper,
- StatusBarKeyguardViewManager statusBarKeyguardViewManager,
- LatencyTracker latencyTracker,
- FlingAnimationUtils.Builder flingAnimationUtilsBuilder,
- StatusBarTouchableRegionManager statusBarTouchableRegionManager,
- LockscreenGestureLogger lockscreenGestureLogger,
- PanelExpansionStateManager panelExpansionStateManager,
- AmbientState ambientState,
- InteractionJankMonitor interactionJankMonitor,
- ShadeLogger shadeLogger,
- SystemClock systemClock) {
- keyguardStateController.addCallback(new KeyguardStateController.Callback() {
- @Override
- public void onKeyguardFadingAwayChanged() {
- requestPanelHeightUpdate();
- }
- });
- mAmbientState = ambientState;
- mView = view;
- mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
- mLockscreenGestureLogger = lockscreenGestureLogger;
- mPanelExpansionStateManager = panelExpansionStateManager;
- mShadeLog = shadeLogger;
- TouchHandler touchHandler = createTouchHandler();
- mView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
- @Override
- public void onViewAttachedToWindow(View v) {
- mViewName = mResources.getResourceName(mView.getId());
- }
-
- @Override
- public void onViewDetachedFromWindow(View v) {
- }
- });
-
- mView.addOnLayoutChangeListener(createLayoutChangeListener());
- mView.setOnTouchListener(touchHandler);
- mView.setOnConfigurationChangedListener(createOnConfigurationChangedListener());
-
- mResources = mView.getResources();
- mKeyguardStateController = keyguardStateController;
- mStatusBarStateController = statusBarStateController;
- mNotificationShadeWindowController = notificationShadeWindowController;
- mFlingAnimationUtils = flingAnimationUtilsBuilder
- .reset()
- .setMaxLengthSeconds(FLING_MAX_LENGTH_SECONDS)
- .setSpeedUpFactor(FLING_SPEED_UP_FACTOR)
- .build();
- mFlingAnimationUtilsClosing = flingAnimationUtilsBuilder
- .reset()
- .setMaxLengthSeconds(FLING_CLOSING_MAX_LENGTH_SECONDS)
- .setSpeedUpFactor(FLING_CLOSING_SPEED_UP_FACTOR)
- .build();
- mFlingAnimationUtilsDismissing = flingAnimationUtilsBuilder
- .reset()
- .setMaxLengthSeconds(0.5f)
- .setSpeedUpFactor(0.6f)
- .setX2(0.6f)
- .setY2(0.84f)
- .build();
- mLatencyTracker = latencyTracker;
- mBounceInterpolator = new BounceInterpolator();
- mFalsingManager = falsingManager;
- mDozeLog = dozeLog;
- mNotificationsDragEnabled = mResources.getBoolean(
- R.bool.config_enableNotificationShadeDrag);
- mVibratorHelper = vibratorHelper;
- mVibrateOnOpening = mResources.getBoolean(R.bool.config_vibrateOnIconAnimation);
- mStatusBarTouchableRegionManager = statusBarTouchableRegionManager;
- mInteractionJankMonitor = interactionJankMonitor;
- mSystemClock = systemClock;
- }
-
- protected void loadDimens() {
- final ViewConfiguration configuration = ViewConfiguration.get(mView.getContext());
- mTouchSlop = configuration.getScaledTouchSlop();
- mSlopMultiplier = configuration.getScaledAmbiguousGestureMultiplier();
- mHintDistance = mResources.getDimension(R.dimen.hint_move_distance);
- mPanelFlingOvershootAmount = mResources.getDimension(R.dimen.panel_overshoot_amount);
- mUnlockFalsingThreshold =
- mResources.getDimensionPixelSize(R.dimen.unlock_falsing_threshold);
- mInSplitShade = mResources.getBoolean(R.bool.config_use_split_notification_shade);
- }
-
- protected float getTouchSlop(MotionEvent event) {
- // Adjust the touch slop if another gesture may be being performed.
- return event.getClassification() == MotionEvent.CLASSIFICATION_AMBIGUOUS_GESTURE
- ? mTouchSlop * mSlopMultiplier
- : mTouchSlop;
- }
-
- private void addMovement(MotionEvent event) {
- // Add movement to velocity tracker using raw screen X and Y coordinates instead
- // of window coordinates because the window frame may be moving at the same time.
- float deltaX = event.getRawX() - event.getX();
- float deltaY = event.getRawY() - event.getY();
- event.offsetLocation(deltaX, deltaY);
- mVelocityTracker.addMovement(event);
- event.offsetLocation(-deltaX, -deltaY);
- }
-
- public void setTouchAndAnimationDisabled(boolean disabled) {
- mTouchDisabled = disabled;
- if (mTouchDisabled) {
- cancelHeightAnimator();
- if (mTracking) {
- onTrackingStopped(true /* expanded */);
- }
- notifyExpandingFinished();
- }
- }
-
- public void startExpandLatencyTracking() {
- if (mLatencyTracker.isEnabled()) {
- mLatencyTracker.onActionStart(LatencyTracker.ACTION_EXPAND_PANEL);
- mExpandLatencyTracking = true;
- }
- }
-
- private void startOpening(MotionEvent event) {
- updatePanelExpansionAndVisibility();
- maybeVibrateOnOpening();
-
- //TODO: keyguard opens QS a different way; log that too?
-
- // Log the position of the swipe that opened the panel
- float width = mCentralSurfaces.getDisplayWidth();
- float height = mCentralSurfaces.getDisplayHeight();
- int rot = mCentralSurfaces.getRotation();
-
- mLockscreenGestureLogger.writeAtFractionalPosition(MetricsEvent.ACTION_PANEL_VIEW_EXPAND,
- (int) (event.getX() / width * 100), (int) (event.getY() / height * 100), rot);
- mLockscreenGestureLogger
- .log(LockscreenUiEvent.LOCKSCREEN_UNLOCKED_NOTIFICATION_PANEL_EXPAND);
- }
-
- protected void maybeVibrateOnOpening() {
- if (mVibrateOnOpening) {
- mVibratorHelper.vibrate(VibrationEffect.EFFECT_TICK);
- }
- }
-
- protected abstract float getOpeningHeight();
-
- /**
- * @return whether the swiping direction is upwards and above a 45 degree angle compared to the
- * horizontal direction
- */
- private boolean isDirectionUpwards(float x, float y) {
- float xDiff = x - mInitialTouchX;
- float yDiff = y - mInitialTouchY;
- if (yDiff >= 0) {
- return false;
- }
- return Math.abs(yDiff) >= Math.abs(xDiff);
- }
-
- public void startExpandMotion(float newX, float newY, boolean startTracking,
- float expandedHeight) {
- if (!mHandlingPointerUp && !mStatusBarStateController.isDozing()) {
- beginJankMonitoring();
- }
- mInitialOffsetOnTouch = expandedHeight;
- mInitialTouchY = newY;
- mInitialTouchX = newX;
- mInitialTouchFromKeyguard = mStatusBarStateController.getState() == StatusBarState.KEYGUARD;
- if (startTracking) {
- mTouchSlopExceeded = true;
- setExpandedHeight(mInitialOffsetOnTouch);
- onTrackingStarted();
- }
- }
-
- private void endMotionEvent(MotionEvent event, float x, float y, boolean forceCancel) {
- mTrackingPointer = -1;
- mAmbientState.setSwipingUp(false);
- if ((mTracking && mTouchSlopExceeded) || Math.abs(x - mInitialTouchX) > mTouchSlop
- || Math.abs(y - mInitialTouchY) > mTouchSlop
- || event.getActionMasked() == MotionEvent.ACTION_CANCEL || forceCancel) {
- mVelocityTracker.computeCurrentVelocity(1000);
- float vel = mVelocityTracker.getYVelocity();
- float vectorVel = (float) Math.hypot(
- mVelocityTracker.getXVelocity(), mVelocityTracker.getYVelocity());
-
- final boolean onKeyguard =
- mStatusBarStateController.getState() == StatusBarState.KEYGUARD;
-
- final boolean expand;
- if (mKeyguardStateController.isKeyguardFadingAway()
- || (mInitialTouchFromKeyguard && !onKeyguard)) {
- // Don't expand for any touches that started from the keyguard and ended after the
- // keyguard is gone.
- expand = false;
- } else if (event.getActionMasked() == MotionEvent.ACTION_CANCEL || forceCancel) {
- if (onKeyguard) {
- expand = true;
- } else if (mCentralSurfaces.isBouncerShowingOverDream()) {
- expand = false;
- } else {
- // If we get a cancel, put the shade back to the state it was in when the
- // gesture started
- expand = !mPanelClosedOnDown;
- }
- } else {
- expand = flingExpands(vel, vectorVel, x, y);
- }
-
- mDozeLog.traceFling(expand, mTouchAboveFalsingThreshold,
- mCentralSurfaces.isFalsingThresholdNeeded(),
- mCentralSurfaces.isWakeUpComingFromTouch());
- // Log collapse gesture if on lock screen.
- if (!expand && onKeyguard) {
- float displayDensity = mCentralSurfaces.getDisplayDensity();
- int heightDp = (int) Math.abs((y - mInitialTouchY) / displayDensity);
- int velocityDp = (int) Math.abs(vel / displayDensity);
- mLockscreenGestureLogger.write(MetricsEvent.ACTION_LS_UNLOCK, heightDp, velocityDp);
- mLockscreenGestureLogger.log(LockscreenUiEvent.LOCKSCREEN_UNLOCK);
- }
- @Classifier.InteractionType int interactionType = vel == 0 ? GENERIC
- : y - mInitialTouchY > 0 ? QUICK_SETTINGS
- : (mKeyguardStateController.canDismissLockScreen()
- ? UNLOCK : BOUNCER_UNLOCK);
-
- fling(vel, expand, isFalseTouch(x, y, interactionType));
- onTrackingStopped(expand);
- mUpdateFlingOnLayout = expand && mPanelClosedOnDown && !mHasLayoutedSinceDown;
- if (mUpdateFlingOnLayout) {
- mUpdateFlingVelocity = vel;
- }
- } else if (!mCentralSurfaces.isBouncerShowing()
- && !mStatusBarKeyguardViewManager.isShowingAlternateAuthOrAnimating()
- && !mKeyguardStateController.isKeyguardGoingAway()) {
- boolean expands = onEmptySpaceClick();
- onTrackingStopped(expands);
- }
- mVelocityTracker.clear();
- }
-
- protected float getCurrentExpandVelocity() {
- mVelocityTracker.computeCurrentVelocity(1000);
- return mVelocityTracker.getYVelocity();
- }
-
- private int getFalsingThreshold() {
- float factor = mCentralSurfaces.isWakeUpComingFromTouch() ? 1.5f : 1.0f;
- return (int) (mUnlockFalsingThreshold * factor);
- }
-
- protected abstract boolean shouldGestureWaitForTouchSlop();
-
- protected void onTrackingStopped(boolean expand) {
- mTracking = false;
- mCentralSurfaces.onTrackingStopped(expand);
- updatePanelExpansionAndVisibility();
- }
-
- protected void onTrackingStarted() {
- endClosing();
- mTracking = true;
- mCentralSurfaces.onTrackingStarted();
- notifyExpandingStarted();
- updatePanelExpansionAndVisibility();
- }
-
- /**
- * @return Whether a pair of coordinates are inside the visible view content bounds.
- */
- protected abstract boolean isInContentBounds(float x, float y);
-
- protected void cancelHeightAnimator() {
- if (mHeightAnimator != null) {
- if (mHeightAnimator.isRunning()) {
- mPanelUpdateWhenAnimatorEnds = false;
- }
- mHeightAnimator.cancel();
- }
- endClosing();
- }
-
- private void endClosing() {
- if (mClosing) {
- setIsClosing(false);
- onClosingFinished();
- }
- }
-
- protected boolean canCollapsePanelOnTouch() {
- return true;
- }
-
- protected float getContentHeight() {
- return mExpandedHeight;
- }
-
- /**
- * @param vel the current vertical velocity of the motion
- * @param vectorVel the length of the vectorial velocity
- * @return whether a fling should expands the panel; contracts otherwise
- */
- protected boolean flingExpands(float vel, float vectorVel, float x, float y) {
- if (mFalsingManager.isUnlockingDisabled()) {
- return true;
- }
-
- @Classifier.InteractionType int interactionType = y - mInitialTouchY > 0
- ? QUICK_SETTINGS : (
- mKeyguardStateController.canDismissLockScreen() ? UNLOCK : BOUNCER_UNLOCK);
-
- if (isFalseTouch(x, y, interactionType)) {
- return true;
- }
- if (Math.abs(vectorVel) < mFlingAnimationUtils.getMinVelocityPxPerSecond()) {
- return shouldExpandWhenNotFlinging();
- } else {
- return vel > 0;
- }
- }
-
- protected boolean shouldExpandWhenNotFlinging() {
- return getExpandedFraction() > 0.5f;
- }
-
- /**
- * @param x the final x-coordinate when the finger was lifted
- * @param y the final y-coordinate when the finger was lifted
- * @return whether this motion should be regarded as a false touch
- */
- private boolean isFalseTouch(float x, float y,
- @Classifier.InteractionType int interactionType) {
- if (!mCentralSurfaces.isFalsingThresholdNeeded()) {
- return false;
- }
- if (mFalsingManager.isClassifierEnabled()) {
- return mFalsingManager.isFalseTouch(interactionType);
- }
- if (!mTouchAboveFalsingThreshold) {
- return true;
- }
- if (mUpwardsWhenThresholdReached) {
- return false;
- }
- return !isDirectionUpwards(x, y);
- }
-
- protected void fling(float vel, boolean expand) {
- fling(vel, expand, 1.0f /* collapseSpeedUpFactor */, false);
- }
-
- protected void fling(float vel, boolean expand, boolean expandBecauseOfFalsing) {
- fling(vel, expand, 1.0f /* collapseSpeedUpFactor */, expandBecauseOfFalsing);
- }
-
- protected void fling(float vel, boolean expand, float collapseSpeedUpFactor,
- boolean expandBecauseOfFalsing) {
- float target = expand ? getMaxPanelHeight() : 0;
- if (!expand) {
- setIsClosing(true);
- }
- flingToHeight(vel, expand, target, collapseSpeedUpFactor, expandBecauseOfFalsing);
- }
-
- protected void flingToHeight(float vel, boolean expand, float target,
- float collapseSpeedUpFactor, boolean expandBecauseOfFalsing) {
- if (target == mExpandedHeight && mOverExpansion == 0.0f) {
- // We're at the target and didn't fling and there's no overshoot
- onFlingEnd(false /* cancelled */);
- return;
- }
- mIsFlinging = true;
- // we want to perform an overshoot animation when flinging open
- final boolean addOverscroll =
- expand
- && !mInSplitShade // Split shade has its own overscroll logic
- && mStatusBarStateController.getState() != StatusBarState.KEYGUARD
- && mOverExpansion == 0.0f
- && vel >= 0;
- final boolean shouldSpringBack = addOverscroll || (mOverExpansion != 0.0f && expand);
- float overshootAmount = 0.0f;
- if (addOverscroll) {
- // Let's overshoot depending on the amount of velocity
- overshootAmount = MathUtils.lerp(
- 0.2f,
- 1.0f,
- MathUtils.saturate(vel
- / (mFlingAnimationUtils.getHighVelocityPxPerSecond()
- * FACTOR_OF_HIGH_VELOCITY_FOR_MAX_OVERSHOOT)));
- overshootAmount += mOverExpansion / mPanelFlingOvershootAmount;
- }
- ValueAnimator animator = createHeightAnimator(target, overshootAmount);
- if (expand) {
- if (expandBecauseOfFalsing && vel < 0) {
- vel = 0;
- }
- mFlingAnimationUtils.apply(animator, mExpandedHeight,
- target + overshootAmount * mPanelFlingOvershootAmount, vel, mView.getHeight());
- if (vel == 0) {
- animator.setDuration(SHADE_OPEN_SPRING_OUT_DURATION);
- }
- } else {
- if (shouldUseDismissingAnimation()) {
- if (vel == 0) {
- animator.setInterpolator(Interpolators.PANEL_CLOSE_ACCELERATED);
- long duration = (long) (200 + mExpandedHeight / mView.getHeight() * 100);
- animator.setDuration(duration);
- } else {
- mFlingAnimationUtilsDismissing.apply(animator, mExpandedHeight, target, vel,
- mView.getHeight());
- }
- } else {
- mFlingAnimationUtilsClosing.apply(
- animator, mExpandedHeight, target, vel, mView.getHeight());
- }
-
- // Make it shorter if we run a canned animation
- if (vel == 0) {
- animator.setDuration((long) (animator.getDuration() / collapseSpeedUpFactor));
- }
- if (mFixedDuration != NO_FIXED_DURATION) {
- animator.setDuration(mFixedDuration);
- }
- }
- animator.addListener(new AnimatorListenerAdapter() {
- private boolean mCancelled;
-
- @Override
- public void onAnimationStart(Animator animation) {
- if (!mStatusBarStateController.isDozing()) {
- beginJankMonitoring();
- }
- }
-
- @Override
- public void onAnimationCancel(Animator animation) {
- mCancelled = true;
- }
-
- @Override
- public void onAnimationEnd(Animator animation) {
- if (shouldSpringBack && !mCancelled) {
- // After the shade is flinged open to an overscrolled state, spring back
- // the shade by reducing section padding to 0.
- springBack();
- } else {
- onFlingEnd(mCancelled);
- }
- }
- });
- setAnimator(animator);
- animator.start();
- }
-
- private void springBack() {
- if (mOverExpansion == 0) {
- onFlingEnd(false /* cancelled */);
- return;
- }
- mIsSpringBackAnimation = true;
- ValueAnimator animator = ValueAnimator.ofFloat(mOverExpansion, 0);
- animator.addUpdateListener(
- animation -> setOverExpansionInternal((float) animation.getAnimatedValue(),
- false /* isFromGesture */));
- animator.setDuration(SHADE_OPEN_SPRING_BACK_DURATION);
- animator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
- animator.addListener(new AnimatorListenerAdapter() {
- private boolean mCancelled;
- @Override
- public void onAnimationCancel(Animator animation) {
- mCancelled = true;
- }
- @Override
- public void onAnimationEnd(Animator animation) {
- mIsSpringBackAnimation = false;
- onFlingEnd(mCancelled);
- }
- });
- setAnimator(animator);
- animator.start();
- }
-
- protected void onFlingEnd(boolean cancelled) {
- mIsFlinging = false;
- // No overshoot when the animation ends
- setOverExpansionInternal(0, false /* isFromGesture */);
- setAnimator(null);
- mKeyguardStateController.notifyPanelFlingEnd();
- if (!cancelled) {
- endJankMonitoring();
- notifyExpandingFinished();
- } else {
- cancelJankMonitoring();
- }
- updatePanelExpansionAndVisibility();
- }
-
- protected abstract boolean shouldUseDismissingAnimation();
-
- public String getName() {
- return mViewName;
- }
-
- public void setExpandedHeight(float height) {
- if (DEBUG) logf("setExpandedHeight(%.1f)", height);
- setExpandedHeightInternal(height);
- }
-
- protected void requestPanelHeightUpdate() {
- float currentMaxPanelHeight = getMaxPanelHeight();
-
- if (isFullyCollapsed()) {
- return;
- }
-
- if (currentMaxPanelHeight == mExpandedHeight) {
- return;
- }
-
- if (mTracking && !isTrackingBlocked()) {
- return;
- }
-
- if (mHeightAnimator != null && !mIsSpringBackAnimation) {
- mPanelUpdateWhenAnimatorEnds = true;
- return;
- }
-
- setExpandedHeight(currentMaxPanelHeight);
- }
-
- public void setExpandedHeightInternal(float h) {
- if (isNaN(h)) {
- Log.wtf(TAG, "ExpandedHeight set to NaN");
- }
- mNotificationShadeWindowController.batchApplyWindowLayoutParams(()-> {
- if (mExpandLatencyTracking && h != 0f) {
- DejankUtils.postAfterTraversal(
- () -> mLatencyTracker.onActionEnd(LatencyTracker.ACTION_EXPAND_PANEL));
- mExpandLatencyTracking = false;
- }
- float maxPanelHeight = getMaxPanelHeight();
- if (mHeightAnimator == null) {
- // Split shade has its own overscroll logic
- if (mTracking && !mInSplitShade) {
- float overExpansionPixels = Math.max(0, h - maxPanelHeight);
- setOverExpansionInternal(overExpansionPixels, true /* isFromGesture */);
- }
- mExpandedHeight = Math.min(h, maxPanelHeight);
- } else {
- mExpandedHeight = h;
- }
-
- // If we are closing the panel and we are almost there due to a slow decelerating
- // interpolator, abort the animation.
- if (mExpandedHeight < 1f && mExpandedHeight != 0f && mClosing) {
- mExpandedHeight = 0f;
- if (mHeightAnimator != null) {
- mHeightAnimator.end();
- }
- }
- mExpansionDragDownAmountPx = h;
- mExpandedFraction = Math.min(1f,
- maxPanelHeight == 0 ? 0 : mExpandedHeight / maxPanelHeight);
- mAmbientState.setExpansionFraction(mExpandedFraction);
- onHeightUpdated(mExpandedHeight);
- updatePanelExpansionAndVisibility();
- });
- }
-
- /**
- * @return true if the panel tracking should be temporarily blocked; this is used when a
- * conflicting gesture (opening QS) is happening
- */
- protected abstract boolean isTrackingBlocked();
-
- protected void setOverExpansion(float overExpansion) {
- mOverExpansion = overExpansion;
- }
-
- /**
- * Set the current overexpansion
- *
- * @param overExpansion the amount of overexpansion to apply
- * @param isFromGesture is this amount from a gesture and needs to be rubberBanded?
- */
- private void setOverExpansionInternal(float overExpansion, boolean isFromGesture) {
- if (!isFromGesture) {
- mLastGesturedOverExpansion = -1;
- setOverExpansion(overExpansion);
- } else if (mLastGesturedOverExpansion != overExpansion) {
- mLastGesturedOverExpansion = overExpansion;
- final float heightForFullOvershoot = mView.getHeight() / 3.0f;
- float newExpansion = MathUtils.saturate(overExpansion / heightForFullOvershoot);
- newExpansion = Interpolators.getOvershootInterpolation(newExpansion);
- setOverExpansion(newExpansion * mPanelFlingOvershootAmount * 2.0f);
- }
- }
-
- protected abstract void onHeightUpdated(float expandedHeight);
-
- /**
- * This returns the maximum height of the panel. Children should override this if their
- * desired height is not the full height.
- *
- * @return the default implementation simply returns the maximum height.
- */
- protected abstract int getMaxPanelHeight();
-
- public void setExpandedFraction(float frac) {
- setExpandedHeight(getMaxPanelHeight() * frac);
- }
-
- public float getExpandedHeight() {
- return mExpandedHeight;
- }
-
- public float getExpandedFraction() {
- return mExpandedFraction;
- }
-
- public boolean isFullyExpanded() {
- return mExpandedHeight >= getMaxPanelHeight();
- }
-
- public boolean isFullyCollapsed() {
- return mExpandedFraction <= 0.0f;
- }
-
- public boolean isCollapsing() {
- return mClosing || mIsLaunchAnimationRunning;
- }
-
- public boolean isFlinging() {
- return mIsFlinging;
- }
-
- public boolean isTracking() {
- return mTracking;
- }
-
- public void collapse(boolean delayed, float speedUpFactor) {
- if (DEBUG) logf("collapse: " + this);
- if (canPanelBeCollapsed()) {
- cancelHeightAnimator();
- notifyExpandingStarted();
-
- // Set after notifyExpandingStarted, as notifyExpandingStarted resets the closing state.
- setIsClosing(true);
- if (delayed) {
- mNextCollapseSpeedUpFactor = speedUpFactor;
- mView.postDelayed(mFlingCollapseRunnable, 120);
- } else {
- fling(0, false /* expand */, speedUpFactor, false /* expandBecauseOfFalsing */);
- }
- }
- }
-
- public boolean canPanelBeCollapsed() {
- return !isFullyCollapsed() && !mTracking && !mClosing;
- }
-
- private final Runnable mFlingCollapseRunnable = () -> fling(0, false /* expand */,
- mNextCollapseSpeedUpFactor, false /* expandBecauseOfFalsing */);
-
- public void expand(final boolean animate) {
- if (!isFullyCollapsed() && !isCollapsing()) {
- return;
- }
-
- mInstantExpanding = true;
- mAnimateAfterExpanding = animate;
- mUpdateFlingOnLayout = false;
- abortAnimations();
- if (mTracking) {
- onTrackingStopped(true /* expands */); // The panel is expanded after this call.
- }
- if (mExpanding) {
- notifyExpandingFinished();
- }
- updatePanelExpansionAndVisibility();
-
- // Wait for window manager to pickup the change, so we know the maximum height of the panel
- // then.
- mView.getViewTreeObserver().addOnGlobalLayoutListener(
- new ViewTreeObserver.OnGlobalLayoutListener() {
- @Override
- public void onGlobalLayout() {
- if (!mInstantExpanding) {
- mView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
- return;
- }
- if (mCentralSurfaces.getNotificationShadeWindowView().isVisibleToUser()) {
- mView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
- if (mAnimateAfterExpanding) {
- notifyExpandingStarted();
- beginJankMonitoring();
- fling(0, true /* expand */);
- } else {
- setExpandedFraction(1f);
- }
- mInstantExpanding = false;
- }
- }
- });
-
- // Make sure a layout really happens.
- mView.requestLayout();
- }
-
- public void instantCollapse() {
- abortAnimations();
- setExpandedFraction(0f);
- if (mExpanding) {
- notifyExpandingFinished();
- }
- if (mInstantExpanding) {
- mInstantExpanding = false;
- updatePanelExpansionAndVisibility();
- }
- }
-
- private void abortAnimations() {
- cancelHeightAnimator();
- mView.removeCallbacks(mFlingCollapseRunnable);
- }
-
- protected abstract void onClosingFinished();
-
- protected void startUnlockHintAnimation() {
-
- // We don't need to hint the user if an animation is already running or the user is changing
- // the expansion.
- if (mHeightAnimator != null || mTracking) {
- return;
- }
- notifyExpandingStarted();
- startUnlockHintAnimationPhase1(() -> {
- notifyExpandingFinished();
- onUnlockHintFinished();
- mHintAnimationRunning = false;
- });
- onUnlockHintStarted();
- mHintAnimationRunning = true;
- }
-
- protected void onUnlockHintFinished() {
- mCentralSurfaces.onHintFinished();
- }
-
- protected void onUnlockHintStarted() {
- mCentralSurfaces.onUnlockHintStarted();
- }
-
- public boolean isUnlockHintRunning() {
- return mHintAnimationRunning;
- }
-
- /**
- * Phase 1: Move everything upwards.
- */
- private void startUnlockHintAnimationPhase1(final Runnable onAnimationFinished) {
- float target = Math.max(0, getMaxPanelHeight() - mHintDistance);
- ValueAnimator animator = createHeightAnimator(target);
- animator.setDuration(250);
- animator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
- animator.addListener(new AnimatorListenerAdapter() {
- private boolean mCancelled;
-
- @Override
- public void onAnimationCancel(Animator animation) {
- mCancelled = true;
- }
-
- @Override
- public void onAnimationEnd(Animator animation) {
- if (mCancelled) {
- setAnimator(null);
- onAnimationFinished.run();
- } else {
- startUnlockHintAnimationPhase2(onAnimationFinished);
- }
- }
- });
- animator.start();
- setAnimator(animator);
-
- final List<ViewPropertyAnimator> indicationAnimators =
- mKeyguardBottomArea.getIndicationAreaAnimators();
- for (final ViewPropertyAnimator indicationAreaAnimator : indicationAnimators) {
- indicationAreaAnimator
- .translationY(-mHintDistance)
- .setDuration(250)
- .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
- .withEndAction(() -> indicationAreaAnimator
- .translationY(0)
- .setDuration(450)
- .setInterpolator(mBounceInterpolator)
- .start())
- .start();
- }
- }
-
- private void setAnimator(ValueAnimator animator) {
- mHeightAnimator = animator;
- if (animator == null && mPanelUpdateWhenAnimatorEnds) {
- mPanelUpdateWhenAnimatorEnds = false;
- requestPanelHeightUpdate();
- }
- }
-
- /**
- * Phase 2: Bounce down.
- */
- private void startUnlockHintAnimationPhase2(final Runnable onAnimationFinished) {
- ValueAnimator animator = createHeightAnimator(getMaxPanelHeight());
- animator.setDuration(450);
- animator.setInterpolator(mBounceInterpolator);
- animator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- setAnimator(null);
- onAnimationFinished.run();
- updatePanelExpansionAndVisibility();
- }
- });
- animator.start();
- setAnimator(animator);
- }
-
- private ValueAnimator createHeightAnimator(float targetHeight) {
- return createHeightAnimator(targetHeight, 0.0f /* performOvershoot */);
- }
-
- /**
- * Create an animator that can also overshoot
- *
- * @param targetHeight the target height
- * @param overshootAmount the amount of overshoot desired
- */
- private ValueAnimator createHeightAnimator(float targetHeight, float overshootAmount) {
- float startExpansion = mOverExpansion;
- ValueAnimator animator = ValueAnimator.ofFloat(mExpandedHeight, targetHeight);
- animator.addUpdateListener(
- animation -> {
- if (overshootAmount > 0.0f
- // Also remove the overExpansion when collapsing
- || (targetHeight == 0.0f && startExpansion != 0)) {
- final float expansion = MathUtils.lerp(
- startExpansion,
- mPanelFlingOvershootAmount * overshootAmount,
- Interpolators.FAST_OUT_SLOW_IN.getInterpolation(
- animator.getAnimatedFraction()));
- setOverExpansionInternal(expansion, false /* isFromGesture */);
- }
- setExpandedHeightInternal((float) animation.getAnimatedValue());
- });
- return animator;
- }
-
- /** Update the visibility of {@link PanelView} if necessary. */
- public void updateVisibility() {
- mView.setVisibility(shouldPanelBeVisible() ? VISIBLE : INVISIBLE);
- }
-
- /** Returns true if {@link PanelView} should be visible. */
- abstract protected boolean shouldPanelBeVisible();
-
- /**
- * Updates the panel expansion and {@link PanelView} visibility if necessary.
- *
- * TODO(b/200063118): Could public calls to this method be replaced with calls to
- * {@link #updateVisibility()}? That would allow us to make this method private.
- */
- public void updatePanelExpansionAndVisibility() {
- mPanelExpansionStateManager.onPanelExpansionChanged(
- mExpandedFraction, isExpanded(), mTracking, mExpansionDragDownAmountPx);
- updateVisibility();
- }
-
- public boolean isExpanded() {
- return mExpandedFraction > 0f
- || mInstantExpanding
- || isPanelVisibleBecauseOfHeadsUp()
- || mTracking
- || mHeightAnimator != null
- && !mIsSpringBackAnimation;
- }
-
- protected abstract boolean isPanelVisibleBecauseOfHeadsUp();
-
- /**
- * Gets called when the user performs a click anywhere in the empty area of the panel.
- *
- * @return whether the panel will be expanded after the action performed by this method
- */
- protected boolean onEmptySpaceClick() {
- if (mHintAnimationRunning) {
- return true;
- }
- return onMiddleClicked();
- }
-
- protected abstract boolean onMiddleClicked();
-
- protected abstract boolean isDozing();
-
- public void dump(PrintWriter pw, String[] args) {
- pw.println(String.format("[PanelView(%s): expandedHeight=%f maxPanelHeight=%d closing=%s"
- + " tracking=%s timeAnim=%s%s "
- + "touchDisabled=%s" + "]",
- this.getClass().getSimpleName(), getExpandedHeight(), getMaxPanelHeight(),
- mClosing ? "T" : "f", mTracking ? "T" : "f", mHeightAnimator,
- ((mHeightAnimator != null && mHeightAnimator.isStarted()) ? " (started)" : ""),
- mTouchDisabled ? "T" : "f"));
- }
-
- public void setHeadsUpManager(HeadsUpManagerPhone headsUpManager) {
- mHeadsUpManager = headsUpManager;
- }
-
- public void setIsLaunchAnimationRunning(boolean running) {
- mIsLaunchAnimationRunning = running;
- }
-
- protected void setIsClosing(boolean isClosing) {
- mClosing = isClosing;
- }
-
- protected boolean isClosing() {
- return mClosing;
- }
-
- public void collapseWithDuration(int animationDuration) {
- mFixedDuration = animationDuration;
- collapse(false /* delayed */, 1.0f /* speedUpFactor */);
- mFixedDuration = NO_FIXED_DURATION;
- }
-
- public ViewGroup getView() {
- // TODO: remove this method, or at least reduce references to it.
- return mView;
- }
-
- public OnLayoutChangeListener createLayoutChangeListener() {
- return new OnLayoutChangeListener();
- }
-
- protected abstract TouchHandler createTouchHandler();
-
- protected OnConfigurationChangedListener createOnConfigurationChangedListener() {
- return new OnConfigurationChangedListener();
- }
-
- public class TouchHandler implements View.OnTouchListener {
-
- public boolean onInterceptTouchEvent(MotionEvent event) {
- if (mInstantExpanding || !mNotificationsDragEnabled || mTouchDisabled || (mMotionAborted
- && event.getActionMasked() != MotionEvent.ACTION_DOWN)) {
- return false;
- }
-
- /*
- * If the user drags anywhere inside the panel we intercept it if the movement is
- * upwards. This allows closing the shade from anywhere inside the panel.
- *
- * We only do this if the current content is scrolled to the bottom,
- * i.e canCollapsePanelOnTouch() is true and therefore there is no conflicting scrolling
- * gesture
- * possible.
- */
- int pointerIndex = event.findPointerIndex(mTrackingPointer);
- if (pointerIndex < 0) {
- pointerIndex = 0;
- mTrackingPointer = event.getPointerId(pointerIndex);
- }
- final float x = event.getX(pointerIndex);
- final float y = event.getY(pointerIndex);
- boolean canCollapsePanel = canCollapsePanelOnTouch();
-
- switch (event.getActionMasked()) {
- case MotionEvent.ACTION_DOWN:
- mCentralSurfaces.userActivity();
- mAnimatingOnDown = mHeightAnimator != null && !mIsSpringBackAnimation;
- mMinExpandHeight = 0.0f;
- mDownTime = mSystemClock.uptimeMillis();
- if (mAnimatingOnDown && mClosing && !mHintAnimationRunning) {
- cancelHeightAnimator();
- mTouchSlopExceeded = true;
- return true;
- }
- mInitialTouchY = y;
- mInitialTouchX = x;
- mTouchStartedInEmptyArea = !isInContentBounds(x, y);
- mTouchSlopExceeded = mTouchSlopExceededBeforeDown;
- mMotionAborted = false;
- mPanelClosedOnDown = isFullyCollapsed();
- mCollapsedAndHeadsUpOnDown = false;
- mHasLayoutedSinceDown = false;
- mUpdateFlingOnLayout = false;
- mTouchAboveFalsingThreshold = false;
- addMovement(event);
- break;
- case MotionEvent.ACTION_POINTER_UP:
- final int upPointer = event.getPointerId(event.getActionIndex());
- if (mTrackingPointer == upPointer) {
- // gesture is ongoing, find a new pointer to track
- final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1;
- mTrackingPointer = event.getPointerId(newIndex);
- mInitialTouchX = event.getX(newIndex);
- mInitialTouchY = event.getY(newIndex);
- }
- break;
- case MotionEvent.ACTION_POINTER_DOWN:
- if (mStatusBarStateController.getState() == StatusBarState.KEYGUARD) {
- mMotionAborted = true;
- mVelocityTracker.clear();
- }
- break;
- case MotionEvent.ACTION_MOVE:
- final float h = y - mInitialTouchY;
- addMovement(event);
- final boolean openShadeWithoutHun =
- mPanelClosedOnDown && !mCollapsedAndHeadsUpOnDown;
- if (canCollapsePanel || mTouchStartedInEmptyArea || mAnimatingOnDown
- || openShadeWithoutHun) {
- float hAbs = Math.abs(h);
- float touchSlop = getTouchSlop(event);
- if ((h < -touchSlop
- || ((openShadeWithoutHun || mAnimatingOnDown) && hAbs > touchSlop))
- && hAbs > Math.abs(x - mInitialTouchX)) {
- cancelHeightAnimator();
- startExpandMotion(x, y, true /* startTracking */, mExpandedHeight);
- return true;
- }
- }
- break;
- case MotionEvent.ACTION_CANCEL:
- case MotionEvent.ACTION_UP:
- mVelocityTracker.clear();
- break;
- }
- return false;
- }
-
- @Override
- public boolean onTouch(View v, MotionEvent event) {
- if (mInstantExpanding) {
- mShadeLog.logMotionEvent(event, "onTouch: touch ignored due to instant expanding");
- return false;
- }
- if (mTouchDisabled && event.getActionMasked() != MotionEvent.ACTION_CANCEL) {
- mShadeLog.logMotionEvent(event, "onTouch: non-cancel action, touch disabled");
- return false;
- }
- if (mMotionAborted && event.getActionMasked() != MotionEvent.ACTION_DOWN) {
- mShadeLog.logMotionEvent(event, "onTouch: non-down action, motion was aborted");
- return false;
- }
-
- // If dragging should not expand the notifications shade, then return false.
- if (!mNotificationsDragEnabled) {
- if (mTracking) {
- // Turn off tracking if it's on or the shade can get stuck in the down position.
- onTrackingStopped(true /* expand */);
- }
- mShadeLog.logMotionEvent(event, "onTouch: drag not enabled");
- return false;
- }
-
- // On expanding, single mouse click expands the panel instead of dragging.
- if (isFullyCollapsed() && event.isFromSource(InputDevice.SOURCE_MOUSE)) {
- if (event.getAction() == MotionEvent.ACTION_UP) {
- expand(true);
- }
- return true;
- }
-
- /*
- * We capture touch events here and update the expand height here in case according to
- * the users fingers. This also handles multi-touch.
- *
- * Flinging is also enabled in order to open or close the shade.
- */
-
- int pointerIndex = event.findPointerIndex(mTrackingPointer);
- if (pointerIndex < 0) {
- pointerIndex = 0;
- mTrackingPointer = event.getPointerId(pointerIndex);
- }
- final float x = event.getX(pointerIndex);
- final float y = event.getY(pointerIndex);
-
- if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
- mGestureWaitForTouchSlop = shouldGestureWaitForTouchSlop();
- mIgnoreXTouchSlop = true;
- }
-
- switch (event.getActionMasked()) {
- case MotionEvent.ACTION_DOWN:
- startExpandMotion(x, y, false /* startTracking */, mExpandedHeight);
- mMinExpandHeight = 0.0f;
- mPanelClosedOnDown = isFullyCollapsed();
- mHasLayoutedSinceDown = false;
- mUpdateFlingOnLayout = false;
- mMotionAborted = false;
- mDownTime = mSystemClock.uptimeMillis();
- mTouchAboveFalsingThreshold = false;
- mCollapsedAndHeadsUpOnDown =
- isFullyCollapsed() && mHeadsUpManager.hasPinnedHeadsUp();
- addMovement(event);
- boolean regularHeightAnimationRunning = mHeightAnimator != null
- && !mHintAnimationRunning && !mIsSpringBackAnimation;
- if (!mGestureWaitForTouchSlop || regularHeightAnimationRunning) {
- mTouchSlopExceeded = regularHeightAnimationRunning
- || mTouchSlopExceededBeforeDown;
- cancelHeightAnimator();
- onTrackingStarted();
- }
- if (isFullyCollapsed() && !mHeadsUpManager.hasPinnedHeadsUp()
- && !mCentralSurfaces.isBouncerShowing()) {
- startOpening(event);
- }
- break;
-
- case MotionEvent.ACTION_POINTER_UP:
- final int upPointer = event.getPointerId(event.getActionIndex());
- if (mTrackingPointer == upPointer) {
- // gesture is ongoing, find a new pointer to track
- final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1;
- final float newY = event.getY(newIndex);
- final float newX = event.getX(newIndex);
- mTrackingPointer = event.getPointerId(newIndex);
- mHandlingPointerUp = true;
- startExpandMotion(newX, newY, true /* startTracking */, mExpandedHeight);
- mHandlingPointerUp = false;
- }
- break;
- case MotionEvent.ACTION_POINTER_DOWN:
- if (mStatusBarStateController.getState() == StatusBarState.KEYGUARD) {
- mMotionAborted = true;
- endMotionEvent(event, x, y, true /* forceCancel */);
- return false;
- }
- break;
- case MotionEvent.ACTION_MOVE:
- addMovement(event);
- float h = y - mInitialTouchY;
-
- // If the panel was collapsed when touching, we only need to check for the
- // y-component of the gesture, as we have no conflicting horizontal gesture.
- if (Math.abs(h) > getTouchSlop(event)
- && (Math.abs(h) > Math.abs(x - mInitialTouchX)
- || mIgnoreXTouchSlop)) {
- mTouchSlopExceeded = true;
- if (mGestureWaitForTouchSlop && !mTracking && !mCollapsedAndHeadsUpOnDown) {
- if (mInitialOffsetOnTouch != 0f) {
- startExpandMotion(x, y, false /* startTracking */, mExpandedHeight);
- h = 0;
- }
- cancelHeightAnimator();
- onTrackingStarted();
- }
- }
- float newHeight = Math.max(0, h + mInitialOffsetOnTouch);
- newHeight = Math.max(newHeight, mMinExpandHeight);
- if (-h >= getFalsingThreshold()) {
- mTouchAboveFalsingThreshold = true;
- mUpwardsWhenThresholdReached = isDirectionUpwards(x, y);
- }
- if ((!mGestureWaitForTouchSlop || mTracking) && !isTrackingBlocked()) {
- // Count h==0 as part of swipe-up,
- // otherwise {@link NotificationStackScrollLayout}
- // wrongly enables stack height updates at the start of lockscreen swipe-up
- mAmbientState.setSwipingUp(h <= 0);
- setExpandedHeightInternal(newHeight);
- }
- break;
-
- case MotionEvent.ACTION_UP:
- case MotionEvent.ACTION_CANCEL:
- addMovement(event);
- endMotionEvent(event, x, y, false /* forceCancel */);
- // mHeightAnimator is null, there is no remaining frame, ends instrumenting.
- if (mHeightAnimator == null) {
- if (event.getActionMasked() == MotionEvent.ACTION_UP) {
- endJankMonitoring();
- } else {
- cancelJankMonitoring();
- }
- }
- break;
- }
- return !mGestureWaitForTouchSlop || mTracking;
- }
- }
-
- public class OnLayoutChangeListener implements View.OnLayoutChangeListener {
- @Override
- public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft,
- int oldTop, int oldRight, int oldBottom) {
- requestPanelHeightUpdate();
- mHasLayoutedSinceDown = true;
- if (mUpdateFlingOnLayout) {
- abortAnimations();
- fling(mUpdateFlingVelocity, true /* expands */);
- mUpdateFlingOnLayout = false;
- }
- }
- }
-
- public class OnConfigurationChangedListener implements
- PanelView.OnConfigurationChangedListener {
- @Override
- public void onConfigurationChanged(Configuration newConfig) {
- loadDimens();
- }
- }
-
- private void beginJankMonitoring() {
- if (mInteractionJankMonitor == null) {
- return;
- }
- InteractionJankMonitor.Configuration.Builder builder =
- InteractionJankMonitor.Configuration.Builder.withView(
- InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE,
- mView)
- .setTag(isFullyCollapsed() ? "Expand" : "Collapse");
- mInteractionJankMonitor.begin(builder);
- }
-
- private void endJankMonitoring() {
- if (mInteractionJankMonitor == null) {
- return;
- }
- InteractionJankMonitor.getInstance().end(
- InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE);
- }
-
- private void cancelJankMonitoring() {
- if (mInteractionJankMonitor == null) {
- return;
- }
- InteractionJankMonitor.getInstance().cancel(
- InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE);
- }
-
- protected float getExpansionFraction() {
- return mExpandedFraction;
- }
-
- protected PanelExpansionStateManager getPanelExpansionStateManager() {
- return mPanelExpansionStateManager;
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/AlphaOptimizedFrameLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/AlphaOptimizedFrameLayout.java
index 359272e8a7e0..662f70ef269e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/AlphaOptimizedFrameLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/AlphaOptimizedFrameLayout.java
@@ -20,12 +20,28 @@ import android.content.Context;
import android.util.AttributeSet;
import android.widget.FrameLayout;
+import com.android.systemui.animation.LaunchableView;
+import com.android.systemui.animation.LaunchableViewDelegate;
+
+import kotlin.Unit;
+
/**
* A frame layout which does not have overlapping renderings commands and therefore does not need a
* layer when alpha is changed.
*/
-public class AlphaOptimizedFrameLayout extends FrameLayout
+public class AlphaOptimizedFrameLayout extends FrameLayout implements LaunchableView
{
+ private final LaunchableViewDelegate mLaunchableViewDelegate = new LaunchableViewDelegate(
+ this,
+ visibility -> {
+ super.setVisibility(visibility);
+ return Unit.INSTANCE;
+ },
+ visibility -> {
+ super.setTransitionVisibility(visibility);
+ return Unit.INSTANCE;
+ });
+
public AlphaOptimizedFrameLayout(Context context) {
super(context);
}
@@ -47,4 +63,19 @@ public class AlphaOptimizedFrameLayout extends FrameLayout
public boolean hasOverlappingRendering() {
return false;
}
+
+ @Override
+ public void setShouldBlockVisibilityChanges(boolean block) {
+ mLaunchableViewDelegate.setShouldBlockVisibilityChanges(block);
+ }
+
+ @Override
+ public void setVisibility(int visibility) {
+ mLaunchableViewDelegate.setVisibility(visibility);
+ }
+
+ @Override
+ public void setTransitionVisibility(int visibility) {
+ mLaunchableViewDelegate.setTransitionVisibility(visibility);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListAttachState.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListAttachState.kt
index 9e5dab1152ec..f8449ae8807b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListAttachState.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListAttachState.kt
@@ -90,6 +90,18 @@ data class ListAttachState private constructor(
stableIndex = -1
}
+ /**
+ * Erases bookkeeping traces stored on an entry when it is removed from the notif list.
+ * This can happen if the entry is removed from a group that was broken up or if the entry was
+ * filtered out during any of the filtering steps.
+ */
+ fun detach() {
+ parent = null
+ section = null
+ promoter = null
+ // stableIndex = -1 // TODO(b/241229236): Clear this once we fix the stability fragility
+ }
+
companion object {
@JvmStatic
fun create(): ListAttachState {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
index 14cc6bf1ea41..3eaa988e8389 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
@@ -958,9 +958,7 @@ public class ShadeListBuilder implements Dumpable, PipelineDumpable {
* filtered out during any of the filtering steps.
*/
private void annulAddition(ListEntry entry) {
- entry.setParent(null);
- entry.getAttachState().setSection(null);
- entry.getAttachState().setPromoter(null);
+ entry.getAttachState().detach();
}
private void assignSections() {
@@ -1198,9 +1196,9 @@ public class ShadeListBuilder implements Dumpable, PipelineDumpable {
o2.getSectionIndex());
if (cmp != 0) return cmp;
- int index1 = canReorder(o1) ? -1 : o1.getPreviousAttachState().getStableIndex();
- int index2 = canReorder(o2) ? -1 : o2.getPreviousAttachState().getStableIndex();
- cmp = Integer.compare(index1, index2);
+ cmp = Integer.compare(
+ getStableOrderIndex(o1),
+ getStableOrderIndex(o2));
if (cmp != 0) return cmp;
NotifComparator sectionComparator = getSectionComparator(o1, o2);
@@ -1214,31 +1212,32 @@ public class ShadeListBuilder implements Dumpable, PipelineDumpable {
if (cmp != 0) return cmp;
}
- final NotificationEntry rep1 = o1.getRepresentativeEntry();
- final NotificationEntry rep2 = o2.getRepresentativeEntry();
- cmp = rep1.getRanking().getRank() - rep2.getRanking().getRank();
+ cmp = Integer.compare(
+ o1.getRepresentativeEntry().getRanking().getRank(),
+ o2.getRepresentativeEntry().getRanking().getRank());
if (cmp != 0) return cmp;
- cmp = Long.compare(
- rep2.getSbn().getNotification().when,
- rep1.getSbn().getNotification().when);
+ cmp = -1 * Long.compare(
+ o1.getRepresentativeEntry().getSbn().getNotification().when,
+ o2.getRepresentativeEntry().getSbn().getNotification().when);
return cmp;
};
private final Comparator<NotificationEntry> mGroupChildrenComparator = (o1, o2) -> {
- int index1 = canReorder(o1) ? -1 : o1.getPreviousAttachState().getStableIndex();
- int index2 = canReorder(o2) ? -1 : o2.getPreviousAttachState().getStableIndex();
- int cmp = Integer.compare(index1, index2);
+ int cmp = Integer.compare(
+ getStableOrderIndex(o1),
+ getStableOrderIndex(o2));
if (cmp != 0) return cmp;
- cmp = o1.getRepresentativeEntry().getRanking().getRank()
- - o2.getRepresentativeEntry().getRanking().getRank();
+ cmp = Integer.compare(
+ o1.getRepresentativeEntry().getRanking().getRank(),
+ o2.getRepresentativeEntry().getRanking().getRank());
if (cmp != 0) return cmp;
- cmp = Long.compare(
- o2.getRepresentativeEntry().getSbn().getNotification().when,
- o1.getRepresentativeEntry().getSbn().getNotification().when);
+ cmp = -1 * Long.compare(
+ o1.getRepresentativeEntry().getSbn().getNotification().when,
+ o2.getRepresentativeEntry().getSbn().getNotification().when);
return cmp;
};
@@ -1248,8 +1247,16 @@ public class ShadeListBuilder implements Dumpable, PipelineDumpable {
*/
private boolean mForceReorderable = false;
- private boolean canReorder(ListEntry entry) {
- return mForceReorderable || getStabilityManager().isEntryReorderingAllowed(entry);
+ private int getStableOrderIndex(ListEntry entry) {
+ if (mForceReorderable) {
+ // this is used to determine if the list is correctly sorted
+ return -1;
+ }
+ if (getStabilityManager().isEntryReorderingAllowed(entry)) {
+ // let the stability manager constrain or allow reordering
+ return -1;
+ }
+ return entry.getPreviousAttachState().getStableIndex();
}
private boolean applyFilters(NotificationEntry entry, long now, List<NotifFilter> filters) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitch.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitch.java
index 5168533cd2b7..365fbace1608 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitch.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitch.java
@@ -29,6 +29,7 @@ import com.android.systemui.R;
/**
* Container for image of the multi user switcher (tappable).
*/
+// TODO(b/242040009): Remove this file.
public class MultiUserSwitch extends FrameLayout {
public MultiUserSwitch(Context context, AttributeSet attrs) {
super(context, attrs);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitchController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitchController.java
index 799e5feb1586..4d6168989691 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitchController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitchController.java
@@ -40,6 +40,7 @@ import com.android.systemui.util.ViewController;
import javax.inject.Inject;
/** View Controller for {@link MultiUserSwitch}. */
+// TODO(b/242040009): Remove this file.
public class MultiUserSwitchController extends ViewController<MultiUserSwitch> {
private final UserManager mUserManager;
private final UserSwitcherController mUserSwitcherController;
diff --git a/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt b/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt
index 74d51112deeb..80c55c0bad07 100644
--- a/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt
@@ -35,6 +35,7 @@ import android.widget.AdapterView
import android.widget.ArrayAdapter
import android.widget.ImageView
import android.widget.TextView
+import androidx.activity.ComponentActivity
import androidx.constraintlayout.helper.widget.Flow
import com.android.internal.annotations.VisibleForTesting
import com.android.internal.util.UserIcons
@@ -51,7 +52,6 @@ import com.android.systemui.statusbar.policy.UserSwitcherController.BaseUserAdap
import com.android.systemui.statusbar.policy.UserSwitcherController.USER_SWITCH_DISABLED_ALPHA
import com.android.systemui.statusbar.policy.UserSwitcherController.USER_SWITCH_ENABLED_ALPHA
import com.android.systemui.statusbar.policy.UserSwitcherController.UserRecord
-import com.android.systemui.util.LifecycleActivity
import javax.inject.Inject
import kotlin.math.ceil
@@ -68,7 +68,7 @@ class UserSwitcherActivity @Inject constructor(
private val falsingManager: FalsingManager,
private val userManager: UserManager,
private val userTracker: UserTracker
-) : LifecycleActivity() {
+) : ComponentActivity() {
private lateinit var parent: UserSwitcherRootView
private lateinit var broadcastReceiver: BroadcastReceiver
diff --git a/packages/SystemUI/src/com/android/systemui/util/LifecycleActivity.kt b/packages/SystemUI/src/com/android/systemui/util/LifecycleActivity.kt
deleted file mode 100644
index e4b7a20aab37..000000000000
--- a/packages/SystemUI/src/com/android/systemui/util/LifecycleActivity.kt
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.util
-
-import android.app.Activity
-import android.os.Bundle
-import android.os.PersistableBundle
-import androidx.lifecycle.LifecycleOwner
-import com.android.settingslib.core.lifecycle.Lifecycle
-
-open class LifecycleActivity : Activity(), LifecycleOwner {
-
- private val lifecycle = Lifecycle(this)
-
- override fun getLifecycle() = lifecycle
-
- override fun onCreate(savedInstanceState: Bundle?) {
- lifecycle.onAttach(this)
- lifecycle.onCreate(savedInstanceState)
- lifecycle.handleLifecycleEvent(androidx.lifecycle.Lifecycle.Event.ON_CREATE)
- super.onCreate(savedInstanceState)
- }
-
- override fun onCreate(
- savedInstanceState: Bundle?,
- persistentState: PersistableBundle?
- ) {
- lifecycle.onAttach(this)
- lifecycle.onCreate(savedInstanceState)
- lifecycle.handleLifecycleEvent(androidx.lifecycle.Lifecycle.Event.ON_CREATE)
- super.onCreate(savedInstanceState, persistentState)
- }
-
- override fun onStart() {
- lifecycle.handleLifecycleEvent(androidx.lifecycle.Lifecycle.Event.ON_START)
- super.onStart()
- }
-
- override fun onResume() {
- lifecycle.handleLifecycleEvent(androidx.lifecycle.Lifecycle.Event.ON_RESUME)
- super.onResume()
- }
-
- override fun onPause() {
- lifecycle.handleLifecycleEvent(androidx.lifecycle.Lifecycle.Event.ON_PAUSE)
- super.onPause()
- }
-
- override fun onStop() {
- lifecycle.handleLifecycleEvent(androidx.lifecycle.Lifecycle.Event.ON_STOP)
- super.onStop()
- }
-
- override fun onDestroy() {
- lifecycle.handleLifecycleEvent(androidx.lifecycle.Lifecycle.Event.ON_DESTROY)
- super.onDestroy()
- }
-} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletActivity.java b/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletActivity.java
index e3cd98996a41..d03148cee335 100644
--- a/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletActivity.java
@@ -32,7 +32,9 @@ import android.view.Window;
import android.view.WindowManager;
import android.widget.Toolbar;
+import androidx.activity.ComponentActivity;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import com.android.internal.logging.UiEventLogger;
import com.android.keyguard.KeyguardUpdateMonitor;
@@ -48,7 +50,6 @@ import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.phone.KeyguardDismissUtil;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
import com.android.systemui.statusbar.policy.KeyguardStateController;
-import com.android.systemui.util.LifecycleActivity;
import java.util.concurrent.Executor;
@@ -57,7 +58,7 @@ import javax.inject.Inject;
/**
* Displays Wallet carousel screen inside an activity.
*/
-public class WalletActivity extends LifecycleActivity implements
+public class WalletActivity extends ComponentActivity implements
QuickAccessWalletClient.WalletServiceEventListener {
private static final String TAG = "WalletActivity";
@@ -105,7 +106,7 @@ public class WalletActivity extends LifecycleActivity implements
}
@Override
- protected void onCreate(Bundle savedInstanceState) {
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt
index 0f112415df0d..e2790e47fe06 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt
@@ -10,12 +10,10 @@ import android.graphics.Rect
import android.os.Looper
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
-import android.util.Log
import android.view.IRemoteAnimationFinishedCallback
import android.view.RemoteAnimationAdapter
import android.view.RemoteAnimationTarget
import android.view.SurfaceControl
-import android.view.View
import android.view.ViewGroup
import android.widget.LinearLayout
import androidx.test.filters.SmallTest
@@ -51,7 +49,6 @@ class ActivityLaunchAnimatorTest : SysuiTestCase() {
@Mock lateinit var listener: ActivityLaunchAnimator.Listener
@Spy private val controller = TestLaunchAnimatorController(launchContainer)
@Mock lateinit var iCallback: IRemoteAnimationFinishedCallback
- @Mock lateinit var failHandler: Log.TerribleFailureHandler
private lateinit var activityLaunchAnimator: ActivityLaunchAnimator
@get:Rule val rule = MockitoJUnit.rule()
@@ -187,13 +184,6 @@ class ActivityLaunchAnimatorTest : SysuiTestCase() {
verify(controller).onLaunchAnimationStart(anyBoolean())
}
- @Test
- fun controllerFromOrphanViewReturnsNullAndIsATerribleFailure() {
- Log.setWtfHandler(failHandler)
- assertNull(ActivityLaunchAnimator.Controller.fromView(View(mContext)))
- verify(failHandler).onTerribleFailure(any(), any(), anyBoolean())
- }
-
private fun fakeWindow(): RemoteAnimationTarget {
val bounds = Rect(10 /* left */, 20 /* top */, 30 /* right */, 40 /* bottom */)
val taskInfo = ActivityManager.RunningTaskInfo()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceViewTest.kt
index 328ad39cddd5..58d906907488 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceViewTest.kt
@@ -41,11 +41,13 @@ import org.mockito.junit.MockitoJUnit
@SmallTest
class AuthBiometricFingerprintAndFaceViewTest : SysuiTestCase() {
- @JvmField @Rule
+ @JvmField
+ @Rule
var mockitoRule = MockitoJUnit.rule()
@Mock
private lateinit var callback: AuthBiometricView.Callback
+
@Mock
private lateinit var panelController: AuthPanelController
@@ -67,6 +69,7 @@ class AuthBiometricFingerprintAndFaceViewTest : SysuiTestCase() {
fun fingerprintSuccessDoesNotRequireExplicitConfirmation() {
biometricView.onDialogAnimatedIn()
biometricView.onAuthenticationSucceeded(TYPE_FINGERPRINT)
+ TestableLooper.get(this).moveTimeForward(1000)
waitForIdleSync()
assertThat(biometricView.isAuthenticated).isTrue()
@@ -86,6 +89,7 @@ class AuthBiometricFingerprintAndFaceViewTest : SysuiTestCase() {
// icon acts as confirm button
biometricView.mIconView.performClick()
+ TestableLooper.get(this).moveTimeForward(1000)
waitForIdleSync()
assertThat(biometricView.isAuthenticated).isTrue()
@@ -102,6 +106,7 @@ class AuthBiometricFingerprintAndFaceViewTest : SysuiTestCase() {
verify(callback, never()).onAction(AuthBiometricView.Callback.ACTION_ERROR)
biometricView.onError(TYPE_FINGERPRINT, "that's a nope")
+ TestableLooper.get(this).moveTimeForward(1000)
waitForIdleSync()
verify(callback).onAction(AuthBiometricView.Callback.ACTION_ERROR)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFingerprintViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFingerprintViewTest.kt
index 687cb517b2f4..bce98cf116d4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFingerprintViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFingerprintViewTest.kt
@@ -42,20 +42,23 @@ import org.mockito.junit.MockitoJUnit
@SmallTest
class AuthBiometricFingerprintViewTest : SysuiTestCase() {
- @JvmField @Rule
+ @JvmField
+ @Rule
val mockitoRule = MockitoJUnit.rule()
@Mock
private lateinit var callback: AuthBiometricView.Callback
+
@Mock
private lateinit var panelController: AuthPanelController
private lateinit var biometricView: AuthBiometricView
private fun createView(allowDeviceCredential: Boolean = false): AuthBiometricFingerprintView {
- val view = R.layout.auth_biometric_fingerprint_view.asTestAuthBiometricView(
+ val view: AuthBiometricFingerprintView =
+ R.layout.auth_biometric_fingerprint_view.asTestAuthBiometricView(
mContext, callback, panelController, allowDeviceCredential = allowDeviceCredential
- ) as AuthBiometricFingerprintView
+ )
waitForIdleSync()
return view
}
@@ -73,6 +76,7 @@ class AuthBiometricFingerprintViewTest : SysuiTestCase() {
@Test
fun testOnAuthenticationSucceeded_noConfirmationRequired_sendsActionAuthenticated() {
biometricView.onAuthenticationSucceeded(BiometricAuthenticator.TYPE_FINGERPRINT)
+ TestableLooper.get(this).moveTimeForward(1000)
waitForIdleSync()
assertThat(biometricView.isAuthenticated).isTrue()
@@ -83,6 +87,7 @@ class AuthBiometricFingerprintViewTest : SysuiTestCase() {
fun testOnAuthenticationSucceeded_confirmationRequired_updatesDialogContents() {
biometricView.setRequireConfirmation(true)
biometricView.onAuthenticationSucceeded(BiometricAuthenticator.TYPE_FINGERPRINT)
+ TestableLooper.get(this).moveTimeForward(1000)
waitForIdleSync()
// TODO: this should be tested in the subclasses
@@ -104,6 +109,7 @@ class AuthBiometricFingerprintViewTest : SysuiTestCase() {
@Test
fun testPositiveButton_sendsActionAuthenticated() {
biometricView.mConfirmButton.performClick()
+ TestableLooper.get(this).moveTimeForward(1000)
waitForIdleSync()
verify(callback).onAction(AuthBiometricView.Callback.ACTION_AUTHENTICATED)
@@ -114,6 +120,7 @@ class AuthBiometricFingerprintViewTest : SysuiTestCase() {
fun testNegativeButton_beforeAuthentication_sendsActionButtonNegative() {
biometricView.onDialogAnimatedIn()
biometricView.mNegativeButton.performClick()
+ TestableLooper.get(this).moveTimeForward(1000)
waitForIdleSync()
verify(callback).onAction(AuthBiometricView.Callback.ACTION_BUTTON_NEGATIVE)
@@ -126,6 +133,7 @@ class AuthBiometricFingerprintViewTest : SysuiTestCase() {
assertThat(biometricView.mNegativeButton.visibility).isEqualTo(View.GONE)
biometricView.mCancelButton.performClick()
+ TestableLooper.get(this).moveTimeForward(1000)
waitForIdleSync()
verify(callback).onAction(AuthBiometricView.Callback.ACTION_USER_CANCELED)
@@ -134,6 +142,7 @@ class AuthBiometricFingerprintViewTest : SysuiTestCase() {
@Test
fun testTryAgainButton_sendsActionTryAgain() {
biometricView.mTryAgainButton.performClick()
+ TestableLooper.get(this).moveTimeForward(1000)
waitForIdleSync()
verify(callback).onAction(AuthBiometricView.Callback.ACTION_BUTTON_TRY_AGAIN)
@@ -144,6 +153,7 @@ class AuthBiometricFingerprintViewTest : SysuiTestCase() {
@Test
fun testOnErrorSendsActionError() {
biometricView.onError(BiometricAuthenticator.TYPE_FACE, "testError")
+ TestableLooper.get(this).moveTimeForward(1000)
waitForIdleSync()
verify(callback).onAction(eq(AuthBiometricView.Callback.ACTION_ERROR))
@@ -156,6 +166,7 @@ class AuthBiometricFingerprintViewTest : SysuiTestCase() {
val message = "another error"
biometricView.onError(BiometricAuthenticator.TYPE_FACE, message)
+ TestableLooper.get(this).moveTimeForward(1000)
waitForIdleSync()
assertThat(biometricView.isAuthenticating).isFalse()
@@ -178,6 +189,7 @@ class AuthBiometricFingerprintViewTest : SysuiTestCase() {
val view = View(mContext)
biometricView.setBackgroundView(view)
biometricView.onAuthenticationSucceeded(BiometricAuthenticator.TYPE_FINGERPRINT)
+ waitForIdleSync()
view.performClick()
verify(callback, never())
@@ -225,14 +237,14 @@ class AuthBiometricFingerprintViewTest : SysuiTestCase() {
biometricView.onSaveState(state)
assertThat(biometricView.mTryAgainButton.visibility).isEqualTo(View.GONE)
assertThat(state.getInt(AuthDialog.KEY_BIOMETRIC_TRY_AGAIN_VISIBILITY))
- .isEqualTo(View.GONE)
+ .isEqualTo(View.GONE)
assertThat(state.getInt(AuthDialog.KEY_BIOMETRIC_STATE))
- .isEqualTo(AuthBiometricView.STATE_ERROR)
+ .isEqualTo(AuthBiometricView.STATE_ERROR)
assertThat(biometricView.mIndicatorView.visibility).isEqualTo(View.VISIBLE)
assertThat(state.getBoolean(AuthDialog.KEY_BIOMETRIC_INDICATOR_ERROR_SHOWING)).isTrue()
assertThat(biometricView.mIndicatorView.text).isEqualTo(failureMessage)
assertThat(state.getString(AuthDialog.KEY_BIOMETRIC_INDICATOR_STRING))
- .isEqualTo(failureMessage)
+ .isEqualTo(failureMessage)
// TODO: Test dialog size. Should move requireConfirmation to buildBiometricPromptBundle
diff --git a/packages/SystemUI/tests/src/com/android/systemui/broadcast/BroadcastDispatcherTest.kt b/packages/SystemUI/tests/src/com/android/systemui/broadcast/BroadcastDispatcherTest.kt
index 7795d2caf091..434cb48bc422 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/broadcast/BroadcastDispatcherTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/broadcast/BroadcastDispatcherTest.kt
@@ -18,6 +18,7 @@ package com.android.systemui.broadcast
import android.content.BroadcastReceiver
import android.content.Context
+import android.content.Intent
import android.content.IntentFilter
import android.os.Handler
import android.os.Looper
@@ -32,22 +33,27 @@ import com.android.systemui.dump.DumpManager
import com.android.systemui.settings.UserTracker
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.mock
import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
+import java.util.concurrent.Executor
import junit.framework.Assert.assertSame
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.runBlockingTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
import org.mockito.Captor
import org.mockito.Mock
-import org.mockito.Mockito.`when`
import org.mockito.Mockito.anyInt
import org.mockito.Mockito.inOrder
import org.mockito.Mockito.mock
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
-import java.util.concurrent.Executor
@RunWith(AndroidTestingRunner::class)
@TestableLooper.RunWithLooper
@@ -381,6 +387,39 @@ class BroadcastDispatcherTest : SysuiTestCase() {
.clearPendingRemoval(broadcastReceiver, user1.identifier)
}
+ @Test
+ fun testBroadcastFlow() = runBlockingTest {
+ val flow = broadcastDispatcher.broadcastFlow(intentFilter, user1) { intent, receiver ->
+ intent to receiver
+ }
+
+ // Collect the values into collectedValues.
+ val collectedValues = mutableListOf<Pair<Intent, BroadcastReceiver>>()
+ val job = launch {
+ flow.collect { collectedValues.add(it) }
+ }
+
+ testableLooper.processAllMessages()
+ verify(mockUBRUser1).registerReceiver(capture(argumentCaptor), eq(DEFAULT_FLAG))
+ val receiver = argumentCaptor.value.receiver
+
+ // Simulate fake broadcasted intents.
+ val fakeIntents = listOf<Intent>(mock(), mock(), mock())
+ fakeIntents.forEach { receiver.onReceive(mockContext, it) }
+
+ // The intents should have been collected.
+ advanceUntilIdle()
+
+ val expectedValues = fakeIntents.map { it to receiver }
+ assertThat(collectedValues).containsExactlyElementsIn(expectedValues)
+
+ // Stop the collection.
+ job.cancel()
+
+ testableLooper.processAllMessages()
+ verify(mockUBRUser1).unregisterReceiver(receiver)
+ }
+
private fun setUserMock(mockContext: Context, user: UserHandle) {
`when`(mockContext.user).thenReturn(user)
`when`(mockContext.userId).thenReturn(user.identifier)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/FgsManagerControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/FgsManagerControllerTest.java
index 2927669020c8..7bae115d2edd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/FgsManagerControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/FgsManagerControllerTest.java
@@ -16,6 +16,8 @@
package com.android.systemui.qs;
+import static android.os.PowerExemptionManager.REASON_ALLOWLISTED_PACKAGE;
+
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.eq;
@@ -34,6 +36,7 @@ import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
import android.os.Binder;
import android.os.RemoteException;
+import android.os.UserHandle;
import android.provider.DeviceConfig;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
@@ -185,9 +188,9 @@ public class FgsManagerControllerTest extends SysuiTestCase {
public void testChangesSinceLastDialog() throws RemoteException {
setUserProfiles(0);
- Assert.assertFalse(mFmc.getChangesSinceDialog());
+ Assert.assertFalse(mFmc.getNewChangesSinceDialogWasDismissed());
mIForegroundServiceObserver.onForegroundStateChanged(new Binder(), "pkg", 0, true);
- Assert.assertTrue(mFmc.getChangesSinceDialog());
+ Assert.assertTrue(mFmc.getNewChangesSinceDialogWasDismissed());
}
@Test
@@ -222,7 +225,41 @@ public class FgsManagerControllerTest extends SysuiTestCase {
Assert.assertEquals(2, mFmc.getNumRunningPackages());
}
+ @Test
+ public void testButtonVisibilityOnShowAllowlistButtonFlagChange() throws Exception {
+ setUserProfiles(0);
+ setBackgroundRestrictionExemptionReason("pkg", 12345, REASON_ALLOWLISTED_PACKAGE);
+
+ final Binder binder = new Binder();
+ setShowStopButtonForUserAllowlistedApps(true);
+ mIForegroundServiceObserver.onForegroundStateChanged(binder, "pkg", 0, true);
+ Assert.assertEquals(1, mFmc.visibleButtonsCount());
+
+ mIForegroundServiceObserver.onForegroundStateChanged(binder, "pkg", 0, false);
+ Assert.assertEquals(0, mFmc.visibleButtonsCount());
+
+ setShowStopButtonForUserAllowlistedApps(false);
+ mIForegroundServiceObserver.onForegroundStateChanged(binder, "pkg", 0, true);
+ Assert.assertEquals(0, mFmc.visibleButtonsCount());
+ }
+ private void setShowStopButtonForUserAllowlistedApps(boolean enable) {
+ mDeviceConfigProxyFake.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
+ SystemUiDeviceConfigFlags.TASK_MANAGER_SHOW_STOP_BUTTON_FOR_USER_ALLOWLISTED_APPS,
+ enable ? "true" : "false", false);
+ mBackgroundExecutor.advanceClockToLast();
+ mBackgroundExecutor.runAllReady();
+ }
+
+ private void setBackgroundRestrictionExemptionReason(String pkgName, int uid, int reason)
+ throws Exception {
+ Mockito.doReturn(uid)
+ .when(mPackageManager)
+ .getPackageUidAsUser(pkgName, UserHandle.getUserId(uid));
+ Mockito.doReturn(reason)
+ .when(mIActivityManager)
+ .getBackgroundRestrictionExemptionReason(uid);
+ }
FgsManagerController createFgsManagerController() throws RemoteException {
ArgumentCaptor<IForegroundServiceObserver> iForegroundServiceObserverArgumentCaptor =
@@ -232,7 +269,7 @@ public class FgsManagerControllerTest extends SysuiTestCase {
ArgumentCaptor<BroadcastReceiver> showFgsManagerReceiverArgumentCaptor =
ArgumentCaptor.forClass(BroadcastReceiver.class);
- FgsManagerController result = new FgsManagerController(
+ FgsManagerController result = new FgsManagerControllerImpl(
mContext,
mMainExecutor,
mBackgroundExecutor,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
index 10f6ce8c0ec9..f08ad2453c5f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
@@ -45,12 +45,15 @@ import com.android.systemui.R;
import com.android.systemui.SysuiBaseFragmentTest;
import com.android.systemui.animation.ShadeInterpolation;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FakeFeatureFlags;
+import com.android.systemui.flags.Flags;
import com.android.systemui.media.MediaHost;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.qs.customize.QSCustomizerController;
import com.android.systemui.qs.dagger.QSFragmentComponent;
import com.android.systemui.qs.external.TileServiceRequestController;
+import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
@@ -390,6 +393,8 @@ public class QSFragmentTest extends SysuiBaseFragmentTest {
setUpMedia();
setUpOther();
+ FakeFeatureFlags featureFlags = new FakeFeatureFlags();
+ featureFlags.set(Flags.NEW_FOOTER_ACTIONS, false);
return new QSFragment(
new RemoteInputQuickSettingsDisabler(
context, commandQueue, mock(ConfigurationController.class)),
@@ -402,7 +407,10 @@ public class QSFragmentTest extends SysuiBaseFragmentTest {
mQsComponentFactory,
mock(QSFragmentDisableFlagsLogger.class),
mFalsingManager,
- mock(DumpManager.class));
+ mock(DumpManager.class),
+ featureFlags,
+ mock(NewFooterActionsController.class),
+ mock(FooterActionsViewModel.Factory.class));
}
private void setUpOther() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java
index c1c0f78ecd6b..233c267c3be0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java
@@ -100,6 +100,7 @@ public class QSSecurityFooterTest extends SysuiTestCase {
private TextView mFooterText;
private TestableImageView mPrimaryFooterIcon;
private QSSecurityFooter mFooter;
+ private QSSecurityFooterUtils mFooterUtils;
@Mock
private SecurityController mSecurityController;
@Mock
@@ -118,13 +119,16 @@ public class QSSecurityFooterTest extends SysuiTestCase {
MockitoAnnotations.initMocks(this);
mTestableLooper = TestableLooper.get(this);
Looper looper = mTestableLooper.getLooper();
+ Handler mainHandler = new Handler(looper);
when(mUserTracker.getUserInfo()).thenReturn(mock(UserInfo.class));
mRootView = (ViewGroup) new LayoutInflaterBuilder(mContext)
.replace("ImageView", TestableImageView.class)
.build().inflate(R.layout.quick_settings_security_footer, null, false);
- mFooter = new QSSecurityFooter(mRootView, mUserTracker, new Handler(looper),
- mActivityStarter, mSecurityController, mDialogLaunchAnimator, looper,
- mBroadcastDispatcher);
+ mFooterUtils = new QSSecurityFooterUtils(getContext(),
+ getContext().getSystemService(DevicePolicyManager.class), mUserTracker,
+ mainHandler, mActivityStarter, mSecurityController, looper, mDialogLaunchAnimator);
+ mFooter = new QSSecurityFooter(mRootView, mainHandler, mSecurityController, looper,
+ mBroadcastDispatcher, mFooterUtils);
mFooterText = mRootView.findViewById(R.id.footer_text);
mPrimaryFooterIcon = mRootView.findViewById(R.id.primary_footer_icon);
@@ -520,7 +524,7 @@ public class QSSecurityFooterTest extends SysuiTestCase {
when(mSecurityController.isDeviceManaged()).thenReturn(true);
assertEquals(mContext.getString(R.string.monitoring_title_device_owned),
- mFooter.getManagementTitle(MANAGING_ORGANIZATION));
+ mFooterUtils.getManagementTitle(MANAGING_ORGANIZATION));
}
@Test
@@ -531,12 +535,12 @@ public class QSSecurityFooterTest extends SysuiTestCase {
assertEquals(mContext.getString(R.string.monitoring_title_financed_device,
MANAGING_ORGANIZATION),
- mFooter.getManagementTitle(MANAGING_ORGANIZATION));
+ mFooterUtils.getManagementTitle(MANAGING_ORGANIZATION));
}
@Test
public void testGetManagementMessage_noManagement() {
- assertEquals(null, mFooter.getManagementMessage(
+ assertEquals(null, mFooterUtils.getManagementMessage(
/* isDeviceManaged= */ false, MANAGING_ORGANIZATION));
}
@@ -544,10 +548,10 @@ public class QSSecurityFooterTest extends SysuiTestCase {
public void testGetManagementMessage_deviceOwner() {
assertEquals(mContext.getString(R.string.monitoring_description_named_management,
MANAGING_ORGANIZATION),
- mFooter.getManagementMessage(
+ mFooterUtils.getManagementMessage(
/* isDeviceManaged= */ true, MANAGING_ORGANIZATION));
assertEquals(mContext.getString(R.string.monitoring_description_management),
- mFooter.getManagementMessage(
+ mFooterUtils.getManagementMessage(
/* isDeviceManaged= */ true,
/* organizationName= */ null));
}
@@ -560,68 +564,68 @@ public class QSSecurityFooterTest extends SysuiTestCase {
assertEquals(mContext.getString(R.string.monitoring_financed_description_named_management,
MANAGING_ORGANIZATION, MANAGING_ORGANIZATION),
- mFooter.getManagementMessage(
+ mFooterUtils.getManagementMessage(
/* isDeviceManaged= */ true, MANAGING_ORGANIZATION));
}
@Test
public void testGetCaCertsMessage() {
- assertEquals(null, mFooter.getCaCertsMessage(true, false, false));
- assertEquals(null, mFooter.getCaCertsMessage(false, false, false));
+ assertEquals(null, mFooterUtils.getCaCertsMessage(true, false, false));
+ assertEquals(null, mFooterUtils.getCaCertsMessage(false, false, false));
assertEquals(mContext.getString(R.string.monitoring_description_management_ca_certificate),
- mFooter.getCaCertsMessage(true, true, true));
+ mFooterUtils.getCaCertsMessage(true, true, true));
assertEquals(mContext.getString(R.string.monitoring_description_management_ca_certificate),
- mFooter.getCaCertsMessage(true, false, true));
+ mFooterUtils.getCaCertsMessage(true, false, true));
assertEquals(mContext.getString(
R.string.monitoring_description_managed_profile_ca_certificate),
- mFooter.getCaCertsMessage(false, false, true));
+ mFooterUtils.getCaCertsMessage(false, false, true));
assertEquals(mContext.getString(
R.string.monitoring_description_ca_certificate),
- mFooter.getCaCertsMessage(false, true, false));
+ mFooterUtils.getCaCertsMessage(false, true, false));
}
@Test
public void testGetNetworkLoggingMessage() {
// Test network logging message on a device with a device owner.
// Network traffic may be monitored on the device.
- assertEquals(null, mFooter.getNetworkLoggingMessage(true, false));
+ assertEquals(null, mFooterUtils.getNetworkLoggingMessage(true, false));
assertEquals(mContext.getString(R.string.monitoring_description_management_network_logging),
- mFooter.getNetworkLoggingMessage(true, true));
+ mFooterUtils.getNetworkLoggingMessage(true, true));
// Test network logging message on a device with a managed profile owner
// Network traffic may be monitored on the work profile.
- assertEquals(null, mFooter.getNetworkLoggingMessage(false, false));
+ assertEquals(null, mFooterUtils.getNetworkLoggingMessage(false, false));
assertEquals(
mContext.getString(R.string.monitoring_description_managed_profile_network_logging),
- mFooter.getNetworkLoggingMessage(false, true));
+ mFooterUtils.getNetworkLoggingMessage(false, true));
}
@Test
public void testGetVpnMessage() {
- assertEquals(null, mFooter.getVpnMessage(true, true, null, null));
+ assertEquals(null, mFooterUtils.getVpnMessage(true, true, null, null));
assertEquals(addLink(mContext.getString(R.string.monitoring_description_two_named_vpns,
VPN_PACKAGE, VPN_PACKAGE_2)),
- mFooter.getVpnMessage(true, true, VPN_PACKAGE, VPN_PACKAGE_2));
+ mFooterUtils.getVpnMessage(true, true, VPN_PACKAGE, VPN_PACKAGE_2));
assertEquals(addLink(mContext.getString(R.string.monitoring_description_two_named_vpns,
VPN_PACKAGE, VPN_PACKAGE_2)),
- mFooter.getVpnMessage(false, true, VPN_PACKAGE, VPN_PACKAGE_2));
+ mFooterUtils.getVpnMessage(false, true, VPN_PACKAGE, VPN_PACKAGE_2));
assertEquals(addLink(mContext.getString(R.string.monitoring_description_named_vpn,
VPN_PACKAGE)),
- mFooter.getVpnMessage(true, false, VPN_PACKAGE, null));
+ mFooterUtils.getVpnMessage(true, false, VPN_PACKAGE, null));
assertEquals(addLink(mContext.getString(R.string.monitoring_description_named_vpn,
VPN_PACKAGE)),
- mFooter.getVpnMessage(false, false, VPN_PACKAGE, null));
+ mFooterUtils.getVpnMessage(false, false, VPN_PACKAGE, null));
assertEquals(addLink(mContext.getString(R.string.monitoring_description_named_vpn,
VPN_PACKAGE_2)),
- mFooter.getVpnMessage(true, true, null, VPN_PACKAGE_2));
+ mFooterUtils.getVpnMessage(true, true, null, VPN_PACKAGE_2));
assertEquals(addLink(mContext.getString(
R.string.monitoring_description_managed_profile_named_vpn,
VPN_PACKAGE_2)),
- mFooter.getVpnMessage(false, true, null, VPN_PACKAGE_2));
+ mFooterUtils.getVpnMessage(false, true, null, VPN_PACKAGE_2));
assertEquals(addLink(mContext.getString(
R.string.monitoring_description_personal_profile_named_vpn,
VPN_PACKAGE)),
- mFooter.getVpnMessage(false, true, VPN_PACKAGE, null));
+ mFooterUtils.getVpnMessage(false, true, VPN_PACKAGE, null));
}
@Test
@@ -631,19 +635,19 @@ public class QSSecurityFooterTest extends SysuiTestCase {
// Device Management subtitle should be shown when there is Device Management section only
// Other sections visibility will be set somewhere else so it will not be tested here
- mFooter.configSubtitleVisibility(true, false, false, false, view);
+ mFooterUtils.configSubtitleVisibility(true, false, false, false, view);
assertEquals(View.VISIBLE,
view.findViewById(R.id.device_management_subtitle).getVisibility());
// If there are multiple sections, all subtitles should be shown
- mFooter.configSubtitleVisibility(true, true, false, false, view);
+ mFooterUtils.configSubtitleVisibility(true, true, false, false, view);
assertEquals(View.VISIBLE,
view.findViewById(R.id.device_management_subtitle).getVisibility());
assertEquals(View.VISIBLE,
view.findViewById(R.id.ca_certs_subtitle).getVisibility());
// If there are multiple sections, all subtitles should be shown
- mFooter.configSubtitleVisibility(true, true, true, true, view);
+ mFooterUtils.configSubtitleVisibility(true, true, true, true, view);
assertEquals(View.VISIBLE,
view.findViewById(R.id.device_management_subtitle).getVisibility());
assertEquals(View.VISIBLE,
@@ -655,7 +659,7 @@ public class QSSecurityFooterTest extends SysuiTestCase {
// If there are multiple sections, all subtitles should be shown, event if there is no
// Device Management section
- mFooter.configSubtitleVisibility(false, true, true, true, view);
+ mFooterUtils.configSubtitleVisibility(false, true, true, true, view);
assertEquals(View.VISIBLE,
view.findViewById(R.id.ca_certs_subtitle).getVisibility());
assertEquals(View.VISIBLE,
@@ -664,13 +668,13 @@ public class QSSecurityFooterTest extends SysuiTestCase {
view.findViewById(R.id.vpn_subtitle).getVisibility());
// If there is only 1 section, the title should be hidden
- mFooter.configSubtitleVisibility(false, true, false, false, view);
+ mFooterUtils.configSubtitleVisibility(false, true, false, false, view);
assertEquals(View.GONE,
view.findViewById(R.id.ca_certs_subtitle).getVisibility());
- mFooter.configSubtitleVisibility(false, false, true, false, view);
+ mFooterUtils.configSubtitleVisibility(false, false, true, false, view);
assertEquals(View.GONE,
view.findViewById(R.id.network_logging_subtitle).getVisibility());
- mFooter.configSubtitleVisibility(false, false, false, true, view);
+ mFooterUtils.configSubtitleVisibility(false, false, false, true, view);
assertEquals(View.GONE,
view.findViewById(R.id.vpn_subtitle).getVisibility());
}
@@ -690,6 +694,9 @@ public class QSSecurityFooterTest extends SysuiTestCase {
@Test
public void testParentalControls() {
+ // Make sure the security footer is visible, so that the images are updated.
+ when(mSecurityController.isProfileOwnerOfOrganizationOwnedDevice()).thenReturn(true);
+
when(mSecurityController.isParentalControlsEnabled()).thenReturn(true);
Drawable testDrawable = new VectorDrawable();
@@ -719,7 +726,7 @@ public class QSSecurityFooterTest extends SysuiTestCase {
when(mSecurityController.isParentalControlsEnabled()).thenReturn(true);
when(mSecurityController.getLabel(any())).thenReturn(PARENTAL_CONTROLS_LABEL);
- View view = mFooter.createDialogView();
+ View view = mFooterUtils.createDialogView();
TextView textView = (TextView) view.findViewById(R.id.parental_controls_title);
assertEquals(PARENTAL_CONTROLS_LABEL, textView.getText());
}
@@ -742,7 +749,7 @@ public class QSSecurityFooterTest extends SysuiTestCase {
when(mSecurityController.getDeviceOwnerType(DEVICE_OWNER_COMPONENT))
.thenReturn(DEVICE_OWNER_TYPE_FINANCED);
- View view = mFooter.createDialogView();
+ View view = mFooterUtils.createDialogView();
TextView managementSubtitle = view.findViewById(R.id.device_management_subtitle);
assertEquals(View.VISIBLE, managementSubtitle.getVisibility());
@@ -753,7 +760,7 @@ public class QSSecurityFooterTest extends SysuiTestCase {
assertEquals(mContext.getString(R.string.monitoring_financed_description_named_management,
MANAGING_ORGANIZATION, MANAGING_ORGANIZATION), managementMessage.getText());
assertEquals(mContext.getString(R.string.monitoring_button_view_policies),
- mFooter.getSettingsButton());
+ mFooterUtils.getSettingsButton());
}
@Test
@@ -773,7 +780,7 @@ public class QSSecurityFooterTest extends SysuiTestCase {
AlertDialog dialog = dialogCaptor.getValue();
dialog.create();
- assertEquals(mFooter.getSettingsButton(),
+ assertEquals(mFooterUtils.getSettingsButton(),
dialog.getButton(DialogInterface.BUTTON_NEGATIVE).getText());
dialog.dismiss();
@@ -816,8 +823,8 @@ public class QSSecurityFooterTest extends SysuiTestCase {
new Intent(DevicePolicyManager.ACTION_SHOW_DEVICE_MONITORING_DIALOG));
mTestableLooper.processAllMessages();
- assertTrue(mFooter.getDialog().isShowing());
- mFooter.getDialog().dismiss();
+ assertTrue(mFooterUtils.getDialog().isShowing());
+ mFooterUtils.getDialog().dismiss();
}
private CharSequence addLink(CharSequence description) {
@@ -825,7 +832,7 @@ public class QSSecurityFooterTest extends SysuiTestCase {
message.append(description);
message.append(mContext.getString(R.string.monitoring_description_vpn_settings_separator));
message.append(mContext.getString(R.string.monitoring_description_vpn_settings),
- mFooter.new VpnSpan(), 0);
+ mFooterUtils.new VpnSpan(), 0);
return message;
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractorTest.kt
new file mode 100644
index 000000000000..3c258077c29d
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractorTest.kt
@@ -0,0 +1,212 @@
+/*
+ * Copyright (C) 2022 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.qs.footer.domain.interactor
+
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.os.UserHandle
+import android.provider.Settings
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.view.View
+import androidx.test.filters.SmallTest
+import com.android.internal.logging.nano.MetricsProto
+import com.android.internal.logging.testing.FakeMetricsLogger
+import com.android.internal.logging.testing.UiEventLoggerFake
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.globalactions.GlobalActionsDialogLite
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.qs.QSSecurityFooterUtils
+import com.android.systemui.qs.footer.FooterActionsTestUtils
+import com.android.systemui.qs.user.UserSwitchDialogController
+import com.android.systemui.statusbar.policy.DeviceProvisionedController
+import com.android.systemui.truth.correspondence.FakeUiEvent
+import com.android.systemui.truth.correspondence.LogMaker
+import com.android.systemui.user.UserSwitcherActivity
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.nullable
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenever
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper
+class FooterActionsInteractorTest : SysuiTestCase() {
+ private lateinit var utils: FooterActionsTestUtils
+
+ @Before
+ fun setUp() {
+ utils = FooterActionsTestUtils(context, TestableLooper.get(this))
+ }
+
+ @Test
+ fun showDeviceMonitoringDialog() {
+ val qsSecurityFooterUtils = mock<QSSecurityFooterUtils>()
+ val underTest = utils.footerActionsInteractor(qsSecurityFooterUtils = qsSecurityFooterUtils)
+
+ val quickSettingsContext = mock<Context>()
+ underTest.showDeviceMonitoringDialog(quickSettingsContext)
+ verify(qsSecurityFooterUtils).showDeviceMonitoringDialog(quickSettingsContext, null)
+
+ val view = mock<View>()
+ whenever(view.context).thenReturn(quickSettingsContext)
+ underTest.showDeviceMonitoringDialog(view)
+ verify(qsSecurityFooterUtils).showDeviceMonitoringDialog(quickSettingsContext, null)
+ }
+
+ @Test
+ fun showPowerMenuDialog() {
+ val uiEventLogger = UiEventLoggerFake()
+ val underTest = utils.footerActionsInteractor(uiEventLogger = uiEventLogger)
+
+ val globalActionsDialogLite = mock<GlobalActionsDialogLite>()
+ val view = mock<View>()
+ underTest.showPowerMenuDialog(globalActionsDialogLite, view)
+
+ // Event is logged.
+ val logs = uiEventLogger.logs
+ assertThat(logs)
+ .comparingElementsUsing(FakeUiEvent.EVENT_ID)
+ .containsExactly(GlobalActionsDialogLite.GlobalActionsEvent.GA_OPEN_QS.id)
+
+ // Dialog is shown.
+ verify(globalActionsDialogLite)
+ .showOrHideDialog(
+ /* keyguardShowing= */ false,
+ /* isDeviceProvisioned= */ true,
+ view,
+ )
+ }
+
+ @Test
+ fun showSettings_userSetUp() {
+ val activityStarter = mock<ActivityStarter>()
+ val deviceProvisionedController = mock<DeviceProvisionedController>()
+ val metricsLogger = FakeMetricsLogger()
+
+ // User is set up.
+ whenever(deviceProvisionedController.isCurrentUserSetup).thenReturn(true)
+
+ val underTest =
+ utils.footerActionsInteractor(
+ activityStarter = activityStarter,
+ deviceProvisionedController = deviceProvisionedController,
+ metricsLogger = metricsLogger,
+ )
+
+ underTest.showSettings(mock())
+
+ // Event is logged.
+ assertThat(metricsLogger.logs.toList())
+ .comparingElementsUsing(LogMaker.CATEGORY)
+ .containsExactly(MetricsProto.MetricsEvent.ACTION_QS_EXPANDED_SETTINGS_LAUNCH)
+
+ // Activity is started.
+ val intentCaptor = argumentCaptor<Intent>()
+ verify(activityStarter)
+ .startActivity(
+ intentCaptor.capture(),
+ /* dismissShade= */ eq(true),
+ nullable() as? ActivityLaunchAnimator.Controller,
+ )
+ assertThat(intentCaptor.value.action).isEqualTo(Settings.ACTION_SETTINGS)
+ }
+
+ @Test
+ fun showSettings_userNotSetUp() {
+ val activityStarter = mock<ActivityStarter>()
+ val deviceProvisionedController = mock<DeviceProvisionedController>()
+
+ // User is not set up.
+ whenever(deviceProvisionedController.isCurrentUserSetup).thenReturn(false)
+
+ val underTest =
+ utils.footerActionsInteractor(
+ activityStarter = activityStarter,
+ deviceProvisionedController = deviceProvisionedController,
+ )
+
+ underTest.showSettings(mock())
+
+ // We only unlock the device.
+ verify(activityStarter).postQSRunnableDismissingKeyguard(any())
+ }
+
+ @Test
+ fun showUserSwitcher_fullScreenDisabled() {
+ val featureFlags = FakeFeatureFlags().apply { set(Flags.FULL_SCREEN_USER_SWITCHER, false) }
+ val userSwitchDialogController = mock<UserSwitchDialogController>()
+ val underTest =
+ utils.footerActionsInteractor(
+ featureFlags = featureFlags,
+ userSwitchDialogController = userSwitchDialogController,
+ )
+
+ val view = mock<View>()
+ underTest.showUserSwitcher(view)
+
+ // Dialog is shown.
+ verify(userSwitchDialogController).showDialog(view)
+ }
+
+ @Test
+ fun showUserSwitcher_fullScreenEnabled() {
+ val featureFlags = FakeFeatureFlags().apply { set(Flags.FULL_SCREEN_USER_SWITCHER, true) }
+ val activityStarter = mock<ActivityStarter>()
+ val underTest =
+ utils.footerActionsInteractor(
+ featureFlags = featureFlags,
+ activityStarter = activityStarter,
+ )
+
+ // The clicked view. The context is necessary because it's used to build the intent, that
+ // we check below.
+ val view = mock<View>()
+ whenever(view.context).thenReturn(context)
+
+ underTest.showUserSwitcher(view)
+
+ // Dialog is shown.
+ val intentCaptor = argumentCaptor<Intent>()
+ verify(activityStarter)
+ .startActivity(
+ intentCaptor.capture(),
+ /* dismissShade= */ eq(true),
+ /* ActivityLaunchAnimator.Controller= */ nullable(),
+ /* showOverLockscreenWhenLocked= */ eq(true),
+ eq(UserHandle.SYSTEM),
+ )
+ assertThat(intentCaptor.value.component)
+ .isEqualTo(
+ ComponentName(
+ context,
+ UserSwitcherActivity::class.java,
+ )
+ )
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt
new file mode 100644
index 000000000000..e4751d135035
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt
@@ -0,0 +1,408 @@
+/*
+ * Copyright (C) 2022 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.qs.footer.ui.viewmodel
+
+import android.graphics.drawable.Drawable
+import android.os.UserManager
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.testing.TestableLooper.RunWithLooper
+import androidx.test.filters.SmallTest
+import com.android.settingslib.Utils
+import com.android.settingslib.drawable.UserIconDrawable
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.common.shared.model.ContentDescription
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.qs.FakeFgsManagerController
+import com.android.systemui.qs.QSSecurityFooterUtils
+import com.android.systemui.qs.footer.FooterActionsTestUtils
+import com.android.systemui.qs.footer.domain.model.SecurityButtonConfig
+import com.android.systemui.security.data.model.SecurityModel
+import com.android.systemui.settings.FakeUserTracker
+import com.android.systemui.statusbar.policy.FakeSecurityController
+import com.android.systemui.statusbar.policy.FakeUserInfoController
+import com.android.systemui.statusbar.policy.FakeUserInfoController.FakeInfo
+import com.android.systemui.statusbar.policy.MockUserSwitcherControllerWrapper
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.nullable
+import com.android.systemui.util.settings.FakeSettings
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.runBlockingTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.`when` as whenever
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@RunWithLooper
+class FooterActionsViewModelTest : SysuiTestCase() {
+ private lateinit var utils: FooterActionsTestUtils
+
+ @Before
+ fun setUp() {
+ utils = FooterActionsTestUtils(context, TestableLooper.get(this))
+ }
+
+ @Test
+ fun settingsButton() = runBlockingTest {
+ val underTest = utils.footerActionsViewModel(showPowerButton = false)
+ val settings = underTest.settings
+
+ assertThat(settings.contentDescription)
+ .isEqualTo(ContentDescription.Resource(R.string.accessibility_quick_settings_settings))
+ assertThat(settings.icon).isEqualTo(Icon.Resource(R.drawable.ic_settings))
+ assertThat(settings.background).isEqualTo(R.drawable.qs_footer_action_circle)
+ assertThat(settings.iconTint).isNull()
+ }
+
+ @Test
+ fun powerButton() = runBlockingTest {
+ // Without power button.
+ val underTestWithoutPower = utils.footerActionsViewModel(showPowerButton = false)
+ assertThat(underTestWithoutPower.power).isNull()
+
+ // With power button.
+ val underTestWithPower = utils.footerActionsViewModel(showPowerButton = true)
+ val power = underTestWithPower.power
+ assertThat(power).isNotNull()
+ assertThat(power!!.contentDescription)
+ .isEqualTo(
+ ContentDescription.Resource(R.string.accessibility_quick_settings_power_menu)
+ )
+ assertThat(power.icon).isEqualTo(Icon.Resource(android.R.drawable.ic_lock_power_off))
+ assertThat(power.background).isEqualTo(R.drawable.qs_footer_action_circle_color)
+ assertThat(power.iconTint)
+ .isEqualTo(
+ Utils.getColorAttrDefaultColor(
+ context,
+ com.android.internal.R.attr.textColorOnAccent,
+ ),
+ )
+ }
+
+ @Test
+ fun userSwitcher() = runBlockingTest {
+ val picture: Drawable = mock()
+ val userInfoController = FakeUserInfoController(FakeInfo(picture = picture))
+ val settings = FakeSettings()
+ val userId = 42
+ val userTracker = FakeUserTracker(userId)
+ val userSwitcherControllerWrapper =
+ MockUserSwitcherControllerWrapper(currentUserName = "foo")
+
+ // Mock UserManager.
+ val userManager = mock<UserManager>()
+ var isUserSwitcherEnabled = false
+ var isGuestUser = false
+ whenever(userManager.isUserSwitcherEnabled(any())).thenAnswer { isUserSwitcherEnabled }
+ whenever(userManager.isGuestUser(any())).thenAnswer { isGuestUser }
+
+ val underTest =
+ utils.footerActionsViewModel(
+ showPowerButton = false,
+ footerActionsInteractor =
+ utils.footerActionsInteractor(
+ userSwitcherRepository =
+ utils.userSwitcherRepository(
+ userTracker = userTracker,
+ settings = settings,
+ userManager = userManager,
+ userInfoController = userInfoController,
+ userSwitcherController = userSwitcherControllerWrapper.controller,
+ ),
+ )
+ )
+
+ // Collect the user switcher into currentUserSwitcher.
+ var currentUserSwitcher: FooterActionsButtonViewModel? = null
+ val job = launch { underTest.userSwitcher.collect { currentUserSwitcher = it } }
+ fun currentUserSwitcher(): FooterActionsButtonViewModel? {
+ // Make sure we finish collecting the current user switcher. This is necessary because
+ // combined flows launch multiple coroutines in the current scope so we need to make
+ // sure we process all coroutines triggered by our flow collection before we make
+ // assertions on the current buttons.
+ advanceUntilIdle()
+ return currentUserSwitcher
+ }
+
+ // The user switcher is disabled.
+ assertThat(currentUserSwitcher()).isNull()
+
+ // Make the user manager return that the User Switcher is enabled. A change of the setting
+ // for the current user will be fired to notify us of that change.
+ isUserSwitcherEnabled = true
+
+ // Update the setting for a random user: nothing should change, given that at this point we
+ // weren't notified of the change yet.
+ utils.setUserSwitcherEnabled(settings, true, 3)
+ assertThat(currentUserSwitcher()).isNull()
+
+ // Update the setting for the observed user: now we will be notified and the button should
+ // be there.
+ utils.setUserSwitcherEnabled(settings, true, userId)
+ val userSwitcher = currentUserSwitcher()
+ assertThat(userSwitcher).isNotNull()
+ assertThat(userSwitcher!!.contentDescription)
+ .isEqualTo(ContentDescription.Loaded("Signed in as foo"))
+ assertThat(userSwitcher.icon).isEqualTo(Icon.Loaded(picture))
+ assertThat(userSwitcher.background).isEqualTo(R.drawable.qs_footer_action_circle)
+
+ // Change the current user name.
+ userSwitcherControllerWrapper.currentUserName = "bar"
+ assertThat(currentUserSwitcher()?.contentDescription)
+ .isEqualTo(ContentDescription.Loaded("Signed in as bar"))
+
+ fun iconTint(): Int? = currentUserSwitcher()!!.iconTint
+
+ // We tint the icon if the current user is not the guest.
+ assertThat(iconTint()).isNull()
+
+ // Make the UserManager return that the current user is the guest. A change of the user
+ // info will be fired to notify us of that change.
+ isGuestUser = true
+
+ // At this point, there was no change of the user info yet so we still didn't pick the
+ // UserManager change.
+ assertThat(iconTint()).isNull()
+
+ // Trigger a user info change: there should now be a tint.
+ userInfoController.updateInfo { userAccount = "doe" }
+ assertThat(iconTint())
+ .isEqualTo(
+ Utils.getColorAttrDefaultColor(
+ context,
+ android.R.attr.colorForeground,
+ )
+ )
+
+ // Make sure we don't tint the icon if it is a user image (and not the default image), even
+ // in guest mode.
+ userInfoController.updateInfo { this.picture = mock<UserIconDrawable>() }
+ assertThat(iconTint()).isNull()
+
+ job.cancel()
+ }
+
+ @Test
+ fun security() = runBlockingTest {
+ val securityController = FakeSecurityController()
+ val qsSecurityFooterUtils = mock<QSSecurityFooterUtils>()
+
+ // Mock QSSecurityFooter to map a SecurityModel into a SecurityButtonConfig using the
+ // logic in securityToConfig.
+ var securityToConfig: (SecurityModel) -> SecurityButtonConfig? = { null }
+ whenever(qsSecurityFooterUtils.getButtonConfig(any())).thenAnswer {
+ securityToConfig(it.arguments.first() as SecurityModel)
+ }
+
+ val underTest =
+ utils.footerActionsViewModel(
+ footerActionsInteractor =
+ utils.footerActionsInteractor(
+ qsSecurityFooterUtils = qsSecurityFooterUtils,
+ securityRepository =
+ utils.securityRepository(
+ securityController = securityController,
+ ),
+ ),
+ )
+
+ // Collect the security model into currentSecurity.
+ var currentSecurity: FooterActionsSecurityButtonViewModel? = null
+ val job = launch { underTest.security.collect { currentSecurity = it } }
+ fun currentSecurity(): FooterActionsSecurityButtonViewModel? {
+ advanceUntilIdle()
+ return currentSecurity
+ }
+
+ // By default, we always return a null SecurityButtonConfig.
+ assertThat(currentSecurity()).isNull()
+
+ // Map any SecurityModel into a non-null SecurityButtonConfig.
+ val buttonConfig =
+ SecurityButtonConfig(
+ icon = Icon.Resource(0),
+ text = "foo",
+ isClickable = true,
+ )
+ securityToConfig = { buttonConfig }
+
+ // There was no change of the security info yet, so the mapper was not called yet.
+ assertThat(currentSecurity()).isNull()
+
+ // Trigger a SecurityModel change, which will call the mapper and add a button.
+ securityController.updateState {}
+ var security = currentSecurity()
+ assertThat(security).isNotNull()
+ assertThat(security!!.icon).isEqualTo(buttonConfig.icon)
+ assertThat(security.text).isEqualTo(buttonConfig.text)
+ assertThat(security.onClick).isNotNull()
+
+ // If the config.clickable = false, then onClick should be null.
+ securityToConfig = { buttonConfig.copy(isClickable = false) }
+ securityController.updateState {}
+ security = currentSecurity()
+ assertThat(security).isNotNull()
+ assertThat(security!!.onClick).isNull()
+
+ job.cancel()
+ }
+
+ @Test
+ fun foregroundServices() = runBlockingTest {
+ val securityController = FakeSecurityController()
+ val fgsManagerController =
+ FakeFgsManagerController(
+ isAvailable = true,
+ showFooterDot = false,
+ numRunningPackages = 0,
+ )
+ val qsSecurityFooterUtils = mock<QSSecurityFooterUtils>()
+
+ // Mock QSSecurityFooter to map a SecurityModel into a SecurityButtonConfig using the
+ // logic in securityToConfig.
+ var securityToConfig: (SecurityModel) -> SecurityButtonConfig? = { null }
+ whenever(qsSecurityFooterUtils.getButtonConfig(any())).thenAnswer {
+ securityToConfig(it.arguments.first() as SecurityModel)
+ }
+
+ val underTest =
+ utils.footerActionsViewModel(
+ footerActionsInteractor =
+ utils.footerActionsInteractor(
+ qsSecurityFooterUtils = qsSecurityFooterUtils,
+ securityRepository = utils.securityRepository(securityController),
+ foregroundServicesRepository =
+ utils.foregroundServicesRepository(fgsManagerController),
+ ),
+ )
+
+ // Collect the security model into currentSecurity.
+ var currentForegroundServices: FooterActionsForegroundServicesButtonViewModel? = null
+ val job = launch { underTest.foregroundServices.collect { currentForegroundServices = it } }
+ fun currentForegroundServices(): FooterActionsForegroundServicesButtonViewModel? {
+ advanceUntilIdle()
+ return currentForegroundServices
+ }
+
+ // We don't show the foreground services button if the number of running packages is not
+ // > 1.
+ assertThat(currentForegroundServices()).isNull()
+
+ // We show it at soon as the number of services is at least 1. Given that there is no
+ // security, it should be displayed with text.
+ fgsManagerController.numRunningPackages = 1
+ val foregroundServices = currentForegroundServices()
+ assertThat(foregroundServices).isNotNull()
+ assertThat(foregroundServices!!.foregroundServicesCount).isEqualTo(1)
+ assertThat(foregroundServices.text).isEqualTo("1 app is active")
+ assertThat(foregroundServices.displayText).isTrue()
+ assertThat(foregroundServices.onClick).isNotNull()
+
+ // We handle plurals correctly.
+ fgsManagerController.numRunningPackages = 3
+ assertThat(currentForegroundServices()?.text).isEqualTo("3 apps are active")
+
+ // Showing new changes (the footer dot) is currently disabled.
+ assertThat(foregroundServices.hasNewChanges).isFalse()
+
+ // Enabling it will show the new changes.
+ fgsManagerController.showFooterDot.value = true
+ assertThat(currentForegroundServices()?.hasNewChanges).isTrue()
+
+ // Dismissing the dialog should remove the new changes dot.
+ fgsManagerController.simulateDialogDismiss()
+ assertThat(currentForegroundServices()?.hasNewChanges).isFalse()
+
+ // Showing the security button will make this show as a simple button without text.
+ assertThat(foregroundServices.displayText).isTrue()
+ securityToConfig = {
+ SecurityButtonConfig(
+ icon = Icon.Resource(0),
+ text = "foo",
+ isClickable = true,
+ )
+ }
+ securityController.updateState {}
+ assertThat(currentForegroundServices()?.displayText).isFalse()
+
+ job.cancel()
+ }
+
+ @Test
+ fun observeDeviceMonitoringDialogRequests() = runBlockingTest {
+ val qsSecurityFooterUtils = mock<QSSecurityFooterUtils>()
+ val broadcastDispatcher = mock<BroadcastDispatcher>()
+
+ // Return a fake broadcastFlow that emits 3 fake events when collected.
+ val broadcastFlow = flowOf(Unit, Unit, Unit)
+ whenever(
+ broadcastDispatcher.broadcastFlow(
+ any(),
+ nullable(),
+ anyInt(),
+ nullable(),
+ )
+ )
+ .thenAnswer { broadcastFlow }
+
+ // Increment nDialogRequests whenever a request to show the dialog is made by the
+ // FooterActionsInteractor.
+ var nDialogRequests = 0
+ whenever(qsSecurityFooterUtils.showDeviceMonitoringDialog(any(), nullable())).then {
+ nDialogRequests++
+ }
+
+ val underTest =
+ utils.footerActionsViewModel(
+ footerActionsInteractor =
+ utils.footerActionsInteractor(
+ qsSecurityFooterUtils = qsSecurityFooterUtils,
+ broadcastDispatcher = broadcastDispatcher,
+ ),
+ )
+
+ val job = launch {
+ underTest.observeDeviceMonitoringDialogRequests(quickSettingsContext = mock())
+ }
+
+ advanceUntilIdle()
+ assertThat(nDialogRequests).isEqualTo(3)
+
+ job.cancel()
+ }
+
+ @Test
+ fun isVisible() {
+ val underTest = utils.footerActionsViewModel()
+ assertThat(underTest.isVisible.value).isTrue()
+
+ underTest.onVisibilityChangeRequested(visible = false)
+ assertThat(underTest.isVisible.value).isFalse()
+
+ underTest.onVisibilityChangeRequested(visible = true)
+ assertThat(underTest.isVisible.value).isTrue()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
index 7d28871e340c..98389c2c7a6f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
@@ -259,7 +259,7 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase {
private DisplayMetrics mDisplayMetrics = new DisplayMetrics();
@Mock
private KeyguardClockSwitch mKeyguardClockSwitch;
- private PanelViewController.TouchHandler mTouchHandler;
+ private NotificationPanelViewController.TouchHandler mTouchHandler;
private ConfigurationController mConfigurationController;
@Mock
private MediaHierarchyManager mMediaHiearchyManager;
@@ -454,7 +454,7 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase {
doAnswer((Answer<Void>) invocation -> {
mTouchHandler = invocation.getArgument(0);
return null;
- }).when(mView).setOnTouchListener(any(PanelViewController.TouchHandler.class));
+ }).when(mView).setOnTouchListener(any(NotificationPanelViewController.TouchHandler.class));
NotificationWakeUpCoordinator coordinator =
new NotificationWakeUpCoordinator(
@@ -1407,7 +1407,7 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase {
when(mQsFrame.getWidth()).thenReturn(1000);
when(mQsHeader.getTop()).thenReturn(0);
when(mQsHeader.getBottom()).thenReturn(1000);
- PanelViewController.TouchHandler touchHandler =
+ NotificationPanelViewController.TouchHandler touchHandler =
mNotificationPanelViewController.createTouchHandler();
mNotificationPanelViewController.setExpandedFraction(1f);
@@ -1427,7 +1427,7 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase {
when(mQsFrame.getWidth()).thenReturn(1000);
when(mQsHeader.getTop()).thenReturn(0);
when(mQsHeader.getBottom()).thenReturn(1000);
- PanelViewController.TouchHandler touchHandler =
+ NotificationPanelViewController.TouchHandler touchHandler =
mNotificationPanelViewController.createTouchHandler();
mNotificationPanelViewController.setExpandedFraction(1f);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
index 9892448aed03..a61fba5c4000 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
@@ -26,22 +26,22 @@ import androidx.test.filters.SmallTest
import androidx.test.platform.app.InstrumentationRegistry
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
-import com.android.systemui.shade.PanelViewController
+import com.android.systemui.shade.NotificationPanelViewController
import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherController
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.unfold.SysUIUnfoldComponent
import com.android.systemui.unfold.config.UnfoldTransitionConfig
import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider
-import com.android.systemui.util.view.ViewUtil
import com.android.systemui.util.mockito.any
+import com.android.systemui.util.view.ViewUtil
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
import org.mockito.ArgumentCaptor
import org.mockito.Mock
-import org.mockito.Mockito.spy
-import org.mockito.Mockito.mock
import org.mockito.Mockito.`when`
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.spy
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
import java.util.Optional
@@ -52,7 +52,7 @@ class PhoneStatusBarViewControllerTest : SysuiTestCase() {
private val touchEventHandler = TestTouchEventHandler()
@Mock
- private lateinit var panelViewController: PanelViewController
+ private lateinit var notificationPanelViewController: NotificationPanelViewController
@Mock
private lateinit var panelView: ViewGroup
@Mock
@@ -76,7 +76,7 @@ class PhoneStatusBarViewControllerTest : SysuiTestCase() {
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
- `when`(panelViewController.view).thenReturn(panelView)
+ `when`(notificationPanelViewController.view).thenReturn(panelView)
`when`(sysuiUnfoldComponent.getStatusBarMoveFromCenterAnimationController())
.thenReturn(moveFromCenterAnimation)
// create the view on main thread as it requires main looper
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt
index d6c995bef229..5aa7f92d22e8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt
@@ -20,7 +20,7 @@ import android.view.MotionEvent
import android.view.ViewGroup
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.shade.PanelViewController
+import com.android.systemui.shade.NotificationPanelViewController
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
@@ -32,7 +32,7 @@ import org.mockito.MockitoAnnotations
class PhoneStatusBarViewTest : SysuiTestCase() {
@Mock
- private lateinit var panelViewController: PanelViewController
+ private lateinit var notificationPanelViewController: NotificationPanelViewController
@Mock
private lateinit var panelView: ViewGroup
@@ -43,7 +43,7 @@ class PhoneStatusBarViewTest : SysuiTestCase() {
MockitoAnnotations.initMocks(this)
// TODO(b/197137564): Setting up a panel view and its controller feels unnecessary when
// testing just [PhoneStatusBarView].
- `when`(panelViewController.view).thenReturn(panelView)
+ `when`(notificationPanelViewController.view).thenReturn(panelView)
view = PhoneStatusBarView(mContext, null)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FakeFeatureFlags.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FakeFeatureFlags.kt
index 7b1455cb2e46..b53ad0a3726f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/FakeFeatureFlags.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FakeFeatureFlags.kt
@@ -56,7 +56,6 @@ class FakeFeatureFlags : FeatureFlags {
stringFlags.put(flag.id, value)
}
-
override fun isEnabled(flag: UnreleasedFlag): Boolean = requireBooleanValue(flag.id)
override fun isEnabled(flag: ReleasedFlag): Boolean = requireBooleanValue(flag.id)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeFgsManagerController.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeFgsManagerController.kt
new file mode 100644
index 000000000000..527258579372
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeFgsManagerController.kt
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2022 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.qs
+
+import android.view.View
+import com.android.systemui.qs.FgsManagerController.OnDialogDismissedListener
+import com.android.systemui.qs.FgsManagerController.OnNumberOfPackagesChangedListener
+import kotlinx.coroutines.flow.MutableStateFlow
+
+/** A fake [FgsManagerController] to be used in tests. */
+class FakeFgsManagerController(
+ isAvailable: Boolean = true,
+ showFooterDot: Boolean = false,
+ numRunningPackages: Int = 0,
+) : FgsManagerController {
+ override val isAvailable: MutableStateFlow<Boolean> = MutableStateFlow(isAvailable)
+
+ override var numRunningPackages = numRunningPackages
+ set(value) {
+ if (value != field) {
+ field = value
+ newChangesSinceDialogWasDismissed = true
+ numRunningPackagesListeners.forEach { it.onNumberOfPackagesChanged(value) }
+ }
+ }
+
+ override var newChangesSinceDialogWasDismissed = false
+ private set
+
+ override val showFooterDot: MutableStateFlow<Boolean> = MutableStateFlow(showFooterDot)
+
+ private val numRunningPackagesListeners = LinkedHashSet<OnNumberOfPackagesChangedListener>()
+ private val dialogDismissedListeners = LinkedHashSet<OnDialogDismissedListener>()
+
+ /** Simulate that a fgs dialog was just dismissed. */
+ fun simulateDialogDismiss() {
+ newChangesSinceDialogWasDismissed = false
+ dialogDismissedListeners.forEach { it.onDialogDismissed() }
+ }
+
+ override fun init() {}
+
+ override fun showDialog(viewLaunchedFrom: View?) {}
+
+ override fun addOnNumberOfPackagesChangedListener(listener: OnNumberOfPackagesChangedListener) {
+ numRunningPackagesListeners.add(listener)
+ }
+
+ override fun removeOnNumberOfPackagesChangedListener(
+ listener: OnNumberOfPackagesChangedListener
+ ) {
+ numRunningPackagesListeners.remove(listener)
+ }
+
+ override fun addOnDialogDismissedListener(listener: OnDialogDismissedListener) {
+ dialogDismissedListeners.add(listener)
+ }
+
+ override fun removeOnDialogDismissedListener(listener: OnDialogDismissedListener) {
+ dialogDismissedListeners.remove(listener)
+ }
+
+ override fun shouldUpdateFooterVisibility(): Boolean = false
+
+ override fun visibleButtonsCount(): Int = 0
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/footer/FooterActionsTestUtils.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/footer/FooterActionsTestUtils.kt
new file mode 100644
index 000000000000..2a9aeddc9aa8
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/footer/FooterActionsTestUtils.kt
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2022 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.qs.footer
+
+import android.content.Context
+import android.os.Handler
+import android.os.UserManager
+import android.provider.Settings
+import android.testing.TestableLooper
+import com.android.internal.logging.MetricsLogger
+import com.android.internal.logging.UiEventLogger
+import com.android.internal.logging.testing.FakeMetricsLogger
+import com.android.internal.logging.testing.UiEventLoggerFake
+import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.classifier.FalsingManagerFake
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.globalactions.GlobalActionsDialogLite
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.qs.FakeFgsManagerController
+import com.android.systemui.qs.FgsManagerController
+import com.android.systemui.qs.QSSecurityFooterUtils
+import com.android.systemui.qs.footer.data.repository.ForegroundServicesRepository
+import com.android.systemui.qs.footer.data.repository.ForegroundServicesRepositoryImpl
+import com.android.systemui.qs.footer.data.repository.UserSwitcherRepository
+import com.android.systemui.qs.footer.data.repository.UserSwitcherRepositoryImpl
+import com.android.systemui.qs.footer.domain.interactor.FooterActionsInteractor
+import com.android.systemui.qs.footer.domain.interactor.FooterActionsInteractorImpl
+import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
+import com.android.systemui.qs.user.UserSwitchDialogController
+import com.android.systemui.security.data.repository.SecurityRepository
+import com.android.systemui.security.data.repository.SecurityRepositoryImpl
+import com.android.systemui.settings.FakeUserTracker
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.statusbar.policy.DeviceProvisionedController
+import com.android.systemui.statusbar.policy.FakeSecurityController
+import com.android.systemui.statusbar.policy.FakeUserInfoController
+import com.android.systemui.statusbar.policy.SecurityController
+import com.android.systemui.statusbar.policy.UserInfoController
+import com.android.systemui.statusbar.policy.UserSwitcherController
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.settings.FakeSettings
+import com.android.systemui.util.settings.GlobalSettings
+import com.android.systemui.util.time.FakeSystemClock
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.test.TestCoroutineDispatcher
+
+/**
+ * Util class to create real implementations of the FooterActions repositories, viewModel and
+ * interactor to be used in tests.
+ */
+class FooterActionsTestUtils(
+ private val context: Context,
+ private val testableLooper: TestableLooper,
+ private val fakeClock: FakeSystemClock = FakeSystemClock(),
+) {
+ /** Enable or disable the user switcher in the settings. */
+ fun setUserSwitcherEnabled(settings: GlobalSettings, enabled: Boolean, userId: Int) {
+ settings.putBoolForUser(Settings.Global.USER_SWITCHER_ENABLED, enabled, userId)
+
+ // The settings listener is processing messages on the bgHandler (usually backed by a
+ // testableLooper in tests), so let's make sure we process the callback before continuing.
+ testableLooper.processAllMessages()
+ }
+
+ /** Create a [FooterActionsViewModel] to be used in tests. */
+ fun footerActionsViewModel(
+ @Application context: Context = this.context.applicationContext,
+ footerActionsInteractor: FooterActionsInteractor = footerActionsInteractor(),
+ falsingManager: FalsingManager = FalsingManagerFake(),
+ globalActionsDialogLite: GlobalActionsDialogLite = mock(),
+ showPowerButton: Boolean = true,
+ ): FooterActionsViewModel {
+ return FooterActionsViewModel(
+ context,
+ footerActionsInteractor,
+ falsingManager,
+ globalActionsDialogLite,
+ showPowerButton,
+ )
+ }
+
+ /** Create a [FooterActionsInteractor] to be used in tests. */
+ fun footerActionsInteractor(
+ activityStarter: ActivityStarter = mock(),
+ featureFlags: FeatureFlags = FakeFeatureFlags(),
+ metricsLogger: MetricsLogger = FakeMetricsLogger(),
+ uiEventLogger: UiEventLogger = UiEventLoggerFake(),
+ deviceProvisionedController: DeviceProvisionedController = mock(),
+ qsSecurityFooterUtils: QSSecurityFooterUtils = mock(),
+ fgsManagerController: FgsManagerController = mock(),
+ userSwitchDialogController: UserSwitchDialogController = mock(),
+ securityRepository: SecurityRepository = securityRepository(),
+ foregroundServicesRepository: ForegroundServicesRepository = foregroundServicesRepository(),
+ userSwitcherRepository: UserSwitcherRepository = userSwitcherRepository(),
+ broadcastDispatcher: BroadcastDispatcher = mock(),
+ bgDispatcher: CoroutineDispatcher = TestCoroutineDispatcher(),
+ ): FooterActionsInteractor {
+ return FooterActionsInteractorImpl(
+ activityStarter,
+ featureFlags,
+ metricsLogger,
+ uiEventLogger,
+ deviceProvisionedController,
+ qsSecurityFooterUtils,
+ fgsManagerController,
+ userSwitchDialogController,
+ securityRepository,
+ foregroundServicesRepository,
+ userSwitcherRepository,
+ broadcastDispatcher,
+ bgDispatcher,
+ )
+ }
+
+ /** Create a [SecurityRepository] to be used in tests. */
+ fun securityRepository(
+ securityController: SecurityController = FakeSecurityController(),
+ bgDispatcher: CoroutineDispatcher = TestCoroutineDispatcher(),
+ ): SecurityRepository {
+ return SecurityRepositoryImpl(
+ securityController,
+ bgDispatcher,
+ )
+ }
+
+ /** Create a [SecurityRepository] to be used in tests. */
+ fun foregroundServicesRepository(
+ fgsManagerController: FakeFgsManagerController = FakeFgsManagerController(),
+ ): ForegroundServicesRepository {
+ return ForegroundServicesRepositoryImpl(fgsManagerController)
+ }
+
+ /** Create a [UserSwitcherRepository] to be used in tests. */
+ fun userSwitcherRepository(
+ @Application context: Context = this.context.applicationContext,
+ bgHandler: Handler = Handler(testableLooper.looper),
+ bgDispatcher: CoroutineDispatcher = TestCoroutineDispatcher(),
+ userManager: UserManager = mock(),
+ userTracker: UserTracker = FakeUserTracker(),
+ userSwitcherController: UserSwitcherController = mock(),
+ userInfoController: UserInfoController = FakeUserInfoController(),
+ settings: GlobalSettings = FakeSettings(),
+ ): UserSwitcherRepository {
+ return UserSwitcherRepositoryImpl(
+ context,
+ bgHandler,
+ bgDispatcher,
+ userManager,
+ userTracker,
+ userSwitcherController,
+ userInfoController,
+ settings,
+ )
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeUserTracker.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeUserTracker.kt
new file mode 100644
index 000000000000..b2b176420e40
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeUserTracker.kt
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2022 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.settings
+
+import android.content.ContentResolver
+import android.content.Context
+import android.content.pm.UserInfo
+import android.os.UserHandle
+import android.test.mock.MockContentResolver
+import com.android.systemui.util.mockito.mock
+import java.util.concurrent.Executor
+
+/** A fake [UserTracker] to be used in tests. */
+class FakeUserTracker(
+ userId: Int = 0,
+ userHandle: UserHandle = UserHandle.of(userId),
+ userInfo: UserInfo = mock(),
+ userProfiles: List<UserInfo> = emptyList(),
+ userContentResolver: ContentResolver = MockContentResolver(),
+ userContext: Context = mock(),
+ private val onCreateCurrentUserContext: (Context) -> Context = { mock() },
+) : UserTracker {
+ val callbacks = mutableListOf<UserTracker.Callback>()
+
+ override val userId: Int = userId
+ override val userHandle: UserHandle = userHandle
+ override val userInfo: UserInfo = userInfo
+ override val userProfiles: List<UserInfo> = userProfiles
+
+ override val userContentResolver: ContentResolver = userContentResolver
+ override val userContext: Context = userContext
+
+ override fun addCallback(callback: UserTracker.Callback, executor: Executor) {
+ callbacks.add(callback)
+ }
+
+ override fun removeCallback(callback: UserTracker.Callback) {
+ callbacks.remove(callback)
+ }
+
+ override fun createCurrentUserContext(context: Context): Context {
+ return onCreateCurrentUserContext(context)
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/FakeSecurityController.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/FakeSecurityController.kt
new file mode 100644
index 000000000000..c6aa3952c4b1
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/FakeSecurityController.kt
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2022 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.policy
+
+import android.app.admin.DeviceAdminInfo
+import android.content.ComponentName
+import android.graphics.drawable.Drawable
+import java.io.PrintWriter
+
+/** A fake [SecurityController] to be used in tests. */
+class FakeSecurityController(
+ private val fakeState: FakeState = FakeState(),
+) : SecurityController {
+ private val callbacks = LinkedHashSet<SecurityController.SecurityControllerCallback>()
+
+ override fun addCallback(callback: SecurityController.SecurityControllerCallback) {
+ callbacks.add(callback)
+ }
+
+ override fun removeCallback(callback: SecurityController.SecurityControllerCallback) {
+ callbacks.remove(callback)
+ }
+
+ /** Update [fakeState], then notify the callbacks. */
+ fun updateState(f: FakeState.() -> Unit) {
+ fakeState.f()
+ callbacks.forEach { it.onStateChanged() }
+ }
+
+ override fun dump(pw: PrintWriter, args: Array<out String>) {}
+
+ override fun isDeviceManaged(): Boolean = fakeState.isDeviceManaged
+
+ override fun hasProfileOwner(): Boolean = fakeState.hasProfileOwner
+
+ override fun hasWorkProfile(): Boolean = fakeState.hasWorkProfile
+
+ override fun isWorkProfileOn(): Boolean = fakeState.isWorkProfileOn
+
+ override fun isProfileOwnerOfOrganizationOwnedDevice(): Boolean =
+ fakeState.isProfileOwnerOfOrganizationOwnedDevice
+
+ override fun getDeviceOwnerName(): String? = fakeState.deviceOwnerName
+
+ override fun getProfileOwnerName(): String? = fakeState.profileOwnerName
+
+ override fun getDeviceOwnerOrganizationName(): String? = fakeState.deviceOwnerOrganizationName
+
+ override fun getWorkProfileOrganizationName(): String? = fakeState.workProfileOrganizationName
+
+ override fun getDeviceOwnerComponentOnAnyUser(): ComponentName? =
+ fakeState.deviceOwnerComponentOnAnyUser
+
+ override fun getDeviceOwnerType(admin: ComponentName?): Int = 0
+
+ override fun isNetworkLoggingEnabled(): Boolean = fakeState.isNetworkLoggingEnabled
+
+ override fun isVpnEnabled(): Boolean = fakeState.isVpnEnabled
+
+ override fun isVpnRestricted(): Boolean = fakeState.isVpnRestricted
+
+ override fun isVpnBranded(): Boolean = fakeState.isVpnBranded
+
+ override fun getPrimaryVpnName(): String? = fakeState.primaryVpnName
+
+ override fun getWorkProfileVpnName(): String? = fakeState.workProfileVpnName
+
+ override fun hasCACertInCurrentUser(): Boolean = fakeState.hasCACertInCurrentUser
+
+ override fun hasCACertInWorkProfile(): Boolean = fakeState.hasCACertInWorkProfile
+
+ override fun onUserSwitched(newUserId: Int) {}
+
+ override fun isParentalControlsEnabled(): Boolean = fakeState.isParentalControlsEnabled
+
+ override fun getDeviceAdminInfo(): DeviceAdminInfo? = fakeState.deviceAdminInfo
+
+ override fun getIcon(info: DeviceAdminInfo?): Drawable? = null
+
+ override fun getLabel(info: DeviceAdminInfo?): CharSequence? = null
+
+ class FakeState(
+ var isDeviceManaged: Boolean = false,
+ var hasProfileOwner: Boolean = false,
+ var hasWorkProfile: Boolean = false,
+ var isWorkProfileOn: Boolean = false,
+ var isProfileOwnerOfOrganizationOwnedDevice: Boolean = false,
+ var deviceOwnerName: String? = null,
+ var profileOwnerName: String? = null,
+ var deviceOwnerOrganizationName: String? = null,
+ var workProfileOrganizationName: String? = null,
+ var deviceOwnerComponentOnAnyUser: ComponentName? = null,
+ var isNetworkLoggingEnabled: Boolean = false,
+ var isVpnEnabled: Boolean = false,
+ var isVpnRestricted: Boolean = false,
+ var isVpnBranded: Boolean = false,
+ var primaryVpnName: String? = null,
+ var workProfileVpnName: String? = null,
+ var hasCACertInCurrentUser: Boolean = false,
+ var hasCACertInWorkProfile: Boolean = false,
+ var isParentalControlsEnabled: Boolean = false,
+ var deviceAdminInfo: DeviceAdminInfo? = null,
+ )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/FakeUserInfoController.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/FakeUserInfoController.kt
new file mode 100644
index 000000000000..32b9a371674f
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/FakeUserInfoController.kt
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2022 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.policy
+
+import android.graphics.drawable.Drawable
+import com.android.systemui.util.mockito.mock
+
+/** A fake [UserInfoController] to be used in tests. */
+class FakeUserInfoController(
+ private val fakeInfo: FakeInfo = FakeInfo(),
+) : UserInfoController {
+ private val listeners = LinkedHashSet<UserInfoController.OnUserInfoChangedListener>()
+
+ /** Update [fakeInfo], then notify the listeners. */
+ fun updateInfo(f: FakeInfo.() -> Unit) {
+ fakeInfo.f()
+ notifyListeners()
+ }
+
+ private fun notifyListeners() {
+ listeners.forEach { listener ->
+ listener.onUserInfoChanged(fakeInfo.name, fakeInfo.picture, fakeInfo.userAccount)
+ }
+ }
+
+ override fun addCallback(listener: UserInfoController.OnUserInfoChangedListener) {
+ listeners.add(listener)
+
+ // The actual implementation notifies the listener when adding it.
+ listener.onUserInfoChanged(fakeInfo.name, fakeInfo.picture, fakeInfo.userAccount)
+ }
+
+ override fun removeCallback(listener: UserInfoController.OnUserInfoChangedListener) {
+ listeners.remove(listener)
+ }
+
+ override fun reloadUserInfo() {}
+
+ class FakeInfo(
+ var name: String = "",
+ var picture: Drawable = mock(),
+ var userAccount: String = "",
+ )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/MockUserSwitcherControllerWrapper.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/MockUserSwitcherControllerWrapper.kt
new file mode 100644
index 000000000000..1304a12a5f7f
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/MockUserSwitcherControllerWrapper.kt
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2022 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.policy
+
+import com.android.systemui.statusbar.policy.UserSwitcherController.UserSwitchCallback
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
+import org.mockito.Mockito.`when` as whenever
+
+/**
+ * A wrapper around a mocked [UserSwitcherController] to be used in tests.
+ *
+ * Note that this was implemented as a mock wrapper instead of fake implementation of a common
+ * interface given how big the UserSwitcherController grew.
+ */
+class MockUserSwitcherControllerWrapper(
+ currentUserName: String = "",
+) {
+ val controller: UserSwitcherController = mock()
+ private val callbacks = LinkedHashSet<UserSwitchCallback>()
+
+ var currentUserName = currentUserName
+ set(value) {
+ if (value != field) {
+ field = value
+ notifyCallbacks()
+ }
+ }
+
+ private fun notifyCallbacks() {
+ callbacks.forEach { it.onUserSwitched() }
+ }
+
+ init {
+ whenever(controller.addUserSwitchCallback(any())).then { invocation ->
+ callbacks.add(invocation.arguments.first() as UserSwitchCallback)
+ }
+
+ whenever(controller.removeUserSwitchCallback(any())).then { invocation ->
+ callbacks.remove(invocation.arguments.first() as UserSwitchCallback)
+ }
+
+ whenever(controller.currentUserName).thenAnswer { this.currentUserName }
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/truth/correspondence/FakeUiEvent.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/truth/correspondence/FakeUiEvent.kt
new file mode 100644
index 000000000000..48cd345b1f68
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/truth/correspondence/FakeUiEvent.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2022 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.truth.correspondence
+
+import com.android.internal.logging.testing.UiEventLoggerFake
+import com.android.internal.logging.testing.UiEventLoggerFake.FakeUiEvent
+import com.google.common.truth.Correspondence
+
+/** Instances of [Correspondence] to match a [UiEventLoggerFake.FakeUiEvent] with Truth. */
+object FakeUiEvent {
+ val EVENT_ID =
+ Correspondence.transforming<FakeUiEvent, Int>(
+ { it?.eventId },
+ "has a eventId of",
+ )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/truth/correspondence/LogMaker.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/truth/correspondence/LogMaker.kt
new file mode 100644
index 000000000000..3f0a95248d9c
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/truth/correspondence/LogMaker.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2022 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.truth.correspondence
+
+import android.metrics.LogMaker
+import com.google.common.truth.Correspondence
+
+/** Instances of [Correspondence] to match a [LogMaker] with Truth. */
+object LogMaker {
+ val CATEGORY =
+ Correspondence.transforming<LogMaker, Int>(
+ { it?.category },
+ "has a category of",
+ )
+}
diff --git a/services/core/java/com/android/server/am/UidObserverController.java b/services/core/java/com/android/server/am/UidObserverController.java
index e8c1b545eb96..51cb9878c0b3 100644
--- a/services/core/java/com/android/server/am/UidObserverController.java
+++ b/services/core/java/com/android/server/am/UidObserverController.java
@@ -15,6 +15,7 @@
*/
package com.android.server.am;
+import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_UID_OBSERVERS;
@@ -25,6 +26,7 @@ import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.ActivityManagerProto;
import android.app.IUidObserver;
+import android.content.pm.PackageManager;
import android.os.Handler;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
@@ -81,7 +83,9 @@ public class UidObserverController {
@NonNull String callingPackage, int callingUid) {
synchronized (mLock) {
mUidObservers.register(observer, new UidObserverRegistration(callingUid,
- callingPackage, which, cutpoint));
+ callingPackage, which, cutpoint,
+ ActivityManager.checkUidPermission(INTERACT_ACROSS_USERS_FULL, callingUid)
+ == PackageManager.PERMISSION_GRANTED));
}
}
@@ -252,6 +256,11 @@ public class UidObserverController {
final ChangeRecord item = mActiveUidChanges[j];
final long start = SystemClock.uptimeMillis();
final int change = item.change;
+ // Does the user have permission? Don't send a non user UID change otherwise
+ if (UserHandle.getUserId(item.uid) != UserHandle.getUserId(reg.mUid)
+ && !reg.mCanInteractAcrossUsers) {
+ continue;
+ }
if (change == UidRecord.CHANGE_PROCSTATE
&& (reg.mWhich & ActivityManager.UID_OBSERVER_PROCSTATE) == 0) {
// No-op common case: no significant change, the observer is not
@@ -437,6 +446,7 @@ public class UidObserverController {
private final String mPkg;
private final int mWhich;
private final int mCutpoint;
+ private final boolean mCanInteractAcrossUsers;
/**
* Total # of callback calls that took more than {@link #SLOW_UID_OBSERVER_THRESHOLD_MS}.
@@ -467,11 +477,13 @@ public class UidObserverController {
ActivityManagerProto.UID_OBSERVER_FLAG_PROC_OOM_ADJ,
};
- UidObserverRegistration(int uid, @NonNull String pkg, int which, int cutpoint) {
+ UidObserverRegistration(int uid, @NonNull String pkg, int which, int cutpoint,
+ boolean canInteractAcrossUsers) {
this.mUid = uid;
this.mPkg = pkg;
this.mWhich = which;
this.mCutpoint = cutpoint;
+ this.mCanInteractAcrossUsers = canInteractAcrossUsers;
mLastProcStates = cutpoint >= ActivityManager.MIN_PROCESS_STATE
? new SparseIntArray() : null;
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricStateCallback.java b/services/core/java/com/android/server/biometrics/sensors/BiometricStateCallback.java
index 0d789f7a1840..3566b430b680 100644
--- a/services/core/java/com/android/server/biometrics/sensors/BiometricStateCallback.java
+++ b/services/core/java/com/android/server/biometrics/sensors/BiometricStateCallback.java
@@ -25,6 +25,7 @@ import static android.hardware.biometrics.BiometricStateListener.STATE_KEYGUARD_
import android.annotation.NonNull;
import android.hardware.biometrics.BiometricStateListener;
import android.hardware.biometrics.IBiometricStateListener;
+import android.os.IBinder;
import android.os.RemoteException;
import android.util.Slog;
@@ -35,7 +36,7 @@ import java.util.concurrent.CopyOnWriteArrayList;
/**
* A callback for receiving notifications about biometric sensor state changes.
*/
-public class BiometricStateCallback implements ClientMonitorCallback {
+public class BiometricStateCallback implements ClientMonitorCallback, IBinder.DeathRecipient {
private static final String TAG = "BiometricStateCallback";
@@ -153,5 +154,25 @@ public class BiometricStateCallback implements ClientMonitorCallback {
*/
public void registerBiometricStateListener(@NonNull IBiometricStateListener listener) {
mBiometricStateListeners.add(listener);
+ try {
+ listener.asBinder().linkToDeath(this, 0 /* flags */);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to link to death", e);
+ }
+ }
+
+ @Override
+ public void binderDied() {
+ // Do nothing, handled below
+ }
+
+ @Override
+ public void binderDied(IBinder who) {
+ Slog.w(TAG, "Callback binder died: " + who);
+ if (mBiometricStateListeners.removeIf(listener -> listener.asBinder().equals(who))) {
+ Slog.w(TAG, "Removed dead listener for " + who);
+ } else {
+ Slog.w(TAG, "No dead listeners found");
+ }
}
}
diff --git a/services/core/java/com/android/server/broadcastradio/hal2/RadioEventLogger.java b/services/core/java/com/android/server/broadcastradio/hal2/RadioEventLogger.java
new file mode 100644
index 000000000000..48112c452f02
--- /dev/null
+++ b/services/core/java/com/android/server/broadcastradio/hal2/RadioEventLogger.java
@@ -0,0 +1,44 @@
+/**
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.broadcastradio.hal2;
+
+import android.util.IndentingPrintWriter;
+import android.util.LocalLog;
+import android.util.Log;
+import android.util.Slog;
+
+final class RadioEventLogger {
+ private final String mTag;
+ private final LocalLog mEventLogger;
+
+ RadioEventLogger(String tag, int loggerQueueSize) {
+ mTag = tag;
+ mEventLogger = new LocalLog(loggerQueueSize);
+ }
+
+ void logRadioEvent(String logFormat, Object... args) {
+ String log = String.format(logFormat, args);
+ mEventLogger.log(log);
+ if (Log.isLoggable(mTag, Log.DEBUG)) {
+ Slog.d(mTag, log);
+ }
+ }
+
+ void dump(IndentingPrintWriter pw) {
+ mEventLogger.dump(pw);
+ }
+}
diff --git a/services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java b/services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java
index 852aa66866f0..0a23e385d67a 100644
--- a/services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java
+++ b/services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java
@@ -55,12 +55,14 @@ import java.util.stream.Collectors;
class RadioModule {
private static final String TAG = "BcRadio2Srv.module";
+ private static final int RADIO_EVENT_LOGGER_QUEUE_SIZE = 25;
@NonNull private final IBroadcastRadio mService;
@NonNull public final RadioManager.ModuleProperties mProperties;
private final Object mLock;
@NonNull private final Handler mHandler;
+ @NonNull private final RadioEventLogger mEventLogger;
@GuardedBy("mLock")
private ITunerSession mHalTunerSession;
@@ -138,6 +140,7 @@ class RadioModule {
mService = Objects.requireNonNull(service);
mLock = Objects.requireNonNull(lock);
mHandler = new Handler(Looper.getMainLooper());
+ mEventLogger = new RadioEventLogger(TAG, RADIO_EVENT_LOGGER_QUEUE_SIZE);
}
public static @Nullable RadioModule tryLoadingModule(int idx, @NonNull String fqName,
@@ -176,13 +179,14 @@ class RadioModule {
public @NonNull TunerSession openSession(@NonNull android.hardware.radio.ITunerCallback userCb)
throws RemoteException {
- Slog.i(TAG, "Open TunerSession");
+ mEventLogger.logRadioEvent("Open TunerSession");
synchronized (mLock) {
if (mHalTunerSession == null) {
Mutable<ITunerSession> hwSession = new Mutable<>();
mService.openSession(mHalTunerCallback, (result, session) -> {
Convert.throwOnError("openSession", result);
hwSession.value = session;
+ mEventLogger.logRadioEvent("New HIDL 2.0 tuner session is opened");
});
mHalTunerSession = Objects.requireNonNull(hwSession.value);
}
@@ -207,7 +211,7 @@ class RadioModule {
// Copy the contents of mAidlTunerSessions into a local array because TunerSession.close()
// must be called without mAidlTunerSessions locked because it can call
// onTunerSessionClosed().
- Slog.i(TAG, "Close TunerSessions");
+ mEventLogger.logRadioEvent("Close TunerSessions");
TunerSession[] tunerSessions;
synchronized (mLock) {
tunerSessions = new TunerSession[mAidlTunerSessions.size()];
@@ -320,7 +324,7 @@ class RadioModule {
}
onTunerSessionProgramListFilterChanged(null);
if (mAidlTunerSessions.isEmpty() && mHalTunerSession != null) {
- Slog.i(TAG, "Closing HAL tuner session");
+ mEventLogger.logRadioEvent("Closing HAL tuner session");
try {
mHalTunerSession.close();
} catch (RemoteException ex) {
@@ -372,7 +376,7 @@ class RadioModule {
public android.hardware.radio.ICloseHandle addAnnouncementListener(@NonNull int[] enabledTypes,
@NonNull android.hardware.radio.IAnnouncementListener listener) throws RemoteException {
- Slog.i(TAG, "Add AnnouncementListener");
+ mEventLogger.logRadioEvent("Add AnnouncementListener");
ArrayList<Byte> enabledList = new ArrayList<>();
for (int type : enabledTypes) {
enabledList.add((byte)type);
@@ -409,7 +413,7 @@ class RadioModule {
}
Bitmap getImage(int id) {
- Slog.i(TAG, "Get image for id " + id);
+ mEventLogger.logRadioEvent("Get image for id %d", id);
if (id == 0) throw new IllegalArgumentException("Image ID is missing");
byte[] rawImage;
@@ -449,6 +453,10 @@ class RadioModule {
}
pw.decreaseIndent();
}
+ pw.printf("Radio module events:\n");
+ pw.increaseIndent();
+ mEventLogger.dump(pw);
+ pw.decreaseIndent();
pw.decreaseIndent();
}
}
diff --git a/services/core/java/com/android/server/broadcastradio/hal2/TunerSession.java b/services/core/java/com/android/server/broadcastradio/hal2/TunerSession.java
index 41f753c11216..918dc98e3a9e 100644
--- a/services/core/java/com/android/server/broadcastradio/hal2/TunerSession.java
+++ b/services/core/java/com/android/server/broadcastradio/hal2/TunerSession.java
@@ -28,7 +28,6 @@ import android.hardware.radio.ProgramSelector;
import android.hardware.radio.RadioManager;
import android.os.RemoteException;
import android.util.IndentingPrintWriter;
-import android.util.Log;
import android.util.MutableBoolean;
import android.util.MutableInt;
import android.util.Slog;
@@ -41,8 +40,10 @@ import java.util.Objects;
class TunerSession extends ITuner.Stub {
private static final String TAG = "BcRadio2Srv.session";
private static final String kAudioDeviceName = "Radio tuner source";
+ private static final int TUNER_EVENT_LOGGER_QUEUE_SIZE = 25;
private final Object mLock;
+ @NonNull private final RadioEventLogger mEventLogger;
private final RadioModule mModule;
private final ITunerSession mHwSession;
@@ -61,15 +62,12 @@ class TunerSession extends ITuner.Stub {
mHwSession = Objects.requireNonNull(hwSession);
mCallback = Objects.requireNonNull(callback);
mLock = Objects.requireNonNull(lock);
- }
-
- private boolean isDebugEnabled() {
- return Log.isLoggable(TAG, Log.DEBUG);
+ mEventLogger = new RadioEventLogger(TAG, TUNER_EVENT_LOGGER_QUEUE_SIZE);
}
@Override
public void close() {
- if (isDebugEnabled()) Slog.d(TAG, "Close");
+ mEventLogger.logRadioEvent("Close");
close(null);
}
@@ -81,7 +79,7 @@ class TunerSession extends ITuner.Stub {
* @param error Optional error to send to client before session is closed.
*/
public void close(@Nullable Integer error) {
- if (isDebugEnabled()) Slog.d(TAG, "Close on error " + error);
+ mEventLogger.logRadioEvent("Close on error %d", error);
synchronized (mLock) {
if (mIsClosed) return;
if (error != null) {
@@ -145,10 +143,8 @@ class TunerSession extends ITuner.Stub {
@Override
public void step(boolean directionDown, boolean skipSubChannel) throws RemoteException {
- if (isDebugEnabled()) {
- Slog.d(TAG, "Step with directionDown " + directionDown
- + " skipSubChannel " + skipSubChannel);
- }
+ mEventLogger.logRadioEvent("Step with direction %s, skipSubChannel? %s",
+ directionDown ? "down" : "up", skipSubChannel ? "yes" : "no");
synchronized (mLock) {
checkNotClosedLocked();
int halResult = mHwSession.step(!directionDown);
@@ -158,10 +154,8 @@ class TunerSession extends ITuner.Stub {
@Override
public void scan(boolean directionDown, boolean skipSubChannel) throws RemoteException {
- if (isDebugEnabled()) {
- Slog.d(TAG, "Scan with directionDown " + directionDown
- + " skipSubChannel " + skipSubChannel);
- }
+ mEventLogger.logRadioEvent("Scan with direction %s, skipSubChannel? %s",
+ directionDown ? "down" : "up", skipSubChannel ? "yes" : "no");
synchronized (mLock) {
checkNotClosedLocked();
int halResult = mHwSession.scan(!directionDown, skipSubChannel);
@@ -171,7 +165,7 @@ class TunerSession extends ITuner.Stub {
@Override
public void tune(ProgramSelector selector) throws RemoteException {
- if (isDebugEnabled()) Slog.d(TAG, "Tune with selector " + selector);
+ mEventLogger.logRadioEvent("Tune with selector %s", selector);
synchronized (mLock) {
checkNotClosedLocked();
int halResult = mHwSession.tune(Convert.programSelectorToHal(selector));
@@ -195,7 +189,7 @@ class TunerSession extends ITuner.Stub {
@Override
public Bitmap getImage(int id) {
- if (isDebugEnabled()) Slog.d(TAG, "Get image for " + id);
+ mEventLogger.logRadioEvent("Get image for %d", id);
return mModule.getImage(id);
}
@@ -208,7 +202,7 @@ class TunerSession extends ITuner.Stub {
@Override
public void startProgramListUpdates(ProgramList.Filter filter) throws RemoteException {
- if (isDebugEnabled()) Slog.d(TAG, "start programList updates " + filter);
+ mEventLogger.logRadioEvent("start programList updates %s", filter);
// If the AIDL client provides a null filter, it wants all updates, so use the most broad
// filter.
if (filter == null) {
@@ -267,7 +261,7 @@ class TunerSession extends ITuner.Stub {
@Override
public void stopProgramListUpdates() throws RemoteException {
- if (isDebugEnabled()) Slog.d(TAG, "Stop programList updates");
+ mEventLogger.logRadioEvent("Stop programList updates");
synchronized (mLock) {
checkNotClosedLocked();
mProgramInfoCache = null;
@@ -291,7 +285,7 @@ class TunerSession extends ITuner.Stub {
@Override
public boolean isConfigFlagSet(int flag) {
- if (isDebugEnabled()) Slog.d(TAG, "Is ConfigFlagSet for " + ConfigFlag.toString(flag));
+ mEventLogger.logRadioEvent("Is ConfigFlagSet for %s", ConfigFlag.toString(flag));
synchronized (mLock) {
checkNotClosedLocked();
@@ -313,9 +307,7 @@ class TunerSession extends ITuner.Stub {
@Override
public void setConfigFlag(int flag, boolean value) throws RemoteException {
- if (isDebugEnabled()) {
- Slog.d(TAG, "Set ConfigFlag " + ConfigFlag.toString(flag) + " = " + value);
- }
+ mEventLogger.logRadioEvent("Set ConfigFlag %s = %b", ConfigFlag.toString(flag), value);
synchronized (mLock) {
checkNotClosedLocked();
int halResult = mHwSession.setConfigFlag(flag, value);
@@ -351,6 +343,10 @@ class TunerSession extends ITuner.Stub {
pw.printf("ProgramInfoCache: %s\n", mProgramInfoCache);
pw.printf("Config: %s\n", mDummyConfig);
}
+ pw.printf("Tuner session events:\n");
+ pw.increaseIndent();
+ mEventLogger.dump(pw);
+ pw.decreaseIndent();
pw.decreaseIndent();
}
}
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index 9691b20066c7..98238ccd93c3 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -4040,6 +4040,7 @@ public class Vpn {
mConfig.proxyInfo = profile.proxy;
mConfig.requiresInternetValidation = profile.requiresInternetValidation;
mConfig.excludeLocalRoutes = profile.excludeLocalRoutes;
+ mConfig.allowBypass = profile.isBypassable;
switch (profile.type) {
case VpnProfile.TYPE_IKEV2_IPSEC_USER_PASS:
diff --git a/services/core/java/com/android/server/display/BrightnessMappingStrategy.java b/services/core/java/com/android/server/display/BrightnessMappingStrategy.java
index c835d2fe1bbd..25d0752844fd 100644
--- a/services/core/java/com/android/server/display/BrightnessMappingStrategy.java
+++ b/services/core/java/com/android/server/display/BrightnessMappingStrategy.java
@@ -116,10 +116,8 @@ public abstract class BrightnessMappingStrategy {
luxLevels = getLuxLevels(resources.getIntArray(
com.android.internal.R.array.config_autoBrightnessLevelsIdle));
} else {
- brightnessLevelsNits = getFloatArray(resources.obtainTypedArray(
- com.android.internal.R.array.config_autoBrightnessDisplayValuesNits));
- luxLevels = getLuxLevels(resources.getIntArray(
- com.android.internal.R.array.config_autoBrightnessLevels));
+ brightnessLevelsNits = displayDeviceConfig.getAutoBrightnessBrighteningLevelsNits();
+ luxLevels = displayDeviceConfig.getAutoBrightnessBrighteningLevelsLux();
}
// Display independent, mode independent values
diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
index 4f3fd6409cd8..3b627ef6a786 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
@@ -20,6 +20,7 @@ import android.annotation.NonNull;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
+import android.content.res.TypedArray;
import android.hardware.display.DisplayManagerInternal;
import android.hardware.display.DisplayManagerInternal.RefreshRateLimitation;
import android.os.Environment;
@@ -149,12 +150,22 @@ import javax.xml.datatype.DatatypeConfigurationException;
* </quirks>
*
* <autoBrightness>
- * <brighteningLightDebounceMillis>
+ * <brighteningLightDebounceMillis>
* 2000
- * </brighteningLightDebounceMillis>
+ * </brighteningLightDebounceMillis>
* <darkeningLightDebounceMillis>
* 1000
* </darkeningLightDebounceMillis>
+ * <displayBrightnessMapping>
+ * <displayBrightnessPoint>
+ * <lux>50</lux>
+ * <nits>45</nits>
+ * </displayBrightnessPoint>
+ * <displayBrightnessPoint>
+ * <lux>80</lux>
+ * <nits>75</nits>
+ * </displayBrightnessPoint>
+ * </displayBrightnessMapping>
* </autoBrightness>
*
* <screenBrightnessRampFastDecrease>0.01</screenBrightnessRampFastDecrease>
@@ -268,6 +279,39 @@ public class DisplayDeviceConfig {
// for the corresponding values above
private float[] mBrightness;
+
+ /**
+ * Array of desired screen brightness in nits corresponding to the lux values
+ * in the mBrightnessLevelsLux array. The display brightness is defined as the
+ * measured brightness of an all-white image. The brightness values must be non-negative and
+ * non-decreasing. This must be overridden in platform specific overlays
+ */
+ private float[] mBrightnessLevelsNits;
+
+ /**
+ * Array of light sensor lux values to define our levels for auto backlight
+ * brightness support.
+ * The N entries of this array define N + 1 control points as follows:
+ * (1-based arrays)
+ *
+ * Point 1: (0, value[1]): lux <= 0
+ * Point 2: (level[1], value[2]): 0 < lux <= level[1]
+ * Point 3: (level[2], value[3]): level[2] < lux <= level[3]
+ * ...
+ * Point N+1: (level[N], value[N+1]): level[N] < lux
+ *
+ * The control points must be strictly increasing. Each control point
+ * corresponds to an entry in the brightness backlight values arrays.
+ * For example, if lux == level[1] (first element of the levels array)
+ * then the brightness will be determined by value[2] (second element
+ * of the brightness values array).
+ *
+ * Spline interpolation is used to determine the auto-brightness
+ * backlight values for lux levels between these control points.
+ *
+ */
+ private float[] mBrightnessLevelsLux;
+
private float mBacklightMinimum = Float.NaN;
private float mBacklightMaximum = Float.NaN;
private float mBrightnessDefault = Float.NaN;
@@ -661,6 +705,20 @@ public class DisplayDeviceConfig {
return mAutoBrightnessBrighteningLightDebounce;
}
+ /**
+ * @return Auto brightness brightening ambient lux levels
+ */
+ public float[] getAutoBrightnessBrighteningLevelsLux() {
+ return mBrightnessLevelsLux;
+ }
+
+ /**
+ * @return Auto brightness brightening nits levels
+ */
+ public float[] getAutoBrightnessBrighteningLevelsNits() {
+ return mBrightnessLevelsNits;
+ }
+
@Override
public String toString() {
return "DisplayDeviceConfig{"
@@ -703,6 +761,8 @@ public class DisplayDeviceConfig {
+ mAutoBrightnessBrighteningLightDebounce
+ ", mAutoBrightnessDarkeningLightDebounce= "
+ mAutoBrightnessDarkeningLightDebounce
+ + ", mBrightnessLevelsLux= " + Arrays.toString(mBrightnessLevelsLux)
+ + ", mBrightnessLevelsNits= " + Arrays.toString(mBrightnessLevelsNits)
+ "}";
}
@@ -779,6 +839,7 @@ public class DisplayDeviceConfig {
loadBrightnessRampsFromConfigXml();
loadAmbientLightSensorFromConfigXml();
setProxSensorUnspecified();
+ loadAutoBrightnessConfigsFromConfigXml();
mLoadedFrom = "<config.xml>";
}
@@ -991,6 +1052,7 @@ public class DisplayDeviceConfig {
private void loadAutoBrightnessConfigValues(DisplayConfiguration config) {
loadAutoBrightnessBrighteningLightDebounce(config.getAutoBrightness());
loadAutoBrightnessDarkeningLightDebounce(config.getAutoBrightness());
+ loadAutoBrightnessDisplayBrightnessMapping(config.getAutoBrightness());
}
/**
@@ -1023,6 +1085,33 @@ public class DisplayDeviceConfig {
}
}
+ /**
+ * Loads the auto-brightness display brightness mappings. Internally, this takes care of
+ * loading the value from the display config, and if not present, falls back to config.xml.
+ */
+ private void loadAutoBrightnessDisplayBrightnessMapping(AutoBrightness autoBrightnessConfig) {
+ if (autoBrightnessConfig == null
+ || autoBrightnessConfig.getDisplayBrightnessMapping() == null) {
+ mBrightnessLevelsNits = getFloatArray(mContext.getResources()
+ .obtainTypedArray(com.android.internal.R.array
+ .config_autoBrightnessDisplayValuesNits));
+ mBrightnessLevelsLux = getFloatArray(mContext.getResources()
+ .obtainTypedArray(com.android.internal.R.array
+ .config_autoBrightnessLevels));
+ } else {
+ final int size = autoBrightnessConfig.getDisplayBrightnessMapping()
+ .getDisplayBrightnessPoint().size();
+ mBrightnessLevelsNits = new float[size];
+ mBrightnessLevelsLux = new float[size];
+ for (int i = 0; i < size; i++) {
+ mBrightnessLevelsNits[i] = autoBrightnessConfig.getDisplayBrightnessMapping()
+ .getDisplayBrightnessPoint().get(i).getNits().floatValue();
+ mBrightnessLevelsLux[i] = autoBrightnessConfig.getDisplayBrightnessMapping()
+ .getDisplayBrightnessPoint().get(i).getLux().floatValue();
+ }
+ }
+ }
+
private void loadBrightnessMapFromConfigXml() {
// Use the config.xml mapping
final Resources res = mContext.getResources();
@@ -1248,6 +1337,10 @@ public class DisplayDeviceConfig {
com.android.internal.R.string.config_displayLightSensorType);
}
+ private void loadAutoBrightnessConfigsFromConfigXml() {
+ loadAutoBrightnessDisplayBrightnessMapping(null /*AutoBrightnessConfig*/);
+ }
+
private void loadAmbientLightSensorFromDdc(DisplayConfiguration config) {
final SensorDetails sensorDetails = config.getLightSensor();
if (sensorDetails != null) {
@@ -1390,6 +1483,22 @@ public class DisplayDeviceConfig {
}
}
+ /**
+ * Extracts a float array from the specified {@link TypedArray}.
+ *
+ * @param array The array to convert.
+ * @return the given array as a float array.
+ */
+ public static float[] getFloatArray(TypedArray array) {
+ final int n = array.length();
+ float[] vals = new float[n];
+ for (int i = 0; i < n; i++) {
+ vals[i] = array.getFloat(i, PowerManager.BRIGHTNESS_OFF_FLOAT);
+ }
+ array.recycle();
+ return vals;
+ }
+
static class SensorData {
public String type;
public String name;
diff --git a/services/core/java/com/android/server/notification/ZenModeFiltering.java b/services/core/java/com/android/server/notification/ZenModeFiltering.java
index 7e36aed81d4a..db0ce2ef6fe2 100644
--- a/services/core/java/com/android/server/notification/ZenModeFiltering.java
+++ b/services/core/java/com/android/server/notification/ZenModeFiltering.java
@@ -149,8 +149,13 @@ public class ZenModeFiltering {
*/
public boolean shouldIntercept(int zen, NotificationManager.Policy policy,
NotificationRecord record) {
- // Zen mode is ignored for critical notifications.
- if (zen == ZEN_MODE_OFF || isCritical(record)) {
+ if (zen == ZEN_MODE_OFF) {
+ return false;
+ }
+
+ if (isCritical(record)) {
+ // Zen mode is ignored for critical notifications.
+ ZenLog.traceNotIntercepted(record, "criticalNotification");
return false;
}
// Make an exception to policy for the notification saying that policy has changed
@@ -168,6 +173,7 @@ public class ZenModeFiltering {
case Global.ZEN_MODE_ALARMS:
if (isAlarm(record)) {
// Alarms only
+ ZenLog.traceNotIntercepted(record, "alarm");
return false;
}
ZenLog.traceIntercepted(record, "alarmsOnly");
@@ -184,6 +190,7 @@ public class ZenModeFiltering {
ZenLog.traceIntercepted(record, "!allowAlarms");
return true;
}
+ ZenLog.traceNotIntercepted(record, "allowedAlarm");
return false;
}
if (isEvent(record)) {
@@ -191,6 +198,7 @@ public class ZenModeFiltering {
ZenLog.traceIntercepted(record, "!allowEvents");
return true;
}
+ ZenLog.traceNotIntercepted(record, "allowedEvent");
return false;
}
if (isReminder(record)) {
@@ -198,6 +206,7 @@ public class ZenModeFiltering {
ZenLog.traceIntercepted(record, "!allowReminders");
return true;
}
+ ZenLog.traceNotIntercepted(record, "allowedReminder");
return false;
}
if (isMedia(record)) {
@@ -205,6 +214,7 @@ public class ZenModeFiltering {
ZenLog.traceIntercepted(record, "!allowMedia");
return true;
}
+ ZenLog.traceNotIntercepted(record, "allowedMedia");
return false;
}
if (isSystem(record)) {
@@ -212,6 +222,7 @@ public class ZenModeFiltering {
ZenLog.traceIntercepted(record, "!allowSystem");
return true;
}
+ ZenLog.traceNotIntercepted(record, "allowedSystem");
return false;
}
if (isConversation(record)) {
@@ -253,6 +264,7 @@ public class ZenModeFiltering {
ZenLog.traceIntercepted(record, "!priority");
return true;
default:
+ ZenLog.traceNotIntercepted(record, "unknownZenMode");
return false;
}
}
@@ -271,10 +283,12 @@ public class ZenModeFiltering {
}
private static boolean shouldInterceptAudience(int source, NotificationRecord record) {
- if (!audienceMatches(source, record.getContactAffinity())) {
- ZenLog.traceIntercepted(record, "!audienceMatches");
+ float affinity = record.getContactAffinity();
+ if (!audienceMatches(source, affinity)) {
+ ZenLog.traceIntercepted(record, "!audienceMatches,affinity=" + affinity);
return true;
}
+ ZenLog.traceNotIntercepted(record, "affinity=" + affinity);
return false;
}
diff --git a/services/core/java/com/android/server/power/Notifier.java b/services/core/java/com/android/server/power/Notifier.java
index dad9584c6722..5a2fb18673ac 100644
--- a/services/core/java/com/android/server/power/Notifier.java
+++ b/services/core/java/com/android/server/power/Notifier.java
@@ -571,8 +571,7 @@ public class Notifier {
/**
* Called when there has been user activity.
*/
- public void onUserActivity(int displayGroupId, @PowerManager.UserActivityEvent int event,
- int uid) {
+ public void onUserActivity(int displayGroupId, int event, int uid) {
if (DEBUG) {
Slog.d(TAG, "onUserActivity: event=" + event + ", uid=" + uid);
}
diff --git a/services/core/java/com/android/server/power/PowerGroup.java b/services/core/java/com/android/server/power/PowerGroup.java
index 9fe53fbfaf25..fec61ac8f2cf 100644
--- a/services/core/java/com/android/server/power/PowerGroup.java
+++ b/services/core/java/com/android/server/power/PowerGroup.java
@@ -74,8 +74,6 @@ public class PowerGroup {
private long mLastPowerOnTime;
private long mLastUserActivityTime;
private long mLastUserActivityTimeNoChangeLights;
- @PowerManager.UserActivityEvent
- private int mLastUserActivityEvent;
/** Timestamp (milliseconds since boot) of the last time the power group was awoken.*/
private long mLastWakeTime;
/** Timestamp (milliseconds since boot) of the last time the power group was put to sleep. */
@@ -246,7 +244,7 @@ public class PowerGroup {
return true;
}
- boolean dozeLocked(long eventTime, int uid, @PowerManager.GoToSleepReason int reason) {
+ boolean dozeLocked(long eventTime, int uid, int reason) {
if (eventTime < getLastWakeTimeLocked() || !isInteractive(mWakefulness)) {
return false;
}
@@ -255,14 +253,9 @@ public class PowerGroup {
try {
reason = Math.min(PowerManager.GO_TO_SLEEP_REASON_MAX,
Math.max(reason, PowerManager.GO_TO_SLEEP_REASON_MIN));
- long millisSinceLastUserActivity = eventTime - Math.max(
- mLastUserActivityTimeNoChangeLights, mLastUserActivityTime);
Slog.i(TAG, "Powering off display group due to "
- + PowerManager.sleepReasonToString(reason)
- + " (groupId= " + getGroupId() + ", uid= " + uid
- + ", millisSinceLastUserActivity=" + millisSinceLastUserActivity
- + ", lastUserActivityEvent=" + PowerManager.userActivityEventToString(
- mLastUserActivityEvent) + ")...");
+ + PowerManager.sleepReasonToString(reason) + " (groupId= " + getGroupId()
+ + ", uid= " + uid + ")...");
setSandmanSummonedLocked(/* isSandmanSummoned= */ true);
setWakefulnessLocked(WAKEFULNESS_DOZING, eventTime, uid, reason, /* opUid= */ 0,
@@ -273,16 +266,14 @@ public class PowerGroup {
return true;
}
- boolean sleepLocked(long eventTime, int uid, @PowerManager.GoToSleepReason int reason) {
+ boolean sleepLocked(long eventTime, int uid, int reason) {
if (eventTime < mLastWakeTime || getWakefulnessLocked() == WAKEFULNESS_ASLEEP) {
return false;
}
Trace.traceBegin(Trace.TRACE_TAG_POWER, "sleepPowerGroup");
try {
- Slog.i(TAG,
- "Sleeping power group (groupId=" + getGroupId() + ", uid=" + uid + ", reason="
- + PowerManager.sleepReasonToString(reason) + ")...");
+ Slog.i(TAG, "Sleeping power group (groupId=" + getGroupId() + ", uid=" + uid + ")...");
setSandmanSummonedLocked(/* isSandmanSummoned= */ true);
setWakefulnessLocked(WAKEFULNESS_ASLEEP, eventTime, uid, reason, /* opUid= */0,
/* opPackageName= */ null, /* details= */ null);
@@ -296,20 +287,16 @@ public class PowerGroup {
return mLastUserActivityTime;
}
- void setLastUserActivityTimeLocked(long lastUserActivityTime,
- @PowerManager.UserActivityEvent int event) {
+ void setLastUserActivityTimeLocked(long lastUserActivityTime) {
mLastUserActivityTime = lastUserActivityTime;
- mLastUserActivityEvent = event;
}
public long getLastUserActivityTimeNoChangeLightsLocked() {
return mLastUserActivityTimeNoChangeLights;
}
- public void setLastUserActivityTimeNoChangeLightsLocked(long time,
- @PowerManager.UserActivityEvent int event) {
+ public void setLastUserActivityTimeNoChangeLightsLocked(long time) {
mLastUserActivityTimeNoChangeLights = time;
- mLastUserActivityEvent = event;
}
public int getUserActivitySummaryLocked() {
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index bc93cb30e449..dbf05f1cd7c7 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -1169,7 +1169,6 @@ public final class PowerManagerService extends SystemService
return;
}
- Slog.i(TAG, "onFlip(): Face " + (isFaceDown ? "down." : "up."));
mIsFaceDown = isFaceDown;
if (isFaceDown) {
final long currentTime = mClock.uptimeMillis();
@@ -1889,13 +1888,12 @@ public final class PowerManagerService extends SystemService
// Called from native code.
@SuppressWarnings("unused")
- private void userActivityFromNative(long eventTime, @PowerManager.UserActivityEvent int event,
- int displayId, int flags) {
+ private void userActivityFromNative(long eventTime, int event, int displayId, int flags) {
userActivityInternal(displayId, eventTime, event, flags, Process.SYSTEM_UID);
}
- private void userActivityInternal(int displayId, long eventTime,
- @PowerManager.UserActivityEvent int event, int flags, int uid) {
+ private void userActivityInternal(int displayId, long eventTime, int event, int flags,
+ int uid) {
synchronized (mLock) {
if (displayId == Display.INVALID_DISPLAY) {
if (userActivityNoUpdateLocked(eventTime, event, flags, uid)) {
@@ -1946,12 +1944,11 @@ public final class PowerManagerService extends SystemService
@GuardedBy("mLock")
private boolean userActivityNoUpdateLocked(final PowerGroup powerGroup, long eventTime,
- @PowerManager.UserActivityEvent int event, int flags, int uid) {
+ int event, int flags, int uid) {
final int groupId = powerGroup.getGroupId();
if (DEBUG_SPEW) {
Slog.d(TAG, "userActivityNoUpdateLocked: groupId=" + groupId
- + ", eventTime=" + eventTime
- + ", event=" + PowerManager.userActivityEventToString(event)
+ + ", eventTime=" + eventTime + ", event=" + event
+ ", flags=0x" + Integer.toHexString(flags) + ", uid=" + uid);
}
@@ -1986,7 +1983,7 @@ public final class PowerManagerService extends SystemService
if ((flags & PowerManager.USER_ACTIVITY_FLAG_NO_CHANGE_LIGHTS) != 0) {
if (eventTime > powerGroup.getLastUserActivityTimeNoChangeLightsLocked()
&& eventTime > powerGroup.getLastUserActivityTimeLocked()) {
- powerGroup.setLastUserActivityTimeNoChangeLightsLocked(eventTime, event);
+ powerGroup.setLastUserActivityTimeNoChangeLightsLocked(eventTime);
mDirty |= DIRTY_USER_ACTIVITY;
if (event == PowerManager.USER_ACTIVITY_EVENT_BUTTON) {
mDirty |= DIRTY_QUIESCENT;
@@ -1996,7 +1993,7 @@ public final class PowerManagerService extends SystemService
}
} else {
if (eventTime > powerGroup.getLastUserActivityTimeLocked()) {
- powerGroup.setLastUserActivityTimeLocked(eventTime, event);
+ powerGroup.setLastUserActivityTimeLocked(eventTime);
mDirty |= DIRTY_USER_ACTIVITY;
if (event == PowerManager.USER_ACTIVITY_EVENT_BUTTON) {
mDirty |= DIRTY_QUIESCENT;
@@ -2023,8 +2020,7 @@ public final class PowerManagerService extends SystemService
@WakeReason int reason, String details, int uid, String opPackageName, int opUid) {
if (DEBUG_SPEW) {
Slog.d(TAG, "wakePowerGroupLocked: eventTime=" + eventTime
- + ", groupId=" + powerGroup.getGroupId()
- + ", reason=" + PowerManager.wakeReasonToString(reason) + ", uid=" + uid);
+ + ", groupId=" + powerGroup.getGroupId() + ", uid=" + uid);
}
if (mForceSuspendActive || !mSystemReady) {
return;
@@ -2047,11 +2043,11 @@ public final class PowerManagerService extends SystemService
@GuardedBy("mLock")
private boolean dozePowerGroupLocked(final PowerGroup powerGroup, long eventTime,
- @GoToSleepReason int reason, int uid) {
+ int reason, int uid) {
if (DEBUG_SPEW) {
Slog.d(TAG, "dozePowerGroup: eventTime=" + eventTime
- + ", groupId=" + powerGroup.getGroupId()
- + ", reason=" + PowerManager.sleepReasonToString(reason) + ", uid=" + uid);
+ + ", groupId=" + powerGroup.getGroupId() + ", reason=" + reason
+ + ", uid=" + uid);
}
if (!mSystemReady || !mBootCompleted) {
@@ -2062,12 +2058,10 @@ public final class PowerManagerService extends SystemService
}
@GuardedBy("mLock")
- private boolean sleepPowerGroupLocked(final PowerGroup powerGroup, long eventTime,
- @GoToSleepReason int reason, int uid) {
+ private boolean sleepPowerGroupLocked(final PowerGroup powerGroup, long eventTime, int reason,
+ int uid) {
if (DEBUG_SPEW) {
- Slog.d(TAG, "sleepPowerGroup: eventTime=" + eventTime
- + ", groupId=" + powerGroup.getGroupId()
- + ", reason=" + PowerManager.sleepReasonToString(reason) + ", uid=" + uid);
+ Slog.d(TAG, "sleepPowerGroup: eventTime=" + eventTime + ", uid=" + uid);
}
if (!mBootCompleted || !mSystemReady) {
return false;
@@ -2128,10 +2122,7 @@ public final class PowerManagerService extends SystemService
case WAKEFULNESS_DOZING:
traceMethodName = "goToSleep";
Slog.i(TAG, "Going to sleep due to " + PowerManager.sleepReasonToString(reason)
- + " (uid " + uid + ", screenOffTimeout=" + mScreenOffTimeoutSetting
- + ", activityTimeoutWM=" + mUserActivityTimeoutOverrideFromWindowManager
- + ", maxDimRatio=" + mMaximumScreenDimRatioConfig
- + ", maxDimDur=" + mMaximumScreenDimDurationConfig + ")...");
+ + " (uid " + uid + ")...");
mLastGlobalSleepTime = eventTime;
mLastGlobalSleepReason = reason;
@@ -4216,7 +4207,7 @@ public final class PowerManagerService extends SystemService
void onUserActivity() {
synchronized (mLock) {
mPowerGroups.get(Display.DEFAULT_DISPLAY_GROUP).setLastUserActivityTimeLocked(
- mClock.uptimeMillis(), PowerManager.USER_ACTIVITY_EVENT_OTHER);
+ mClock.uptimeMillis());
}
}
@@ -5599,8 +5590,7 @@ public final class PowerManagerService extends SystemService
}
@Override // Binder call
- public void userActivity(int displayId, long eventTime,
- @PowerManager.UserActivityEvent int event, int flags) {
+ public void userActivity(int displayId, long eventTime, int event, int flags) {
final long now = mClock.uptimeMillis();
if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER)
!= PackageManager.PERMISSION_GRANTED
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 635cf0e61685..a174c54eae98 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -313,7 +313,6 @@ import android.util.TypedXmlPullParser;
import android.util.TypedXmlSerializer;
import android.util.proto.ProtoOutputStream;
import android.view.AppTransitionAnimationSpec;
-import android.view.DisplayCutout;
import android.view.DisplayInfo;
import android.view.IAppTransitionAnimationSpecsFuture;
import android.view.InputApplicationHandle;
@@ -357,6 +356,7 @@ import com.android.server.wm.ActivityMetricsLogger.TransitionInfoSnapshot;
import com.android.server.wm.SurfaceAnimator.AnimationType;
import com.android.server.wm.WindowManagerService.H;
import com.android.server.wm.utils.InsetUtils;
+import com.android.server.wm.utils.WmDisplayCutout;
import dalvik.annotation.optimization.NeverCompile;
@@ -9694,9 +9694,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
final boolean rotated = (rotation == ROTATION_90 || rotation == ROTATION_270);
final int dw = rotated ? display.mBaseDisplayHeight : display.mBaseDisplayWidth;
final int dh = rotated ? display.mBaseDisplayWidth : display.mBaseDisplayHeight;
- final DisplayCutout cutout = display.calculateDisplayCutoutForRotation(rotation)
- .getDisplayCutout();
- policy.getNonDecorInsetsLw(rotation, cutout, mNonDecorInsets[rotation]);
+ final WmDisplayCutout cutout = display.calculateDisplayCutoutForRotation(rotation);
+ policy.getNonDecorInsetsLw(rotation, dw, dh, cutout, mNonDecorInsets[rotation]);
mStableInsets[rotation].set(mNonDecorInsets[rotation]);
policy.convertNonDecorInsetsToStableInsets(mStableInsets[rotation], rotation);
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index d4bbc86c4850..31f87416db67 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -226,6 +226,7 @@ import android.view.IWindowFocusObserver;
import android.view.RemoteAnimationAdapter;
import android.view.RemoteAnimationDefinition;
import android.view.WindowManager;
+import android.window.BackAnimationAdaptor;
import android.window.BackNavigationInfo;
import android.window.IWindowOrganizerController;
import android.window.SplashScreenView.SplashScreenViewParcelable;
@@ -457,7 +458,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
private final ClientLifecycleManager mLifecycleManager;
@Nullable
- private final BackNavigationController mBackNavigationController;
+ final BackNavigationController mBackNavigationController;
private TaskChangeNotificationController mTaskChangeNotificationController;
/** The controller for all operations related to locktask. */
@@ -1836,13 +1837,14 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
@Override
public BackNavigationInfo startBackNavigation(boolean requestAnimation,
- IWindowFocusObserver observer) {
+ IWindowFocusObserver observer, BackAnimationAdaptor backAnimationAdaptor) {
mAmInternal.enforceCallingPermission(START_TASKS_FROM_RECENTS,
"startBackNavigation()");
if (mBackNavigationController == null) {
return null;
}
- return mBackNavigationController.startBackNavigation(requestAnimation, observer);
+ return mBackNavigationController.startBackNavigation(
+ requestAnimation, observer, backAnimationAdaptor);
}
/**
diff --git a/services/core/java/com/android/server/wm/BLASTSyncEngine.java b/services/core/java/com/android/server/wm/BLASTSyncEngine.java
index 9a94a4f54b61..d3452277a29f 100644
--- a/services/core/java/com/android/server/wm/BLASTSyncEngine.java
+++ b/services/core/java/com/android/server/wm/BLASTSyncEngine.java
@@ -22,6 +22,7 @@ import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_SYNC_ENGINE;
import static com.android.server.wm.WindowState.BLAST_TIMEOUT_DURATION;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.os.Trace;
import android.util.ArraySet;
import android.util.Slog;
@@ -63,6 +64,15 @@ import java.util.ArrayList;
class BLASTSyncEngine {
private static final String TAG = "BLASTSyncEngine";
+ /** No specific method. Used by override specifiers. */
+ public static final int METHOD_UNDEFINED = -1;
+
+ /** No sync method. Apps will draw/present internally and just report. */
+ public static final int METHOD_NONE = 0;
+
+ /** Sync with BLAST. Apps will draw and then send the buffer to be applied in sync. */
+ public static final int METHOD_BLAST = 1;
+
interface TransactionReadyListener {
void onTransactionReady(int mSyncId, SurfaceControl.Transaction transaction);
}
@@ -85,6 +95,7 @@ class BLASTSyncEngine {
*/
class SyncGroup {
final int mSyncId;
+ final int mSyncMethod;
final TransactionReadyListener mListener;
final Runnable mOnTimeout;
boolean mReady = false;
@@ -92,8 +103,9 @@ class BLASTSyncEngine {
private SurfaceControl.Transaction mOrphanTransaction = null;
private String mTraceName;
- private SyncGroup(TransactionReadyListener listener, int id, String name) {
+ private SyncGroup(TransactionReadyListener listener, int id, String name, int method) {
mSyncId = id;
+ mSyncMethod = method;
mListener = listener;
mOnTimeout = () -> {
Slog.w(TAG, "Sync group " + mSyncId + " timeout");
@@ -271,16 +283,13 @@ class BLASTSyncEngine {
* Prepares a {@link SyncGroup} that is not active yet. Caller must call {@link #startSyncSet}
* before calling {@link #addToSyncSet(int, WindowContainer)} on any {@link WindowContainer}.
*/
- SyncGroup prepareSyncSet(TransactionReadyListener listener, String name) {
- return new SyncGroup(listener, mNextSyncId++, name);
+ SyncGroup prepareSyncSet(TransactionReadyListener listener, String name, int method) {
+ return new SyncGroup(listener, mNextSyncId++, name, method);
}
- int startSyncSet(TransactionReadyListener listener) {
- return startSyncSet(listener, BLAST_TIMEOUT_DURATION, "");
- }
-
- int startSyncSet(TransactionReadyListener listener, long timeoutMs, String name) {
- final SyncGroup s = prepareSyncSet(listener, name);
+ int startSyncSet(TransactionReadyListener listener, long timeoutMs, String name,
+ int method) {
+ final SyncGroup s = prepareSyncSet(listener, name, method);
startSyncSet(s, timeoutMs);
return s.mSyncId;
}
@@ -302,6 +311,11 @@ class BLASTSyncEngine {
scheduleTimeout(s, timeoutMs);
}
+ @Nullable
+ SyncGroup getSyncSet(int id) {
+ return mActiveSyncs.get(id);
+ }
+
boolean hasActiveSync() {
return mActiveSyncs.size() != 0;
}
diff --git a/services/core/java/com/android/server/wm/BackNaviAnimationController.java b/services/core/java/com/android/server/wm/BackNaviAnimationController.java
new file mode 100644
index 000000000000..ecc7534ad386
--- /dev/null
+++ b/services/core/java/com/android/server/wm/BackNaviAnimationController.java
@@ -0,0 +1,418 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static android.app.ActivityTaskManager.INVALID_TASK_ID;
+import static android.view.RemoteAnimationTarget.MODE_CLOSING;
+import static android.view.RemoteAnimationTarget.MODE_OPENING;
+import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
+
+import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
+import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_RECENTS;
+
+import android.annotation.NonNull;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.util.Slog;
+import android.util.proto.ProtoOutputStream;
+import android.view.RemoteAnimationTarget;
+import android.view.SurfaceControl;
+import android.view.WindowInsets;
+import android.window.BackNavigationInfo;
+import android.window.IBackAnimationRunner;
+import android.window.IBackNaviAnimationController;
+
+import com.android.server.wm.utils.InsetUtils;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+
+/**
+ * Controls the back navigation animation.
+ * This is throw-away code and should only be used for Android T, most code is duplicated from
+ * RecentsAnimationController which should be stable to handle animation leash resources/flicker/
+ * fixed rotation, etc. Remove this class at U and migrate to shell transition.
+ */
+public class BackNaviAnimationController implements IBinder.DeathRecipient {
+ private static final String TAG = BackNavigationController.TAG;
+ // Constant for a yet-to-be-calculated {@link RemoteAnimationTarget#Mode} state
+ private static final int MODE_UNKNOWN = -1;
+
+ // The activity which host this animation
+ private ActivityRecord mTargetActivityRecord;
+ // The original top activity
+ private ActivityRecord mTopActivity;
+
+ private final DisplayContent mDisplayContent;
+ private final WindowManagerService mWindowManagerService;
+ private final BackNavigationController mBackNavigationController;
+
+ // We start the BackAnimationController in a pending-start state since we need to wait for
+ // the wallpaper/activity to draw before we can give control to the handler to start animating
+ // the visible task surfaces
+ private boolean mPendingStart;
+ private IBackAnimationRunner mRunner;
+ final IBackNaviAnimationController mRemoteController;
+ private boolean mLinkedToDeathOfRunner;
+
+ private final ArrayList<TaskAnimationAdapter> mPendingAnimations = new ArrayList<>();
+
+ BackNaviAnimationController(IBackAnimationRunner runner,
+ BackNavigationController backNavigationController, int displayId) {
+ mRunner = runner;
+ mBackNavigationController = backNavigationController;
+ mWindowManagerService = mBackNavigationController.mWindowManagerService;
+ mDisplayContent = mWindowManagerService.mRoot.getDisplayContent(displayId);
+
+ mRemoteController = new IBackNaviAnimationController.Stub() {
+ @Override
+ public void finish(boolean triggerBack) {
+ synchronized (mWindowManagerService.getWindowManagerLock()) {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mWindowManagerService.inSurfaceTransaction(() -> {
+ mWindowManagerService.mAtmService.deferWindowLayout();
+ try {
+ if (triggerBack) {
+ mDisplayContent.mFixedRotationTransitionListener
+ .notifyRecentsWillBeTop();
+ if (mTopActivity != null) {
+ mWindowManagerService.mTaskSnapshotController
+ .recordTaskSnapshot(mTopActivity.getTask(), false);
+ // TODO consume moveTaskToBack?
+ mTopActivity.commitVisibility(false, false, true);
+ }
+ } else {
+ mTargetActivityRecord.mTaskSupervisor
+ .scheduleLaunchTaskBehindComplete(
+ mTargetActivityRecord.token);
+ }
+ cleanupAnimation();
+ } finally {
+ mWindowManagerService.mAtmService.continueWindowLayout();
+ }
+ });
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+ }
+ };
+ }
+
+ /**
+ * @param targetActivity The home or opening activity which should host the wallpaper
+ * @param topActivity The current top activity before animation start.
+ */
+ void initialize(ActivityRecord targetActivity, ActivityRecord topActivity) {
+ mTargetActivityRecord = targetActivity;
+ mTopActivity = topActivity;
+ final Task topTask = mTopActivity.getTask();
+
+ createAnimationAdapter(topTask, (type, anim) -> topTask.forAllWindows(
+ win -> {
+ win.onAnimationFinished(type, anim);
+ }, true));
+ final Task homeTask = mTargetActivityRecord.getRootTask();
+ createAnimationAdapter(homeTask, (type, anim) -> homeTask.forAllWindows(
+ win -> {
+ win.onAnimationFinished(type, anim);
+ }, true));
+ try {
+ linkToDeathOfRunner();
+ } catch (RemoteException e) {
+ cancelAnimation();
+ return;
+ }
+
+ if (targetActivity.windowsCanBeWallpaperTarget()) {
+ mDisplayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
+ mDisplayContent.setLayoutNeeded();
+ }
+
+ mWindowManagerService.mWindowPlacerLocked.performSurfacePlacement();
+
+ mDisplayContent.mFixedRotationTransitionListener.onStartRecentsAnimation(targetActivity);
+ mPendingStart = true;
+ }
+
+ void cleanupAnimation() {
+ for (int i = mPendingAnimations.size() - 1; i >= 0; i--) {
+ final TaskAnimationAdapter taskAdapter = mPendingAnimations.get(i);
+
+ removeAnimationAdapter(taskAdapter);
+ taskAdapter.onCleanup();
+ }
+ mTargetActivityRecord.mLaunchTaskBehind = false;
+ // Clear references to the runner
+ unlinkToDeathOfRunner();
+ mRunner = null;
+
+ // Update the input windows after the animation is complete
+ final InputMonitor inputMonitor = mDisplayContent.getInputMonitor();
+ inputMonitor.updateInputWindowsLw(true /*force*/);
+
+ mDisplayContent.mFixedRotationTransitionListener.onFinishRecentsAnimation();
+ mBackNavigationController.finishAnimation();
+ }
+
+ void removeAnimationAdapter(TaskAnimationAdapter taskAdapter) {
+ taskAdapter.onRemove();
+ mPendingAnimations.remove(taskAdapter);
+ }
+
+ void checkAnimationReady(WallpaperController wallpaperController) {
+ if (mPendingStart) {
+ final boolean wallpaperReady = !isTargetOverWallpaper()
+ || (wallpaperController.getWallpaperTarget() != null
+ && wallpaperController.wallpaperTransitionReady());
+ if (wallpaperReady) {
+ startAnimation();
+ }
+ }
+ }
+
+ boolean isWallpaperVisible(WindowState w) {
+ return w != null && w.mAttrs.type == TYPE_BASE_APPLICATION
+ && ((w.mActivityRecord != null && mTargetActivityRecord == w.mActivityRecord)
+ || isAnimatingTask(w.getTask()))
+ && isTargetOverWallpaper() && w.isOnScreen();
+ }
+
+ boolean isAnimatingTask(Task task) {
+ for (int i = mPendingAnimations.size() - 1; i >= 0; i--) {
+ if (task == mPendingAnimations.get(i).mTask) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ void linkFixedRotationTransformIfNeeded(@NonNull WindowToken wallpaper) {
+ if (mTargetActivityRecord == null) {
+ return;
+ }
+ wallpaper.linkFixedRotationTransform(mTargetActivityRecord);
+ }
+
+ private void linkToDeathOfRunner() throws RemoteException {
+ if (!mLinkedToDeathOfRunner) {
+ mRunner.asBinder().linkToDeath(this, 0);
+ mLinkedToDeathOfRunner = true;
+ }
+ }
+
+ private void unlinkToDeathOfRunner() {
+ if (mLinkedToDeathOfRunner) {
+ mRunner.asBinder().unlinkToDeath(this, 0);
+ mLinkedToDeathOfRunner = false;
+ }
+ }
+
+ void startAnimation() {
+ if (!mPendingStart) {
+ // Skip starting if we've already started or canceled the animation
+ return;
+ }
+ // Create the app targets
+ final RemoteAnimationTarget[] appTargets = createAppAnimations();
+
+ // Skip the animation if there is nothing to animate
+ if (appTargets.length == 0) {
+ cancelAnimation();
+ return;
+ }
+
+ mPendingStart = false;
+
+ try {
+ mRunner.onAnimationStart(mRemoteController, BackNavigationInfo.TYPE_RETURN_TO_HOME,
+ appTargets, null /* wallpapers */, null /*nonApps*/);
+ } catch (RemoteException e) {
+ cancelAnimation();
+ }
+ }
+
+ @Override
+ public void binderDied() {
+ cancelAnimation();
+ }
+
+ TaskAnimationAdapter createAnimationAdapter(Task task,
+ SurfaceAnimator.OnAnimationFinishedCallback finishedCallback) {
+ final TaskAnimationAdapter taskAdapter = new TaskAnimationAdapter(task,
+ mTargetActivityRecord, this::cancelAnimation);
+ // borrow from recents since we cannot start back animation if recents is playing
+ task.startAnimation(task.getPendingTransaction(), taskAdapter, false /* hidden */,
+ ANIMATION_TYPE_RECENTS, finishedCallback);
+ task.commitPendingTransaction();
+ mPendingAnimations.add(taskAdapter);
+ return taskAdapter;
+ }
+
+ private RemoteAnimationTarget[] createAppAnimations() {
+ final ArrayList<RemoteAnimationTarget> targets = new ArrayList<>();
+ for (int i = mPendingAnimations.size() - 1; i >= 0; i--) {
+ final TaskAnimationAdapter taskAdapter = mPendingAnimations.get(i);
+ final RemoteAnimationTarget target =
+ taskAdapter.createRemoteAnimationTarget(INVALID_TASK_ID, MODE_UNKNOWN);
+ if (target != null) {
+ targets.add(target);
+ } else {
+ removeAnimationAdapter(taskAdapter);
+ }
+ }
+ return targets.toArray(new RemoteAnimationTarget[targets.size()]);
+ }
+
+ private void cancelAnimation() {
+ synchronized (mWindowManagerService.getWindowManagerLock()) {
+ // Notify the runner and clean up the animation immediately
+ // Note: In the fallback case, this can trigger multiple onAnimationCancel() calls
+ // to the runner if we this actually triggers cancel twice on the caller
+ try {
+ mRunner.onAnimationCancelled();
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to cancel recents animation", e);
+ }
+ cleanupAnimation();
+ }
+ }
+
+ private boolean isTargetOverWallpaper() {
+ if (mTargetActivityRecord == null) {
+ return false;
+ }
+ return mTargetActivityRecord.windowsCanBeWallpaperTarget();
+ }
+
+ private static class TaskAnimationAdapter implements AnimationAdapter {
+ private final Task mTask;
+ private SurfaceControl mCapturedLeash;
+ private SurfaceAnimator.OnAnimationFinishedCallback mCapturedFinishCallback;
+ @SurfaceAnimator.AnimationType private int mLastAnimationType;
+ private RemoteAnimationTarget mTarget;
+ private final ActivityRecord mTargetActivityRecord;
+ private final Runnable mCancelCallback;
+
+ private final Rect mBounds = new Rect();
+ // The bounds of the target relative to its parent.
+ private final Rect mLocalBounds = new Rect();
+
+ TaskAnimationAdapter(Task task, ActivityRecord target, Runnable cancelCallback) {
+ mTask = task;
+ mBounds.set(mTask.getBounds());
+
+ mLocalBounds.set(mBounds);
+ Point tmpPos = new Point();
+ mTask.getRelativePosition(tmpPos);
+ mLocalBounds.offsetTo(tmpPos.x, tmpPos.y);
+ mTargetActivityRecord = target;
+ mCancelCallback = cancelCallback;
+ }
+
+ // Keep overrideTaskId and overrideMode now, if we need to add other type of back animation
+ // on legacy transition system then they can be useful.
+ RemoteAnimationTarget createRemoteAnimationTarget(int overrideTaskId, int overrideMode) {
+ ActivityRecord topApp = mTask.getTopRealVisibleActivity();
+ if (topApp == null) {
+ topApp = mTask.getTopVisibleActivity();
+ }
+ final WindowState mainWindow = topApp != null
+ ? topApp.findMainWindow()
+ : null;
+ if (mainWindow == null) {
+ return null;
+ }
+ final Rect insets =
+ mainWindow.getInsetsStateWithVisibilityOverride().calculateInsets(
+ mBounds, WindowInsets.Type.systemBars(),
+ false /* ignoreVisibility */).toRect();
+ InsetUtils.addInsets(insets, mainWindow.mActivityRecord.getLetterboxInsets());
+ final int mode = overrideMode != MODE_UNKNOWN
+ ? overrideMode
+ : topApp.getActivityType() == mTargetActivityRecord.getActivityType()
+ ? MODE_OPENING
+ : MODE_CLOSING;
+ if (overrideTaskId < 0) {
+ overrideTaskId = mTask.mTaskId;
+ }
+ mTarget = new RemoteAnimationTarget(overrideTaskId, mode, mCapturedLeash,
+ !topApp.fillsParent(), new Rect(),
+ insets, mTask.getPrefixOrderIndex(), new Point(mBounds.left, mBounds.top),
+ mLocalBounds, mBounds, mTask.getWindowConfiguration(),
+ true /* isNotInRecents */, null, null, mTask.getTaskInfo(),
+ topApp.checkEnterPictureInPictureAppOpsState());
+ return mTarget;
+ }
+ @Override
+ public boolean getShowWallpaper() {
+ return false;
+ }
+ @Override
+ public void startAnimation(SurfaceControl animationLeash, SurfaceControl.Transaction t,
+ @SurfaceAnimator.AnimationType int type,
+ @NonNull SurfaceAnimator.OnAnimationFinishedCallback finishCallback) {
+ t.setPosition(animationLeash, mLocalBounds.left, mLocalBounds.top);
+ final Rect tmpRect = new Rect();
+ tmpRect.set(mLocalBounds);
+ tmpRect.offsetTo(0, 0);
+ t.setWindowCrop(animationLeash, tmpRect);
+ mCapturedLeash = animationLeash;
+ mCapturedFinishCallback = finishCallback;
+ mLastAnimationType = type;
+ }
+
+ @Override
+ public void onAnimationCancelled(SurfaceControl animationLeash) {
+ mCancelCallback.run();
+ }
+
+ void onRemove() {
+ mCapturedFinishCallback.onAnimationFinished(mLastAnimationType, this);
+ }
+
+ void onCleanup() {
+ final SurfaceControl.Transaction pendingTransaction = mTask.getPendingTransaction();
+ if (!mTask.isAttached()) {
+ // Apply the task's pending transaction in case it is detached and its transaction
+ // is not reachable.
+ pendingTransaction.apply();
+ }
+ }
+
+ @Override
+ public long getDurationHint() {
+ return 0;
+ }
+
+ @Override
+ public long getStatusBarTransitionsStartTime() {
+ return SystemClock.uptimeMillis();
+ }
+
+ @Override
+ public void dump(PrintWriter pw, String prefix) { }
+
+ @Override
+ public void dumpDebug(ProtoOutputStream proto) { }
+ }
+}
diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java
index d9ab971c9a78..35a39c048e57 100644
--- a/services/core/java/com/android/server/wm/BackNavigationController.java
+++ b/services/core/java/com/android/server/wm/BackNavigationController.java
@@ -34,6 +34,7 @@ import android.util.Slog;
import android.view.IWindowFocusObserver;
import android.view.RemoteAnimationTarget;
import android.view.SurfaceControl;
+import android.window.BackAnimationAdaptor;
import android.window.BackNavigationInfo;
import android.window.OnBackInvokedCallbackInfo;
import android.window.TaskSnapshot;
@@ -46,9 +47,15 @@ import com.android.server.LocalServices;
* Controller to handle actions related to the back gesture on the server side.
*/
class BackNavigationController {
- private static final String TAG = "BackNavigationController";
- private WindowManagerService mWindowManagerService;
+ static final String TAG = "BackNavigationController";
+ WindowManagerService mWindowManagerService;
private IWindowFocusObserver mFocusObserver;
+ // TODO (b/241808055) Find a appropriate time to remove during refactor
+ // Execute back animation with legacy transition system. Temporary flag for easier debugging.
+ static final boolean USE_TRANSITION =
+ SystemProperties.getInt("persist.wm.debug.predictive_back_ani_trans", 1) != 0;
+
+ BackNaviAnimationController mBackNaviAnimationController;
/**
* Returns true if the back predictability feature is enabled
@@ -72,7 +79,7 @@ class BackNavigationController {
@VisibleForTesting
@Nullable
BackNavigationInfo startBackNavigation(boolean requestAnimation,
- IWindowFocusObserver observer) {
+ IWindowFocusObserver observer, BackAnimationAdaptor backAnimationAdaptor) {
final WindowManagerService wmService = mWindowManagerService;
final SurfaceControl.Transaction tx = wmService.mTransactionFactory.get();
mFocusObserver = observer;
@@ -259,6 +266,8 @@ class BackNavigationController {
&& requestAnimation
// Only create a new leash if no leash has been created.
// Otherwise return null for animation target to avoid conflict.
+ // TODO isAnimating, recents can cancel app transition animation, can't back
+ // cancel like recents?
&& !removedWindowContainer.hasCommittedReparentToAnimationLeash();
if (prepareAnimation) {
@@ -266,19 +275,21 @@ class BackNavigationController {
currentTask.getTaskInfo().configuration.windowConfiguration;
infoBuilder.setTaskWindowConfiguration(taskWindowConfiguration);
- // Prepare a leash to animate the current top window
- // TODO(b/220934562): Use surface animator to better manage animation conflicts.
- SurfaceControl animLeash = removedWindowContainer.makeAnimationLeash()
- .setName("BackPreview Leash for " + removedWindowContainer)
- .setHidden(false)
- .setBLASTLayer()
- .build();
- removedWindowContainer.reparentSurfaceControl(tx, animLeash);
- animationLeashParent = removedWindowContainer.getAnimationLeashParent();
- topAppTarget = createRemoteAnimationTargetLocked(removedWindowContainer,
- currentActivity,
- currentTask, animLeash);
- infoBuilder.setDepartingAnimationTarget(topAppTarget);
+ if (!USE_TRANSITION) {
+ // Prepare a leash to animate the current top window
+ // TODO(b/220934562): Use surface animator to better manage animation conflicts.
+ SurfaceControl animLeash = removedWindowContainer.makeAnimationLeash()
+ .setName("BackPreview Leash for " + removedWindowContainer)
+ .setHidden(false)
+ .setBLASTLayer()
+ .build();
+ removedWindowContainer.reparentSurfaceControl(tx, animLeash);
+ animationLeashParent = removedWindowContainer.getAnimationLeashParent();
+ topAppTarget = createRemoteAnimationTargetLocked(removedWindowContainer,
+ currentActivity,
+ currentTask, animLeash);
+ infoBuilder.setDepartingAnimationTarget(topAppTarget);
+ }
}
//TODO(207481538) Remove once the infrastructure to support per-activity screenshot is
@@ -293,21 +304,32 @@ class BackNavigationController {
// Special handling for back to home animation
if (backType == BackNavigationInfo.TYPE_RETURN_TO_HOME && prepareAnimation
&& prevTask != null) {
- currentTask.mBackGestureStarted = true;
- // Make launcher show from behind by marking its top activity as visible and
- // launch-behind to bump its visibility for the duration of the back gesture.
- prevActivity = prevTask.getTopNonFinishingActivity();
- if (prevActivity != null) {
- if (!prevActivity.mVisibleRequested) {
- prevActivity.setVisibility(true);
+ if (USE_TRANSITION && mBackNaviAnimationController == null) {
+ if (backAnimationAdaptor != null
+ && backAnimationAdaptor.getSupportType() == backType) {
+ mBackNaviAnimationController = new BackNaviAnimationController(
+ backAnimationAdaptor.getRunner(), this,
+ currentActivity.getDisplayId());
+ prepareBackToHomeTransition(currentTask, prevTask);
+ infoBuilder.setPrepareAnimation(true);
+ }
+ } else {
+ currentTask.mBackGestureStarted = true;
+ // Make launcher show from behind by marking its top activity as visible and
+ // launch-behind to bump its visibility for the duration of the back gesture.
+ prevActivity = prevTask.getTopNonFinishingActivity();
+ if (prevActivity != null) {
+ if (!prevActivity.mVisibleRequested) {
+ prevActivity.setVisibility(true);
+ }
+ prevActivity.mLaunchTaskBehind = true;
+ ProtoLog.d(WM_DEBUG_BACK_PREVIEW,
+ "Setting Activity.mLauncherTaskBehind to true. Activity=%s",
+ prevActivity);
+ prevActivity.mRootWindowContainer.ensureActivitiesVisible(
+ null /* starting */, 0 /* configChanges */,
+ false /* preserveWindows */);
}
- prevActivity.mLaunchTaskBehind = true;
- ProtoLog.d(WM_DEBUG_BACK_PREVIEW,
- "Setting Activity.mLauncherTaskBehind to true. Activity=%s",
- prevActivity);
- prevActivity.mRootWindowContainer.ensureActivitiesVisible(
- null /* starting */, 0 /* configChanges */,
- false /* preserveWindows */);
}
}
} // Release wm Lock
@@ -388,29 +410,30 @@ class BackNavigationController {
BackNavigationInfo.KEY_TRIGGER_BACK);
ProtoLog.d(WM_DEBUG_BACK_PREVIEW, "onBackNavigationDone backType=%s, "
+ "task=%s, prevActivity=%s", backType, task, prevActivity);
-
- if (backType == BackNavigationInfo.TYPE_RETURN_TO_HOME && prepareAnimation) {
- if (triggerBack) {
- if (surfaceControl != null && surfaceControl.isValid()) {
- // When going back to home, hide the task surface before it is re-parented to
- // avoid flicker.
- SurfaceControl.Transaction t = windowContainer.getSyncTransaction();
- t.hide(surfaceControl);
- t.apply();
+ if (!USE_TRANSITION) {
+ if (backType == BackNavigationInfo.TYPE_RETURN_TO_HOME && prepareAnimation) {
+ if (triggerBack) {
+ if (surfaceControl != null && surfaceControl.isValid()) {
+ // When going back to home, hide the task surface before it is re-parented
+ // to avoid flicker.
+ SurfaceControl.Transaction t = windowContainer.getSyncTransaction();
+ t.hide(surfaceControl);
+ t.apply();
+ }
}
+ if (prevActivity != null && !triggerBack) {
+ // Restore the launch-behind state.
+ task.mTaskSupervisor.scheduleLaunchTaskBehindComplete(prevActivity.token);
+ prevActivity.mLaunchTaskBehind = false;
+ ProtoLog.d(WM_DEBUG_BACK_PREVIEW,
+ "Setting Activity.mLauncherTaskBehind to false. Activity=%s",
+ prevActivity);
+ }
+ } else {
+ task.mBackGestureStarted = false;
}
- if (prevActivity != null && !triggerBack) {
- // Restore the launch-behind state.
- task.mTaskSupervisor.scheduleLaunchTaskBehindComplete(prevActivity.token);
- prevActivity.mLaunchTaskBehind = false;
- ProtoLog.d(WM_DEBUG_BACK_PREVIEW,
- "Setting Activity.mLauncherTaskBehind to false. Activity=%s",
- prevActivity);
- }
- } else if (task != null) {
- task.mBackGestureStarted = false;
+ resetSurfaces(windowContainer);
}
- resetSurfaces(windowContainer);
if (mFocusObserver != null) {
focusedWindow.unregisterFocusObserver(mFocusObserver);
@@ -465,4 +488,21 @@ class BackNavigationController {
void setWindowManager(WindowManagerService wm) {
mWindowManagerService = wm;
}
+
+ private void prepareBackToHomeTransition(Task currentTask, Task homeTask) {
+ final DisplayContent dc = currentTask.getDisplayContent();
+ final ActivityRecord homeActivity = homeTask.getTopNonFinishingActivity();
+ if (!homeActivity.mVisibleRequested) {
+ homeActivity.setVisibility(true);
+ }
+ homeActivity.mLaunchTaskBehind = true;
+ dc.ensureActivitiesVisible(
+ null /* starting */, 0 /* configChanges */,
+ false /* preserveWindows */, true);
+ mBackNaviAnimationController.initialize(homeActivity, currentTask.getTopMostActivity());
+ }
+
+ void finishAnimation() {
+ mBackNaviAnimationController = null;
+ }
}
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index b6452543a988..0376974900e3 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -438,7 +438,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
*/
final DisplayMetrics mRealDisplayMetrics = new DisplayMetrics();
- /** @see #computeCompatSmallestWidth(boolean, int, int, int) */
+ /** @see #computeCompatSmallestWidth(boolean, int, int) */
private final DisplayMetrics mTmpDisplayMetrics = new DisplayMetrics();
/**
@@ -2014,7 +2014,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
// the top of the method, the caller is obligated to call computeNewConfigurationLocked().
// By updating the Display info here it will be available to
// #computeScreenConfiguration() later.
- updateDisplayAndOrientation(getConfiguration().uiMode, null /* outConfig */);
+ updateDisplayAndOrientation(null /* outConfig */);
// NOTE: We disable the rotation in the emulator because
// it doesn't support hardware OpenGL emulation yet.
@@ -2064,7 +2064,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
* changed.
* Do not call if {@link WindowManagerService#mDisplayReady} == false.
*/
- private DisplayInfo updateDisplayAndOrientation(int uiMode, Configuration outConfig) {
+ private DisplayInfo updateDisplayAndOrientation(Configuration outConfig) {
// Use the effective "visual" dimensions based on current rotation
final int rotation = getRotation();
final boolean rotated = (rotation == ROTATION_90 || rotation == ROTATION_270);
@@ -2076,18 +2076,16 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
final DisplayCutout displayCutout = wmDisplayCutout.getDisplayCutout();
final RoundedCorners roundedCorners = calculateRoundedCornersForRotation(rotation);
- final int appWidth = mDisplayPolicy.getNonDecorDisplayWidth(dw, dh, rotation, uiMode,
- displayCutout);
- final int appHeight = mDisplayPolicy.getNonDecorDisplayHeight(dh, rotation,
- displayCutout);
+ final Rect appFrame = mDisplayPolicy.getNonDecorDisplayFrame(dw, dh, rotation,
+ wmDisplayCutout);
mDisplayInfo.rotation = rotation;
mDisplayInfo.logicalWidth = dw;
mDisplayInfo.logicalHeight = dh;
mDisplayInfo.logicalDensityDpi = mBaseDisplayDensity;
mDisplayInfo.physicalXDpi = mBaseDisplayPhysicalXDpi;
mDisplayInfo.physicalYDpi = mBaseDisplayPhysicalYDpi;
- mDisplayInfo.appWidth = appWidth;
- mDisplayInfo.appHeight = appHeight;
+ mDisplayInfo.appWidth = appFrame.width();
+ mDisplayInfo.appHeight = appFrame.height();
if (isDefaultDisplay) {
mDisplayInfo.getLogicalMetrics(mRealDisplayMetrics,
CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null);
@@ -2101,7 +2099,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
mDisplayInfo.flags &= ~Display.FLAG_SCALING_DISABLED;
}
- computeSizeRangesAndScreenLayout(mDisplayInfo, rotated, uiMode, dw, dh,
+ computeSizeRangesAndScreenLayout(mDisplayInfo, rotated, dw, dh,
mDisplayMetrics.density, outConfig);
mWmService.mDisplayManagerInternal.setDisplayInfoOverrideFromWindowManager(mDisplayId,
@@ -2191,10 +2189,8 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
outConfig.windowConfiguration.setMaxBounds(0, 0, dw, dh);
outConfig.windowConfiguration.setBounds(outConfig.windowConfiguration.getMaxBounds());
- final int uiMode = getConfiguration().uiMode;
- final DisplayCutout displayCutout =
- calculateDisplayCutoutForRotation(rotation).getDisplayCutout();
- computeScreenAppConfiguration(outConfig, dw, dh, rotation, uiMode, displayCutout);
+ final WmDisplayCutout wmDisplayCutout = calculateDisplayCutoutForRotation(rotation);
+ computeScreenAppConfiguration(outConfig, dw, dh, rotation, wmDisplayCutout);
final DisplayInfo displayInfo = new DisplayInfo(mDisplayInfo);
displayInfo.rotation = rotation;
@@ -2203,38 +2199,35 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
final Rect appBounds = outConfig.windowConfiguration.getAppBounds();
displayInfo.appWidth = appBounds.width();
displayInfo.appHeight = appBounds.height();
+ final DisplayCutout displayCutout = wmDisplayCutout.getDisplayCutout();
displayInfo.displayCutout = displayCutout.isEmpty() ? null : displayCutout;
- computeSizeRangesAndScreenLayout(displayInfo, rotated, uiMode, dw, dh,
+ computeSizeRangesAndScreenLayout(displayInfo, rotated, dw, dh,
mDisplayMetrics.density, outConfig);
return displayInfo;
}
/** Compute configuration related to application without changing current display. */
private void computeScreenAppConfiguration(Configuration outConfig, int dw, int dh,
- int rotation, int uiMode, DisplayCutout displayCutout) {
- final int appWidth = mDisplayPolicy.getNonDecorDisplayWidth(dw, dh, rotation, uiMode,
- displayCutout);
- final int appHeight = mDisplayPolicy.getNonDecorDisplayHeight(dh, rotation,
- displayCutout);
- mDisplayPolicy.getNonDecorInsetsLw(rotation, displayCutout, mTmpRect);
- final int leftInset = mTmpRect.left;
- final int topInset = mTmpRect.top;
+ int rotation, WmDisplayCutout wmDisplayCutout) {
+ final DisplayFrames displayFrames =
+ mDisplayPolicy.getSimulatedDisplayFrames(rotation, dw, dh, wmDisplayCutout);
+ final Rect appFrame =
+ mDisplayPolicy.getNonDecorDisplayFrameWithSimulatedFrame(displayFrames);
// AppBounds at the root level should mirror the app screen size.
- outConfig.windowConfiguration.setAppBounds(leftInset /* left */, topInset /* top */,
- leftInset + appWidth /* right */, topInset + appHeight /* bottom */);
+ outConfig.windowConfiguration.setAppBounds(appFrame);
outConfig.windowConfiguration.setRotation(rotation);
outConfig.orientation = (dw <= dh) ? ORIENTATION_PORTRAIT : ORIENTATION_LANDSCAPE;
final float density = mDisplayMetrics.density;
- outConfig.screenWidthDp = (int) (mDisplayPolicy.getConfigDisplayWidth(dw, dh, rotation,
- uiMode, displayCutout) / density + 0.5f);
- outConfig.screenHeightDp = (int) (mDisplayPolicy.getConfigDisplayHeight(dw, dh, rotation,
- uiMode, displayCutout) / density + 0.5f);
+ final Point configSize =
+ mDisplayPolicy.getConfigDisplaySizeWithSimulatedFrame(displayFrames);
+ outConfig.screenWidthDp = (int) (configSize.x / density + 0.5f);
+ outConfig.screenHeightDp = (int) (configSize.y / density + 0.5f);
outConfig.compatScreenWidthDp = (int) (outConfig.screenWidthDp / mCompatibleScreenScale);
outConfig.compatScreenHeightDp = (int) (outConfig.screenHeightDp / mCompatibleScreenScale);
final boolean rotated = (rotation == ROTATION_90 || rotation == ROTATION_270);
- outConfig.compatSmallestScreenWidthDp = computeCompatSmallestWidth(rotated, uiMode, dw, dh);
+ outConfig.compatSmallestScreenWidthDp = computeCompatSmallestWidth(rotated, dw, dh);
outConfig.windowConfiguration.setDisplayRotation(rotation);
}
@@ -2243,7 +2236,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
* Do not call if mDisplayReady == false.
*/
void computeScreenConfiguration(Configuration config) {
- final DisplayInfo displayInfo = updateDisplayAndOrientation(config.uiMode, config);
+ final DisplayInfo displayInfo = updateDisplayAndOrientation(config);
final int dw = displayInfo.logicalWidth;
final int dh = displayInfo.logicalHeight;
mTmpRect.set(0, 0, dw, dh);
@@ -2252,8 +2245,8 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
config.windowConfiguration.setWindowingMode(getWindowingMode());
config.windowConfiguration.setDisplayWindowingMode(getWindowingMode());
- computeScreenAppConfiguration(config, dw, dh, displayInfo.rotation, config.uiMode,
- displayInfo.displayCutout);
+ computeScreenAppConfiguration(config, dw, dh, displayInfo.rotation,
+ calculateDisplayCutoutForRotation(getRotation()));
config.screenLayout = (config.screenLayout & ~Configuration.SCREENLAYOUT_ROUND_MASK)
| ((displayInfo.flags & Display.FLAG_ROUND) != 0
@@ -2342,7 +2335,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
mWmService.mPolicy.adjustConfigurationLw(config, keyboardPresence, navigationPresence);
}
- private int computeCompatSmallestWidth(boolean rotated, int uiMode, int dw, int dh) {
+ private int computeCompatSmallestWidth(boolean rotated, int dw, int dh) {
mTmpDisplayMetrics.setTo(mDisplayMetrics);
final DisplayMetrics tmpDm = mTmpDisplayMetrics;
final int unrotDw, unrotDh;
@@ -2353,25 +2346,20 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
unrotDw = dw;
unrotDh = dh;
}
- int sw = reduceCompatConfigWidthSize(0, Surface.ROTATION_0, uiMode, tmpDm, unrotDw,
- unrotDh);
- sw = reduceCompatConfigWidthSize(sw, Surface.ROTATION_90, uiMode, tmpDm, unrotDh,
- unrotDw);
- sw = reduceCompatConfigWidthSize(sw, Surface.ROTATION_180, uiMode, tmpDm, unrotDw,
- unrotDh);
- sw = reduceCompatConfigWidthSize(sw, Surface.ROTATION_270, uiMode, tmpDm, unrotDh,
- unrotDw);
+ int sw = reduceCompatConfigWidthSize(0, Surface.ROTATION_0, tmpDm, unrotDw, unrotDh);
+ sw = reduceCompatConfigWidthSize(sw, Surface.ROTATION_90, tmpDm, unrotDh, unrotDw);
+ sw = reduceCompatConfigWidthSize(sw, Surface.ROTATION_180, tmpDm, unrotDw, unrotDh);
+ sw = reduceCompatConfigWidthSize(sw, Surface.ROTATION_270, tmpDm, unrotDh, unrotDw);
return sw;
}
- private int reduceCompatConfigWidthSize(int curSize, int rotation, int uiMode,
+ private int reduceCompatConfigWidthSize(int curSize, int rotation,
DisplayMetrics dm, int dw, int dh) {
- final DisplayCutout displayCutout = calculateDisplayCutoutForRotation(
- rotation).getDisplayCutout();
- dm.noncompatWidthPixels = mDisplayPolicy.getNonDecorDisplayWidth(dw, dh, rotation, uiMode,
- displayCutout);
- dm.noncompatHeightPixels = mDisplayPolicy.getNonDecorDisplayHeight(dh, rotation,
- displayCutout);
+ final WmDisplayCutout wmDisplayCutout = calculateDisplayCutoutForRotation(rotation);
+ final Rect nonDecorSize = mDisplayPolicy.getNonDecorDisplayFrame(dw, dh, rotation,
+ wmDisplayCutout);
+ dm.noncompatWidthPixels = nonDecorSize.width();
+ dm.noncompatHeightPixels = nonDecorSize.height();
float scale = CompatibilityInfo.computeCompatibleScaling(dm, null);
int size = (int)(((dm.noncompatWidthPixels / scale) / dm.density) + .5f);
if (curSize == 0 || size < curSize) {
@@ -2381,7 +2369,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
}
private void computeSizeRangesAndScreenLayout(DisplayInfo displayInfo, boolean rotated,
- int uiMode, int dw, int dh, float density, Configuration outConfig) {
+ int dw, int dh, float density, Configuration outConfig) {
// We need to determine the smallest width that will occur under normal
// operation. To this, start with the base screen size and compute the
@@ -2399,37 +2387,34 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
displayInfo.smallestNominalAppHeight = 1<<30;
displayInfo.largestNominalAppWidth = 0;
displayInfo.largestNominalAppHeight = 0;
- adjustDisplaySizeRanges(displayInfo, Surface.ROTATION_0, uiMode, unrotDw, unrotDh);
- adjustDisplaySizeRanges(displayInfo, Surface.ROTATION_90, uiMode, unrotDh, unrotDw);
- adjustDisplaySizeRanges(displayInfo, Surface.ROTATION_180, uiMode, unrotDw, unrotDh);
- adjustDisplaySizeRanges(displayInfo, Surface.ROTATION_270, uiMode, unrotDh, unrotDw);
+ adjustDisplaySizeRanges(displayInfo, Surface.ROTATION_0, unrotDw, unrotDh);
+ adjustDisplaySizeRanges(displayInfo, Surface.ROTATION_90, unrotDh, unrotDw);
+ adjustDisplaySizeRanges(displayInfo, Surface.ROTATION_180, unrotDw, unrotDh);
+ adjustDisplaySizeRanges(displayInfo, Surface.ROTATION_270, unrotDh, unrotDw);
if (outConfig == null) {
return;
}
int sl = Configuration.resetScreenLayout(outConfig.screenLayout);
- sl = reduceConfigLayout(sl, Surface.ROTATION_0, density, unrotDw, unrotDh, uiMode);
- sl = reduceConfigLayout(sl, Surface.ROTATION_90, density, unrotDh, unrotDw, uiMode);
- sl = reduceConfigLayout(sl, Surface.ROTATION_180, density, unrotDw, unrotDh, uiMode);
- sl = reduceConfigLayout(sl, Surface.ROTATION_270, density, unrotDh, unrotDw, uiMode);
+ sl = reduceConfigLayout(sl, Surface.ROTATION_0, density, unrotDw, unrotDh);
+ sl = reduceConfigLayout(sl, Surface.ROTATION_90, density, unrotDh, unrotDw);
+ sl = reduceConfigLayout(sl, Surface.ROTATION_180, density, unrotDw, unrotDh);
+ sl = reduceConfigLayout(sl, Surface.ROTATION_270, density, unrotDh, unrotDw);
outConfig.smallestScreenWidthDp =
(int) (displayInfo.smallestNominalAppWidth / density + 0.5f);
outConfig.screenLayout = sl;
}
- private int reduceConfigLayout(int curLayout, int rotation, float density, int dw, int dh,
- int uiMode) {
+ private int reduceConfigLayout(int curLayout, int rotation, float density, int dw, int dh) {
// Get the display cutout at this rotation.
- final DisplayCutout displayCutout = calculateDisplayCutoutForRotation(
- rotation).getDisplayCutout();
+ final WmDisplayCutout wmDisplayCutout = calculateDisplayCutoutForRotation(rotation);
// Get the app screen size at this rotation.
- int w = mDisplayPolicy.getNonDecorDisplayWidth(dw, dh, rotation, uiMode, displayCutout);
- int h = mDisplayPolicy.getNonDecorDisplayHeight(dh, rotation, displayCutout);
+ final Rect size = mDisplayPolicy.getNonDecorDisplayFrame(dw, dh, rotation, wmDisplayCutout);
// Compute the screen layout size class for this rotation.
- int longSize = w;
- int shortSize = h;
+ int longSize = size.width();
+ int shortSize = size.height();
if (longSize < shortSize) {
int tmp = longSize;
longSize = shortSize;
@@ -2440,25 +2425,20 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
return Configuration.reduceScreenLayout(curLayout, longSize, shortSize);
}
- private void adjustDisplaySizeRanges(DisplayInfo displayInfo, int rotation,
- int uiMode, int dw, int dh) {
- final DisplayCutout displayCutout = calculateDisplayCutoutForRotation(
- rotation).getDisplayCutout();
- final int width = mDisplayPolicy.getConfigDisplayWidth(dw, dh, rotation, uiMode,
- displayCutout);
- if (width < displayInfo.smallestNominalAppWidth) {
- displayInfo.smallestNominalAppWidth = width;
+ private void adjustDisplaySizeRanges(DisplayInfo displayInfo, int rotation, int dw, int dh) {
+ final WmDisplayCutout wmDisplayCutout = calculateDisplayCutoutForRotation(rotation);
+ final Point size = mDisplayPolicy.getConfigDisplaySize(dw, dh, rotation, wmDisplayCutout);
+ if (size.x < displayInfo.smallestNominalAppWidth) {
+ displayInfo.smallestNominalAppWidth = size.x;
}
- if (width > displayInfo.largestNominalAppWidth) {
- displayInfo.largestNominalAppWidth = width;
+ if (size.x > displayInfo.largestNominalAppWidth) {
+ displayInfo.largestNominalAppWidth = size.x;
}
- final int height = mDisplayPolicy.getConfigDisplayHeight(dw, dh, rotation, uiMode,
- displayCutout);
- if (height < displayInfo.smallestNominalAppHeight) {
- displayInfo.smallestNominalAppHeight = height;
+ if (size.y < displayInfo.smallestNominalAppHeight) {
+ displayInfo.smallestNominalAppHeight = size.y;
}
- if (height > displayInfo.largestNominalAppHeight) {
- displayInfo.largestNominalAppHeight = height;
+ if (size.y > displayInfo.largestNominalAppHeight) {
+ displayInfo.largestNominalAppHeight = size.y;
}
}
@@ -3304,6 +3284,14 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
mAsyncRotationController.keepAppearanceInPreviousRotation();
}
} else if (isRotationChanging()) {
+ if (displayChange != null) {
+ final boolean seamless = mDisplayRotation.shouldRotateSeamlessly(
+ displayChange.getStartRotation(), displayChange.getEndRotation(),
+ false /* forceUpdate */);
+ if (seamless) {
+ t.onSeamlessRotating(this);
+ }
+ }
mWmService.mLatencyTracker.onActionStart(ACTION_ROTATE_SCREEN);
controller.mTransitionMetricsReporter.associate(t,
startTime -> mWmService.mLatencyTracker.onActionEnd(ACTION_ROTATE_SCREEN));
@@ -4400,13 +4388,20 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
*/
@VisibleForTesting
InsetsControlTarget computeImeControlTarget() {
+ if (mImeInputTarget == null) {
+ // A special case that if there is no IME input target while the IME is being killed,
+ // in case seeing unexpected IME surface visibility change when delivering the IME leash
+ // to the remote insets target during the IME restarting, but the focus window is not in
+ // multi-windowing mode, return null target until the next input target updated.
+ return null;
+ }
+
+ final WindowState imeInputTarget = mImeInputTarget.getWindowState();
if (!isImeControlledByApp() && mRemoteInsetsControlTarget != null
- || (mImeInputTarget != null
- && getImeHostOrFallback(mImeInputTarget.getWindowState())
- == mRemoteInsetsControlTarget)) {
+ || getImeHostOrFallback(imeInputTarget) == mRemoteInsetsControlTarget) {
return mRemoteInsetsControlTarget;
} else {
- return mImeInputTarget != null ? mImeInputTarget.getWindowState() : null;
+ return imeInputTarget;
}
}
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 1a34c93f2ad6..8e06a810ead1 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -19,15 +19,19 @@ package com.android.server.wm;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.view.Display.TYPE_INTERNAL;
+import static android.view.InsetsState.ITYPE_BOTTOM_DISPLAY_CUTOUT;
import static android.view.InsetsState.ITYPE_BOTTOM_MANDATORY_GESTURES;
import static android.view.InsetsState.ITYPE_BOTTOM_TAPPABLE_ELEMENT;
import static android.view.InsetsState.ITYPE_CAPTION_BAR;
import static android.view.InsetsState.ITYPE_CLIMATE_BAR;
import static android.view.InsetsState.ITYPE_EXTRA_NAVIGATION_BAR;
+import static android.view.InsetsState.ITYPE_LEFT_DISPLAY_CUTOUT;
import static android.view.InsetsState.ITYPE_LEFT_GESTURES;
import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
+import static android.view.InsetsState.ITYPE_RIGHT_DISPLAY_CUTOUT;
import static android.view.InsetsState.ITYPE_RIGHT_GESTURES;
import static android.view.InsetsState.ITYPE_STATUS_BAR;
+import static android.view.InsetsState.ITYPE_TOP_DISPLAY_CUTOUT;
import static android.view.InsetsState.ITYPE_TOP_MANDATORY_GESTURES;
import static android.view.InsetsState.ITYPE_TOP_TAPPABLE_ELEMENT;
import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS;
@@ -103,6 +107,7 @@ import android.content.Intent;
import android.content.res.Resources;
import android.graphics.Insets;
import android.graphics.PixelFormat;
+import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.Region;
import android.gui.DropInputMode;
@@ -127,6 +132,8 @@ import android.view.InsetsSource;
import android.view.InsetsState;
import android.view.InsetsState.InternalInsetsType;
import android.view.InsetsVisibilities;
+import android.view.PrivacyIndicatorBounds;
+import android.view.RoundedCorners;
import android.view.Surface;
import android.view.View;
import android.view.ViewDebug;
@@ -136,7 +143,6 @@ import android.view.WindowLayout;
import android.view.WindowManager;
import android.view.WindowManager.LayoutParams;
import android.view.WindowManagerGlobal;
-import android.view.WindowManagerPolicyConstants;
import android.view.accessibility.AccessibilityManager;
import android.window.ClientWindowFrames;
@@ -160,6 +166,7 @@ import com.android.server.policy.WindowManagerPolicy.ScreenOnListener;
import com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs;
import com.android.server.statusbar.StatusBarManagerInternal;
import com.android.server.wallpaper.WallpaperManagerInternal;
+import com.android.server.wm.utils.WmDisplayCutout;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -383,6 +390,16 @@ public class DisplayPolicy {
private static final int MSG_REQUEST_TRANSIENT_BARS_ARG_STATUS = 0;
private static final int MSG_REQUEST_TRANSIENT_BARS_ARG_NAVIGATION = 1;
+ // TODO (b/235842600): Use public type once we can treat task bar as navigation bar.
+ private static final int[] STABLE_TYPES = new int[]{
+ ITYPE_TOP_DISPLAY_CUTOUT, ITYPE_RIGHT_DISPLAY_CUTOUT, ITYPE_BOTTOM_DISPLAY_CUTOUT,
+ ITYPE_LEFT_DISPLAY_CUTOUT, ITYPE_NAVIGATION_BAR, ITYPE_STATUS_BAR, ITYPE_CLIMATE_BAR
+ };
+ private static final int[] NON_DECOR_TYPES = new int[]{
+ ITYPE_TOP_DISPLAY_CUTOUT, ITYPE_RIGHT_DISPLAY_CUTOUT, ITYPE_BOTTOM_DISPLAY_CUTOUT,
+ ITYPE_LEFT_DISPLAY_CUTOUT, ITYPE_NAVIGATION_BAR
+ };
+
private final GestureNavigationSettingsObserver mGestureNavigationSettingsObserver;
private final WindowManagerInternal.AppTransitionListener mAppTransitionListener;
@@ -2007,35 +2024,6 @@ public class DisplayPolicy {
return mUiContext;
}
- private int getNavigationBarWidth(int rotation, int uiMode, int position) {
- if (mNavigationBar == null) {
- return 0;
- }
- LayoutParams lp = mNavigationBar.mAttrs;
- if (lp.paramsForRotation != null
- && lp.paramsForRotation.length == 4
- && lp.paramsForRotation[rotation] != null) {
- lp = lp.paramsForRotation[rotation];
- }
- Insets providedInsetsSize = null;
- if (lp.providedInsets != null) {
- for (InsetsFrameProvider provider : lp.providedInsets) {
- if (provider.type != ITYPE_NAVIGATION_BAR) {
- continue;
- }
- providedInsetsSize = provider.insetsSize;
- }
- }
- if (providedInsetsSize != null) {
- if (position == NAV_BAR_LEFT) {
- return providedInsetsSize.left;
- } else if (position == NAV_BAR_RIGHT) {
- return providedInsetsSize.right;
- }
- }
- return lp.width;
- }
-
@VisibleForTesting
void setCanSystemBarsBeShownByUser(boolean canBeShown) {
mCanSystemBarsBeShownByUser = canBeShown;
@@ -2057,45 +2045,24 @@ public class DisplayPolicy {
}
/**
- * Return the display width available after excluding any screen
- * decorations that could never be removed in Honeycomb. That is, system bar or
- * button bar.
+ * Return the display frame available after excluding any screen decorations that could never be
+ * removed in Honeycomb. That is, system bar or button bar.
+ *
+ * @return display frame excluding all non-decor insets.
*/
- public int getNonDecorDisplayWidth(int fullWidth, int fullHeight, int rotation, int uiMode,
- DisplayCutout displayCutout) {
- int width = fullWidth;
- if (hasNavigationBar()) {
- final int navBarPosition = navigationBarPosition(rotation);
- if (navBarPosition == NAV_BAR_LEFT || navBarPosition == NAV_BAR_RIGHT) {
- width -= getNavigationBarWidth(rotation, uiMode, navBarPosition);
- }
- }
- if (displayCutout != null) {
- width -= displayCutout.getSafeInsetLeft() + displayCutout.getSafeInsetRight();
- }
- return width;
+ Rect getNonDecorDisplayFrame(int fullWidth, int fullHeight, int rotation,
+ WmDisplayCutout cutout) {
+ final DisplayFrames displayFrames =
+ getSimulatedDisplayFrames(rotation, fullWidth, fullHeight, cutout);
+ return getNonDecorDisplayFrameWithSimulatedFrame(displayFrames);
}
- @VisibleForTesting
- int getNavigationBarHeight(int rotation) {
- if (mNavigationBar == null) {
- return 0;
- }
- LayoutParams lp = mNavigationBar.mAttrs.forRotation(rotation);
- Insets providedInsetsSize = null;
- if (lp.providedInsets != null) {
- for (InsetsFrameProvider provider : lp.providedInsets) {
- if (provider.type != ITYPE_NAVIGATION_BAR) {
- continue;
- }
- providedInsetsSize = provider.insetsSize;
- if (providedInsetsSize != null) {
- return providedInsetsSize.bottom;
- }
- break;
- }
- }
- return lp.height;
+ Rect getNonDecorDisplayFrameWithSimulatedFrame(DisplayFrames displayFrames) {
+ final Rect nonDecorInsets =
+ getInsetsWithInternalTypes(displayFrames, NON_DECOR_TYPES).toRect();
+ final Rect displayFrame = new Rect(displayFrames.mInsetsState.getDisplayFrame());
+ displayFrame.inset(nonDecorInsets);
+ return displayFrame;
}
/**
@@ -2117,53 +2084,23 @@ public class DisplayPolicy {
}
/**
- * Return the display height available after excluding any screen
- * decorations that could never be removed in Honeycomb. That is, system bar or
- * button bar.
- */
- public int getNonDecorDisplayHeight(int fullHeight, int rotation, DisplayCutout displayCutout) {
- int height = fullHeight;
- final int navBarPosition = navigationBarPosition(rotation);
- if (navBarPosition == NAV_BAR_BOTTOM) {
- height -= getNavigationBarHeight(rotation);
- }
- if (displayCutout != null) {
- height -= displayCutout.getSafeInsetTop() + displayCutout.getSafeInsetBottom();
- }
- return height;
- }
-
- /**
- * Return the available screen width that we should report for the
+ * Return the available screen size that we should report for the
* configuration. This must be no larger than
- * {@link #getNonDecorDisplayWidth(int, int, int, int, DisplayCutout)}; it may be smaller
+ * {@link #getNonDecorDisplayFrame(int, int, int, DisplayCutout)}; it may be smaller
* than that to account for more transient decoration like a status bar.
*/
- public int getConfigDisplayWidth(int fullWidth, int fullHeight, int rotation, int uiMode,
- DisplayCutout displayCutout) {
- return getNonDecorDisplayWidth(fullWidth, fullHeight, rotation, uiMode, displayCutout);
+ public Point getConfigDisplaySize(int fullWidth, int fullHeight, int rotation,
+ WmDisplayCutout wmDisplayCutout) {
+ final DisplayFrames displayFrames = getSimulatedDisplayFrames(rotation, fullWidth,
+ fullHeight, wmDisplayCutout);
+ return getConfigDisplaySizeWithSimulatedFrame(displayFrames);
}
- /**
- * Return the available screen height that we should report for the
- * configuration. This must be no larger than
- * {@link #getNonDecorDisplayHeight(int, int, DisplayCutout)}; it may be smaller
- * than that to account for more transient decoration like a status bar.
- */
- public int getConfigDisplayHeight(int fullWidth, int fullHeight, int rotation, int uiMode,
- DisplayCutout displayCutout) {
- // There is a separate status bar at the top of the display. We don't count that as part
- // of the fixed decor, since it can hide; however, for purposes of configurations,
- // we do want to exclude it since applications can't generally use that part
- // of the screen.
- int statusBarHeight = mStatusBarHeightForRotation[rotation];
- if (displayCutout != null) {
- // If there is a cutout, it may already have accounted for some part of the status
- // bar height.
- statusBarHeight = Math.max(0, statusBarHeight - displayCutout.getSafeInsetTop());
- }
- return getNonDecorDisplayHeight(fullHeight, rotation, displayCutout)
- - statusBarHeight;
+ Point getConfigDisplaySizeWithSimulatedFrame(DisplayFrames displayFrames) {
+ final Insets insets = getInsetsWithInternalTypes(displayFrames, STABLE_TYPES);
+ Rect configFrame = new Rect(displayFrames.mInsetsState.getDisplayFrame());
+ configFrame.inset(insets);
+ return new Point(configFrame.width(), configFrame.height());
}
/**
@@ -2195,48 +2132,75 @@ public class DisplayPolicy {
* Calculates the stable insets without running a layout.
*
* @param displayRotation the current display rotation
+ * @param displayWidth full display width
+ * @param displayHeight full display height
* @param displayCutout the current display cutout
* @param outInsets the insets to return
*/
- public void getStableInsetsLw(int displayRotation, DisplayCutout displayCutout,
- Rect outInsets) {
- outInsets.setEmpty();
+ public void getStableInsetsLw(int displayRotation, int displayWidth, int displayHeight,
+ WmDisplayCutout displayCutout, Rect outInsets) {
+ final DisplayFrames displayFrames = getSimulatedDisplayFrames(displayRotation,
+ displayWidth, displayHeight, displayCutout);
+ getStableInsetsWithSimulatedFrame(displayFrames, outInsets);
+ }
- // Navigation bar and status bar.
- getNonDecorInsetsLw(displayRotation, displayCutout, outInsets);
- convertNonDecorInsetsToStableInsets(outInsets, displayRotation);
+ void getStableInsetsWithSimulatedFrame(DisplayFrames displayFrames, Rect outInsets) {
+ // Navigation bar, status bar, and cutout.
+ outInsets.set(getInsetsWithInternalTypes(displayFrames, STABLE_TYPES).toRect());
}
/**
* Calculates the insets for the areas that could never be removed in Honeycomb, i.e. system
- * bar or button bar. See {@link #getNonDecorDisplayWidth}.
- * @param displayRotation the current display rotation
- * @param displayCutout the current display cutout
+ * bar or button bar. See {@link #getNonDecorDisplayFrame}.
+ *
+ * @param displayRotation the current display rotation
+ * @param fullWidth the width of the display, including all insets
+ * @param fullHeight the height of the display, including all insets
+ * @param cutout the current display cutout
* @param outInsets the insets to return
*/
- public void getNonDecorInsetsLw(int displayRotation, DisplayCutout displayCutout,
- Rect outInsets) {
- outInsets.setEmpty();
-
- // Only navigation bar
- if (hasNavigationBar()) {
- final int uiMode = mService.mPolicy.getUiMode();
- int position = navigationBarPosition(displayRotation);
- if (position == NAV_BAR_BOTTOM) {
- outInsets.bottom = getNavigationBarHeight(displayRotation);
- } else if (position == NAV_BAR_RIGHT) {
- outInsets.right = getNavigationBarWidth(displayRotation, uiMode, position);
- } else if (position == NAV_BAR_LEFT) {
- outInsets.left = getNavigationBarWidth(displayRotation, uiMode, position);
- }
- }
+ public void getNonDecorInsetsLw(int displayRotation, int fullWidth, int fullHeight,
+ WmDisplayCutout cutout, Rect outInsets) {
+ final DisplayFrames displayFrames =
+ getSimulatedDisplayFrames(displayRotation, fullWidth, fullHeight, cutout);
+ getNonDecorInsetsWithSimulatedFrame(displayFrames, outInsets);
+ }
+
+ void getNonDecorInsetsWithSimulatedFrame(DisplayFrames displayFrames, Rect outInsets) {
+ outInsets.set(getInsetsWithInternalTypes(displayFrames, NON_DECOR_TYPES).toRect());
+ }
+
+ DisplayFrames getSimulatedDisplayFrames(int displayRotation, int fullWidth,
+ int fullHeight, WmDisplayCutout cutout) {
+ final DisplayInfo info = new DisplayInfo(mDisplayContent.getDisplayInfo());
+ info.rotation = displayRotation;
+ info.logicalWidth = fullWidth;
+ info.logicalHeight = fullHeight;
+ info.displayCutout = cutout.getDisplayCutout();
+ final RoundedCorners roundedCorners =
+ mDisplayContent.calculateRoundedCornersForRotation(displayRotation);
+ final PrivacyIndicatorBounds indicatorBounds =
+ mDisplayContent.calculatePrivacyIndicatorBoundsForRotation(displayRotation);
+ final DisplayFrames displayFrames = new DisplayFrames(getDisplayId(), new InsetsState(),
+ info, cutout, roundedCorners, indicatorBounds);
+ simulateLayoutDisplay(displayFrames);
+ return displayFrames;
+ }
- if (displayCutout != null) {
- outInsets.left += displayCutout.getSafeInsetLeft();
- outInsets.top += displayCutout.getSafeInsetTop();
- outInsets.right += displayCutout.getSafeInsetRight();
- outInsets.bottom += displayCutout.getSafeInsetBottom();
- }
+ @VisibleForTesting
+ Insets getInsets(DisplayFrames displayFrames, @InsetsType int type) {
+ final InsetsState state = displayFrames.mInsetsState;
+ final Insets insets = state.calculateInsets(state.getDisplayFrame(), type,
+ true /* ignoreVisibility */);
+ return insets;
+ }
+
+ Insets getInsetsWithInternalTypes(DisplayFrames displayFrames,
+ @InternalInsetsType int[] types) {
+ final InsetsState state = displayFrames.mInsetsState;
+ final Insets insets = state.calculateInsetsWithInternalTypes(state.getDisplayFrame(), types,
+ true /* ignoreVisibility */);
+ return insets;
}
@NavigationBarPosition
@@ -2256,17 +2220,6 @@ public class DisplayPolicy {
}
/**
- * @return The side of the screen where navigation bar is positioned.
- * @see WindowManagerPolicyConstants#NAV_BAR_LEFT
- * @see WindowManagerPolicyConstants#NAV_BAR_RIGHT
- * @see WindowManagerPolicyConstants#NAV_BAR_BOTTOM
- */
- @NavigationBarPosition
- public int getNavBarPosition() {
- return mNavigationBarPosition;
- }
-
- /**
* A new window has been focused.
*/
public void focusChangedLw(WindowState lastFocus, WindowState newFocus) {
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index b79c6f44bad5..856430dae6c4 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -847,6 +847,10 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
if (recentsAnimationController != null) {
recentsAnimationController.checkAnimationReady(defaultDisplay.mWallpaperController);
}
+ final BackNaviAnimationController bnac = mWmService.getBackNaviAnimationController();
+ if (bnac != null) {
+ bnac.checkAnimationReady(defaultDisplay.mWallpaperController);
+ }
for (int displayNdx = 0; displayNdx < mChildren.size(); ++displayNdx) {
final DisplayContent displayContent = mChildren.get(displayNdx);
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index e1334dc0ab88..18b0e3311a94 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -1889,8 +1889,7 @@ class Task extends TaskFragment {
}
final int newWinMode = getWindowingMode();
- if ((prevWinMode != newWinMode) && (mDisplayContent != null)
- && shouldStartChangeTransition(prevWinMode, newWinMode)) {
+ if (shouldStartChangeTransition(prevWinMode, mTmpPrevBounds)) {
initializeChangeTransition(mTmpPrevBounds);
}
@@ -2141,10 +2140,16 @@ class Task extends TaskFragment {
bounds.offset(horizontalDiff, verticalDiff);
}
- private boolean shouldStartChangeTransition(int prevWinMode, int newWinMode) {
+ private boolean shouldStartChangeTransition(int prevWinMode, @NonNull Rect prevBounds) {
if (!isLeafTask() || !canStartChangeTransition()) {
return false;
}
+ final int newWinMode = getWindowingMode();
+ if (mTransitionController.inTransition(this)) {
+ final Rect newBounds = getConfiguration().windowConfiguration.getBounds();
+ return prevWinMode != newWinMode || prevBounds.width() != newBounds.width()
+ || prevBounds.height() != newBounds.height();
+ }
// Only do an animation into and out-of freeform mode for now. Other mode
// transition animations are currently handled by system-ui.
return (prevWinMode == WINDOWING_MODE_FREEFORM) != (newWinMode == WINDOWING_MODE_FREEFORM);
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 679a231265d1..da731e842aea 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -98,6 +98,7 @@ import com.android.internal.util.function.pooled.PooledLambda;
import com.android.internal.util.function.pooled.PooledPredicate;
import com.android.server.am.HostingRecord;
import com.android.server.pm.parsing.pkg.AndroidPackage;
+import com.android.server.wm.utils.WmDisplayCutout;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -2208,11 +2209,13 @@ class TaskFragment extends WindowContainer<WindowContainer> {
mTmpBounds.set(0, 0, displayInfo.logicalWidth, displayInfo.logicalHeight);
final DisplayPolicy policy = rootTask.mDisplayContent.getDisplayPolicy();
- policy.getNonDecorInsetsLw(displayInfo.rotation,
- displayInfo.displayCutout, mTmpInsets);
+ final WmDisplayCutout cutout =
+ rootTask.mDisplayContent.calculateDisplayCutoutForRotation(displayInfo.rotation);
+ final DisplayFrames displayFrames = policy.getSimulatedDisplayFrames(displayInfo.rotation,
+ displayInfo.logicalWidth, displayInfo.logicalHeight, cutout);
+ policy.getNonDecorInsetsWithSimulatedFrame(displayFrames, mTmpInsets);
intersectWithInsetsIfFits(outNonDecorBounds, mTmpBounds, mTmpInsets);
-
- policy.convertNonDecorInsetsToStableInsets(mTmpInsets, displayInfo.rotation);
+ policy.getStableInsetsWithSimulatedFrame(displayFrames, mTmpInsets);
intersectWithInsetsIfFits(outStableBounds, mTmpBounds, mTmpInsets);
}
diff --git a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
index d615583f4d7f..d8a054cf45fa 100644
--- a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
@@ -138,12 +138,12 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr
new SparseArray<>();
/**
- * List of {@link TaskFragmentTransaction#getTransactionToken()} that have been sent to the
- * organizer. If the transaction is sent during a transition, the
- * {@link TransitionController} will wait until the transaction is finished.
+ * Map from {@link TaskFragmentTransaction#getTransactionToken()} to the
+ * {@link Transition#getSyncId()} that has been deferred. {@link TransitionController} will
+ * wait until the organizer finished handling the {@link TaskFragmentTransaction}.
* @see #onTransactionFinished(IBinder)
*/
- private final List<IBinder> mRunningTransactions = new ArrayList<>();
+ private final ArrayMap<IBinder, Integer> mDeferredTransitions = new ArrayMap<>();
TaskFragmentOrganizerState(ITaskFragmentOrganizer organizer, int pid, int uid) {
mOrganizer = organizer;
@@ -190,9 +190,9 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr
taskFragment.removeImmediately();
mOrganizedTaskFragments.remove(taskFragment);
}
- for (int i = mRunningTransactions.size() - 1; i >= 0; i--) {
+ for (int i = mDeferredTransitions.size() - 1; i >= 0; i--) {
// Cleanup any running transaction to unblock the current transition.
- onTransactionFinished(mRunningTransactions.get(i));
+ onTransactionFinished(mDeferredTransitions.keyAt(i));
}
mOrganizer.asBinder().unlinkToDeath(this, 0 /*flags*/);
}
@@ -357,19 +357,34 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr
if (!mWindowOrganizerController.getTransitionController().isCollecting()) {
return;
}
+ final int transitionId = mWindowOrganizerController.getTransitionController()
+ .getCollectingTransitionId();
ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
- "Defer transition ready for TaskFragmentTransaction=%s", transactionToken);
- mRunningTransactions.add(transactionToken);
+ "Defer transition id=%d for TaskFragmentTransaction=%s", transitionId,
+ transactionToken);
+ mDeferredTransitions.put(transactionToken, transitionId);
mWindowOrganizerController.getTransitionController().deferTransitionReady();
}
/** Called when the transaction is finished. */
void onTransactionFinished(@NonNull IBinder transactionToken) {
- if (!mRunningTransactions.remove(transactionToken)) {
+ if (!mDeferredTransitions.containsKey(transactionToken)) {
+ return;
+ }
+ final int transitionId = mDeferredTransitions.remove(transactionToken);
+ if (!mWindowOrganizerController.getTransitionController().isCollecting()
+ || mWindowOrganizerController.getTransitionController()
+ .getCollectingTransitionId() != transitionId) {
+ // This can happen when the transition is timeout or abort.
+ ProtoLog.w(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
+ "Deferred transition id=%d has been continued before the"
+ + " TaskFragmentTransaction=%s is finished",
+ transitionId, transactionToken);
return;
}
ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
- "Continue transition ready for TaskFragmentTransaction=%s", transactionToken);
+ "Continue transition id=%d for TaskFragmentTransaction=%s", transitionId,
+ transactionToken);
mWindowOrganizerController.getTransitionController().continueTransitionReady();
}
}
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 2d3e437bed60..8e389d30ffe1 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -16,6 +16,7 @@
package com.android.server.wm;
+import static android.app.ActivityOptions.ANIM_OPEN_CROSS_PROFILE_APPS;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
@@ -68,6 +69,7 @@ import android.app.ActivityManager;
import android.content.pm.ActivityInfo;
import android.graphics.Point;
import android.graphics.Rect;
+import android.hardware.HardwareBuffer;
import android.os.Binder;
import android.os.IBinder;
import android.os.IRemoteCallback;
@@ -205,6 +207,9 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe
/** @see #setCanPipOnFinish */
private boolean mCanPipOnFinish = true;
+ private boolean mIsSeamlessRotation = false;
+ private IContainerFreezer mContainerFreezer = null;
+
Transition(@TransitionType int type, @TransitionFlags int flags,
TransitionController controller, BLASTSyncEngine syncEngine) {
mType = type;
@@ -265,10 +270,31 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe
return mTargetDisplays.contains(dc);
}
+ /** Set a transition to be a seamless-rotation. */
void setSeamlessRotation(@NonNull WindowContainer wc) {
final ChangeInfo info = mChanges.get(wc);
if (info == null) return;
info.mFlags = info.mFlags | ChangeInfo.FLAG_SEAMLESS_ROTATION;
+ onSeamlessRotating(wc.getDisplayContent());
+ }
+
+ /**
+ * Called when it's been determined that this is transition is a seamless rotation. This should
+ * be called before any WM changes have happened.
+ */
+ void onSeamlessRotating(@NonNull DisplayContent dc) {
+ // Don't need to do anything special if everything is using BLAST sync already.
+ if (mSyncEngine.getSyncSet(mSyncId).mSyncMethod == BLASTSyncEngine.METHOD_BLAST) return;
+ if (mContainerFreezer == null) {
+ mContainerFreezer = new ScreenshotFreezer();
+ }
+ mIsSeamlessRotation = true;
+ final WindowState top = dc.getDisplayPolicy().getTopFullscreenOpaqueWindow();
+ if (top != null) {
+ top.mSyncMethodOverride = BLASTSyncEngine.METHOD_BLAST;
+ ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Override sync-method for %s "
+ + "because seamless rotating", top.getName());
+ }
}
/**
@@ -285,6 +311,11 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe
}
}
+ /** Only for testing. */
+ void setContainerFreezer(IContainerFreezer freezer) {
+ mContainerFreezer = freezer;
+ }
+
@TransitionState
int getState() {
return mState;
@@ -314,13 +345,18 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe
return mState == STATE_COLLECTING || mState == STATE_STARTED;
}
- /** Starts collecting phase. Once this starts, all relevant surface operations are sync. */
+ @VisibleForTesting
void startCollecting(long timeoutMs) {
+ startCollecting(timeoutMs, TransitionController.SYNC_METHOD);
+ }
+
+ /** Starts collecting phase. Once this starts, all relevant surface operations are sync. */
+ void startCollecting(long timeoutMs, int method) {
if (mState != STATE_PENDING) {
throw new IllegalStateException("Attempting to re-use a transition");
}
mState = STATE_COLLECTING;
- mSyncId = mSyncEngine.startSyncSet(this, timeoutMs, TAG);
+ mSyncId = mSyncEngine.startSyncSet(this, timeoutMs, TAG, method);
mController.mTransitionTracer.logState(this);
}
@@ -415,6 +451,37 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe
}
/**
+ * Records that a particular container is changing visibly (ie. something about it is changing
+ * while it remains visible). This only effects windows that are already in the collecting
+ * transition.
+ */
+ void collectVisibleChange(WindowContainer wc) {
+ if (mSyncEngine.getSyncSet(mSyncId).mSyncMethod == BLASTSyncEngine.METHOD_BLAST) {
+ // All windows are synced already.
+ return;
+ }
+ if (!isInTransition(wc)) return;
+
+ if (mContainerFreezer == null) {
+ mContainerFreezer = new ScreenshotFreezer();
+ }
+ Transition.ChangeInfo change = mChanges.get(wc);
+ if (change == null || !change.mVisible || !wc.isVisibleRequested()) return;
+ // Note: many more tests have already been done by caller.
+ mContainerFreezer.freeze(wc, change.mAbsoluteBounds);
+ }
+
+ /**
+ * @return {@code true} if `wc` is a participant or is a descendant of one.
+ */
+ boolean isInTransition(WindowContainer wc) {
+ for (WindowContainer p = wc; p != null; p = p.getParent()) {
+ if (mParticipants.contains(p)) return true;
+ }
+ return false;
+ }
+
+ /**
* Specifies configuration change explicitly for the window container, so it can be chosen as
* transition target. This is usually used with transition mode
* {@link android.view.WindowManager#TRANSIT_CHANGE}.
@@ -531,6 +598,10 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe
displays.add(target.getDisplayContent());
}
}
+ // Remove screenshot layers if necessary
+ if (mContainerFreezer != null) {
+ mContainerFreezer.cleanUp(t);
+ }
// Need to update layers on involved displays since they were all paused while
// the animation played. This puts the layers back into the correct order.
mController.mBuildingFinishLayers = true;
@@ -817,6 +888,19 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe
transaction);
if (mOverrideOptions != null) {
info.setAnimationOptions(mOverrideOptions);
+ if (mOverrideOptions.getType() == ANIM_OPEN_CROSS_PROFILE_APPS) {
+ for (int i = 0; i < mTargets.size(); ++i) {
+ final TransitionInfo.Change c = info.getChanges().get(i);
+ final ActivityRecord ar = mTargets.get(i).asActivityRecord();
+ if (ar == null || c.getMode() != TRANSIT_OPEN) continue;
+ int flags = c.getFlags();
+ flags |= ar.mUserId == ar.mWmService.mCurrentUserId
+ ? TransitionInfo.FLAG_CROSS_PROFILE_OWNER_THUMBNAIL
+ : TransitionInfo.FLAG_CROSS_PROFILE_WORK_THUMBNAIL;
+ c.setFlags(flags);
+ break;
+ }
+ }
}
// TODO(b/188669821): Move to animation impl in shell.
@@ -1810,6 +1894,8 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe
*/
void deferTransitionReady() {
++mReadyTracker.mDeferReadyDepth;
+ // Make sure it wait until #continueTransitionReady() is called.
+ mSyncEngine.setReady(mSyncId, false);
}
/** This undoes one call to {@link #deferTransitionReady}. */
@@ -1982,4 +2068,111 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe
return sortedTargets;
}
}
+
+ /**
+ * Interface for freezing a container's content during sync preparation. Really just one impl
+ * but broken into an interface for testing (since you can't take screenshots in unit tests).
+ */
+ interface IContainerFreezer {
+ /**
+ * Makes sure a particular window is "frozen" for the remainder of a sync.
+ *
+ * @return whether the freeze was successful. It fails if `wc` is already in a frozen window
+ * or is not visible/ready.
+ */
+ boolean freeze(@NonNull WindowContainer wc, @NonNull Rect bounds);
+
+ /** Populates `t` with operations that clean-up any state created to set-up the freeze. */
+ void cleanUp(SurfaceControl.Transaction t);
+ }
+
+ /**
+ * Freezes container content by taking a screenshot. Because screenshots are heavy, usage of
+ * any container "freeze" is currently explicit. WM code needs to be prudent about which
+ * containers to freeze.
+ */
+ @VisibleForTesting
+ private class ScreenshotFreezer implements IContainerFreezer {
+ /** Values are the screenshot "surfaces" or null if it was frozen via BLAST override. */
+ private final ArrayMap<WindowContainer, SurfaceControl> mSnapshots = new ArrayMap<>();
+
+ /** Takes a screenshot and puts it at the top of the container's surface. */
+ @Override
+ public boolean freeze(@NonNull WindowContainer wc, @NonNull Rect bounds) {
+ if (!wc.isVisibleRequested()) return false;
+
+ // Check if any parents have already been "frozen". If so, `wc` is already part of that
+ // snapshot, so just skip it.
+ for (WindowContainer p = wc; p != null; p = p.getParent()) {
+ if (mSnapshots.containsKey(p)) return false;
+ }
+
+ if (mIsSeamlessRotation) {
+ WindowState top = wc.getDisplayContent() == null ? null
+ : wc.getDisplayContent().getDisplayPolicy().getTopFullscreenOpaqueWindow();
+ if (top != null && (top == wc || top.isDescendantOf(wc))) {
+ // Don't use screenshots for seamless windows: these will use BLAST even if not
+ // BLAST mode.
+ mSnapshots.put(wc, null);
+ return true;
+ }
+ }
+
+ ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Screenshotting %s [%s]",
+ wc.toString(), bounds.toString());
+
+ Rect cropBounds = new Rect(bounds);
+ cropBounds.offsetTo(0, 0);
+ SurfaceControl.LayerCaptureArgs captureArgs =
+ new SurfaceControl.LayerCaptureArgs.Builder(wc.getSurfaceControl())
+ .setSourceCrop(cropBounds)
+ .setCaptureSecureLayers(true)
+ .setAllowProtected(true)
+ .build();
+ SurfaceControl.ScreenshotHardwareBuffer screenshotBuffer =
+ SurfaceControl.captureLayers(captureArgs);
+ final HardwareBuffer buffer = screenshotBuffer == null ? null
+ : screenshotBuffer.getHardwareBuffer();
+ if (buffer == null || buffer.getWidth() <= 1 || buffer.getHeight() <= 1) {
+ // This can happen when display is not ready.
+ Slog.w(TAG, "Failed to capture screenshot for " + wc);
+ return false;
+ }
+ SurfaceControl snapshotSurface = wc.makeAnimationLeash()
+ .setName("transition snapshot: " + wc.toString())
+ .setOpaque(true)
+ .setParent(wc.getSurfaceControl())
+ .setSecure(screenshotBuffer.containsSecureLayers())
+ .setCallsite("Transition.ScreenshotSync")
+ .setBLASTLayer()
+ .build();
+ mSnapshots.put(wc, snapshotSurface);
+ SurfaceControl.Transaction t = wc.mWmService.mTransactionFactory.get();
+
+ t.setBuffer(snapshotSurface, buffer);
+ t.setDataSpace(snapshotSurface, screenshotBuffer.getColorSpace().getDataSpace());
+ t.show(snapshotSurface);
+
+ // Place it on top of anything else in the container.
+ t.setLayer(snapshotSurface, Integer.MAX_VALUE);
+ t.apply();
+ t.close();
+
+ // Detach the screenshot on the sync transaction (the screenshot is just meant to
+ // freeze the window until the sync transaction is applied (with all its other
+ // corresponding changes), so this is how we unfreeze it.
+ wc.getSyncTransaction().reparent(snapshotSurface, null /* newParent */);
+ return true;
+ }
+
+ @Override
+ public void cleanUp(SurfaceControl.Transaction t) {
+ for (int i = 0; i < mSnapshots.size(); ++i) {
+ SurfaceControl snap = mSnapshots.valueAt(i);
+ // May be null if it was frozen via BLAST override.
+ if (snap == null) continue;
+ t.remove(snap);
+ }
+ }
+ }
}
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index 846aa3e3739a..23928aed6f65 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -46,6 +46,7 @@ import android.window.TransitionInfo;
import android.window.TransitionRequestInfo;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.ProtoLogGroup;
import com.android.internal.protolog.common.ProtoLog;
import com.android.server.LocalServices;
@@ -64,6 +65,11 @@ class TransitionController {
private static final boolean SHELL_TRANSITIONS_ROTATION =
SystemProperties.getBoolean("persist.wm.debug.shell_transit_rotate", false);
+ /** Which sync method to use for transition syncs. */
+ static final int SYNC_METHOD =
+ android.os.SystemProperties.getBoolean("persist.wm.debug.shell_transit_blast", true)
+ ? BLASTSyncEngine.METHOD_BLAST : BLASTSyncEngine.METHOD_NONE;
+
/** The same as legacy APP_TRANSITION_TIMEOUT_MS. */
private static final int DEFAULT_TIMEOUT_MS = 5000;
/** Less duration for CHANGE type because it does not involve app startup. */
@@ -160,6 +166,12 @@ class TransitionController {
/** Starts Collecting */
void moveToCollecting(@NonNull Transition transition) {
+ moveToCollecting(transition, SYNC_METHOD);
+ }
+
+ /** Starts Collecting */
+ @VisibleForTesting
+ void moveToCollecting(@NonNull Transition transition, int method) {
if (mCollectingTransition != null) {
throw new IllegalStateException("Simultaneous transition collection not supported.");
}
@@ -167,7 +179,7 @@ class TransitionController {
// Distinguish change type because the response time is usually expected to be not too long.
final long timeoutMs =
transition.mType == TRANSIT_CHANGE ? CHANGE_TIMEOUT_MS : DEFAULT_TIMEOUT_MS;
- mCollectingTransition.startCollecting(timeoutMs);
+ mCollectingTransition.startCollecting(timeoutMs, method);
ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Start collecting in Transition: %s",
mCollectingTransition);
dispatchLegacyAppTransitionPending();
@@ -215,6 +227,17 @@ class TransitionController {
}
/**
+ * @return the collecting transition sync Id. This should only be called when there is a
+ * collecting transition.
+ */
+ int getCollectingTransitionId() {
+ if (mCollectingTransition == null) {
+ throw new IllegalStateException("There is no collecting transition");
+ }
+ return mCollectingTransition.getSyncId();
+ }
+
+ /**
* @return {@code true} if transition is actively collecting changes and `wc` is one of them.
* This is {@code false} once a transition is playing.
*/
@@ -228,10 +251,7 @@ class TransitionController {
*/
boolean inCollectingTransition(@NonNull WindowContainer wc) {
if (!isCollecting()) return false;
- for (WindowContainer p = wc; p != null; p = p.getParent()) {
- if (mCollectingTransition.mParticipants.contains(p)) return true;
- }
- return false;
+ return mCollectingTransition.isInTransition(wc);
}
/**
@@ -247,9 +267,7 @@ class TransitionController {
*/
boolean inPlayingTransition(@NonNull WindowContainer wc) {
for (int i = mPlayingTransitions.size() - 1; i >= 0; --i) {
- for (WindowContainer p = wc; p != null; p = p.getParent()) {
- if (mPlayingTransitions.get(i).mParticipants.contains(p)) return true;
- }
+ if (mPlayingTransitions.get(i).isInTransition(wc)) return true;
}
return false;
}
@@ -469,6 +487,7 @@ class TransitionController {
void collectForDisplayAreaChange(@NonNull DisplayArea<?> wc) {
final Transition transition = mCollectingTransition;
if (transition == null || !transition.mParticipants.contains(wc)) return;
+ transition.collectVisibleChange(wc);
// Collect all visible tasks.
wc.forAllLeafTasks(task -> {
if (task.isVisible()) {
@@ -488,6 +507,16 @@ class TransitionController {
}
}
+ /**
+ * Records that a particular container is changing visibly (ie. something about it is changing
+ * while it remains visible). This only effects windows that are already in the collecting
+ * transition.
+ */
+ void collectVisibleChange(WindowContainer wc) {
+ if (!isCollecting()) return;
+ mCollectingTransition.collectVisibleChange(wc);
+ }
+
/** @see Transition#mStatusBarTransitionDelay */
void setStatusBarTransitionDelay(long delay) {
if (mCollectingTransition == null) return;
diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java
index 6245005606d7..e7c0a8aba285 100644
--- a/services/core/java/com/android/server/wm/WallpaperController.java
+++ b/services/core/java/com/android/server/wm/WallpaperController.java
@@ -186,7 +186,7 @@ class WallpaperController {
&& animatingContainer.getAnimation() != null
&& animatingContainer.getAnimation().getShowWallpaper();
final boolean hasWallpaper = w.hasWallpaper() || animationWallpaper;
- if (isRecentsTransitionTarget(w)) {
+ if (isRecentsTransitionTarget(w) || isBackAnimationTarget(w)) {
if (DEBUG_WALLPAPER) Slog.v(TAG, "Found recents animation wallpaper target: " + w);
mFindResults.setWallpaperTarget(w);
return true;
@@ -226,6 +226,13 @@ class WallpaperController {
return controller != null && controller.isWallpaperVisible(w);
}
+ private boolean isBackAnimationTarget(WindowState w) {
+ // The window is either the back activity or is in the task animating by the back gesture.
+ final BackNaviAnimationController bthController = mService.getBackNaviAnimationController();
+ return bthController != null && bthController.isWallpaperVisible(w);
+ }
+
+
/**
* @see #computeLastWallpaperZoomOut()
*/
diff --git a/services/core/java/com/android/server/wm/WallpaperWindowToken.java b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
index 6ee30bb956f0..8fdaec613ad5 100644
--- a/services/core/java/com/android/server/wm/WallpaperWindowToken.java
+++ b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
@@ -128,12 +128,15 @@ class WallpaperWindowToken extends WindowToken {
if (visible && wallpaperTarget != null) {
final RecentsAnimationController recentsAnimationController =
mWmService.getRecentsAnimationController();
+ final BackNaviAnimationController bac = mWmService.getBackNaviAnimationController();
if (recentsAnimationController != null
&& recentsAnimationController.isAnimatingTask(wallpaperTarget.getTask())) {
// If the Recents animation is running, and the wallpaper target is the animating
// task we want the wallpaper to be rotated in the same orientation as the
// RecentsAnimation's target (e.g the launcher)
recentsAnimationController.linkFixedRotationTransformIfNeeded(this);
+ } else if (bac != null && bac.isAnimatingTask(wallpaperTarget.getTask())) {
+ bac.linkFixedRotationTransformIfNeeded(this);
} else if ((wallpaperTarget.mActivityRecord == null
// Ignore invisible activity because it may be moving to background.
|| wallpaperTarget.mActivityRecord.mVisibleRequested)
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 797904890f74..92e52de2c01f 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -343,6 +343,7 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
BLASTSyncEngine.SyncGroup mSyncGroup = null;
final SurfaceControl.Transaction mSyncTransaction;
@SyncState int mSyncState = SYNC_STATE_NONE;
+ int mSyncMethodOverride = BLASTSyncEngine.METHOD_UNDEFINED;
private final List<WindowContainerListener> mListeners = new ArrayList<>();
@@ -2825,6 +2826,7 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
*/
void initializeChangeTransition(Rect startBounds, @Nullable SurfaceControl freezeTarget) {
if (mDisplayContent.mTransitionController.isShellTransitionsEnabled()) {
+ mDisplayContent.mTransitionController.collectVisibleChange(this);
// TODO(b/207070762): request shell transition for activityEmbedding change.
return;
}
@@ -3666,6 +3668,7 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
boolean onSyncFinishedDrawing() {
if (mSyncState == SYNC_STATE_NONE) return false;
mSyncState = SYNC_STATE_READY;
+ mSyncMethodOverride = BLASTSyncEngine.METHOD_UNDEFINED;
mWmService.mWindowPlacerLocked.requestTraversal();
ProtoLog.v(WM_DEBUG_SYNC_ENGINE, "onSyncFinishedDrawing %s", this);
return true;
@@ -3684,6 +3687,13 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
mSyncGroup = group;
}
+ @Nullable
+ BLASTSyncEngine.SyncGroup getSyncGroup() {
+ if (mSyncGroup != null) return mSyncGroup;
+ if (mParent != null) return mParent.getSyncGroup();
+ return null;
+ }
+
/**
* Prepares this container for participation in a sync-group. This includes preparing all its
* children.
@@ -3723,6 +3733,7 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
}
if (cancel && mSyncGroup != null) mSyncGroup.onCancelSync(this);
mSyncState = SYNC_STATE_NONE;
+ mSyncMethodOverride = BLASTSyncEngine.METHOD_UNDEFINED;
mSyncGroup = null;
}
@@ -3825,6 +3836,7 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
// disable this when shell transitions is disabled.
if (mTransitionController.isShellTransitionsEnabled()) {
mSyncState = SYNC_STATE_NONE;
+ mSyncMethodOverride = BLASTSyncEngine.METHOD_UNDEFINED;
}
prepareSync();
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index dce0fbe42f3c..285e0ac1c67a 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -93,7 +93,6 @@ import static android.view.WindowManager.fixScale;
import static android.view.WindowManagerGlobal.ADD_OKAY;
import static android.view.WindowManagerGlobal.RELAYOUT_RES_CANCEL_AND_REDRAW;
import static android.view.WindowManagerGlobal.RELAYOUT_RES_SURFACE_CHANGED;
-import static android.view.WindowManagerPolicyConstants.NAV_BAR_INVALID;
import static android.view.WindowManagerPolicyConstants.TYPE_LAYER_MULTIPLIER;
import static android.view.displayhash.DisplayHashResultCallback.DISPLAY_HASH_ERROR_MISSING_WINDOW;
import static android.view.displayhash.DisplayHashResultCallback.DISPLAY_HASH_ERROR_NOT_VISIBLE_ON_SCREEN;
@@ -322,6 +321,7 @@ import com.android.server.policy.WindowManagerPolicy;
import com.android.server.policy.WindowManagerPolicy.ScreenOffListener;
import com.android.server.power.ShutdownThread;
import com.android.server.utils.PriorityDump;
+import com.android.server.wm.utils.WmDisplayCutout;
import dalvik.annotation.optimization.NeverCompile;
@@ -1870,7 +1870,8 @@ public class WindowManagerService extends IWindowManager.Stub
ProtoLog.v(WM_DEBUG_ADD_REMOVE, "addWindow: New client %s"
+ ": window=%s Callers=%s", client.asBinder(), win, Debug.getCallers(5));
- if (win.isVisibleRequestedOrAdding() && displayContent.updateOrientation()) {
+ if ((win.isVisibleRequestedOrAdding() && displayContent.updateOrientation())
+ || win.providesNonDecorInsets()) {
displayContent.sendNewConfiguration();
}
@@ -2587,7 +2588,7 @@ public class WindowManagerService extends IWindowManager.Stub
final int maybeSyncSeqId;
if (mUseBLASTSync && win.useBLASTSync() && viewVisibility != View.GONE
&& win.mSyncSeqId > lastSyncSeqId) {
- maybeSyncSeqId = win.mSyncSeqId;
+ maybeSyncSeqId = win.shouldSyncWithBuffers() ? win.mSyncSeqId : -1;
win.markRedrawForSyncReported();
} else {
maybeSyncSeqId = -1;
@@ -6383,27 +6384,6 @@ public class WindowManagerService extends IWindowManager.Stub
}
}
- /**
- * Used by ActivityManager to determine where to position an app with aspect ratio shorter then
- * the screen is.
- * @see DisplayPolicy#getNavBarPosition()
- */
- @Override
- @WindowManagerPolicy.NavigationBarPosition
- public int getNavBarPosition(int displayId) {
- synchronized (mGlobalLock) {
- // Perform layout if it was scheduled before to make sure that we get correct nav bar
- // position when doing rotations.
- final DisplayContent displayContent = mRoot.getDisplayContent(displayId);
- if (displayContent == null) {
- Slog.w(TAG, "getNavBarPosition with invalid displayId=" + displayId
- + " callers=" + Debug.getCallers(3));
- return NAV_BAR_INVALID;
- }
- return displayContent.getDisplayPolicy().getNavBarPosition();
- }
- }
-
@Override
public void createInputConsumer(IBinder token, String name, int displayId,
InputChannel inputChannel) {
@@ -7242,7 +7222,9 @@ public class WindowManagerService extends IWindowManager.Stub
final DisplayContent dc = mRoot.getDisplayContent(displayId);
if (dc != null) {
final DisplayInfo di = dc.getDisplayInfo();
- dc.getDisplayPolicy().getStableInsetsLw(di.rotation, di.displayCutout, outInsets);
+ final WmDisplayCutout cutout = dc.calculateDisplayCutoutForRotation(di.rotation);
+ dc.getDisplayPolicy().getStableInsetsLw(di.rotation, di.logicalWidth, di.logicalHeight,
+ cutout, outInsets);
}
}
@@ -9298,4 +9280,9 @@ public class WindowManagerService extends IWindowManager.Stub
"Unexpected letterbox background type: " + letterboxBackgroundType);
}
}
+
+ BackNaviAnimationController getBackNaviAnimationController() {
+ return mAtmService.mBackNavigationController != null
+ ? mAtmService.mBackNavigationController.mBackNaviAnimationController : null;
+ }
}
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 68b1d354272d..34a4bf1d9c2a 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -99,6 +99,7 @@ import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.function.IntSupplier;
/**
@@ -1405,7 +1406,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
private BLASTSyncEngine.SyncGroup prepareSyncWithOrganizer(
IWindowContainerTransactionCallback callback) {
final BLASTSyncEngine.SyncGroup s = mService.mWindowManager.mSyncEngine
- .prepareSyncSet(this, "");
+ .prepareSyncSet(this, "", BLASTSyncEngine.METHOD_BLAST);
mTransactionCallbacksByPendingSyncId.put(s.mSyncId, callback);
return s;
}
@@ -1524,7 +1525,6 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
final int type = hop.getType();
// Check for each type of the operations that are allowed for TaskFragmentOrganizer.
switch (type) {
- case HIERARCHY_OP_TYPE_REORDER:
case HIERARCHY_OP_TYPE_DELETE_TASK_FRAGMENT:
enforceTaskFragmentOrganized(func,
WindowContainer.fromBinder(hop.getContainer()), organizer);
@@ -1540,14 +1540,19 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
// We are allowing organizer to create TaskFragment. We will check the
// ownerToken in #createTaskFragment, and trigger error callback if that is not
// valid.
+ break;
case HIERARCHY_OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT:
+ case HIERARCHY_OP_TYPE_REQUEST_FOCUS_ON_TASK_FRAGMENT:
+ enforceTaskFragmentOrganized(func, hop.getContainer(), organizer);
+ break;
case HIERARCHY_OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENT:
+ enforceTaskFragmentOrganized(func, hop.getNewParent(), organizer);
+ break;
case HIERARCHY_OP_TYPE_SET_ADJACENT_TASK_FRAGMENTS:
- case HIERARCHY_OP_TYPE_REQUEST_FOCUS_ON_TASK_FRAGMENT:
- // We are allowing organizer to start/reparent activity to a TaskFragment it
- // created, or set two TaskFragments adjacent to each other. Nothing to check
- // here because the TaskFragment may not be created yet, but will be created in
- // the same transaction.
+ enforceTaskFragmentOrganized(func, hop.getContainer(), organizer);
+ if (hop.getAdjacentRoot() != null) {
+ enforceTaskFragmentOrganized(func, hop.getAdjacentRoot(), organizer);
+ }
break;
case HIERARCHY_OP_TYPE_REPARENT_CHILDREN:
enforceTaskFragmentOrganized(func,
@@ -1570,8 +1575,12 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
}
}
- private void enforceTaskFragmentOrganized(String func, @Nullable WindowContainer wc,
- ITaskFragmentOrganizer organizer) {
+ /**
+ * Makes sure that the given {@link WindowContainer} is a {@link TaskFragment} organized by the
+ * given {@link ITaskFragmentOrganizer}.
+ */
+ private void enforceTaskFragmentOrganized(@NonNull String func, @Nullable WindowContainer wc,
+ @NonNull ITaskFragmentOrganizer organizer) {
if (wc == null) {
Slog.e(TAG, "Attempt to operate on window that no longer exists");
return;
@@ -1588,6 +1597,26 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
}
/**
+ * Makes sure that the {@link TaskFragment} of the given fragment token is created and organized
+ * by the given {@link ITaskFragmentOrganizer}.
+ */
+ private void enforceTaskFragmentOrganized(@NonNull String func,
+ @NonNull IBinder fragmentToken, @NonNull ITaskFragmentOrganizer organizer) {
+ Objects.requireNonNull(fragmentToken);
+ final TaskFragment tf = mLaunchTaskFragments.get(fragmentToken);
+ // When the TaskFragment is {@code null}, it means that the TaskFragment will be created
+ // later in the same transaction, in which case it will always be organized by the given
+ // organizer.
+ if (tf != null && !tf.hasTaskFragmentOrganizer(organizer)) {
+ String msg = "Permission Denial: " + func + " from pid=" + Binder.getCallingPid()
+ + ", uid=" + Binder.getCallingUid() + " trying to modify TaskFragment not"
+ + " belonging to the TaskFragmentOrganizer=" + organizer;
+ Slog.w(TAG, msg);
+ throw new SecurityException(msg);
+ }
+ }
+
+ /**
* Makes sure that SurfaceControl transactions and the ability to set bounds outside of the
* parent bounds are not allowed for embedding without full trust between the host and the
* target.
@@ -1669,6 +1698,13 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
final ITaskFragmentOrganizer organizer = ITaskFragmentOrganizer.Stub.asInterface(
creationParams.getOrganizer().asBinder());
+ if (mLaunchTaskFragments.containsKey(creationParams.getFragmentToken())) {
+ final Throwable exception =
+ new IllegalArgumentException("TaskFragment token must be unique");
+ sendTaskFragmentOperationFailure(organizer, errorCallbackToken, null /* taskFragment */,
+ HIERARCHY_OP_TYPE_CREATE_TASK_FRAGMENT, exception);
+ return;
+ }
if (ownerActivity == null || ownerActivity.getTask() == null) {
final Throwable exception =
new IllegalArgumentException("Not allowed to operate with invalid ownerToken");
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 2432afbc03ef..d79011be7931 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -243,6 +243,7 @@ import android.view.View;
import android.view.ViewDebug;
import android.view.ViewTreeObserver;
import android.view.WindowInfo;
+import android.view.WindowInsets;
import android.view.WindowInsets.Type.InsetsType;
import android.view.WindowManager;
import android.view.animation.Animation;
@@ -391,7 +392,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
*/
int mSyncSeqId = 0;
- /** The last syncId associated with a prepareSync or 0 when no sync is active. */
+ /** The last syncId associated with a BLAST prepareSync or 0 when no BLAST sync is active. */
int mPrepareSyncSeqId = 0;
/**
@@ -1897,6 +1898,19 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
return (mPolicyVisibility & POLICY_VISIBILITY_ALL) == POLICY_VISIBILITY_ALL;
}
+ boolean providesNonDecorInsets() {
+ if (mProvidedInsetsSources == null) {
+ return false;
+ }
+ for (int i = mProvidedInsetsSources.size() - 1; i >= 0; i--) {
+ final int type = mProvidedInsetsSources.keyAt(i);
+ if ((InsetsState.toPublicType(type) & WindowInsets.Type.navigationBars()) != 0) {
+ return true;
+ }
+ }
+ return false;
+ }
+
void clearPolicyVisibilityFlag(int policyVisibilityFlag) {
mPolicyVisibility &= ~policyVisibilityFlag;
mWmService.scheduleAnimationLocked();
@@ -2609,14 +2623,19 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
}
removeImmediately();
- // Removing a visible window will effect the computed orientation
- // So just update orientation if needed.
+ boolean sentNewConfig = false;
if (wasVisible) {
+ // Removing a visible window will effect the computed orientation
+ // So just update orientation if needed.
final DisplayContent displayContent = getDisplayContent();
if (displayContent.updateOrientation()) {
displayContent.sendNewConfiguration();
+ sentNewConfig = true;
}
}
+ if (!sentNewConfig && providesNonDecorInsets()) {
+ getDisplayContent().sendNewConfiguration();
+ }
mWmService.updateFocusedWindowLocked(isFocused()
? UPDATE_FOCUS_REMOVING_FOCUS
: UPDATE_FOCUS_NORMAL,
@@ -3893,9 +3912,10 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
fillClientWindowFramesAndConfiguration(mClientWindowFrames, mLastReportedConfiguration,
true /* useLatestConfig */, false /* relayoutVisible */);
final boolean syncRedraw = shouldSendRedrawForSync();
+ final boolean syncWithBuffers = syncRedraw && shouldSyncWithBuffers();
final boolean reportDraw = syncRedraw || drawPending;
final boolean isDragResizeChanged = isDragResizeChanged();
- final boolean forceRelayout = syncRedraw || isDragResizeChanged;
+ final boolean forceRelayout = syncWithBuffers || isDragResizeChanged;
final DisplayContent displayContent = getDisplayContent();
final boolean alwaysConsumeSystemBars =
displayContent.getDisplayPolicy().areSystemBarsForcedShownLw();
@@ -3921,7 +3941,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
try {
mClient.resized(mClientWindowFrames, reportDraw, mLastReportedConfiguration,
getCompatInsetsState(), forceRelayout, alwaysConsumeSystemBars, displayId,
- mSyncSeqId, resizeMode);
+ syncWithBuffers ? mSyncSeqId : -1, resizeMode);
if (drawPending && prevRotation >= 0 && prevRotation != mLastReportedConfiguration
.getMergedConfiguration().windowConfiguration.getRotation()) {
mOrientationChangeRedrawRequestTime = SystemClock.elapsedRealtime();
@@ -5936,7 +5956,9 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
}
mSyncSeqId++;
- mPrepareSyncSeqId = mSyncSeqId;
+ if (getSyncMethod() == BLASTSyncEngine.METHOD_BLAST) {
+ mPrepareSyncSeqId = mSyncSeqId;
+ }
requestRedrawForSync();
return true;
}
@@ -6009,6 +6031,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
postDrawTransaction = null;
skipLayout = true;
} else if (syncActive) {
+ // Currently in a Sync that is using BLAST.
if (!syncStillPending) {
onSyncFinishedDrawing();
}
@@ -6017,6 +6040,9 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
// Consume the transaction because the sync group will merge it.
postDrawTransaction = null;
}
+ } else if (useBLASTSync()) {
+ // Sync that is not using BLAST
+ onSyncFinishedDrawing();
}
final boolean layoutNeeded =
@@ -6075,6 +6101,18 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
return useBLASTSync();
}
+ int getSyncMethod() {
+ final BLASTSyncEngine.SyncGroup syncGroup = getSyncGroup();
+ if (syncGroup == null) return BLASTSyncEngine.METHOD_NONE;
+ if (mSyncMethodOverride != BLASTSyncEngine.METHOD_UNDEFINED) return mSyncMethodOverride;
+ return syncGroup.mSyncMethod;
+ }
+
+ boolean shouldSyncWithBuffers() {
+ if (!mDrawHandlers.isEmpty()) return true;
+ return getSyncMethod() == BLASTSyncEngine.METHOD_BLAST;
+ }
+
void requestRedrawForSync() {
mRedrawForSyncReported = false;
}
diff --git a/services/core/xsd/display-device-config/autobrightness.xsd b/services/core/xsd/display-device-config/autobrightness.xsd
deleted file mode 100644
index 477625a36cbd..000000000000
--- a/services/core/xsd/display-device-config/autobrightness.xsd
+++ /dev/null
@@ -1,33 +0,0 @@
-<!--
- Copyright (C) 2022 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.
--->
-<xs:schema version="2.0"
- elementFormDefault="qualified"
- xmlns:xs="http://www.w3.org/2001/XMLSchema">
- <xs:complexType name="autoBrightness">
- <xs:sequence>
- <!-- Sets the debounce for autoBrightness brightening in millis-->
- <xs:element name="brighteningLightDebounceMillis" type="xs:nonNegativeInteger"
- minOccurs="0" maxOccurs="1">
- <xs:annotation name="final"/>
- </xs:element>
- <!-- Sets the debounce for autoBrightness darkening in millis-->
- <xs:element name="darkeningLightDebounceMillis" type="xs:nonNegativeInteger"
- minOccurs="0" maxOccurs="1">
- <xs:annotation name="final"/>
- </xs:element>
- </xs:sequence>
- </xs:complexType>
-</xs:schema> \ No newline at end of file
diff --git a/services/core/xsd/display-device-config/display-device-config.xsd b/services/core/xsd/display-device-config/display-device-config.xsd
index bea5e2c2de74..98f83d8c0d09 100644
--- a/services/core/xsd/display-device-config/display-device-config.xsd
+++ b/services/core/xsd/display-device-config/display-device-config.xsd
@@ -23,7 +23,6 @@
<xs:schema version="2.0"
elementFormDefault="qualified"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
- <xs:include schemaLocation="autobrightness.xsd" />
<xs:element name="displayConfiguration">
<xs:complexType>
<xs:sequence>
@@ -343,4 +342,74 @@
<xs:annotation name="final"/>
</xs:element>
</xs:complexType>
+
+ <xs:complexType name="autoBrightness">
+ <xs:sequence>
+ <!-- Sets the debounce for autoBrightness brightening in millis-->
+ <xs:element name="brighteningLightDebounceMillis" type="xs:nonNegativeInteger"
+ minOccurs="0" maxOccurs="1">
+ <xs:annotation name="final"/>
+ </xs:element>
+ <!-- Sets the debounce for autoBrightness darkening in millis-->
+ <xs:element name="darkeningLightDebounceMillis" type="xs:nonNegativeInteger"
+ minOccurs="0" maxOccurs="1">
+ <xs:annotation name="final"/>
+ </xs:element>
+ <!-- Sets the brightness mapping of the desired screen brightness in nits to the
+ corresponding lux for the current display -->
+ <xs:element name="displayBrightnessMapping" type="displayBrightnessMapping"
+ minOccurs="0" maxOccurs="1">
+ <xs:annotation name="final"/>
+ </xs:element>
+ </xs:sequence>
+ </xs:complexType>
+
+ <!-- Represents the brightness mapping of the desired screen brightness in nits to the
+ corresponding lux for the current display -->
+ <xs:complexType name="displayBrightnessMapping">
+ <xs:sequence>
+ <!-- Sets the list of display brightness points, each representing the desired screen
+ brightness in nits to the corresponding lux for the current display
+
+ The N entries of this array define N + 1 control points as follows:
+ (1-based arrays)
+
+ Point 1: (0, nits[1]): currentLux <= 0
+ Point 2: (lux[1], nits[2]): 0 < currentLux <= lux[1]
+ Point 3: (lux[2], nits[3]): lux[2] < currentLux <= lux[3]
+ ...
+ Point N+1: (lux[N], nits[N+1]): lux[N] < currentLux
+
+ The control points must be strictly increasing. Each control point
+ corresponds to an entry in the brightness backlight values arrays.
+ For example, if currentLux == lux[1] (first element of the levels array)
+ then the brightness will be determined by nits[2] (second element
+ of the brightness values array).
+ -->
+ <xs:element name="displayBrightnessPoint" type="displayBrightnessPoint"
+ minOccurs="1" maxOccurs="unbounded">
+ <xs:annotation name="final"/>
+ </xs:element>
+ </xs:sequence>
+ </xs:complexType>
+
+ <!-- Represents a point in the display brightness mapping, representing the lux level from the
+ light sensor to the desired screen brightness in nits at this level -->
+ <xs:complexType name="displayBrightnessPoint">
+ <xs:sequence>
+ <!-- The lux level from the light sensor. This must be a non-negative integer -->
+ <xs:element name="lux" type="xs:nonNegativeInteger"
+ minOccurs="1" maxOccurs="1">
+ <xs:annotation name="final"/>
+ </xs:element>
+
+ <!-- Desired screen brightness in nits corresponding to the suggested lux values.
+ The display brightness is defined as the measured brightness of an all-white image.
+ This must be a non-negative integer -->
+ <xs:element name="nits" type="xs:nonNegativeInteger"
+ minOccurs="1" maxOccurs="1">
+ <xs:annotation name="final"/>
+ </xs:element>
+ </xs:sequence>
+ </xs:complexType>
</xs:schema>
diff --git a/services/core/xsd/display-device-config/schema/current.txt b/services/core/xsd/display-device-config/schema/current.txt
index e9a926946764..e5d26177b725 100644
--- a/services/core/xsd/display-device-config/schema/current.txt
+++ b/services/core/xsd/display-device-config/schema/current.txt
@@ -5,8 +5,10 @@ package com.android.server.display.config {
ctor public AutoBrightness();
method public final java.math.BigInteger getBrighteningLightDebounceMillis();
method public final java.math.BigInteger getDarkeningLightDebounceMillis();
+ method public final com.android.server.display.config.DisplayBrightnessMapping getDisplayBrightnessMapping();
method public final void setBrighteningLightDebounceMillis(java.math.BigInteger);
method public final void setDarkeningLightDebounceMillis(java.math.BigInteger);
+ method public final void setDisplayBrightnessMapping(com.android.server.display.config.DisplayBrightnessMapping);
}
public class BrightnessThresholds {
@@ -43,6 +45,19 @@ package com.android.server.display.config {
method public java.util.List<com.android.server.display.config.Density> getDensity();
}
+ public class DisplayBrightnessMapping {
+ ctor public DisplayBrightnessMapping();
+ method public final java.util.List<com.android.server.display.config.DisplayBrightnessPoint> getDisplayBrightnessPoint();
+ }
+
+ public class DisplayBrightnessPoint {
+ ctor public DisplayBrightnessPoint();
+ method public final java.math.BigInteger getLux();
+ method public final java.math.BigInteger getNits();
+ method public final void setLux(java.math.BigInteger);
+ method public final void setNits(java.math.BigInteger);
+ }
+
public class DisplayConfiguration {
ctor public DisplayConfiguration();
method @NonNull public final com.android.server.display.config.Thresholds getAmbientBrightnessChangeThresholds();
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java b/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java
index 617321beadd2..220cd890e045 100644
--- a/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java
@@ -149,6 +149,12 @@ public class LocalDisplayAdapterTest {
.thenReturn(mockArray);
when(mMockedResources.obtainTypedArray(R.array.config_roundedCornerBottomRadiusArray))
.thenReturn(mockArray);
+ when(mMockedResources.obtainTypedArray(
+ com.android.internal.R.array.config_autoBrightnessDisplayValuesNits))
+ .thenReturn(mockArray);
+ when(mMockedResources.obtainTypedArray(
+ com.android.internal.R.array.config_autoBrightnessLevels))
+ .thenReturn(mockArray);
}
@After
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/BiometricStateCallbackTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/BiometricStateCallbackTest.java
index 5f88c99b1d1e..50769155cfec 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/BiometricStateCallbackTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/BiometricStateCallbackTest.java
@@ -24,7 +24,8 @@ import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import android.hardware.biometrics.BiometricStateListener;
+import android.hardware.biometrics.IBiometricStateListener;
+import android.os.RemoteException;
import android.platform.test.annotations.Presubmit;
import androidx.test.filters.SmallTest;
@@ -45,36 +46,46 @@ public class BiometricStateCallbackTest {
private BiometricStateCallback mCallback;
@Mock
- BiometricStateListener mBiometricStateListener;
+ private IBiometricStateListener.Stub mBiometricStateListener;
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
+ when(mBiometricStateListener.asBinder()).thenReturn(mBiometricStateListener);
+
mCallback = new BiometricStateCallback();
mCallback.registerBiometricStateListener(mBiometricStateListener);
}
@Test
- public void testNoEnrollmentsToEnrollments_callbackNotified() {
+ public void testNoEnrollmentsToEnrollments_callbackNotified() throws RemoteException {
testEnrollmentCallback(true /* changed */, true /* isNowEnrolled */,
true /* expectCallback */, true /* expectedCallbackValue */);
}
@Test
- public void testEnrollmentsToNoEnrollments_callbackNotified() {
+ public void testEnrollmentsToNoEnrollments_callbackNotified() throws RemoteException {
testEnrollmentCallback(true /* changed */, false /* isNowEnrolled */,
true /* expectCallback */, false /* expectedCallbackValue */);
}
@Test
- public void testEnrollmentsToEnrollments_callbackNotNotified() {
+ public void testEnrollmentsToEnrollments_callbackNotNotified() throws RemoteException {
testEnrollmentCallback(false /* changed */, true /* isNowEnrolled */,
false /* expectCallback */, false /* expectedCallbackValue */);
}
+ @Test
+ public void testBinderDeath() throws RemoteException {
+ mCallback.binderDied(mBiometricStateListener.asBinder());
+
+ testEnrollmentCallback(true /* changed */, false /* isNowEnrolled */,
+ false /* expectCallback */, false /* expectedCallbackValue */);
+ }
+
private void testEnrollmentCallback(boolean changed, boolean isNowEnrolled,
- boolean expectCallback, boolean expectedCallbackValue) {
+ boolean expectCallback, boolean expectedCallbackValue) throws RemoteException {
EnrollClient<?> client = mock(EnrollClient.class);
final int userId = 10;
@@ -96,7 +107,7 @@ public class BiometricStateCallbackTest {
}
@Test
- public void testAuthentication_enrollmentCallbackNeverNotified() {
+ public void testAuthentication_enrollmentCallbackNeverNotified() throws RemoteException {
AuthenticationClient<?> client = mock(AuthenticationClient.class);
mCallback.onClientFinished(client, true /* success */);
verify(mBiometricStateListener, never()).onEnrollmentsChanged(anyInt(), anyInt(),
diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java
index 03ea6137074d..261b882319d8 100644
--- a/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java
@@ -19,16 +19,19 @@ package com.android.server.display;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.anyFloat;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import android.content.Context;
import android.content.res.Resources;
+import android.content.res.TypedArray;
import android.platform.test.annotations.Presubmit;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
-
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -52,22 +55,16 @@ public final class DisplayDeviceConfigTest {
private Resources mResources;
@Before
- public void setUp() throws IOException {
+ public void setUp() {
MockitoAnnotations.initMocks(this);
when(mContext.getResources()).thenReturn(mResources);
mockDeviceConfigs();
- try {
- Path tempFile = Files.createTempFile("display_config", ".tmp");
- Files.write(tempFile, getContent().getBytes(StandardCharsets.UTF_8));
- mDisplayDeviceConfig = new DisplayDeviceConfig(mContext);
- mDisplayDeviceConfig.initFromFile(tempFile.toFile());
- } catch (IOException e) {
- throw new IOException("Failed to setup the display device config.", e);
- }
}
@Test
- public void testConfigValues() {
+ public void testConfigValuesFromDisplayConfig() throws IOException {
+ setupDisplayDeviceConfigFromDisplayConfigFile();
+
assertEquals(mDisplayDeviceConfig.getAmbientHorizonLong(), 5000);
assertEquals(mDisplayDeviceConfig.getAmbientHorizonShort(), 50);
assertEquals(mDisplayDeviceConfig.getBrightnessRampDecreaseMaxMillis(), 3000);
@@ -88,10 +85,24 @@ public final class DisplayDeviceConfigTest {
assertEquals(mDisplayDeviceConfig.getScreenDarkeningMinThreshold(), 0.002, 0.000001f);
assertEquals(mDisplayDeviceConfig.getAutoBrightnessBrighteningLightDebounce(), 2000);
assertEquals(mDisplayDeviceConfig.getAutoBrightnessDarkeningLightDebounce(), 1000);
+ assertArrayEquals(mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsLux(), new
+ float[]{50.0f, 80.0f}, 0.0f);
+ assertArrayEquals(mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsNits(), new
+ float[]{45.0f, 75.0f}, 0.0f);
+ // Todo(brup): Add asserts for BrightnessThrottlingData, DensityMapping,
+ // HighBrightnessModeData AmbientLightSensor, RefreshRateLimitations and ProximitySensor.
+ }
+ @Test
+ public void testConfigValuesFromDeviceConfig() {
+ setupDisplayDeviceConfigFromDeviceConfigFile();
+ assertArrayEquals(mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsLux(), new
+ float[]{0.0f, 110.0f, 500.0f}, 0.0f);
+ assertArrayEquals(mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsNits(), new
+ float[]{2.0f, 200.0f, 600.0f}, 0.0f);
// Todo(brup): Add asserts for BrightnessThrottlingData, DensityMapping,
// HighBrightnessModeData AmbientLightSensor, RefreshRateLimitations and ProximitySensor.
- // Also add test for the case where optional display configs are null
+
}
private String getContent() {
@@ -114,6 +125,16 @@ public final class DisplayDeviceConfigTest {
+ "<autoBrightness>\n"
+ "<brighteningLightDebounceMillis>2000</brighteningLightDebounceMillis>\n"
+ "<darkeningLightDebounceMillis>1000</darkeningLightDebounceMillis>\n"
+ + "<displayBrightnessMapping>\n"
+ + "<displayBrightnessPoint>\n"
+ + "<lux>50</lux>\n"
+ + "<nits>45</nits>\n"
+ + "</displayBrightnessPoint>\n"
+ + "<displayBrightnessPoint>\n"
+ + "<lux>80</lux>\n"
+ + "<nits>75</nits>\n"
+ + "</displayBrightnessPoint>\n"
+ + "</displayBrightnessMapping>\n"
+ "</autoBrightness>\n"
+ "<highBrightnessMode enabled=\"true\">\n"
+ "<transitionPoint>0.62</transitionPoint>\n"
@@ -185,4 +206,64 @@ public final class DisplayDeviceConfigTest {
when(mResources.getFloat(com.android.internal.R.dimen
.config_screenBrightnessSettingMaximumFloat)).thenReturn(1.0f);
}
+
+ private void setupDisplayDeviceConfigFromDisplayConfigFile() throws IOException {
+ Path tempFile = Files.createTempFile("display_config", ".tmp");
+ Files.write(tempFile, getContent().getBytes(StandardCharsets.UTF_8));
+ mDisplayDeviceConfig = new DisplayDeviceConfig(mContext);
+ mDisplayDeviceConfig.initFromFile(tempFile.toFile());
+ }
+
+ private void setupDisplayDeviceConfigFromDeviceConfigFile() {
+ TypedArray screenBrightnessNits = createFloatTypedArray(new float[]{2.0f, 250.0f, 650.0f});
+ when(mResources.obtainTypedArray(
+ com.android.internal.R.array.config_screenBrightnessNits))
+ .thenReturn(screenBrightnessNits);
+ TypedArray screenBrightnessBacklight = createFloatTypedArray(new
+ float[]{0.0f, 120.0f, 255.0f});
+ when(mResources.obtainTypedArray(
+ com.android.internal.R.array.config_screenBrightnessBacklight))
+ .thenReturn(screenBrightnessBacklight);
+ when(mResources.getIntArray(com.android.internal.R.array
+ .config_screenBrightnessBacklight)).thenReturn(new int[]{0, 120, 255});
+
+ when(mResources.getIntArray(com.android.internal.R.array
+ .config_autoBrightnessLevels)).thenReturn(new int[]{30, 80});
+ when(mResources.getIntArray(com.android.internal.R.array
+ .config_autoBrightnessDisplayValuesNits)).thenReturn(new int[]{25, 55});
+
+ TypedArray screenBrightnessLevelNits = createFloatTypedArray(new
+ float[]{2.0f, 200.0f, 600.0f});
+ when(mResources.obtainTypedArray(
+ com.android.internal.R.array.config_autoBrightnessDisplayValuesNits))
+ .thenReturn(screenBrightnessLevelNits);
+ TypedArray screenBrightnessLevelLux = createFloatTypedArray(new
+ float[]{0.0f, 110.0f, 500.0f});
+ when(mResources.obtainTypedArray(
+ com.android.internal.R.array.config_autoBrightnessLevels))
+ .thenReturn(screenBrightnessLevelLux);
+
+ mDisplayDeviceConfig = DisplayDeviceConfig.create(mContext, true);
+
+ }
+
+ private TypedArray createFloatTypedArray(float[] vals) {
+ TypedArray mockArray = mock(TypedArray.class);
+ when(mockArray.length()).thenAnswer(invocation -> {
+ return vals.length;
+ });
+ when(mockArray.getFloat(anyInt(), anyFloat())).thenAnswer(invocation -> {
+ final float def = (float) invocation.getArguments()[1];
+ if (vals == null) {
+ return def;
+ }
+ int idx = (int) invocation.getArguments()[0];
+ if (idx >= 0 && idx < vals.length) {
+ return vals[idx];
+ } else {
+ return def;
+ }
+ });
+ return mockArray;
+ }
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index 8b350f4d0ac7..a8b864bab553 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -158,6 +158,7 @@ import androidx.test.filters.MediumTest;
import com.android.internal.R;
import com.android.server.wm.ActivityRecord.State;
+import com.android.server.wm.utils.WmDisplayCutout;
import org.junit.Assert;
import org.junit.Before;
@@ -551,7 +552,8 @@ public class ActivityRecordTests extends WindowTestsBase {
final Rect insets = new Rect();
final DisplayInfo displayInfo = task.mDisplayContent.getDisplayInfo();
final DisplayPolicy policy = task.mDisplayContent.getDisplayPolicy();
- policy.getNonDecorInsetsLw(displayInfo.rotation, displayInfo.displayCutout, insets);
+ policy.getNonDecorInsetsLw(displayInfo.rotation, displayInfo.logicalWidth,
+ displayInfo.logicalHeight, WmDisplayCutout.NO_CUTOUT, insets);
policy.convertNonDecorInsetsToStableInsets(insets, displayInfo.rotation);
Task.intersectWithInsetsIfFits(stableRect, stableRect, insets);
@@ -592,7 +594,8 @@ public class ActivityRecordTests extends WindowTestsBase {
final Rect insets = new Rect();
final DisplayInfo displayInfo = rootTask.mDisplayContent.getDisplayInfo();
final DisplayPolicy policy = rootTask.mDisplayContent.getDisplayPolicy();
- policy.getNonDecorInsetsLw(displayInfo.rotation, displayInfo.displayCutout, insets);
+ policy.getNonDecorInsetsLw(displayInfo.rotation, displayInfo.logicalWidth,
+ displayInfo.logicalHeight, WmDisplayCutout.NO_CUTOUT, insets);
policy.convertNonDecorInsetsToStableInsets(insets, displayInfo.rotation);
Task.intersectWithInsetsIfFits(stableRect, stableRect, insets);
diff --git a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
index c2ca0a227f26..1cd0b198ff5a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
@@ -80,10 +80,12 @@ public class BackNavigationControllerTests extends WindowTestsBase {
IOnBackInvokedCallback callback = withSystemCallback(task);
BackNavigationInfo backNavigationInfo =
- mBackNavigationController.startBackNavigation(true, null);
+ mBackNavigationController.startBackNavigation(true, null, null);
assertWithMessage("BackNavigationInfo").that(backNavigationInfo).isNotNull();
- assertThat(backNavigationInfo.getDepartingAnimationTarget()).isNotNull();
- assertThat(backNavigationInfo.getTaskWindowConfiguration()).isNotNull();
+ if (!BackNavigationController.USE_TRANSITION) {
+ assertThat(backNavigationInfo.getDepartingAnimationTarget()).isNotNull();
+ assertThat(backNavigationInfo.getTaskWindowConfiguration()).isNotNull();
+ }
assertThat(backNavigationInfo.getOnBackInvokedCallback()).isEqualTo(callback);
assertThat(typeToString(backNavigationInfo.getType()))
.isEqualTo(typeToString(BackNavigationInfo.TYPE_RETURN_TO_HOME));
@@ -233,7 +235,7 @@ public class BackNavigationControllerTests extends WindowTestsBase {
@Nullable
private BackNavigationInfo startBackNavigation() {
- return mBackNavigationController.startBackNavigation(true, null);
+ return mBackNavigationController.startBackNavigation(true, null, null);
}
@NonNull
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index be266c9f991e..fd97d910e127 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -1251,7 +1251,15 @@ public class DisplayContentTests extends WindowTestsBase {
public void testComputeImeControlTarget() throws Exception {
final DisplayContent dc = createNewDisplay();
dc.setRemoteInsetsController(createDisplayWindowInsetsController());
- dc.setImeInputTarget(createWindow(null, TYPE_BASE_APPLICATION, "app"));
+ dc.mCurrentFocus = createWindow(null, TYPE_BASE_APPLICATION, "app");
+
+ // Expect returning null IME control target when the focus window has not yet been the
+ // IME input target (e.g. IME is restarting) in fullscreen windowing mode.
+ dc.setImeInputTarget(null);
+ assertFalse(dc.mCurrentFocus.inMultiWindowMode());
+ assertNull(dc.computeImeControlTarget());
+
+ dc.setImeInputTarget(dc.mCurrentFocus);
dc.setImeLayeringTarget(dc.getImeInputTarget().getWindowState());
assertEquals(dc.getImeInputTarget().getWindowState(), dc.computeImeControlTarget());
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyInsetsTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyInsetsTests.java
index f41fee789bf2..a001eda2f86e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyInsetsTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyInsetsTests.java
@@ -25,10 +25,13 @@ import static org.hamcrest.Matchers.equalTo;
import android.graphics.Rect;
import android.platform.test.annotations.Presubmit;
+import android.util.Pair;
import android.view.DisplayInfo;
import androidx.test.filters.SmallTest;
+import com.android.server.wm.utils.WmDisplayCutout;
+
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ErrorCollector;
@@ -46,7 +49,8 @@ public class DisplayPolicyInsetsTests extends DisplayPolicyTestsBase {
@Test
public void portrait() {
- final DisplayInfo di = displayInfoForRotation(ROTATION_0, false /* withCutout */);
+ final Pair<DisplayInfo, WmDisplayCutout> di =
+ displayInfoForRotation(ROTATION_0, false /* withCutout */);
verifyStableInsets(di, 0, STATUS_BAR_HEIGHT, 0, NAV_BAR_HEIGHT);
verifyNonDecorInsets(di, 0, 0, 0, NAV_BAR_HEIGHT);
@@ -55,7 +59,8 @@ public class DisplayPolicyInsetsTests extends DisplayPolicyTestsBase {
@Test
public void portrait_withCutout() {
- final DisplayInfo di = displayInfoForRotation(ROTATION_0, true /* withCutout */);
+ final Pair<DisplayInfo, WmDisplayCutout> di =
+ displayInfoForRotation(ROTATION_0, true /* withCutout */);
verifyStableInsets(di, 0, STATUS_BAR_HEIGHT, 0, NAV_BAR_HEIGHT);
verifyNonDecorInsets(di, 0, DISPLAY_CUTOUT_HEIGHT, 0, NAV_BAR_HEIGHT);
@@ -64,7 +69,8 @@ public class DisplayPolicyInsetsTests extends DisplayPolicyTestsBase {
@Test
public void landscape() {
- final DisplayInfo di = displayInfoForRotation(ROTATION_90, false /* withCutout */);
+ final Pair<DisplayInfo, WmDisplayCutout> di =
+ displayInfoForRotation(ROTATION_90, false /* withCutout */);
if (mDisplayPolicy.navigationBarCanMove()) {
verifyStableInsets(di, 0, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT, 0);
@@ -79,7 +85,8 @@ public class DisplayPolicyInsetsTests extends DisplayPolicyTestsBase {
@Test
public void landscape_withCutout() {
- final DisplayInfo di = displayInfoForRotation(ROTATION_90, true /* withCutout */);
+ final Pair<DisplayInfo, WmDisplayCutout> di =
+ displayInfoForRotation(ROTATION_90, true /* withCutout */);
if (mDisplayPolicy.navigationBarCanMove()) {
verifyStableInsets(di, DISPLAY_CUTOUT_HEIGHT, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT, 0);
@@ -94,7 +101,8 @@ public class DisplayPolicyInsetsTests extends DisplayPolicyTestsBase {
@Test
public void seascape() {
- final DisplayInfo di = displayInfoForRotation(ROTATION_270, false /* withCutout */);
+ final Pair<DisplayInfo, WmDisplayCutout> di =
+ displayInfoForRotation(ROTATION_270, false /* withCutout */);
if (mDisplayPolicy.navigationBarCanMove()) {
verifyStableInsets(di, NAV_BAR_HEIGHT, STATUS_BAR_HEIGHT, 0, 0);
@@ -109,7 +117,8 @@ public class DisplayPolicyInsetsTests extends DisplayPolicyTestsBase {
@Test
public void seascape_withCutout() {
- final DisplayInfo di = displayInfoForRotation(ROTATION_270, true /* withCutout */);
+ final Pair<DisplayInfo, WmDisplayCutout> di =
+ displayInfoForRotation(ROTATION_270, true /* withCutout */);
if (mDisplayPolicy.navigationBarCanMove()) {
verifyStableInsets(di, NAV_BAR_HEIGHT, STATUS_BAR_HEIGHT, DISPLAY_CUTOUT_HEIGHT, 0);
@@ -124,7 +133,8 @@ public class DisplayPolicyInsetsTests extends DisplayPolicyTestsBase {
@Test
public void upsideDown() {
- final DisplayInfo di = displayInfoForRotation(ROTATION_180, false /* withCutout */);
+ final Pair<DisplayInfo, WmDisplayCutout> di =
+ displayInfoForRotation(ROTATION_180, false /* withCutout */);
verifyStableInsets(di, 0, STATUS_BAR_HEIGHT, 0, NAV_BAR_HEIGHT);
verifyNonDecorInsets(di, 0, 0, 0, NAV_BAR_HEIGHT);
@@ -133,28 +143,34 @@ public class DisplayPolicyInsetsTests extends DisplayPolicyTestsBase {
@Test
public void upsideDown_withCutout() {
- final DisplayInfo di = displayInfoForRotation(ROTATION_180, true /* withCutout */);
+ final Pair<DisplayInfo, WmDisplayCutout> di =
+ displayInfoForRotation(ROTATION_180, true /* withCutout */);
verifyStableInsets(di, 0, STATUS_BAR_HEIGHT, 0, NAV_BAR_HEIGHT + DISPLAY_CUTOUT_HEIGHT);
verifyNonDecorInsets(di, 0, 0, 0, NAV_BAR_HEIGHT + DISPLAY_CUTOUT_HEIGHT);
verifyConsistency(di);
}
- private void verifyStableInsets(DisplayInfo di, int left, int top, int right, int bottom) {
- mErrorCollector.checkThat("stableInsets", getStableInsetsLw(di), equalTo(new Rect(
- left, top, right, bottom)));
+ private void verifyStableInsets(Pair<DisplayInfo, WmDisplayCutout> diPair, int left, int top,
+ int right, int bottom) {
+ mErrorCollector.checkThat("stableInsets", getStableInsetsLw(diPair.first, diPair.second),
+ equalTo(new Rect(left, top, right, bottom)));
}
- private void verifyNonDecorInsets(DisplayInfo di, int left, int top, int right, int bottom) {
- mErrorCollector.checkThat("nonDecorInsets", getNonDecorInsetsLw(di), equalTo(new Rect(
+ private void verifyNonDecorInsets(Pair<DisplayInfo, WmDisplayCutout> diPair, int left, int top,
+ int right, int bottom) {
+ mErrorCollector.checkThat("nonDecorInsets",
+ getNonDecorInsetsLw(diPair.first, diPair.second), equalTo(new Rect(
left, top, right, bottom)));
}
- private void verifyConsistency(DisplayInfo di) {
- verifyConsistency("configDisplay", di, getStableInsetsLw(di),
- getConfigDisplayWidth(di), getConfigDisplayHeight(di));
- verifyConsistency("nonDecorDisplay", di, getNonDecorInsetsLw(di),
- getNonDecorDisplayWidth(di), getNonDecorDisplayHeight(di));
+ private void verifyConsistency(Pair<DisplayInfo, WmDisplayCutout> diPair) {
+ final DisplayInfo di = diPair.first;
+ final WmDisplayCutout cutout = diPair.second;
+ verifyConsistency("configDisplay", di, getStableInsetsLw(di, cutout),
+ getConfigDisplayWidth(di, cutout), getConfigDisplayHeight(di, cutout));
+ verifyConsistency("nonDecorDisplay", di, getNonDecorInsetsLw(di, cutout),
+ getNonDecorDisplayWidth(di, cutout), getNonDecorDisplayHeight(di, cutout));
}
private void verifyConsistency(String what, DisplayInfo di, Rect insets, int width,
@@ -165,39 +181,42 @@ public class DisplayPolicyInsetsTests extends DisplayPolicyTestsBase {
equalTo(di.logicalHeight - insets.top - insets.bottom));
}
- private Rect getStableInsetsLw(DisplayInfo di) {
+ private Rect getStableInsetsLw(DisplayInfo di, WmDisplayCutout cutout) {
Rect result = new Rect();
- mDisplayPolicy.getStableInsetsLw(di.rotation, di.displayCutout, result);
+ mDisplayPolicy.getStableInsetsLw(di.rotation, di.logicalWidth, di.logicalHeight,
+ cutout, result);
return result;
}
- private Rect getNonDecorInsetsLw(DisplayInfo di) {
+ private Rect getNonDecorInsetsLw(DisplayInfo di, WmDisplayCutout cutout) {
Rect result = new Rect();
- mDisplayPolicy.getNonDecorInsetsLw(di.rotation, di.displayCutout, result);
+ mDisplayPolicy.getNonDecorInsetsLw(di.rotation, di.logicalWidth, di.logicalHeight,
+ cutout, result);
return result;
}
- private int getNonDecorDisplayWidth(DisplayInfo di) {
- return mDisplayPolicy.getNonDecorDisplayWidth(di.logicalWidth, di.logicalHeight,
- di.rotation, 0 /* ui */, di.displayCutout);
+ private int getNonDecorDisplayWidth(DisplayInfo di, WmDisplayCutout cutout) {
+ return mDisplayPolicy.getNonDecorDisplayFrame(di.logicalWidth, di.logicalHeight,
+ di.rotation, cutout).width();
}
- private int getNonDecorDisplayHeight(DisplayInfo di) {
- return mDisplayPolicy.getNonDecorDisplayHeight(di.logicalHeight, di.rotation,
- di.displayCutout);
+ private int getNonDecorDisplayHeight(DisplayInfo di, WmDisplayCutout cutout) {
+ return mDisplayPolicy.getNonDecorDisplayFrame(di.logicalWidth, di.logicalHeight,
+ di.rotation, cutout).height();
}
- private int getConfigDisplayWidth(DisplayInfo di) {
- return mDisplayPolicy.getConfigDisplayWidth(di.logicalWidth, di.logicalHeight,
- di.rotation, 0 /* ui */, di.displayCutout);
+ private int getConfigDisplayWidth(DisplayInfo di, WmDisplayCutout cutout) {
+ return mDisplayPolicy.getConfigDisplaySize(di.logicalWidth, di.logicalHeight,
+ di.rotation, cutout).x;
}
- private int getConfigDisplayHeight(DisplayInfo di) {
- return mDisplayPolicy.getConfigDisplayHeight(di.logicalWidth, di.logicalHeight,
- di.rotation, 0 /* ui */, di.displayCutout);
+ private int getConfigDisplayHeight(DisplayInfo di, WmDisplayCutout cutout) {
+ return mDisplayPolicy.getConfigDisplaySize(di.logicalWidth, di.logicalHeight,
+ di.rotation, cutout).y;
}
- private static DisplayInfo displayInfoForRotation(int rotation, boolean withDisplayCutout) {
- return displayInfoAndCutoutForRotation(rotation, withDisplayCutout, false).first;
+ private static Pair<DisplayInfo, WmDisplayCutout> displayInfoForRotation(int rotation,
+ boolean withDisplayCutout) {
+ return displayInfoAndCutoutForRotation(rotation, withDisplayCutout, false);
}
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/SyncEngineTests.java b/services/tests/wmtests/src/com/android/server/wm/SyncEngineTests.java
index 420ea8e63562..d3aa073c84d8 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SyncEngineTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SyncEngineTests.java
@@ -16,13 +16,18 @@
package com.android.server.wm;
+import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
+
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+import static com.android.server.wm.BLASTSyncEngine.METHOD_BLAST;
+import static com.android.server.wm.BLASTSyncEngine.METHOD_NONE;
import static com.android.server.wm.WindowContainer.POSITION_BOTTOM;
import static com.android.server.wm.WindowContainer.POSITION_TOP;
import static com.android.server.wm.WindowContainer.SYNC_STATE_NONE;
+import static com.android.server.wm.WindowState.BLAST_TIMEOUT_DURATION;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -67,7 +72,7 @@ public class SyncEngineTests extends WindowTestsBase {
BLASTSyncEngine.TransactionReadyListener listener = mock(
BLASTSyncEngine.TransactionReadyListener.class);
- int id = bse.startSyncSet(listener);
+ int id = startSyncSet(bse, listener);
bse.addToSyncSet(id, mockWC);
// Make sure a traversal is requested
verify(mWm.mWindowPlacerLocked, times(1)).requestTraversal();
@@ -95,7 +100,7 @@ public class SyncEngineTests extends WindowTestsBase {
BLASTSyncEngine.TransactionReadyListener listener = mock(
BLASTSyncEngine.TransactionReadyListener.class);
- int id = bse.startSyncSet(listener);
+ int id = startSyncSet(bse, listener);
bse.addToSyncSet(id, mockWC);
bse.setReady(id);
// Make sure traversals requested (one for add and another for setReady)
@@ -119,7 +124,7 @@ public class SyncEngineTests extends WindowTestsBase {
BLASTSyncEngine.TransactionReadyListener listener = mock(
BLASTSyncEngine.TransactionReadyListener.class);
- int id = bse.startSyncSet(listener);
+ int id = startSyncSet(bse, listener);
bse.addToSyncSet(id, mockWC);
bse.setReady(id);
// Make sure traversals requested (one for add and another for setReady)
@@ -147,7 +152,7 @@ public class SyncEngineTests extends WindowTestsBase {
BLASTSyncEngine.TransactionReadyListener listener = mock(
BLASTSyncEngine.TransactionReadyListener.class);
- int id = bse.startSyncSet(listener);
+ int id = startSyncSet(bse, listener);
bse.addToSyncSet(id, parentWC);
bse.setReady(id);
bse.onSurfacePlacement();
@@ -180,7 +185,7 @@ public class SyncEngineTests extends WindowTestsBase {
BLASTSyncEngine.TransactionReadyListener listener = mock(
BLASTSyncEngine.TransactionReadyListener.class);
- int id = bse.startSyncSet(listener);
+ int id = startSyncSet(bse, listener);
bse.addToSyncSet(id, parentWC);
bse.setReady(id);
bse.onSurfacePlacement();
@@ -211,7 +216,7 @@ public class SyncEngineTests extends WindowTestsBase {
BLASTSyncEngine.TransactionReadyListener listener = mock(
BLASTSyncEngine.TransactionReadyListener.class);
- int id = bse.startSyncSet(listener);
+ int id = startSyncSet(bse, listener);
bse.addToSyncSet(id, parentWC);
bse.setReady(id);
bse.onSurfacePlacement();
@@ -243,7 +248,7 @@ public class SyncEngineTests extends WindowTestsBase {
BLASTSyncEngine.TransactionReadyListener listener = mock(
BLASTSyncEngine.TransactionReadyListener.class);
- int id = bse.startSyncSet(listener);
+ int id = startSyncSet(bse, listener);
bse.addToSyncSet(id, parentWC);
bse.setReady(id);
bse.onSurfacePlacement();
@@ -278,7 +283,7 @@ public class SyncEngineTests extends WindowTestsBase {
BLASTSyncEngine.TransactionReadyListener listener = mock(
BLASTSyncEngine.TransactionReadyListener.class);
- int id = bse.startSyncSet(listener);
+ int id = startSyncSet(bse, listener);
bse.addToSyncSet(id, parentWC);
bse.setReady(id);
bse.onSurfacePlacement();
@@ -317,7 +322,7 @@ public class SyncEngineTests extends WindowTestsBase {
BLASTSyncEngine.TransactionReadyListener listener = mock(
BLASTSyncEngine.TransactionReadyListener.class);
- int id = bse.startSyncSet(listener);
+ int id = startSyncSet(bse, listener);
bse.addToSyncSet(id, parentWC);
final BLASTSyncEngine.SyncGroup syncGroup = parentWC.mSyncGroup;
bse.setReady(id);
@@ -350,6 +355,33 @@ public class SyncEngineTests extends WindowTestsBase {
assertEquals(SYNC_STATE_NONE, botChildWC.mSyncState);
}
+ @Test
+ public void testNonBlastMethod() {
+ mAppWindow = createWindow(null, TYPE_BASE_APPLICATION, "mAppWindow");
+
+ final BLASTSyncEngine bse = createTestBLASTSyncEngine();
+
+ BLASTSyncEngine.TransactionReadyListener listener = mock(
+ BLASTSyncEngine.TransactionReadyListener.class);
+
+ int id = startSyncSet(bse, listener, METHOD_NONE);
+ bse.addToSyncSet(id, mAppWindow.mToken);
+ mAppWindow.prepareSync();
+ assertFalse(mAppWindow.shouldSyncWithBuffers());
+
+ mAppWindow.removeImmediately();
+ }
+
+ static int startSyncSet(BLASTSyncEngine engine,
+ BLASTSyncEngine.TransactionReadyListener listener) {
+ return startSyncSet(engine, listener, METHOD_BLAST);
+ }
+
+ static int startSyncSet(BLASTSyncEngine engine,
+ BLASTSyncEngine.TransactionReadyListener listener, int method) {
+ return engine.startSyncSet(listener, BLAST_TIMEOUT_DURATION, "", method);
+ }
+
static class TestWindowContainer extends WindowContainer {
final boolean mWaiter;
boolean mVisibleRequested = true;
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
index 9274eb3f1490..90ac5880506e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
@@ -434,22 +434,6 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
assertApplyTransactionAllowed(mTransaction);
}
- @Test
- public void testApplyTransaction_enforceHierarchyChange_reorder() throws RemoteException {
- mOrganizer.applyTransaction(mTransaction);
-
- // Throw exception if the transaction is trying to change a window that is not organized by
- // the organizer.
- mTransaction.reorder(mFragmentWindowToken, true /* onTop */);
-
- assertApplyTransactionDisallowed(mTransaction);
-
- // Allow transaction to change a TaskFragment created by the organizer.
- mTaskFragment.setTaskFragmentOrganizer(mOrganizerToken, 10 /* uid */,
- "Test:TaskFragmentOrganizer" /* processName */);
-
- assertApplyTransactionAllowed(mTransaction);
- }
@Test
public void testApplyTransaction_enforceHierarchyChange_deleteTaskFragment()
@@ -531,6 +515,112 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
}
@Test
+ public void testApplyTransaction_enforceTaskFragmentOrganized_startActivityInTaskFragment() {
+ final Task task = createTask(mDisplayContent);
+ final ActivityRecord ownerActivity = createActivityRecord(task);
+ mController.registerOrganizer(mIOrganizer);
+ mTaskFragment = new TaskFragmentBuilder(mAtm)
+ .setParentTask(task)
+ .setFragmentToken(mFragmentToken)
+ .build();
+ mWindowOrganizerController.mLaunchTaskFragments.put(mFragmentToken, mTaskFragment);
+ mTransaction.startActivityInTaskFragment(
+ mFragmentToken, ownerActivity.token, new Intent(), null /* activityOptions */);
+ mOrganizer.applyTransaction(mTransaction);
+
+ // Not allowed because TaskFragment is not organized by the caller organizer.
+ assertApplyTransactionDisallowed(mTransaction);
+
+ mTaskFragment.setTaskFragmentOrganizer(mOrganizerToken, 10 /* uid */,
+ "Test:TaskFragmentOrganizer" /* processName */);
+
+ assertApplyTransactionAllowed(mTransaction);
+ }
+
+ @Test
+ public void testApplyTransaction_enforceTaskFragmentOrganized_reparentActivityInTaskFragment() {
+ final Task task = createTask(mDisplayContent);
+ final ActivityRecord activity = createActivityRecord(task);
+ mController.registerOrganizer(mIOrganizer);
+ mTaskFragment = new TaskFragmentBuilder(mAtm)
+ .setParentTask(task)
+ .setFragmentToken(mFragmentToken)
+ .build();
+ mWindowOrganizerController.mLaunchTaskFragments.put(mFragmentToken, mTaskFragment);
+ mTransaction.reparentActivityToTaskFragment(mFragmentToken, activity.token);
+ mOrganizer.applyTransaction(mTransaction);
+
+ // Not allowed because TaskFragment is not organized by the caller organizer.
+ assertApplyTransactionDisallowed(mTransaction);
+
+ mTaskFragment.setTaskFragmentOrganizer(mOrganizerToken, 10 /* uid */,
+ "Test:TaskFragmentOrganizer" /* processName */);
+
+ assertApplyTransactionAllowed(mTransaction);
+ }
+
+ @Test
+ public void testApplyTransaction_enforceTaskFragmentOrganized_setAdjacentTaskFragments() {
+ final Task task = createTask(mDisplayContent);
+ mController.registerOrganizer(mIOrganizer);
+ mTaskFragment = new TaskFragmentBuilder(mAtm)
+ .setParentTask(task)
+ .setFragmentToken(mFragmentToken)
+ .build();
+ mWindowOrganizerController.mLaunchTaskFragments.put(mFragmentToken, mTaskFragment);
+ final IBinder fragmentToken2 = new Binder();
+ final TaskFragment taskFragment2 = new TaskFragmentBuilder(mAtm)
+ .setParentTask(task)
+ .setFragmentToken(fragmentToken2)
+ .build();
+ mWindowOrganizerController.mLaunchTaskFragments.put(fragmentToken2, taskFragment2);
+ mTransaction.setAdjacentTaskFragments(mFragmentToken, fragmentToken2, null /* params */);
+ mOrganizer.applyTransaction(mTransaction);
+
+ // Not allowed because TaskFragments are not organized by the caller organizer.
+ assertApplyTransactionDisallowed(mTransaction);
+
+ mTaskFragment.setTaskFragmentOrganizer(mOrganizerToken, 10 /* uid */,
+ "Test:TaskFragmentOrganizer" /* processName */);
+
+ // Not allowed because TaskFragment2 is not organized by the caller organizer.
+ assertApplyTransactionDisallowed(mTransaction);
+
+ mTaskFragment.onTaskFragmentOrganizerRemoved();
+ taskFragment2.setTaskFragmentOrganizer(mOrganizerToken, 10 /* uid */,
+ "Test:TaskFragmentOrganizer" /* processName */);
+
+ // Not allowed because mTaskFragment is not organized by the caller organizer.
+ assertApplyTransactionDisallowed(mTransaction);
+
+ mTaskFragment.setTaskFragmentOrganizer(mOrganizerToken, 10 /* uid */,
+ "Test:TaskFragmentOrganizer" /* processName */);
+
+ assertApplyTransactionAllowed(mTransaction);
+ }
+
+ @Test
+ public void testApplyTransaction_enforceTaskFragmentOrganized_requestFocusOnTaskFragment() {
+ final Task task = createTask(mDisplayContent);
+ mController.registerOrganizer(mIOrganizer);
+ mTaskFragment = new TaskFragmentBuilder(mAtm)
+ .setParentTask(task)
+ .setFragmentToken(mFragmentToken)
+ .build();
+ mWindowOrganizerController.mLaunchTaskFragments.put(mFragmentToken, mTaskFragment);
+ mTransaction.requestFocusOnTaskFragment(mFragmentToken);
+ mOrganizer.applyTransaction(mTransaction);
+
+ // Not allowed because TaskFragment is not organized by the caller organizer.
+ assertApplyTransactionDisallowed(mTransaction);
+
+ mTaskFragment.setTaskFragmentOrganizer(mOrganizerToken, 10 /* uid */,
+ "Test:TaskFragmentOrganizer" /* processName */);
+
+ assertApplyTransactionAllowed(mTransaction);
+ }
+
+ @Test
public void testApplyTransaction_createTaskFragment_failForDifferentUid()
throws RemoteException {
mController.registerOrganizer(mIOrganizer);
@@ -592,14 +682,16 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
throws RemoteException {
final Task task = createTask(mDisplayContent);
final ActivityRecord activity = createActivityRecord(task);
+ // Skip manipulate the SurfaceControl.
+ doNothing().when(activity).setDropInputMode(anyInt());
mOrganizer.applyTransaction(mTransaction);
mController.registerOrganizer(mIOrganizer);
mTaskFragment = new TaskFragmentBuilder(mAtm)
.setParentTask(task)
.setFragmentToken(mFragmentToken)
+ .setOrganizer(mOrganizer)
.build();
- mWindowOrganizerController.mLaunchTaskFragments
- .put(mFragmentToken, mTaskFragment);
+ mWindowOrganizerController.mLaunchTaskFragments.put(mFragmentToken, mTaskFragment);
mTransaction.reparentActivityToTaskFragment(mFragmentToken, activity.token);
doReturn(EMBEDDING_ALLOWED).when(mTaskFragment).isAllowedToEmbedActivity(activity);
clearInvocations(mAtm.mRootWindowContainer);
@@ -1119,6 +1211,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
final ArgumentCaptor<WindowContainerTransaction> wctCaptor =
ArgumentCaptor.forClass(WindowContainerTransaction.class);
doReturn(true).when(mTransitionController).isCollecting();
+ doReturn(10).when(mTransitionController).getCollectingTransitionId();
mController.onTaskFragmentAppeared(mTaskFragment.getTaskFragmentOrganizer(), mTaskFragment);
mController.dispatchPendingEvents();
diff --git a/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java b/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java
index 1e64e469fe7f..f5304d00faab 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java
@@ -18,6 +18,13 @@ package com.android.server.wm;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.view.DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS;
+import static android.view.InsetsState.ITYPE_BOTTOM_DISPLAY_CUTOUT;
+import static android.view.InsetsState.ITYPE_CLIMATE_BAR;
+import static android.view.InsetsState.ITYPE_LEFT_DISPLAY_CUTOUT;
+import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
+import static android.view.InsetsState.ITYPE_RIGHT_DISPLAY_CUTOUT;
+import static android.view.InsetsState.ITYPE_STATUS_BAR;
+import static android.view.InsetsState.ITYPE_TOP_DISPLAY_CUTOUT;
import static android.view.WindowManagerPolicyConstants.NAV_BAR_BOTTOM;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
@@ -26,12 +33,9 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyInt;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.eq;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.Mockito.doReturn;
-
import android.annotation.Nullable;
import android.content.Context;
import android.content.res.Configuration;
@@ -43,6 +47,7 @@ import android.util.DisplayMetrics;
import android.view.Display;
import android.view.DisplayCutout;
import android.view.DisplayInfo;
+import android.view.WindowInsets;
import com.android.server.wm.DisplayWindowSettings.SettingsProvider.SettingsEntry;
@@ -204,7 +209,26 @@ class TestDisplayContent extends DisplayContent {
doReturn(true).when(newDisplay).supportsSystemDecorations();
doReturn(true).when(displayPolicy).hasNavigationBar();
doReturn(NAV_BAR_BOTTOM).when(displayPolicy).navigationBarPosition(anyInt());
- doReturn(20).when(displayPolicy).getNavigationBarHeight(anyInt());
+ doReturn(Insets.of(0, 0, 0, 20)).when(displayPolicy).getInsets(any(),
+ eq(WindowInsets.Type.displayCutout() | WindowInsets.Type.navigationBars()));
+ doReturn(Insets.of(0, 20, 0, 20)).when(displayPolicy).getInsets(any(),
+ eq(WindowInsets.Type.displayCutout() | WindowInsets.Type.navigationBars()
+ | WindowInsets.Type.statusBars()));
+ final int[] nonDecorTypes = new int[]{
+ ITYPE_TOP_DISPLAY_CUTOUT, ITYPE_RIGHT_DISPLAY_CUTOUT,
+ ITYPE_BOTTOM_DISPLAY_CUTOUT, ITYPE_LEFT_DISPLAY_CUTOUT, ITYPE_NAVIGATION_BAR
+ };
+ doReturn(Insets.of(0, 0, 0, 20)).when(displayPolicy).getInsetsWithInternalTypes(
+ any(),
+ eq(nonDecorTypes));
+ final int[] stableTypes = new int[]{
+ ITYPE_TOP_DISPLAY_CUTOUT, ITYPE_RIGHT_DISPLAY_CUTOUT,
+ ITYPE_BOTTOM_DISPLAY_CUTOUT, ITYPE_LEFT_DISPLAY_CUTOUT,
+ ITYPE_NAVIGATION_BAR, ITYPE_STATUS_BAR, ITYPE_CLIMATE_BAR
+ };
+ doReturn(Insets.of(0, 20, 0, 20)).when(displayPolicy).getInsetsWithInternalTypes(
+ any(),
+ eq(stableTypes));
} else {
doReturn(false).when(displayPolicy).hasNavigationBar();
doReturn(false).when(displayPolicy).hasStatusBar();
diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
index 0f13fb20a06e..4f68e9809473 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
@@ -56,6 +56,7 @@ import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
+import android.content.res.Configuration;
import android.graphics.Point;
import android.graphics.Rect;
import android.os.IBinder;
@@ -72,6 +73,7 @@ import android.window.RemoteTransition;
import android.window.TaskFragmentOrganizer;
import android.window.TransitionInfo;
+import androidx.annotation.NonNull;
import androidx.test.filters.SmallTest;
import org.junit.Test;
@@ -93,6 +95,7 @@ import java.util.function.Function;
@RunWith(WindowTestRunner.class)
public class TransitionTests extends WindowTestsBase {
final SurfaceControl.Transaction mMockT = mock(SurfaceControl.Transaction.class);
+ private BLASTSyncEngine mSyncEngine;
private Transition createTestTransition(int transitType) {
TransitionTracer tracer = mock(TransitionTracer.class);
@@ -100,8 +103,8 @@ public class TransitionTests extends WindowTestsBase {
mock(ActivityTaskManagerService.class), mock(TaskSnapshotController.class),
mock(TransitionTracer.class));
- final BLASTSyncEngine sync = createTestBLASTSyncEngine();
- final Transition t = new Transition(transitType, 0 /* flags */, controller, sync);
+ mSyncEngine = createTestBLASTSyncEngine();
+ final Transition t = new Transition(transitType, 0 /* flags */, controller, mSyncEngine);
t.startCollecting(0 /* timeoutMs */);
return t;
}
@@ -1114,6 +1117,56 @@ public class TransitionTests extends WindowTestsBase {
assertTrue(targets.contains(activity));
}
+ @Test
+ public void testTransitionVisibleChange() {
+ registerTestTransitionPlayer();
+ final ActivityRecord app = createActivityRecord(mDisplayContent);
+ final Transition transition = new Transition(TRANSIT_OPEN, 0 /* flags */,
+ app.mTransitionController, mWm.mSyncEngine);
+ app.mTransitionController.moveToCollecting(transition, BLASTSyncEngine.METHOD_NONE);
+ final ArrayList<WindowContainer> freezeCalls = new ArrayList<>();
+ transition.setContainerFreezer(new Transition.IContainerFreezer() {
+ @Override
+ public boolean freeze(@NonNull WindowContainer wc, @NonNull Rect bounds) {
+ freezeCalls.add(wc);
+ return true;
+ }
+
+ @Override
+ public void cleanUp(SurfaceControl.Transaction t) {
+ }
+ });
+ final Task task = app.getTask();
+ transition.collect(task);
+ final Rect bounds = new Rect(task.getBounds());
+ Configuration c = new Configuration(task.getRequestedOverrideConfiguration());
+ bounds.inset(10, 10);
+ c.windowConfiguration.setBounds(bounds);
+ task.onRequestedOverrideConfigurationChanged(c);
+ assertTrue(freezeCalls.contains(task));
+ transition.abort();
+ }
+
+ @Test
+ public void testDeferTransitionReady_deferStartedTransition() {
+ final Transition transition = createTestTransition(TRANSIT_OPEN);
+ transition.setAllReady();
+ transition.start();
+
+ assertTrue(mSyncEngine.isReady(transition.getSyncId()));
+
+ transition.deferTransitionReady();
+
+ // Both transition ready tracker and sync engine should be deferred.
+ assertFalse(transition.allReady());
+ assertFalse(mSyncEngine.isReady(transition.getSyncId()));
+
+ transition.continueTransitionReady();
+
+ assertTrue(transition.allReady());
+ assertTrue(mSyncEngine.isReady(transition.getSyncId()));
+ }
+
private static void makeTaskOrganized(Task... tasks) {
final ITaskOrganizer organizer = mock(ITaskOrganizer.class);
for (Task t : tasks) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
index 7d4e6fa53a64..24fc93aa644e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
@@ -42,8 +42,10 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
import static com.android.server.wm.ActivityRecord.State.RESUMED;
+import static com.android.server.wm.BLASTSyncEngine.METHOD_BLAST;
import static com.android.server.wm.WindowContainer.POSITION_TOP;
import static com.android.server.wm.WindowContainer.SYNC_STATE_READY;
+import static com.android.server.wm.WindowState.BLAST_TIMEOUT_DURATION;
import static com.google.common.truth.Truth.assertThat;
@@ -1000,7 +1002,7 @@ public class WindowOrganizerTests extends WindowTestsBase {
BLASTSyncEngine.TransactionReadyListener transactionListener =
mock(BLASTSyncEngine.TransactionReadyListener.class);
- int id = bse.startSyncSet(transactionListener);
+ int id = bse.startSyncSet(transactionListener, BLAST_TIMEOUT_DURATION, "", METHOD_BLAST);
bse.addToSyncSet(id, task);
bse.setReady(id);
bse.onSurfacePlacement();
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index ee5f36412df2..77e12f40f72e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -323,6 +323,10 @@ class WindowTestsBase extends SystemServiceTestsBase {
mNavBarWindow.mAttrs.gravity = Gravity.BOTTOM;
mNavBarWindow.mAttrs.paramsForRotation = new WindowManager.LayoutParams[4];
mNavBarWindow.mAttrs.setFitInsetsTypes(0);
+ mNavBarWindow.mAttrs.layoutInDisplayCutoutMode =
+ LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+ mNavBarWindow.mAttrs.privateFlags |=
+ WindowManager.LayoutParams.PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT;
for (int rot = Surface.ROTATION_0; rot <= Surface.ROTATION_270; rot++) {
mNavBarWindow.mAttrs.paramsForRotation[rot] =
getNavBarLayoutParamsForRotation(rot);
@@ -379,6 +383,9 @@ class WindowTestsBase extends SystemServiceTestsBase {
lp.height = height;
lp.gravity = gravity;
lp.setFitInsetsTypes(0);
+ lp.privateFlags |=
+ WindowManager.LayoutParams.PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT;
+ lp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
return lp;
}
diff --git a/telephony/java/android/telephony/ims/ImsMmTelManager.java b/telephony/java/android/telephony/ims/ImsMmTelManager.java
index 5bae1ad72d93..a6ccb220d74e 100644
--- a/telephony/java/android/telephony/ims/ImsMmTelManager.java
+++ b/telephony/java/android/telephony/ims/ImsMmTelManager.java
@@ -223,6 +223,10 @@ public class ImsMmTelManager implements RegistrationManager {
private final int mSubId;
private final BinderCacheManager<ITelephony> mBinderCache;
+ // Cache Telephony Binder interfaces, one cache per process.
+ private static final BinderCacheManager<ITelephony> sTelephonyCache =
+ new BinderCacheManager<>(ImsMmTelManager::getITelephonyInterface);
+
/**
* Create an instance of {@link ImsMmTelManager} for the subscription id specified.
*
@@ -251,8 +255,7 @@ public class ImsMmTelManager implements RegistrationManager {
throw new IllegalArgumentException("Invalid subscription ID");
}
- return new ImsMmTelManager(subId, new BinderCacheManager<>(
- ImsMmTelManager::getITelephonyInterface));
+ return new ImsMmTelManager(subId, sTelephonyCache);
}
/**
diff --git a/tests/testables/src/android/testing/TestableLooper.java b/tests/testables/src/android/testing/TestableLooper.java
index ebe9b5706bf8..edd6dd3468ef 100644
--- a/tests/testables/src/android/testing/TestableLooper.java
+++ b/tests/testables/src/android/testing/TestableLooper.java
@@ -30,6 +30,7 @@ import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
+import java.lang.reflect.Field;
import java.util.Map;
/**
@@ -45,6 +46,9 @@ public class TestableLooper {
* catch crashes.
*/
public static final boolean HOLD_MAIN_THREAD = false;
+ private static final Field MESSAGE_QUEUE_MESSAGES_FIELD;
+ private static final Field MESSAGE_NEXT_FIELD;
+ private static final Field MESSAGE_WHEN_FIELD;
private Looper mLooper;
private MessageQueue mQueue;
@@ -54,6 +58,19 @@ public class TestableLooper {
private Runnable mEmptyMessage;
private TestLooperManager mQueueWrapper;
+ static {
+ try {
+ MESSAGE_QUEUE_MESSAGES_FIELD = MessageQueue.class.getDeclaredField("mMessages");
+ MESSAGE_QUEUE_MESSAGES_FIELD.setAccessible(true);
+ MESSAGE_NEXT_FIELD = Message.class.getDeclaredField("next");
+ MESSAGE_NEXT_FIELD.setAccessible(true);
+ MESSAGE_WHEN_FIELD = Message.class.getDeclaredField("when");
+ MESSAGE_WHEN_FIELD.setAccessible(true);
+ } catch (NoSuchFieldException e) {
+ throw new RuntimeException("Failed to initialize TestableLooper", e);
+ }
+ }
+
public TestableLooper(Looper l) throws Exception {
this(acquireLooperManager(l), l);
}
@@ -119,6 +136,33 @@ public class TestableLooper {
while (processQueuedMessages() != 0) ;
}
+ public void moveTimeForward(long milliSeconds) {
+ try {
+ Message msg = getMessageLinkedList();
+ while (msg != null) {
+ long updatedWhen = msg.getWhen() - milliSeconds;
+ if (updatedWhen < 0) {
+ updatedWhen = 0;
+ }
+ MESSAGE_WHEN_FIELD.set(msg, updatedWhen);
+ msg = (Message) MESSAGE_NEXT_FIELD.get(msg);
+ }
+ } catch (IllegalAccessException e) {
+ throw new RuntimeException("Access failed in TestableLooper: set - Message.when", e);
+ }
+ }
+
+ private Message getMessageLinkedList() {
+ try {
+ MessageQueue queue = mLooper.getQueue();
+ return (Message) MESSAGE_QUEUE_MESSAGES_FIELD.get(queue);
+ } catch (IllegalAccessException e) {
+ throw new RuntimeException(
+ "Access failed in TestableLooper: get - MessageQueue.mMessages",
+ e);
+ }
+ }
+
private int processQueuedMessages() {
int count = 0;
mEmptyMessage = () -> { };
diff --git a/tests/testables/tests/src/android/testing/TestableLooperTest.java b/tests/testables/tests/src/android/testing/TestableLooperTest.java
index 25f6a48871d3..0f491b86626c 100644
--- a/tests/testables/tests/src/android/testing/TestableLooperTest.java
+++ b/tests/testables/tests/src/android/testing/TestableLooperTest.java
@@ -19,15 +19,19 @@ import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.InOrder;
import android.os.Handler;
import android.os.Looper;
@@ -162,7 +166,7 @@ public class TestableLooperTest {
@Test
public void testCorrectLooperExecution() throws Exception {
- boolean[] hasRun = new boolean[] { false };
+ boolean[] hasRun = new boolean[]{false};
Runnable r = () -> {
assertEquals("Should run on main looper", Looper.getMainLooper(), Looper.myLooper());
hasRun[0] = true;
@@ -177,4 +181,63 @@ public class TestableLooperTest {
testableLooper.destroy();
}
}
+
+ @Test
+ public void testDelayedDispatchNoTimeMove() {
+ Handler handler = spy(new Handler(mTestableLooper.getLooper()));
+ InOrder inOrder = inOrder(handler);
+
+ final Message messageA = handler.obtainMessage(1);
+ final Message messageB = handler.obtainMessage(2);
+
+ handler.sendMessageDelayed(messageA, 0);
+ handler.sendMessageDelayed(messageB, 0);
+
+ mTestableLooper.processAllMessages();
+
+ inOrder.verify(handler).dispatchMessage(messageA);
+ inOrder.verify(handler).dispatchMessage(messageB);
+ }
+
+ @Test
+ public void testDelayedMessageDoesntSend() {
+ Handler handler = spy(new Handler(mTestableLooper.getLooper()));
+ InOrder inOrder = inOrder(handler);
+
+ final Message messageA = handler.obtainMessage(1);
+ final Message messageB = handler.obtainMessage(2);
+ final Message messageC = handler.obtainMessage(3);
+
+ handler.sendMessageDelayed(messageA, 0);
+ handler.sendMessageDelayed(messageB, 0);
+ handler.sendMessageDelayed(messageC, 500);
+
+ mTestableLooper.processAllMessages();
+
+ inOrder.verify(handler).dispatchMessage(messageA);
+ inOrder.verify(handler).dispatchMessage(messageB);
+ verify(handler, never()).dispatchMessage(messageC);
+ }
+
+ @Test
+ public void testMessageSendsAfterDelay() {
+ Handler handler = spy(new Handler(mTestableLooper.getLooper()));
+ InOrder inOrder = inOrder(handler);
+
+ final Message messageA = handler.obtainMessage(1);
+ final Message messageB = handler.obtainMessage(2);
+ final Message messageC = handler.obtainMessage(3);
+
+ handler.sendMessageDelayed(messageA, 0);
+ handler.sendMessageDelayed(messageB, 0);
+ handler.sendMessageDelayed(messageC, 500);
+
+ mTestableLooper.moveTimeForward(500);
+ mTestableLooper.processAllMessages();
+
+ inOrder.verify(handler).dispatchMessage(messageA);
+ inOrder.verify(handler).dispatchMessage(messageB);
+ inOrder.verify(handler).dispatchMessage(messageC);
+ }
+
}
diff --git a/tests/vcn/java/android/net/vcn/persistablebundleutils/IkeSessionParamsUtilsTest.java b/tests/vcn/java/android/net/vcn/persistablebundleutils/IkeSessionParamsUtilsTest.java
index 3b201f9d20dd..e4add8098105 100644
--- a/tests/vcn/java/android/net/vcn/persistablebundleutils/IkeSessionParamsUtilsTest.java
+++ b/tests/vcn/java/android/net/vcn/persistablebundleutils/IkeSessionParamsUtilsTest.java
@@ -16,6 +16,9 @@
package android.net.vcn.persistablebundleutils;
+import static android.net.vcn.persistablebundleutils.IkeSessionParamsUtils.IKE_OPTION_AUTOMATIC_ADDRESS_FAMILY_SELECTION;
+import static android.net.vcn.persistablebundleutils.IkeSessionParamsUtils.IKE_OPTION_AUTOMATIC_NATT_KEEPALIVES;
+import static android.net.vcn.persistablebundleutils.IkeSessionParamsUtils.isIkeOptionValid;
import static android.system.OsConstants.AF_INET;
import static android.system.OsConstants.AF_INET6;
import static android.telephony.TelephonyManager.APPTYPE_USIM;
@@ -134,15 +137,37 @@ public class IkeSessionParamsUtilsTest {
verifyPersistableBundleEncodeDecodeIsLossless(params);
}
+ private static IkeSessionParams.Builder createBuilderMinimumWithEap() throws Exception {
+ final X509Certificate serverCaCert = createCertFromPemFile("self-signed-ca.pem");
+
+ final byte[] eapId = "test@android.net".getBytes(StandardCharsets.US_ASCII);
+ final int subId = 1;
+ final EapSessionConfig eapConfig =
+ new EapSessionConfig.Builder()
+ .setEapIdentity(eapId)
+ .setEapSimConfig(subId, APPTYPE_USIM)
+ .setEapAkaConfig(subId, APPTYPE_USIM)
+ .build();
+ return createBuilderMinimum().setAuthEap(serverCaCert, eapConfig);
+ }
+
@Test
public void testEncodeDecodeParamsWithIkeOptions() throws Exception {
- final IkeSessionParams params =
- createBuilderMinimum()
+ final IkeSessionParams.Builder builder =
+ createBuilderMinimumWithEap()
.addIkeOption(IkeSessionParams.IKE_OPTION_ACCEPT_ANY_REMOTE_ID)
+ .addIkeOption(IkeSessionParams.IKE_OPTION_EAP_ONLY_AUTH)
.addIkeOption(IkeSessionParams.IKE_OPTION_MOBIKE)
+ .addIkeOption(IkeSessionParams.IKE_OPTION_FORCE_PORT_4500)
.addIkeOption(IkeSessionParams.IKE_OPTION_INITIAL_CONTACT)
- .build();
- verifyPersistableBundleEncodeDecodeIsLossless(params);
+ .addIkeOption(IkeSessionParams.IKE_OPTION_REKEY_MOBILITY);
+ if (isIkeOptionValid(IKE_OPTION_AUTOMATIC_ADDRESS_FAMILY_SELECTION)) {
+ builder.addIkeOption(IKE_OPTION_AUTOMATIC_ADDRESS_FAMILY_SELECTION);
+ }
+ if (isIkeOptionValid(IKE_OPTION_AUTOMATIC_NATT_KEEPALIVES)) {
+ builder.addIkeOption(IKE_OPTION_AUTOMATIC_NATT_KEEPALIVES);
+ }
+ verifyPersistableBundleEncodeDecodeIsLossless(builder.build());
}
private static InputStream openAssetsFile(String fileName) throws Exception {
@@ -176,19 +201,7 @@ public class IkeSessionParamsUtilsTest {
@Test
public void testEncodeRecodeParamsWithEapAuth() throws Exception {
- final X509Certificate serverCaCert = createCertFromPemFile("self-signed-ca.pem");
-
- final byte[] eapId = "test@android.net".getBytes(StandardCharsets.US_ASCII);
- final int subId = 1;
- final EapSessionConfig eapConfig =
- new EapSessionConfig.Builder()
- .setEapIdentity(eapId)
- .setEapSimConfig(subId, APPTYPE_USIM)
- .setEapAkaConfig(subId, APPTYPE_USIM)
- .build();
-
- final IkeSessionParams params =
- createBuilderMinimum().setAuthEap(serverCaCert, eapConfig).build();
+ final IkeSessionParams params = createBuilderMinimumWithEap().build();
verifyPersistableBundleEncodeDecodeIsLossless(params);
}
}