summaryrefslogtreecommitdiff
path: root/packages/SystemUI/src
diff options
context:
space:
mode:
Diffstat (limited to 'packages/SystemUI/src')
-rw-r--r--packages/SystemUI/src/com/android/keyguard/DisabledUdfpsController.java112
-rw-r--r--packages/SystemUI/src/com/android/keyguard/DisabledUdfpsView.java7
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java7
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java17
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java5
-rwxr-xr-xpackages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/CornerHandleView.java194
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuView.java76
-rw-r--r--packages/SystemUI/src/com/android/systemui/assist/AssistHandleBehaviorController.java336
-rw-r--r--packages/SystemUI/src/com/android/systemui/assist/AssistHandleCallbacks.java41
-rw-r--r--packages/SystemUI/src/com/android/systemui/assist/AssistHandleLikeHomeBehavior.java172
-rw-r--r--packages/SystemUI/src/com/android/systemui/assist/AssistHandleOffBehavior.java38
-rw-r--r--packages/SystemUI/src/com/android/systemui/assist/AssistHandleReminderExpBehavior.java662
-rw-r--r--packages/SystemUI/src/com/android/systemui/assist/AssistHandleViewController.java191
-rw-r--r--packages/SystemUI/src/com/android/systemui/assist/AssistLogger.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/assist/AssistManager.java25
-rw-r--r--packages/SystemUI/src/com/android/systemui/assist/AssistModule.java27
-rw-r--r--packages/SystemUI/src/com/android/systemui/assist/ui/DefaultUiController.java15
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceToUdfpsView.java147
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceView.java55
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricUdfpsView.java284
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java17
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java55
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationView.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewController.java22
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java18
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapter.java330
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDrawable.java25
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardDrawable.java34
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.java19
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java166
-rw-r--r--packages/SystemUI/src/com/android/systemui/camera/CameraIntents.kt71
-rw-r--r--packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/classifier/FalsingCollector.java14
-rw-r--r--packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorFake.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java13
-rw-r--r--packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt24
-rw-r--r--packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt35
-rw-r--r--packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt23
-rw-r--r--packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinator.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt16
-rw-r--r--packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt71
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java44
-rw-r--r--packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java32
-rw-r--r--packages/SystemUI/src/com/android/systemui/fragments/FragmentService.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java2327
-rw-r--r--packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java2391
-rw-r--r--packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsLayoutLite.java133
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt14
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaDataCombineLatest.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaDataFilter.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt120
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaHost.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaSessionBasedFilter.kt25
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/SmartspaceMediaDataProvider.kt39
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java21
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/buttons/ButtonDispatcher.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java108
-rw-r--r--packages/SystemUI/src/com/android/systemui/people/PeopleSpaceTileView.java30
-rw-r--r--packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java28
-rw-r--r--packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java44
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java37
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSDetail.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSFooterView.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSFooterViewController.java13
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSFragment.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java56
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java17
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/SignalTileView.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tileimpl/HeightOverrideable.kt (renamed from packages/SystemUI/src/com/android/systemui/assist/AssistHandleBehavior.java)20
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileView.java20
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewHorizontal.kt47
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/CameraToggleTile.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt17
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/MicrophoneToggleTile.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java89
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java33
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java15
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureClient.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java30
-rw-r--r--packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java39
-rw-r--r--packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessControllerSettings.java49
-rw-r--r--packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSlider.java60
-rw-r--r--packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderView.java69
-rw-r--r--packages/SystemUI/src/com/android/systemui/settings/brightness/ToggleSlider.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationGroupingUtil.java16
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java17
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/charging/ChargingRippleView.kt22
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/charging/RippleShader.kt134
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java14
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionListener.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java22
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java17
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java14
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java20
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java63
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java14
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowView.java13
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java63
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java27
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java37
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java26
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt67
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/CallbackHandler.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceControlsController.kt35
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceControlsControllerImpl.kt173
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java42
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java59
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java103
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/UserInfoControllerImpl.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/telephony/TelephonyCallback.java24
-rw-r--r--packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java72
-rw-r--r--packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java122
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java57
-rw-r--r--packages/SystemUI/src/com/android/systemui/wallet/dagger/WalletModule.java51
-rw-r--r--packages/SystemUI/src/com/android/systemui/wallet/ui/DotIndicatorDecoration.java181
-rw-r--r--packages/SystemUI/src/com/android/systemui/wallet/ui/WalletActivity.java122
-rw-r--r--packages/SystemUI/src/com/android/systemui/wallet/ui/WalletCardCarousel.java473
-rw-r--r--packages/SystemUI/src/com/android/systemui/wallet/ui/WalletCardView.java54
-rw-r--r--packages/SystemUI/src/com/android/systemui/wallet/ui/WalletCardViewHolder.java42
-rw-r--r--packages/SystemUI/src/com/android/systemui/wallet/ui/WalletCardViewInfo.java50
-rw-r--r--packages/SystemUI/src/com/android/systemui/wallet/ui/WalletScreenController.java330
-rw-r--r--packages/SystemUI/src/com/android/systemui/wallet/ui/WalletView.java242
-rw-r--r--packages/SystemUI/src/com/android/systemui/wmshell/TvPipModule.java13
-rw-r--r--packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/wmshell/WMShellModule.java34
160 files changed, 7147 insertions, 5349 deletions
diff --git a/packages/SystemUI/src/com/android/keyguard/DisabledUdfpsController.java b/packages/SystemUI/src/com/android/keyguard/DisabledUdfpsController.java
index 118f98da11e2..15312ad9dfd1 100644
--- a/packages/SystemUI/src/com/android/keyguard/DisabledUdfpsController.java
+++ b/packages/SystemUI/src/com/android/keyguard/DisabledUdfpsController.java
@@ -18,27 +18,32 @@ package com.android.keyguard;
import static android.hardware.biometrics.BiometricSourceType.FINGERPRINT;
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.InsetDrawable;
import android.hardware.biometrics.BiometricSourceType;
+import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
+import com.android.settingslib.Utils;
import com.android.systemui.Dumpable;
+import com.android.systemui.R;
import com.android.systemui.biometrics.AuthController;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.ViewController;
import java.io.FileDescriptor;
import java.io.PrintWriter;
/**
- * Controls when to show the DisabledUdfpsView to unlock the device on the lockscreen.
- * If the device is not authenticated, the bouncer will show.
+ * Controls when to show the DisabledUdfpsView affordance (unlock icon or circle) on lock screen.
*
- * This tap target will only show when:
+ * This view only exists when:
* - User has UDFPS enrolled
* - UDFPS is currently unavailable see {@link KeyguardUpdateMonitor#shouldListenForUdfps}
*/
@@ -47,48 +52,66 @@ public class DisabledUdfpsController extends ViewController<DisabledUdfpsView> i
@NonNull private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
@NonNull private final KeyguardViewController mKeyguardViewController;
@NonNull private final StatusBarStateController mStatusBarStateController;
+ @NonNull private final KeyguardStateController mKeyguardStateController;
+ @NonNull private final Drawable mButton;
+ @NonNull private final Drawable mUnlockIcon;
private boolean mIsDozing;
private boolean mIsBouncerShowing;
private boolean mIsKeyguardShowing;
private boolean mRunningFPS;
- private boolean mAuthenticated;
+ private boolean mCanDismissLockScreen;
private boolean mShowButton;
+ private boolean mShowUnlockIcon;
public DisabledUdfpsController(
@NonNull DisabledUdfpsView view,
@NonNull StatusBarStateController statusBarStateController,
@NonNull KeyguardUpdateMonitor keyguardUpdateMonitor,
@NonNull AuthController authController,
- @NonNull KeyguardViewController keyguardViewController
+ @NonNull KeyguardViewController keyguardViewController,
+ @NonNull KeyguardStateController keyguardStateController
) {
super(view);
- mView.setOnClickListener(mOnClickListener);
+ mView.setOnTouchListener(mOnTouchListener);
mView.setSensorProperties(authController.getUdfpsProps().get(0));
mStatusBarStateController = statusBarStateController;
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
mKeyguardViewController = keyguardViewController;
+ mKeyguardStateController = keyguardStateController;
+
+ final Context context = view.getContext();
+ mButton = context.getResources().getDrawable(
+ com.android.systemui.R.drawable.circle_white, context.getTheme());
+ mUnlockIcon = new InsetDrawable(context.getResources().getDrawable(
+ com.android.internal.R.drawable.ic_lock_open, context.getTheme()),
+ context.getResources().getDimensionPixelSize(
+ com.android.systemui.R.dimen.udfps_unlock_icon_inset));
}
@Override
protected void onViewAttached() {
mIsBouncerShowing = mKeyguardViewController.isBouncerShowing();
- mIsKeyguardShowing = mStatusBarStateController.getState() == StatusBarState.KEYGUARD;
+ mIsKeyguardShowing = mKeyguardStateController.isShowing();
mIsDozing = mStatusBarStateController.isDozing();
mRunningFPS = mKeyguardUpdateMonitor.isFingerprintDetectionRunning();
- mAuthenticated = false;
- updateButtonVisibility();
+ mCanDismissLockScreen = mKeyguardStateController.canDismissLockScreen();
+ mUnlockIcon.setTint(Utils.getColorAttrDefaultColor(mView.getContext(),
+ R.attr.wallpaperTextColorAccent));
+ updateVisibility();
mKeyguardUpdateMonitor.registerCallback(mKeyguardUpdateMonitorCallback);
mStatusBarStateController.addCallback(mStatusBarStateListener);
+ mKeyguardStateController.addCallback(mKeyguardStateCallback);
}
@Override
protected void onViewDetached() {
mKeyguardUpdateMonitor.removeCallback(mKeyguardUpdateMonitorCallback);
mStatusBarStateController.removeCallback(mStatusBarStateListener);
+ mKeyguardStateController.removeCallback(mKeyguardStateCallback);
}
/**
@@ -100,46 +123,51 @@ public class DisabledUdfpsController extends ViewController<DisabledUdfpsView> i
}
}
- private void updateButtonVisibility() {
- mShowButton = !mAuthenticated && !mIsDozing && mIsKeyguardShowing
- && !mIsBouncerShowing && !mRunningFPS;
+ private void updateVisibility() {
+ mShowButton = !mCanDismissLockScreen && !mRunningFPS && isLockScreen();
+ mShowUnlockIcon = mCanDismissLockScreen && isLockScreen();
+
if (mShowButton) {
+ mView.setImageDrawable(mButton);
+ mView.setVisibility(View.VISIBLE);
+ } else if (mShowUnlockIcon) {
+ mView.setImageDrawable(mUnlockIcon);
mView.setVisibility(View.VISIBLE);
} else {
mView.setVisibility(View.INVISIBLE);
}
}
+ private boolean isLockScreen() {
+ return mIsKeyguardShowing && !mIsDozing && !mIsBouncerShowing;
+ }
+
@Override
public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) {
pw.println("DisabledUdfpsController state:");
pw.println(" mShowBouncerButton: " + mShowButton);
+ pw.println(" mShowUnlockIcon: " + mShowUnlockIcon);
pw.println(" mIsDozing: " + mIsDozing);
pw.println(" mIsKeyguardShowing: " + mIsKeyguardShowing);
pw.println(" mIsBouncerShowing: " + mIsBouncerShowing);
pw.println(" mRunningFPS: " + mRunningFPS);
- pw.println(" mAuthenticated: " + mAuthenticated);
+ pw.println(" mCanDismissLockScreen: " + mCanDismissLockScreen);
}
- private final View.OnClickListener mOnClickListener = new View.OnClickListener() {
+ private final View.OnTouchListener mOnTouchListener = new View.OnTouchListener() {
@Override
- public void onClick(View v) {
+ public boolean onTouch(View v, MotionEvent event) {
mKeyguardViewController.showBouncer(/* scrim */ true);
+ return true;
}
};
private StatusBarStateController.StateListener mStatusBarStateListener =
new StatusBarStateController.StateListener() {
@Override
- public void onStateChanged(int newState) {
- mIsKeyguardShowing = newState == StatusBarState.KEYGUARD;
- updateButtonVisibility();
- }
-
- @Override
public void onDozingChanged(boolean isDozing) {
mIsDozing = isDozing;
- updateButtonVisibility();
+ updateVisibility();
}
};
@@ -148,7 +176,7 @@ public class DisabledUdfpsController extends ViewController<DisabledUdfpsView> i
@Override
public void onKeyguardBouncerChanged(boolean bouncer) {
mIsBouncerShowing = bouncer;
- updateButtonVisibility();
+ updateVisibility();
}
@Override
@@ -157,21 +185,29 @@ public class DisabledUdfpsController extends ViewController<DisabledUdfpsView> i
if (biometricSourceType == FINGERPRINT) {
mRunningFPS = running;
}
- mAuthenticated &= !mRunningFPS;
- updateButtonVisibility();
- }
- @Override
- public void onBiometricAuthenticated(int userId,
- BiometricSourceType biometricSourceType, boolean isStrongBiometric) {
- mAuthenticated = true;
- updateButtonVisibility();
- }
-
- @Override
- public void onUserUnlocked() {
- mAuthenticated = true;
- updateButtonVisibility();
+ updateVisibility();
}
};
+
+ private final KeyguardStateController.Callback mKeyguardStateCallback =
+ new KeyguardStateController.Callback() {
+ @Override
+ public void onKeyguardShowingChanged() {
+ updateIsKeyguardShowing();
+ updateVisibility();
+ }
+
+ @Override
+ public void onUnlockedChanged() {
+ updateIsKeyguardShowing();
+ mCanDismissLockScreen = mKeyguardStateController.canDismissLockScreen();
+ updateVisibility();
+ }
+
+ private void updateIsKeyguardShowing() {
+ mIsKeyguardShowing = mKeyguardStateController.isShowing()
+ && !mKeyguardStateController.isKeyguardGoingAway();
+ }
+ };
}
diff --git a/packages/SystemUI/src/com/android/keyguard/DisabledUdfpsView.java b/packages/SystemUI/src/com/android/keyguard/DisabledUdfpsView.java
index d8ab780eb6bd..8ae753e7f3f7 100644
--- a/packages/SystemUI/src/com/android/keyguard/DisabledUdfpsView.java
+++ b/packages/SystemUI/src/com/android/keyguard/DisabledUdfpsView.java
@@ -22,14 +22,13 @@ import android.graphics.RectF;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.util.AttributeSet;
import android.view.Surface;
-import android.widget.Button;
import android.widget.FrameLayout;
+import android.widget.ImageView;
/**
- * A full screen view with an oval target where the UDFPS sensor is.
- * Controlled by {@link DisabledUdfpsController}.
+ * A view positioned in the area of the UDPFS sensor.
*/
-public class DisabledUdfpsView extends Button {
+public class DisabledUdfpsView extends ImageView {
@NonNull private final RectF mSensorRect;
@NonNull private final Context mContext;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java
index 02a8958ef657..31f1332b265c 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java
@@ -492,4 +492,11 @@ public class KeyguardHostViewController extends ViewController<KeyguardHostView>
mKeyguardSecurityContainerController.updateResources();
}
}
+
+ /** Update keyguard position based on a tapped X coordinate. */
+ public void updateKeyguardPosition(float x) {
+ if (mKeyguardSecurityContainerController != null) {
+ mKeyguardSecurityContainerController.updateKeyguardPosition(x);
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
index 708b2d55b75a..7ed63375a334 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
@@ -267,6 +267,13 @@ public class KeyguardSecurityContainer extends FrameLayout {
updateSecurityViewLocation(false);
}
+ /** Update keyguard position based on a tapped X coordinate. */
+ public void updateKeyguardPosition(float x) {
+ if (mOneHandedMode) {
+ moveBouncerForXCoordinate(x, /* animate= */false);
+ }
+ }
+
/** Return whether the one-handed keyguard should be enabled. */
private boolean canUseOneHandedBouncer() {
// Is it enabled?
@@ -488,9 +495,13 @@ public class KeyguardSecurityContainer extends FrameLayout {
return;
}
+ moveBouncerForXCoordinate(event.getX(), /* animate= */true);
+ }
+
+ private void moveBouncerForXCoordinate(float x, boolean animate) {
// Did the tap hit the "other" side of the bouncer?
- if ((mIsSecurityViewLeftAligned && (event.getX() > getWidth() / 2f))
- || (!mIsSecurityViewLeftAligned && (event.getX() < getWidth() / 2f))) {
+ if ((mIsSecurityViewLeftAligned && (x > getWidth() / 2f))
+ || (!mIsSecurityViewLeftAligned && (x < getWidth() / 2f))) {
mIsSecurityViewLeftAligned = !mIsSecurityViewLeftAligned;
Settings.Global.putInt(
@@ -499,7 +510,7 @@ public class KeyguardSecurityContainer extends FrameLayout {
mIsSecurityViewLeftAligned ? Settings.Global.ONE_HANDED_KEYGUARD_SIDE_LEFT
: Settings.Global.ONE_HANDED_KEYGUARD_SIDE_RIGHT);
- updateSecurityViewLocation(true);
+ updateSecurityViewLocation(animate);
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index 760eaecae247..4827cab3b5c0 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -515,6 +515,11 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
}
}
+ /** Update keyguard position based on a tapped X coordinate. */
+ public void updateKeyguardPosition(float x) {
+ mView.updateKeyguardPosition(x);
+ }
+
static class Factory {
private final KeyguardSecurityContainer mView;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 825affef9dbc..36109d691808 100755
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -428,6 +428,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
public void onTrustChanged(boolean enabled, int userId, int flags) {
Assert.isMainThread();
mUserHasTrust.put(userId, enabled);
+ updateBiometricListeningState();
for (int i = 0; i < mCallbacks.size(); i++) {
KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
if (cb != null) {
@@ -628,6 +629,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
}
// Don't send cancel if authentication succeeds
mFingerprintCancelSignal = null;
+ updateBiometricListeningState();
for (int i = 0; i < mCallbacks.size(); i++) {
KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
if (cb != null) {
@@ -811,6 +813,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
}
// Don't send cancel if authentication succeeds
mFaceCancelSignal = null;
+ updateBiometricListeningState();
for (int i = 0; i < mCallbacks.size(); i++) {
KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
if (cb != null) {
@@ -1775,6 +1778,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
break;
case MSG_TIME_FORMAT_UPDATE:
handleTimeFormatUpdate((String) msg.obj);
+ break;
case MSG_REQUIRE_NFC_UNLOCK:
handleRequireUnlockForNfc();
break;
@@ -2110,6 +2114,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
boolean shouldListenForUdfps() {
return shouldListenForFingerprint()
&& !mBouncer
+ && !getUserCanSkipBouncer(getCurrentUser())
+ && !isEncryptedOrLockdown(getCurrentUser())
&& mStrongAuthTracker.hasUserAuthenticatedSinceBoot();
}
diff --git a/packages/SystemUI/src/com/android/systemui/CornerHandleView.java b/packages/SystemUI/src/com/android/systemui/CornerHandleView.java
deleted file mode 100644
index e52245b83e19..000000000000
--- a/packages/SystemUI/src/com/android/systemui/CornerHandleView.java
+++ /dev/null
@@ -1,194 +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;
-
-import android.animation.ArgbEvaluator;
-import android.content.Context;
-import android.graphics.Canvas;
-import android.graphics.Paint;
-import android.graphics.Path;
-import android.graphics.RectF;
-import android.util.AttributeSet;
-import android.util.DisplayMetrics;
-import android.view.ContextThemeWrapper;
-import android.view.View;
-import android.os.SystemProperties;
-
-import com.android.settingslib.Utils;
-
-/**
- * CornerHandleView draws an inset arc intended to be displayed within the screen decoration
- * corners.
- */
-public class CornerHandleView extends View {
- private static final float STROKE_DP_LARGE = 2f;
- private static final float STROKE_DP_SMALL = 1.95f;
- // Radius to use if none is available.
- private static final int FALLBACK_RADIUS_DP = 15;
- private static final float MARGIN_DP = 8;
- private static final int MAX_ARC_DEGREES = 90;
- // Arc length along the phone's perimeter used to measure the desired angle.
- private static final float ARC_LENGTH_DP = 31f;
- private static int mDisableRoundedCorner =
- SystemProperties.getInt("vendor.display.disable_rounded_corner", 0);
-
- private Paint mPaint;
- private int mLightColor;
- private int mDarkColor;
- private Path mPath;
- private boolean mRequiresInvalidate;
-
- public CornerHandleView(Context context, AttributeSet attrs) {
- super(context, attrs);
-
- mPaint = new Paint();
- mPaint.setAntiAlias(true);
- mPaint.setStyle(Paint.Style.STROKE);
- mPaint.setStrokeCap(Paint.Cap.ROUND);
- mPaint.setStrokeWidth(getStrokePx());
-
- final int dualToneDarkTheme = Utils.getThemeAttr(mContext, R.attr.darkIconTheme);
- final int dualToneLightTheme = Utils.getThemeAttr(mContext, R.attr.lightIconTheme);
- Context lightContext = new ContextThemeWrapper(mContext, dualToneLightTheme);
- Context darkContext = new ContextThemeWrapper(mContext, dualToneDarkTheme);
- mLightColor = Utils.getColorAttrDefaultColor(lightContext, R.attr.singleToneColor);
- mDarkColor = Utils.getColorAttrDefaultColor(darkContext, R.attr.singleToneColor);
-
- updatePath();
- }
-
- @Override
- public void setAlpha(float alpha) {
- super.setAlpha(alpha);
- if (alpha > 0f && mRequiresInvalidate) {
- mRequiresInvalidate = false;
- invalidate();
- }
- }
-
- private void updatePath() {
- mPath = new Path();
-
- float marginPx = getMarginPx();
- float radiusPx = getInnerRadiusPx();
- float halfStrokePx = getStrokePx() / 2f;
- float angle = getAngle();
- float startAngle = 180 + ((90 - angle) / 2);
- RectF circle = new RectF(marginPx + halfStrokePx,
- marginPx + halfStrokePx,
- marginPx + 2 * radiusPx - halfStrokePx,
- marginPx + 2 * radiusPx - halfStrokePx);
-
- if (angle >= 90f) {
- float innerCircumferenceDp = convertPixelToDp(radiusPx * 2 * (float) Math.PI,
- mContext);
- float arcDp = innerCircumferenceDp * getAngle() / 360f;
- // Add additional "arms" to the two ends of the arc. The length computation is
- // hand-tuned.
- float lineLengthPx = convertDpToPixel((ARC_LENGTH_DP - arcDp - MARGIN_DP) / 2,
- mContext);
-
- mPath.moveTo(marginPx + halfStrokePx, marginPx + radiusPx + lineLengthPx);
- mPath.lineTo(marginPx + halfStrokePx, marginPx + radiusPx);
- mPath.arcTo(circle, startAngle, angle);
- mPath.moveTo(marginPx + radiusPx, marginPx + halfStrokePx);
- mPath.lineTo(marginPx + radiusPx + lineLengthPx, marginPx + halfStrokePx);
- } else {
- mPath.arcTo(circle, startAngle, angle);
- }
- }
-
- /**
- * Receives an intensity from 0 (lightest) to 1 (darkest) and sets the handle color
- * appropriately. Intention is to match the home handle color.
- */
- public void updateDarkness(float darkIntensity) {
- // Handle color is same as home handle color.
- int color = (int) ArgbEvaluator.getInstance().evaluate(darkIntensity,
- mLightColor, mDarkColor);
- if (mPaint.getColor() != color) {
- mPaint.setColor(color);
- if (getVisibility() == VISIBLE && getAlpha() > 0) {
- invalidate();
- } else {
- // If we are currently invisible, then invalidate when we are next made visible
- mRequiresInvalidate = true;
- }
- }
- }
-
- @Override
- public void onDraw(Canvas canvas) {
- super.onDraw(canvas);
- canvas.drawPath(mPath, mPaint);
- }
-
- private static float convertDpToPixel(float dp, Context context) {
- return dp * ((float) context.getResources().getDisplayMetrics().densityDpi
- / DisplayMetrics.DENSITY_DEFAULT);
- }
-
- private static float convertPixelToDp(float px, Context context) {
- return px * DisplayMetrics.DENSITY_DEFAULT
- / ((float) context.getResources().getDisplayMetrics().densityDpi);
- }
-
- private float getAngle() {
- // Measure a length of ARC_LENGTH_DP along the *screen's* perimeter, get the angle and cap
- // it at 90.
- float circumferenceDp = convertPixelToDp((
- getOuterRadiusPx()) * 2 * (float) Math.PI, mContext);
- float angleDeg = (ARC_LENGTH_DP / circumferenceDp) * 360;
- if (angleDeg > MAX_ARC_DEGREES) {
- angleDeg = MAX_ARC_DEGREES;
- }
- return angleDeg;
- }
-
- private float getMarginPx() {
- return convertDpToPixel(MARGIN_DP, mContext);
- }
-
- private float getInnerRadiusPx() {
- return getOuterRadiusPx() - getMarginPx();
- }
-
- private float getOuterRadiusPx() {
- // Attempt to get the bottom corner radius, otherwise fall back on the generic or top
- // values. If none are available, use the FALLBACK_RADIUS_DP.
- int radius = getResources().getDimensionPixelSize(
- com.android.systemui.R.dimen.config_rounded_mask_size_bottom);
- if (radius == 0 && mDisableRoundedCorner == 0) {
- radius = getResources().getDimensionPixelSize(
- com.android.systemui.R.dimen.config_rounded_mask_size);
- }
- if (radius == 0) {
- radius = getResources().getDimensionPixelSize(
- com.android.systemui.R.dimen.config_rounded_mask_size_top);
- }
- if (radius == 0) {
- radius = (int) convertDpToPixel(FALLBACK_RADIUS_DP, mContext);
- }
- return radius;
- }
-
- private float getStrokePx() {
- // Use a slightly smaller stroke if we need to cover the full corner angle.
- return convertDpToPixel((getAngle() < 90) ? STROKE_DP_LARGE : STROKE_DP_SMALL,
- getContext());
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuView.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuView.java
index ab05c2a273ad..57be4e8477b2 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuView.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuView.java
@@ -31,6 +31,7 @@ import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable;
import android.graphics.drawable.LayerDrawable;
+import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.util.DisplayMetrics;
@@ -39,6 +40,8 @@ import android.view.MotionEvent;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.WindowManager;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
import android.view.animation.OvershootInterpolator;
import android.widget.FrameLayout;
@@ -284,6 +287,44 @@ public class AccessibilityFloatingMenuView extends FrameLayout
// Do Nothing
}
+ @Override
+ public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfo(info);
+ setupAccessibilityActions(info);
+ }
+
+ @Override
+ public boolean performAccessibilityAction(int action, Bundle arguments) {
+ if (super.performAccessibilityAction(action, arguments)) {
+ return true;
+ }
+
+ fadeIn();
+
+ final Rect bounds = getAvailableBounds();
+ if (action == R.id.action_move_top_left) {
+ snapToLocation(bounds.left, bounds.top);
+ return true;
+ }
+
+ if (action == R.id.action_move_top_right) {
+ snapToLocation(bounds.right, bounds.top);
+ return true;
+ }
+
+ if (action == R.id.action_move_bottom_left) {
+ snapToLocation(bounds.left, bounds.bottom);
+ return true;
+ }
+
+ if (action == R.id.action_move_bottom_right) {
+ snapToLocation(bounds.right, bounds.bottom);
+ return true;
+ }
+
+ return false;
+ }
+
void show() {
if (isShowing()) {
return;
@@ -380,6 +421,33 @@ public class AccessibilityFloatingMenuView extends FrameLayout
mUiHandler.postDelayed(() -> mFadeOutAnimator.start(), FADE_EFFECT_DURATION_MS);
}
+ private void setupAccessibilityActions(AccessibilityNodeInfo info) {
+ final Resources res = mContext.getResources();
+ final AccessibilityAction moveTopLeft =
+ new AccessibilityAction(R.id.action_move_top_left,
+ res.getString(
+ R.string.accessibility_floating_button_action_move_top_left));
+ info.addAction(moveTopLeft);
+
+ final AccessibilityAction moveTopRight =
+ new AccessibilityAction(R.id.action_move_top_right,
+ res.getString(
+ R.string.accessibility_floating_button_action_move_top_right));
+ info.addAction(moveTopRight);
+
+ final AccessibilityAction moveBottomLeft =
+ new AccessibilityAction(R.id.action_move_bottom_left,
+ res.getString(
+ R.string.accessibility_floating_button_action_move_bottom_left));
+ info.addAction(moveBottomLeft);
+
+ final AccessibilityAction moveBottomRight =
+ new AccessibilityAction(R.id.action_move_bottom_right,
+ res.getString(
+ R.string.accessibility_floating_button_action_move_bottom_right));
+ info.addAction(moveBottomRight);
+ }
+
private boolean onTouched(MotionEvent event) {
final int action = event.getAction();
final int currentX = (int) event.getX();
@@ -524,7 +592,8 @@ public class AccessibilityFloatingMenuView extends FrameLayout
updateLocationWith(mAlignment, mPercentageY);
}
- private void snapToLocation(int endX, int endY) {
+ @VisibleForTesting
+ void snapToLocation(int endX, int endY) {
mDragAnimator.cancel();
mDragAnimator.removeAllUpdateListeners();
mDragAnimator.addUpdateListener(anim -> onDragAnimationUpdate(anim, endX, endY));
@@ -662,6 +731,11 @@ public class AccessibilityFloatingMenuView extends FrameLayout
: R.dimen.accessibility_floating_menu_large_single_radius;
}
+ @VisibleForTesting
+ Rect getAvailableBounds() {
+ return new Rect(0, 0, mScreenWidth - getWindowWidth(), mScreenHeight - getWindowHeight());
+ }
+
private int getLayoutWidth() {
return mPadding * 2 + mIconWidth;
}
diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistHandleBehaviorController.java b/packages/SystemUI/src/com/android/systemui/assist/AssistHandleBehaviorController.java
deleted file mode 100644
index 4390d513a9fb..000000000000
--- a/packages/SystemUI/src/com/android/systemui/assist/AssistHandleBehaviorController.java
+++ /dev/null
@@ -1,336 +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.assist;
-
-import static com.android.systemui.assist.AssistModule.ASSIST_HANDLE_THREAD_NAME;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.os.Handler;
-import android.os.SystemClock;
-import android.provider.Settings;
-import android.util.Log;
-import android.view.accessibility.AccessibilityManager;
-
-import androidx.annotation.Nullable;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.app.AssistUtils;
-import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
-import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.systemui.Dumpable;
-import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.dump.DumpManager;
-import com.android.systemui.navigationbar.NavigationModeController;
-import com.android.systemui.shared.system.QuickStepContract;
-
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-import java.util.Map;
-import java.util.concurrent.TimeUnit;
-
-import javax.inject.Inject;
-import javax.inject.Named;
-import javax.inject.Provider;
-
-import dagger.Lazy;
-
-/**
- * A class for managing Assistant handle logic.
- *
- * Controls when visual handles for Assistant gesture affordance should be shown or hidden using an
- * {@link AssistHandleBehavior}.
- */
-@SysUISingleton
-public final class AssistHandleBehaviorController implements AssistHandleCallbacks, Dumpable {
-
- private static final String TAG = "AssistHandleBehavior";
-
- private static final long DEFAULT_SHOWN_FREQUENCY_THRESHOLD_MS = 0;
- private static final long DEFAULT_SHOW_AND_GO_DURATION_MS = TimeUnit.SECONDS.toMillis(3);
- private static final String SETTINGS_SECURE_USER_SETUP_COMPLETE = "user_setup_complete";
-
- /**
- * This is the default behavior that will be used once the system is up. It will be set once the
- * behavior dependencies are available. This ensures proper behavior lifecycle.
- */
- private static final AssistHandleBehavior DEFAULT_BEHAVIOR = AssistHandleBehavior.REMINDER_EXP;
-
- private final Context mContext;
- private final AssistUtils mAssistUtils;
- private final Handler mHandler;
- private final Runnable mHideHandles = this::hideHandles;
- private final Runnable mShowAndGo = this::showAndGoInternal;
- private final Provider<AssistHandleViewController> mAssistHandleViewController;
- private final DeviceConfigHelper mDeviceConfigHelper;
- private final Map<AssistHandleBehavior, BehaviorController> mBehaviorMap;
- private final Lazy<AccessibilityManager> mA11yManager;
-
- private boolean mHandlesShowing = false;
- private long mHandlesLastHiddenAt;
- private long mShowAndGoEndsAt;
- /**
- * This should always be initialized as {@link AssistHandleBehavior#OFF} to ensure proper
- * behavior lifecycle.
- */
- private AssistHandleBehavior mCurrentBehavior = AssistHandleBehavior.OFF;
- private boolean mInGesturalMode;
-
- @Inject
- AssistHandleBehaviorController(
- Context context,
- AssistUtils assistUtils,
- @Named(ASSIST_HANDLE_THREAD_NAME) Handler handler,
- Provider<AssistHandleViewController> assistHandleViewController,
- DeviceConfigHelper deviceConfigHelper,
- Map<AssistHandleBehavior, BehaviorController> behaviorMap,
- NavigationModeController navigationModeController,
- Lazy<AccessibilityManager> a11yManager,
- DumpManager dumpManager) {
- mContext = context;
- mAssistUtils = assistUtils;
- mHandler = handler;
- mAssistHandleViewController = assistHandleViewController;
- mDeviceConfigHelper = deviceConfigHelper;
- mBehaviorMap = behaviorMap;
- mA11yManager = a11yManager;
-
- mInGesturalMode = QuickStepContract.isGesturalMode(
- navigationModeController.addListener(this::handleNavigationModeChange));
-
- setBehavior(getBehaviorMode());
- mDeviceConfigHelper.addOnPropertiesChangedListener(
- mHandler::post,
- (properties) -> {
- if (properties.getKeyset().contains(
- SystemUiDeviceConfigFlags.ASSIST_HANDLES_BEHAVIOR_MODE)) {
- setBehavior(properties.getString(
- SystemUiDeviceConfigFlags.ASSIST_HANDLES_BEHAVIOR_MODE, null));
- }
- });
-
- dumpManager.registerDumpable(TAG, this);
- }
-
- @Override // AssistHandleCallbacks
- public void hide() {
- clearPendingCommands();
- mHandler.post(mHideHandles);
- }
-
- @Override // AssistHandleCallbacks
- public void showAndGo() {
- clearPendingCommands();
- mHandler.post(mShowAndGo);
- }
-
- private void showAndGoInternal() {
- maybeShowHandles(/* ignoreThreshold = */ false);
- long showAndGoDuration = getShowAndGoDuration();
- mShowAndGoEndsAt = SystemClock.elapsedRealtime() + showAndGoDuration;
- mHandler.postDelayed(mHideHandles, showAndGoDuration);
- }
-
- @Override // AssistHandleCallbacks
- public void showAndGoDelayed(long delayMs, boolean hideIfShowing) {
- clearPendingCommands();
- if (hideIfShowing) {
- mHandler.post(mHideHandles);
- }
- mHandler.postDelayed(mShowAndGo, delayMs);
- }
-
- @Override // AssistHandleCallbacks
- public void showAndStay() {
- clearPendingCommands();
- mHandler.post(() -> maybeShowHandles(/* ignoreThreshold = */ true));
- }
-
- public long getShowAndGoRemainingTimeMs() {
- return Long.max(mShowAndGoEndsAt - SystemClock.elapsedRealtime(), 0);
- }
-
- public boolean areHandlesShowing() {
- return mHandlesShowing;
- }
-
- void onAssistantGesturePerformed() {
- mBehaviorMap.get(mCurrentBehavior).onAssistantGesturePerformed();
- }
-
- void onAssistHandlesRequested() {
- if (mInGesturalMode) {
- mBehaviorMap.get(mCurrentBehavior).onAssistHandlesRequested();
- }
- }
-
- void setBehavior(AssistHandleBehavior behavior) {
- if (mCurrentBehavior == behavior) {
- return;
- }
-
- if (!mBehaviorMap.containsKey(behavior)) {
- Log.e(TAG, "Unsupported behavior requested: " + behavior.toString());
- return;
- }
-
- if (mInGesturalMode) {
- mBehaviorMap.get(mCurrentBehavior).onModeDeactivated();
- mBehaviorMap.get(behavior).onModeActivated(mContext, /* callbacks = */ this);
- }
-
- mCurrentBehavior = behavior;
- }
-
- private void setBehavior(@Nullable String behavior) {
- try {
- setBehavior(AssistHandleBehavior.valueOf(behavior));
- } catch (IllegalArgumentException | NullPointerException e) {
- Log.e(TAG, "Invalid behavior: " + behavior);
- }
- }
-
- private boolean handlesUnblocked(boolean ignoreThreshold) {
- if (!isUserSetupComplete()) {
- return false;
- }
-
- long timeSinceHidden = SystemClock.elapsedRealtime() - mHandlesLastHiddenAt;
- boolean notThrottled = ignoreThreshold || timeSinceHidden >= getShownFrequencyThreshold();
- ComponentName assistantComponent =
- mAssistUtils.getAssistComponentForUser(KeyguardUpdateMonitor.getCurrentUser());
- return notThrottled && assistantComponent != null;
- }
-
- private long getShownFrequencyThreshold() {
- return mDeviceConfigHelper.getLong(
- SystemUiDeviceConfigFlags.ASSIST_HANDLES_SHOWN_FREQUENCY_THRESHOLD_MS,
- DEFAULT_SHOWN_FREQUENCY_THRESHOLD_MS);
- }
-
- private long getShowAndGoDuration() {
- long configuredTime = mDeviceConfigHelper.getLong(
- SystemUiDeviceConfigFlags.ASSIST_HANDLES_SHOW_AND_GO_DURATION_MS,
- DEFAULT_SHOW_AND_GO_DURATION_MS);
- return mA11yManager.get().getRecommendedTimeoutMillis(
- (int) configuredTime, AccessibilityManager.FLAG_CONTENT_ICONS);
- }
-
- private String getBehaviorMode() {
- return mDeviceConfigHelper.getString(
- SystemUiDeviceConfigFlags.ASSIST_HANDLES_BEHAVIOR_MODE,
- DEFAULT_BEHAVIOR.toString());
- }
-
- private void maybeShowHandles(boolean ignoreThreshold) {
- if (mHandlesShowing) {
- return;
- }
-
- if (handlesUnblocked(ignoreThreshold)) {
- mHandlesShowing = true;
- AssistHandleViewController assistHandleViewController =
- mAssistHandleViewController.get();
- if (assistHandleViewController == null) {
- Log.w(TAG, "Couldn't show handles, AssistHandleViewController unavailable");
- } else {
- assistHandleViewController.setAssistHintVisible(true);
- }
- }
- }
-
- private void hideHandles() {
- if (!mHandlesShowing) {
- return;
- }
-
- mHandlesShowing = false;
- mHandlesLastHiddenAt = SystemClock.elapsedRealtime();
- AssistHandleViewController assistHandleViewController =
- mAssistHandleViewController.get();
- if (assistHandleViewController == null) {
- Log.w(TAG, "Couldn't show handles, AssistHandleViewController unavailable");
- } else {
- assistHandleViewController.setAssistHintVisible(false);
- }
- }
-
- private void handleNavigationModeChange(int navigationMode) {
- boolean inGesturalMode = QuickStepContract.isGesturalMode(navigationMode);
- if (mInGesturalMode == inGesturalMode) {
- return;
- }
-
- mInGesturalMode = inGesturalMode;
- if (mInGesturalMode) {
- mBehaviorMap.get(mCurrentBehavior).onModeActivated(mContext, /* callbacks = */ this);
- } else {
- mBehaviorMap.get(mCurrentBehavior).onModeDeactivated();
- hide();
- }
- }
-
- private void clearPendingCommands() {
- mHandler.removeCallbacks(mHideHandles);
- mHandler.removeCallbacks(mShowAndGo);
- mShowAndGoEndsAt = 0;
- }
-
- private boolean isUserSetupComplete() {
- return Settings.Secure.getInt(
- mContext.getContentResolver(), SETTINGS_SECURE_USER_SETUP_COMPLETE, 0) == 1;
- }
-
- @VisibleForTesting
- void setInGesturalModeForTest(boolean inGesturalMode) {
- mInGesturalMode = inGesturalMode;
- }
-
- @Override // Dumpable
- public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
- pw.println("Current AssistHandleBehaviorController State:");
-
- pw.println(" mHandlesShowing=" + mHandlesShowing);
- pw.println(" mHandlesLastHiddenAt=" + mHandlesLastHiddenAt);
- pw.println(" mInGesturalMode=" + mInGesturalMode);
-
- pw.println(" Phenotype Flags:");
- pw.println(" "
- + SystemUiDeviceConfigFlags.ASSIST_HANDLES_SHOW_AND_GO_DURATION_MS + "(a11y modded)"
- + "="
- + getShowAndGoDuration());
- pw.println(" "
- + SystemUiDeviceConfigFlags.ASSIST_HANDLES_SHOWN_FREQUENCY_THRESHOLD_MS
- + "="
- + getShownFrequencyThreshold());
- pw.println(" "
- + SystemUiDeviceConfigFlags.ASSIST_HANDLES_BEHAVIOR_MODE
- + "="
- + getBehaviorMode());
-
- pw.println(" mCurrentBehavior=" + mCurrentBehavior.toString());
- mBehaviorMap.get(mCurrentBehavior).dump(pw, " ");
- }
-
- interface BehaviorController {
- void onModeActivated(Context context, AssistHandleCallbacks callbacks);
- default void onModeDeactivated() {}
- default void onAssistantGesturePerformed() {}
- default void onAssistHandlesRequested() {}
- default void dump(PrintWriter pw, String prefix) {}
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistHandleCallbacks.java b/packages/SystemUI/src/com/android/systemui/assist/AssistHandleCallbacks.java
deleted file mode 100644
index 3db861d57268..000000000000
--- a/packages/SystemUI/src/com/android/systemui/assist/AssistHandleCallbacks.java
+++ /dev/null
@@ -1,41 +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.assist;
-
-/** Callback for controlling Assistant handle behavior. */
-public interface AssistHandleCallbacks {
-
- /** Hide the Assistant handles. */
- void hide();
-
- /**
- * Show the Assistant handles for the configured duration and then hide them.
- *
- * Won't show if the handles have been shown within the configured timeout.
- */
- void showAndGo();
-
- /**
- * Same as show and go, but will not do anything until a delay has elapsed.
- *
- * Will be cancelled if another command is given during the delay.
- */
- void showAndGoDelayed(long delayMs, boolean hideIfShowing);
-
- /** Show the Assistant handles. */
- void showAndStay();
-}
diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistHandleLikeHomeBehavior.java b/packages/SystemUI/src/com/android/systemui/assist/AssistHandleLikeHomeBehavior.java
deleted file mode 100644
index 5d8ec4bb330b..000000000000
--- a/packages/SystemUI/src/com/android/systemui/assist/AssistHandleLikeHomeBehavior.java
+++ /dev/null
@@ -1,172 +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.assist;
-
-import android.content.Context;
-
-import androidx.annotation.Nullable;
-
-import com.android.systemui.assist.AssistHandleBehaviorController.BehaviorController;
-import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.keyguard.WakefulnessLifecycle;
-import com.android.systemui.model.SysUiState;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.shared.system.QuickStepContract;
-
-import java.io.PrintWriter;
-
-import javax.inject.Inject;
-
-import dagger.Lazy;
-
-/**
- * Assistant Handle behavior that makes Assistant handles show/hide when the home handle is
- * shown/hidden, respectively.
- */
-@SysUISingleton
-final class AssistHandleLikeHomeBehavior implements BehaviorController {
-
- private final StatusBarStateController.StateListener mStatusBarStateListener =
- new StatusBarStateController.StateListener() {
- @Override
- public void onDozingChanged(boolean isDozing) {
- handleDozingChanged(isDozing);
- }
- };
- private final WakefulnessLifecycle.Observer mWakefulnessLifecycleObserver =
- new WakefulnessLifecycle.Observer() {
- @Override
- public void onStartedWakingUp() {
- handleWakefullnessChanged(/* isAwake = */ false);
- }
-
- @Override
- public void onFinishedWakingUp() {
- handleWakefullnessChanged(/* isAwake = */ true);
- }
-
- @Override
- public void onStartedGoingToSleep() {
- handleWakefullnessChanged(/* isAwake = */ false);
- }
-
- @Override
- public void onFinishedGoingToSleep() {
- handleWakefullnessChanged(/* isAwake = */ false);
- }
- };
-
- private final SysUiState.SysUiStateCallback mSysUiStateCallback =
- this::handleSystemUiStateChange;
-
- private final Lazy<StatusBarStateController> mStatusBarStateController;
- private final Lazy<WakefulnessLifecycle> mWakefulnessLifecycle;
- private final Lazy<SysUiState> mSysUiFlagContainer;
-
- private boolean mIsDozing;
- private boolean mIsAwake;
- private boolean mIsHomeHandleHiding;
-
- @Nullable private AssistHandleCallbacks mAssistHandleCallbacks;
-
- @Inject
- AssistHandleLikeHomeBehavior(
- Lazy<StatusBarStateController> statusBarStateController,
- Lazy<WakefulnessLifecycle> wakefulnessLifecycle,
- Lazy<SysUiState> sysUiFlagContainer) {
- mStatusBarStateController = statusBarStateController;
- mWakefulnessLifecycle = wakefulnessLifecycle;
- mSysUiFlagContainer = sysUiFlagContainer;
- }
-
- @Override
- public void onModeActivated(Context context, AssistHandleCallbacks callbacks) {
- mAssistHandleCallbacks = callbacks;
- mIsDozing = mStatusBarStateController.get().isDozing();
- mStatusBarStateController.get().addCallback(mStatusBarStateListener);
- mIsAwake = mWakefulnessLifecycle.get().getWakefulness()
- == WakefulnessLifecycle.WAKEFULNESS_AWAKE;
- mWakefulnessLifecycle.get().addObserver(mWakefulnessLifecycleObserver);
- mSysUiFlagContainer.get().addCallback(mSysUiStateCallback);
- callbackForCurrentState();
- }
-
- @Override
- public void onModeDeactivated() {
- mAssistHandleCallbacks = null;
- mStatusBarStateController.get().removeCallback(mStatusBarStateListener);
- mWakefulnessLifecycle.get().removeObserver(mWakefulnessLifecycleObserver);
- mSysUiFlagContainer.get().removeCallback(mSysUiStateCallback);
- }
-
- private static boolean isHomeHandleHiding(int sysuiStateFlags) {
- return (sysuiStateFlags & QuickStepContract.SYSUI_STATE_NAV_BAR_HIDDEN) != 0;
- }
-
- private void handleDozingChanged(boolean isDozing) {
- if (mIsDozing == isDozing) {
- return;
- }
-
- mIsDozing = isDozing;
- callbackForCurrentState();
- }
-
- private void handleWakefullnessChanged(boolean isAwake) {
- if (mIsAwake == isAwake) {
- return;
- }
-
- mIsAwake = isAwake;
- callbackForCurrentState();
- }
-
- private void handleSystemUiStateChange(int sysuiStateFlags) {
- boolean isHomeHandleHiding = isHomeHandleHiding(sysuiStateFlags);
- if (mIsHomeHandleHiding == isHomeHandleHiding) {
- return;
- }
-
- mIsHomeHandleHiding = isHomeHandleHiding;
- callbackForCurrentState();
- }
-
- private void callbackForCurrentState() {
- if (mAssistHandleCallbacks == null) {
- return;
- }
-
- if (mIsHomeHandleHiding || !isFullyAwake()) {
- mAssistHandleCallbacks.hide();
- } else {
- mAssistHandleCallbacks.showAndStay();
- }
- }
-
- private boolean isFullyAwake() {
- return mIsAwake && !mIsDozing;
- }
-
- @Override
- public void dump(PrintWriter pw, String prefix) {
- pw.println(prefix + "Current AssistHandleLikeHomeBehavior State:");
-
- pw.println(prefix + " mIsDozing=" + mIsDozing);
- pw.println(prefix + " mIsAwake=" + mIsAwake);
- pw.println(prefix + " mIsHomeHandleHiding=" + mIsHomeHandleHiding);
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistHandleOffBehavior.java b/packages/SystemUI/src/com/android/systemui/assist/AssistHandleOffBehavior.java
deleted file mode 100644
index 86d3254dc7f9..000000000000
--- a/packages/SystemUI/src/com/android/systemui/assist/AssistHandleOffBehavior.java
+++ /dev/null
@@ -1,38 +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.assist;
-
-import android.content.Context;
-
-import com.android.systemui.assist.AssistHandleBehaviorController.BehaviorController;
-import com.android.systemui.dagger.SysUISingleton;
-
-import javax.inject.Inject;
-
-/** Assistant handle behavior that hides the Assistant handles. */
-@SysUISingleton
-final class AssistHandleOffBehavior implements BehaviorController {
-
- @Inject
- AssistHandleOffBehavior() {
- }
-
- @Override
- public void onModeActivated(Context context, AssistHandleCallbacks callbacks) {
- callbacks.hide();
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistHandleReminderExpBehavior.java b/packages/SystemUI/src/com/android/systemui/assist/AssistHandleReminderExpBehavior.java
deleted file mode 100644
index c1c2de166627..000000000000
--- a/packages/SystemUI/src/com/android/systemui/assist/AssistHandleReminderExpBehavior.java
+++ /dev/null
@@ -1,662 +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.assist;
-
-import static com.android.systemui.assist.AssistModule.ASSIST_HANDLE_THREAD_NAME;
-import static com.android.systemui.assist.AssistModule.UPTIME_NAME;
-
-import android.app.ActivityManager;
-import android.content.BroadcastReceiver;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.pm.ResolveInfo;
-import android.database.ContentObserver;
-import android.net.Uri;
-import android.os.Handler;
-import android.provider.Settings;
-
-import androidx.annotation.Nullable;
-import androidx.slice.Clock;
-
-import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
-import com.android.systemui.BootCompleteCache;
-import com.android.systemui.assist.AssistHandleBehaviorController.BehaviorController;
-import com.android.systemui.broadcast.BroadcastDispatcher;
-import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.keyguard.WakefulnessLifecycle;
-import com.android.systemui.model.SysUiState;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.recents.OverviewProxyService;
-import com.android.systemui.shared.system.ActivityManagerWrapper;
-import com.android.systemui.shared.system.PackageManagerWrapper;
-import com.android.systemui.shared.system.QuickStepContract;
-import com.android.systemui.shared.system.TaskStackChangeListener;
-import com.android.systemui.shared.system.TaskStackChangeListeners;
-import com.android.systemui.statusbar.StatusBarState;
-
-import java.io.PrintWriter;
-import java.time.LocalDate;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.TimeUnit;
-
-import javax.inject.Inject;
-import javax.inject.Named;
-
-import dagger.Lazy;
-
-/**
- * Assistant handle behavior that hides the handles when the phone is dozing or in immersive mode,
- * shows the handles when on lockscreen, and shows the handles temporarily when changing tasks or
- * entering overview.
- */
-@SysUISingleton
-final class AssistHandleReminderExpBehavior implements BehaviorController {
-
- private static final Uri LEARNING_TIME_ELAPSED_URI =
- Settings.Secure.getUriFor(Settings.Secure.ASSIST_HANDLES_LEARNING_TIME_ELAPSED_MILLIS);
- private static final Uri LEARNING_EVENT_COUNT_URI =
- Settings.Secure.getUriFor(Settings.Secure.ASSIST_HANDLES_LEARNING_EVENT_COUNT);
- private static final String LEARNED_HINT_LAST_SHOWN_KEY =
- "reminder_exp_learned_hint_last_shown";
- private static final long DEFAULT_LEARNING_TIME_MS = TimeUnit.DAYS.toMillis(10);
- private static final int DEFAULT_LEARNING_COUNT = 10;
- private static final long DEFAULT_SHOW_AND_GO_DELAYED_SHORT_DELAY_MS = 150;
- private static final long DEFAULT_SHOW_AND_GO_DELAYED_LONG_DELAY_MS =
- TimeUnit.SECONDS.toMillis(1);
- private static final long DEFAULT_SHOW_AND_GO_DELAY_RESET_TIMEOUT_MS =
- TimeUnit.SECONDS.toMillis(3);
- private static final boolean DEFAULT_SUPPRESS_ON_LOCKSCREEN = false;
- private static final boolean DEFAULT_SUPPRESS_ON_LAUNCHER = false;
- private static final boolean DEFAULT_SUPPRESS_ON_APPS = true;
- private static final boolean DEFAULT_SHOW_WHEN_TAUGHT = false;
-
- private static final String[] DEFAULT_HOME_CHANGE_ACTIONS = new String[] {
- PackageManagerWrapper.ACTION_PREFERRED_ACTIVITY_CHANGED,
- Intent.ACTION_PACKAGE_ADDED,
- Intent.ACTION_PACKAGE_CHANGED,
- Intent.ACTION_PACKAGE_REMOVED
- };
-
- private final StatusBarStateController.StateListener mStatusBarStateListener =
- new StatusBarStateController.StateListener() {
- @Override
- public void onStateChanged(int newState) {
- handleStatusBarStateChanged(newState);
- }
-
- @Override
- public void onDozingChanged(boolean isDozing) {
- handleDozingChanged(isDozing);
- }
- };
- private final TaskStackChangeListener mTaskStackChangeListener =
- new TaskStackChangeListener() {
- @Override
- public void onTaskMovedToFront(ActivityManager.RunningTaskInfo taskInfo) {
- handleTaskStackTopChanged(taskInfo.taskId, taskInfo.topActivity);
- }
-
- @Override
- public void onTaskCreated(int taskId, ComponentName componentName) {
- handleTaskStackTopChanged(taskId, componentName);
- }
- };
- private final OverviewProxyService.OverviewProxyListener mOverviewProxyListener =
- new OverviewProxyService.OverviewProxyListener() {
- @Override
- public void onOverviewShown(boolean fromHome) {
- handleOverviewShown();
- }
- };
- private final SysUiState.SysUiStateCallback mSysUiStateCallback =
- this::handleSystemUiStateChanged;
- private final WakefulnessLifecycle.Observer mWakefulnessLifecycleObserver =
- new WakefulnessLifecycle.Observer() {
- @Override
- public void onStartedWakingUp() {
- handleWakefullnessChanged(/* isAwake = */ false);
- }
-
- @Override
- public void onFinishedWakingUp() {
- handleWakefullnessChanged(/* isAwake = */ true);
- }
-
- @Override
- public void onStartedGoingToSleep() {
- handleWakefullnessChanged(/* isAwake = */ false);
- }
-
- @Override
- public void onFinishedGoingToSleep() {
- handleWakefullnessChanged(/* isAwake = */ false);
- }
- };
- private final BroadcastReceiver mDefaultHomeBroadcastReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- mDefaultHome = getCurrentDefaultHome();
- }
- };
-
- private final BootCompleteCache.BootCompleteListener mBootCompleteListener =
- new BootCompleteCache.BootCompleteListener() {
- @Override
- public void onBootComplete() {
- mDefaultHome = getCurrentDefaultHome();
- }
- };
-
- private final IntentFilter mDefaultHomeIntentFilter;
- private final Runnable mResetConsecutiveTaskSwitches = this::resetConsecutiveTaskSwitches;
-
- private final Clock mClock;
- private final Handler mHandler;
- private final DeviceConfigHelper mDeviceConfigHelper;
- private final Lazy<StatusBarStateController> mStatusBarStateController;
- private final Lazy<ActivityManagerWrapper> mActivityManagerWrapper;
- private final Lazy<TaskStackChangeListeners> mTaskStackChangeListeners;
- private final Lazy<OverviewProxyService> mOverviewProxyService;
- private final Lazy<SysUiState> mSysUiFlagContainer;
- private final Lazy<WakefulnessLifecycle> mWakefulnessLifecycle;
- private final Lazy<PackageManagerWrapper> mPackageManagerWrapper;
- private final Lazy<BroadcastDispatcher> mBroadcastDispatcher;
- private final Lazy<BootCompleteCache> mBootCompleteCache;
-
- private boolean mOnLockscreen;
- private boolean mIsDozing;
- private boolean mIsAwake;
- private int mRunningTaskId;
- private boolean mIsNavBarHidden;
- private boolean mIsLauncherShowing;
- private int mConsecutiveTaskSwitches;
- @Nullable private ContentObserver mSettingObserver;
-
- /** Whether user has learned the gesture. */
- private boolean mIsLearned;
- private long mLastLearningTimestamp;
- /** Uptime while in this behavior. */
- private long mLearningTimeElapsed;
- /** Number of successful Assistant invocations while in this behavior. */
- private int mLearningCount;
- private long mLearnedHintLastShownEpochDay;
-
- @Nullable private Context mContext;
- @Nullable private AssistHandleCallbacks mAssistHandleCallbacks;
- @Nullable private ComponentName mDefaultHome;
-
- @Inject
- AssistHandleReminderExpBehavior(
- @Named(UPTIME_NAME) Clock clock,
- @Named(ASSIST_HANDLE_THREAD_NAME) Handler handler,
- DeviceConfigHelper deviceConfigHelper,
- Lazy<StatusBarStateController> statusBarStateController,
- Lazy<ActivityManagerWrapper> activityManagerWrapper,
- Lazy<TaskStackChangeListeners> taskStackChangeListeners,
- Lazy<OverviewProxyService> overviewProxyService,
- Lazy<SysUiState> sysUiFlagContainer,
- Lazy<WakefulnessLifecycle> wakefulnessLifecycle,
- Lazy<PackageManagerWrapper> packageManagerWrapper,
- Lazy<BroadcastDispatcher> broadcastDispatcher,
- Lazy<BootCompleteCache> bootCompleteCache) {
- mClock = clock;
- mHandler = handler;
- mDeviceConfigHelper = deviceConfigHelper;
- mStatusBarStateController = statusBarStateController;
- mActivityManagerWrapper = activityManagerWrapper;
- mTaskStackChangeListeners = taskStackChangeListeners;
- mOverviewProxyService = overviewProxyService;
- mSysUiFlagContainer = sysUiFlagContainer;
- mWakefulnessLifecycle = wakefulnessLifecycle;
- mPackageManagerWrapper = packageManagerWrapper;
- mDefaultHomeIntentFilter = new IntentFilter();
- for (String action : DEFAULT_HOME_CHANGE_ACTIONS) {
- mDefaultHomeIntentFilter.addAction(action);
- }
- mBroadcastDispatcher = broadcastDispatcher;
- mBootCompleteCache = bootCompleteCache;
- }
-
- @Override
- public void onModeActivated(Context context, AssistHandleCallbacks callbacks) {
- mContext = context;
- mAssistHandleCallbacks = callbacks;
- mConsecutiveTaskSwitches = 0;
- mBootCompleteCache.get().addListener(mBootCompleteListener);
- mDefaultHome = getCurrentDefaultHome();
- mBroadcastDispatcher.get()
- .registerReceiver(mDefaultHomeBroadcastReceiver, mDefaultHomeIntentFilter);
- mOnLockscreen = onLockscreen(mStatusBarStateController.get().getState());
- mIsDozing = mStatusBarStateController.get().isDozing();
- mStatusBarStateController.get().addCallback(mStatusBarStateListener);
- ActivityManager.RunningTaskInfo runningTaskInfo =
- mActivityManagerWrapper.get().getRunningTask();
- mRunningTaskId = runningTaskInfo == null ? 0 : runningTaskInfo.taskId;
- mTaskStackChangeListeners.get().registerTaskStackListener(mTaskStackChangeListener);
- mOverviewProxyService.get().addCallback(mOverviewProxyListener);
- mSysUiFlagContainer.get().addCallback(mSysUiStateCallback);
- mIsAwake = mWakefulnessLifecycle.get().getWakefulness()
- == WakefulnessLifecycle.WAKEFULNESS_AWAKE;
- mWakefulnessLifecycle.get().addObserver(mWakefulnessLifecycleObserver);
-
- mLearningTimeElapsed = Settings.Secure.getLong(
- context.getContentResolver(),
- Settings.Secure.ASSIST_HANDLES_LEARNING_TIME_ELAPSED_MILLIS,
- /* default = */ 0);
- mLearningCount = Settings.Secure.getInt(
- context.getContentResolver(),
- Settings.Secure.ASSIST_HANDLES_LEARNING_EVENT_COUNT,
- /* default = */ 0);
- mSettingObserver = new SettingsObserver(context, mHandler);
- context.getContentResolver().registerContentObserver(
- LEARNING_TIME_ELAPSED_URI,
- /* notifyForDescendants = */ true,
- mSettingObserver);
- context.getContentResolver().registerContentObserver(
- LEARNING_EVENT_COUNT_URI,
- /* notifyForDescendants = */ true,
- mSettingObserver);
- mLearnedHintLastShownEpochDay = Settings.Secure.getLong(
- context.getContentResolver(), LEARNED_HINT_LAST_SHOWN_KEY, /* default = */ 0);
- mLastLearningTimestamp = mClock.currentTimeMillis();
-
- callbackForCurrentState(/* justUnlocked = */ false);
- }
-
- @Override
- public void onModeDeactivated() {
- mAssistHandleCallbacks = null;
- if (mContext != null) {
- mBroadcastDispatcher.get().unregisterReceiver(mDefaultHomeBroadcastReceiver);
- mBootCompleteCache.get().removeListener(mBootCompleteListener);
- mContext.getContentResolver().unregisterContentObserver(mSettingObserver);
- mSettingObserver = null;
- // putString in order to use overrideableByRestore
- Settings.Secure.putString(
- mContext.getContentResolver(),
- Settings.Secure.ASSIST_HANDLES_LEARNING_TIME_ELAPSED_MILLIS,
- Long.toString(0L),
- /* overrideableByRestore = */ true);
- // putString in order to use overrideableByRestore
- Settings.Secure.putString(
- mContext.getContentResolver(),
- Settings.Secure.ASSIST_HANDLES_LEARNING_EVENT_COUNT,
- Integer.toString(0),
- /* overrideableByRestore = */ true);
- Settings.Secure.putLong(mContext.getContentResolver(), LEARNED_HINT_LAST_SHOWN_KEY, 0);
- mContext = null;
- }
- mStatusBarStateController.get().removeCallback(mStatusBarStateListener);
- mTaskStackChangeListeners.get().unregisterTaskStackListener(mTaskStackChangeListener);
- mOverviewProxyService.get().removeCallback(mOverviewProxyListener);
- mSysUiFlagContainer.get().removeCallback(mSysUiStateCallback);
- mWakefulnessLifecycle.get().removeObserver(mWakefulnessLifecycleObserver);
- }
-
- @Override
- public void onAssistantGesturePerformed() {
- if (mContext == null) {
- return;
- }
-
- // putString in order to use overrideableByRestore
- Settings.Secure.putString(
- mContext.getContentResolver(),
- Settings.Secure.ASSIST_HANDLES_LEARNING_EVENT_COUNT,
- Integer.toString(++mLearningCount),
- /* overrideableByRestore = */ true);
- }
-
- @Override
- public void onAssistHandlesRequested() {
- if (mAssistHandleCallbacks != null
- && isFullyAwake()
- && !mIsNavBarHidden
- && !mOnLockscreen) {
- mAssistHandleCallbacks.showAndGo();
- }
- }
-
- @Nullable
- private ComponentName getCurrentDefaultHome() {
- List<ResolveInfo> homeActivities = new ArrayList<>();
- ComponentName defaultHome = mPackageManagerWrapper.get().getHomeActivities(homeActivities);
- if (defaultHome != null) {
- return defaultHome;
- }
-
- int topPriority = Integer.MIN_VALUE;
- ComponentName topComponent = null;
- for (ResolveInfo resolveInfo : homeActivities) {
- if (resolveInfo.priority > topPriority) {
- topComponent = resolveInfo.activityInfo.getComponentName();
- topPriority = resolveInfo.priority;
- } else if (resolveInfo.priority == topPriority) {
- topComponent = null;
- }
- }
- return topComponent;
- }
-
- private void handleStatusBarStateChanged(int newState) {
- boolean onLockscreen = onLockscreen(newState);
- if (mOnLockscreen == onLockscreen) {
- return;
- }
-
- resetConsecutiveTaskSwitches();
- mOnLockscreen = onLockscreen;
- callbackForCurrentState(!onLockscreen);
- }
-
- private void handleDozingChanged(boolean isDozing) {
- if (mIsDozing == isDozing) {
- return;
- }
-
- resetConsecutiveTaskSwitches();
- mIsDozing = isDozing;
- callbackForCurrentState(/* justUnlocked = */ false);
- }
-
- private void handleWakefullnessChanged(boolean isAwake) {
- if (mIsAwake == isAwake) {
- return;
- }
-
- resetConsecutiveTaskSwitches();
- mIsAwake = isAwake;
- callbackForCurrentState(/* justUnlocked = */ false);
- }
-
- private void handleTaskStackTopChanged(int taskId, @Nullable ComponentName taskComponentName) {
- if (mRunningTaskId == taskId || taskComponentName == null) {
- return;
- }
-
- mRunningTaskId = taskId;
- mIsLauncherShowing = taskComponentName.equals(mDefaultHome);
- if (mIsLauncherShowing) {
- resetConsecutiveTaskSwitches();
- } else {
- rescheduleConsecutiveTaskSwitchesReset();
- mConsecutiveTaskSwitches++;
- }
- callbackForCurrentState(/* justUnlocked = */ false);
- }
-
- private void handleSystemUiStateChanged(int sysuiStateFlags) {
- boolean isNavBarHidden =
- (sysuiStateFlags & QuickStepContract.SYSUI_STATE_NAV_BAR_HIDDEN) != 0;
- if (mIsNavBarHidden == isNavBarHidden) {
- return;
- }
-
- resetConsecutiveTaskSwitches();
- mIsNavBarHidden = isNavBarHidden;
- callbackForCurrentState(/* justUnlocked = */ false);
- }
-
- private void handleOverviewShown() {
- resetConsecutiveTaskSwitches();
- callbackForCurrentState(/* justUnlocked = */ false);
- }
-
- private boolean onLockscreen(int statusBarState) {
- return statusBarState == StatusBarState.KEYGUARD
- || statusBarState == StatusBarState.SHADE_LOCKED;
- }
-
- private void callbackForCurrentState(boolean justUnlocked) {
- updateLearningStatus();
-
- if (mIsLearned) {
- callbackForLearnedState(justUnlocked);
- } else {
- callbackForUnlearnedState();
- }
- }
-
- private void callbackForLearnedState(boolean justUnlocked) {
- if (mAssistHandleCallbacks == null) {
- return;
- }
-
- if (!isFullyAwake() || mIsNavBarHidden || mOnLockscreen || !getShowWhenTaught()) {
- mAssistHandleCallbacks.hide();
- } else if (justUnlocked) {
- long currentEpochDay = LocalDate.now().toEpochDay();
- if (mLearnedHintLastShownEpochDay < currentEpochDay) {
- if (mContext != null) {
- Settings.Secure.putLong(
- mContext.getContentResolver(),
- LEARNED_HINT_LAST_SHOWN_KEY,
- currentEpochDay);
- }
- mLearnedHintLastShownEpochDay = currentEpochDay;
- mAssistHandleCallbacks.showAndGo();
- }
- }
- }
-
- private void callbackForUnlearnedState() {
- if (mAssistHandleCallbacks == null) {
- return;
- }
-
- if (!isFullyAwake() || mIsNavBarHidden || isSuppressed()) {
- mAssistHandleCallbacks.hide();
- } else if (mOnLockscreen) {
- mAssistHandleCallbacks.showAndStay();
- } else if (mIsLauncherShowing) {
- mAssistHandleCallbacks.showAndGo();
- } else if (mConsecutiveTaskSwitches == 1) {
- mAssistHandleCallbacks.showAndGoDelayed(
- getShowAndGoDelayedShortDelayMs(), /* hideIfShowing = */ false);
- } else {
- mAssistHandleCallbacks.showAndGoDelayed(
- getShowAndGoDelayedLongDelayMs(), /* hideIfShowing = */ true);
- }
- }
-
- private boolean isSuppressed() {
- if (mOnLockscreen) {
- return getSuppressOnLockscreen();
- } else if (mIsLauncherShowing) {
- return getSuppressOnLauncher();
- } else {
- return getSuppressOnApps();
- }
- }
-
- private void updateLearningStatus() {
- if (mContext == null) {
- return;
- }
-
- long currentTimestamp = mClock.currentTimeMillis();
- mLearningTimeElapsed += currentTimestamp - mLastLearningTimestamp;
- mLastLearningTimestamp = currentTimestamp;
-
- mIsLearned =
- mLearningCount >= getLearningCount() || mLearningTimeElapsed >= getLearningTimeMs();
-
- // putString in order to use overrideableByRestore
- mHandler.post(() -> Settings.Secure.putString(
- mContext.getContentResolver(),
- Settings.Secure.ASSIST_HANDLES_LEARNING_TIME_ELAPSED_MILLIS,
- Long.toString(mLearningTimeElapsed),
- /* overrideableByRestore = */ true));
- }
-
- private void resetConsecutiveTaskSwitches() {
- mHandler.removeCallbacks(mResetConsecutiveTaskSwitches);
- mConsecutiveTaskSwitches = 0;
- }
-
- private void rescheduleConsecutiveTaskSwitchesReset() {
- mHandler.removeCallbacks(mResetConsecutiveTaskSwitches);
- mHandler.postDelayed(mResetConsecutiveTaskSwitches, getShowAndGoDelayResetTimeoutMs());
- }
-
- private boolean isFullyAwake() {
- return mIsAwake && !mIsDozing;
- }
-
- private long getLearningTimeMs() {
- return mDeviceConfigHelper.getLong(
- SystemUiDeviceConfigFlags.ASSIST_HANDLES_LEARN_TIME_MS,
- DEFAULT_LEARNING_TIME_MS);
- }
-
- private int getLearningCount() {
- return mDeviceConfigHelper.getInt(
- SystemUiDeviceConfigFlags.ASSIST_HANDLES_LEARN_COUNT,
- DEFAULT_LEARNING_COUNT);
- }
-
- private long getShowAndGoDelayedShortDelayMs() {
- return mDeviceConfigHelper.getLong(
- SystemUiDeviceConfigFlags.ASSIST_HANDLES_SHOW_AND_GO_DELAYED_SHORT_DELAY_MS,
- DEFAULT_SHOW_AND_GO_DELAYED_SHORT_DELAY_MS);
- }
-
- private long getShowAndGoDelayedLongDelayMs() {
- return mDeviceConfigHelper.getLong(
- SystemUiDeviceConfigFlags.ASSIST_HANDLES_SHOW_AND_GO_DELAYED_LONG_DELAY_MS,
- DEFAULT_SHOW_AND_GO_DELAYED_LONG_DELAY_MS);
- }
-
- private long getShowAndGoDelayResetTimeoutMs() {
- return mDeviceConfigHelper.getLong(
- SystemUiDeviceConfigFlags.ASSIST_HANDLES_SHOW_AND_GO_DELAY_RESET_TIMEOUT_MS,
- DEFAULT_SHOW_AND_GO_DELAY_RESET_TIMEOUT_MS);
- }
-
- private boolean getSuppressOnLockscreen() {
- return mDeviceConfigHelper.getBoolean(
- SystemUiDeviceConfigFlags.ASSIST_HANDLES_SUPPRESS_ON_LOCKSCREEN,
- DEFAULT_SUPPRESS_ON_LOCKSCREEN);
- }
-
- private boolean getSuppressOnLauncher() {
- return mDeviceConfigHelper.getBoolean(
- SystemUiDeviceConfigFlags.ASSIST_HANDLES_SUPPRESS_ON_LAUNCHER,
- DEFAULT_SUPPRESS_ON_LAUNCHER);
- }
-
- private boolean getSuppressOnApps() {
- return mDeviceConfigHelper.getBoolean(
- SystemUiDeviceConfigFlags.ASSIST_HANDLES_SUPPRESS_ON_APPS,
- DEFAULT_SUPPRESS_ON_APPS);
- }
-
- private boolean getShowWhenTaught() {
- return mDeviceConfigHelper.getBoolean(
- SystemUiDeviceConfigFlags.ASSIST_HANDLES_SHOW_WHEN_TAUGHT,
- DEFAULT_SHOW_WHEN_TAUGHT);
- }
-
- @Override
- public void dump(PrintWriter pw, String prefix) {
- pw.println(prefix + "Current AssistHandleReminderExpBehavior State:");
- pw.println(prefix + " mOnLockscreen=" + mOnLockscreen);
- pw.println(prefix + " mIsDozing=" + mIsDozing);
- pw.println(prefix + " mIsAwake=" + mIsAwake);
- pw.println(prefix + " mRunningTaskId=" + mRunningTaskId);
- pw.println(prefix + " mDefaultHome=" + mDefaultHome);
- pw.println(prefix + " mIsNavBarHidden=" + mIsNavBarHidden);
- pw.println(prefix + " mIsLauncherShowing=" + mIsLauncherShowing);
- pw.println(prefix + " mConsecutiveTaskSwitches=" + mConsecutiveTaskSwitches);
- pw.println(prefix + " mIsLearned=" + mIsLearned);
- pw.println(prefix + " mLastLearningTimestamp=" + mLastLearningTimestamp);
- pw.println(prefix + " mLearningTimeElapsed=" + mLearningTimeElapsed);
- pw.println(prefix + " mLearningCount=" + mLearningCount);
- pw.println(prefix + " mLearnedHintLastShownEpochDay=" + mLearnedHintLastShownEpochDay);
- pw.println(
- prefix + " mAssistHandleCallbacks present: " + (mAssistHandleCallbacks != null));
-
- pw.println(prefix + " Phenotype Flags:");
- pw.println(prefix + " "
- + SystemUiDeviceConfigFlags.ASSIST_HANDLES_LEARN_TIME_MS
- + "="
- + getLearningTimeMs());
- pw.println(prefix + " "
- + SystemUiDeviceConfigFlags.ASSIST_HANDLES_LEARN_COUNT
- + "="
- + getLearningCount());
- pw.println(prefix + " "
- + SystemUiDeviceConfigFlags.ASSIST_HANDLES_SHOW_AND_GO_DELAYED_SHORT_DELAY_MS
- + "="
- + getShowAndGoDelayedShortDelayMs());
- pw.println(prefix + " "
- + SystemUiDeviceConfigFlags.ASSIST_HANDLES_SHOW_AND_GO_DELAYED_LONG_DELAY_MS
- + "="
- + getShowAndGoDelayedLongDelayMs());
- pw.println(prefix + " "
- + SystemUiDeviceConfigFlags.ASSIST_HANDLES_SHOW_AND_GO_DELAY_RESET_TIMEOUT_MS
- + "="
- + getShowAndGoDelayResetTimeoutMs());
- pw.println(prefix + " "
- + SystemUiDeviceConfigFlags.ASSIST_HANDLES_SUPPRESS_ON_LOCKSCREEN
- + "="
- + getSuppressOnLockscreen());
- pw.println(prefix + " "
- + SystemUiDeviceConfigFlags.ASSIST_HANDLES_SUPPRESS_ON_LAUNCHER
- + "="
- + getSuppressOnLauncher());
- pw.println(prefix + " "
- + SystemUiDeviceConfigFlags.ASSIST_HANDLES_SUPPRESS_ON_APPS
- + "="
- + getSuppressOnApps());
- pw.println(prefix + " "
- + SystemUiDeviceConfigFlags.ASSIST_HANDLES_SHOW_WHEN_TAUGHT
- + "="
- + getShowWhenTaught());
- }
-
- private final class SettingsObserver extends ContentObserver {
-
- private final Context mContext;
-
- SettingsObserver(Context context, Handler handler) {
- super(handler);
- mContext = context;
- }
-
- @Override
- public void onChange(boolean selfChange, @Nullable Uri uri) {
- if (LEARNING_TIME_ELAPSED_URI.equals(uri)) {
- mLastLearningTimestamp = mClock.currentTimeMillis();
- mLearningTimeElapsed = Settings.Secure.getLong(
- mContext.getContentResolver(),
- Settings.Secure.ASSIST_HANDLES_LEARNING_TIME_ELAPSED_MILLIS,
- /* default = */ 0);
- } else if (LEARNING_EVENT_COUNT_URI.equals(uri)) {
- mLearningCount = Settings.Secure.getInt(
- mContext.getContentResolver(),
- Settings.Secure.ASSIST_HANDLES_LEARNING_EVENT_COUNT,
- /* default = */ 0);
- }
-
- super.onChange(selfChange, uri);
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistHandleViewController.java b/packages/SystemUI/src/com/android/systemui/assist/AssistHandleViewController.java
deleted file mode 100644
index f19e53f03849..000000000000
--- a/packages/SystemUI/src/com/android/systemui/assist/AssistHandleViewController.java
+++ /dev/null
@@ -1,191 +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.assist;
-
-import android.animation.Animator;
-import android.animation.AnimatorSet;
-import android.animation.ObjectAnimator;
-import android.os.Handler;
-import android.util.Log;
-import android.util.MathUtils;
-import android.view.View;
-import android.view.animation.AccelerateInterpolator;
-import android.view.animation.Interpolator;
-import android.view.animation.PathInterpolator;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.systemui.CornerHandleView;
-import com.android.systemui.R;
-import com.android.systemui.navigationbar.NavigationBarTransitions;
-
-/**
- * A class for managing Assistant handle show, hide and animation.
- */
-public class AssistHandleViewController implements NavigationBarTransitions.DarkIntensityListener {
-
- private static final boolean DEBUG = false;
- private static final String TAG = "AssistHandleViewController";
-
- private Handler mHandler;
- private CornerHandleView mAssistHintLeft;
- private CornerHandleView mAssistHintRight;
- private int mBottomOffset;
-
- @VisibleForTesting
- boolean mAssistHintVisible;
- @VisibleForTesting
- boolean mAssistHintBlocked = false;
-
- public AssistHandleViewController(Handler handler, View navBar) {
- mHandler = handler;
- mAssistHintLeft = navBar.findViewById(R.id.assist_hint_left);
- mAssistHintRight = navBar.findViewById(R.id.assist_hint_right);
- }
-
- @Override
- public void onDarkIntensity(float darkIntensity) {
- mAssistHintLeft.updateDarkness(darkIntensity);
- mAssistHintRight.updateDarkness(darkIntensity);
- }
-
- /**
- * Set the bottom offset.
- *
- * @param bottomOffset the bottom offset to translate.
- */
- public void setBottomOffset(int bottomOffset) {
- if (mBottomOffset != bottomOffset) {
- mBottomOffset = bottomOffset;
- if (mAssistHintVisible) {
- // If assist handles are visible, hide them without animation and then make them
- // show once again (with corrected bottom offset).
- hideAssistHandles();
- setAssistHintVisible(true);
- }
- }
- }
-
- /**
- * Controls the visibility of the assist gesture handles.
- *
- * @param visible whether the handles should be shown
- */
- public void setAssistHintVisible(boolean visible) {
- if (!mHandler.getLooper().isCurrentThread()) {
- mHandler.post(() -> setAssistHintVisible(visible));
- return;
- }
-
- if (mAssistHintBlocked && visible) {
- if (DEBUG) {
- Log.v(TAG, "Assist hint blocked, cannot make it visible");
- }
- return;
- }
-
- if (mAssistHintVisible != visible) {
- mAssistHintVisible = visible;
- fade(mAssistHintLeft, mAssistHintVisible, /* isLeft = */ true);
- fade(mAssistHintRight, mAssistHintVisible, /* isLeft = */ false);
- }
- }
-
- /**
- * Prevents the assist hint from becoming visible even if `mAssistHintVisible` is true.
- */
- public void setAssistHintBlocked(boolean blocked) {
- if (!mHandler.getLooper().isCurrentThread()) {
- mHandler.post(() -> setAssistHintBlocked(blocked));
- return;
- }
-
- mAssistHintBlocked = blocked;
- if (mAssistHintVisible && mAssistHintBlocked) {
- hideAssistHandles();
- }
- }
-
- private void hideAssistHandles() {
- mAssistHintLeft.setVisibility(View.GONE);
- mAssistHintRight.setVisibility(View.GONE);
- mAssistHintVisible = false;
- }
-
- /**
- * Returns an animator that animates the given view from start to end over durationMs. Start and
- * end represent total animation progress: 0 is the start, 1 is the end, 1.1 would be an
- * overshoot.
- */
- Animator getHandleAnimator(View view, float start, float end, boolean isLeft, long durationMs,
- Interpolator interpolator) {
- // Note that lerp does allow overshoot, in cases where start and end are outside of [0,1].
- float scaleStart = MathUtils.lerp(2f, 1f, start);
- float scaleEnd = MathUtils.lerp(2f, 1f, end);
- Animator scaleX = ObjectAnimator.ofFloat(view, View.SCALE_X, scaleStart, scaleEnd);
- Animator scaleY = ObjectAnimator.ofFloat(view, View.SCALE_Y, scaleStart, scaleEnd);
- float translationStart = MathUtils.lerp(0.2f, 0f, start);
- float translationEnd = MathUtils.lerp(0.2f, 0f, end);
- int xDirection = isLeft ? -1 : 1;
- Animator translateX = ObjectAnimator.ofFloat(view, View.TRANSLATION_X,
- xDirection * translationStart * view.getWidth(),
- xDirection * translationEnd * view.getWidth());
- Animator translateY = ObjectAnimator.ofFloat(view, View.TRANSLATION_Y,
- translationStart * view.getHeight() + mBottomOffset,
- translationEnd * view.getHeight() + mBottomOffset);
-
- AnimatorSet set = new AnimatorSet();
- set.play(scaleX).with(scaleY);
- set.play(scaleX).with(translateX);
- set.play(scaleX).with(translateY);
- set.setDuration(durationMs);
- set.setInterpolator(interpolator);
- return set;
- }
-
- private void fade(View view, boolean fadeIn, boolean isLeft) {
- if (fadeIn) {
- view.animate().cancel();
- view.setAlpha(1f);
- view.setVisibility(View.VISIBLE);
-
- // A piecewise spring-like interpolation.
- // End value in one animator call must match the start value in the next, otherwise
- // there will be a discontinuity.
- AnimatorSet anim = new AnimatorSet();
- Animator first = getHandleAnimator(view, 0, 1.1f, isLeft, 750,
- new PathInterpolator(0, 0.45f, .67f, 1f));
- Interpolator secondInterpolator = new PathInterpolator(0.33f, 0, 0.67f, 1f);
- Animator second = getHandleAnimator(view, 1.1f, 0.97f, isLeft, 400,
- secondInterpolator);
- Animator third = getHandleAnimator(view, 0.97f, 1.02f, isLeft, 400,
- secondInterpolator);
- Animator fourth = getHandleAnimator(view, 1.02f, 1f, isLeft, 400,
- secondInterpolator);
- anim.play(first).before(second);
- anim.play(second).before(third);
- anim.play(third).before(fourth);
- anim.start();
- } else {
- view.animate().cancel();
- view.animate()
- .setInterpolator(new AccelerateInterpolator(1.5f))
- .setDuration(250)
- .alpha(0f);
- }
-
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistLogger.kt b/packages/SystemUI/src/com/android/systemui/assist/AssistLogger.kt
index 4d0fd4313209..e30f2e440d7a 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/AssistLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/assist/AssistLogger.kt
@@ -37,8 +37,7 @@ open class AssistLogger @Inject constructor(
protected val context: Context,
protected val uiEventLogger: UiEventLogger,
private val assistUtils: AssistUtils,
- private val phoneStateMonitor: PhoneStateMonitor,
- private val assistHandleBehaviorController: AssistHandleBehaviorController
+ private val phoneStateMonitor: PhoneStateMonitor
) {
private val instanceIdSequence = InstanceIdSequence(INSTANCE_ID_MAX)
@@ -82,7 +81,7 @@ open class AssistLogger @Inject constructor(
assistComponentFinal.flattenToString(),
getOrCreateInstanceId().id,
deviceStateFinal,
- assistHandleBehaviorController.areHandlesShowing())
+ false)
reportAssistantInvocationExtraData()
}
diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java b/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java
index b2c620d88de9..b1197e6aa6f5 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java
+++ b/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java
@@ -87,7 +87,6 @@ public class AssistManager {
private static final String INVOCATION_TIME_MS_KEY = "invocation_time_ms";
private static final String INVOCATION_PHONE_STATE_KEY = "invocation_phone_state";
protected static final String ACTION_KEY = "action";
- protected static final String SHOW_ASSIST_HANDLES_ACTION = "show_assist_handles";
protected static final String SET_ASSIST_GESTURE_CONSTRAINED_ACTION =
"set_assist_gesture_constrained";
protected static final String CONSTRAINED_KEY = "should_constrain";
@@ -119,7 +118,6 @@ public class AssistManager {
protected final Context mContext;
private final AssistDisclosure mAssistDisclosure;
private final PhoneStateMonitor mPhoneStateMonitor;
- private final AssistHandleBehaviorController mHandleController;
private final UiController mUiController;
protected final Lazy<SysUiState> mSysUiState;
protected final AssistLogger mAssistLogger;
@@ -148,7 +146,6 @@ public class AssistManager {
DeviceProvisionedController controller,
Context context,
AssistUtils assistUtils,
- AssistHandleBehaviorController handleController,
CommandQueue commandQueue,
PhoneStateMonitor phoneStateMonitor,
OverviewProxyService overviewProxyService,
@@ -162,7 +159,6 @@ public class AssistManager {
mAssistUtils = assistUtils;
mAssistDisclosure = new AssistDisclosure(context, new Handler());
mPhoneStateMonitor = phoneStateMonitor;
- mHandleController = handleController;
mAssistLogger = assistLogger;
mOrbController = new AssistOrbController(configurationController, context);
@@ -216,9 +212,7 @@ public class AssistManager {
}
String action = hints.getString(ACTION_KEY);
- if (SHOW_ASSIST_HANDLES_ACTION.equals(action)) {
- requestAssistHandles();
- } else if (SET_ASSIST_GESTURE_CONSTRAINED_ACTION.equals(action)) {
+ if (SET_ASSIST_GESTURE_CONSTRAINED_ACTION.equals(action)) {
mSysUiState.get()
.setFlag(
SYSUI_STATE_ASSIST_GESTURE_CONSTRAINED,
@@ -249,9 +243,6 @@ public class AssistManager {
args = new Bundle();
}
int legacyInvocationType = args.getInt(INVOCATION_TYPE_KEY, 0);
- if (legacyInvocationType == INVOCATION_TYPE_GESTURE) {
- mHandleController.onAssistantGesturePerformed();
- }
int legacyDeviceState = mPhoneStateMonitor.getPhoneState();
args.putInt(INVOCATION_PHONE_STATE_KEY, legacyDeviceState);
args.putLong(INVOCATION_TIME_MS_KEY, SystemClock.elapsedRealtime());
@@ -278,10 +269,6 @@ public class AssistManager {
mUiController.onGestureCompletion(velocity);
}
- protected void requestAssistHandles() {
- mHandleController.onAssistHandlesRequested();
- }
-
public void hideAssist() {
mAssistUtils.hideCurrentSession();
}
@@ -362,10 +349,6 @@ public class AssistManager {
return mAssistUtils.isSessionRunning();
}
- protected AssistHandleBehaviorController getHandleBehaviorController() {
- return mHandleController;
- }
-
@Nullable
public ComponentName getAssistInfoForUser(int userId) {
return mAssistUtils.getAssistComponentForUser(userId);
@@ -389,10 +372,6 @@ public class AssistManager {
});
}
- public long getAssistHandleShowAndGoRemainingDurationMs() {
- return mHandleController.getShowAndGoRemainingTimeMs();
- }
-
/** Returns the logging flags for the given Assistant invocation type. */
public int toLoggingSubType(int invocationType) {
return toLoggingSubType(invocationType, mPhoneStateMonitor.getPhoneState());
@@ -409,7 +388,7 @@ public class AssistManager {
// Note that this logic will break if the number of Assistant invocation types exceeds 7.
// There are currently 5 invocation types, but we will be migrating to the new logging
// framework in the next update.
- int subType = mHandleController.areHandlesShowing() ? 0 : 1;
+ int subType = 0;
subType |= invocationType << 1;
subType |= phoneState << 4;
return subType;
diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistModule.java b/packages/SystemUI/src/com/android/systemui/assist/AssistModule.java
index ef43f87d20e8..f9138b6eac54 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/AssistModule.java
+++ b/packages/SystemUI/src/com/android/systemui/assist/AssistModule.java
@@ -21,15 +21,10 @@ import android.os.Handler;
import android.os.HandlerThread;
import android.os.SystemClock;
-import androidx.annotation.Nullable;
import androidx.slice.Clock;
import com.android.internal.app.AssistUtils;
import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.navigationbar.NavigationBarController;
-
-import java.util.EnumMap;
-import java.util.Map;
import javax.inject.Named;
@@ -55,28 +50,6 @@ public abstract class AssistModule {
@Provides
@SysUISingleton
- static Map<AssistHandleBehavior, AssistHandleBehaviorController.BehaviorController>
- provideAssistHandleBehaviorControllerMap(
- AssistHandleOffBehavior offBehavior,
- AssistHandleLikeHomeBehavior likeHomeBehavior,
- AssistHandleReminderExpBehavior reminderExpBehavior) {
- Map<AssistHandleBehavior, AssistHandleBehaviorController.BehaviorController> map =
- new EnumMap<>(AssistHandleBehavior.class);
- map.put(AssistHandleBehavior.OFF, offBehavior);
- map.put(AssistHandleBehavior.LIKE_HOME, likeHomeBehavior);
- map.put(AssistHandleBehavior.REMINDER_EXP, reminderExpBehavior);
- return map;
- }
-
- @Provides
- @Nullable
- static AssistHandleViewController provideAssistHandleViewController(
- NavigationBarController navigationBarController) {
- return navigationBarController.getAssistHandlerViewController();
- }
-
- @Provides
- @SysUISingleton
static AssistUtils provideAssistUtils(Context context) {
return new AssistUtils(context);
}
diff --git a/packages/SystemUI/src/com/android/systemui/assist/ui/DefaultUiController.java b/packages/SystemUI/src/com/android/systemui/assist/ui/DefaultUiController.java
index 1d9009668b47..57321454ce96 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/ui/DefaultUiController.java
+++ b/packages/SystemUI/src/com/android/systemui/assist/ui/DefaultUiController.java
@@ -37,12 +37,10 @@ import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.systemui.Dependency;
import com.android.systemui.R;
-import com.android.systemui.assist.AssistHandleViewController;
import com.android.systemui.assist.AssistLogger;
import com.android.systemui.assist.AssistManager;
import com.android.systemui.assist.AssistantSessionEvent;
import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.navigationbar.NavigationBarController;
import java.util.Locale;
@@ -113,7 +111,6 @@ public class DefaultUiController implements AssistManager.UiController {
if (!mInvocationInProgress) {
attach();
mInvocationInProgress = true;
- updateAssistHandleVisibility();
}
setProgressInternal(type, progress);
}
@@ -136,7 +133,6 @@ public class DefaultUiController implements AssistManager.UiController {
}
mInvocationLightsView.hide();
mInvocationInProgress = false;
- updateAssistHandleVisibility();
}
protected void logInvocationProgressMetrics(
@@ -174,17 +170,6 @@ public class DefaultUiController implements AssistManager.UiController {
}
}
- private void updateAssistHandleVisibility() {
- NavigationBarController navigationBarController =
- Dependency.get(NavigationBarController.class);
- AssistHandleViewController controller =
- navigationBarController == null
- ? null : navigationBarController.getAssistHandlerViewController();
- if (controller != null) {
- controller.setAssistHintBlocked(mInvocationInProgress);
- }
- }
-
private void attach() {
if (!mAttached) {
mWindowManager.addView(mRoot, mLayoutParams);
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceToUdfpsView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceToUdfpsView.java
new file mode 100644
index 000000000000..197f35ba3e86
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceToUdfpsView.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.biometrics;
+
+import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE;
+import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.hardware.biometrics.BiometricAuthenticator;
+import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.android.systemui.R;
+
+/**
+ * Manages the layout of an auth dialog for devices with a face sensor and an under-display
+ * fingerprint sensor (UDFPS). Face authentication is attempted first, followed by fingerprint if
+ * the initial attempt is unsuccessful.
+ */
+public class AuthBiometricFaceToUdfpsView extends AuthBiometricFaceView {
+ private static final String TAG = "BiometricPrompt/AuthBiometricFaceToUdfpsView";
+
+ protected static class UdfpsIconController extends IconController {
+ protected UdfpsIconController(
+ @NonNull Context context, @NonNull ImageView iconView, @NonNull TextView textView) {
+ super(context, iconView, textView);
+ }
+
+ @Override
+ protected void updateState(int lastState, int newState) {
+ final boolean lastStateIsErrorIcon =
+ lastState == STATE_ERROR || lastState == STATE_HELP;
+
+ switch (newState) {
+ case STATE_IDLE:
+ case STATE_AUTHENTICATING_ANIMATING_IN:
+ case STATE_AUTHENTICATING:
+ case STATE_PENDING_CONFIRMATION:
+ case STATE_AUTHENTICATED:
+ if (lastStateIsErrorIcon) {
+ animateOnce(R.drawable.fingerprint_dialog_error_to_fp);
+ } else {
+ showStaticDrawable(R.drawable.fingerprint_dialog_fp_to_error);
+ }
+ mIconView.setContentDescription(mContext.getString(
+ R.string.accessibility_fingerprint_dialog_fingerprint_icon));
+ break;
+
+ case STATE_ERROR:
+ case STATE_HELP:
+ if (!lastStateIsErrorIcon) {
+ animateOnce(R.drawable.fingerprint_dialog_fp_to_error);
+ } else {
+ showStaticDrawable(R.drawable.fingerprint_dialog_error_to_fp);
+ }
+ mIconView.setContentDescription(mContext.getString(
+ R.string.biometric_dialog_try_again));
+ break;
+
+ default:
+ Log.e(TAG, "Unknown biometric dialog state: " + newState);
+ break;
+ }
+
+ mState = newState;
+ }
+ }
+
+ @BiometricAuthenticator.Modality private int mActiveSensorType = TYPE_FACE;
+
+ @Nullable UdfpsDialogMeasureAdapter mMeasureAdapter;
+ @Nullable private UdfpsIconController mUdfpsIconController;
+
+ public AuthBiometricFaceToUdfpsView(Context context) {
+ super(context);
+ }
+
+ public AuthBiometricFaceToUdfpsView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ void setFingerprintSensorProps(@NonNull FingerprintSensorPropertiesInternal sensorProps) {
+ if (mMeasureAdapter == null || mMeasureAdapter.getSensorProps() != sensorProps) {
+ mMeasureAdapter = new UdfpsDialogMeasureAdapter(this, sensorProps);
+ }
+ }
+
+ @Override
+ protected int getDelayAfterAuthenticatedDurationMs() {
+ return mActiveSensorType == TYPE_FINGERPRINT ? 0
+ : super.getDelayAfterAuthenticatedDurationMs();
+ }
+
+ @Override
+ protected boolean supportsManualRetry() {
+ return false;
+ }
+
+ @Override
+ @NonNull
+ protected IconController getIconController() {
+ if (mActiveSensorType == TYPE_FINGERPRINT) {
+ if (!(mIconController instanceof UdfpsIconController)) {
+ mIconController = new UdfpsIconController(getContext(), mIconView, mIndicatorView);
+ }
+ return mIconController;
+ }
+ return super.getIconController();
+ }
+
+ @Override
+ public void updateState(int newState) {
+ if (mState == STATE_HELP || mState == STATE_ERROR) {
+ mActiveSensorType = TYPE_FINGERPRINT;
+ setRequireConfirmation(false);
+ }
+ super.updateState(newState);
+ }
+
+ @Override
+ @NonNull
+ AuthDialog.LayoutParams onMeasureInternal(int width, int height) {
+ final AuthDialog.LayoutParams layoutParams = super.onMeasureInternal(width, height);
+ return mMeasureAdapter != null
+ ? mMeasureAdapter.onMeasureInternal(width, height, layoutParams)
+ : layoutParams;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceView.java
index 9b09cfd0dba6..4c5ca691939c 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceView.java
@@ -16,6 +16,7 @@
package com.android.systemui.biometrics;
+import android.annotation.NonNull;
import android.content.Context;
import android.graphics.drawable.Animatable2;
import android.graphics.drawable.AnimatedVectorDrawable;
@@ -28,7 +29,6 @@ import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
-import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.R;
public class AuthBiometricFaceView extends AuthBiometricView {
@@ -38,15 +38,15 @@ public class AuthBiometricFaceView extends AuthBiometricView {
// Delay before dismissing after being authenticated/confirmed.
private static final int HIDE_DELAY_MS = 500;
- public static class IconController extends Animatable2.AnimationCallback {
- Context mContext;
- ImageView mIconView;
- TextView mTextView;
- Handler mHandler;
- boolean mLastPulseLightToDark; // false = dark to light, true = light to dark
- @BiometricState int mState;
+ protected static class IconController extends Animatable2.AnimationCallback {
+ protected Context mContext;
+ protected ImageView mIconView;
+ protected TextView mTextView;
+ protected Handler mHandler;
+ protected boolean mLastPulseLightToDark; // false = dark to light, true = light to dark
+ protected @BiometricState int mState;
- IconController(Context context, ImageView iconView, TextView textView) {
+ protected IconController(Context context, ImageView iconView, TextView textView) {
mContext = context;
mIconView = iconView;
mTextView = textView;
@@ -54,15 +54,15 @@ public class AuthBiometricFaceView extends AuthBiometricView {
showStaticDrawable(R.drawable.face_dialog_pulse_dark_to_light);
}
- void animateOnce(int iconRes) {
+ protected void animateOnce(int iconRes) {
animateIcon(iconRes, false);
}
- public void showStaticDrawable(int iconRes) {
+ protected void showStaticDrawable(int iconRes) {
mIconView.setImageDrawable(mContext.getDrawable(iconRes));
}
- void animateIcon(int iconRes, boolean repeat) {
+ protected void animateIcon(int iconRes, boolean repeat) {
final AnimatedVectorDrawable icon =
(AnimatedVectorDrawable) mContext.getDrawable(iconRes);
mIconView.setImageDrawable(icon);
@@ -73,12 +73,12 @@ public class AuthBiometricFaceView extends AuthBiometricView {
icon.start();
}
- void startPulsing() {
+ protected void startPulsing() {
mLastPulseLightToDark = false;
animateIcon(R.drawable.face_dialog_pulse_dark_to_light, true);
}
- void pulseInNextDirection() {
+ protected void pulseInNextDirection() {
int iconRes = mLastPulseLightToDark ? R.drawable.face_dialog_pulse_dark_to_light
: R.drawable.face_dialog_pulse_light_to_dark;
animateIcon(iconRes, true /* repeat */);
@@ -93,7 +93,7 @@ public class AuthBiometricFaceView extends AuthBiometricView {
}
}
- public void updateState(int lastState, int newState) {
+ protected void updateState(int lastState, int newState) {
final boolean lastStateIsErrorIcon =
lastState == STATE_ERROR || lastState == STATE_HELP;
@@ -138,7 +138,7 @@ public class AuthBiometricFaceView extends AuthBiometricView {
}
}
- @VisibleForTesting IconController mIconController;
+ protected IconController mIconController;
public AuthBiometricFaceView(Context context) {
this(context, null);
@@ -174,14 +174,21 @@ public class AuthBiometricFaceView extends AuthBiometricView {
}
@Override
- protected void onFinishInflate() {
- super.onFinishInflate();
- mIconController = new IconController(mContext, mIconView, mIndicatorView);
+ protected boolean supportsManualRetry() {
+ return true;
+ }
+
+ @NonNull
+ protected IconController getIconController() {
+ if (mIconController == null) {
+ mIconController = new IconController(mContext, mIconView, mIndicatorView);
+ }
+ return mIconController;
}
@Override
public void updateState(@BiometricState int newState) {
- mIconController.updateState(mState, newState);
+ getIconController().updateState(mState, newState);
if (newState == STATE_AUTHENTICATING_ANIMATING_IN ||
(newState == STATE_AUTHENTICATING && getSize() == AuthDialog.SIZE_MEDIUM)) {
@@ -195,11 +202,13 @@ public class AuthBiometricFaceView extends AuthBiometricView {
@Override
public void onAuthenticationFailed(String failureReason) {
if (getSize() == AuthDialog.SIZE_MEDIUM) {
- mTryAgainButton.setVisibility(View.VISIBLE);
- mConfirmButton.setVisibility(View.GONE);
+ if (supportsManualRetry()) {
+ mTryAgainButton.setVisibility(View.VISIBLE);
+ mConfirmButton.setVisibility(View.GONE);
+ }
}
- // Do this last since wa want to know if the button is being animated (in the case of
+ // Do this last since we want to know if the button is being animated (in the case of
// small -> medium dialog)
super.onAuthenticationFailed(failureReason);
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricUdfpsView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricUdfpsView.java
index 007080bc8603..376368fbf9d4 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricUdfpsView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricUdfpsView.java
@@ -16,33 +16,18 @@
package com.android.systemui.biometrics;
-import android.annotation.IdRes;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
-import android.graphics.Insets;
-import android.graphics.Rect;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.util.AttributeSet;
-import android.util.Log;
-import android.view.Surface;
-import android.view.View;
-import android.view.WindowInsets;
-import android.view.WindowManager;
-import android.widget.FrameLayout;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.systemui.R;
/**
* Manages the layout for under-display fingerprint sensors (UDFPS). Ensures that UI elements
* do not overlap with
*/
public class AuthBiometricUdfpsView extends AuthBiometricFingerprintView {
-
- private static final String TAG = "AuthBiometricUdfpsView";
-
- @Nullable private FingerprintSensorPropertiesInternal mSensorProps;
+ @Nullable private UdfpsDialogMeasureAdapter mMeasureAdapter;
public AuthBiometricUdfpsView(Context context) {
this(context, null /* attrs */);
@@ -52,269 +37,18 @@ public class AuthBiometricUdfpsView extends AuthBiometricFingerprintView {
super(context, attrs);
}
- void setSensorProps(@NonNull FingerprintSensorPropertiesInternal prop) {
- mSensorProps = prop;
+ void setSensorProps(@NonNull FingerprintSensorPropertiesInternal sensorProps) {
+ if (mMeasureAdapter == null || mMeasureAdapter.getSensorProps() != sensorProps) {
+ mMeasureAdapter = new UdfpsDialogMeasureAdapter(this, sensorProps);
+ }
}
@Override
@NonNull
AuthDialog.LayoutParams onMeasureInternal(int width, int height) {
- final int displayRotation = getDisplay().getRotation();
- switch (displayRotation) {
- case Surface.ROTATION_0:
- return onMeasureInternalPortrait(width, height);
- case Surface.ROTATION_90:
- case Surface.ROTATION_270:
- return onMeasureInternalLandscape(width, height);
- default:
- Log.e(TAG, "Unsupported display rotation: " + displayRotation);
- return super.onMeasureInternal(width, height);
- }
- }
-
- @NonNull
- private AuthDialog.LayoutParams onMeasureInternalPortrait(int width, int height) {
- // Get the height of the everything below the icon. Currently, that's the indicator and
- // button bar.
- final int textIndicatorHeight = getViewHeightPx(R.id.indicator);
- final int buttonBarHeight = getViewHeightPx(R.id.button_bar);
-
- // Figure out where the bottom of the sensor anim should be.
- // Navbar + dialogMargin + buttonBar + textIndicator + spacerHeight = sensorDistFromBottom
- final int dialogMargin = getDialogMarginPx();
- final WindowManager windowManager = getContext().getSystemService(WindowManager.class);
- final int displayHeight = getWindowBounds(windowManager).height();
- final Insets navbarInsets = getNavbarInsets(windowManager);
- final int bottomSpacerHeight = calculateBottomSpacerHeightForPortrait(
- mSensorProps, displayHeight, textIndicatorHeight, buttonBarHeight,
- dialogMargin, navbarInsets.bottom);
-
- // Go through each of the children and do the custom measurement.
- int totalHeight = 0;
- final int numChildren = getChildCount();
- final int sensorDiameter = mSensorProps.sensorRadius * 2;
- for (int i = 0; i < numChildren; i++) {
- final View child = getChildAt(i);
- if (child.getId() == R.id.biometric_icon_frame) {
- final FrameLayout iconFrame = (FrameLayout) child;
- final View icon = iconFrame.getChildAt(0);
-
- // Ensure that the icon is never larger than the sensor.
- icon.measure(
- MeasureSpec.makeMeasureSpec(sensorDiameter, MeasureSpec.AT_MOST),
- MeasureSpec.makeMeasureSpec(sensorDiameter, MeasureSpec.AT_MOST));
-
- // Create a frame that's exactly the height of the sensor circle.
- iconFrame.measure(
- MeasureSpec.makeMeasureSpec(
- child.getLayoutParams().width, MeasureSpec.EXACTLY),
- MeasureSpec.makeMeasureSpec(sensorDiameter, MeasureSpec.EXACTLY));
- } else if (child.getId() == R.id.space_above_icon) {
- child.measure(
- MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
- MeasureSpec.makeMeasureSpec(
- child.getLayoutParams().height, MeasureSpec.EXACTLY));
- } else if (child.getId() == R.id.button_bar) {
- child.measure(
- MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
- MeasureSpec.makeMeasureSpec(child.getLayoutParams().height,
- MeasureSpec.EXACTLY));
- } else if (child.getId() == R.id.space_below_icon) {
- // Set the spacer height so the fingerprint icon is on the physical sensor area
- child.measure(
- MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
- MeasureSpec.makeMeasureSpec(bottomSpacerHeight, MeasureSpec.EXACTLY));
- } else {
- child.measure(
- MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
- MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST));
- }
-
- if (child.getVisibility() != View.GONE) {
- totalHeight += child.getMeasuredHeight();
- }
- }
-
- return new AuthDialog.LayoutParams(width, totalHeight);
- }
-
- @NonNull
- private AuthDialog.LayoutParams onMeasureInternalLandscape(int width, int height) {
- // Find the spacer height needed to vertically align the icon with the sensor.
- final int titleHeight = getViewHeightPx(R.id.title);
- final int subtitleHeight = getViewHeightPx(R.id.subtitle);
- final int descriptionHeight = getViewHeightPx(R.id.description);
- final int topSpacerHeight = getViewHeightPx(R.id.space_above_icon);
- final int textIndicatorHeight = getViewHeightPx(R.id.indicator);
- final int buttonBarHeight = getViewHeightPx(R.id.button_bar);
- final WindowManager windowManager = getContext().getSystemService(WindowManager.class);
- final Insets navbarInsets = getNavbarInsets(windowManager);
- final int bottomSpacerHeight = calculateBottomSpacerHeightForLandscape(titleHeight,
- subtitleHeight, descriptionHeight, topSpacerHeight, textIndicatorHeight,
- buttonBarHeight, navbarInsets.bottom);
-
- // Find the spacer width needed to horizontally align the icon with the sensor.
- final int displayWidth = getWindowBounds(windowManager).width();
- final int dialogMargin = getDialogMarginPx();
- final int horizontalInset = navbarInsets.left + navbarInsets.right;
- final int horizontalSpacerWidth = calculateHorizontalSpacerWidthForLandscape(
- mSensorProps, displayWidth, dialogMargin, horizontalInset);
-
- final int sensorDiameter = mSensorProps.sensorRadius * 2;
- final int remeasuredWidth = sensorDiameter + 2 * horizontalSpacerWidth;
-
- int remeasuredHeight = 0;
- final int numChildren = getChildCount();
- for (int i = 0; i < numChildren; i++) {
- final View child = getChildAt(i);
- if (child.getId() == R.id.biometric_icon_frame) {
- final FrameLayout iconFrame = (FrameLayout) child;
- final View icon = iconFrame.getChildAt(0);
-
- // Ensure that the icon is never larger than the sensor.
- icon.measure(
- MeasureSpec.makeMeasureSpec(sensorDiameter, MeasureSpec.AT_MOST),
- MeasureSpec.makeMeasureSpec(sensorDiameter, MeasureSpec.AT_MOST));
-
- // Create a frame that's exactly the height of the sensor circle.
- iconFrame.measure(
- MeasureSpec.makeMeasureSpec(remeasuredWidth, MeasureSpec.EXACTLY),
- MeasureSpec.makeMeasureSpec(sensorDiameter, MeasureSpec.EXACTLY));
- } else if (child.getId() == R.id.space_above_icon || child.getId() == R.id.button_bar) {
- // Adjust the width of the top spacer and button bar while preserving their heights.
- child.measure(
- MeasureSpec.makeMeasureSpec(remeasuredWidth, MeasureSpec.EXACTLY),
- MeasureSpec.makeMeasureSpec(
- child.getLayoutParams().height, MeasureSpec.EXACTLY));
- } else if (child.getId() == R.id.space_below_icon) {
- // Adjust the bottom spacer height to align the fingerprint icon with the sensor.
- child.measure(
- MeasureSpec.makeMeasureSpec(remeasuredWidth, MeasureSpec.EXACTLY),
- MeasureSpec.makeMeasureSpec(bottomSpacerHeight, MeasureSpec.EXACTLY));
- } else {
- // Use the remeasured width for all other child views.
- child.measure(
- MeasureSpec.makeMeasureSpec(remeasuredWidth, MeasureSpec.EXACTLY),
- MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST));
- }
-
- if (child.getVisibility() != View.GONE) {
- remeasuredHeight += child.getMeasuredHeight();
- }
- }
-
- return new AuthDialog.LayoutParams(remeasuredWidth, remeasuredHeight);
- }
-
- private int getViewHeightPx(@IdRes int viewId) {
- final View view = findViewById(viewId);
- return view != null ? view.getMeasuredHeight() : 0;
- }
-
- private int getDialogMarginPx() {
- return getResources().getDimensionPixelSize(R.dimen.biometric_dialog_border_padding);
- }
-
- @NonNull
- private static Insets getNavbarInsets(@Nullable WindowManager windowManager) {
- return windowManager != null && windowManager.getCurrentWindowMetrics() != null
- ? windowManager.getCurrentWindowMetrics().getWindowInsets()
- .getInsets(WindowInsets.Type.navigationBars())
- : Insets.NONE;
- }
-
- @NonNull
- private static Rect getWindowBounds(@Nullable WindowManager windowManager) {
- return windowManager != null && windowManager.getCurrentWindowMetrics() != null
- ? windowManager.getCurrentWindowMetrics().getBounds()
- : new Rect();
- }
-
- /**
- * For devices in portrait orientation where the sensor is too high up, calculates the amount of
- * padding necessary to center the biometric icon within the sensor's physical location.
- */
- @VisibleForTesting
- static int calculateBottomSpacerHeightForPortrait(
- @NonNull FingerprintSensorPropertiesInternal sensorProperties, int displayHeightPx,
- int textIndicatorHeightPx, int buttonBarHeightPx, int dialogMarginPx,
- int navbarBottomInsetPx) {
-
- final int sensorDistanceFromBottom = displayHeightPx
- - sensorProperties.sensorLocationY
- - sensorProperties.sensorRadius;
-
- final int spacerHeight = sensorDistanceFromBottom
- - textIndicatorHeightPx
- - buttonBarHeightPx
- - dialogMarginPx
- - navbarBottomInsetPx;
-
- Log.d(TAG, "Display height: " + displayHeightPx
- + ", Distance from bottom: " + sensorDistanceFromBottom
- + ", Bottom margin: " + dialogMarginPx
- + ", Navbar bottom inset: " + navbarBottomInsetPx
- + ", Bottom spacer height (portrait): " + spacerHeight);
-
- return spacerHeight;
- }
-
- /**
- * For devices in landscape orientation where the sensor is too high up, calculates the amount
- * of padding necessary to center the biometric icon within the sensor's physical location.
- */
- @VisibleForTesting
- static int calculateBottomSpacerHeightForLandscape(int titleHeightPx, int subtitleHeightPx,
- int descriptionHeightPx, int topSpacerHeightPx, int textIndicatorHeightPx,
- int buttonBarHeightPx, int navbarBottomInsetPx) {
-
- final int dialogHeightAboveIcon = titleHeightPx
- + subtitleHeightPx
- + descriptionHeightPx
- + topSpacerHeightPx;
-
- final int dialogHeightBelowIcon = textIndicatorHeightPx + buttonBarHeightPx;
-
- final int bottomSpacerHeight = dialogHeightAboveIcon
- - dialogHeightBelowIcon
- - navbarBottomInsetPx;
-
- Log.d(TAG, "Title height: " + titleHeightPx
- + ", Subtitle height: " + subtitleHeightPx
- + ", Description height: " + descriptionHeightPx
- + ", Top spacer height: " + topSpacerHeightPx
- + ", Text indicator height: " + textIndicatorHeightPx
- + ", Button bar height: " + buttonBarHeightPx
- + ", Navbar bottom inset: " + navbarBottomInsetPx
- + ", Bottom spacer height (landscape): " + bottomSpacerHeight);
-
- return bottomSpacerHeight;
- }
-
- /**
- * For devices in landscape orientation where the sensor is too left/right, calculates the
- * amount of padding necessary to center the biometric icon within the sensor's physical
- * location.
- */
- @VisibleForTesting
- static int calculateHorizontalSpacerWidthForLandscape(
- @NonNull FingerprintSensorPropertiesInternal sensorProperties, int displayWidthPx,
- int dialogMarginPx, int navbarHorizontalInsetPx) {
-
- final int sensorDistanceFromEdge = displayWidthPx
- - sensorProperties.sensorLocationY
- - sensorProperties.sensorRadius;
-
- final int horizontalPadding = sensorDistanceFromEdge
- - dialogMarginPx
- - navbarHorizontalInsetPx;
-
- Log.d(TAG, "Display width: " + displayWidthPx
- + ", Distance from edge: " + sensorDistanceFromEdge
- + ", Dialog margin: " + dialogMarginPx
- + ", Navbar horizontal inset: " + navbarHorizontalInsetPx
- + ", Horizontal spacer width (landscape): " + horizontalPadding);
-
- return horizontalPadding;
+ final AuthDialog.LayoutParams layoutParams = super.onMeasureInternal(width, height);
+ return mMeasureAdapter != null
+ ? mMeasureAdapter.onMeasureInternal(width, height, layoutParams)
+ : layoutParams;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java
index d59a865e2add..a40af70adb8f 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java
@@ -379,7 +379,9 @@ public abstract class AuthBiometricView extends LinearLayout {
} else {
mNegativeButton.setVisibility(View.VISIBLE);
}
- mTryAgainButton.setVisibility(View.VISIBLE);
+ if (supportsManualRetry()) {
+ mTryAgainButton.setVisibility(View.VISIBLE);
+ }
if (!TextUtils.isEmpty(mSubtitleView.getText())) {
mSubtitleView.setVisibility(View.VISIBLE);
@@ -462,6 +464,10 @@ public abstract class AuthBiometricView extends LinearLayout {
Utils.notifyAccessibilityContentChanged(mAccessibilityManager, this);
}
+ protected boolean supportsManualRetry() {
+ return false;
+ }
+
public void updateState(@BiometricState int newState) {
Log.v(TAG, "newState: " + newState);
@@ -749,7 +755,9 @@ public abstract class AuthBiometricView extends LinearLayout {
for (int i = 0; i < numChildren; i++) {
final View child = getChildAt(i);
- if (child.getId() == R.id.space_above_icon) {
+ if (child.getId() == R.id.space_above_icon
+ || child.getId() == R.id.space_below_icon
+ || child.getId() == R.id.button_bar) {
child.measure(
MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(child.getLayoutParams().height,
@@ -765,11 +773,6 @@ public abstract class AuthBiometricView extends LinearLayout {
child.measure(
MeasureSpec.makeMeasureSpec(width, MeasureSpec.AT_MOST),
MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST));
- } else if (child.getId() == R.id.button_bar) {
- child.measure(
- MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
- MeasureSpec.makeMeasureSpec(child.getLayoutParams().height,
- MeasureSpec.EXACTLY));
} else {
child.measure(
MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
index d05e9278762d..4e93f58dd51f 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
@@ -282,8 +282,9 @@ public class AuthContainerView extends LinearLayout
mPanelController = mInjector.getPanelController(mContext, mPanelView);
// Inflate biometric view only if necessary.
+ final int sensorCount = config.mSensorIds.length;
if (Utils.isBiometricAllowed(mConfig.mPromptInfo)) {
- if (config.mSensorIds.length == 1 || config.mSensorIds.length == 2) {
+ if (sensorCount == 1) {
final int singleSensorAuthId = config.mSensorIds[0];
if (Utils.containsSensorId(mFpProps, singleSensorAuthId)) {
FingerprintSensorPropertiesInternal sensorProps = null;
@@ -314,8 +315,54 @@ public class AuthContainerView extends LinearLayout
mBiometricScrollView = null;
return;
}
+ } else if (sensorCount == 2) {
+ int fingerprintSensorId = -1;
+ int faceSensorId = -1;
+ for (final int sensorId : config.mSensorIds) {
+ if (Utils.containsSensorId(mFpProps, sensorId)) {
+ fingerprintSensorId = sensorId;
+ continue;
+ } else if (Utils.containsSensorId(mFaceProps, sensorId)) {
+ faceSensorId = sensorId;
+ continue;
+ }
+
+ if (fingerprintSensorId != -1 && faceSensorId != -1) {
+ break;
+ }
+ }
+
+ if (fingerprintSensorId == -1 || faceSensorId == -1) {
+ Log.e(TAG, "Missing fingerprint or face for dual-sensor config");
+ mBiometricView = null;
+ mBackgroundView = null;
+ mBiometricScrollView = null;
+ return;
+ }
+
+ FingerprintSensorPropertiesInternal fingerprintSensorProps = null;
+ for (FingerprintSensorPropertiesInternal prop : mFpProps) {
+ if (prop.sensorId == fingerprintSensorId) {
+ fingerprintSensorProps = prop;
+ break;
+ }
+ }
+
+ if (fingerprintSensorProps != null && fingerprintSensorProps.isAnyUdfpsType()) {
+ final AuthBiometricFaceToUdfpsView faceToUdfpsView =
+ (AuthBiometricFaceToUdfpsView) factory.inflate(
+ R.layout.auth_biometric_face_to_udfps_view, null, false);
+ faceToUdfpsView.setFingerprintSensorProps(fingerprintSensorProps);
+ mBiometricView = faceToUdfpsView;
+ } else {
+ Log.e(TAG, "Fingerprint must be UDFPS for dual-sensor config");
+ mBiometricView = null;
+ mBackgroundView = null;
+ mBiometricScrollView = null;
+ return;
+ }
} else {
- Log.e(TAG, "Unsupported sensor array, length: " + config.mSensorIds.length);
+ Log.e(TAG, "Unsupported sensor array, length: " + sensorCount);
mBiometricView = null;
mBackgroundView = null;
mBiometricScrollView = null;
@@ -442,14 +489,18 @@ public class AuthContainerView extends LinearLayout
mPanelController.setPosition(AuthPanelController.POSITION_BOTTOM);
setScrollViewGravity(Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM);
break;
+
case Surface.ROTATION_90:
mPanelController.setPosition(AuthPanelController.POSITION_RIGHT);
setScrollViewGravity(Gravity.CENTER_VERTICAL | Gravity.RIGHT);
break;
+
case Surface.ROTATION_270:
mPanelController.setPosition(AuthPanelController.POSITION_LEFT);
setScrollViewGravity(Gravity.CENTER_VERTICAL | Gravity.LEFT);
break;
+
+ case Surface.ROTATION_180:
default:
Log.e(TAG, "Unsupported display rotation: " + displayRotation);
mPanelController.setPosition(AuthPanelController.POSITION_BOTTOM);
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt
index 2e321338999c..1270677ccbc3 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt
@@ -60,9 +60,8 @@ class AuthRippleView(context: Context?, attrs: AttributeSet?) : View(context, at
animator.duration = RIPPLE_ANIMATION_DURATION
animator.addUpdateListener { animator ->
val now = animator.currentPlayTime
- val phase = now / 30000f
rippleShader.progress = animator.animatedValue as Float
- rippleShader.noisePhase = phase
+ rippleShader.time = now.toFloat()
invalidate()
}
animator.addListener(object : AnimatorListenerAdapter() {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationView.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationView.java
index 60fdbab8482c..5647e4366075 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationView.java
@@ -32,9 +32,10 @@ import android.widget.FrameLayout;
* - optionally can override dozeTimeTick to adjust views for burn-in mitigation
*/
abstract class UdfpsAnimationView extends FrameLayout {
-
+ // mAlpha takes into consideration the status bar expansion amount to fade out icon when
+ // the status bar is expanded
private int mAlpha;
- private boolean mPauseAuth;
+ boolean mPauseAuth;
public UdfpsAnimationView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
@@ -75,7 +76,7 @@ abstract class UdfpsAnimationView extends FrameLayout {
getDrawable().setAlpha(calculateAlpha());
}
- protected final int calculateAlpha() {
+ int calculateAlpha() {
return mPauseAuth ? mAlpha : 255;
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewController.java
index 2f025f63034e..f4993f46bf1d 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewController.java
@@ -16,10 +16,6 @@
package com.android.systemui.biometrics;
-import static com.android.systemui.statusbar.StatusBarState.FULLSCREEN_USER_SWITCHER;
-import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
-import static com.android.systemui.statusbar.StatusBarState.SHADE_LOCKED;
-
import android.annotation.NonNull;
import android.graphics.PointF;
import android.graphics.RectF;
@@ -68,18 +64,13 @@ abstract class UdfpsAnimationViewController<T extends UdfpsAnimationView>
@Override
protected void onViewAttached() {
- mStatusBarStateController.addCallback(mStateListener);
- mStateListener.onStateChanged(mStatusBarStateController.getState());
mStatusBar.addExpansionChangedListener(mStatusBarExpansionChangedListener);
-
mDumpManger.registerDumpable(getDumpTag(), this);
}
@Override
protected void onViewDetached() {
- mStatusBarStateController.removeCallback(mStateListener);
mStatusBar.removeExpansionChangedListener(mStatusBarExpansionChangedListener);
-
mDumpManger.unregisterDumpable(getDumpTag());
}
@@ -106,9 +97,7 @@ abstract class UdfpsAnimationViewController<T extends UdfpsAnimationView>
* authentication.
*/
boolean shouldPauseAuth() {
- return (mNotificationShadeExpanded && mStatusBarState != KEYGUARD)
- || mStatusBarState == SHADE_LOCKED
- || mStatusBarState == FULLSCREEN_USER_SWITCHER;
+ return mNotificationShadeExpanded;
}
/**
@@ -188,13 +177,4 @@ abstract class UdfpsAnimationViewController<T extends UdfpsAnimationView>
updatePauseAuth();
}
};
-
- private final StatusBarStateController.StateListener mStateListener =
- new StatusBarStateController.StateListener() {
- @Override
- public void onStateChanged(int newState) {
- mStatusBarState = newState;
- updatePauseAuth();
- }
- };
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index 078ec9fdfd1c..2bdbf518e203 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -45,12 +45,14 @@ import android.view.VelocityTracker;
import android.view.WindowManager;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.R;
import com.android.systemui.biometrics.HbmTypes.HbmType;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.doze.DozeReceiver;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
@@ -88,6 +90,8 @@ public class UdfpsController implements DozeReceiver, HbmCallback {
@NonNull private final StatusBarKeyguardViewManager mKeyguardViewManager;
@NonNull private final DumpManager mDumpManager;
@NonNull private final AuthRippleController mAuthRippleController;
+ @NonNull private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+ @NonNull private final KeyguardViewMediator mKeyguardViewMediator;
// Currently the UdfpsController supports a single UDFPS sensor. If devices have multiple
// sensors, this, in addition to a lot of the code here, will be updated.
@VisibleForTesting final FingerprintSensorPropertiesInternal mSensorProps;
@@ -307,7 +311,9 @@ public class UdfpsController implements DozeReceiver, HbmCallback {
@NonNull StatusBar statusBar,
@NonNull StatusBarKeyguardViewManager statusBarKeyguardViewManager,
@NonNull DumpManager dumpManager,
- @NonNull AuthRippleController authRippleController) {
+ @NonNull AuthRippleController authRippleController,
+ @NonNull KeyguardUpdateMonitor keyguardUpdateMonitor,
+ @NonNull KeyguardViewMediator keyguardViewMediator) {
mContext = context;
mInflater = inflater;
// The fingerprint manager is queried for UDFPS before this class is constructed, so the
@@ -320,6 +326,8 @@ public class UdfpsController implements DozeReceiver, HbmCallback {
mKeyguardViewManager = statusBarKeyguardViewManager;
mDumpManager = dumpManager;
mAuthRippleController = authRippleController;
+ mKeyguardUpdateMonitor = keyguardUpdateMonitor;
+ mKeyguardViewMediator = keyguardViewMediator;
mSensorProps = findFirstUdfps();
// At least one UDFPS sensor exists
@@ -327,8 +335,7 @@ public class UdfpsController implements DozeReceiver, HbmCallback {
mStatusBar.setSensorRect(getSensorLocation());
mCoreLayoutParams = new WindowManager.LayoutParams(
- // TODO(b/152419866): Use the UDFPS window type when it becomes available.
- WindowManager.LayoutParams.TYPE_BOOT_PROGRESS,
+ WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG,
WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
| WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
| WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
@@ -486,7 +493,10 @@ public class UdfpsController implements DozeReceiver, HbmCallback {
mStatusBarStateController,
mStatusBar,
mKeyguardViewManager,
- mDumpManager
+ mKeyguardUpdateMonitor,
+ mFgExecutor,
+ mDumpManager,
+ mKeyguardViewMediator
);
case IUdfpsOverlayController.REASON_AUTH_BP:
// note: empty controller, currently shows no visual affordance
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapter.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapter.java
new file mode 100644
index 000000000000..1ad2b9ca856c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapter.java
@@ -0,0 +1,330 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.biometrics;
+
+import android.annotation.IdRes;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.graphics.Insets;
+import android.graphics.Rect;
+import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
+import android.util.Log;
+import android.view.Surface;
+import android.view.View;
+import android.view.View.MeasureSpec;
+import android.view.ViewGroup;
+import android.view.WindowInsets;
+import android.view.WindowManager;
+import android.widget.FrameLayout;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.R;
+
+/**
+ * Adapter that remeasures an auth dialog view to ensure that it matches the location of a physical
+ * under-display fingerprint sensor (UDFPS).
+ */
+public class UdfpsDialogMeasureAdapter {
+ private static final String TAG = "UdfpsDialogMeasurementAdapter";
+
+ @NonNull private final ViewGroup mView;
+ @NonNull private final FingerprintSensorPropertiesInternal mSensorProps;
+
+ @Nullable private WindowManager mWindowManager;
+
+ public UdfpsDialogMeasureAdapter(
+ @NonNull ViewGroup view, @NonNull FingerprintSensorPropertiesInternal sensorProps) {
+ mView = view;
+ mSensorProps = sensorProps;
+ }
+
+ @NonNull
+ FingerprintSensorPropertiesInternal getSensorProps() {
+ return mSensorProps;
+ }
+
+ @NonNull
+ AuthDialog.LayoutParams onMeasureInternal(
+ int width, int height, @NonNull AuthDialog.LayoutParams layoutParams) {
+
+ final int displayRotation = mView.getDisplay().getRotation();
+ switch (displayRotation) {
+ case Surface.ROTATION_0:
+ return onMeasureInternalPortrait(width, height);
+ case Surface.ROTATION_90:
+ case Surface.ROTATION_270:
+ return onMeasureInternalLandscape(width, height);
+ default:
+ Log.e(TAG, "Unsupported display rotation: " + displayRotation);
+ return layoutParams;
+ }
+ }
+
+ @NonNull
+ private AuthDialog.LayoutParams onMeasureInternalPortrait(int width, int height) {
+ // Get the height of the everything below the icon. Currently, that's the indicator and
+ // button bar.
+ final int textIndicatorHeight = getViewHeightPx(R.id.indicator);
+ final int buttonBarHeight = getViewHeightPx(R.id.button_bar);
+
+ // Figure out where the bottom of the sensor anim should be.
+ // Navbar + dialogMargin + buttonBar + textIndicator + spacerHeight = sensorDistFromBottom
+ final int dialogMargin = getDialogMarginPx();
+ final int displayHeight = getWindowBounds().height();
+ final Insets navbarInsets = getNavbarInsets();
+ final int bottomSpacerHeight = calculateBottomSpacerHeightForPortrait(
+ mSensorProps, displayHeight, textIndicatorHeight, buttonBarHeight,
+ dialogMargin, navbarInsets.bottom);
+
+ // Go through each of the children and do the custom measurement.
+ int totalHeight = 0;
+ final int numChildren = mView.getChildCount();
+ final int sensorDiameter = mSensorProps.sensorRadius * 2;
+ for (int i = 0; i < numChildren; i++) {
+ final View child = mView.getChildAt(i);
+ if (child.getId() == R.id.biometric_icon_frame) {
+ final FrameLayout iconFrame = (FrameLayout) child;
+ final View icon = iconFrame.getChildAt(0);
+
+ // Ensure that the icon is never larger than the sensor.
+ icon.measure(
+ MeasureSpec.makeMeasureSpec(sensorDiameter, MeasureSpec.AT_MOST),
+ MeasureSpec.makeMeasureSpec(sensorDiameter, MeasureSpec.AT_MOST));
+
+ // Create a frame that's exactly the height of the sensor circle.
+ iconFrame.measure(
+ MeasureSpec.makeMeasureSpec(
+ child.getLayoutParams().width, MeasureSpec.EXACTLY),
+ MeasureSpec.makeMeasureSpec(sensorDiameter, MeasureSpec.EXACTLY));
+ } else if (child.getId() == R.id.space_above_icon) {
+ child.measure(
+ MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
+ MeasureSpec.makeMeasureSpec(
+ child.getLayoutParams().height, MeasureSpec.EXACTLY));
+ } else if (child.getId() == R.id.button_bar) {
+ child.measure(
+ MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
+ MeasureSpec.makeMeasureSpec(child.getLayoutParams().height,
+ MeasureSpec.EXACTLY));
+ } else if (child.getId() == R.id.space_below_icon) {
+ // Set the spacer height so the fingerprint icon is on the physical sensor area
+ child.measure(
+ MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
+ MeasureSpec.makeMeasureSpec(bottomSpacerHeight, MeasureSpec.EXACTLY));
+ } else {
+ child.measure(
+ MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
+ MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST));
+ }
+
+ if (child.getVisibility() != View.GONE) {
+ totalHeight += child.getMeasuredHeight();
+ }
+ }
+
+ return new AuthDialog.LayoutParams(width, totalHeight);
+ }
+
+ @NonNull
+ private AuthDialog.LayoutParams onMeasureInternalLandscape(int width, int height) {
+ // Find the spacer height needed to vertically align the icon with the sensor.
+ final int titleHeight = getViewHeightPx(R.id.title);
+ final int subtitleHeight = getViewHeightPx(R.id.subtitle);
+ final int descriptionHeight = getViewHeightPx(R.id.description);
+ final int topSpacerHeight = getViewHeightPx(R.id.space_above_icon);
+ final int textIndicatorHeight = getViewHeightPx(R.id.indicator);
+ final int buttonBarHeight = getViewHeightPx(R.id.button_bar);
+ final Insets navbarInsets = getNavbarInsets();
+ final int bottomSpacerHeight = calculateBottomSpacerHeightForLandscape(titleHeight,
+ subtitleHeight, descriptionHeight, topSpacerHeight, textIndicatorHeight,
+ buttonBarHeight, navbarInsets.bottom);
+
+ // Find the spacer width needed to horizontally align the icon with the sensor.
+ final int displayWidth = getWindowBounds().width();
+ final int dialogMargin = getDialogMarginPx();
+ final int horizontalInset = navbarInsets.left + navbarInsets.right;
+ final int horizontalSpacerWidth = calculateHorizontalSpacerWidthForLandscape(
+ mSensorProps, displayWidth, dialogMargin, horizontalInset);
+
+ final int sensorDiameter = mSensorProps.sensorRadius * 2;
+ final int remeasuredWidth = sensorDiameter + 2 * horizontalSpacerWidth;
+
+ int remeasuredHeight = 0;
+ final int numChildren = mView.getChildCount();
+ for (int i = 0; i < numChildren; i++) {
+ final View child = mView.getChildAt(i);
+ if (child.getId() == R.id.biometric_icon_frame) {
+ final FrameLayout iconFrame = (FrameLayout) child;
+ final View icon = iconFrame.getChildAt(0);
+
+ // Ensure that the icon is never larger than the sensor.
+ icon.measure(
+ MeasureSpec.makeMeasureSpec(sensorDiameter, MeasureSpec.AT_MOST),
+ MeasureSpec.makeMeasureSpec(sensorDiameter, MeasureSpec.AT_MOST));
+
+ // Create a frame that's exactly the height of the sensor circle.
+ iconFrame.measure(
+ MeasureSpec.makeMeasureSpec(remeasuredWidth, MeasureSpec.EXACTLY),
+ MeasureSpec.makeMeasureSpec(sensorDiameter, MeasureSpec.EXACTLY));
+ } else if (child.getId() == R.id.space_above_icon || child.getId() == R.id.button_bar) {
+ // Adjust the width of the top spacer and button bar while preserving their heights.
+ child.measure(
+ MeasureSpec.makeMeasureSpec(remeasuredWidth, MeasureSpec.EXACTLY),
+ MeasureSpec.makeMeasureSpec(
+ child.getLayoutParams().height, MeasureSpec.EXACTLY));
+ } else if (child.getId() == R.id.space_below_icon) {
+ // Adjust the bottom spacer height to align the fingerprint icon with the sensor.
+ child.measure(
+ MeasureSpec.makeMeasureSpec(remeasuredWidth, MeasureSpec.EXACTLY),
+ MeasureSpec.makeMeasureSpec(bottomSpacerHeight, MeasureSpec.EXACTLY));
+ } else {
+ // Use the remeasured width for all other child views.
+ child.measure(
+ MeasureSpec.makeMeasureSpec(remeasuredWidth, MeasureSpec.EXACTLY),
+ MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST));
+ }
+
+ if (child.getVisibility() != View.GONE) {
+ remeasuredHeight += child.getMeasuredHeight();
+ }
+ }
+
+ return new AuthDialog.LayoutParams(remeasuredWidth, remeasuredHeight);
+ }
+
+ private int getViewHeightPx(@IdRes int viewId) {
+ final View view = mView.findViewById(viewId);
+ return view != null ? view.getMeasuredHeight() : 0;
+ }
+
+ private int getDialogMarginPx() {
+ return mView.getResources().getDimensionPixelSize(R.dimen.biometric_dialog_border_padding);
+ }
+
+ @NonNull
+ private Insets getNavbarInsets() {
+ final WindowManager windowManager = getWindowManager();
+ return windowManager != null && windowManager.getCurrentWindowMetrics() != null
+ ? windowManager.getCurrentWindowMetrics().getWindowInsets()
+ .getInsets(WindowInsets.Type.navigationBars())
+ : Insets.NONE;
+ }
+
+ @NonNull
+ private Rect getWindowBounds() {
+ final WindowManager windowManager = getWindowManager();
+ return windowManager != null && windowManager.getCurrentWindowMetrics() != null
+ ? windowManager.getCurrentWindowMetrics().getBounds()
+ : new Rect();
+ }
+
+ @Nullable
+ private WindowManager getWindowManager() {
+ if (mWindowManager == null) {
+ mWindowManager = mView.getContext().getSystemService(WindowManager.class);
+ }
+ return mWindowManager;
+ }
+
+ /**
+ * For devices in portrait orientation where the sensor is too high up, calculates the amount of
+ * padding necessary to center the biometric icon within the sensor's physical location.
+ */
+ @VisibleForTesting
+ static int calculateBottomSpacerHeightForPortrait(
+ @NonNull FingerprintSensorPropertiesInternal sensorProperties, int displayHeightPx,
+ int textIndicatorHeightPx, int buttonBarHeightPx, int dialogMarginPx,
+ int navbarBottomInsetPx) {
+
+ final int sensorDistanceFromBottom = displayHeightPx
+ - sensorProperties.sensorLocationY
+ - sensorProperties.sensorRadius;
+
+ final int spacerHeight = sensorDistanceFromBottom
+ - textIndicatorHeightPx
+ - buttonBarHeightPx
+ - dialogMarginPx
+ - navbarBottomInsetPx;
+
+ Log.d(TAG, "Display height: " + displayHeightPx
+ + ", Distance from bottom: " + sensorDistanceFromBottom
+ + ", Bottom margin: " + dialogMarginPx
+ + ", Navbar bottom inset: " + navbarBottomInsetPx
+ + ", Bottom spacer height (portrait): " + spacerHeight);
+
+ return spacerHeight;
+ }
+
+ /**
+ * For devices in landscape orientation where the sensor is too high up, calculates the amount
+ * of padding necessary to center the biometric icon within the sensor's physical location.
+ */
+ @VisibleForTesting
+ static int calculateBottomSpacerHeightForLandscape(int titleHeightPx, int subtitleHeightPx,
+ int descriptionHeightPx, int topSpacerHeightPx, int textIndicatorHeightPx,
+ int buttonBarHeightPx, int navbarBottomInsetPx) {
+
+ final int dialogHeightAboveIcon = titleHeightPx
+ + subtitleHeightPx
+ + descriptionHeightPx
+ + topSpacerHeightPx;
+
+ final int dialogHeightBelowIcon = textIndicatorHeightPx + buttonBarHeightPx;
+
+ final int bottomSpacerHeight = dialogHeightAboveIcon
+ - dialogHeightBelowIcon
+ - navbarBottomInsetPx;
+
+ Log.d(TAG, "Title height: " + titleHeightPx
+ + ", Subtitle height: " + subtitleHeightPx
+ + ", Description height: " + descriptionHeightPx
+ + ", Top spacer height: " + topSpacerHeightPx
+ + ", Text indicator height: " + textIndicatorHeightPx
+ + ", Button bar height: " + buttonBarHeightPx
+ + ", Navbar bottom inset: " + navbarBottomInsetPx
+ + ", Bottom spacer height (landscape): " + bottomSpacerHeight);
+
+ return bottomSpacerHeight;
+ }
+
+ /**
+ * For devices in landscape orientation where the sensor is too left/right, calculates the
+ * amount of padding necessary to center the biometric icon within the sensor's physical
+ * location.
+ */
+ @VisibleForTesting
+ static int calculateHorizontalSpacerWidthForLandscape(
+ @NonNull FingerprintSensorPropertiesInternal sensorProperties, int displayWidthPx,
+ int dialogMarginPx, int navbarHorizontalInsetPx) {
+
+ final int sensorDistanceFromEdge = displayWidthPx
+ - sensorProperties.sensorLocationY
+ - sensorProperties.sensorRadius;
+
+ final int horizontalPadding = sensorDistanceFromEdge
+ - dialogMarginPx
+ - navbarHorizontalInsetPx;
+
+ Log.d(TAG, "Display width: " + displayWidthPx
+ + ", Distance from edge: " + sensorDistanceFromEdge
+ + ", Dialog margin: " + dialogMarginPx
+ + ", Navbar horizontal inset: " + navbarHorizontalInsetPx
+ + ", Horizontal spacer width (landscape): " + horizontalPadding);
+
+ return horizontalPadding;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDrawable.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDrawable.java
index 18f54166ad28..55ed5aaff958 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDrawable.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDrawable.java
@@ -18,9 +18,13 @@ package com.android.systemui.biometrics;
import android.content.Context;
import android.graphics.ColorFilter;
+import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
+import android.graphics.drawable.ShapeDrawable;
+import android.graphics.drawable.shapes.PathShape;
+import android.util.PathParser;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -32,15 +36,30 @@ import com.android.systemui.R;
* sensor area.
*/
public abstract class UdfpsDrawable extends Drawable {
- @NonNull protected final Context mContext;
- @NonNull protected final Drawable mFingerprintDrawable;
+ static final float DEFAULT_STROKE_WIDTH = 3f;
+
+ @NonNull final Context mContext;
+ @NonNull final ShapeDrawable mFingerprintDrawable;
+ private final Paint mPaint;
private boolean mIlluminationShowing;
int mAlpha = 255; // 0 - 255
public UdfpsDrawable(@NonNull Context context) {
mContext = context;
- mFingerprintDrawable = context.getResources().getDrawable(R.drawable.ic_fingerprint, null);
+ final String fpPath = context.getResources().getString(R.string.config_udfpsIcon);
+ mFingerprintDrawable = new ShapeDrawable(
+ new PathShape(PathParser.createPathFromPathData(fpPath), 72, 72));
mFingerprintDrawable.mutate();
+
+ mPaint = mFingerprintDrawable.getPaint();
+ mPaint.setStyle(Paint.Style.STROKE);
+ mPaint.setStrokeCap(Paint.Cap.ROUND);
+ setStrokeWidth(DEFAULT_STROKE_WIDTH);
+ }
+
+ void setStrokeWidth(float strokeWidth) {
+ mPaint.setStrokeWidth(strokeWidth);
+ invalidateSelf();
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardDrawable.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardDrawable.java
index 12c15a0882f9..71ed3f8c9b0a 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardDrawable.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardDrawable.java
@@ -18,6 +18,7 @@ package com.android.systemui.biometrics;
import static com.android.systemui.doze.util.BurnInHelperKt.getBurnInOffset;
+import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
@@ -27,6 +28,7 @@ import androidx.annotation.NonNull;
import com.android.internal.graphics.ColorUtils;
import com.android.settingslib.Utils;
+import com.android.systemui.Interpolators;
import com.android.systemui.R;
import com.android.systemui.doze.DozeReceiver;
@@ -37,6 +39,7 @@ public class UdfpsKeyguardDrawable extends UdfpsDrawable implements DozeReceiver
private static final String TAG = "UdfpsAnimationKeyguard";
private final int mAmbientDisplayColor;
+ static final float DEFAULT_AOD_STROKE_WIDTH = 1f;
@NonNull private final Context mContext;
private int mLockScreenColor;
@@ -48,22 +51,31 @@ public class UdfpsKeyguardDrawable extends UdfpsDrawable implements DozeReceiver
private float mBurnInOffsetX;
private float mBurnInOffsetY;
+ private final ValueAnimator mHintAnimator = ValueAnimator.ofFloat(
+ UdfpsKeyguardDrawable.DEFAULT_STROKE_WIDTH,
+ .5f,
+ UdfpsKeyguardDrawable.DEFAULT_STROKE_WIDTH);
+
UdfpsKeyguardDrawable(@NonNull Context context) {
super(context);
mContext = context;
- // TODO: move burn-in to view
mMaxBurnInOffsetX = context.getResources()
.getDimensionPixelSize(R.dimen.udfps_burn_in_offset_x);
mMaxBurnInOffsetY = context.getResources()
.getDimensionPixelSize(R.dimen.udfps_burn_in_offset_y);
+ mHintAnimator.setDuration(2000);
+ mHintAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
+ mHintAnimator.addUpdateListener(anim -> setStrokeWidth((float) anim.getAnimatedValue()));
+
mLockScreenColor = Utils.getColorAttrDefaultColor(mContext, R.attr.wallpaperTextColor);
mAmbientDisplayColor = Color.WHITE;
- updateAodPositionAndColor();
+
+ updateIcon();
}
- private void updateAodPositionAndColor() {
+ private void updateIcon() {
mBurnInOffsetX = MathUtils.lerp(0f,
getBurnInOffset(mMaxBurnInOffsetX * 2, true /* xAxis */)
- mMaxBurnInOffsetX,
@@ -75,12 +87,14 @@ public class UdfpsKeyguardDrawable extends UdfpsDrawable implements DozeReceiver
mFingerprintDrawable.setTint(ColorUtils.blendARGB(mLockScreenColor,
mAmbientDisplayColor, mInterpolatedDarkAmount));
+ setStrokeWidth(MathUtils.lerp(DEFAULT_STROKE_WIDTH, DEFAULT_AOD_STROKE_WIDTH,
+ mInterpolatedDarkAmount));
invalidateSelf();
}
@Override
public void dozeTimeTick() {
- updateAodPositionAndColor();
+ updateIcon();
}
@Override
@@ -88,17 +102,25 @@ public class UdfpsKeyguardDrawable extends UdfpsDrawable implements DozeReceiver
if (isIlluminationShowing()) {
return;
}
+ canvas.save();
+ canvas.translate(mBurnInOffsetX, mBurnInOffsetY);
mFingerprintDrawable.draw(canvas);
+ canvas.restore();
+ }
+
+ void animateHint() {
+ mHintAnimator.start();
}
void onDozeAmountChanged(float linear, float eased) {
+ mHintAnimator.cancel();
mInterpolatedDarkAmount = eased;
- updateAodPositionAndColor();
+ updateIcon();
}
void setLockScreenColor(int color) {
if (mLockScreenColor == color) return;
mLockScreenColor = color;
- updateAodPositionAndColor();
+ updateIcon();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.java
index e2748437c0c6..fc906f2137f2 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.java
@@ -85,7 +85,6 @@ public class UdfpsKeyguardView extends UdfpsAnimationView {
@Override
public boolean dozeTimeTick() {
- // TODO: burnin
mFingerprintDrawable.dozeTimeTick();
return true;
}
@@ -98,15 +97,23 @@ public class UdfpsKeyguardView extends UdfpsAnimationView {
}
}
+ @Override
+ int calculateAlpha() {
+ return mPauseAuth ? 0 : 255;
+ }
+
void onDozeAmountChanged(float linear, float eased) {
mFingerprintDrawable.onDozeAmountChanged(linear, eased);
- invalidate();
+ }
+
+ void animateHint() {
+ mFingerprintDrawable.animateHint();
}
/**
* Animates in the bg protection circle behind the fp icon to highlight the icon.
*/
- void animateHighlightFp() {
+ void animateUdfpsBouncer() {
if (mBgProtection.getVisibility() == View.VISIBLE && mBgProtection.getAlpha() == 1f) {
// already fully highlighted, don't re-animate
return;
@@ -151,10 +158,14 @@ public class UdfpsKeyguardView extends UdfpsAnimationView {
return mStatusBarState == StatusBarState.SHADE_LOCKED;
}
+ boolean isHome() {
+ return mStatusBarState == StatusBarState.SHADE;
+ }
+
/**
* Animates out the bg protection circle behind the fp icon to unhighlight the icon.
*/
- void animateUnhighlightFp(@Nullable Runnable onEndAnimation) {
+ void animateAwayUdfpsBouncer(@Nullable Runnable onEndAnimation) {
if (mBgProtection.getVisibility() == View.GONE) {
// already hidden
return;
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java
index 1f652dbfdaf2..dc0c685bf01e 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java
@@ -16,33 +16,63 @@
package com.android.systemui.biometrics;
+import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
+
import android.annotation.NonNull;
+import android.hardware.biometrics.BiometricSourceType;
+
+import androidx.annotation.Nullable;
+import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.keyguard.KeyguardUpdateMonitorCallback;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
+import com.android.systemui.util.concurrency.DelayableExecutor;
import java.io.FileDescriptor;
import java.io.PrintWriter;
/**
* Class that coordinates non-HBM animations during keyguard authentication.
+ *
+ * Highlights the udfps icon when:
+ * - Face authentication has failed
+ * - Face authentication has been run for > 2 seconds
*/
public class UdfpsKeyguardViewController extends UdfpsAnimationViewController<UdfpsKeyguardView> {
+ private static final long AFTER_FACE_AUTH_HINT_DELAY = 2000;
+
@NonNull private final StatusBarKeyguardViewManager mKeyguardViewManager;
+ @NonNull private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+ @NonNull private final DelayableExecutor mExecutor;
+ @NonNull private final KeyguardViewMediator mKeyguardViewMediator;
- private boolean mForceShow;
+ @Nullable private Runnable mCancelRunnable;
+ private boolean mShowBouncer;
private boolean mQsExpanded;
+ private boolean mFaceDetectRunning;
+ private boolean mHintShown;
+ private boolean mTransitioningFromHome;
+ private int mStatusBarState;
protected UdfpsKeyguardViewController(
@NonNull UdfpsKeyguardView view,
@NonNull StatusBarStateController statusBarStateController,
@NonNull StatusBar statusBar,
@NonNull StatusBarKeyguardViewManager statusBarKeyguardViewManager,
- @NonNull DumpManager dumpManager) {
+ @NonNull KeyguardUpdateMonitor keyguardUpdateMonitor,
+ @NonNull DelayableExecutor mainDelayableExecutor,
+ @NonNull DumpManager dumpManager,
+ @NonNull KeyguardViewMediator keyguardViewMediator) {
super(view, statusBarStateController, statusBar, dumpManager);
mKeyguardViewManager = statusBarKeyguardViewManager;
+ mKeyguardUpdateMonitor = keyguardUpdateMonitor;
+ mExecutor = mainDelayableExecutor;
+ mKeyguardViewMediator = keyguardViewMediator;
}
@Override
@@ -53,6 +83,9 @@ public class UdfpsKeyguardViewController extends UdfpsAnimationViewController<Ud
@Override
protected void onViewAttached() {
super.onViewAttached();
+ mHintShown = false;
+ mKeyguardUpdateMonitor.registerCallback(mKeyguardUpdateMonitorCallback);
+ updateFaceDetectRunning(mKeyguardUpdateMonitor.isFaceDetectionRunning());
final float dozeAmount = mStatusBarStateController.getDozeAmount();
mStatusBarStateController.addCallback(mStateListener);
@@ -64,90 +97,177 @@ public class UdfpsKeyguardViewController extends UdfpsAnimationViewController<Ud
@Override
protected void onViewDetached() {
super.onViewDetached();
+ mKeyguardUpdateMonitor.removeCallback(mKeyguardUpdateMonitorCallback);
+ mFaceDetectRunning = false;
+
mStatusBarStateController.removeCallback(mStateListener);
- mAlternateAuthInterceptor.resetForceShow();
+ mAlternateAuthInterceptor.hideAlternateAuthBouncer();
mKeyguardViewManager.setAlternateAuthInterceptor(null);
+ mTransitioningFromHome = false;
+
+ if (mCancelRunnable != null) {
+ mCancelRunnable.run();
+ mCancelRunnable = null;
+ }
}
@Override
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
super.dump(fd, pw, args);
- pw.println("mForceShow=" + mForceShow);
+ pw.println("mShowBouncer=" + mShowBouncer);
+ pw.println("mFaceDetectRunning=" + mFaceDetectRunning);
+ pw.println("mTransitioningFromHomeToKeyguard=" + mTransitioningFromHome);
}
/**
- * Overrides non-force show logic in shouldPauseAuth to still auth.
+ * Overrides non-bouncer show logic in shouldPauseAuth to still auth.
*/
- private void forceShow(boolean forceShow) {
- if (mForceShow == forceShow) {
+ private void showBouncer(boolean show) {
+ if (mShowBouncer == show) {
return;
}
- mForceShow = forceShow;
+ mShowBouncer = show;
updatePauseAuth();
- if (mForceShow) {
- mView.animateHighlightFp();
+ if (mShowBouncer) {
+ mView.animateUdfpsBouncer();
} else {
- mView.animateUnhighlightFp(() -> mKeyguardViewManager.cancelPostAuthActions());
+ mView.animateAwayUdfpsBouncer(() -> mKeyguardViewManager.cancelPostAuthActions());
}
}
/**
* Returns true if the fingerprint manager is running but we want to temporarily pause
* authentication. On the keyguard, we may want to show udfps when the shade
- * is expanded, so this can be overridden with the forceShow method.
+ * is expanded, so this can be overridden with the showBouncer method.
*/
public boolean shouldPauseAuth() {
- if (mForceShow) {
+ if (mShowBouncer) {
return false;
}
+ if (mStatusBarState != KEYGUARD) {
+ return true;
+ }
+
+ if (mTransitioningFromHome && mKeyguardViewMediator.isAnimatingScreenOff()) {
+ return true;
+ }
+
if (mQsExpanded) {
return true;
}
- return super.shouldPauseAuth();
+ return false;
+ }
+
+ private void cancelDelayedHint() {
+ if (mCancelRunnable != null) {
+ mCancelRunnable.run();
+ mCancelRunnable = null;
+ }
+ }
+
+ private void updateFaceDetectRunning(boolean running) {
+ if (mFaceDetectRunning == running) {
+ return;
+ }
+
+ // show udfps hint a few seconds after face auth started running
+ if (!mFaceDetectRunning && running && !mHintShown && mCancelRunnable == null) {
+ // Face detect started running, show udfps hint after a delay
+ mCancelRunnable = mExecutor.executeDelayed(() -> showHint(false),
+ AFTER_FACE_AUTH_HINT_DELAY);
+ }
+
+ mFaceDetectRunning = running;
+ }
+
+ private void showHint(boolean forceShow) {
+ cancelDelayedHint();
+ if (!mHintShown || forceShow) {
+ mHintShown = true;
+ mView.animateHint();
+ }
}
private final StatusBarStateController.StateListener mStateListener =
new StatusBarStateController.StateListener() {
@Override
public void onDozeAmountChanged(float linear, float eased) {
+ if (linear != 0) showBouncer(false);
mView.onDozeAmountChanged(linear, eased);
- if (linear != 0) forceShow(false);
+ if (linear == 1f) {
+ // transition has finished
+ mTransitioningFromHome = false;
+ }
+ updatePauseAuth();
+ }
+
+ @Override
+ public void onStatePreChange(int oldState, int newState) {
+ mTransitioningFromHome = oldState == StatusBarState.SHADE
+ && newState == StatusBarState.KEYGUARD;
+ updatePauseAuth();
}
@Override
public void onStateChanged(int statusBarState) {
+ mStatusBarState = statusBarState;
mView.setStatusBarState(statusBarState);
}
};
+ private final KeyguardUpdateMonitorCallback mKeyguardUpdateMonitorCallback =
+ new KeyguardUpdateMonitorCallback() {
+ public void onBiometricRunningStateChanged(boolean running,
+ BiometricSourceType biometricSourceType) {
+ if (biometricSourceType == BiometricSourceType.FACE) {
+ updateFaceDetectRunning(running);
+ }
+ }
+
+ public void onBiometricAuthFailed(BiometricSourceType biometricSourceType) {
+ if (biometricSourceType == BiometricSourceType.FACE) {
+ // show udfps hint when face auth fails
+ showHint(true);
+ }
+ }
+
+ public void onBiometricAuthenticated(int userId,
+ BiometricSourceType biometricSourceType, boolean isStrongBiometric) {
+ if (biometricSourceType == BiometricSourceType.FACE) {
+ // cancel delayed hint if face auth succeeded
+ cancelDelayedHint();
+ }
+ }
+ };
+
private final StatusBarKeyguardViewManager.AlternateAuthInterceptor mAlternateAuthInterceptor =
new StatusBarKeyguardViewManager.AlternateAuthInterceptor() {
@Override
- public boolean showAlternativeAuthMethod() {
- if (mForceShow) {
+ public boolean showAlternateAuthBouncer() {
+ if (mShowBouncer) {
return false;
}
- forceShow(true);
+ showBouncer(true);
return true;
}
@Override
- public boolean resetForceShow() {
- if (!mForceShow) {
+ public boolean hideAlternateAuthBouncer() {
+ if (!mShowBouncer) {
return false;
}
- forceShow(false);
+ showBouncer(false);
return true;
}
@Override
- public boolean isShowingAlternateAuth() {
- return mForceShow;
+ public boolean isShowingAlternateAuthBouncer() {
+ return mShowBouncer;
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/camera/CameraIntents.kt b/packages/SystemUI/src/com/android/systemui/camera/CameraIntents.kt
new file mode 100644
index 000000000000..f8a20023e47a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/camera/CameraIntents.kt
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.camera
+
+import android.content.Context
+import android.content.Intent
+import android.provider.MediaStore
+import android.text.TextUtils
+
+import com.android.systemui.R
+
+class CameraIntents {
+ companion object {
+ val DEFAULT_SECURE_CAMERA_INTENT_ACTION =
+ MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE
+ val DEFAULT_INSECURE_CAMERA_INTENT_ACTION =
+ MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA
+
+ @JvmStatic
+ fun getOverrideCameraPackage(context: Context): String? {
+ context.resources.getString(R.string.config_cameraGesturePackage)?.let {
+ if (!TextUtils.isEmpty(it)) {
+ return it
+ }
+ }
+ return null
+ }
+
+ @JvmStatic
+ fun getInsecureCameraIntent(context: Context): Intent {
+ val intent = Intent(DEFAULT_INSECURE_CAMERA_INTENT_ACTION)
+ getOverrideCameraPackage(context)?.let {
+ intent.setPackage(it)
+ }
+ return intent
+ }
+
+ @JvmStatic
+ fun getSecureCameraIntent(context: Context): Intent {
+ val intent = Intent(DEFAULT_SECURE_CAMERA_INTENT_ACTION)
+ getOverrideCameraPackage(context)?.let {
+ intent.setPackage(it)
+ }
+ return intent.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS)
+ }
+
+ @JvmStatic
+ fun isSecureCameraIntent(intent: Intent?): Boolean {
+ return intent?.getAction()?.equals(DEFAULT_SECURE_CAMERA_INTENT_ACTION) ?: false
+ }
+
+ @JvmStatic
+ fun isInsecureCameraIntent(intent: Intent?): Boolean {
+ return intent?.getAction()?.equals(DEFAULT_INSECURE_CAMERA_INTENT_ACTION) ?: false
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java
index ce0b51489490..5cc8299acb94 100644
--- a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java
@@ -42,7 +42,7 @@ import java.text.NumberFormat;
*/
public class WirelessChargingLayout extends FrameLayout {
public static final int UNKNOWN_BATTERY_LEVEL = -1;
- private static final long RIPPLE_ANIMATION_DURATION = 2000;
+ private static final long RIPPLE_ANIMATION_DURATION = 1133;
private ChargingRippleView mRippleView;
public WirelessChargingLayout(Context context) {
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollector.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollector.java
index bb037202d985..3871248eccd5 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollector.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollector.java
@@ -112,9 +112,21 @@ public interface FalsingCollector {
/** */
void onBouncerHidden();
- /** */
+ /**
+ * Call this to record a MotionEvent in the {@link com.android.systemui.plugins.FalsingManager}.
+ *
+ * Be sure to call {@link #onMotionEventComplete()} after the rest of SystemUI is done with the
+ * MotionEvent.
+ */
void onTouchEvent(MotionEvent ev);
+ /**
+ * Call this once SystemUI has completed all processing of a given MotionEvent.
+ *
+ * See {@link #onTouchEvent(MotionEvent)}.
+ */
+ void onMotionEventComplete();
+
/** */
void avoidGesture();
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorFake.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorFake.java
index 939b45a2b4a5..28aac051c66d 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorFake.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorFake.java
@@ -147,6 +147,10 @@ public class FalsingCollectorFake implements FalsingCollector {
}
@Override
+ public void onMotionEventComplete() {
+ }
+
+ @Override
public void avoidGesture() {
}
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java
index cf6169703dca..aaea9ce98359 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java
@@ -275,6 +275,11 @@ class FalsingCollectorImpl implements FalsingCollector {
}
@Override
+ public void onMotionEventComplete() {
+ mFalsingDataProvider.onMotionEventComplete();
+ }
+
+ @Override
public void avoidGesture() {
mAvoidGesture = true;
if (mPendingDownEvent != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java
index 1aaa139eb7ce..2e60a65c3bd1 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java
@@ -81,8 +81,8 @@ public class FalsingDataProvider {
}
if (motionEvent.getActionMasked() == MotionEvent.ACTION_DOWN) {
+ // Ensure prior gesture was completed. May be a no-op.
completePriorGesture();
- mRecentMotionEvents = new TimeLimitedMotionEventBuffer(MOTION_EVENT_AGE_MS);
}
mRecentMotionEvents.addAll(motionEvents);
@@ -100,12 +100,23 @@ public class FalsingDataProvider {
mDirty = true;
}
+ void onMotionEventComplete() {
+ if (mRecentMotionEvents.isEmpty()) {
+ return;
+ }
+ int action = mRecentMotionEvents.get(mRecentMotionEvents.size() - 1).getActionMasked();
+ if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
+ completePriorGesture();
+ }
+ }
+
private void completePriorGesture() {
if (!mRecentMotionEvents.isEmpty()) {
mGestureFinalizedListeners.forEach(listener -> listener.onGestureFinalized(
mRecentMotionEvents.get(mRecentMotionEvents.size() - 1).getEventTime()));
mPriorMotionEvents = mRecentMotionEvents;
+ mRecentMotionEvents = new TimeLimitedMotionEventBuffer(MOTION_EVENT_AGE_MS);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
index 96f207215f71..8c3ef68f0bde 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
@@ -44,8 +44,9 @@ import com.android.systemui.controls.ui.ControlsUiController
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dump.DumpManager
-import com.android.systemui.globalactions.GlobalActionsDialog
import com.android.systemui.settings.UserTracker
+import com.android.systemui.statusbar.policy.DeviceControlsControllerImpl.Companion.PREFS_CONTROLS_FILE
+import com.android.systemui.statusbar.policy.DeviceControlsControllerImpl.Companion.PREFS_CONTROLS_SEEDING_COMPLETED
import com.android.systemui.util.concurrency.DelayableExecutor
import java.io.FileDescriptor
import java.io.PrintWriter
@@ -198,11 +199,11 @@ class ControlsControllerImpl @Inject constructor (
// When a component is uninstalled, allow seeding to happen again if the user
// reinstalls the app
val prefs = userStructure.userContext.getSharedPreferences(
- GlobalActionsDialog.PREFS_CONTROLS_FILE, Context.MODE_PRIVATE)
+ PREFS_CONTROLS_FILE, Context.MODE_PRIVATE)
val completedSeedingPackageSet = prefs.getStringSet(
- GlobalActionsDialog.PREFS_CONTROLS_SEEDING_COMPLETED, mutableSetOf<String>())
+ PREFS_CONTROLS_SEEDING_COMPLETED, mutableSetOf<String>())
val servicePackageSet = serviceInfoSet.map { it.packageName }
- prefs.edit().putStringSet(GlobalActionsDialog.PREFS_CONTROLS_SEEDING_COMPLETED,
+ prefs.edit().putStringSet(PREFS_CONTROLS_SEEDING_COMPLETED,
completedSeedingPackageSet.intersect(servicePackageSet)).apply()
var changed = false
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 7dd1d28170b2..6f94943472b1 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt
@@ -16,6 +16,7 @@
package com.android.systemui.controls.management
+import android.app.ActivityOptions
import android.content.ComponentName
import android.content.Intent
import android.os.Bundle
@@ -34,7 +35,6 @@ import com.android.systemui.controls.controller.ControlsControllerImpl
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.globalactions.GlobalActionsComponent
import com.android.systemui.settings.CurrentUserTracker
import com.android.systemui.util.LifecycleActivity
import javax.inject.Inject
@@ -45,7 +45,6 @@ import javax.inject.Inject
class ControlsEditingActivity @Inject constructor(
private val controller: ControlsControllerImpl,
private val broadcastDispatcher: BroadcastDispatcher,
- private val globalActionsComponent: GlobalActionsComponent,
private val customIconCache: CustomIconCache,
private val uiController: ControlsUiController
) : LifecycleActivity() {
@@ -62,7 +61,6 @@ class ControlsEditingActivity @Inject constructor(
private lateinit var model: FavoritesModel
private lateinit var subtitle: TextView
private lateinit var saveButton: View
- private var backToGlobalActions = true
private val currentUserTracker = object : CurrentUserTracker(broadcastDispatcher) {
private val startingUser = controller.currentUserId
@@ -86,11 +84,6 @@ class ControlsEditingActivity @Inject constructor(
structure = it
} ?: run(this::finish)
- backToGlobalActions = intent.getBooleanExtra(
- ControlsUiController.BACK_TO_GLOBAL_ACTIONS,
- true
- )
-
bindViews()
bindButtons()
@@ -109,15 +102,6 @@ class ControlsEditingActivity @Inject constructor(
}
override fun onBackPressed() {
- if (backToGlobalActions) {
- globalActionsComponent.handleShowGlobalActionsMenu()
- } else {
- val i = Intent().apply {
- component = ComponentName(applicationContext, ControlsActivity::class.java)
- addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK)
- }
- startActivity(i)
- }
animateExitAndFinish()
}
@@ -161,8 +145,12 @@ class ControlsEditingActivity @Inject constructor(
setText(R.string.save)
setOnClickListener {
saveFavorites()
+ startActivity(
+ Intent(applicationContext, ControlsActivity::class.java),
+ ActivityOptions
+ .makeSceneTransitionAnimation(this@ControlsEditingActivity).toBundle()
+ )
animateExitAndFinish()
- globalActionsComponent.handleShowGlobalActionsMenu()
}
}
}
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 309901443393..dca52a9678b9 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt
@@ -43,7 +43,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.dagger.qualifiers.Main
-import com.android.systemui.globalactions.GlobalActionsComponent
import com.android.systemui.settings.CurrentUserTracker
import com.android.systemui.util.LifecycleActivity
import java.text.Collator
@@ -56,7 +55,6 @@ class ControlsFavoritingActivity @Inject constructor(
private val controller: ControlsControllerImpl,
private val listingController: ControlsListingController,
private val broadcastDispatcher: BroadcastDispatcher,
- private val globalActionsComponent: GlobalActionsComponent,
private val uiController: ControlsUiController
) : LifecycleActivity() {
@@ -92,7 +90,6 @@ class ControlsFavoritingActivity @Inject constructor(
private lateinit var comparator: Comparator<StructureContainer>
private var cancelLoadRunnable: Runnable? = null
private var isPagerLoaded = false
- private var backToGlobalActions = true
private val currentUserTracker = object : CurrentUserTracker(broadcastDispatcher) {
private val startingUser = controller.currentUserId
@@ -133,11 +130,6 @@ class ControlsFavoritingActivity @Inject constructor(
component = intent.getParcelableExtra<ComponentName>(Intent.EXTRA_COMPONENT_NAME)
fromProviderSelector = intent.getBooleanExtra(EXTRA_FROM_PROVIDER_SELECTOR, false)
- backToGlobalActions = intent.getBooleanExtra(
- ControlsUiController.BACK_TO_GLOBAL_ACTIONS,
- true
- )
-
bindViews()
}
@@ -311,13 +303,6 @@ class ControlsFavoritingActivity @Inject constructor(
private fun bindButtons() {
otherAppsButton = requireViewById<Button>(R.id.other_apps).apply {
setOnClickListener {
- val i = Intent().apply {
- component = ComponentName(context, ControlsProviderSelectorActivity::class.java)
- putExtra(
- ControlsUiController.BACK_TO_GLOBAL_ACTIONS,
- backToGlobalActions
- )
- }
if (doneButton.isEnabled) {
// The user has made changes
Toast.makeText(
@@ -326,8 +311,11 @@ class ControlsFavoritingActivity @Inject constructor(
Toast.LENGTH_SHORT
).show()
}
- startActivity(i, ActivityOptions
- .makeSceneTransitionAnimation(this@ControlsFavoritingActivity).toBundle())
+ startActivity(
+ Intent(context, ControlsProviderSelectorActivity::class.java),
+ ActivityOptions
+ .makeSceneTransitionAnimation(this@ControlsFavoritingActivity).toBundle()
+ )
animateExitAndFinish()
}
}
@@ -349,15 +337,10 @@ class ControlsFavoritingActivity @Inject constructor(
}
private fun openControlsOrigin() {
- if (backToGlobalActions) {
- globalActionsComponent.handleShowGlobalActionsMenu()
- } else {
- val i = Intent().apply {
- component = ComponentName(applicationContext, ControlsActivity::class.java)
- addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK)
- }
- startActivity(i)
- }
+ startActivity(
+ Intent(applicationContext, ControlsActivity::class.java),
+ ActivityOptions.makeSceneTransitionAnimation(this).toBundle()
+ )
}
override fun onPause() {
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 fa1c41f01e6c..cba3dabc4ff4 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt
@@ -36,7 +36,6 @@ import com.android.systemui.controls.ui.ControlsActivity
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.globalactions.GlobalActionsComponent
import com.android.systemui.settings.CurrentUserTracker
import com.android.systemui.util.LifecycleActivity
import java.util.concurrent.Executor
@@ -50,7 +49,6 @@ class ControlsProviderSelectorActivity @Inject constructor(
@Background private val backExecutor: Executor,
private val listingController: ControlsListingController,
private val controlsController: ControlsController,
- private val globalActionsComponent: GlobalActionsComponent,
private val broadcastDispatcher: BroadcastDispatcher,
private val uiController: ControlsUiController
) : LifecycleActivity() {
@@ -59,7 +57,6 @@ class ControlsProviderSelectorActivity @Inject constructor(
private const val TAG = "ControlsProviderSelectorActivity"
}
- private var backToGlobalActions = true
private lateinit var recyclerView: RecyclerView
private val currentUserTracker = object : CurrentUserTracker(broadcastDispatcher) {
private val startingUser = listingController.currentUserId
@@ -105,23 +102,13 @@ class ControlsProviderSelectorActivity @Inject constructor(
}
}
requireViewById<View>(R.id.done).visibility = View.GONE
-
- backToGlobalActions = intent.getBooleanExtra(
- ControlsUiController.BACK_TO_GLOBAL_ACTIONS,
- true
- )
}
override fun onBackPressed() {
- if (backToGlobalActions) {
- globalActionsComponent.handleShowGlobalActionsMenu()
- } else {
- val i = Intent().apply {
- component = ComponentName(applicationContext, ControlsActivity::class.java)
- addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK)
- }
- startActivity(i)
+ val i = Intent().apply {
+ component = ComponentName(applicationContext, ControlsActivity::class.java)
}
+ startActivity(i, ActivityOptions.makeSceneTransitionAnimation(this).toBundle())
animateExitAndFinish()
}
@@ -169,10 +156,6 @@ class ControlsProviderSelectorActivity @Inject constructor(
listingController.getAppLabel(it))
putExtra(Intent.EXTRA_COMPONENT_NAME, it)
putExtra(ControlsFavoritingActivity.EXTRA_FROM_PROVIDER_SELECTOR, true)
- putExtra(
- ControlsUiController.BACK_TO_GLOBAL_ACTIONS,
- backToGlobalActions
- )
}
startActivity(intent, ActivityOptions.makeSceneTransitionAnimation(this).toBundle())
animateExitAndFinish()
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinator.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinator.kt
index 0db15e83fc93..8029ba844850 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinator.kt
@@ -26,7 +26,7 @@ import android.service.controls.Control
interface ControlActionCoordinator {
// If launched from an Activity, continue within this stack
- var activityContext: Context?
+ var activityContext: Context
/**
* Close any dialogs which may have been open
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt
index 477c22068851..d3d6e03c9bc7 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt
@@ -63,7 +63,7 @@ class ControlActionCoordinatorImpl @Inject constructor(
private var actionsInProgress = mutableSetOf<String>()
private val isLocked: Boolean
get() = !keyguardStateController.isUnlocked()
- override var activityContext: Context? = null
+ override lateinit var activityContext: Context
companion object {
private const val RESPONSE_TIMEOUT_IN_MILLIS = 3000L
@@ -153,14 +153,9 @@ class ControlActionCoordinatorImpl @Inject constructor(
// pending actions will only run after the control state has been refreshed
pendingAction = action
}
- val wasLocked = isLocked
activityStarter.dismissKeyguardThenExecute({
Log.d(ControlsUiController.TAG, "Device unlocked, invoking controls action")
- if (wasLocked && activityContext == null) {
- globalActionsComponent.handleShowGlobalActionsMenu()
- } else {
- action.invoke()
- }
+ action.invoke()
true
}, { pendingAction = null }, true /* afterKeyguardGone */)
} else {
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 a35b792ac7f1..c241c083a0a3 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt
@@ -41,6 +41,14 @@ class ControlsActivity @Inject constructor(
setContentView(R.layout.controls_fullscreen)
+ getLifecycle().addObserver(
+ ControlsAnimations.observerForAnimations(
+ requireViewById<ViewGroup>(R.id.control_detail_root),
+ window,
+ intent
+ )
+ )
+
requireViewById<ViewGroup>(R.id.control_detail_root).apply {
setOnApplyWindowInsetsListener {
v: View, insets: WindowInsets ->
@@ -61,7 +69,7 @@ class ControlsActivity @Inject constructor(
parent = requireViewById<ViewGroup>(R.id.global_actions_controls)
parent.alpha = 0f
- uiController.show(parent, { animateExitAndFinish() }, this)
+ uiController.show(parent, { finish() }, this)
}
override fun onResume() {
@@ -71,7 +79,7 @@ class ControlsActivity @Inject constructor(
}
override fun onBackPressed() {
- animateExitAndFinish()
+ finish()
}
override fun onStop() {
@@ -79,8 +87,4 @@ class ControlsActivity @Inject constructor(
uiController.hide()
}
-
- private fun animateExitAndFinish() {
- ControlsAnimations.exitAnimation(parent, { finish() }).start()
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt
index f86948ee8957..ac13aadeeb40 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt
@@ -28,10 +28,9 @@ interface ControlsUiController {
companion object {
public const val TAG = "ControlsUiController"
public const val EXTRA_ANIMATE = "extra_animate"
- public const val BACK_TO_GLOBAL_ACTIONS = "back_to_global_actions"
}
- fun show(parent: ViewGroup, onDismiss: Runnable, activityContext: Context?)
+ fun show(parent: ViewGroup, onDismiss: Runnable, activityContext: Context)
fun hide()
/**
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
index d08882b1dbd2..954bfb3ff891 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
@@ -19,6 +19,8 @@ package com.android.systemui.controls.ui
import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.animation.ObjectAnimator
+import android.app.Activity
+import android.app.ActivityOptions
import android.content.ComponentName
import android.content.Context
import android.content.Intent
@@ -129,6 +131,7 @@ class ControlsUiControllerImpl @Inject constructor (
override val available: Boolean
get() = controlsController.get().available
+ private lateinit var activityContext: Context
private lateinit var listingCallback: ControlsListingController.ControlsListingCallback
private fun createCallback(
@@ -153,11 +156,12 @@ class ControlsUiControllerImpl @Inject constructor (
override fun show(
parent: ViewGroup,
onDismiss: Runnable,
- activityContext: Context?
+ activityContext: Context
) {
Log.d(ControlsUiController.TAG, "show()")
this.parent = parent
this.onDismiss = onDismiss
+ this.activityContext = activityContext
hidden = false
retainCache = false
@@ -198,7 +202,7 @@ class ControlsUiControllerImpl @Inject constructor (
controlViewsById.clear()
controlsById.clear()
- show(parent, onDismiss, controlActionCoordinator.activityContext)
+ show(parent, onDismiss, activityContext)
val showAnim = ObjectAnimator.ofFloat(parent, "alpha", 0.0f, 1.0f)
showAnim.setInterpolator(DecelerateInterpolator(1.0f))
showAnim.setDuration(FADE_IN_MILLIS)
@@ -220,7 +224,7 @@ class ControlsUiControllerImpl @Inject constructor (
inflater.inflate(R.layout.controls_no_favorites, parent, true)
val viewGroup = parent.requireViewById(R.id.controls_no_favorites_group) as ViewGroup
- viewGroup.setOnClickListener { v: View -> startProviderSelectorActivity(v.context) }
+ viewGroup.setOnClickListener { _: View -> startProviderSelectorActivity() }
val subtitle = parent.requireViewById<TextView>(R.id.controls_subtitle)
subtitle.setText(context.resources.getString(R.string.quick_controls_subtitle))
@@ -234,20 +238,18 @@ class ControlsUiControllerImpl @Inject constructor (
}
}
- private fun startFavoritingActivity(context: Context, si: StructureInfo) {
- startTargetedActivity(context, si, ControlsFavoritingActivity::class.java)
+ private fun startFavoritingActivity(si: StructureInfo) {
+ startTargetedActivity(si, ControlsFavoritingActivity::class.java)
}
- private fun startEditingActivity(context: Context, si: StructureInfo) {
- startTargetedActivity(context, si, ControlsEditingActivity::class.java)
+ private fun startEditingActivity(si: StructureInfo) {
+ startTargetedActivity(si, ControlsEditingActivity::class.java)
}
- private fun startTargetedActivity(context: Context, si: StructureInfo, klazz: Class<*>) {
- val i = Intent(context, klazz).apply {
- addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK)
- }
+ private fun startTargetedActivity(si: StructureInfo, klazz: Class<*>) {
+ val i = Intent(activityContext, klazz)
putIntentExtras(i, si)
- startActivity(context, i)
+ startActivity(i)
retainCache = true
}
@@ -261,27 +263,22 @@ class ControlsUiControllerImpl @Inject constructor (
}
}
- private fun startProviderSelectorActivity(context: Context) {
- val i = Intent(context, ControlsProviderSelectorActivity::class.java).apply {
- addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK)
- }
- startActivity(context, i)
+ private fun startProviderSelectorActivity() {
+ startActivity(Intent(activityContext, ControlsProviderSelectorActivity::class.java))
}
- private fun startActivity(context: Context, intent: Intent) {
+ private fun startActivity(intent: Intent) {
// Force animations when transitioning from a dialog to an activity
intent.putExtra(ControlsUiController.EXTRA_ANIMATE, true)
- intent.putExtra(
- ControlsUiController.BACK_TO_GLOBAL_ACTIONS,
- controlActionCoordinator.activityContext == null
- )
- onDismiss.run()
- activityStarter.dismissKeyguardThenExecute({
- shadeController.collapsePanel(false)
- context.startActivity(intent)
- true
- }, null, true)
+ if (keyguardStateController.isUnlocked()) {
+ activityContext.startActivity(
+ intent,
+ ActivityOptions.makeSceneTransitionAnimation(activityContext as Activity).toBundle()
+ )
+ } else {
+ activityStarter.postStartActivityDismissingKeyguard(intent, 0 /* delay */)
+ }
}
private fun showControlsView(items: List<SelectionItem>) {
@@ -328,9 +325,9 @@ class ControlsUiControllerImpl @Inject constructor (
) {
when (pos) {
// 0: Add Control
- 0 -> startFavoritingActivity(view.context, selectedStructure)
+ 0 -> startFavoritingActivity(selectedStructure)
// 1: Edit controls
- 1 -> startEditingActivity(view.context, selectedStructure)
+ 1 -> startEditingActivity(selectedStructure)
}
dismiss()
}
@@ -399,15 +396,9 @@ class ControlsUiControllerImpl @Inject constructor (
val inflater = LayoutInflater.from(context)
inflater.inflate(R.layout.controls_with_favorites, parent, true)
- if (controlActionCoordinator.activityContext == null) {
- parent.requireViewById<View>(R.id.controls_spacer).apply {
- visibility = View.VISIBLE
- }
- } else {
- parent.requireViewById<ImageView>(R.id.controls_close).apply {
- setOnClickListener { _: View -> onDismiss.run() }
- visibility = View.VISIBLE
- }
+ parent.requireViewById<ImageView>(R.id.controls_close).apply {
+ setOnClickListener { _: View -> onDismiss.run() }
+ visibility = View.VISIBLE
}
val maxColumns = findMaxColumns()
@@ -522,8 +513,6 @@ class ControlsUiControllerImpl @Inject constructor (
override fun hide() {
hidden = true
- controlActionCoordinator.activityContext = null
-
closeDialogs(true)
controlsController.get().unsubscribe()
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java b/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java
index 1a729295165c..187caf9c2b2e 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java
@@ -31,7 +31,6 @@ import android.os.HandlerThread;
import android.os.Looper;
import android.os.ServiceManager;
import android.os.UserHandle;
-import android.service.quickaccesswallet.QuickAccessWalletClient;
import android.view.Choreographer;
import android.view.IWindowManager;
import android.view.LayoutInflater;
@@ -376,11 +375,4 @@ public class DependencyProvider {
public ModeSwitchesController providesModeSwitchesController(Context context) {
return new ModeSwitchesController(context);
}
-
- /** */
- @Provides
- @SysUISingleton
- public QuickAccessWalletClient provideQuickAccessWalletClient(Context context) {
- return QuickAccessWalletClient.create(context);
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
index 1c5715c0296d..fd80d50c2894 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
@@ -59,6 +59,7 @@ import android.permission.PermissionManager;
import android.service.dreams.DreamService;
import android.service.dreams.IDreamManager;
import android.telecom.TelecomManager;
+import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.view.IWindowManager;
import android.view.ViewConfiguration;
@@ -328,6 +329,12 @@ public class FrameworkServicesModule {
@Provides
@Singleton
+ static SubscriptionManager provideSubcriptionManager(Context context) {
+ return context.getSystemService(SubscriptionManager.class);
+ }
+
+ @Provides
+ @Singleton
@Nullable
static TelecomManager provideTelecomManager(Context context) {
return context.getSystemService(TelecomManager.class);
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index b67db03a743c..365a102b61dd 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -73,6 +73,7 @@ import com.android.systemui.util.settings.SettingsUtilModule;
import com.android.systemui.util.time.SystemClock;
import com.android.systemui.util.time.SystemClockImpl;
import com.android.systemui.volume.dagger.VolumeModule;
+import com.android.systemui.wallet.dagger.WalletModule;
import com.android.systemui.wmshell.BubblesManager;
import com.android.wm.shell.bubbles.Bubbles;
@@ -109,7 +110,8 @@ import dagger.Provides;
TunerModule.class,
UserModule.class,
UtilModule.class,
- VolumeModule.class
+ VolumeModule.class,
+ WalletModule.class
},
subcomponents = {
StatusBarComponent.class,
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
index b7f6e2bb965a..5cea31b5a905 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
@@ -81,6 +81,10 @@ public class DozeSensors {
private boolean mSettingRegistered;
private boolean mListening;
private boolean mListeningTouchScreenSensors;
+ private boolean mListeningProxSensors;
+
+ // whether to only register sensors that use prox when the display state is dozing or off
+ private boolean mSelectivelyRegisterProxSensors;
@VisibleForTesting
public enum DozeSensorsUiEvent implements UiEventLogger.UiEventEnum {
@@ -112,6 +116,8 @@ public class DozeSensors {
mSecureSettings = secureSettings;
mCallback = callback;
mProximitySensor = proximitySensor;
+ mSelectivelyRegisterProxSensors = dozeParameters.getSelectivelyRegisterSensorsUsingProx();
+ mListeningProxSensors = !mSelectivelyRegisterProxSensors;
boolean udfpsEnrolled =
authController.isUdfpsEnrolled(KeyguardUpdateMonitor.getCurrentUser());
@@ -131,6 +137,7 @@ public class DozeSensors {
DozeLog.REASON_SENSOR_PICKUP, false /* touchCoords */,
false /* touchscreen */,
false /* ignoresSetting */,
+ false /* requires prox */,
dozeLog),
new TriggerSensor(
findSensorWithType(config.doubleTapSensorType()),
@@ -143,10 +150,13 @@ public class DozeSensors {
new TriggerSensor(
findSensorWithType(config.tapSensorType()),
Settings.Secure.DOZE_TAP_SCREEN_GESTURE,
+ true /* settingDef */,
true /* configured */,
DozeLog.REASON_SENSOR_TAP,
false /* reports touch coordinates */,
true /* touchscreen */,
+ false /* ignoresSetting */,
+ dozeParameters.singleTapUsesProx() /* requiresProx */,
dozeLog),
new TriggerSensor(
findSensorWithType(config.longPressSensorType()),
@@ -156,6 +166,8 @@ public class DozeSensors {
DozeLog.PULSE_REASON_SENSOR_LONG_PRESS,
true /* reports touch coordinates */,
true /* touchscreen */,
+ false /* ignoresSetting */,
+ dozeParameters.longPressUsesProx() /* requiresProx */,
dozeLog),
new TriggerSensor(
findSensorWithType(config.udfpsLongPressSensorType()),
@@ -165,6 +177,8 @@ public class DozeSensors {
DozeLog.REASON_SENSOR_UDFPS_LONG_PRESS,
true /* reports touch coordinates */,
true /* touchscreen */,
+ false /* ignoresSetting */,
+ dozeParameters.longPressUsesProx(),
dozeLog),
new PluginSensor(
new SensorManagerPlugin.Sensor(TYPE_WAKE_DISPLAY),
@@ -255,13 +269,31 @@ public class DozeSensors {
}
/**
+ * If sensors should be registered and sending signals.
+ */
+ public void setListening(boolean listen, boolean includeTouchScreenSensors,
+ boolean lowPowerStateOrOff) {
+ final boolean shouldRegisterProxSensors =
+ !mSelectivelyRegisterProxSensors || lowPowerStateOrOff;
+ if (mListening == listen && mListeningTouchScreenSensors == includeTouchScreenSensors
+ && mListeningProxSensors == shouldRegisterProxSensors) {
+ return;
+ }
+ mListening = listen;
+ mListeningTouchScreenSensors = includeTouchScreenSensors;
+ mListeningProxSensors = shouldRegisterProxSensors;
+ updateListening();
+ }
+
+ /**
* Registers/unregisters sensors based on internal state.
*/
private void updateListening() {
boolean anyListening = false;
for (TriggerSensor s : mSensors) {
boolean listen = mListening
- && (!s.mRequiresTouchscreen || mListeningTouchScreenSensors);
+ && (!s.mRequiresTouchscreen || mListeningTouchScreenSensors)
+ && (!s.mRequiresProx || mListeningProxSensors);
s.setListening(listen);
if (listen) {
anyListening = true;
@@ -337,6 +369,8 @@ public class DozeSensors {
public void dump(PrintWriter pw) {
pw.println("mListening=" + mListening);
pw.println("mListeningTouchScreenSensors=" + mListeningTouchScreenSensors);
+ pw.println("mSelectivelyRegisterProxSensors=" + mSelectivelyRegisterProxSensors);
+ pw.println("mListeningProxSensors=" + mListeningProxSensors);
IndentingPrintWriter idpw = new IndentingPrintWriter(pw);
idpw.increaseIndent();
for (TriggerSensor s : mSensors) {
@@ -361,6 +395,7 @@ public class DozeSensors {
private final boolean mReportsTouchCoordinates;
private final boolean mSettingDefault;
private final boolean mRequiresTouchscreen;
+ private final boolean mRequiresProx;
protected boolean mRequested;
protected boolean mRegistered;
@@ -378,12 +413,14 @@ public class DozeSensors {
boolean configured, int pulseReason, boolean reportsTouchCoordinates,
boolean requiresTouchscreen, DozeLog dozeLog) {
this(sensor, setting, settingDef, configured, pulseReason, reportsTouchCoordinates,
- requiresTouchscreen, false /* ignoresSetting */, dozeLog);
+ requiresTouchscreen, false /* ignoresSetting */,
+ false /* requiresProx */, dozeLog);
}
private TriggerSensor(Sensor sensor, String setting, boolean settingDef,
boolean configured, int pulseReason, boolean reportsTouchCoordinates,
- boolean requiresTouchscreen, boolean ignoresSetting, DozeLog dozeLog) {
+ boolean requiresTouchscreen, boolean ignoresSetting, boolean requiresProx,
+ DozeLog dozeLog) {
mSensor = sensor;
mSetting = setting;
mSettingDefault = settingDef;
@@ -392,6 +429,7 @@ public class DozeSensors {
mReportsTouchCoordinates = reportsTouchCoordinates;
mRequiresTouchscreen = requiresTouchscreen;
mIgnoresSetting = ignoresSetting;
+ mRequiresProx = requiresProx;
mDozeLog = dozeLog;
}
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
index aa66b75325c2..ee559659801b 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
@@ -17,7 +17,6 @@
package com.android.systemui.doze;
import android.annotation.Nullable;
-import android.app.AlarmManager;
import android.app.UiModeManager;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -42,7 +41,6 @@ import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.systemui.Dependency;
import com.android.systemui.biometrics.AuthController;
import com.android.systemui.broadcast.BroadcastDispatcher;
-import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dock.DockManager;
import com.android.systemui.doze.dagger.DozeScope;
@@ -101,15 +99,16 @@ public class DozeTriggers implements DozeMachine.Part {
private final BroadcastDispatcher mBroadcastDispatcher;
private final AuthController mAuthController;
private final DelayableExecutor mMainExecutor;
- private final DelayableExecutor mBgExecutor;
private long mNotificationPulseTime;
private boolean mPulsePending;
private final MetricsLogger mMetricsLogger = Dependency.get(MetricsLogger.class);
- private boolean mWantProx;
- private boolean mWantSensors;
+
+ /** see {@link #onProximityFar} prox for callback */
+ private boolean mWantProxSensor;
private boolean mWantTouchScreenSensors;
+ private boolean mWantSensors;
@VisibleForTesting
public enum DozingUpdateUiEvent implements UiEventLogger.UiEventEnum {
@@ -177,13 +176,13 @@ public class DozeTriggers implements DozeMachine.Part {
@Inject
public DozeTriggers(Context context, DozeHost dozeHost,
- AlarmManager alarmManager, AmbientDisplayConfiguration config,
+ AmbientDisplayConfiguration config,
DozeParameters dozeParameters, AsyncSensorManager sensorManager,
WakeLock wakeLock, DockManager dockManager,
ProximitySensor proximitySensor, ProximitySensor.ProximityCheck proxCheck,
DozeLog dozeLog, BroadcastDispatcher broadcastDispatcher,
SecureSettings secureSettings, AuthController authController,
- @Main DelayableExecutor mainExecutor, @Background DelayableExecutor bgExecutor) {
+ @Main DelayableExecutor mainExecutor) {
mContext = context;
mDozeHost = dozeHost;
mConfig = config;
@@ -201,7 +200,6 @@ public class DozeTriggers implements DozeMachine.Part {
mBroadcastDispatcher = broadcastDispatcher;
mAuthController = authController;
mMainExecutor = mainExecutor;
- mBgExecutor = bgExecutor;
}
@Override
@@ -459,7 +457,7 @@ public class DozeTriggers implements DozeMachine.Part {
break;
case DOZE:
case DOZE_AOD:
- mWantProx = newState != DozeMachine.State.DOZE;
+ mWantProxSensor = newState != DozeMachine.State.DOZE;
mWantSensors = true;
mWantTouchScreenSensors = true;
if (newState == DozeMachine.State.DOZE_AOD && !sWakeDisplaySensorState) {
@@ -468,15 +466,15 @@ public class DozeTriggers implements DozeMachine.Part {
break;
case DOZE_AOD_PAUSED:
case DOZE_AOD_PAUSING:
- mWantProx = true;
+ mWantProxSensor = true;
break;
case DOZE_PULSING:
case DOZE_PULSING_BRIGHT:
- mWantProx = true;
+ mWantProxSensor = true;
mWantTouchScreenSensors = false;
break;
case DOZE_AOD_DOCKED:
- mWantProx = false;
+ mWantProxSensor = false;
mWantTouchScreenSensors = false;
break;
case DOZE_PULSE_DONE:
@@ -490,7 +488,7 @@ public class DozeTriggers implements DozeMachine.Part {
mDozeSensors.setListening(false, false);
mDozeSensors.setProxListening(false);
mWantSensors = false;
- mWantProx = false;
+ mWantProxSensor = false;
mWantTouchScreenSensors = false;
break;
default:
@@ -501,10 +499,10 @@ public class DozeTriggers implements DozeMachine.Part {
@Override
public void onScreenState(int state) {
mDozeSensors.onScreenState(state);
- mDozeSensors.setProxListening(mWantProx && (state == Display.STATE_DOZE
- || state == Display.STATE_DOZE_SUSPEND
- || state == Display.STATE_OFF));
- mDozeSensors.setListening(mWantSensors, mWantTouchScreenSensors);
+ final boolean lowPowerStateOrOff = state == Display.STATE_DOZE
+ || state == Display.STATE_DOZE_SUSPEND || state == Display.STATE_OFF;
+ mDozeSensors.setProxListening(mWantProxSensor && lowPowerStateOrOff);
+ mDozeSensors.setListening(mWantSensors, mWantTouchScreenSensors, lowPowerStateOrOff);
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/fragments/FragmentService.java b/packages/SystemUI/src/com/android/systemui/fragments/FragmentService.java
index 0673879758ad..37b8a2c953fe 100644
--- a/packages/SystemUI/src/com/android/systemui/fragments/FragmentService.java
+++ b/packages/SystemUI/src/com/android/systemui/fragments/FragmentService.java
@@ -23,6 +23,7 @@ import android.view.View;
import com.android.systemui.Dumpable;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.qs.QSFragment;
+import com.android.systemui.statusbar.phone.CollapsedStatusBarFragment;
import com.android.systemui.statusbar.policy.ConfigurationController;
import java.io.FileDescriptor;
@@ -128,6 +129,9 @@ public class FragmentService implements Dumpable {
* Inject a QSFragment.
*/
QSFragment createQSFragment();
+
+ /** Inject a CollapsedStatusBarFragment. */
+ CollapsedStatusBarFragment createCollapsedStatusBarFragment();
}
private class FragmentHostState {
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
index 18627189f188..ab1de9528586 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
@@ -15,18 +15,8 @@
package com.android.systemui.globalactions;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
-import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
-import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
-import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_GLOBAL_ACTIONS;
-import static android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN;
-import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_2BUTTON;
-import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST;
-import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT;
-import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN;
-import static com.android.systemui.controls.dagger.ControlsComponent.Visibility.AVAILABLE;
-import static com.android.systemui.controls.dagger.ControlsComponent.Visibility.AVAILABLE_AFTER_UNLOCK;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_GLOBAL_ACTIONS_SHOWING;
import android.animation.Animator;
@@ -34,121 +24,62 @@ import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.annotation.Nullable;
-import android.app.ActivityManager;
-import android.app.Dialog;
import android.app.IActivityManager;
import android.app.PendingIntent;
-import android.app.StatusBarManager;
-import android.app.WallpaperManager;
import android.app.admin.DevicePolicyManager;
import android.app.trust.TrustManager;
-import android.content.BroadcastReceiver;
-import android.content.ComponentName;
-import android.content.ContentResolver;
import android.content.Context;
import android.content.DialogInterface;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.SharedPreferences;
-import android.content.pm.PackageManager;
-import android.content.pm.UserInfo;
-import android.content.res.ColorStateList;
-import android.content.res.Configuration;
import android.content.res.Resources;
import android.database.ContentObserver;
-import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.media.AudioManager;
-import android.net.ConnectivityManager;
-import android.os.Binder;
import android.os.Handler;
-import android.os.IBinder;
-import android.os.Message;
-import android.os.RemoteException;
-import android.os.SystemProperties;
-import android.os.UserHandle;
import android.os.UserManager;
import android.os.Vibrator;
import android.provider.Settings;
import android.service.dreams.IDreamManager;
-import android.sysprop.TelephonyProperties;
import android.telecom.TelecomManager;
-import android.telephony.ServiceState;
-import android.telephony.TelephonyCallback;
-import android.telephony.TelephonyManager;
import android.transition.AutoTransition;
import android.transition.TransitionManager;
import android.transition.TransitionSet;
-import android.util.ArraySet;
-import android.util.Log;
-import android.view.ContextThemeWrapper;
import android.view.IWindowManager;
-import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowInsets;
import android.view.WindowManager;
-import android.view.accessibility.AccessibilityEvent;
-import android.widget.BaseAdapter;
import android.widget.FrameLayout;
-import android.widget.ImageView;
-import android.widget.ImageView.ScaleType;
-import android.widget.LinearLayout;
-import android.widget.ListPopupWindow;
import android.widget.TextView;
-import androidx.annotation.NonNull;
-import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.LifecycleOwner;
-import androidx.lifecycle.LifecycleRegistry;
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.colorextraction.ColorExtractor;
-import com.android.internal.colorextraction.ColorExtractor.GradientColors;
-import com.android.internal.colorextraction.drawable.ScrimDrawable;
import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.UiEvent;
import com.android.internal.logging.UiEventLogger;
-import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.internal.statusbar.IStatusBarService;
-import com.android.internal.util.EmergencyAffordanceManager;
-import com.android.internal.util.ScreenshotHelper;
import com.android.internal.view.RotationPolicy;
import com.android.internal.widget.LockPatternUtils;
import com.android.systemui.Interpolators;
-import com.android.systemui.MultiListLayout;
-import com.android.systemui.MultiListLayout.MultiListAdapter;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.colorextraction.SysuiColorExtractor;
-import com.android.systemui.controls.ControlsServiceInfo;
-import com.android.systemui.controls.controller.ControlsController;
-import com.android.systemui.controls.dagger.ControlsComponent;
-import com.android.systemui.controls.management.ControlsAnimations;
-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.model.SysUiState;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.GlobalActions.GlobalActionsManager;
import com.android.systemui.plugins.GlobalActionsPanelPlugin;
-import com.android.systemui.settings.UserContextProvider;
import com.android.systemui.statusbar.NotificationShadeDepthController;
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.telephony.TelephonyListenerManager;
-import com.android.systemui.util.EmergencyDialerConstants;
import com.android.systemui.util.RingerModeTracker;
import com.android.systemui.util.leak.RotationUtils;
+import com.android.systemui.util.settings.GlobalSettings;
+import com.android.systemui.util.settings.SecureSettings;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Optional;
-import java.util.Set;
import java.util.concurrent.Executor;
import javax.inject.Inject;
@@ -157,144 +88,29 @@ import javax.inject.Provider;
/**
* Helper to show the global actions dialog. Each item is an {@link Action} that may show depending
* on whether the keyguard is showing, and whether the device is provisioned.
+ * This version includes wallet.
*/
-public class GlobalActionsDialog implements DialogInterface.OnDismissListener,
+public class GlobalActionsDialog extends GlobalActionsDialogLite
+ implements DialogInterface.OnDismissListener,
DialogInterface.OnShowListener,
ConfigurationController.ConfigurationListener,
GlobalActionsPanelPlugin.Callbacks,
LifecycleOwner {
- public static final String SYSTEM_DIALOG_REASON_KEY = "reason";
- public static final String SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS = "globalactions";
- public static final String SYSTEM_DIALOG_REASON_DREAM = "dream";
-
private static final String TAG = "GlobalActionsDialog";
- private static final boolean SHOW_SILENT_TOGGLE = true;
-
- /* Valid settings for global actions keys.
- * see config.xml config_globalActionList */
- @VisibleForTesting
- static final String GLOBAL_ACTION_KEY_POWER = "power";
- private static final String GLOBAL_ACTION_KEY_AIRPLANE = "airplane";
- static final String GLOBAL_ACTION_KEY_BUGREPORT = "bugreport";
- private static final String GLOBAL_ACTION_KEY_SILENT = "silent";
- private static final String GLOBAL_ACTION_KEY_USERS = "users";
- private static final String GLOBAL_ACTION_KEY_SETTINGS = "settings";
- static final String GLOBAL_ACTION_KEY_LOCKDOWN = "lockdown";
- private static final String GLOBAL_ACTION_KEY_VOICEASSIST = "voiceassist";
- private static final String GLOBAL_ACTION_KEY_ASSIST = "assist";
- static final String GLOBAL_ACTION_KEY_RESTART = "restart";
- private static final String GLOBAL_ACTION_KEY_LOGOUT = "logout";
- static final String GLOBAL_ACTION_KEY_EMERGENCY = "emergency";
- static final String GLOBAL_ACTION_KEY_SCREENSHOT = "screenshot";
-
- public static final String PREFS_CONTROLS_SEEDING_COMPLETED = "SeedingCompleted";
- public static final String PREFS_CONTROLS_FILE = "controls_prefs";
- private static final int SEEDING_MAX = 2;
-
- private final Context mContext;
- private final GlobalActionsManager mWindowManagerFuncs;
- private final AudioManager mAudioManager;
- private final IDreamManager mDreamManager;
- private final DevicePolicyManager mDevicePolicyManager;
private final LockPatternUtils mLockPatternUtils;
private final KeyguardStateController mKeyguardStateController;
- private final BroadcastDispatcher mBroadcastDispatcher;
- private final ContentResolver mContentResolver;
- private final Resources mResources;
- private final ConfigurationController mConfigurationController;
- private final UserManager mUserManager;
- private final TrustManager mTrustManager;
- private final IActivityManager mIActivityManager;
- private final TelecomManager mTelecomManager;
- private final MetricsLogger mMetricsLogger;
- private final UiEventLogger mUiEventLogger;
private final NotificationShadeDepthController mDepthController;
private final SysUiState mSysUiState;
-
- // Used for RingerModeTracker
- private final LifecycleRegistry mLifecycle = new LifecycleRegistry(this);
-
- @VisibleForTesting
- protected final ArrayList<Action> mItems = new ArrayList<>();
- @VisibleForTesting
- protected final ArrayList<Action> mOverflowItems = new ArrayList<>();
- @VisibleForTesting
- protected final ArrayList<Action> mPowerItems = new ArrayList<>();
-
- @VisibleForTesting
- protected ActionsDialog mDialog;
-
- private Action mSilentModeAction;
- private ToggleAction mAirplaneModeOn;
-
- private MyAdapter mAdapter;
- private MyOverflowAdapter mOverflowAdapter;
- private MyPowerOptionsAdapter mPowerAdapter;
-
- private boolean mKeyguardShowing = false;
- private boolean mDeviceProvisioned = false;
- private ToggleState mAirplaneState = ToggleState.Off;
- private boolean mIsWaitingForEcmExit = false;
- private boolean mHasTelephony;
- private boolean mHasVibrator;
- private final boolean mShowSilentToggle;
- private final EmergencyAffordanceManager mEmergencyAffordanceManager;
- private final ScreenshotHelper mScreenshotHelper;
private final ActivityStarter mActivityStarter;
private final SysuiColorExtractor mSysuiColorExtractor;
private final IStatusBarService mStatusBarService;
private final NotificationShadeWindowController mNotificationShadeWindowController;
private GlobalActionsPanelPlugin mWalletPlugin;
- private Optional<ControlsUiController> mControlsUiControllerOptional;
- private final IWindowManager mIWindowManager;
- private final Executor mBackgroundExecutor;
- private List<ControlsServiceInfo> mControlsServiceInfos = new ArrayList<>();
- private ControlsComponent mControlsComponent;
- private Optional<ControlsController> mControlsControllerOptional;
- private final RingerModeTracker mRingerModeTracker;
- private int mDialogPressDelay = DIALOG_PRESS_DELAY; // ms
- private Handler mMainHandler;
- private UserContextProvider mUserContextProvider;
- @VisibleForTesting
- boolean mShowLockScreenCardsAndControls = false;
- private int mSmallestScreenWidthDp;
@VisibleForTesting
- public enum GlobalActionsEvent implements UiEventLogger.UiEventEnum {
- @UiEvent(doc = "The global actions / power menu surface became visible on the screen.")
- GA_POWER_MENU_OPEN(337),
-
- @UiEvent(doc = "The global actions / power menu surface was dismissed.")
- GA_POWER_MENU_CLOSE(471),
-
- @UiEvent(doc = "The global actions bugreport button was pressed.")
- GA_BUGREPORT_PRESS(344),
-
- @UiEvent(doc = "The global actions bugreport button was long pressed.")
- GA_BUGREPORT_LONG_PRESS(345),
-
- @UiEvent(doc = "The global actions emergency button was pressed.")
- GA_EMERGENCY_DIALER_PRESS(346),
-
- @UiEvent(doc = "The global actions screenshot button was pressed.")
- GA_SCREENSHOT_PRESS(347),
-
- @UiEvent(doc = "The global actions screenshot button was long pressed.")
- GA_SCREENSHOT_LONG_PRESS(348);
-
- private final int mId;
-
- GlobalActionsEvent(int id) {
- mId = id;
- }
-
- @Override
- public int getId() {
- return mId;
- }
- }
+ boolean mShowLockScreenCards = false;
/**
* @param context everything needs a context :(
@@ -304,9 +120,9 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener,
AudioManager audioManager, IDreamManager iDreamManager,
DevicePolicyManager devicePolicyManager, LockPatternUtils lockPatternUtils,
BroadcastDispatcher broadcastDispatcher,
- ConnectivityManager connectivityManager,
TelephonyListenerManager telephonyListenerManager,
- ContentResolver contentResolver, @Nullable Vibrator vibrator, @Main Resources resources,
+ GlobalSettings globalSettings, SecureSettings secureSettings,
+ @Nullable Vibrator vibrator, @Main Resources resources,
ConfigurationController configurationController, ActivityStarter activityStarter,
KeyguardStateController keyguardStateController, UserManager userManager,
TrustManager trustManager, IActivityManager iActivityManager,
@@ -317,114 +133,57 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener,
IWindowManager iWindowManager,
@Background Executor backgroundExecutor,
UiEventLogger uiEventLogger,
- RingerModeTracker ringerModeTracker, SysUiState sysUiState, @Main Handler handler,
- ControlsComponent controlsComponent,
- UserContextProvider userContextProvider) {
- mContext = context;
- mWindowManagerFuncs = windowManagerFuncs;
- mAudioManager = audioManager;
- mDreamManager = iDreamManager;
- mDevicePolicyManager = devicePolicyManager;
+ RingerModeTracker ringerModeTracker, SysUiState sysUiState, @Main Handler handler) {
+
+ super(context, windowManagerFuncs,
+ audioManager, iDreamManager,
+ devicePolicyManager, lockPatternUtils,
+ broadcastDispatcher, telephonyListenerManager,
+ globalSettings, secureSettings,
+ vibrator, resources,
+ configurationController,
+ keyguardStateController, userManager,
+ trustManager, iActivityManager,
+ telecomManager, metricsLogger,
+ depthController, colorExtractor,
+ statusBarService,
+ notificationShadeWindowController,
+ iWindowManager,
+ backgroundExecutor,
+ uiEventLogger,
+ ringerModeTracker, sysUiState, handler);
+
mLockPatternUtils = lockPatternUtils;
mKeyguardStateController = keyguardStateController;
- mBroadcastDispatcher = broadcastDispatcher;
- mContentResolver = contentResolver;
- mResources = resources;
- mConfigurationController = configurationController;
- mUserManager = userManager;
- mTrustManager = trustManager;
- mIActivityManager = iActivityManager;
- mTelecomManager = telecomManager;
- mMetricsLogger = metricsLogger;
- mUiEventLogger = uiEventLogger;
mDepthController = depthController;
mSysuiColorExtractor = colorExtractor;
mStatusBarService = statusBarService;
mNotificationShadeWindowController = notificationShadeWindowController;
- mControlsComponent = controlsComponent;
- mControlsUiControllerOptional = controlsComponent.getControlsUiController();
- mIWindowManager = iWindowManager;
- mBackgroundExecutor = backgroundExecutor;
- mRingerModeTracker = ringerModeTracker;
- mControlsControllerOptional = controlsComponent.getControlsController();
mSysUiState = sysUiState;
- mMainHandler = handler;
- mUserContextProvider = userContextProvider;
- mSmallestScreenWidthDp = mContext.getResources().getConfiguration().smallestScreenWidthDp;
-
- // receive broadcasts
- IntentFilter filter = new IntentFilter();
- filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
- filter.addAction(Intent.ACTION_SCREEN_OFF);
- filter.addAction(TelephonyManager.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED);
- mBroadcastDispatcher.registerReceiver(mBroadcastReceiver, filter);
-
- mHasTelephony =
- context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEPHONY);
-
- // get notified of phone state changes
- telephonyListenerManager.addServiceStateListener(mPhoneStateListener);
- contentResolver.registerContentObserver(
- Settings.Global.getUriFor(Settings.Global.AIRPLANE_MODE_ON), true,
- mAirplaneModeObserver);
- mHasVibrator = vibrator != null && vibrator.hasVibrator();
-
- mShowSilentToggle = SHOW_SILENT_TOGGLE && !resources.getBoolean(
- R.bool.config_useFixedVolume);
- if (mShowSilentToggle) {
- mRingerModeTracker.getRingerMode().observe(this, ringer ->
- mHandler.sendEmptyMessage(MESSAGE_REFRESH)
- );
- }
-
- mEmergencyAffordanceManager = new EmergencyAffordanceManager(context);
- mScreenshotHelper = new ScreenshotHelper(context);
-
- mConfigurationController.addCallback(this);
-
mActivityStarter = activityStarter;
keyguardStateController.addCallback(new KeyguardStateController.Callback() {
@Override
public void onUnlockedChanged() {
if (mDialog != null) {
+ ActionsDialog dialog = (ActionsDialog) mDialog;
boolean unlocked = mKeyguardStateController.isUnlocked();
- if (mDialog.mWalletViewController != null) {
- mDialog.mWalletViewController.onDeviceLockStateChanged(!unlocked);
- }
- if (!mDialog.isShowingControls()
- && mControlsComponent.getVisibility() == AVAILABLE) {
- mDialog.showControls(mControlsUiControllerOptional.get());
+ if (dialog.mWalletViewController != null) {
+ dialog.mWalletViewController.onDeviceLockStateChanged(!unlocked);
}
+
if (unlocked) {
- mDialog.hideLockMessage();
+ dialog.hideLockMessage();
}
}
}
});
- if (mControlsComponent.getControlsListingController().isPresent()) {
- mControlsComponent.getControlsListingController().get()
- .addCallback(list -> {
- mControlsServiceInfos = list;
- // This callback may occur after the dialog has been shown. If so, add
- // controls into the already visible space or show the lock msg if needed.
- if (mDialog != null) {
- if (!mDialog.isShowingControls()
- && mControlsComponent.getVisibility() == AVAILABLE) {
- mDialog.showControls(mControlsUiControllerOptional.get());
- } else if (shouldShowLockMessage(mDialog)) {
- mDialog.showLockMessage();
- }
- }
- });
- }
-
- // Listen for changes to show controls on the power menu while locked
+ // Listen for changes to show pay on the power menu while locked
onPowerMenuLockScreenSettingsChanged();
- mContext.getContentResolver().registerContentObserver(
+ mGlobalSettings.registerContentObserver(
Settings.Secure.getUriFor(Settings.Secure.POWER_MENU_LOCKED_SHOW_CONTENT),
false /* notifyForDescendants */,
- new ContentObserver(mMainHandler) {
+ new ContentObserver(handler) {
@Override
public void onChange(boolean selfChange) {
onPowerMenuLockScreenSettingsChanged();
@@ -433,132 +192,14 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener,
}
/**
- * See if any available control service providers match one of the preferred components. If
- * they do, and there are no current favorites for that component, query the preferred
- * component for a limited number of suggested controls.
- */
- private void seedFavorites() {
- if (!mControlsControllerOptional.isPresent()
- || mControlsServiceInfos.isEmpty()) {
- return;
- }
-
- String[] preferredControlsPackages = mContext.getResources()
- .getStringArray(com.android.systemui.R.array.config_controlsPreferredPackages);
-
- SharedPreferences prefs = mUserContextProvider.getUserContext()
- .getSharedPreferences(PREFS_CONTROLS_FILE, Context.MODE_PRIVATE);
- Set<String> seededPackages = prefs.getStringSet(PREFS_CONTROLS_SEEDING_COMPLETED,
- Collections.emptySet());
-
- List<ComponentName> componentsToSeed = new ArrayList<>();
- for (int i = 0; i < Math.min(SEEDING_MAX, preferredControlsPackages.length); i++) {
- String pkg = preferredControlsPackages[i];
- for (ControlsServiceInfo info : mControlsServiceInfos) {
- if (!pkg.equals(info.componentName.getPackageName())) continue;
- if (seededPackages.contains(pkg)) {
- break;
- } else if (mControlsControllerOptional.get()
- .countFavoritesForComponent(info.componentName) > 0) {
- // When there are existing controls but no saved preference, assume it
- // is out of sync, perhaps through a device restore, and update the
- // preference
- addPackageToSeededSet(prefs, pkg);
- break;
- }
- componentsToSeed.add(info.componentName);
- break;
- }
- }
-
- if (componentsToSeed.isEmpty()) return;
-
- mControlsControllerOptional.get().seedFavoritesForComponents(
- componentsToSeed,
- (response) -> {
- Log.d(TAG, "Controls seeded: " + response);
- if (response.getAccepted()) {
- addPackageToSeededSet(prefs, response.getPackageName());
- }
- });
- }
-
- private void addPackageToSeededSet(SharedPreferences prefs, String pkg) {
- Set<String> seededPackages = prefs.getStringSet(PREFS_CONTROLS_SEEDING_COMPLETED,
- Collections.emptySet());
- Set<String> updatedPkgs = new HashSet<>(seededPackages);
- updatedPkgs.add(pkg);
- prefs.edit().putStringSet(PREFS_CONTROLS_SEEDING_COMPLETED, updatedPkgs).apply();
- }
-
- /**
* Show the global actions dialog (creating if necessary)
*
* @param keyguardShowing True if keyguard is showing
*/
public void showOrHideDialog(boolean keyguardShowing, boolean isDeviceProvisioned,
GlobalActionsPanelPlugin walletPlugin) {
- mKeyguardShowing = keyguardShowing;
- mDeviceProvisioned = isDeviceProvisioned;
mWalletPlugin = walletPlugin;
- if (mDialog != null && mDialog.isShowing()) {
- // In order to force global actions to hide on the same affordance press, we must
- // register a call to onGlobalActionsShown() first to prevent the default actions
- // menu from showing. This will be followed by a subsequent call to
- // onGlobalActionsHidden() on dismiss()
- mWindowManagerFuncs.onGlobalActionsShown();
- mDialog.dismiss();
- mDialog = null;
- } else {
- handleShow();
- }
- }
-
- /**
- * Dismiss the global actions dialog, if it's currently shown
- */
- public void dismissDialog() {
- mHandler.removeMessages(MESSAGE_DISMISS);
- mHandler.sendEmptyMessage(MESSAGE_DISMISS);
- }
-
- private void awakenIfNecessary() {
- if (mDreamManager != null) {
- try {
- if (mDreamManager.isDreaming()) {
- mDreamManager.awaken();
- }
- } catch (RemoteException e) {
- // we tried
- }
- }
- }
-
- private void handleShow() {
- awakenIfNecessary();
- mDialog = createDialog();
- prepareDialog();
- seedFavorites();
-
- WindowManager.LayoutParams attrs = mDialog.getWindow().getAttributes();
- attrs.setTitle("ActionsDialog");
- attrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
- mDialog.getWindow().setAttributes(attrs);
- // Don't acquire soft keyboard focus, to avoid destroying state when capturing bugreports
- mDialog.getWindow().setFlags(FLAG_ALT_FOCUSABLE_IM, FLAG_ALT_FOCUSABLE_IM);
- mDialog.show();
- mWindowManagerFuncs.onGlobalActionsShown();
- }
-
- @VisibleForTesting
- protected boolean shouldShowAction(Action action) {
- if (mKeyguardShowing && !action.showDuringKeyguard()) {
- return false;
- }
- if (!mDeviceProvisioned && !action.showBeforeProvisioning()) {
- return false;
- }
- return action.shouldShow();
+ super.showOrHideDialog(keyguardShowing, isDeviceProvisioned);
}
/**
@@ -566,134 +207,10 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener,
* layout is being used.
*/
@VisibleForTesting
+ @Override
protected int getMaxShownPowerItems() {
- return mResources.getInteger(com.android.systemui.R.integer.power_menu_max_columns);
- }
-
- /**
- * Add a power menu action item for to either the main or overflow items lists, depending on
- * whether controls are enabled and whether the max number of shown items has been reached.
- */
- private void addActionItem(Action action) {
- if (mItems.size() < getMaxShownPowerItems()) {
- mItems.add(action);
- } else {
- mOverflowItems.add(action);
- }
- }
-
- @VisibleForTesting
- protected String[] getDefaultActions() {
- return mResources.getStringArray(R.array.config_globalActionsList);
- }
-
- private void addIfShouldShowAction(List<Action> actions, Action action) {
- if (shouldShowAction(action)) {
- actions.add(action);
- }
- }
-
- @VisibleForTesting
- protected void createActionItems() {
- // Simple toggle style if there's no vibrator, otherwise use a tri-state
- if (!mHasVibrator) {
- mSilentModeAction = new SilentModeToggleAction();
- } else {
- mSilentModeAction = new SilentModeTriStateAction(mAudioManager, mHandler);
- }
- mAirplaneModeOn = new AirplaneModeAction();
- onAirplaneModeChanged();
-
- mItems.clear();
- mOverflowItems.clear();
- mPowerItems.clear();
- String[] defaultActions = getDefaultActions();
-
- ShutDownAction shutdownAction = new ShutDownAction();
- RestartAction restartAction = new RestartAction();
- ArraySet<String> addedKeys = new ArraySet<String>();
- List<Action> tempActions = new ArrayList<>();
- CurrentUserProvider currentUser = new CurrentUserProvider();
-
- // make sure emergency affordance action is first, if needed
- if (mEmergencyAffordanceManager.needsEmergencyAffordance()) {
- addIfShouldShowAction(tempActions, new EmergencyAffordanceAction());
- addedKeys.add(GLOBAL_ACTION_KEY_EMERGENCY);
- }
-
- for (int i = 0; i < defaultActions.length; i++) {
- String actionKey = defaultActions[i];
- if (addedKeys.contains(actionKey)) {
- // If we already have added this, don't add it again.
- continue;
- }
- if (GLOBAL_ACTION_KEY_POWER.equals(actionKey)) {
- addIfShouldShowAction(tempActions, shutdownAction);
- } else if (GLOBAL_ACTION_KEY_AIRPLANE.equals(actionKey)) {
- addIfShouldShowAction(tempActions, mAirplaneModeOn);
- } else if (GLOBAL_ACTION_KEY_BUGREPORT.equals(actionKey)) {
- if (shouldDisplayBugReport(currentUser.get())) {
- addIfShouldShowAction(tempActions, new BugReportAction());
- }
- } else if (GLOBAL_ACTION_KEY_SILENT.equals(actionKey)) {
- if (mShowSilentToggle) {
- addIfShouldShowAction(tempActions, mSilentModeAction);
- }
- } else if (GLOBAL_ACTION_KEY_USERS.equals(actionKey)) {
- if (SystemProperties.getBoolean("fw.power_user_switcher", false)) {
- addUserActions(tempActions, currentUser.get());
- }
- } else if (GLOBAL_ACTION_KEY_SETTINGS.equals(actionKey)) {
- addIfShouldShowAction(tempActions, getSettingsAction());
- } else if (GLOBAL_ACTION_KEY_LOCKDOWN.equals(actionKey)) {
- if (shouldDisplayLockdown(currentUser.get())) {
- addIfShouldShowAction(tempActions, new LockDownAction());
- }
- } else if (GLOBAL_ACTION_KEY_VOICEASSIST.equals(actionKey)) {
- addIfShouldShowAction(tempActions, getVoiceAssistAction());
- } else if (GLOBAL_ACTION_KEY_ASSIST.equals(actionKey)) {
- addIfShouldShowAction(tempActions, getAssistAction());
- } else if (GLOBAL_ACTION_KEY_RESTART.equals(actionKey)) {
- addIfShouldShowAction(tempActions, restartAction);
- } else if (GLOBAL_ACTION_KEY_SCREENSHOT.equals(actionKey)) {
- addIfShouldShowAction(tempActions, new ScreenshotAction());
- } else if (GLOBAL_ACTION_KEY_LOGOUT.equals(actionKey)) {
- if (mDevicePolicyManager.isLogoutEnabled()
- && currentUser.get() != null
- && currentUser.get().id != UserHandle.USER_SYSTEM) {
- addIfShouldShowAction(tempActions, new LogoutAction());
- }
- } else if (GLOBAL_ACTION_KEY_EMERGENCY.equals(actionKey)) {
- addIfShouldShowAction(tempActions, new EmergencyDialerAction());
- } else {
- Log.e(TAG, "Invalid global action key " + actionKey);
- }
- // Add here so we don't add more than one.
- addedKeys.add(actionKey);
- }
-
- // replace power and restart with a single power options action, if needed
- if (tempActions.contains(shutdownAction) && tempActions.contains(restartAction)
- && tempActions.size() > getMaxShownPowerItems()) {
- // transfer shutdown and restart to their own list of power actions
- int powerOptionsIndex = Math.min(tempActions.indexOf(restartAction),
- tempActions.indexOf(shutdownAction));
- tempActions.remove(shutdownAction);
- tempActions.remove(restartAction);
- mPowerItems.add(shutdownAction);
- mPowerItems.add(restartAction);
-
- // add the PowerOptionsAction after Emergency, if present
- tempActions.add(powerOptionsIndex, new PowerOptionsAction());
- }
- for (Action action : tempActions) {
- addActionItem(action);
- }
- }
-
- private void onRotate() {
- // re-allocate actions between main and overflow lists
- this.createActionItems();
+ return getContext().getResources().getInteger(
+ com.android.systemui.R.integer.power_menu_max_columns);
}
/**
@@ -701,23 +218,15 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener,
*
* @return A new dialog.
*/
- private ActionsDialog createDialog() {
- createActionItems();
-
- mAdapter = new MyAdapter();
- mOverflowAdapter = new MyOverflowAdapter();
- mPowerAdapter = new MyPowerOptionsAdapter();
+ @Override
+ protected ActionsDialogLite createDialog() {
+ initDialogItems();
mDepthController.setShowingHomeControls(true);
- ControlsUiController uiController = null;
- if (mControlsComponent.getVisibility() == AVAILABLE) {
- uiController = mControlsUiControllerOptional.get();
- }
- ActionsDialog dialog = new ActionsDialog(mContext, mAdapter, mOverflowAdapter,
+ ActionsDialog dialog = new ActionsDialog(getContext(), mAdapter, mOverflowAdapter,
this::getWalletViewController, mDepthController, mSysuiColorExtractor,
mStatusBarService, mNotificationShadeWindowController,
- controlsAvailable(), uiController,
- mSysUiState, this::onRotate, mKeyguardShowing, mPowerAdapter);
+ mSysUiState, this::onRotate, isKeyguardShowing(), mPowerAdapter);
if (shouldShowLockMessage(dialog)) {
dialog.showLockMessage();
@@ -729,59 +238,6 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener,
return dialog;
}
- @VisibleForTesting
- boolean shouldDisplayLockdown(UserInfo user) {
- if (user == null) {
- return false;
- }
-
- int userId = user.id;
-
- // No lockdown option if it's not turned on in Settings
- if (Settings.Secure.getIntForUser(mContentResolver,
- Settings.Secure.LOCKDOWN_IN_POWER_MENU, 0, userId) == 0) {
- return false;
- }
-
- // Lockdown is meaningless without a place to go.
- if (!mKeyguardStateController.isMethodSecure()) {
- return false;
- }
-
- // Only show the lockdown button if the device isn't locked down (for whatever reason).
- int state = mLockPatternUtils.getStrongAuthForUser(userId);
- return (state == STRONG_AUTH_NOT_REQUIRED
- || state == SOME_AUTH_REQUIRED_AFTER_USER_REQUEST);
- }
-
- @VisibleForTesting
- boolean shouldDisplayBugReport(UserInfo currentUser) {
- return Settings.Global.getInt(
- mContentResolver, Settings.Global.BUGREPORT_IN_POWER_MENU, 0) != 0
- && (currentUser == null || currentUser.isPrimary());
- }
-
- @Override
- public void onUiModeChanged() {
- mContext.getTheme().applyStyle(mContext.getThemeResId(), true);
- if (mDialog != null && mDialog.isShowing()) {
- mDialog.refreshDialog();
- }
- }
-
- @Override
- public void onConfigChanged(Configuration newConfig) {
- if (mDialog != null && mDialog.isShowing()
- && (newConfig.smallestScreenWidthDp != mSmallestScreenWidthDp)) {
- mSmallestScreenWidthDp = newConfig.smallestScreenWidthDp;
- mDialog.refreshDialog();
- }
- }
-
- public void destroy() {
- mConfigurationController.removeCallback(this);
- }
-
@Nullable
private GlobalActionsPanelPlugin.PanelViewController getWalletViewController() {
if (mWalletPlugin == null) {
@@ -792,15 +248,6 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener,
/**
* Implements {@link GlobalActionsPanelPlugin.Callbacks#dismissGlobalActionsMenu()}, which is
- * called when the quick access wallet requests dismissal.
- */
- @Override
- public void dismissGlobalActionsMenu() {
- dismissDialog();
- }
-
- /**
- * Implements {@link GlobalActionsPanelPlugin.Callbacks#dismissGlobalActionsMenu()}, which is
* called when the quick access wallet requests that an intent be started (with lock screen
* shown first if needed).
*/
@@ -809,1364 +256,35 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener,
mActivityStarter.startPendingIntentDismissingKeyguard(pendingIntent);
}
- @VisibleForTesting
- protected final class PowerOptionsAction extends SinglePressAction {
- private PowerOptionsAction() {
- super(com.android.systemui.R.drawable.ic_settings_power,
- R.string.global_action_power_options);
- }
-
- @Override
- public boolean showDuringKeyguard() {
- return true;
- }
-
- @Override
- public boolean showBeforeProvisioning() {
- return true;
- }
-
- @Override
- public void onPress() {
- if (mDialog != null) {
- mDialog.showPowerOptionsMenu();
- }
- }
- }
-
- @VisibleForTesting
- final class ShutDownAction extends SinglePressAction implements LongPressAction {
- private ShutDownAction() {
- super(R.drawable.ic_lock_power_off,
- R.string.global_action_power_off);
- }
-
- @Override
- public boolean onLongPress() {
- if (!mUserManager.hasUserRestriction(UserManager.DISALLOW_SAFE_BOOT)) {
- mWindowManagerFuncs.reboot(true);
- return true;
- }
- return false;
- }
-
- @Override
- public boolean showDuringKeyguard() {
- return true;
- }
-
- @Override
- public boolean showBeforeProvisioning() {
- return true;
- }
-
- @Override
- public void onPress() {
- // shutdown by making sure radio and power are handled accordingly.
- mWindowManagerFuncs.shutdown();
- }
- }
-
- @VisibleForTesting
- protected abstract class EmergencyAction extends SinglePressAction {
- EmergencyAction(int iconResId, int messageResId) {
- super(iconResId, messageResId);
- }
-
- @Override
- public boolean shouldBeSeparated() {
- return false;
- }
-
- @Override
- public View create(
- Context context, View convertView, ViewGroup parent, LayoutInflater inflater) {
- View v = super.create(context, convertView, parent, inflater);
- int textColor;
- v.setBackgroundTintList(ColorStateList.valueOf(v.getResources().getColor(
- com.android.systemui.R.color.global_actions_emergency_background)));
- textColor = v.getResources().getColor(
- com.android.systemui.R.color.global_actions_emergency_text);
- TextView messageView = v.findViewById(R.id.message);
- messageView.setTextColor(textColor);
- messageView.setSelected(true); // necessary for marquee to work
- ImageView icon = v.findViewById(R.id.icon);
- icon.getDrawable().setTint(textColor);
- return v;
- }
-
- @Override
- public boolean showDuringKeyguard() {
- return true;
- }
-
- @Override
- public boolean showBeforeProvisioning() {
- return true;
- }
- }
-
- private class EmergencyAffordanceAction extends EmergencyAction {
- EmergencyAffordanceAction() {
- super(R.drawable.emergency_icon,
- R.string.global_action_emergency);
- }
-
- @Override
- public void onPress() {
- mEmergencyAffordanceManager.performEmergencyCall();
- }
- }
-
- @VisibleForTesting
- class EmergencyDialerAction extends EmergencyAction {
- private EmergencyDialerAction() {
- super(com.android.systemui.R.drawable.ic_emergency_star,
- R.string.global_action_emergency);
- }
-
- @Override
- public void onPress() {
- mMetricsLogger.action(MetricsEvent.ACTION_EMERGENCY_DIALER_FROM_POWER_MENU);
- mUiEventLogger.log(GlobalActionsEvent.GA_EMERGENCY_DIALER_PRESS);
- if (mTelecomManager != null) {
- Intent intent = mTelecomManager.createLaunchEmergencyDialerIntent(
- null /* number */);
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
- | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
- | Intent.FLAG_ACTIVITY_CLEAR_TOP);
- intent.putExtra(EmergencyDialerConstants.EXTRA_ENTRY_TYPE,
- EmergencyDialerConstants.ENTRY_TYPE_POWER_MENU);
- mContext.startActivityAsUser(intent, UserHandle.CURRENT);
- }
- }
- }
-
- @VisibleForTesting
- EmergencyDialerAction makeEmergencyDialerActionForTesting() {
- return new EmergencyDialerAction();
- }
-
- @VisibleForTesting
- final class RestartAction extends SinglePressAction implements LongPressAction {
- private RestartAction() {
- super(R.drawable.ic_restart, R.string.global_action_restart);
- }
-
- @Override
- public boolean onLongPress() {
- if (!mUserManager.hasUserRestriction(UserManager.DISALLOW_SAFE_BOOT)) {
- mWindowManagerFuncs.reboot(true);
- return true;
- }
- return false;
- }
-
- @Override
- public boolean showDuringKeyguard() {
- return true;
- }
-
- @Override
- public boolean showBeforeProvisioning() {
- return true;
- }
-
- @Override
- public void onPress() {
- mWindowManagerFuncs.reboot(false);
- }
- }
-
- @VisibleForTesting
- class ScreenshotAction extends SinglePressAction {
- final String KEY_SYSTEM_NAV_2BUTTONS = "system_nav_2buttons";
-
- public ScreenshotAction() {
- super(R.drawable.ic_screenshot, R.string.global_action_screenshot);
- }
-
- @Override
- public void onPress() {
- // Add a little delay before executing, to give the
- // dialog a chance to go away before it takes a
- // screenshot.
- // TODO: instead, omit global action dialog layer
- mHandler.postDelayed(new Runnable() {
- @Override
- public void run() {
- mScreenshotHelper.takeScreenshot(TAKE_SCREENSHOT_FULLSCREEN, true, true,
- SCREENSHOT_GLOBAL_ACTIONS, mHandler, null);
- mMetricsLogger.action(MetricsEvent.ACTION_SCREENSHOT_POWER_MENU);
- mUiEventLogger.log(GlobalActionsEvent.GA_SCREENSHOT_PRESS);
- }
- }, mDialogPressDelay);
- }
-
- @Override
- public boolean showDuringKeyguard() {
- return true;
- }
-
- @Override
- public boolean showBeforeProvisioning() {
- return false;
- }
-
- @Override
- public boolean shouldShow() {
- // Include screenshot in power menu for legacy nav because it is not accessible
- // through Recents in that mode
- return is2ButtonNavigationEnabled();
- }
-
- boolean is2ButtonNavigationEnabled() {
- return NAV_BAR_MODE_2BUTTON == mContext.getResources().getInteger(
- com.android.internal.R.integer.config_navBarInteractionMode);
- }
- }
-
- @VisibleForTesting
- ScreenshotAction makeScreenshotActionForTesting() {
- return new ScreenshotAction();
- }
-
- @VisibleForTesting
- class BugReportAction extends SinglePressAction implements LongPressAction {
-
- public BugReportAction() {
- super(R.drawable.ic_lock_bugreport, R.string.bugreport_title);
- }
-
- @Override
- public void onPress() {
- // don't actually trigger the bugreport if we are running stability
- // tests via monkey
- if (ActivityManager.isUserAMonkey()) {
- return;
- }
- // Add a little delay before executing, to give the
- // dialog a chance to go away before it takes a
- // screenshot.
- mHandler.postDelayed(new Runnable() {
- @Override
- public void run() {
- try {
- // Take an "interactive" bugreport.
- mMetricsLogger.action(
- MetricsEvent.ACTION_BUGREPORT_FROM_POWER_MENU_INTERACTIVE);
- mUiEventLogger.log(GlobalActionsEvent.GA_BUGREPORT_PRESS);
- if (!mIActivityManager.launchBugReportHandlerApp()) {
- Log.w(TAG, "Bugreport handler could not be launched");
- mIActivityManager.requestInteractiveBugReport();
- }
- } catch (RemoteException e) {
- }
- }
- }, mDialogPressDelay);
- }
-
- @Override
- public boolean onLongPress() {
- // don't actually trigger the bugreport if we are running stability
- // tests via monkey
- if (ActivityManager.isUserAMonkey()) {
- return false;
- }
- try {
- // Take a "full" bugreport.
- mMetricsLogger.action(MetricsEvent.ACTION_BUGREPORT_FROM_POWER_MENU_FULL);
- mUiEventLogger.log(GlobalActionsEvent.GA_BUGREPORT_LONG_PRESS);
- mIActivityManager.requestFullBugReport();
- } catch (RemoteException e) {
- }
- return false;
- }
-
- public boolean showDuringKeyguard() {
- return true;
- }
-
- @Override
- public boolean showBeforeProvisioning() {
- return false;
- }
- }
-
- @VisibleForTesting
- BugReportAction makeBugReportActionForTesting() {
- return new BugReportAction();
- }
-
- private final class LogoutAction extends SinglePressAction {
- private LogoutAction() {
- super(R.drawable.ic_logout, R.string.global_action_logout);
- }
-
- @Override
- public boolean showDuringKeyguard() {
- return true;
- }
-
- @Override
- public boolean showBeforeProvisioning() {
- return false;
- }
-
- @Override
- public void onPress() {
- // Add a little delay before executing, to give the dialog a chance to go away before
- // switching user
- mHandler.postDelayed(() -> {
- try {
- int currentUserId = getCurrentUser().id;
- mIActivityManager.switchUser(UserHandle.USER_SYSTEM);
- mIActivityManager.stopUser(currentUserId, true /*force*/, null);
- } catch (RemoteException re) {
- Log.e(TAG, "Couldn't logout user " + re);
- }
- }, mDialogPressDelay);
- }
- }
-
- private Action getSettingsAction() {
- return new SinglePressAction(R.drawable.ic_settings,
- R.string.global_action_settings) {
-
- @Override
- public void onPress() {
- Intent intent = new Intent(Settings.ACTION_SETTINGS);
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
- mContext.startActivity(intent);
- }
-
- @Override
- public boolean showDuringKeyguard() {
- return true;
- }
-
- @Override
- public boolean showBeforeProvisioning() {
- return true;
- }
- };
- }
-
- private Action getAssistAction() {
- return new SinglePressAction(R.drawable.ic_action_assist_focused,
- R.string.global_action_assist) {
- @Override
- public void onPress() {
- Intent intent = new Intent(Intent.ACTION_ASSIST);
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
- mContext.startActivity(intent);
- }
-
- @Override
- public boolean showDuringKeyguard() {
- return true;
- }
-
- @Override
- public boolean showBeforeProvisioning() {
- return true;
- }
- };
- }
-
- private Action getVoiceAssistAction() {
- return new SinglePressAction(R.drawable.ic_voice_search,
- R.string.global_action_voice_assist) {
- @Override
- public void onPress() {
- Intent intent = new Intent(Intent.ACTION_VOICE_ASSIST);
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
- mContext.startActivity(intent);
- }
-
- @Override
- public boolean showDuringKeyguard() {
- return true;
- }
-
- @Override
- public boolean showBeforeProvisioning() {
- return true;
- }
- };
- }
-
- @VisibleForTesting
- class LockDownAction extends SinglePressAction {
- LockDownAction() {
- super(R.drawable.ic_lock_lockdown, R.string.global_action_lockdown);
- }
-
- @Override
- public void onPress() {
- mLockPatternUtils.requireStrongAuth(STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN,
- UserHandle.USER_ALL);
- try {
- mIWindowManager.lockNow(null);
- // Lock profiles (if any) on the background thread.
- mBackgroundExecutor.execute(() -> lockProfiles());
- } catch (RemoteException e) {
- Log.e(TAG, "Error while trying to lock device.", e);
- }
- }
-
- @Override
- public boolean showDuringKeyguard() {
- return true;
- }
-
- @Override
- public boolean showBeforeProvisioning() {
- return false;
- }
- }
-
- private void lockProfiles() {
- final int currentUserId = getCurrentUser().id;
- final int[] profileIds = mUserManager.getEnabledProfileIds(currentUserId);
- for (final int id : profileIds) {
- if (id != currentUserId) {
- mTrustManager.setDeviceLockedForUser(id, true);
- }
- }
- }
-
- private UserInfo getCurrentUser() {
- try {
- return mIActivityManager.getCurrentUser();
- } catch (RemoteException re) {
- return null;
- }
- }
-
- /**
- * Non-thread-safe current user provider that caches the result - helpful when a method needs
- * to fetch it an indeterminate number of times.
- */
- private class CurrentUserProvider {
- private UserInfo mUserInfo = null;
- private boolean mFetched = false;
-
- @Nullable
- UserInfo get() {
- if (!mFetched) {
- mFetched = true;
- mUserInfo = getCurrentUser();
- }
- return mUserInfo;
- }
- }
-
- private void addUserActions(List<Action> actions, UserInfo currentUser) {
- if (mUserManager.isUserSwitcherEnabled()) {
- List<UserInfo> users = mUserManager.getUsers();
- for (final UserInfo user : users) {
- if (user.supportsSwitchToByUser()) {
- boolean isCurrentUser = currentUser == null
- ? user.id == 0 : (currentUser.id == user.id);
- Drawable icon = user.iconPath != null ? Drawable.createFromPath(user.iconPath)
- : null;
- SinglePressAction switchToUser = new SinglePressAction(
- R.drawable.ic_menu_cc, icon,
- (user.name != null ? user.name : "Primary")
- + (isCurrentUser ? " \u2714" : "")) {
- public void onPress() {
- try {
- mIActivityManager.switchUser(user.id);
- } catch (RemoteException re) {
- Log.e(TAG, "Couldn't switch user " + re);
- }
- }
-
- public boolean showDuringKeyguard() {
- return true;
- }
-
- public boolean showBeforeProvisioning() {
- return false;
- }
- };
- addIfShouldShowAction(actions, switchToUser);
- }
- }
- }
- }
-
- private void prepareDialog() {
- refreshSilentMode();
- mAirplaneModeOn.updateState(mAirplaneState);
- mAdapter.notifyDataSetChanged();
- mLifecycle.setCurrentState(Lifecycle.State.RESUMED);
- }
-
- private void refreshSilentMode() {
- if (!mHasVibrator) {
- Integer value = mRingerModeTracker.getRingerMode().getValue();
- final boolean silentModeOn = value != null && value != AudioManager.RINGER_MODE_NORMAL;
- ((ToggleAction) mSilentModeAction).updateState(
- silentModeOn ? ToggleState.On : ToggleState.Off);
- }
- }
-
- /**
- * {@inheritDoc}
- */
- public void onDismiss(DialogInterface dialog) {
- if (mDialog == dialog) {
- mDialog = null;
- }
- mUiEventLogger.log(GlobalActionsEvent.GA_POWER_MENU_CLOSE);
- mWindowManagerFuncs.onGlobalActionsHidden();
- mLifecycle.setCurrentState(Lifecycle.State.CREATED);
- }
-
- /**
- * {@inheritDoc}
- */
- public void onShow(DialogInterface dialog) {
- mMetricsLogger.visible(MetricsEvent.POWER_MENU);
- mUiEventLogger.log(GlobalActionsEvent.GA_POWER_MENU_OPEN);
- }
-
- /**
- * The adapter used for power menu items shown in the global actions dialog.
- */
- public class MyAdapter extends MultiListAdapter {
- private int countItems(boolean separated) {
- int count = 0;
- for (int i = 0; i < mItems.size(); i++) {
- final Action action = mItems.get(i);
-
- if (action.shouldBeSeparated() == separated) {
- count++;
- }
- }
- return count;
- }
-
- @Override
- public int countSeparatedItems() {
- return countItems(true);
- }
-
- @Override
- public int countListItems() {
- return countItems(false);
- }
-
- @Override
- public int getCount() {
- return countSeparatedItems() + countListItems();
- }
-
- @Override
- public boolean isEnabled(int position) {
- return getItem(position).isEnabled();
- }
-
- @Override
- public boolean areAllItemsEnabled() {
- return false;
- }
-
- @Override
- public Action getItem(int position) {
- int filteredPos = 0;
- for (int i = 0; i < mItems.size(); i++) {
- final Action action = mItems.get(i);
- if (!shouldShowAction(action)) {
- continue;
- }
- if (filteredPos == position) {
- return action;
- }
- filteredPos++;
- }
-
- throw new IllegalArgumentException("position " + position
- + " out of range of showable actions"
- + ", filtered count=" + getCount()
- + ", keyguardshowing=" + mKeyguardShowing
- + ", provisioned=" + mDeviceProvisioned);
- }
-
-
- public long getItemId(int position) {
- return position;
- }
-
- @Override
- public View getView(int position, View convertView, ViewGroup parent) {
- Action action = getItem(position);
- View view = action.create(mContext, convertView, parent, LayoutInflater.from(mContext));
- view.setOnClickListener(v -> onClickItem(position));
- if (action instanceof LongPressAction) {
- view.setOnLongClickListener(v -> onLongClickItem(position));
- }
- return view;
- }
-
- @Override
- public boolean onLongClickItem(int position) {
- final Action action = mAdapter.getItem(position);
- if (action instanceof LongPressAction) {
- if (mDialog != null) {
- mDialog.dismiss();
- } else {
- Log.w(TAG, "Action long-clicked while mDialog is null.");
- }
- return ((LongPressAction) action).onLongPress();
- }
- return false;
- }
-
- @Override
- public void onClickItem(int position) {
- Action item = mAdapter.getItem(position);
- if (!(item instanceof SilentModeTriStateAction)) {
- if (mDialog != null) {
- // don't dismiss the dialog if we're opening the power options menu
- if (!(item instanceof PowerOptionsAction)) {
- mDialog.dismiss();
- }
- } else {
- Log.w(TAG, "Action clicked while mDialog is null.");
- }
- item.onPress();
- }
- }
-
- @Override
- public boolean shouldBeSeparated(int position) {
- return getItem(position).shouldBeSeparated();
- }
- }
-
- /**
- * The adapter used for items in the overflow menu.
- */
- public class MyPowerOptionsAdapter extends BaseAdapter {
- @Override
- public int getCount() {
- return mPowerItems.size();
- }
-
- @Override
- public Action getItem(int position) {
- return mPowerItems.get(position);
- }
-
- @Override
- public long getItemId(int position) {
- return position;
- }
-
- @Override
- public View getView(int position, View convertView, ViewGroup parent) {
- Action action = getItem(position);
- if (action == null) {
- Log.w(TAG, "No power options action found at position: " + position);
- return null;
- }
- int viewLayoutResource = com.android.systemui.R.layout.global_actions_power_item;
- View view = convertView != null ? convertView
- : LayoutInflater.from(mContext).inflate(viewLayoutResource, parent, false);
- view.setOnClickListener(v -> onClickItem(position));
- if (action instanceof LongPressAction) {
- view.setOnLongClickListener(v -> onLongClickItem(position));
- }
- ImageView icon = view.findViewById(R.id.icon);
- TextView messageView = view.findViewById(R.id.message);
- messageView.setSelected(true); // necessary for marquee to work
-
- icon.setImageDrawable(action.getIcon(mContext));
- icon.setScaleType(ScaleType.CENTER_CROP);
-
- if (action.getMessage() != null) {
- messageView.setText(action.getMessage());
- } else {
- messageView.setText(action.getMessageResId());
- }
- return view;
- }
-
- private boolean onLongClickItem(int position) {
- final Action action = getItem(position);
- if (action instanceof LongPressAction) {
- if (mDialog != null) {
- mDialog.dismiss();
- } else {
- Log.w(TAG, "Action long-clicked while mDialog is null.");
- }
- return ((LongPressAction) action).onLongPress();
- }
- return false;
- }
-
- private void onClickItem(int position) {
- Action item = getItem(position);
- if (!(item instanceof SilentModeTriStateAction)) {
- if (mDialog != null) {
- mDialog.dismiss();
- } else {
- Log.w(TAG, "Action clicked while mDialog is null.");
- }
- item.onPress();
- }
- }
- }
-
- /**
- * The adapter used for items in the power options menu, triggered by the PowerOptionsAction.
- */
- public class MyOverflowAdapter extends BaseAdapter {
- @Override
- public int getCount() {
- return mOverflowItems.size();
- }
-
- @Override
- public Action getItem(int position) {
- return mOverflowItems.get(position);
- }
-
- @Override
- public long getItemId(int position) {
- return position;
- }
-
- @Override
- public View getView(int position, View convertView, ViewGroup parent) {
- Action action = getItem(position);
- if (action == null) {
- Log.w(TAG, "No overflow action found at position: " + position);
- return null;
- }
- int viewLayoutResource = com.android.systemui.R.layout.controls_more_item;
- View view = convertView != null ? convertView
- : LayoutInflater.from(mContext).inflate(viewLayoutResource, parent, false);
- TextView textView = (TextView) view;
- if (action.getMessageResId() != 0) {
- textView.setText(action.getMessageResId());
- } else {
- textView.setText(action.getMessage());
- }
- return textView;
- }
-
- private boolean onLongClickItem(int position) {
- final Action action = getItem(position);
- if (action instanceof LongPressAction) {
- if (mDialog != null) {
- mDialog.dismiss();
- } else {
- Log.w(TAG, "Action long-clicked while mDialog is null.");
- }
- return ((LongPressAction) action).onLongPress();
- }
- return false;
- }
-
- private void onClickItem(int position) {
- Action item = getItem(position);
- if (!(item instanceof SilentModeTriStateAction)) {
- if (mDialog != null) {
- mDialog.dismiss();
- } else {
- Log.w(TAG, "Action clicked while mDialog is null.");
- }
- item.onPress();
- }
- }
- }
-
- // note: the scheme below made more sense when we were planning on having
- // 8 different things in the global actions dialog. seems overkill with
- // only 3 items now, but may as well keep this flexible approach so it will
- // be easy should someone decide at the last minute to include something
- // else, such as 'enable wifi', or 'enable bluetooth'
-
- /**
- * What each item in the global actions dialog must be able to support.
- */
- public interface Action {
- /**
- * @return Text that will be announced when dialog is created. null for none.
- */
- CharSequence getLabelForAccessibility(Context context);
-
- View create(Context context, View convertView, ViewGroup parent, LayoutInflater inflater);
-
- void onPress();
-
- /**
- * @return whether this action should appear in the dialog when the keygaurd is showing.
- */
- boolean showDuringKeyguard();
-
- /**
- * @return whether this action should appear in the dialog before the
- * device is provisioned.f
- */
- boolean showBeforeProvisioning();
-
- boolean isEnabled();
-
- default boolean shouldBeSeparated() {
- return false;
- }
-
- /**
- * Return the id of the message associated with this action, or 0 if it doesn't have one.
- * @return
- */
- int getMessageResId();
-
- /**
- * Return the icon drawable for this action.
- */
- Drawable getIcon(Context context);
-
- /**
- * Return the message associated with this action, or null if it doesn't have one.
- * @return
- */
- CharSequence getMessage();
-
- default boolean shouldShow() {
- return true;
- }
- }
-
- /**
- * An action that also supports long press.
- */
- private interface LongPressAction extends Action {
- boolean onLongPress();
- }
-
- /**
- * A single press action maintains no state, just responds to a press and takes an action.
- */
-
- private abstract class SinglePressAction implements Action {
- private final int mIconResId;
- private final Drawable mIcon;
- private final int mMessageResId;
- private final CharSequence mMessage;
-
- protected SinglePressAction(int iconResId, int messageResId) {
- mIconResId = iconResId;
- mMessageResId = messageResId;
- mMessage = null;
- mIcon = null;
- }
-
- protected SinglePressAction(int iconResId, Drawable icon, CharSequence message) {
- mIconResId = iconResId;
- mMessageResId = 0;
- mMessage = message;
- mIcon = icon;
- }
-
- public boolean isEnabled() {
- return true;
- }
-
- public String getStatus() {
- return null;
- }
-
- abstract public void onPress();
-
- public CharSequence getLabelForAccessibility(Context context) {
- if (mMessage != null) {
- return mMessage;
- } else {
- return context.getString(mMessageResId);
- }
- }
-
- public int getMessageResId() {
- return mMessageResId;
- }
-
- public CharSequence getMessage() {
- return mMessage;
- }
-
- @Override
- public Drawable getIcon(Context context) {
- if (mIcon != null) {
- return mIcon;
- } else {
- return context.getDrawable(mIconResId);
- }
- }
-
- public View create(
- Context context, View convertView, ViewGroup parent, LayoutInflater inflater) {
- View v = inflater.inflate(com.android.systemui.R.layout.global_actions_grid_item_v2,
- parent, false /* attach */);
-
- ImageView icon = v.findViewById(R.id.icon);
- TextView messageView = v.findViewById(R.id.message);
- messageView.setSelected(true); // necessary for marquee to work
-
- icon.setImageDrawable(getIcon(context));
- icon.setScaleType(ScaleType.CENTER_CROP);
-
- if (mMessage != null) {
- messageView.setText(mMessage);
- } else {
- messageView.setText(mMessageResId);
- }
-
- return v;
- }
- }
-
- private enum ToggleState {
- Off(false),
- TurningOn(true),
- TurningOff(true),
- On(false);
-
- private final boolean mInTransition;
-
- ToggleState(boolean intermediate) {
- mInTransition = intermediate;
- }
-
- public boolean inTransition() {
- return mInTransition;
- }
- }
-
- /**
- * A toggle action knows whether it is on or off, and displays an icon and status message
- * accordingly.
- */
- private abstract class ToggleAction implements Action {
-
- protected ToggleState mState = ToggleState.Off;
-
- // prefs
- protected int mEnabledIconResId;
- protected int mDisabledIconResid;
- protected int mMessageResId;
- protected int mEnabledStatusMessageResId;
- protected int mDisabledStatusMessageResId;
-
- /**
- * @param enabledIconResId The icon for when this action is on.
- * @param disabledIconResid The icon for when this action is off.
- * @param message The general information message, e.g 'Silent Mode'
- * @param enabledStatusMessageResId The on status message, e.g 'sound disabled'
- * @param disabledStatusMessageResId The off status message, e.g. 'sound enabled'
- */
- public ToggleAction(int enabledIconResId,
- int disabledIconResid,
- int message,
- int enabledStatusMessageResId,
- int disabledStatusMessageResId) {
- mEnabledIconResId = enabledIconResId;
- mDisabledIconResid = disabledIconResid;
- mMessageResId = message;
- mEnabledStatusMessageResId = enabledStatusMessageResId;
- mDisabledStatusMessageResId = disabledStatusMessageResId;
- }
-
- /**
- * Override to make changes to resource IDs just before creating the View.
- */
- void willCreate() {
-
- }
-
- @Override
- public CharSequence getLabelForAccessibility(Context context) {
- return context.getString(mMessageResId);
- }
-
- private boolean isOn() {
- return mState == ToggleState.On || mState == ToggleState.TurningOn;
- }
-
- @Override
- public CharSequence getMessage() {
- return null;
- }
- @Override
- public int getMessageResId() {
- return isOn() ? mEnabledStatusMessageResId : mDisabledStatusMessageResId;
- }
-
- private int getIconResId() {
- return isOn() ? mEnabledIconResId : mDisabledIconResid;
- }
-
- @Override
- public Drawable getIcon(Context context) {
- return context.getDrawable(getIconResId());
- }
-
- public View create(Context context, View convertView, ViewGroup parent,
- LayoutInflater inflater) {
- willCreate();
-
- View v = inflater.inflate(com.android.systemui.R.layout.global_actions_grid_item_v2,
- parent, false /* attach */);
-
- ImageView icon = (ImageView) v.findViewById(R.id.icon);
- TextView messageView = (TextView) v.findViewById(R.id.message);
- final boolean enabled = isEnabled();
-
- if (messageView != null) {
- messageView.setText(getMessageResId());
- messageView.setEnabled(enabled);
- messageView.setSelected(true); // necessary for marquee to work
- }
-
- if (icon != null) {
- icon.setImageDrawable(context.getDrawable(getIconResId()));
- icon.setEnabled(enabled);
- }
-
- v.setEnabled(enabled);
-
- return v;
- }
-
- public final void onPress() {
- if (mState.inTransition()) {
- Log.w(TAG, "shouldn't be able to toggle when in transition");
- return;
- }
-
- final boolean nowOn = !(mState == ToggleState.On);
- onToggle(nowOn);
- changeStateFromPress(nowOn);
- }
-
- public boolean isEnabled() {
- return !mState.inTransition();
- }
-
- /**
- * Implementations may override this if their state can be in on of the intermediate states
- * until some notification is received (e.g airplane mode is 'turning off' until we know the
- * wireless connections are back online
- *
- * @param buttonOn Whether the button was turned on or off
- */
- protected void changeStateFromPress(boolean buttonOn) {
- mState = buttonOn ? ToggleState.On : ToggleState.Off;
- }
-
- abstract void onToggle(boolean on);
-
- public void updateState(ToggleState state) {
- mState = state;
- }
- }
-
- private class AirplaneModeAction extends ToggleAction {
- AirplaneModeAction() {
- super(
- R.drawable.ic_lock_airplane_mode,
- R.drawable.ic_lock_airplane_mode_off,
- R.string.global_actions_toggle_airplane_mode,
- R.string.global_actions_airplane_mode_on_status,
- R.string.global_actions_airplane_mode_off_status);
- }
-
- void onToggle(boolean on) {
- if (mHasTelephony && TelephonyProperties.in_ecm_mode().orElse(false)) {
- mIsWaitingForEcmExit = true;
- // Launch ECM exit dialog
- Intent ecmDialogIntent =
- new Intent(TelephonyManager.ACTION_SHOW_NOTICE_ECM_BLOCK_OTHERS, null);
- ecmDialogIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- mContext.startActivity(ecmDialogIntent);
- } else {
- changeAirplaneModeSystemSetting(on);
- }
- }
-
- @Override
- protected void changeStateFromPress(boolean buttonOn) {
- if (!mHasTelephony) return;
-
- // In ECM mode airplane state cannot be changed
- if (!TelephonyProperties.in_ecm_mode().orElse(false)) {
- mState = buttonOn ? ToggleState.TurningOn : ToggleState.TurningOff;
- mAirplaneState = mState;
- }
- }
-
- public boolean showDuringKeyguard() {
- return true;
- }
-
- public boolean showBeforeProvisioning() {
- return false;
- }
- }
-
- private class SilentModeToggleAction extends ToggleAction {
- public SilentModeToggleAction() {
- super(R.drawable.ic_audio_vol_mute,
- R.drawable.ic_audio_vol,
- R.string.global_action_toggle_silent_mode,
- R.string.global_action_silent_mode_on_status,
- R.string.global_action_silent_mode_off_status);
- }
-
- void onToggle(boolean on) {
- if (on) {
- mAudioManager.setRingerMode(AudioManager.RINGER_MODE_SILENT);
- } else {
- mAudioManager.setRingerMode(AudioManager.RINGER_MODE_NORMAL);
- }
- }
-
- public boolean showDuringKeyguard() {
- return true;
- }
-
- public boolean showBeforeProvisioning() {
- return false;
- }
- }
-
- private static class SilentModeTriStateAction implements Action, View.OnClickListener {
-
- private final int[] ITEM_IDS = {R.id.option1, R.id.option2, R.id.option3};
-
- private final AudioManager mAudioManager;
- private final Handler mHandler;
-
- SilentModeTriStateAction(AudioManager audioManager, Handler handler) {
- mAudioManager = audioManager;
- mHandler = handler;
- }
-
- private int ringerModeToIndex(int ringerMode) {
- // They just happen to coincide
- return ringerMode;
- }
-
- private int indexToRingerMode(int index) {
- // They just happen to coincide
- return index;
- }
-
- @Override
- public CharSequence getLabelForAccessibility(Context context) {
- return null;
- }
-
- @Override
- public int getMessageResId() {
- return 0;
- }
-
- @Override
- public CharSequence getMessage() {
- return null;
- }
-
- @Override
- public Drawable getIcon(Context context) {
- return null;
- }
-
-
- public View create(Context context, View convertView, ViewGroup parent,
- LayoutInflater inflater) {
- View v = inflater.inflate(R.layout.global_actions_silent_mode, parent, false);
-
- int selectedIndex = ringerModeToIndex(mAudioManager.getRingerMode());
- for (int i = 0; i < 3; i++) {
- View itemView = v.findViewById(ITEM_IDS[i]);
- itemView.setSelected(selectedIndex == i);
- // Set up click handler
- itemView.setTag(i);
- itemView.setOnClickListener(this);
- }
- return v;
- }
-
- public void onPress() {
- }
-
- public boolean showDuringKeyguard() {
- return true;
- }
-
- public boolean showBeforeProvisioning() {
- return false;
- }
-
- public boolean isEnabled() {
- return true;
- }
-
- void willCreate() {
- }
-
- public void onClick(View v) {
- if (!(v.getTag() instanceof Integer)) return;
-
- int index = (Integer) v.getTag();
- mAudioManager.setRingerMode(indexToRingerMode(index));
- mHandler.sendEmptyMessageDelayed(MESSAGE_DISMISS, DIALOG_DISMISS_DELAY);
- }
- }
-
- private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
- public void onReceive(Context context, Intent intent) {
- String action = intent.getAction();
- if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action)
- || Intent.ACTION_SCREEN_OFF.equals(action)) {
- String reason = intent.getStringExtra(SYSTEM_DIALOG_REASON_KEY);
- if (!SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS.equals(reason)) {
- mHandler.sendMessage(mHandler.obtainMessage(MESSAGE_DISMISS, reason));
- }
- } else if (TelephonyManager.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED.equals(action)) {
- // Airplane mode can be changed after ECM exits if airplane toggle button
- // is pressed during ECM mode
- if (!(intent.getBooleanExtra(TelephonyManager.EXTRA_PHONE_IN_ECM_STATE, false))
- && mIsWaitingForEcmExit) {
- mIsWaitingForEcmExit = false;
- changeAirplaneModeSystemSetting(true);
- }
- }
- }
- };
-
- private final TelephonyCallback.ServiceStateListener mPhoneStateListener =
- new TelephonyCallback.ServiceStateListener() {
- @Override
- public void onServiceStateChanged(ServiceState serviceState) {
- if (!mHasTelephony) return;
- final boolean inAirplaneMode = serviceState.getState() == ServiceState.STATE_POWER_OFF;
- mAirplaneState = inAirplaneMode ? ToggleState.On : ToggleState.Off;
- mAirplaneModeOn.updateState(mAirplaneState);
- mAdapter.notifyDataSetChanged();
- mOverflowAdapter.notifyDataSetChanged();
- mPowerAdapter.notifyDataSetChanged();
- }
- };
-
- private ContentObserver mAirplaneModeObserver = new ContentObserver(mMainHandler) {
- @Override
- public void onChange(boolean selfChange) {
- onAirplaneModeChanged();
- }
- };
-
- private static final int MESSAGE_DISMISS = 0;
- private static final int MESSAGE_REFRESH = 1;
- private static final int DIALOG_DISMISS_DELAY = 300; // ms
- private static final int DIALOG_PRESS_DELAY = 850; // ms
-
- @VisibleForTesting void setZeroDialogPressDelayForTesting() {
- mDialogPressDelay = 0; // ms
+ @Override
+ protected int getEmergencyTextColor(Context context) {
+ return context.getResources().getColor(
+ com.android.systemui.R.color.global_actions_emergency_text);
}
- private Handler mHandler = new Handler() {
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case MESSAGE_DISMISS:
- if (mDialog != null) {
- if (SYSTEM_DIALOG_REASON_DREAM.equals(msg.obj)) {
- mDialog.completeDismiss();
- } else {
- mDialog.dismiss();
- }
- mDialog = null;
- }
- break;
- case MESSAGE_REFRESH:
- refreshSilentMode();
- mAdapter.notifyDataSetChanged();
- break;
- }
- }
- };
-
- private void onAirplaneModeChanged() {
- // Let the service state callbacks handle the state.
- if (mHasTelephony) return;
-
- boolean airplaneModeOn = Settings.Global.getInt(
- mContentResolver,
- Settings.Global.AIRPLANE_MODE_ON,
- 0) == 1;
- mAirplaneState = airplaneModeOn ? ToggleState.On : ToggleState.Off;
- mAirplaneModeOn.updateState(mAirplaneState);
+ @Override
+ protected int getEmergencyIconColor(Context context) {
+ return getContext().getResources().getColor(
+ com.android.systemui.R.color.global_actions_emergency_text);
}
- /**
- * Change the airplane mode system setting
- */
- private void changeAirplaneModeSystemSetting(boolean on) {
- Settings.Global.putInt(
- mContentResolver,
- Settings.Global.AIRPLANE_MODE_ON,
- on ? 1 : 0);
- Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
- intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
- intent.putExtra("state", on);
- mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
- if (!mHasTelephony) {
- mAirplaneState = on ? ToggleState.On : ToggleState.Off;
- }
+ @Override
+ protected int getEmergencyBackgroundColor(Context context) {
+ return getContext().getResources().getColor(
+ com.android.systemui.R.color.global_actions_emergency_background);
}
- @NonNull
@Override
- public Lifecycle getLifecycle() {
- return mLifecycle;
+ protected int getGridItemLayoutResource() {
+ return com.android.systemui.R.layout.global_actions_grid_item_v2;
}
@VisibleForTesting
- static final class ActionsDialog extends Dialog implements DialogInterface,
- ColorExtractor.OnColorsChangedListener {
+ static class ActionsDialog extends ActionsDialogLite {
- private final Context mContext;
- private final MyAdapter mAdapter;
- private final MyOverflowAdapter mOverflowAdapter;
- private final MyPowerOptionsAdapter mPowerOptionsAdapter;
- private final IStatusBarService mStatusBarService;
- private final IBinder mToken = new Binder();
- private MultiListLayout mGlobalActionsLayout;
- private Drawable mBackgroundDrawable;
- private final SysuiColorExtractor mColorExtractor;
private final Provider<GlobalActionsPanelPlugin.PanelViewController> mWalletFactory;
@Nullable private GlobalActionsPanelPlugin.PanelViewController mWalletViewController;
- private boolean mKeyguardShowing;
- private boolean mShowing;
- private float mScrimAlpha;
private ResetOrientationData mResetOrientationData;
- private final NotificationShadeWindowController mNotificationShadeWindowController;
- private final NotificationShadeDepthController mDepthController;
- private final SysUiState mSysUiState;
- private ListPopupWindow mOverflowPopup;
- private Dialog mPowerOptionsDialog;
- private final Runnable mOnRotateCallback;
- private final boolean mControlsAvailable;
-
- private ControlsUiController mControlsUiController;
- private ViewGroup mControlsView;
- private ViewGroup mContainer;
@VisibleForTesting ViewGroup mLockMessageContainer;
private TextView mLockMessage;
@@ -2175,30 +293,16 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener,
NotificationShadeDepthController depthController,
SysuiColorExtractor sysuiColorExtractor, IStatusBarService statusBarService,
NotificationShadeWindowController notificationShadeWindowController,
- boolean controlsAvailable, @Nullable ControlsUiController controlsUiController,
SysUiState sysuiState, Runnable onRotateCallback, boolean keyguardShowing,
MyPowerOptionsAdapter powerAdapter) {
- super(context, com.android.systemui.R.style.Theme_SystemUI_Dialog_GlobalActions);
- mContext = context;
- mAdapter = adapter;
- mOverflowAdapter = overflowAdapter;
- mPowerOptionsAdapter = powerAdapter;
- mDepthController = depthController;
- mColorExtractor = sysuiColorExtractor;
- mStatusBarService = statusBarService;
- mNotificationShadeWindowController = notificationShadeWindowController;
- mControlsAvailable = controlsAvailable;
- mControlsUiController = controlsUiController;
- mSysUiState = sysuiState;
- mOnRotateCallback = onRotateCallback;
- mKeyguardShowing = keyguardShowing;
+ super(context, com.android.systemui.R.style.Theme_SystemUI_Dialog_GlobalActions,
+ adapter, overflowAdapter, depthController, sysuiColorExtractor,
+ statusBarService, notificationShadeWindowController, sysuiState,
+ onRotateCallback, keyguardShowing, powerAdapter);
mWalletFactory = walletFactory;
- // Window initialization
+ // Update window attributes
Window window = getWindow();
- window.requestFeature(Window.FEATURE_NO_TITLE);
- // Inflate the decor view, so the attributes below are not overwritten by the theme.
- window.getDecorView();
window.getAttributes().systemUiVisibility |= View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| View.SYSTEM_UI_FLAG_LAYOUT_STABLE
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION;
@@ -2211,28 +315,18 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener,
| WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
| WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
| WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
- window.setType(WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY);
- window.getAttributes().setFitInsetsTypes(0 /* types */);
setTitle(R.string.global_actions);
-
initializeLayout();
}
- private boolean isShowingControls() {
- return mControlsUiController != null;
- }
-
- private void showControls(ControlsUiController controller) {
- mControlsUiController = controller;
- mControlsUiController.show(mControlsView, this::dismissForControlsActivity,
- null /* activityContext */);
- }
-
private boolean isWalletViewAvailable() {
return mWalletViewController != null && mWalletViewController.getPanelContent() != null;
}
private void initializeWalletView() {
+ if (mWalletFactory == null) {
+ return;
+ }
mWalletViewController = mWalletFactory.get();
if (!isWalletViewAvailable()) {
return;
@@ -2291,10 +385,8 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener,
new FrameLayout.LayoutParams(
FrameLayout.LayoutParams.MATCH_PARENT,
FrameLayout.LayoutParams.MATCH_PARENT);
- if (!mControlsAvailable) {
- panelParams.topMargin = mContext.getResources().getDimensionPixelSize(
- com.android.systemui.R.dimen.global_actions_wallet_top_margin);
- }
+ panelParams.topMargin = mContext.getResources().getDimensionPixelSize(
+ com.android.systemui.R.dimen.global_actions_wallet_top_margin);
View walletView = mWalletViewController.getPanelContent();
panelContainer.addView(walletView, panelParams);
// Smooth transitions when wallet is resized, which can happen when a card is added
@@ -2313,132 +405,23 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener,
}
}
- private ListPopupWindow createPowerOverflowPopup() {
- GlobalActionsPopupMenu popup = new GlobalActionsPopupMenu(
- new ContextThemeWrapper(
- mContext,
- com.android.systemui.R.style.Control_ListPopupWindow
- ), false /* isDropDownMode */);
- popup.setOnItemClickListener(
- (parent, view, position, id) -> mOverflowAdapter.onClickItem(position));
- popup.setOnItemLongClickListener(
- (parent, view, position, id) -> mOverflowAdapter.onLongClickItem(position));
- View overflowButton =
- findViewById(com.android.systemui.R.id.global_actions_overflow_button);
- popup.setAnchorView(overflowButton);
- popup.setAdapter(mOverflowAdapter);
- return popup;
- }
-
- public void showPowerOptionsMenu() {
- mPowerOptionsDialog = GlobalActionsPowerDialog.create(mContext, mPowerOptionsAdapter);
- mPowerOptionsDialog.show();
- }
-
- private void showPowerOverflowMenu() {
- mOverflowPopup = createPowerOverflowPopup();
- mOverflowPopup.show();
+ @Override
+ protected int getLayoutResource() {
+ return com.android.systemui.R.layout.global_actions_grid_v2;
}
- private void initializeLayout() {
- setContentView(com.android.systemui.R.layout.global_actions_grid_v2);
- fixNavBarClipping();
- mControlsView = findViewById(com.android.systemui.R.id.global_actions_controls);
- mGlobalActionsLayout = findViewById(com.android.systemui.R.id.global_actions_view);
- mGlobalActionsLayout.setListViewAccessibilityDelegate(new View.AccessibilityDelegate() {
- @Override
- public boolean dispatchPopulateAccessibilityEvent(
- View host, AccessibilityEvent event) {
- // Populate the title here, just as Activity does
- event.getText().add(mContext.getString(R.string.global_actions));
- return true;
- }
- });
- mGlobalActionsLayout.setRotationListener(this::onRotate);
- mGlobalActionsLayout.setAdapter(mAdapter);
- mContainer = findViewById(com.android.systemui.R.id.global_actions_container);
+ @Override
+ protected void initializeLayout() {
+ super.initializeLayout();
mLockMessageContainer = requireViewById(
com.android.systemui.R.id.global_actions_lock_message_container);
mLockMessage = requireViewById(com.android.systemui.R.id.global_actions_lock_message);
-
- View overflowButton = findViewById(
- com.android.systemui.R.id.global_actions_overflow_button);
- if (overflowButton != null) {
- if (mOverflowAdapter.getCount() > 0) {
- overflowButton.setOnClickListener((view) -> showPowerOverflowMenu());
- LinearLayout.LayoutParams params =
- (LinearLayout.LayoutParams) mGlobalActionsLayout.getLayoutParams();
- params.setMarginEnd(0);
- mGlobalActionsLayout.setLayoutParams(params);
- } else {
- overflowButton.setVisibility(View.GONE);
- LinearLayout.LayoutParams params =
- (LinearLayout.LayoutParams) mGlobalActionsLayout.getLayoutParams();
- params.setMarginEnd(mContext.getResources().getDimensionPixelSize(
- com.android.systemui.R.dimen.global_actions_side_margin));
- mGlobalActionsLayout.setLayoutParams(params);
- }
- }
-
initializeWalletView();
- if (mBackgroundDrawable == null) {
- mBackgroundDrawable = new ScrimDrawable();
- mScrimAlpha = 1.0f;
- }
getWindow().setBackgroundDrawable(mBackgroundDrawable);
}
- private void fixNavBarClipping() {
- ViewGroup content = findViewById(android.R.id.content);
- content.setClipChildren(false);
- content.setClipToPadding(false);
- ViewGroup contentParent = (ViewGroup) content.getParent();
- contentParent.setClipChildren(false);
- contentParent.setClipToPadding(false);
- }
-
- @Override
- protected void onStart() {
- super.setCanceledOnTouchOutside(true);
- super.onStart();
- mGlobalActionsLayout.updateList();
-
- if (mBackgroundDrawable instanceof ScrimDrawable) {
- mColorExtractor.addOnColorsChangedListener(this);
- GradientColors colors = mColorExtractor.getNeutralColors();
- updateColors(colors, false /* animate */);
- }
- }
-
- /**
- * Updates background and system bars according to current GradientColors.
- *
- * @param colors Colors and hints to use.
- * @param animate Interpolates gradient if true, just sets otherwise.
- */
- private void updateColors(GradientColors colors, boolean animate) {
- if (!(mBackgroundDrawable instanceof ScrimDrawable)) {
- return;
- }
- ((ScrimDrawable) mBackgroundDrawable).setColor(Color.BLACK, animate);
- View decorView = getWindow().getDecorView();
- if (colors.supportsDarkText()) {
- decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR |
- View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
- } else {
- decorView.setSystemUiVisibility(0);
- }
- }
-
- @Override
- protected void onStop() {
- super.onStop();
- mColorExtractor.removeOnColorsChangedListener(this);
- }
-
@Override
- public void show() {
- super.show();
+ protected void showDialog() {
mShowing = true;
mNotificationShadeWindowController.setRequestTopUi(true, TAG);
mSysUiState.setFlag(SYSUI_STATE_GLOBAL_ACTIONS_SHOWING, true)
@@ -2452,10 +435,6 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener,
windowInsets.getStableInsetBottom());
return WindowInsets.CONSUMED;
});
- if (mControlsUiController != null) {
- mControlsUiController.show(mControlsView, this::dismissForControlsActivity,
- null /* activityContext */);
- }
mBackgroundDrawable.setAlpha(0);
float xOffset = mGlobalActionsLayout.getAnimationOffsetX();
@@ -2482,71 +461,15 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener,
}
@Override
- public void dismiss() {
- dismissWithAnimation(() -> {
- mContainer.setTranslationX(0);
- ObjectAnimator alphaAnimator =
- ObjectAnimator.ofFloat(mContainer, "alpha", 1f, 0f);
- alphaAnimator.setInterpolator(Interpolators.FAST_OUT_LINEAR_IN);
- alphaAnimator.setDuration(233);
- alphaAnimator.addUpdateListener((animation) -> {
- float animatedValue = 1f - animation.getAnimatedFraction();
- int alpha = (int) (animatedValue * mScrimAlpha * 255);
- mBackgroundDrawable.setAlpha(alpha);
- mDepthController.updateGlobalDialogVisibility(animatedValue,
- mGlobalActionsLayout);
- });
-
- float xOffset = mGlobalActionsLayout.getAnimationOffsetX();
- ObjectAnimator xAnimator =
- ObjectAnimator.ofFloat(mContainer, "translationX", 0f, xOffset);
- xAnimator.setInterpolator(Interpolators.FAST_OUT_LINEAR_IN);
- xAnimator.setDuration(350);
-
- AnimatorSet animatorSet = new AnimatorSet();
- animatorSet.playTogether(alphaAnimator, xAnimator);
- animatorSet.addListener(new AnimatorListenerAdapter() {
- public void onAnimationEnd(Animator animation) {
- completeDismiss();
- }
- });
-
- animatorSet.start();
-
- // close first, as popup windows will not fade during the animation
- dismissOverflow(false);
- dismissPowerOptions(false);
- if (mControlsUiController != null) mControlsUiController.closeDialogs(false);
- });
- }
-
- private void dismissForControlsActivity() {
- dismissWithAnimation(() -> {
- ViewGroup root = (ViewGroup) mGlobalActionsLayout.getParent();
- ControlsAnimations.exitAnimation(root, this::completeDismiss).start();
- });
+ protected void dismissInternal() {
+ super.dismissInternal();
}
- void dismissWithAnimation(Runnable animation) {
- if (!mShowing) {
- return;
- }
- mShowing = false;
- animation.run();
- }
-
- private void completeDismiss() {
- mShowing = false;
- resetOrientation();
+ @Override
+ protected void completeDismiss() {
dismissWallet();
- dismissOverflow(true);
- dismissPowerOptions(true);
- if (mControlsUiController != null) mControlsUiController.hide();
- mNotificationShadeWindowController.setRequestTopUi(false, TAG);
- mDepthController.updateGlobalDialogVisibility(0, null /* view */);
- mSysUiState.setFlag(SYSUI_STATE_GLOBAL_ACTIONS_SHOWING, false)
- .commitUpdate(mContext.getDisplayId());
- super.dismiss();
+ resetOrientation();
+ super.completeDismiss();
}
private void dismissWallet() {
@@ -2557,38 +480,6 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener,
}
}
- private void dismissOverflow(boolean immediate) {
- if (mOverflowPopup != null) {
- if (immediate) {
- mOverflowPopup.dismissImmediate();
- } else {
- mOverflowPopup.dismiss();
- }
- }
- }
-
- private void dismissPowerOptions(boolean immediate) {
- if (mPowerOptionsDialog != null) {
- if (immediate) {
- mPowerOptionsDialog.dismiss();
- } else {
- mPowerOptionsDialog.dismiss();
- }
- }
- }
-
- private void setRotationSuggestionsEnabled(boolean enabled) {
- try {
- final int userId = Binder.getCallingUserHandle().getIdentifier();
- final int what = enabled
- ? StatusBarManager.DISABLE2_NONE
- : StatusBarManager.DISABLE2_ROTATE_SUGGESTIONS;
- mStatusBarService.disable2ForUser(what, mToken, mContext.getPackageName(), userId);
- } catch (RemoteException ex) {
- throw ex.rethrowFromSystemServer();
- }
- }
-
private void resetOrientation() {
if (mResetOrientationData != null) {
RotationPolicy.setRotationLockAtAngle(mContext, mResetOrientationData.locked,
@@ -2598,47 +489,10 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener,
}
@Override
- public void onColorsChanged(ColorExtractor extractor, int which) {
- if (mKeyguardShowing) {
- if ((WallpaperManager.FLAG_LOCK & which) != 0) {
- updateColors(extractor.getColors(WallpaperManager.FLAG_LOCK),
- true /* animate */);
- }
- } else {
- if ((WallpaperManager.FLAG_SYSTEM & which) != 0) {
- updateColors(extractor.getColors(WallpaperManager.FLAG_SYSTEM),
- true /* animate */);
- }
- }
- }
-
- public void setKeyguardShowing(boolean keyguardShowing) {
- mKeyguardShowing = keyguardShowing;
- }
-
public void refreshDialog() {
// ensure dropdown menus are dismissed before re-initializing the dialog
dismissWallet();
- dismissOverflow(true);
- dismissPowerOptions(true);
- if (mControlsUiController != null) {
- mControlsUiController.hide();
- }
-
- // re-create dialog
- initializeLayout();
- mGlobalActionsLayout.updateList();
- if (mControlsUiController != null) {
- mControlsUiController.show(mControlsView, this::dismissForControlsActivity,
- null /* activityContext */);
- }
- }
-
- public void onRotate(int from, int to) {
- if (mShowing) {
- mOnRotateCallback.run();
- refreshDialog();
- }
+ super.refreshDialog();
}
void hideLockMessage() {
@@ -2682,15 +536,8 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener,
return isPanelDebugModeEnabled(context);
}
- private boolean controlsAvailable() {
- return mDeviceProvisioned
- && mControlsComponent.isEnabled()
- && !mControlsServiceInfos.isEmpty();
- }
-
private boolean shouldShowLockMessage(ActionsDialog dialog) {
- return mControlsComponent.getVisibility() == AVAILABLE_AFTER_UNLOCK
- || isWalletAvailableAfterUnlock(dialog);
+ return isWalletAvailableAfterUnlock(dialog);
}
// Temporary while we move items out of the power menu
@@ -2698,12 +545,12 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener,
boolean isLockedAfterBoot = mLockPatternUtils.getStrongAuthForUser(getCurrentUser().id)
== STRONG_AUTH_REQUIRED_AFTER_BOOT;
return !mKeyguardStateController.isUnlocked()
- && (!mShowLockScreenCardsAndControls || isLockedAfterBoot)
+ && (!mShowLockScreenCards || isLockedAfterBoot)
&& dialog.isWalletViewAvailable();
}
private void onPowerMenuLockScreenSettingsChanged() {
- mShowLockScreenCardsAndControls = Settings.Secure.getInt(mContentResolver,
+ mShowLockScreenCards = mSecureSettings.getInt(
Settings.Secure.POWER_MENU_LOCKED_SHOW_CONTENT, 0) != 0;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
new file mode 100644
index 000000000000..47ae145590b5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
@@ -0,0 +1,2391 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.globalactions;
+
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
+import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
+import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_GLOBAL_ACTIONS;
+import static android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN;
+import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_2BUTTON;
+
+import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST;
+import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED;
+import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_GLOBAL_ACTIONS_SHOWING;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.annotation.Nullable;
+import android.app.ActivityManager;
+import android.app.Dialog;
+import android.app.IActivityManager;
+import android.app.StatusBarManager;
+import android.app.WallpaperManager;
+import android.app.admin.DevicePolicyManager;
+import android.app.trust.TrustManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.content.pm.UserInfo;
+import android.content.res.ColorStateList;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.database.ContentObserver;
+import android.graphics.Color;
+import android.graphics.drawable.Drawable;
+import android.media.AudioManager;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.RemoteException;
+import android.os.SystemProperties;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.os.Vibrator;
+import android.provider.Settings;
+import android.service.dreams.IDreamManager;
+import android.sysprop.TelephonyProperties;
+import android.telecom.TelecomManager;
+import android.telephony.ServiceState;
+import android.telephony.TelephonyCallback;
+import android.telephony.TelephonyManager;
+import android.util.ArraySet;
+import android.util.Log;
+import android.view.ContextThemeWrapper;
+import android.view.IWindowManager;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.Window;
+import android.view.WindowInsets;
+import android.view.WindowManager;
+import android.view.accessibility.AccessibilityEvent;
+import android.widget.BaseAdapter;
+import android.widget.ImageView;
+import android.widget.ImageView.ScaleType;
+import android.widget.LinearLayout;
+import android.widget.ListPopupWindow;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.lifecycle.Lifecycle;
+import androidx.lifecycle.LifecycleOwner;
+import androidx.lifecycle.LifecycleRegistry;
+
+import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.colorextraction.ColorExtractor;
+import com.android.internal.colorextraction.ColorExtractor.GradientColors;
+import com.android.internal.colorextraction.drawable.ScrimDrawable;
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.UiEvent;
+import com.android.internal.logging.UiEventLogger;
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.internal.statusbar.IStatusBarService;
+import com.android.internal.util.EmergencyAffordanceManager;
+import com.android.internal.util.ScreenshotHelper;
+import com.android.internal.widget.LockPatternUtils;
+import com.android.systemui.Interpolators;
+import com.android.systemui.MultiListLayout;
+import com.android.systemui.MultiListLayout.MultiListAdapter;
+import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.colorextraction.SysuiColorExtractor;
+import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.model.SysUiState;
+import com.android.systemui.plugins.GlobalActions.GlobalActionsManager;
+import com.android.systemui.plugins.GlobalActionsPanelPlugin;
+import com.android.systemui.statusbar.NotificationShadeDepthController;
+import com.android.systemui.statusbar.NotificationShadeWindowController;
+import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.telephony.TelephonyListenerManager;
+import com.android.systemui.util.EmergencyDialerConstants;
+import com.android.systemui.util.RingerModeTracker;
+import com.android.systemui.util.settings.GlobalSettings;
+import com.android.systemui.util.settings.SecureSettings;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Executor;
+
+import javax.inject.Inject;
+
+/**
+ * Helper to show the global actions dialog. Each item is an {@link Action} that may show depending
+ * on whether the keyguard is showing, and whether the device is provisioned.
+ */
+public class GlobalActionsDialogLite implements DialogInterface.OnDismissListener,
+ DialogInterface.OnShowListener,
+ ConfigurationController.ConfigurationListener,
+ GlobalActionsPanelPlugin.Callbacks,
+ LifecycleOwner {
+
+ public static final String SYSTEM_DIALOG_REASON_KEY = "reason";
+ public static final String SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS = "globalactions";
+ public static final String SYSTEM_DIALOG_REASON_DREAM = "dream";
+
+ private static final String TAG = "GlobalActionsDialogLite";
+
+ private static final boolean SHOW_SILENT_TOGGLE = true;
+
+ /* Valid settings for global actions keys.
+ * see config.xml config_globalActionList */
+ @VisibleForTesting
+ static final String GLOBAL_ACTION_KEY_POWER = "power";
+ private static final String GLOBAL_ACTION_KEY_AIRPLANE = "airplane";
+ static final String GLOBAL_ACTION_KEY_BUGREPORT = "bugreport";
+ private static final String GLOBAL_ACTION_KEY_SILENT = "silent";
+ private static final String GLOBAL_ACTION_KEY_USERS = "users";
+ private static final String GLOBAL_ACTION_KEY_SETTINGS = "settings";
+ static final String GLOBAL_ACTION_KEY_LOCKDOWN = "lockdown";
+ private static final String GLOBAL_ACTION_KEY_VOICEASSIST = "voiceassist";
+ private static final String GLOBAL_ACTION_KEY_ASSIST = "assist";
+ static final String GLOBAL_ACTION_KEY_RESTART = "restart";
+ private static final String GLOBAL_ACTION_KEY_LOGOUT = "logout";
+ static final String GLOBAL_ACTION_KEY_EMERGENCY = "emergency";
+ static final String GLOBAL_ACTION_KEY_SCREENSHOT = "screenshot";
+
+ private final Context mContext;
+ private final GlobalActionsManager mWindowManagerFuncs;
+ private final AudioManager mAudioManager;
+ private final IDreamManager mDreamManager;
+ private final DevicePolicyManager mDevicePolicyManager;
+ private final LockPatternUtils mLockPatternUtils;
+ private final KeyguardStateController mKeyguardStateController;
+ private final BroadcastDispatcher mBroadcastDispatcher;
+ protected final GlobalSettings mGlobalSettings;
+ protected final SecureSettings mSecureSettings;
+ protected final Resources mResources;
+ private final ConfigurationController mConfigurationController;
+ private final UserManager mUserManager;
+ private final TrustManager mTrustManager;
+ private final IActivityManager mIActivityManager;
+ private final TelecomManager mTelecomManager;
+ private final MetricsLogger mMetricsLogger;
+ private final UiEventLogger mUiEventLogger;
+ private final NotificationShadeDepthController mDepthController;
+ private final SysUiState mSysUiState;
+
+ // Used for RingerModeTracker
+ private final LifecycleRegistry mLifecycle = new LifecycleRegistry(this);
+
+ @VisibleForTesting
+ protected final ArrayList<Action> mItems = new ArrayList<>();
+ @VisibleForTesting
+ protected final ArrayList<Action> mOverflowItems = new ArrayList<>();
+ @VisibleForTesting
+ protected final ArrayList<Action> mPowerItems = new ArrayList<>();
+
+ @VisibleForTesting
+ protected ActionsDialogLite mDialog;
+
+ private Action mSilentModeAction;
+ private ToggleAction mAirplaneModeOn;
+
+ protected MyAdapter mAdapter;
+ protected MyOverflowAdapter mOverflowAdapter;
+ protected MyPowerOptionsAdapter mPowerAdapter;
+
+ private boolean mKeyguardShowing = false;
+ private boolean mDeviceProvisioned = false;
+ private ToggleState mAirplaneState = ToggleState.Off;
+ private boolean mIsWaitingForEcmExit = false;
+ private boolean mHasTelephony;
+ private boolean mHasVibrator;
+ private final boolean mShowSilentToggle;
+ private final EmergencyAffordanceManager mEmergencyAffordanceManager;
+ private final ScreenshotHelper mScreenshotHelper;
+ private final SysuiColorExtractor mSysuiColorExtractor;
+ private final IStatusBarService mStatusBarService;
+ protected final NotificationShadeWindowController mNotificationShadeWindowController;
+ private final IWindowManager mIWindowManager;
+ private final Executor mBackgroundExecutor;
+ private final RingerModeTracker mRingerModeTracker;
+ private int mDialogPressDelay = DIALOG_PRESS_DELAY; // ms
+ protected Handler mMainHandler;
+ private int mSmallestScreenWidthDp;
+
+ @VisibleForTesting
+ public enum GlobalActionsEvent implements UiEventLogger.UiEventEnum {
+ @UiEvent(doc = "The global actions / power menu surface became visible on the screen.")
+ GA_POWER_MENU_OPEN(337),
+
+ @UiEvent(doc = "The global actions / power menu surface was dismissed.")
+ GA_POWER_MENU_CLOSE(471),
+
+ @UiEvent(doc = "The global actions bugreport button was pressed.")
+ GA_BUGREPORT_PRESS(344),
+
+ @UiEvent(doc = "The global actions bugreport button was long pressed.")
+ GA_BUGREPORT_LONG_PRESS(345),
+
+ @UiEvent(doc = "The global actions emergency button was pressed.")
+ GA_EMERGENCY_DIALER_PRESS(346),
+
+ @UiEvent(doc = "The global actions screenshot button was pressed.")
+ GA_SCREENSHOT_PRESS(347),
+
+ @UiEvent(doc = "The global actions screenshot button was long pressed.")
+ GA_SCREENSHOT_LONG_PRESS(348);
+
+ private final int mId;
+
+ GlobalActionsEvent(int id) {
+ mId = id;
+ }
+
+ @Override
+ public int getId() {
+ return mId;
+ }
+ }
+
+ /**
+ * @param context everything needs a context :(
+ */
+ @Inject
+ public GlobalActionsDialogLite(Context context, GlobalActionsManager windowManagerFuncs,
+ AudioManager audioManager, IDreamManager iDreamManager,
+ DevicePolicyManager devicePolicyManager, LockPatternUtils lockPatternUtils,
+ BroadcastDispatcher broadcastDispatcher,
+ TelephonyListenerManager telephonyListenerManager,
+ GlobalSettings globalSettings, SecureSettings secureSettings,
+ @Nullable Vibrator vibrator, @Main Resources resources,
+ ConfigurationController configurationController,
+ KeyguardStateController keyguardStateController, UserManager userManager,
+ TrustManager trustManager, IActivityManager iActivityManager,
+ @Nullable TelecomManager telecomManager, MetricsLogger metricsLogger,
+ NotificationShadeDepthController depthController, SysuiColorExtractor colorExtractor,
+ IStatusBarService statusBarService,
+ NotificationShadeWindowController notificationShadeWindowController,
+ IWindowManager iWindowManager,
+ @Background Executor backgroundExecutor,
+ UiEventLogger uiEventLogger,
+ RingerModeTracker ringerModeTracker, SysUiState sysUiState, @Main Handler handler) {
+ mContext = context;
+ mWindowManagerFuncs = windowManagerFuncs;
+ mAudioManager = audioManager;
+ mDreamManager = iDreamManager;
+ mDevicePolicyManager = devicePolicyManager;
+ mLockPatternUtils = lockPatternUtils;
+ mKeyguardStateController = keyguardStateController;
+ mBroadcastDispatcher = broadcastDispatcher;
+ mGlobalSettings = globalSettings;
+ mSecureSettings = secureSettings;
+ mResources = resources;
+ mConfigurationController = configurationController;
+ mUserManager = userManager;
+ mTrustManager = trustManager;
+ mIActivityManager = iActivityManager;
+ mTelecomManager = telecomManager;
+ mMetricsLogger = metricsLogger;
+ mUiEventLogger = uiEventLogger;
+ mDepthController = depthController;
+ mSysuiColorExtractor = colorExtractor;
+ mStatusBarService = statusBarService;
+ mNotificationShadeWindowController = notificationShadeWindowController;
+ mIWindowManager = iWindowManager;
+ mBackgroundExecutor = backgroundExecutor;
+ mRingerModeTracker = ringerModeTracker;
+ mSysUiState = sysUiState;
+ mMainHandler = handler;
+ mSmallestScreenWidthDp = mContext.getResources().getConfiguration().smallestScreenWidthDp;
+
+ // receive broadcasts
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
+ filter.addAction(Intent.ACTION_SCREEN_OFF);
+ filter.addAction(TelephonyManager.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED);
+ mBroadcastDispatcher.registerReceiver(mBroadcastReceiver, filter);
+
+ mHasTelephony =
+ context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEPHONY);
+
+ // get notified of phone state changes
+ telephonyListenerManager.addServiceStateListener(mPhoneStateListener);
+ mGlobalSettings.registerContentObserver(
+ Settings.Global.getUriFor(Settings.Global.AIRPLANE_MODE_ON), true,
+ mAirplaneModeObserver);
+ mHasVibrator = vibrator != null && vibrator.hasVibrator();
+
+ mShowSilentToggle = SHOW_SILENT_TOGGLE && !resources.getBoolean(
+ R.bool.config_useFixedVolume);
+ if (mShowSilentToggle) {
+ mRingerModeTracker.getRingerMode().observe(this, ringer ->
+ mHandler.sendEmptyMessage(MESSAGE_REFRESH)
+ );
+ }
+
+ mEmergencyAffordanceManager = new EmergencyAffordanceManager(context);
+ mScreenshotHelper = new ScreenshotHelper(context);
+
+ mConfigurationController.addCallback(this);
+ }
+
+ protected Context getContext() {
+ return mContext;
+ }
+
+ /**
+ * Show the global actions dialog (creating if necessary)
+ *
+ * @param keyguardShowing True if keyguard is showing
+ */
+ public void showOrHideDialog(boolean keyguardShowing, boolean isDeviceProvisioned) {
+ mKeyguardShowing = keyguardShowing;
+ mDeviceProvisioned = isDeviceProvisioned;
+ if (mDialog != null && mDialog.isShowing()) {
+ // In order to force global actions to hide on the same affordance press, we must
+ // register a call to onGlobalActionsShown() first to prevent the default actions
+ // menu from showing. This will be followed by a subsequent call to
+ // onGlobalActionsHidden() on dismiss()
+ mWindowManagerFuncs.onGlobalActionsShown();
+ mDialog.dismiss();
+ mDialog = null;
+ } else {
+ handleShow();
+ }
+ }
+
+ protected boolean isKeyguardShowing() {
+ return mKeyguardShowing;
+ }
+
+ protected boolean isDeviceProvisioned() {
+ return mDeviceProvisioned;
+ }
+
+ /**
+ * Dismiss the global actions dialog, if it's currently shown
+ */
+ public void dismissDialog() {
+ mHandler.removeMessages(MESSAGE_DISMISS);
+ mHandler.sendEmptyMessage(MESSAGE_DISMISS);
+ }
+
+ protected void awakenIfNecessary() {
+ if (mDreamManager != null) {
+ try {
+ if (mDreamManager.isDreaming()) {
+ mDreamManager.awaken();
+ }
+ } catch (RemoteException e) {
+ // we tried
+ }
+ }
+ }
+
+ protected void handleShow() {
+ awakenIfNecessary();
+ mDialog = createDialog();
+ prepareDialog();
+
+ WindowManager.LayoutParams attrs = mDialog.getWindow().getAttributes();
+ attrs.setTitle("ActionsDialog");
+ attrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+ mDialog.getWindow().setAttributes(attrs);
+ // Don't acquire soft keyboard focus, to avoid destroying state when capturing bugreports
+ mDialog.getWindow().setFlags(FLAG_ALT_FOCUSABLE_IM, FLAG_ALT_FOCUSABLE_IM);
+ mDialog.show();
+ mWindowManagerFuncs.onGlobalActionsShown();
+ }
+
+ @VisibleForTesting
+ protected boolean shouldShowAction(Action action) {
+ if (mKeyguardShowing && !action.showDuringKeyguard()) {
+ return false;
+ }
+ if (!mDeviceProvisioned && !action.showBeforeProvisioning()) {
+ return false;
+ }
+ return action.shouldShow();
+ }
+
+ /**
+ * Returns the maximum number of power menu items to show based on which GlobalActions
+ * layout is being used.
+ */
+ @VisibleForTesting
+ protected int getMaxShownPowerItems() {
+ return mResources.getInteger(com.android.systemui.R.integer.power_menu_lite_max_columns)
+ * mResources.getInteger(com.android.systemui.R.integer.power_menu_lite_max_rows);
+ }
+
+ /**
+ * Add a power menu action item for to either the main or overflow items lists, depending on
+ * whether controls are enabled and whether the max number of shown items has been reached.
+ */
+ private void addActionItem(Action action) {
+ if (mItems.size() < getMaxShownPowerItems()) {
+ mItems.add(action);
+ } else {
+ mOverflowItems.add(action);
+ }
+ }
+
+ @VisibleForTesting
+ protected String[] getDefaultActions() {
+ return mResources.getStringArray(R.array.config_globalActionsList);
+ }
+
+ private void addIfShouldShowAction(List<Action> actions, Action action) {
+ if (shouldShowAction(action)) {
+ actions.add(action);
+ }
+ }
+
+ @VisibleForTesting
+ protected void createActionItems() {
+ // Simple toggle style if there's no vibrator, otherwise use a tri-state
+ if (!mHasVibrator) {
+ mSilentModeAction = new SilentModeToggleAction();
+ } else {
+ mSilentModeAction = new SilentModeTriStateAction(mAudioManager, mHandler);
+ }
+ mAirplaneModeOn = new AirplaneModeAction();
+ onAirplaneModeChanged();
+
+ mItems.clear();
+ mOverflowItems.clear();
+ mPowerItems.clear();
+ String[] defaultActions = getDefaultActions();
+
+ ShutDownAction shutdownAction = new ShutDownAction();
+ RestartAction restartAction = new RestartAction();
+ ArraySet<String> addedKeys = new ArraySet<>();
+ List<Action> tempActions = new ArrayList<>();
+ CurrentUserProvider currentUser = new CurrentUserProvider();
+
+ // make sure emergency affordance action is first, if needed
+ if (mEmergencyAffordanceManager.needsEmergencyAffordance()) {
+ addIfShouldShowAction(tempActions, new EmergencyAffordanceAction());
+ addedKeys.add(GLOBAL_ACTION_KEY_EMERGENCY);
+ }
+
+ for (int i = 0; i < defaultActions.length; i++) {
+ String actionKey = defaultActions[i];
+ if (addedKeys.contains(actionKey)) {
+ // If we already have added this, don't add it again.
+ continue;
+ }
+ if (GLOBAL_ACTION_KEY_POWER.equals(actionKey)) {
+ addIfShouldShowAction(tempActions, shutdownAction);
+ } else if (GLOBAL_ACTION_KEY_AIRPLANE.equals(actionKey)) {
+ addIfShouldShowAction(tempActions, mAirplaneModeOn);
+ } else if (GLOBAL_ACTION_KEY_BUGREPORT.equals(actionKey)) {
+ if (shouldDisplayBugReport(currentUser.get())) {
+ addIfShouldShowAction(tempActions, new BugReportAction());
+ }
+ } else if (GLOBAL_ACTION_KEY_SILENT.equals(actionKey)) {
+ if (mShowSilentToggle) {
+ addIfShouldShowAction(tempActions, mSilentModeAction);
+ }
+ } else if (GLOBAL_ACTION_KEY_USERS.equals(actionKey)) {
+ if (SystemProperties.getBoolean("fw.power_user_switcher", false)) {
+ addUserActions(tempActions, currentUser.get());
+ }
+ } else if (GLOBAL_ACTION_KEY_SETTINGS.equals(actionKey)) {
+ addIfShouldShowAction(tempActions, getSettingsAction());
+ } else if (GLOBAL_ACTION_KEY_LOCKDOWN.equals(actionKey)) {
+ if (shouldDisplayLockdown(currentUser.get())) {
+ addIfShouldShowAction(tempActions, new LockDownAction());
+ }
+ } else if (GLOBAL_ACTION_KEY_VOICEASSIST.equals(actionKey)) {
+ addIfShouldShowAction(tempActions, getVoiceAssistAction());
+ } else if (GLOBAL_ACTION_KEY_ASSIST.equals(actionKey)) {
+ addIfShouldShowAction(tempActions, getAssistAction());
+ } else if (GLOBAL_ACTION_KEY_RESTART.equals(actionKey)) {
+ addIfShouldShowAction(tempActions, restartAction);
+ } else if (GLOBAL_ACTION_KEY_SCREENSHOT.equals(actionKey)) {
+ addIfShouldShowAction(tempActions, new ScreenshotAction());
+ } else if (GLOBAL_ACTION_KEY_LOGOUT.equals(actionKey)) {
+ if (mDevicePolicyManager.isLogoutEnabled()
+ && currentUser.get() != null
+ && currentUser.get().id != UserHandle.USER_SYSTEM) {
+ addIfShouldShowAction(tempActions, new LogoutAction());
+ }
+ } else if (GLOBAL_ACTION_KEY_EMERGENCY.equals(actionKey)) {
+ addIfShouldShowAction(tempActions, new EmergencyDialerAction());
+ } else {
+ Log.e(TAG, "Invalid global action key " + actionKey);
+ }
+ // Add here so we don't add more than one.
+ addedKeys.add(actionKey);
+ }
+
+ // replace power and restart with a single power options action, if needed
+ if (tempActions.contains(shutdownAction) && tempActions.contains(restartAction)
+ && tempActions.size() > getMaxShownPowerItems()) {
+ // transfer shutdown and restart to their own list of power actions
+ int powerOptionsIndex = Math.min(tempActions.indexOf(restartAction),
+ tempActions.indexOf(shutdownAction));
+ tempActions.remove(shutdownAction);
+ tempActions.remove(restartAction);
+ mPowerItems.add(shutdownAction);
+ mPowerItems.add(restartAction);
+
+ // add the PowerOptionsAction after Emergency, if present
+ tempActions.add(powerOptionsIndex, new PowerOptionsAction());
+ }
+ for (Action action : tempActions) {
+ addActionItem(action);
+ }
+ }
+
+ protected void onRotate() {
+ // re-allocate actions between main and overflow lists
+ this.createActionItems();
+ }
+
+ protected void initDialogItems() {
+ createActionItems();
+ mAdapter = new MyAdapter();
+ mOverflowAdapter = new MyOverflowAdapter();
+ mPowerAdapter = new MyPowerOptionsAdapter();
+ }
+
+ /**
+ * Create the global actions dialog.
+ *
+ * @return A new dialog.
+ */
+ protected ActionsDialogLite createDialog() {
+ initDialogItems();
+
+ mDepthController.setShowingHomeControls(false);
+ ActionsDialogLite dialog = new ActionsDialogLite(mContext,
+ com.android.systemui.R.style.Theme_SystemUI_Dialog_GlobalActionsLite,
+ mAdapter, mOverflowAdapter,
+ mDepthController, mSysuiColorExtractor,
+ mStatusBarService, mNotificationShadeWindowController,
+ mSysUiState, this::onRotate, mKeyguardShowing, mPowerAdapter);
+
+ dialog.setCanceledOnTouchOutside(true);
+ dialog.setOnDismissListener(this);
+ dialog.setOnShowListener(this);
+
+ return dialog;
+ }
+
+ @VisibleForTesting
+ boolean shouldDisplayLockdown(UserInfo user) {
+ if (user == null) {
+ return false;
+ }
+
+ int userId = user.id;
+
+ // No lockdown option if it's not turned on in Settings
+ if (mSecureSettings.getIntForUser(Settings.Secure.LOCKDOWN_IN_POWER_MENU, 0, userId) == 0) {
+ return false;
+ }
+
+ // Lockdown is meaningless without a place to go.
+ if (!mKeyguardStateController.isMethodSecure()) {
+ return false;
+ }
+
+ // Only show the lockdown button if the device isn't locked down (for whatever reason).
+ int state = mLockPatternUtils.getStrongAuthForUser(userId);
+ return (state == STRONG_AUTH_NOT_REQUIRED
+ || state == SOME_AUTH_REQUIRED_AFTER_USER_REQUEST);
+ }
+
+ @VisibleForTesting
+ boolean shouldDisplayBugReport(UserInfo currentUser) {
+ return mGlobalSettings.getInt(Settings.Global.BUGREPORT_IN_POWER_MENU, 0) != 0
+ && (currentUser == null || currentUser.isPrimary());
+ }
+
+ @Override
+ public void onUiModeChanged() {
+ mContext.getTheme().applyStyle(mContext.getThemeResId(), true);
+ if (mDialog != null && mDialog.isShowing()) {
+ mDialog.refreshDialog();
+ }
+ }
+
+ @Override
+ public void onConfigChanged(Configuration newConfig) {
+ if (mDialog != null && mDialog.isShowing()
+ && (newConfig.smallestScreenWidthDp != mSmallestScreenWidthDp)) {
+ mSmallestScreenWidthDp = newConfig.smallestScreenWidthDp;
+ mDialog.refreshDialog();
+ }
+ }
+
+ /**
+ * Clean up callbacks
+ */
+ public void destroy() {
+ mConfigurationController.removeCallback(this);
+ }
+
+ /**
+ * Implements {@link GlobalActionsPanelPlugin.Callbacks#dismissGlobalActionsMenu()}, which is
+ * called when the quick access wallet requests dismissal.
+ */
+ @Override
+ public void dismissGlobalActionsMenu() {
+ dismissDialog();
+ }
+
+ @VisibleForTesting
+ protected final class PowerOptionsAction extends SinglePressAction {
+ private PowerOptionsAction() {
+ super(com.android.systemui.R.drawable.ic_settings_power,
+ R.string.global_action_power_options);
+ }
+
+ @Override
+ public boolean showDuringKeyguard() {
+ return true;
+ }
+
+ @Override
+ public boolean showBeforeProvisioning() {
+ return true;
+ }
+
+ @Override
+ public void onPress() {
+ if (mDialog != null) {
+ mDialog.showPowerOptionsMenu();
+ }
+ }
+ }
+
+ @VisibleForTesting
+ final class ShutDownAction extends SinglePressAction implements LongPressAction {
+ private ShutDownAction() {
+ super(R.drawable.ic_lock_power_off,
+ R.string.global_action_power_off);
+ }
+
+ @Override
+ public boolean onLongPress() {
+ if (!mUserManager.hasUserRestriction(UserManager.DISALLOW_SAFE_BOOT)) {
+ mWindowManagerFuncs.reboot(true);
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public boolean showDuringKeyguard() {
+ return true;
+ }
+
+ @Override
+ public boolean showBeforeProvisioning() {
+ return true;
+ }
+
+ @Override
+ public void onPress() {
+ // shutdown by making sure radio and power are handled accordingly.
+ mWindowManagerFuncs.shutdown();
+ }
+ }
+
+ @VisibleForTesting
+ protected abstract class EmergencyAction extends SinglePressAction {
+ EmergencyAction(int iconResId, int messageResId) {
+ super(iconResId, messageResId);
+ }
+
+ @Override
+ public boolean shouldBeSeparated() {
+ return false;
+ }
+
+ @Override
+ public View create(
+ Context context, View convertView, ViewGroup parent, LayoutInflater inflater) {
+ View v = super.create(context, convertView, parent, inflater);
+ int textColor = getEmergencyTextColor(context);
+ int iconColor = getEmergencyIconColor(context);
+ int backgroundColor = getEmergencyBackgroundColor(context);
+ TextView messageView = v.findViewById(R.id.message);
+ messageView.setTextColor(textColor);
+ messageView.setSelected(true); // necessary for marquee to work
+ ImageView icon = v.findViewById(R.id.icon);
+ icon.getDrawable().setTint(iconColor);
+ icon.setBackgroundTintList(ColorStateList.valueOf(backgroundColor));
+ v.setBackgroundTintList(ColorStateList.valueOf(backgroundColor));
+ return v;
+ }
+
+ @Override
+ public boolean showDuringKeyguard() {
+ return true;
+ }
+
+ @Override
+ public boolean showBeforeProvisioning() {
+ return true;
+ }
+ }
+
+ protected int getEmergencyTextColor(Context context) {
+ return context.getResources().getColor(
+ com.android.systemui.R.color.global_actions_lite_text);
+ }
+
+ protected int getEmergencyIconColor(Context context) {
+ return context.getResources().getColor(
+ com.android.systemui.R.color.global_actions_lite_emergency_icon);
+ }
+
+ protected int getEmergencyBackgroundColor(Context context) {
+ return context.getResources().getColor(
+ com.android.systemui.R.color.global_actions_lite_emergency_background);
+ }
+
+ private class EmergencyAffordanceAction extends EmergencyAction {
+ EmergencyAffordanceAction() {
+ super(R.drawable.emergency_icon,
+ R.string.global_action_emergency);
+ }
+
+ @Override
+ public void onPress() {
+ mEmergencyAffordanceManager.performEmergencyCall();
+ }
+ }
+
+ @VisibleForTesting
+ class EmergencyDialerAction extends EmergencyAction {
+ private EmergencyDialerAction() {
+ super(com.android.systemui.R.drawable.ic_emergency_star,
+ R.string.global_action_emergency);
+ }
+
+ @Override
+ public void onPress() {
+ mMetricsLogger.action(MetricsEvent.ACTION_EMERGENCY_DIALER_FROM_POWER_MENU);
+ mUiEventLogger.log(GlobalActionsEvent.GA_EMERGENCY_DIALER_PRESS);
+ if (mTelecomManager != null) {
+ Intent intent = mTelecomManager.createLaunchEmergencyDialerIntent(
+ null /* number */);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+ | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
+ | Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ intent.putExtra(EmergencyDialerConstants.EXTRA_ENTRY_TYPE,
+ EmergencyDialerConstants.ENTRY_TYPE_POWER_MENU);
+ mContext.startActivityAsUser(intent, UserHandle.CURRENT);
+ }
+ }
+ }
+
+ @VisibleForTesting
+ EmergencyDialerAction makeEmergencyDialerActionForTesting() {
+ return new EmergencyDialerAction();
+ }
+
+ @VisibleForTesting
+ final class RestartAction extends SinglePressAction implements LongPressAction {
+ private RestartAction() {
+ super(R.drawable.ic_restart, R.string.global_action_restart);
+ }
+
+ @Override
+ public boolean onLongPress() {
+ if (!mUserManager.hasUserRestriction(UserManager.DISALLOW_SAFE_BOOT)) {
+ mWindowManagerFuncs.reboot(true);
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public boolean showDuringKeyguard() {
+ return true;
+ }
+
+ @Override
+ public boolean showBeforeProvisioning() {
+ return true;
+ }
+
+ @Override
+ public void onPress() {
+ mWindowManagerFuncs.reboot(false);
+ }
+ }
+
+ @VisibleForTesting
+ class ScreenshotAction extends SinglePressAction {
+ ScreenshotAction() {
+ super(R.drawable.ic_screenshot, R.string.global_action_screenshot);
+ }
+
+ @Override
+ public void onPress() {
+ // Add a little delay before executing, to give the
+ // dialog a chance to go away before it takes a
+ // screenshot.
+ // TODO: instead, omit global action dialog layer
+ mHandler.postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ mScreenshotHelper.takeScreenshot(TAKE_SCREENSHOT_FULLSCREEN, true, true,
+ SCREENSHOT_GLOBAL_ACTIONS, mHandler, null);
+ mMetricsLogger.action(MetricsEvent.ACTION_SCREENSHOT_POWER_MENU);
+ mUiEventLogger.log(GlobalActionsEvent.GA_SCREENSHOT_PRESS);
+ }
+ }, mDialogPressDelay);
+ }
+
+ @Override
+ public boolean showDuringKeyguard() {
+ return true;
+ }
+
+ @Override
+ public boolean showBeforeProvisioning() {
+ return false;
+ }
+
+ @Override
+ public boolean shouldShow() {
+ // Include screenshot in power menu for legacy nav because it is not accessible
+ // through Recents in that mode
+ return is2ButtonNavigationEnabled();
+ }
+
+ boolean is2ButtonNavigationEnabled() {
+ return NAV_BAR_MODE_2BUTTON == mContext.getResources().getInteger(
+ com.android.internal.R.integer.config_navBarInteractionMode);
+ }
+ }
+
+ @VisibleForTesting
+ ScreenshotAction makeScreenshotActionForTesting() {
+ return new ScreenshotAction();
+ }
+
+ @VisibleForTesting
+ class BugReportAction extends SinglePressAction implements LongPressAction {
+
+ BugReportAction() {
+ super(R.drawable.ic_lock_bugreport, R.string.bugreport_title);
+ }
+
+ @Override
+ public void onPress() {
+ // don't actually trigger the bugreport if we are running stability
+ // tests via monkey
+ if (ActivityManager.isUserAMonkey()) {
+ return;
+ }
+ // Add a little delay before executing, to give the
+ // dialog a chance to go away before it takes a
+ // screenshot.
+ mHandler.postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ // Take an "interactive" bugreport.
+ mMetricsLogger.action(
+ MetricsEvent.ACTION_BUGREPORT_FROM_POWER_MENU_INTERACTIVE);
+ mUiEventLogger.log(GlobalActionsEvent.GA_BUGREPORT_PRESS);
+ if (!mIActivityManager.launchBugReportHandlerApp()) {
+ Log.w(TAG, "Bugreport handler could not be launched");
+ mIActivityManager.requestInteractiveBugReport();
+ }
+ } catch (RemoteException e) {
+ }
+ }
+ }, mDialogPressDelay);
+ }
+
+ @Override
+ public boolean onLongPress() {
+ // don't actually trigger the bugreport if we are running stability
+ // tests via monkey
+ if (ActivityManager.isUserAMonkey()) {
+ return false;
+ }
+ try {
+ // Take a "full" bugreport.
+ mMetricsLogger.action(MetricsEvent.ACTION_BUGREPORT_FROM_POWER_MENU_FULL);
+ mUiEventLogger.log(GlobalActionsEvent.GA_BUGREPORT_LONG_PRESS);
+ mIActivityManager.requestFullBugReport();
+ } catch (RemoteException e) {
+ }
+ return false;
+ }
+
+ public boolean showDuringKeyguard() {
+ return true;
+ }
+
+ @Override
+ public boolean showBeforeProvisioning() {
+ return false;
+ }
+ }
+
+ @VisibleForTesting
+ BugReportAction makeBugReportActionForTesting() {
+ return new BugReportAction();
+ }
+
+ private final class LogoutAction extends SinglePressAction {
+ private LogoutAction() {
+ super(R.drawable.ic_logout, R.string.global_action_logout);
+ }
+
+ @Override
+ public boolean showDuringKeyguard() {
+ return true;
+ }
+
+ @Override
+ public boolean showBeforeProvisioning() {
+ return false;
+ }
+
+ @Override
+ public void onPress() {
+ // Add a little delay before executing, to give the dialog a chance to go away before
+ // switching user
+ mHandler.postDelayed(() -> {
+ try {
+ int currentUserId = getCurrentUser().id;
+ mIActivityManager.switchUser(UserHandle.USER_SYSTEM);
+ mIActivityManager.stopUser(currentUserId, true /*force*/, null);
+ } catch (RemoteException re) {
+ Log.e(TAG, "Couldn't logout user " + re);
+ }
+ }, mDialogPressDelay);
+ }
+ }
+
+ private Action getSettingsAction() {
+ return new SinglePressAction(R.drawable.ic_settings,
+ R.string.global_action_settings) {
+
+ @Override
+ public void onPress() {
+ Intent intent = new Intent(Settings.ACTION_SETTINGS);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ mContext.startActivity(intent);
+ }
+
+ @Override
+ public boolean showDuringKeyguard() {
+ return true;
+ }
+
+ @Override
+ public boolean showBeforeProvisioning() {
+ return true;
+ }
+ };
+ }
+
+ private Action getAssistAction() {
+ return new SinglePressAction(R.drawable.ic_action_assist_focused,
+ R.string.global_action_assist) {
+ @Override
+ public void onPress() {
+ Intent intent = new Intent(Intent.ACTION_ASSIST);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ mContext.startActivity(intent);
+ }
+
+ @Override
+ public boolean showDuringKeyguard() {
+ return true;
+ }
+
+ @Override
+ public boolean showBeforeProvisioning() {
+ return true;
+ }
+ };
+ }
+
+ private Action getVoiceAssistAction() {
+ return new SinglePressAction(R.drawable.ic_voice_search,
+ R.string.global_action_voice_assist) {
+ @Override
+ public void onPress() {
+ Intent intent = new Intent(Intent.ACTION_VOICE_ASSIST);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ mContext.startActivity(intent);
+ }
+
+ @Override
+ public boolean showDuringKeyguard() {
+ return true;
+ }
+
+ @Override
+ public boolean showBeforeProvisioning() {
+ return true;
+ }
+ };
+ }
+
+ @VisibleForTesting
+ class LockDownAction extends SinglePressAction {
+ LockDownAction() {
+ super(R.drawable.ic_lock_lockdown, R.string.global_action_lockdown);
+ }
+
+ @Override
+ public void onPress() {
+ mLockPatternUtils.requireStrongAuth(STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN,
+ UserHandle.USER_ALL);
+ try {
+ mIWindowManager.lockNow(null);
+ // Lock profiles (if any) on the background thread.
+ mBackgroundExecutor.execute(() -> lockProfiles());
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error while trying to lock device.", e);
+ }
+ }
+
+ @Override
+ public boolean showDuringKeyguard() {
+ return true;
+ }
+
+ @Override
+ public boolean showBeforeProvisioning() {
+ return false;
+ }
+ }
+
+ private void lockProfiles() {
+ final int currentUserId = getCurrentUser().id;
+ final int[] profileIds = mUserManager.getEnabledProfileIds(currentUserId);
+ for (final int id : profileIds) {
+ if (id != currentUserId) {
+ mTrustManager.setDeviceLockedForUser(id, true);
+ }
+ }
+ }
+
+ protected UserInfo getCurrentUser() {
+ try {
+ return mIActivityManager.getCurrentUser();
+ } catch (RemoteException re) {
+ return null;
+ }
+ }
+
+ /**
+ * Non-thread-safe current user provider that caches the result - helpful when a method needs
+ * to fetch it an indeterminate number of times.
+ */
+ private class CurrentUserProvider {
+ private UserInfo mUserInfo = null;
+ private boolean mFetched = false;
+
+ @Nullable
+ UserInfo get() {
+ if (!mFetched) {
+ mFetched = true;
+ mUserInfo = getCurrentUser();
+ }
+ return mUserInfo;
+ }
+ }
+
+ private void addUserActions(List<Action> actions, UserInfo currentUser) {
+ if (mUserManager.isUserSwitcherEnabled()) {
+ List<UserInfo> users = mUserManager.getUsers();
+ for (final UserInfo user : users) {
+ if (user.supportsSwitchToByUser()) {
+ boolean isCurrentUser = currentUser == null
+ ? user.id == 0 : (currentUser.id == user.id);
+ Drawable icon = user.iconPath != null ? Drawable.createFromPath(user.iconPath)
+ : null;
+ SinglePressAction switchToUser = new SinglePressAction(
+ R.drawable.ic_menu_cc, icon,
+ (user.name != null ? user.name : "Primary")
+ + (isCurrentUser ? " \u2714" : "")) {
+ public void onPress() {
+ try {
+ mIActivityManager.switchUser(user.id);
+ } catch (RemoteException re) {
+ Log.e(TAG, "Couldn't switch user " + re);
+ }
+ }
+
+ public boolean showDuringKeyguard() {
+ return true;
+ }
+
+ public boolean showBeforeProvisioning() {
+ return false;
+ }
+ };
+ addIfShouldShowAction(actions, switchToUser);
+ }
+ }
+ }
+ }
+
+ protected void prepareDialog() {
+ refreshSilentMode();
+ mAirplaneModeOn.updateState(mAirplaneState);
+ mAdapter.notifyDataSetChanged();
+ mLifecycle.setCurrentState(Lifecycle.State.RESUMED);
+ }
+
+ private void refreshSilentMode() {
+ if (!mHasVibrator) {
+ Integer value = mRingerModeTracker.getRingerMode().getValue();
+ final boolean silentModeOn = value != null && value != AudioManager.RINGER_MODE_NORMAL;
+ ((ToggleAction) mSilentModeAction).updateState(
+ silentModeOn ? ToggleState.On : ToggleState.Off);
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onDismiss(DialogInterface dialog) {
+ if (mDialog == dialog) {
+ mDialog = null;
+ }
+ mUiEventLogger.log(GlobalActionsEvent.GA_POWER_MENU_CLOSE);
+ mWindowManagerFuncs.onGlobalActionsHidden();
+ mLifecycle.setCurrentState(Lifecycle.State.CREATED);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onShow(DialogInterface dialog) {
+ mMetricsLogger.visible(MetricsEvent.POWER_MENU);
+ mUiEventLogger.log(GlobalActionsEvent.GA_POWER_MENU_OPEN);
+ }
+
+ /**
+ * The adapter used for power menu items shown in the global actions dialog.
+ */
+ public class MyAdapter extends MultiListAdapter {
+ private int countItems(boolean separated) {
+ int count = 0;
+ for (int i = 0; i < mItems.size(); i++) {
+ final Action action = mItems.get(i);
+
+ if (action.shouldBeSeparated() == separated) {
+ count++;
+ }
+ }
+ return count;
+ }
+
+ @Override
+ public int countSeparatedItems() {
+ return countItems(true);
+ }
+
+ @Override
+ public int countListItems() {
+ return countItems(false);
+ }
+
+ @Override
+ public int getCount() {
+ return countSeparatedItems() + countListItems();
+ }
+
+ @Override
+ public boolean isEnabled(int position) {
+ return getItem(position).isEnabled();
+ }
+
+ @Override
+ public boolean areAllItemsEnabled() {
+ return false;
+ }
+
+ @Override
+ public Action getItem(int position) {
+ int filteredPos = 0;
+ for (int i = 0; i < mItems.size(); i++) {
+ final Action action = mItems.get(i);
+ if (!shouldShowAction(action)) {
+ continue;
+ }
+ if (filteredPos == position) {
+ return action;
+ }
+ filteredPos++;
+ }
+
+ throw new IllegalArgumentException("position " + position
+ + " out of range of showable actions"
+ + ", filtered count=" + getCount()
+ + ", keyguardshowing=" + mKeyguardShowing
+ + ", provisioned=" + mDeviceProvisioned);
+ }
+
+ /**
+ * Get the row ID for an item
+ * @param position The position of the item within the adapter's data set
+ * @return
+ */
+ public long getItemId(int position) {
+ return position;
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ Action action = getItem(position);
+ View view = action.create(mContext, convertView, parent, LayoutInflater.from(mContext));
+ view.setOnClickListener(v -> onClickItem(position));
+ if (action instanceof LongPressAction) {
+ view.setOnLongClickListener(v -> onLongClickItem(position));
+ }
+ return view;
+ }
+
+ @Override
+ public boolean onLongClickItem(int position) {
+ final Action action = mAdapter.getItem(position);
+ if (action instanceof LongPressAction) {
+ if (mDialog != null) {
+ mDialog.dismiss();
+ } else {
+ Log.w(TAG, "Action long-clicked while mDialog is null.");
+ }
+ return ((LongPressAction) action).onLongPress();
+ }
+ return false;
+ }
+
+ @Override
+ public void onClickItem(int position) {
+ Action item = mAdapter.getItem(position);
+ if (!(item instanceof SilentModeTriStateAction)) {
+ if (mDialog != null) {
+ // don't dismiss the dialog if we're opening the power options menu
+ if (!(item instanceof PowerOptionsAction)) {
+ mDialog.dismiss();
+ }
+ } else {
+ Log.w(TAG, "Action clicked while mDialog is null.");
+ }
+ item.onPress();
+ }
+ }
+
+ @Override
+ public boolean shouldBeSeparated(int position) {
+ return getItem(position).shouldBeSeparated();
+ }
+ }
+
+ /**
+ * The adapter used for items in the overflow menu.
+ */
+ public class MyPowerOptionsAdapter extends BaseAdapter {
+ @Override
+ public int getCount() {
+ return mPowerItems.size();
+ }
+
+ @Override
+ public Action getItem(int position) {
+ return mPowerItems.get(position);
+ }
+
+ @Override
+ public long getItemId(int position) {
+ return position;
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ Action action = getItem(position);
+ if (action == null) {
+ Log.w(TAG, "No power options action found at position: " + position);
+ return null;
+ }
+ int viewLayoutResource = com.android.systemui.R.layout.global_actions_power_item;
+ View view = convertView != null ? convertView
+ : LayoutInflater.from(mContext).inflate(viewLayoutResource, parent, false);
+ view.setOnClickListener(v -> onClickItem(position));
+ if (action instanceof LongPressAction) {
+ view.setOnLongClickListener(v -> onLongClickItem(position));
+ }
+ ImageView icon = view.findViewById(R.id.icon);
+ TextView messageView = view.findViewById(R.id.message);
+ messageView.setSelected(true); // necessary for marquee to work
+
+ icon.setImageDrawable(action.getIcon(mContext));
+ icon.setScaleType(ScaleType.CENTER_CROP);
+
+ if (action.getMessage() != null) {
+ messageView.setText(action.getMessage());
+ } else {
+ messageView.setText(action.getMessageResId());
+ }
+ return view;
+ }
+
+ private boolean onLongClickItem(int position) {
+ final Action action = getItem(position);
+ if (action instanceof LongPressAction) {
+ if (mDialog != null) {
+ mDialog.dismiss();
+ } else {
+ Log.w(TAG, "Action long-clicked while mDialog is null.");
+ }
+ return ((LongPressAction) action).onLongPress();
+ }
+ return false;
+ }
+
+ private void onClickItem(int position) {
+ Action item = getItem(position);
+ if (!(item instanceof SilentModeTriStateAction)) {
+ if (mDialog != null) {
+ mDialog.dismiss();
+ } else {
+ Log.w(TAG, "Action clicked while mDialog is null.");
+ }
+ item.onPress();
+ }
+ }
+ }
+
+ /**
+ * The adapter used for items in the power options menu, triggered by the PowerOptionsAction.
+ */
+ public class MyOverflowAdapter extends BaseAdapter {
+ @Override
+ public int getCount() {
+ return mOverflowItems.size();
+ }
+
+ @Override
+ public Action getItem(int position) {
+ return mOverflowItems.get(position);
+ }
+
+ @Override
+ public long getItemId(int position) {
+ return position;
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ Action action = getItem(position);
+ if (action == null) {
+ Log.w(TAG, "No overflow action found at position: " + position);
+ return null;
+ }
+ int viewLayoutResource = com.android.systemui.R.layout.controls_more_item;
+ View view = convertView != null ? convertView
+ : LayoutInflater.from(mContext).inflate(viewLayoutResource, parent, false);
+ TextView textView = (TextView) view;
+ if (action.getMessageResId() != 0) {
+ textView.setText(action.getMessageResId());
+ } else {
+ textView.setText(action.getMessage());
+ }
+ return textView;
+ }
+
+ protected boolean onLongClickItem(int position) {
+ final Action action = getItem(position);
+ if (action instanceof LongPressAction) {
+ if (mDialog != null) {
+ mDialog.dismiss();
+ } else {
+ Log.w(TAG, "Action long-clicked while mDialog is null.");
+ }
+ return ((LongPressAction) action).onLongPress();
+ }
+ return false;
+ }
+
+ protected void onClickItem(int position) {
+ Action item = getItem(position);
+ if (!(item instanceof SilentModeTriStateAction)) {
+ if (mDialog != null) {
+ mDialog.dismiss();
+ } else {
+ Log.w(TAG, "Action clicked while mDialog is null.");
+ }
+ item.onPress();
+ }
+ }
+ }
+
+ // note: the scheme below made more sense when we were planning on having
+ // 8 different things in the global actions dialog. seems overkill with
+ // only 3 items now, but may as well keep this flexible approach so it will
+ // be easy should someone decide at the last minute to include something
+ // else, such as 'enable wifi', or 'enable bluetooth'
+
+ /**
+ * What each item in the global actions dialog must be able to support.
+ */
+ public interface Action {
+ /**
+ * @return Text that will be announced when dialog is created. null for none.
+ */
+ CharSequence getLabelForAccessibility(Context context);
+
+ /**
+ * Create the item's view
+ * @param context
+ * @param convertView
+ * @param parent
+ * @param inflater
+ * @return
+ */
+ View create(Context context, View convertView, ViewGroup parent, LayoutInflater inflater);
+
+ /**
+ * Handle a regular press
+ */
+ void onPress();
+
+ /**
+ * @return whether this action should appear in the dialog when the keygaurd is showing.
+ */
+ boolean showDuringKeyguard();
+
+ /**
+ * @return whether this action should appear in the dialog before the
+ * device is provisioned.f
+ */
+ boolean showBeforeProvisioning();
+
+ /**
+ * @return whether this action is enabled
+ */
+ boolean isEnabled();
+
+ /**
+ * @return whether this action should be in a separate section
+ */
+ default boolean shouldBeSeparated() {
+ return false;
+ }
+
+ /**
+ * Return the id of the message associated with this action, or 0 if it doesn't have one.
+ * @return
+ */
+ int getMessageResId();
+
+ /**
+ * Return the icon drawable for this action.
+ */
+ Drawable getIcon(Context context);
+
+ /**
+ * Return the message associated with this action, or null if it doesn't have one.
+ * @return
+ */
+ CharSequence getMessage();
+
+ /**
+ * @return whether the action should be visible
+ */
+ default boolean shouldShow() {
+ return true;
+ }
+ }
+
+ /**
+ * An action that also supports long press.
+ */
+ private interface LongPressAction extends Action {
+ boolean onLongPress();
+ }
+
+ /**
+ * A single press action maintains no state, just responds to a press and takes an action.
+ */
+
+ private abstract class SinglePressAction implements Action {
+ private final int mIconResId;
+ private final Drawable mIcon;
+ private final int mMessageResId;
+ private final CharSequence mMessage;
+
+ protected SinglePressAction(int iconResId, int messageResId) {
+ mIconResId = iconResId;
+ mMessageResId = messageResId;
+ mMessage = null;
+ mIcon = null;
+ }
+
+ protected SinglePressAction(int iconResId, Drawable icon, CharSequence message) {
+ mIconResId = iconResId;
+ mMessageResId = 0;
+ mMessage = message;
+ mIcon = icon;
+ }
+
+ public boolean isEnabled() {
+ return true;
+ }
+
+ public String getStatus() {
+ return null;
+ }
+
+ public abstract void onPress();
+
+ public CharSequence getLabelForAccessibility(Context context) {
+ if (mMessage != null) {
+ return mMessage;
+ } else {
+ return context.getString(mMessageResId);
+ }
+ }
+
+ public int getMessageResId() {
+ return mMessageResId;
+ }
+
+ public CharSequence getMessage() {
+ return mMessage;
+ }
+
+ @Override
+ public Drawable getIcon(Context context) {
+ if (mIcon != null) {
+ return mIcon;
+ } else {
+ return context.getDrawable(mIconResId);
+ }
+ }
+
+ public View create(
+ Context context, View convertView, ViewGroup parent, LayoutInflater inflater) {
+ View v = inflater.inflate(getGridItemLayoutResource(), parent, false /* attach */);
+ // ConstraintLayout flow needs an ID to reference
+ v.setId(View.generateViewId());
+
+ ImageView icon = v.findViewById(R.id.icon);
+ TextView messageView = v.findViewById(R.id.message);
+ messageView.setSelected(true); // necessary for marquee to work
+
+ icon.setImageDrawable(getIcon(context));
+ icon.setScaleType(ScaleType.CENTER_CROP);
+
+ if (mMessage != null) {
+ messageView.setText(mMessage);
+ } else {
+ messageView.setText(mMessageResId);
+ }
+
+ return v;
+ }
+ }
+
+ protected int getGridItemLayoutResource() {
+ return com.android.systemui.R.layout.global_actions_grid_item_lite;
+ }
+
+ private enum ToggleState {
+ Off(false),
+ TurningOn(true),
+ TurningOff(true),
+ On(false);
+
+ private final boolean mInTransition;
+
+ ToggleState(boolean intermediate) {
+ mInTransition = intermediate;
+ }
+
+ public boolean inTransition() {
+ return mInTransition;
+ }
+ }
+
+ /**
+ * A toggle action knows whether it is on or off, and displays an icon and status message
+ * accordingly.
+ */
+ private abstract class ToggleAction implements Action {
+
+ protected ToggleState mState = ToggleState.Off;
+
+ // prefs
+ protected int mEnabledIconResId;
+ protected int mDisabledIconResid;
+ protected int mMessageResId;
+ protected int mEnabledStatusMessageResId;
+ protected int mDisabledStatusMessageResId;
+
+ /**
+ * @param enabledIconResId The icon for when this action is on.
+ * @param disabledIconResid The icon for when this action is off.
+ * @param message The general information message, e.g 'Silent Mode'
+ * @param enabledStatusMessageResId The on status message, e.g 'sound disabled'
+ * @param disabledStatusMessageResId The off status message, e.g. 'sound enabled'
+ */
+ ToggleAction(int enabledIconResId,
+ int disabledIconResid,
+ int message,
+ int enabledStatusMessageResId,
+ int disabledStatusMessageResId) {
+ mEnabledIconResId = enabledIconResId;
+ mDisabledIconResid = disabledIconResid;
+ mMessageResId = message;
+ mEnabledStatusMessageResId = enabledStatusMessageResId;
+ mDisabledStatusMessageResId = disabledStatusMessageResId;
+ }
+
+ /**
+ * Override to make changes to resource IDs just before creating the View.
+ */
+ void willCreate() {
+
+ }
+
+ @Override
+ public CharSequence getLabelForAccessibility(Context context) {
+ return context.getString(mMessageResId);
+ }
+
+ private boolean isOn() {
+ return mState == ToggleState.On || mState == ToggleState.TurningOn;
+ }
+
+ @Override
+ public CharSequence getMessage() {
+ return null;
+ }
+ @Override
+ public int getMessageResId() {
+ return isOn() ? mEnabledStatusMessageResId : mDisabledStatusMessageResId;
+ }
+
+ private int getIconResId() {
+ return isOn() ? mEnabledIconResId : mDisabledIconResid;
+ }
+
+ @Override
+ public Drawable getIcon(Context context) {
+ return context.getDrawable(getIconResId());
+ }
+
+ public View create(Context context, View convertView, ViewGroup parent,
+ LayoutInflater inflater) {
+ willCreate();
+
+ View v = inflater.inflate(com.android.systemui.R.layout.global_actions_grid_item_v2,
+ parent, false /* attach */);
+ ViewGroup.LayoutParams p = v.getLayoutParams();
+ p.width = WRAP_CONTENT;
+ v.setLayoutParams(p);
+
+ ImageView icon = (ImageView) v.findViewById(R.id.icon);
+ TextView messageView = (TextView) v.findViewById(R.id.message);
+ final boolean enabled = isEnabled();
+
+ if (messageView != null) {
+ messageView.setText(getMessageResId());
+ messageView.setEnabled(enabled);
+ messageView.setSelected(true); // necessary for marquee to work
+ }
+
+ if (icon != null) {
+ icon.setImageDrawable(context.getDrawable(getIconResId()));
+ icon.setEnabled(enabled);
+ }
+
+ v.setEnabled(enabled);
+
+ return v;
+ }
+
+ public final void onPress() {
+ if (mState.inTransition()) {
+ Log.w(TAG, "shouldn't be able to toggle when in transition");
+ return;
+ }
+
+ final boolean nowOn = !(mState == ToggleState.On);
+ onToggle(nowOn);
+ changeStateFromPress(nowOn);
+ }
+
+ public boolean isEnabled() {
+ return !mState.inTransition();
+ }
+
+ /**
+ * Implementations may override this if their state can be in on of the intermediate states
+ * until some notification is received (e.g airplane mode is 'turning off' until we know the
+ * wireless connections are back online
+ *
+ * @param buttonOn Whether the button was turned on or off
+ */
+ protected void changeStateFromPress(boolean buttonOn) {
+ mState = buttonOn ? ToggleState.On : ToggleState.Off;
+ }
+
+ abstract void onToggle(boolean on);
+
+ public void updateState(ToggleState state) {
+ mState = state;
+ }
+ }
+
+ private class AirplaneModeAction extends ToggleAction {
+ AirplaneModeAction() {
+ super(
+ R.drawable.ic_lock_airplane_mode,
+ R.drawable.ic_lock_airplane_mode_off,
+ R.string.global_actions_toggle_airplane_mode,
+ R.string.global_actions_airplane_mode_on_status,
+ R.string.global_actions_airplane_mode_off_status);
+ }
+
+ void onToggle(boolean on) {
+ if (mHasTelephony && TelephonyProperties.in_ecm_mode().orElse(false)) {
+ mIsWaitingForEcmExit = true;
+ // Launch ECM exit dialog
+ Intent ecmDialogIntent =
+ new Intent(TelephonyManager.ACTION_SHOW_NOTICE_ECM_BLOCK_OTHERS, null);
+ ecmDialogIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ mContext.startActivity(ecmDialogIntent);
+ } else {
+ changeAirplaneModeSystemSetting(on);
+ }
+ }
+
+ @Override
+ protected void changeStateFromPress(boolean buttonOn) {
+ if (!mHasTelephony) return;
+
+ // In ECM mode airplane state cannot be changed
+ if (!TelephonyProperties.in_ecm_mode().orElse(false)) {
+ mState = buttonOn ? ToggleState.TurningOn : ToggleState.TurningOff;
+ mAirplaneState = mState;
+ }
+ }
+
+ public boolean showDuringKeyguard() {
+ return true;
+ }
+
+ public boolean showBeforeProvisioning() {
+ return false;
+ }
+ }
+
+ private class SilentModeToggleAction extends ToggleAction {
+ SilentModeToggleAction() {
+ super(R.drawable.ic_audio_vol_mute,
+ R.drawable.ic_audio_vol,
+ R.string.global_action_toggle_silent_mode,
+ R.string.global_action_silent_mode_on_status,
+ R.string.global_action_silent_mode_off_status);
+ }
+
+ void onToggle(boolean on) {
+ if (on) {
+ mAudioManager.setRingerMode(AudioManager.RINGER_MODE_SILENT);
+ } else {
+ mAudioManager.setRingerMode(AudioManager.RINGER_MODE_NORMAL);
+ }
+ }
+
+ public boolean showDuringKeyguard() {
+ return true;
+ }
+
+ public boolean showBeforeProvisioning() {
+ return false;
+ }
+ }
+
+ private static class SilentModeTriStateAction implements Action, View.OnClickListener {
+
+ private static final int[] ITEM_IDS = {R.id.option1, R.id.option2, R.id.option3};
+
+ private final AudioManager mAudioManager;
+ private final Handler mHandler;
+
+ SilentModeTriStateAction(AudioManager audioManager, Handler handler) {
+ mAudioManager = audioManager;
+ mHandler = handler;
+ }
+
+ private int ringerModeToIndex(int ringerMode) {
+ // They just happen to coincide
+ return ringerMode;
+ }
+
+ private int indexToRingerMode(int index) {
+ // They just happen to coincide
+ return index;
+ }
+
+ @Override
+ public CharSequence getLabelForAccessibility(Context context) {
+ return null;
+ }
+
+ @Override
+ public int getMessageResId() {
+ return 0;
+ }
+
+ @Override
+ public CharSequence getMessage() {
+ return null;
+ }
+
+ @Override
+ public Drawable getIcon(Context context) {
+ return null;
+ }
+
+
+ public View create(Context context, View convertView, ViewGroup parent,
+ LayoutInflater inflater) {
+ View v = inflater.inflate(R.layout.global_actions_silent_mode, parent, false);
+
+ int selectedIndex = ringerModeToIndex(mAudioManager.getRingerMode());
+ for (int i = 0; i < 3; i++) {
+ View itemView = v.findViewById(ITEM_IDS[i]);
+ itemView.setSelected(selectedIndex == i);
+ // Set up click handler
+ itemView.setTag(i);
+ itemView.setOnClickListener(this);
+ }
+ return v;
+ }
+
+ public void onPress() {
+ }
+
+ public boolean showDuringKeyguard() {
+ return true;
+ }
+
+ public boolean showBeforeProvisioning() {
+ return false;
+ }
+
+ public boolean isEnabled() {
+ return true;
+ }
+
+ void willCreate() {
+ }
+
+ public void onClick(View v) {
+ if (!(v.getTag() instanceof Integer)) return;
+
+ int index = (Integer) v.getTag();
+ mAudioManager.setRingerMode(indexToRingerMode(index));
+ mHandler.sendEmptyMessageDelayed(MESSAGE_DISMISS, DIALOG_DISMISS_DELAY);
+ }
+ }
+
+ private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action)
+ || Intent.ACTION_SCREEN_OFF.equals(action)) {
+ String reason = intent.getStringExtra(SYSTEM_DIALOG_REASON_KEY);
+ if (!SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS.equals(reason)) {
+ mHandler.sendMessage(mHandler.obtainMessage(MESSAGE_DISMISS, reason));
+ }
+ } else if (TelephonyManager.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED.equals(action)) {
+ // Airplane mode can be changed after ECM exits if airplane toggle button
+ // is pressed during ECM mode
+ if (!(intent.getBooleanExtra(TelephonyManager.EXTRA_PHONE_IN_ECM_STATE, false))
+ && mIsWaitingForEcmExit) {
+ mIsWaitingForEcmExit = false;
+ changeAirplaneModeSystemSetting(true);
+ }
+ }
+ }
+ };
+
+ private final TelephonyCallback.ServiceStateListener mPhoneStateListener =
+ new TelephonyCallback.ServiceStateListener() {
+ @Override
+ public void onServiceStateChanged(ServiceState serviceState) {
+ if (!mHasTelephony) return;
+ if (mAirplaneModeOn == null) {
+ Log.d(TAG, "Service changed before actions created");
+ return;
+ }
+ final boolean inAirplaneMode = serviceState.getState() == ServiceState.STATE_POWER_OFF;
+ mAirplaneState = inAirplaneMode ? ToggleState.On : ToggleState.Off;
+ mAirplaneModeOn.updateState(mAirplaneState);
+ mAdapter.notifyDataSetChanged();
+ mOverflowAdapter.notifyDataSetChanged();
+ mPowerAdapter.notifyDataSetChanged();
+ }
+ };
+
+ private ContentObserver mAirplaneModeObserver = new ContentObserver(mMainHandler) {
+ @Override
+ public void onChange(boolean selfChange) {
+ onAirplaneModeChanged();
+ }
+ };
+
+ private static final int MESSAGE_DISMISS = 0;
+ private static final int MESSAGE_REFRESH = 1;
+ private static final int DIALOG_DISMISS_DELAY = 300; // ms
+ private static final int DIALOG_PRESS_DELAY = 850; // ms
+
+ @VisibleForTesting void setZeroDialogPressDelayForTesting() {
+ mDialogPressDelay = 0; // ms
+ }
+
+ private Handler mHandler = new Handler() {
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MESSAGE_DISMISS:
+ if (mDialog != null) {
+ if (SYSTEM_DIALOG_REASON_DREAM.equals(msg.obj)) {
+ mDialog.completeDismiss();
+ } else {
+ mDialog.dismiss();
+ }
+ mDialog = null;
+ }
+ break;
+ case MESSAGE_REFRESH:
+ refreshSilentMode();
+ mAdapter.notifyDataSetChanged();
+ break;
+ }
+ }
+ };
+
+ private void onAirplaneModeChanged() {
+ // Let the service state callbacks handle the state.
+ if (mHasTelephony || mAirplaneModeOn == null) return;
+
+ boolean airplaneModeOn = mGlobalSettings.getInt(
+ Settings.Global.AIRPLANE_MODE_ON,
+ 0) == 1;
+ mAirplaneState = airplaneModeOn ? ToggleState.On : ToggleState.Off;
+ mAirplaneModeOn.updateState(mAirplaneState);
+ }
+
+ /**
+ * Change the airplane mode system setting
+ */
+ private void changeAirplaneModeSystemSetting(boolean on) {
+ mGlobalSettings.putInt(Settings.Global.AIRPLANE_MODE_ON, on ? 1 : 0);
+ Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+ intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
+ intent.putExtra("state", on);
+ mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
+ if (!mHasTelephony) {
+ mAirplaneState = on ? ToggleState.On : ToggleState.Off;
+ }
+ }
+
+ @NonNull
+ @Override
+ public Lifecycle getLifecycle() {
+ return mLifecycle;
+ }
+
+ @VisibleForTesting
+ static class ActionsDialogLite extends Dialog implements DialogInterface,
+ ColorExtractor.OnColorsChangedListener {
+
+ protected final Context mContext;
+ protected MultiListLayout mGlobalActionsLayout;
+ protected final MyAdapter mAdapter;
+ protected final MyOverflowAdapter mOverflowAdapter;
+ protected final MyPowerOptionsAdapter mPowerOptionsAdapter;
+ protected final IStatusBarService mStatusBarService;
+ protected final IBinder mToken = new Binder();
+ protected Drawable mBackgroundDrawable;
+ protected final SysuiColorExtractor mColorExtractor;
+ private boolean mKeyguardShowing;
+ protected boolean mShowing;
+ protected float mScrimAlpha;
+ protected final NotificationShadeWindowController mNotificationShadeWindowController;
+ protected final NotificationShadeDepthController mDepthController;
+ protected final SysUiState mSysUiState;
+ private ListPopupWindow mOverflowPopup;
+ private Dialog mPowerOptionsDialog;
+ protected final Runnable mOnRotateCallback;
+
+ protected ViewGroup mContainer;
+
+ ActionsDialogLite(Context context, int themeRes, MyAdapter adapter,
+ MyOverflowAdapter overflowAdapter,
+ NotificationShadeDepthController depthController,
+ SysuiColorExtractor sysuiColorExtractor, IStatusBarService statusBarService,
+ NotificationShadeWindowController notificationShadeWindowController,
+ SysUiState sysuiState, Runnable onRotateCallback, boolean keyguardShowing,
+ MyPowerOptionsAdapter powerAdapter) {
+ super(context, themeRes);
+ mContext = context;
+ mAdapter = adapter;
+ mOverflowAdapter = overflowAdapter;
+ mPowerOptionsAdapter = powerAdapter;
+ mDepthController = depthController;
+ mColorExtractor = sysuiColorExtractor;
+ mStatusBarService = statusBarService;
+ mNotificationShadeWindowController = notificationShadeWindowController;
+ mSysUiState = sysuiState;
+ mOnRotateCallback = onRotateCallback;
+ mKeyguardShowing = keyguardShowing;
+
+ // Window initialization
+ Window window = getWindow();
+ window.requestFeature(Window.FEATURE_NO_TITLE);
+ // Inflate the decor view, so the attributes below are not overwritten by the theme.
+ window.getDecorView();
+ window.getAttributes().systemUiVisibility |= View.SYSTEM_UI_FLAG_LAYOUT_STABLE
+ | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION;
+ window.setLayout(MATCH_PARENT, WRAP_CONTENT);
+ window.addFlags(
+ WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
+ | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR
+ | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
+ | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
+ window.setType(WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY);
+ window.getAttributes().setFitInsetsTypes(0 /* types */);
+ setTitle(R.string.global_actions);
+
+ initializeLayout();
+ }
+
+ private ListPopupWindow createPowerOverflowPopup() {
+ GlobalActionsPopupMenu popup = new GlobalActionsPopupMenu(
+ new ContextThemeWrapper(
+ mContext,
+ com.android.systemui.R.style.Control_ListPopupWindow
+ ), false /* isDropDownMode */);
+ popup.setOnItemClickListener(
+ (parent, view, position, id) -> mOverflowAdapter.onClickItem(position));
+ popup.setOnItemLongClickListener(
+ (parent, view, position, id) -> mOverflowAdapter.onLongClickItem(position));
+ View overflowButton =
+ findViewById(com.android.systemui.R.id.global_actions_overflow_button);
+ popup.setAnchorView(overflowButton);
+ popup.setAdapter(mOverflowAdapter);
+ return popup;
+ }
+
+ public void showPowerOptionsMenu() {
+ mPowerOptionsDialog = GlobalActionsPowerDialog.create(mContext, mPowerOptionsAdapter);
+ mPowerOptionsDialog.show();
+ }
+
+ protected void showPowerOverflowMenu() {
+ mOverflowPopup = createPowerOverflowPopup();
+ mOverflowPopup.show();
+ }
+
+ protected int getLayoutResource() {
+ return com.android.systemui.R.layout.global_actions_grid_lite;
+ }
+
+ protected void initializeLayout() {
+ setContentView(getLayoutResource());
+ fixNavBarClipping();
+
+ mGlobalActionsLayout = findViewById(com.android.systemui.R.id.global_actions_view);
+ mGlobalActionsLayout.setListViewAccessibilityDelegate(new View.AccessibilityDelegate() {
+ @Override
+ public boolean dispatchPopulateAccessibilityEvent(
+ View host, AccessibilityEvent event) {
+ // Populate the title here, just as Activity does
+ event.getText().add(mContext.getString(R.string.global_actions));
+ return true;
+ }
+ });
+ mGlobalActionsLayout.setRotationListener(this::onRotate);
+ mGlobalActionsLayout.setAdapter(mAdapter);
+ mContainer = findViewById(com.android.systemui.R.id.global_actions_container);
+
+ View overflowButton = findViewById(
+ com.android.systemui.R.id.global_actions_overflow_button);
+ if (overflowButton != null) {
+ if (mOverflowAdapter.getCount() > 0) {
+ overflowButton.setOnClickListener((view) -> showPowerOverflowMenu());
+ LinearLayout.LayoutParams params =
+ (LinearLayout.LayoutParams) mGlobalActionsLayout.getLayoutParams();
+ params.setMarginEnd(0);
+ mGlobalActionsLayout.setLayoutParams(params);
+ } else {
+ overflowButton.setVisibility(View.GONE);
+ LinearLayout.LayoutParams params =
+ (LinearLayout.LayoutParams) mGlobalActionsLayout.getLayoutParams();
+ params.setMarginEnd(mContext.getResources().getDimensionPixelSize(
+ com.android.systemui.R.dimen.global_actions_side_margin));
+ mGlobalActionsLayout.setLayoutParams(params);
+ }
+ }
+
+ if (mBackgroundDrawable == null) {
+ mBackgroundDrawable = new ScrimDrawable();
+ mScrimAlpha = 1.0f;
+ }
+ }
+
+ protected void fixNavBarClipping() {
+ ViewGroup content = findViewById(android.R.id.content);
+ content.setClipChildren(false);
+ content.setClipToPadding(false);
+ ViewGroup contentParent = (ViewGroup) content.getParent();
+ contentParent.setClipChildren(false);
+ contentParent.setClipToPadding(false);
+ }
+
+ @Override
+ protected void onStart() {
+ super.setCanceledOnTouchOutside(true);
+ super.onStart();
+ mGlobalActionsLayout.updateList();
+
+ if (mBackgroundDrawable instanceof ScrimDrawable) {
+ mColorExtractor.addOnColorsChangedListener(this);
+ GradientColors colors = mColorExtractor.getNeutralColors();
+ updateColors(colors, false /* animate */);
+ }
+ }
+
+ /**
+ * Updates background and system bars according to current GradientColors.
+ *
+ * @param colors Colors and hints to use.
+ * @param animate Interpolates gradient if true, just sets otherwise.
+ */
+ private void updateColors(GradientColors colors, boolean animate) {
+ if (!(mBackgroundDrawable instanceof ScrimDrawable)) {
+ return;
+ }
+ ((ScrimDrawable) mBackgroundDrawable).setColor(Color.BLACK, animate);
+ View decorView = getWindow().getDecorView();
+ if (colors.supportsDarkText()) {
+ decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR
+ | View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
+ } else {
+ decorView.setSystemUiVisibility(0);
+ }
+ }
+
+ @Override
+ protected void onStop() {
+ super.onStop();
+ mColorExtractor.removeOnColorsChangedListener(this);
+ }
+
+ @Override
+ public void show() {
+ super.show();
+ // split this up so we can override but still call Dialog.show
+ showDialog();
+ }
+
+ protected void showDialog() {
+ mShowing = true;
+ mNotificationShadeWindowController.setRequestTopUi(true, TAG);
+ mSysUiState.setFlag(SYSUI_STATE_GLOBAL_ACTIONS_SHOWING, true)
+ .commitUpdate(mContext.getDisplayId());
+
+ ViewGroup root = (ViewGroup) mGlobalActionsLayout.getRootView();
+ root.setOnApplyWindowInsetsListener((v, windowInsets) -> {
+ root.setPadding(windowInsets.getStableInsetLeft(),
+ windowInsets.getStableInsetTop(),
+ windowInsets.getStableInsetRight(),
+ windowInsets.getStableInsetBottom());
+ return WindowInsets.CONSUMED;
+ });
+
+ mBackgroundDrawable.setAlpha(0);
+ float xOffset = mGlobalActionsLayout.getAnimationOffsetX();
+ ObjectAnimator alphaAnimator =
+ ObjectAnimator.ofFloat(mContainer, "alpha", 0f, 1f);
+ alphaAnimator.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
+ alphaAnimator.setDuration(183);
+ alphaAnimator.addUpdateListener((animation) -> {
+ float animatedValue = animation.getAnimatedFraction();
+ int alpha = (int) (animatedValue * mScrimAlpha * 255);
+ mBackgroundDrawable.setAlpha(alpha);
+ mDepthController.updateGlobalDialogVisibility(animatedValue, mGlobalActionsLayout);
+ });
+
+ ObjectAnimator xAnimator =
+ ObjectAnimator.ofFloat(mContainer, "translationX", xOffset, 0f);
+ xAnimator.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
+ xAnimator.setDuration(350);
+
+ AnimatorSet animatorSet = new AnimatorSet();
+ animatorSet.playTogether(alphaAnimator, xAnimator);
+ animatorSet.start();
+ }
+
+ @Override
+ public void dismiss() {
+ dismissWithAnimation(() -> {
+ dismissInternal();
+ });
+ }
+
+ protected void dismissInternal() {
+ mContainer.setTranslationX(0);
+ ObjectAnimator alphaAnimator =
+ ObjectAnimator.ofFloat(mContainer, "alpha", 1f, 0f);
+ alphaAnimator.setInterpolator(Interpolators.FAST_OUT_LINEAR_IN);
+ alphaAnimator.setDuration(233);
+ alphaAnimator.addUpdateListener((animation) -> {
+ float animatedValue = 1f - animation.getAnimatedFraction();
+ int alpha = (int) (animatedValue * mScrimAlpha * 255);
+ mBackgroundDrawable.setAlpha(alpha);
+ mDepthController.updateGlobalDialogVisibility(animatedValue, mGlobalActionsLayout);
+ });
+
+ float xOffset = mGlobalActionsLayout.getAnimationOffsetX();
+ ObjectAnimator xAnimator =
+ ObjectAnimator.ofFloat(mContainer, "translationX", 0f, xOffset);
+ xAnimator.setInterpolator(Interpolators.FAST_OUT_LINEAR_IN);
+ xAnimator.setDuration(350);
+
+ AnimatorSet animatorSet = new AnimatorSet();
+ animatorSet.playTogether(alphaAnimator, xAnimator);
+ animatorSet.addListener(new AnimatorListenerAdapter() {
+ public void onAnimationEnd(Animator animation) {
+ completeDismiss();
+ }
+ });
+
+ animatorSet.start();
+
+ // close first, as popup windows will not fade during the animation
+ dismissOverflow(false);
+ dismissPowerOptions(false);
+ }
+
+ void dismissWithAnimation(Runnable animation) {
+ if (!mShowing) {
+ return;
+ }
+ mShowing = false;
+ animation.run();
+ }
+
+ protected void completeDismiss() {
+ mShowing = false;
+ dismissOverflow(true);
+ dismissPowerOptions(true);
+ mNotificationShadeWindowController.setRequestTopUi(false, TAG);
+ mDepthController.updateGlobalDialogVisibility(0, null /* view */);
+ mSysUiState.setFlag(SYSUI_STATE_GLOBAL_ACTIONS_SHOWING, false)
+ .commitUpdate(mContext.getDisplayId());
+ super.dismiss();
+ }
+
+ protected final void dismissOverflow(boolean immediate) {
+ if (mOverflowPopup != null) {
+ if (immediate) {
+ mOverflowPopup.dismissImmediate();
+ } else {
+ mOverflowPopup.dismiss();
+ }
+ }
+ }
+
+ protected final void dismissPowerOptions(boolean immediate) {
+ if (mPowerOptionsDialog != null) {
+ if (immediate) {
+ mPowerOptionsDialog.dismiss();
+ } else {
+ mPowerOptionsDialog.dismiss();
+ }
+ }
+ }
+
+ protected final void setRotationSuggestionsEnabled(boolean enabled) {
+ try {
+ final int userId = Binder.getCallingUserHandle().getIdentifier();
+ final int what = enabled
+ ? StatusBarManager.DISABLE2_NONE
+ : StatusBarManager.DISABLE2_ROTATE_SUGGESTIONS;
+ mStatusBarService.disable2ForUser(what, mToken, mContext.getPackageName(), userId);
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
+ }
+ }
+
+ @Override
+ public void onColorsChanged(ColorExtractor extractor, int which) {
+ if (mKeyguardShowing) {
+ if ((WallpaperManager.FLAG_LOCK & which) != 0) {
+ updateColors(extractor.getColors(WallpaperManager.FLAG_LOCK),
+ true /* animate */);
+ }
+ } else {
+ if ((WallpaperManager.FLAG_SYSTEM & which) != 0) {
+ updateColors(extractor.getColors(WallpaperManager.FLAG_SYSTEM),
+ true /* animate */);
+ }
+ }
+ }
+
+ public void setKeyguardShowing(boolean keyguardShowing) {
+ mKeyguardShowing = keyguardShowing;
+ }
+
+ public void refreshDialog() {
+ // ensure dropdown menus are dismissed before re-initializing the dialog
+ dismissOverflow(true);
+ dismissPowerOptions(true);
+
+ // re-create dialog
+ initializeLayout();
+ mGlobalActionsLayout.updateList();
+ }
+
+ public void onRotate(int from, int to) {
+ if (mShowing) {
+ mOnRotateCallback.run();
+ refreshDialog();
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsLayoutLite.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsLayoutLite.java
new file mode 100644
index 000000000000..eb4cd6b449a2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsLayoutLite.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.globalactions;
+
+import static com.android.systemui.util.leak.RotationUtils.ROTATION_NONE;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.constraintlayout.helper.widget.Flow;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.HardwareBgDrawable;
+import com.android.systemui.R;
+
+/**
+ * ConstraintLayout implementation of the button layout created by the global actions dialog.
+ */
+public class GlobalActionsLayoutLite extends GlobalActionsLayout {
+ private final int mMaxColumns;
+ private final int mMaxRows;
+
+ public GlobalActionsLayoutLite(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ mMaxColumns = getResources().getInteger(
+ com.android.systemui.R.integer.power_menu_lite_max_columns);
+ mMaxRows = getResources().getInteger(
+ com.android.systemui.R.integer.power_menu_lite_max_rows);
+ }
+
+ @VisibleForTesting
+ @Override
+ protected boolean shouldReverseListItems() {
+ // Handled in XML
+ return false;
+ }
+
+ @Override
+ protected HardwareBgDrawable getBackgroundDrawable(int backgroundColor) {
+ return null;
+ }
+
+ @Override
+ public void onUpdateList() {
+ super.onUpdateList();
+ int nElementsWrap = (getCurrentRotation() == ROTATION_NONE) ? mMaxColumns : mMaxRows;
+ int nChildren = getListView().getChildCount() - 1; // don't count flow element
+ if (getCurrentRotation() != ROTATION_NONE && nChildren > mMaxRows) {
+ // up to 4 elements can fit in a row in landscape, otherwise limit for balance
+ nElementsWrap -= 1;
+ }
+ Flow flow = findViewById(R.id.list_flow);
+ flow.setMaxElementsWrap(nElementsWrap);
+ }
+
+ @Override
+ protected void addToListView(View v, boolean reverse) {
+ super.addToListView(v, reverse);
+ Flow flow = findViewById(R.id.list_flow);
+ flow.addView(v);
+ }
+
+ @Override
+ protected void removeAllListViews() {
+ View flow = findViewById(R.id.list_flow);
+ super.removeAllListViews();
+
+ // Add flow element back after clearing the list view
+ super.addToListView(flow, false);
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ super.onLayout(changed, left, top, right, bottom);
+ boolean anyTruncated = false;
+ ViewGroup listView = getListView();
+
+ // Check to see if any of the GlobalActionsItems have had their messages truncated
+ for (int i = 0; i < listView.getChildCount(); i++) {
+ View child = listView.getChildAt(i);
+ if (child instanceof GlobalActionsItem) {
+ GlobalActionsItem item = (GlobalActionsItem) child;
+ anyTruncated = anyTruncated || item.isTruncated();
+ }
+ }
+ // If any of the items have been truncated, set the all to single-line marquee
+ if (anyTruncated) {
+ for (int i = 0; i < listView.getChildCount(); i++) {
+ View child = listView.getChildAt(i);
+ if (child instanceof GlobalActionsItem) {
+ GlobalActionsItem item = (GlobalActionsItem) child;
+ item.setMarquee(true);
+ }
+ }
+ }
+ }
+
+ @VisibleForTesting
+ protected float getGridItemSize() {
+ return getContext().getResources().getDimension(R.dimen.global_actions_grid_item_height);
+ }
+
+ @VisibleForTesting
+ protected float getAnimationDistance() {
+ return getGridItemSize() / 2;
+ }
+
+ @Override
+ public float getAnimationOffsetX() {
+ return getAnimationDistance();
+ }
+
+ @Override
+ public float getAnimationOffsetY() {
+ return 0f;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
index a3ff3753cbf9..33681c8d03e5 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
@@ -1,6 +1,7 @@
package com.android.systemui.media
import android.animation.ArgbEvaluator
+import android.app.smartspace.SmartspaceTarget
import android.content.Context
import android.content.Intent
import android.content.res.ColorStateList
@@ -201,9 +202,18 @@ class MediaCarouselController @Inject constructor(
}
}
+ override fun onSmartspaceMediaDataLoaded(key: String, data: SmartspaceTarget) {
+ Log.d(TAG, "My Smartspace media update is here")
+ addOrUpdateSmartspaceMediaRecommendations(key, data)
+ }
+
override fun onMediaDataRemoved(key: String) {
removePlayer(key)
}
+
+ override fun onSmartspaceMediaDataRemoved(key: String) {
+ Log.d(TAG, "My Smartspace media removal request is received")
+ }
})
mediaFrame.addOnLayoutChangeListener { _, _, _, _, _, _, _, _, _ ->
// The pageIndicator is not laid out yet when we get the current state update,
@@ -291,6 +301,10 @@ class MediaCarouselController @Inject constructor(
}
}
+ private fun addOrUpdateSmartspaceMediaRecommendations(key: String, data: SmartspaceTarget) {
+ // TODO(b/182813345): Add Smartspace media recommendation view.
+ }
+
private fun removePlayer(key: String, dismissMediaData: Boolean = true) {
val removed = MediaPlayerData.removeMediaPlayer(key)
removed?.apply {
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
index 743ac86cbdcb..d3ae9320d91d 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
@@ -16,6 +16,7 @@
package com.android.systemui.media;
+import static android.app.Notification.safeCharSequence;
import static android.provider.Settings.ACTION_MEDIA_CONTROLS_SETTINGS;
import android.app.PendingIntent;
@@ -266,7 +267,7 @@ public class MediaControlPanel {
// Song name
TextView titleText = mViewHolder.getTitleText();
- titleText.setText(data.getSong());
+ titleText.setText(safeCharSequence(data.getSong()));
// App title
TextView appName = mViewHolder.getAppName();
@@ -277,7 +278,7 @@ public class MediaControlPanel {
// Artist name
TextView artistText = mViewHolder.getArtistText();
- artistText.setText(data.getArtist());
+ artistText.setText(safeCharSequence(data.getArtist()));
// Transfer chip
mViewHolder.getSeamless().setVisibility(View.VISIBLE);
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataCombineLatest.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDataCombineLatest.kt
index aa3699e9a22b..2c094b885032 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaDataCombineLatest.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataCombineLatest.kt
@@ -16,6 +16,7 @@
package com.android.systemui.media
+import android.app.smartspace.SmartspaceTarget
import javax.inject.Inject
/**
@@ -37,10 +38,18 @@ class MediaDataCombineLatest @Inject constructor() : MediaDataManager.Listener,
}
}
+ override fun onSmartspaceMediaDataLoaded(key: String, data: SmartspaceTarget) {
+ listeners.toSet().forEach { it.onSmartspaceMediaDataLoaded(key, data) }
+ }
+
override fun onMediaDataRemoved(key: String) {
remove(key)
}
+ override fun onSmartspaceMediaDataRemoved(key: String) {
+ listeners.toSet().forEach { it.onSmartspaceMediaDataRemoved(key) }
+ }
+
override fun onMediaDeviceChanged(
key: String,
oldKey: String?,
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataFilter.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDataFilter.kt
index 1f580a953d09..aab27473d3e8 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaDataFilter.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataFilter.kt
@@ -16,6 +16,7 @@
package com.android.systemui.media
+import android.app.smartspace.SmartspaceTarget
import android.util.Log
import com.android.internal.annotations.VisibleForTesting
import com.android.systemui.broadcast.BroadcastDispatcher
@@ -82,6 +83,10 @@ class MediaDataFilter @Inject constructor(
}
}
+ override fun onSmartspaceMediaDataLoaded(key: String, data: SmartspaceTarget) {
+ listeners.forEach { it.onSmartspaceMediaDataLoaded(key, data) }
+ }
+
override fun onMediaDataRemoved(key: String) {
allEntries.remove(key)
userEntries.remove(key)?.let {
@@ -92,6 +97,10 @@ class MediaDataFilter @Inject constructor(
}
}
+ override fun onSmartspaceMediaDataRemoved(key: String) {
+ listeners.forEach { it.onSmartspaceMediaDataRemoved(key) }
+ }
+
@VisibleForTesting
internal fun handleUserSwitched(id: Int) {
// If the user changes, remove all current MediaData objects and inform listeners
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
index 41c9daedf2d4..dfd588d0406e 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
@@ -18,6 +18,10 @@ package com.android.systemui.media
import android.app.Notification
import android.app.PendingIntent
+import android.app.smartspace.SmartspaceConfig
+import android.app.smartspace.SmartspaceManager
+import android.app.smartspace.SmartspaceSession
+import android.app.smartspace.SmartspaceTarget
import android.content.BroadcastReceiver
import android.content.ContentResolver
import android.content.Context
@@ -33,6 +37,7 @@ import android.media.MediaMetadata
import android.media.session.MediaController
import android.media.session.MediaSession
import android.net.Uri
+import android.os.Parcelable
import android.os.UserHandle
import android.service.notification.StatusBarNotification
import android.text.TextUtils
@@ -45,6 +50,7 @@ import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.dump.DumpManager
import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.plugins.BcSmartspaceDataPlugin
import com.android.systemui.statusbar.NotificationMediaManager.isPlayingState
import com.android.systemui.statusbar.notification.row.HybridGroupManager
import com.android.systemui.util.Assert
@@ -54,6 +60,7 @@ import java.io.FileDescriptor
import java.io.IOException
import java.io.PrintWriter
import java.util.concurrent.Executor
+import java.util.concurrent.Executors
import javax.inject.Inject
// URI fields to try loading album art from
@@ -99,9 +106,16 @@ class MediaDataManager(
mediaDataCombineLatest: MediaDataCombineLatest,
private val mediaDataFilter: MediaDataFilter,
private val activityStarter: ActivityStarter,
+ private val smartspaceMediaDataProvider: SmartspaceMediaDataProvider,
private var useMediaResumption: Boolean,
private val useQsMediaPlayer: Boolean
-) : Dumpable {
+) : Dumpable, BcSmartspaceDataPlugin.SmartspaceTargetListener {
+
+ companion object {
+ // UI surface label for subscribing Smartspace updates.
+ @JvmField
+ val SMARTSPACE_UI_SURFACE_LABEL = "media_data_manager"
+ }
private val themeText = com.android.settingslib.Utils.getColorAttr(context,
com.android.internal.R.attr.textColorPrimary).defaultColor
@@ -117,6 +131,8 @@ class MediaDataManager(
// TODO(b/159539991#comment5): Move internal listeners to separate package.
private val internalListeners: MutableSet<Listener> = mutableSetOf()
private val mediaEntries: LinkedHashMap<String, MediaData> = LinkedHashMap()
+ // There should ONLY be at most one Smartspace media recommendation.
+ private var smartspaceMediaTarget: SmartspaceTarget? = null
internal var appsBlockedFromResume: MutableSet<String> = Utils.getBlockedMediaApps(context)
set(value) {
// Update list
@@ -128,6 +144,7 @@ class MediaDataManager(
removeAllForPackage(it)
}
}
+ private var smartspaceSession: SmartspaceSession? = null
@Inject
constructor(
@@ -143,11 +160,13 @@ class MediaDataManager(
mediaDeviceManager: MediaDeviceManager,
mediaDataCombineLatest: MediaDataCombineLatest,
mediaDataFilter: MediaDataFilter,
- activityStarter: ActivityStarter
+ activityStarter: ActivityStarter,
+ smartspaceMediaDataProvider: SmartspaceMediaDataProvider
) : this(context, backgroundExecutor, foregroundExecutor, mediaControllerFactory,
broadcastDispatcher, dumpManager, mediaTimeoutListener, mediaResumeListener,
mediaSessionBasedFilter, mediaDeviceManager, mediaDataCombineLatest, mediaDataFilter,
- activityStarter, Utils.useMediaResumption(context), Utils.useQsMediaPlayer(context))
+ activityStarter, smartspaceMediaDataProvider, Utils.useMediaResumption(context),
+ Utils.useQsMediaPlayer(context))
private val appChangeReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
@@ -200,9 +219,31 @@ class MediaDataManager(
}
// BroadcastDispatcher does not allow filters with data schemes
context.registerReceiver(appChangeReceiver, uninstallFilter)
+
+ // Register for Smartspace data updates.
+ smartspaceMediaDataProvider.registerListener(this)
+ val smartspaceManager: SmartspaceManager =
+ context.getSystemService(SmartspaceManager::class.java)
+ smartspaceSession = smartspaceManager.createSmartspaceSession(
+ SmartspaceConfig.Builder(context, SMARTSPACE_UI_SURFACE_LABEL).build())
+ smartspaceSession?.let {
+ it.registerSmartspaceUpdates(
+ // Use a new thread listening to Smartspace updates instead of using the existing
+ // backgroundExecutor. SmartspaceSession has scheduled routine updates which can be
+ // unpredictable on test simulators, using the backgroundExecutor makes it's hard to
+ // test the threads numbers.
+ // Switch to use backgroundExecutor when SmartspaceSession has a good way to be
+ // mocked.
+ Executors.newCachedThreadPool(),
+ SmartspaceSession.Callback { targets ->
+ smartspaceMediaDataProvider.onTargetsAvailable(targets)
+ })
+ }
+ smartspaceSession?.let { it.requestSmartspaceUpdate() }
}
fun destroy() {
+ smartspaceMediaDataProvider.unregisterListener(this)
context.unregisterReceiver(appChangeReceiver)
}
@@ -309,7 +350,7 @@ class MediaDataManager(
private fun addInternalListener(listener: Listener) = internalListeners.add(listener)
/**
- * Notify internal listeners of loaded event.
+ * Notify internal listeners of media loaded event.
*
* External listeners registered with [addListener] will be notified after the event propagates
* through the internal listener pipeline.
@@ -319,7 +360,17 @@ class MediaDataManager(
}
/**
- * Notify internal listeners of removed event.
+ * Notify internal listeners of Smartspace media loaded event.
+ *
+ * External listeners registered with [addListener] will be notified after the event propagates
+ * through the internal listener pipeline.
+ */
+ private fun notifySmartspaceMediaDataLoaded(key: String, info: SmartspaceTarget) {
+ internalListeners.forEach { it.onSmartspaceMediaDataLoaded(key, info) }
+ }
+
+ /**
+ * Notify internal listeners of media removed event.
*
* External listeners registered with [addListener] will be notified after the event propagates
* through the internal listener pipeline.
@@ -329,6 +380,16 @@ class MediaDataManager(
}
/**
+ * Notify internal listeners of Smartspace media removed event.
+ *
+ * External listeners registered with [addListener] will be notified after the event propagates
+ * through the internal listener pipeline.
+ */
+ private fun notifySmartspaceMediaDataRemoved(key: String) {
+ internalListeners.forEach { it.onSmartspaceMediaDataRemoved(key) }
+ }
+
+ /**
* Called whenever the player has been paused or stopped for a while, or swiped from QQS.
* This will make the player not active anymore, hiding it from QQS and Keyguard.
* @see MediaData.active
@@ -601,6 +662,49 @@ class MediaDataManager(
}
}
+ override fun onSmartspaceTargetsUpdated(targets: List<Parcelable>) {
+ Log.d(TAG, "My Smartspace media updates are here")
+ val mediaTargets = targets.filterIsInstance<SmartspaceTarget>()
+ when (mediaTargets.size) {
+ 0 -> {
+ Log.d(TAG, "Empty Smartspace media target")
+ smartspaceMediaTarget?.let {
+ notifySmartspaceMediaDataRemoved(it.smartspaceTargetId)
+ }
+ smartspaceMediaTarget = null
+ }
+ 1 -> {
+ // TODO(b/182811956): Reactivate the resumable media sessions whose last active
+ // time is within 3 hours.
+ // TODO(b/182813365): Wire this up with MediaTimeoutListener so the session can be
+ // expired after 30 seconds.
+ val newMediaTarget = mediaTargets.get(0)
+ if (smartspaceMediaTarget != null &&
+ smartspaceMediaTarget!!.smartspaceTargetId ==
+ newMediaTarget.smartspaceTargetId) {
+ // The same Smartspace updates can be received. Only send the first one.
+ Log.d(TAG, "Same Smartspace media update exists. Skip loading data.")
+ } else {
+ smartspaceMediaTarget?.let {
+ notifySmartspaceMediaDataRemoved(it.smartspaceTargetId)
+ }
+ notifySmartspaceMediaDataLoaded(
+ newMediaTarget.smartspaceTargetId, newMediaTarget)
+ smartspaceMediaTarget = newMediaTarget
+ }
+ }
+ else -> {
+ // There should NOT be more than 1 Smartspace media update. When it happens, it
+ // indicates a bad state or an error. Reset the status accordingly.
+ Log.wtf(TAG, "More than 1 Smartspace Media Update. Resetting the status...")
+ smartspaceMediaTarget?.let {
+ notifySmartspaceMediaDataRemoved(it.smartspaceTargetId)
+ }
+ smartspaceMediaTarget = null
+ }
+ }
+ }
+
fun onNotificationRemoved(key: String) {
Assert.isMainThread()
val removed = mediaEntries.remove(key)
@@ -685,10 +789,16 @@ class MediaDataManager(
*/
fun onMediaDataLoaded(key: String, oldKey: String?, data: MediaData) {}
+ /** Called whenever there's new Smartspace media data loaded. */
+ fun onSmartspaceMediaDataLoaded(key: String, data: SmartspaceTarget) {}
+
/**
* Called whenever a previously existing Media notification was removed
*/
fun onMediaDataRemoved(key: String) {}
+
+ /** Called whenever a previously existing Smartspace media data was removed. */
+ fun onSmartspaceMediaDataRemoved(key: String) {}
}
override fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<out String>) {
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaHost.kt b/packages/SystemUI/src/com/android/systemui/media/MediaHost.kt
index b74ca283f16a..8c12a305992e 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaHost.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaHost.kt
@@ -1,5 +1,6 @@
package com.android.systemui.media
+import android.app.smartspace.SmartspaceTarget
import android.graphics.Rect
import android.util.ArraySet
import android.view.View
@@ -53,9 +54,17 @@ class MediaHost constructor(
updateViewVisibility()
}
+ override fun onSmartspaceMediaDataLoaded(key: String, data: SmartspaceTarget) {
+ updateViewVisibility()
+ }
+
override fun onMediaDataRemoved(key: String) {
updateViewVisibility()
}
+
+ override fun onSmartspaceMediaDataRemoved(key: String) {
+ updateViewVisibility()
+ }
}
fun addVisibilityChangeListener(listener: (Boolean) -> Unit) {
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaSessionBasedFilter.kt b/packages/SystemUI/src/com/android/systemui/media/MediaSessionBasedFilter.kt
index f695622b943a..d973478625de 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaSessionBasedFilter.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaSessionBasedFilter.kt
@@ -16,6 +16,7 @@
package com.android.systemui.media
+import android.app.smartspace.SmartspaceTarget
import android.content.ComponentName
import android.content.Context
import android.media.session.MediaController
@@ -134,6 +135,12 @@ class MediaSessionBasedFilter @Inject constructor(
}
}
+ override fun onSmartspaceMediaDataLoaded(key: String, data: SmartspaceTarget) {
+ backgroundExecutor.execute {
+ dispatchSmartspaceMediaDataLoaded(key, data)
+ }
+ }
+
override fun onMediaDataRemoved(key: String) {
// Queue on background thread to ensure ordering of loaded and removed events is maintained.
backgroundExecutor.execute {
@@ -142,6 +149,12 @@ class MediaSessionBasedFilter @Inject constructor(
}
}
+ override fun onSmartspaceMediaDataRemoved(key: String) {
+ backgroundExecutor.execute {
+ dispatchSmartspaceMediaDataRemoved(key)
+ }
+ }
+
private fun dispatchMediaDataLoaded(key: String, oldKey: String?, info: MediaData) {
foregroundExecutor.execute {
listeners.toSet().forEach { it.onMediaDataLoaded(key, oldKey, info) }
@@ -154,6 +167,18 @@ class MediaSessionBasedFilter @Inject constructor(
}
}
+ private fun dispatchSmartspaceMediaDataLoaded(key: String, info: SmartspaceTarget) {
+ foregroundExecutor.execute {
+ listeners.toSet().forEach { it.onSmartspaceMediaDataLoaded(key, info) }
+ }
+ }
+
+ private fun dispatchSmartspaceMediaDataRemoved(key: String) {
+ foregroundExecutor.execute {
+ listeners.toSet().forEach { it.onSmartspaceMediaDataRemoved(key) }
+ }
+ }
+
private fun handleControllersChanged(controllers: List<MediaController>) {
packageControllers.clear()
controllers.forEach {
diff --git a/packages/SystemUI/src/com/android/systemui/media/SmartspaceMediaDataProvider.kt b/packages/SystemUI/src/com/android/systemui/media/SmartspaceMediaDataProvider.kt
new file mode 100644
index 000000000000..0eab572f315f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/SmartspaceMediaDataProvider.kt
@@ -0,0 +1,39 @@
+package com.android.systemui.media
+
+import android.app.smartspace.SmartspaceTarget
+import com.android.systemui.plugins.BcSmartspaceDataPlugin
+import com.android.systemui.plugins.BcSmartspaceDataPlugin.SmartspaceTargetListener
+import javax.inject.Inject
+
+/** Provides SmartspaceTargets of media types for SystemUI media control. */
+class SmartspaceMediaDataProvider @Inject constructor() : BcSmartspaceDataPlugin {
+
+ private val smartspaceMediaTargetListeners: MutableList<SmartspaceTargetListener> =
+ mutableListOf()
+ private var smartspaceMediaTargets: List<SmartspaceTarget> = listOf()
+
+ override fun registerListener(smartspaceTargetListener: SmartspaceTargetListener) {
+ smartspaceMediaTargetListeners.add(smartspaceTargetListener)
+ }
+
+ override fun unregisterListener(smartspaceTargetListener: SmartspaceTargetListener?) {
+ smartspaceMediaTargetListeners.remove(smartspaceTargetListener)
+ }
+
+ /** Updates Smartspace data and propagates it to any listeners. */
+ fun onTargetsAvailable(targets: List<SmartspaceTarget>) {
+ // Filter out non-media targets.
+ val mediaTargets = mutableListOf<SmartspaceTarget>()
+ for (target in targets) {
+ val smartspaceTarget = target
+ if (smartspaceTarget.featureType == SmartspaceTarget.FEATURE_MEDIA) {
+ mediaTargets.add(smartspaceTarget)
+ }
+ }
+
+ smartspaceMediaTargets = mediaTargets
+ smartspaceMediaTargetListeners.forEach {
+ it.onSmartspaceTargetsUpdated(smartspaceMediaTargets)
+ }
+ }
+} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
index 594c0af4cb44..be9d6bd6edd2 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
@@ -116,7 +116,6 @@ import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.accessibility.AccessibilityButtonModeObserver;
import com.android.systemui.accessibility.SystemActions;
-import com.android.systemui.assist.AssistHandleViewController;
import com.android.systemui.assist.AssistManager;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.qualifiers.Main;
@@ -276,11 +275,6 @@ public class NavigationBar implements View.OnAttachStateChangeListener,
}
}
- /** Only for default display */
- @Nullable
- AssistHandleViewController mAssistHandlerViewController;
-
-
private final AutoHideUiElement mAutoHideUiElement = new AutoHideUiElement() {
@Override
public void synchronizeState() {
@@ -630,11 +624,6 @@ public class NavigationBar implements View.OnAttachStateChangeListener,
mDisabledFlags2 |= StatusBarManager.DISABLE2_ROTATE_SUGGESTIONS;
}
setDisabled2Flags(mDisabledFlags2);
- if (mIsOnDefaultDisplay) {
- mAssistHandlerViewController =
- new AssistHandleViewController(mHandler, mNavigationBarView);
- getBarTransitions().addDarkIntensityListener(mAssistHandlerViewController);
- }
initSecondaryHomeHandleForRotation();
@@ -664,11 +653,6 @@ public class NavigationBar implements View.OnAttachStateChangeListener,
@Override
public void onViewDetachedFromWindow(View v) {
if (mNavigationBarView != null) {
- if (mIsOnDefaultDisplay) {
- mNavigationBarView.getBarTransitions()
- .removeDarkIntensityListener(mAssistHandlerViewController);
- mAssistHandlerViewController = null;
- }
mNavigationBarView.getBarTransitions().destroy();
mNavigationBarView.getLightTransitionsController().destroy(mContext);
}
@@ -1596,11 +1580,6 @@ public class NavigationBar implements View.OnAttachStateChangeListener,
delay + StackStateAnimator.ANIMATION_DURATION_GO_TO_FULL_SHADE);
}
- @Nullable
- public AssistHandleViewController getAssistHandlerViewController() {
- return mAssistHandlerViewController;
- }
-
/**
* Performs transitions on navigation bar.
*
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
index 50efa8def5bf..6d1109ee5f51 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
@@ -45,7 +45,6 @@ import com.android.settingslib.applications.InterestingConfigChanges;
import com.android.systemui.Dumpable;
import com.android.systemui.accessibility.AccessibilityButtonModeObserver;
import com.android.systemui.accessibility.SystemActions;
-import com.android.systemui.assist.AssistHandleViewController;
import com.android.systemui.assist.AssistManager;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.SysUISingleton;
@@ -399,13 +398,6 @@ public class NavigationBarController implements Callbacks,
return mNavigationBars.get(DEFAULT_DISPLAY);
}
- /** @return {@link AssistHandleViewController} (only on the default display). */
- @Nullable
- public AssistHandleViewController getAssistHandlerViewController() {
- NavigationBar navBar = getDefaultNavigationBar();
- return navBar == null ? null : navBar.getAssistHandlerViewController();
- }
-
@Override
public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) {
for (int i = 0; i < mNavigationBars.size(); i++) {
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
index 091b17dbb0c0..339331bdd827 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
@@ -69,7 +69,6 @@ import com.android.settingslib.Utils;
import com.android.systemui.Dependency;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
-import com.android.systemui.assist.AssistHandleViewController;
import com.android.systemui.model.SysUiState;
import com.android.systemui.navigationbar.buttons.ButtonDispatcher;
import com.android.systemui.navigationbar.buttons.ContextualButton;
@@ -1357,14 +1356,6 @@ public class NavigationBarView extends FrameLayout implements
setClipChildren(shouldClip);
setClipToPadding(shouldClip);
- NavigationBarController navigationBarController =
- Dependency.get(NavigationBarController.class);
- AssistHandleViewController controller =
- navigationBarController == null
- ? null : navigationBarController.getAssistHandlerViewController();
- if (controller != null) {
- controller.setBottomOffset(insets.getSystemWindowInsetBottom());
- }
return super.onApplyWindowInsets(insets);
}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/ButtonDispatcher.java b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/ButtonDispatcher.java
index ade2923acdf6..a4f5548a659c 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/ButtonDispatcher.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/ButtonDispatcher.java
@@ -196,8 +196,6 @@ public class ButtonDispatcher {
if (animate) {
setVisibility(View.VISIBLE);
mFadeAnimator = ValueAnimator.ofFloat(getAlpha(), alpha);
- mFadeAnimator.setStartDelay(
- mAssistManager.getAssistHandleShowAndGoRemainingDurationMs());
mFadeAnimator.setDuration(duration);
mFadeAnimator.setInterpolator(LINEAR);
mFadeAnimator.addListener(mFadeListener);
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java
index f0e4cce299ee..9ea938325659 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java
@@ -127,8 +127,10 @@ public class KeyButtonView extends ImageView implements ButtonInterface {
performLongClick();
mLongClicked = true;
} else {
- sendEvent(KeyEvent.ACTION_DOWN, KeyEvent.FLAG_LONG_PRESS);
- sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_LONG_CLICKED);
+ if (mCode != KEYCODE_UNKNOWN) {
+ sendEvent(KeyEvent.ACTION_DOWN, KeyEvent.FLAG_LONG_PRESS);
+ sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_LONG_CLICKED);
+ }
mLongClicked = true;
}
}
@@ -434,6 +436,9 @@ public class KeyButtonView extends ImageView implements ButtonInterface {
@Override
public void abortCurrentGesture() {
Log.d("b/63783866", "KeyButtonView.abortCurrentGesture");
+ if (mCode != KeyEvent.KEYCODE_UNKNOWN) {
+ sendEvent(KeyEvent.ACTION_UP, KeyEvent.FLAG_CANCELED);
+ }
setPressed(false);
mRipple.abortDelayedRipple();
mGestureAborted = true;
diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java
index 54d6a2eda82a..02c12f6d51e5 100644
--- a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java
@@ -19,6 +19,9 @@ package com.android.systemui.people;
import static android.appwidget.AppWidgetManager.EXTRA_APPWIDGET_ID;
import static android.appwidget.AppWidgetManager.INVALID_APPWIDGET_ID;
+import static com.android.systemui.people.PeopleTileViewHelper.getPersonIconBitmap;
+import static com.android.systemui.people.PeopleTileViewHelper.getSizeInDp;
+
import android.app.Activity;
import android.app.INotificationManager;
import android.app.people.IPeopleManager;
@@ -27,28 +30,33 @@ import android.content.Context;
import android.content.Intent;
import android.content.pm.LauncherApps;
import android.content.pm.PackageManager;
+import android.content.res.TypedArray;
+import android.graphics.Color;
+import android.graphics.Outline;
+import android.graphics.drawable.GradientDrawable;
import android.os.Bundle;
import android.os.ServiceManager;
import android.util.Log;
+import android.view.View;
import android.view.ViewGroup;
+import android.view.ViewOutlineProvider;
+import android.widget.LinearLayout;
import com.android.systemui.R;
import com.android.systemui.people.widget.PeopleSpaceWidgetManager;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
+import java.util.ArrayList;
import java.util.List;
import javax.inject.Inject;
-/**
- * Shows the user their tiles for their priority People (go/live-status).
- */
+/** People Tile Widget configuration activity that shows the user their conversation tiles. */
public class PeopleSpaceActivity extends Activity {
private static final String TAG = "PeopleSpaceActivity";
private static final boolean DEBUG = PeopleSpaceUtils.DEBUG;
- private ViewGroup mPeopleSpaceLayout;
private IPeopleManager mPeopleManager;
private PeopleSpaceWidgetManager mPeopleSpaceWidgetManager;
private INotificationManager mNotificationManager;
@@ -70,8 +78,6 @@ public class PeopleSpaceActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- setContentView(R.layout.people_space_activity);
- mPeopleSpaceLayout = findViewById(R.id.people_space_layout);
mContext = getApplicationContext();
mNotificationManager = INotificationManager.Stub.asInterface(
ServiceManager.getService(Context.NOTIFICATION_SERVICE));
@@ -79,42 +85,78 @@ public class PeopleSpaceActivity extends Activity {
mPeopleManager = IPeopleManager.Stub.asInterface(
ServiceManager.getService(Context.PEOPLE_SERVICE));
mLauncherApps = mContext.getSystemService(LauncherApps.class);
- setTileViewsWithPriorityConversations();
mAppWidgetId = getIntent().getIntExtra(EXTRA_APPWIDGET_ID,
INVALID_APPWIDGET_ID);
setResult(RESULT_CANCELED);
}
- /**
- * Retrieves all priority conversations and sets a {@link PeopleSpaceTileView}s for each
- * priority conversation.
- */
- private void setTileViewsWithPriorityConversations() {
+ /** Builds the conversation selection activity. */
+ private void buildActivity() {
+ List<PeopleSpaceTile> priorityTiles = new ArrayList<>();
+ List<PeopleSpaceTile> recentTiles = new ArrayList<>();
try {
- List<PeopleSpaceTile> tiles = PeopleSpaceUtils.getTiles(mContext, mNotificationManager,
+ priorityTiles = PeopleSpaceUtils.getPriorityTiles(mContext, mNotificationManager,
+ mPeopleManager, mLauncherApps, mNotificationEntryManager);
+ recentTiles = PeopleSpaceUtils.getRecentTiles(mContext, mNotificationManager,
mPeopleManager, mLauncherApps, mNotificationEntryManager);
- for (PeopleSpaceTile tile : tiles) {
- PeopleSpaceTileView tileView = new PeopleSpaceTileView(mContext, mPeopleSpaceLayout,
- tile.getId());
- setTileView(tileView, tile);
- }
} catch (Exception e) {
Log.e(TAG, "Couldn't retrieve conversations", e);
}
+
+ // If no conversations, render activity without conversations
+ if (recentTiles.isEmpty() && priorityTiles.isEmpty()) {
+ setContentView(R.layout.people_space_activity_no_conversations);
+
+ // The Tile preview has colorBackground as its background. Change it so it's different
+ // than the activity's background.
+ LinearLayout item = findViewById(R.id.item);
+ GradientDrawable shape = (GradientDrawable) item.getBackground();
+ final TypedArray ta = mContext.obtainStyledAttributes(
+ new int[] {android.R.attr.colorBackgroundFloating});
+ shape.setColor(ta.getColor(0, Color.WHITE));
+ return;
+ }
+
+ setContentView(R.layout.people_space_activity);
+ setTileViews(R.id.priority, R.id.priority_tiles, priorityTiles);
+ setTileViews(R.id.recent, R.id.recent_tiles, recentTiles);
+ }
+
+ private ViewOutlineProvider mViewOutlineProvider = new ViewOutlineProvider() {
+ @Override
+ public void getOutline(View view, Outline outline) {
+ outline.setRoundRect(0, 0, view.getWidth(), view.getHeight(),
+ mContext.getResources().getDimension(R.dimen.people_space_widget_radius));
+ }
+ };
+
+ /** Sets a {@link PeopleSpaceTileView}s for each conversation. */
+ private void setTileViews(int viewId, int tilesId, List<PeopleSpaceTile> tiles) {
+ if (tiles.isEmpty()) {
+ LinearLayout view = findViewById(viewId);
+ view.setVisibility(View.GONE);
+ return;
+ }
+
+ ViewGroup layout = findViewById(tilesId);
+ layout.setClipToOutline(true);
+ layout.setOutlineProvider(mViewOutlineProvider);
+ for (int i = 0; i < tiles.size(); ++i) {
+ PeopleSpaceTile tile = tiles.get(i);
+ PeopleSpaceTileView tileView = new PeopleSpaceTileView(mContext,
+ layout, tile.getId(), i == (tiles.size() - 1));
+ setTileView(tileView, tile);
+ }
}
/** Sets {@code tileView} with the data in {@code conversation}. */
private void setTileView(PeopleSpaceTileView tileView, PeopleSpaceTile tile) {
try {
- String pkg = tile.getPackageName();
- String status =
- PeopleSpaceUtils.getLastInteractionString(mContext,
- tile.getLastInteractionTimestamp());
- tileView.setStatus(status);
-
tileView.setName(tile.getUserName().toString());
- tileView.setPackageIcon(mPackageManager.getApplicationIcon(pkg));
- tileView.setPersonIcon(tile.getUserIcon());
+ tileView.setPersonIcon(getPersonIconBitmap(mContext, tile,
+ getSizeInDp(mContext, R.dimen.avatar_size_for_medium,
+ mContext.getResources().getDisplayMetrics().density)));
+
tileView.setOnClickListener(v -> storeWidgetConfiguration(tile));
} catch (Exception e) {
Log.e(TAG, "Couldn't retrieve shortcut information", e);
@@ -141,6 +183,12 @@ public class PeopleSpaceActivity extends Activity {
finish();
}
+ /** Finish activity without choosing a widget. */
+ public void dismissActivity(View v) {
+ if (DEBUG) Log.d(TAG, "Activity dismissed with no widgets added!");
+ finish();
+ }
+
private void setActivityResult(int result) {
Intent resultValue = new Intent();
resultValue.putExtra(EXTRA_APPWIDGET_ID, mAppWidgetId);
@@ -151,6 +199,12 @@ public class PeopleSpaceActivity extends Activity {
protected void onResume() {
super.onResume();
// Refresh tile views to sync new conversations.
- setTileViewsWithPriorityConversations();
+ buildActivity();
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ finish();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceTileView.java b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceTileView.java
index 6f89332e66a9..36b435b04fd6 100644
--- a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceTileView.java
+++ b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceTileView.java
@@ -19,8 +19,7 @@ package com.android.systemui.people;
import android.app.people.PeopleSpaceTile;
import android.content.Context;
import android.content.pm.LauncherApps;
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.Icon;
+import android.graphics.Bitmap;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -37,11 +36,9 @@ public class PeopleSpaceTileView extends LinearLayout {
private View mTileView;
private TextView mNameView;
- private TextView mStatusView;
- private ImageView mPackageIconView;
private ImageView mPersonIconView;
- public PeopleSpaceTileView(Context context, ViewGroup view, String shortcutId) {
+ public PeopleSpaceTileView(Context context, ViewGroup view, String shortcutId, boolean isLast) {
super(context);
mTileView = view.findViewWithTag(shortcutId);
if (mTileView == null) {
@@ -50,10 +47,13 @@ public class PeopleSpaceTileView extends LinearLayout {
view.addView(mTileView, LayoutParams.MATCH_PARENT,
LayoutParams.MATCH_PARENT);
mTileView.setTag(shortcutId);
+
+ // If it's not the last conversation in this section, add a divider.
+ if (!isLast) {
+ inflater.inflate(R.layout.people_space_activity_list_divider, view, true);
+ }
}
mNameView = mTileView.findViewById(R.id.tile_view_name);
- mStatusView = mTileView.findViewById(R.id.tile_view_status);
- mPackageIconView = mTileView.findViewById(R.id.tile_view_package_icon);
mPersonIconView = mTileView.findViewById(R.id.tile_view_person_icon);
}
@@ -62,19 +62,9 @@ public class PeopleSpaceTileView extends LinearLayout {
mNameView.setText(name);
}
- /** Sets the status text on the tile. */
- public void setStatus(String status) {
- mStatusView.setText(status);
- }
-
- /** Sets the package drawable on the tile. */
- public void setPackageIcon(Drawable drawable) {
- mPackageIconView.setImageDrawable(drawable);
- }
-
- /** Sets the person bitmap on the tile. */
- public void setPersonIcon(Icon icon) {
- mPersonIconView.setImageIcon(icon);
+ /** Sets the person and package drawable on the tile. */
+ public void setPersonIcon(Bitmap bitmap) {
+ mPersonIconView.setImageBitmap(bitmap);
}
/** Sets the click listener of the tile. */
diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java
index 440c5efab008..5f6d95f4824d 100644
--- a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java
+++ b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java
@@ -19,6 +19,7 @@ package com.android.systemui.people;
import static android.app.Notification.CATEGORY_MISSED_CALL;
import static android.app.Notification.EXTRA_MESSAGES;
+import android.annotation.NonNull;
import android.app.INotificationManager;
import android.app.Notification;
import android.app.people.ConversationChannel;
@@ -126,8 +127,9 @@ public class PeopleSpaceUtils {
}
}
- /** Returns a list of map entries corresponding to user's conversations. */
- public static List<PeopleSpaceTile> getTiles(
+ /** Returns a list of map entries corresponding to user's priority conversations. */
+ @NonNull
+ public static List<PeopleSpaceTile> getPriorityTiles(
Context context, INotificationManager notificationManager, IPeopleManager peopleManager,
LauncherApps launcherApps, NotificationEntryManager notificationEntryManager)
throws Exception {
@@ -139,11 +141,23 @@ public class PeopleSpaceUtils {
.filter(c -> c.getNotificationChannel() != null
&& c.getNotificationChannel().isImportantConversation())
.map(c -> c.getShortcutInfo());
- List<PeopleSpaceTile> tiles = getSortedTiles(peopleManager, launcherApps,
+ List<PeopleSpaceTile> priorityTiles = getSortedTiles(peopleManager, launcherApps,
priorityConversations);
+ priorityTiles = augmentTilesFromVisibleNotifications(
+ context, priorityTiles, notificationEntryManager);
+ return priorityTiles;
+ }
- // Sort and then add recent and non priority conversations to tiles list.
+ /** Returns a list of map entries corresponding to user's recent conversations. */
+ @NonNull
+ public static List<PeopleSpaceTile> getRecentTiles(
+ Context context, INotificationManager notificationManager, IPeopleManager peopleManager,
+ LauncherApps launcherApps, NotificationEntryManager notificationEntryManager)
+ throws Exception {
if (DEBUG) Log.d(TAG, "Add recent conversations");
+ List<ConversationChannelWrapper> conversations =
+ notificationManager.getConversations(
+ false).getList();
Stream<ShortcutInfo> nonPriorityConversations = conversations.stream()
.filter(c -> c.getNotificationChannel() == null
|| !c.getNotificationChannel().isImportantConversation())
@@ -159,10 +173,10 @@ public class PeopleSpaceUtils {
recentConversations);
List<PeopleSpaceTile> recentTiles =
getSortedTiles(peopleManager, launcherApps, mergedStream);
- tiles.addAll(recentTiles);
- tiles = augmentTilesFromVisibleNotifications(context, tiles, notificationEntryManager);
- return tiles;
+ recentTiles = augmentTilesFromVisibleNotifications(
+ context, recentTiles, notificationEntryManager);
+ return recentTiles;
}
/** Returns stored widgets for the conversation specified. */
diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java b/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java
index 8d1b712e0807..96fbe6987562 100644
--- a/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java
@@ -192,7 +192,11 @@ class PeopleTileViewHelper {
}
private int getSizeInDp(int dimenResourceId) {
- return (int) (mContext.getResources().getDimension(dimenResourceId) / mDensity);
+ return getSizeInDp(mContext, dimenResourceId, mDensity);
+ }
+
+ public static int getSizeInDp(Context context, int dimenResourceId, float density) {
+ return (int) (context.getResources().getDimension(dimenResourceId) / density);
}
private int getContentHeightForLayout(int lineHeight) {
@@ -278,24 +282,11 @@ class PeopleTileViewHelper {
} else {
views.setViewVisibility(R.id.availability, View.GONE);
}
- boolean hasNewStory =
- mTile.getStatuses() != null && mTile.getStatuses().stream().anyMatch(
- c -> c.getActivity() == ACTIVITY_NEW_STORY);
+
views.setTextViewText(R.id.name, mTile.getUserName().toString());
views.setBoolean(R.id.image, "setClipToOutline", true);
-
- Icon icon = mTile.getUserIcon();
- PeopleStoryIconFactory storyIcon = new PeopleStoryIconFactory(mContext,
- mContext.getPackageManager(),
- IconDrawableFactory.newInstance(mContext, false),
- maxAvatarSize);
- Drawable drawable = icon.loadDrawable(mContext);
- Drawable personDrawable = storyIcon.getPeopleTileDrawable(drawable,
- mTile.getPackageName(), getUserId(mTile), mTile.isImportantConversation(),
- hasNewStory);
- Bitmap bitmap = convertDrawableToBitmap(personDrawable);
- views.setImageViewBitmap(R.id.person_icon, bitmap);
-
+ views.setImageViewBitmap(R.id.person_icon,
+ getPersonIconBitmap(mContext, mTile, maxAvatarSize));
return views;
} catch (Exception e) {
Log.e(TAG, "Failed to set common fields: " + e);
@@ -583,4 +574,23 @@ class PeopleTileViewHelper {
return R.layout.people_tile_small;
}
}
+
+ /** Returns a bitmap with the user icon and package icon. */
+ public static Bitmap getPersonIconBitmap(
+ Context context, PeopleSpaceTile tile, int maxAvatarSize) {
+ boolean hasNewStory =
+ tile.getStatuses() != null && tile.getStatuses().stream().anyMatch(
+ c -> c.getActivity() == ACTIVITY_NEW_STORY);
+
+ Icon icon = tile.getUserIcon();
+ PeopleStoryIconFactory storyIcon = new PeopleStoryIconFactory(context,
+ context.getPackageManager(),
+ IconDrawableFactory.newInstance(context, false),
+ maxAvatarSize);
+ Drawable drawable = icon.loadDrawable(context);
+ Drawable personDrawable = storyIcon.getPeopleTileDrawable(drawable,
+ tile.getPackageName(), getUserId(tile), tile.isImportantConversation(),
+ hasNewStory);
+ return convertDrawableToBitmap(personDrawable);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
index a089a05f2776..ea471b957d68 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
@@ -31,6 +31,8 @@ import com.android.systemui.qs.QSPanel.QSTileLayout;
import com.android.systemui.qs.TouchAnimator.Builder;
import com.android.systemui.qs.TouchAnimator.Listener;
import com.android.systemui.qs.dagger.QSScope;
+import com.android.systemui.qs.tileimpl.HeightOverrideable;
+import com.android.systemui.statusbar.CrossFadeHelper;
import com.android.systemui.statusbar.FeatureFlags;
import com.android.systemui.tuner.TunerService;
import com.android.systemui.tuner.TunerService.Tunable;
@@ -53,6 +55,9 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha
private static final String MOVE_FULL_ROWS = "sysui_qs_move_whole_rows";
public static final float EXPANDED_TILE_DELAY = .86f;
+ private static final long QQS_FADE_IN_DURATION = 200L;
+ // Fade out faster than fade in to finish before QQS hides.
+ private static final long QQS_FADE_OUT_DURATION = 50L;
private final ArrayList<View> mAllViews = new ArrayList<>();
@@ -86,7 +91,7 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha
private HeightExpansionAnimator mOtherTilesExpandAnimator;
private boolean mNeedsAnimatorUpdate = false;
-
+ private boolean mToShowing;
private boolean mOnKeyguard;
private boolean mAllowFancy;
@@ -149,6 +154,18 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha
}
}
+ void startAlphaAnimation(boolean show) {
+ if (show == mToShowing) {
+ return;
+ }
+ mToShowing = show;
+ if (show) {
+ CrossFadeHelper.fadeIn(mQs.getView(), QQS_FADE_IN_DURATION, 0 /* delay */);
+ } else {
+ CrossFadeHelper.fadeOut(mQs.getView(), QQS_FADE_OUT_DURATION, 0 /* delay */,
+ null /* endRunnable */);
+ }
+ }
/**
* Sets whether or not the keyguard is currently being shown with a collapsed header.
@@ -590,6 +607,15 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
float t = valueAnimator.getAnimatedFraction();
+ final int viewCount = mViews.size();
+ int height = (Integer) valueAnimator.getAnimatedValue();
+ for (int i = 0; i < viewCount; i++) {
+ View v = mViews.get(i);
+ v.setBottom(v.getTop() + height);
+ if (v instanceof HeightOverrideable) {
+ ((HeightOverrideable) v).setHeightOverride(height);
+ }
+ }
if (t == 0f) {
mListener.onAnimationAtStart();
} else if (t == 1f) {
@@ -598,12 +624,6 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha
mListener.onAnimationStarted();
}
mLastT = t;
- final int viewCount = mViews.size();
- int height = (Integer) valueAnimator.getAnimatedValue();
- for (int i = 0; i < viewCount; i++) {
- View v = mViews.get(i);
- v.setBottom(v.getTop() + height);
- }
}
};
@@ -632,6 +652,9 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha
for (int i = 0; i < viewsCount; i++) {
View v = mViews.get(i);
v.setBottom(v.getTop() + v.getMeasuredHeight());
+ if (v instanceof HeightOverrideable) {
+ ((HeightOverrideable) v).resetOverride();
+ }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
index e8976d379fef..5256bc45b7b5 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
@@ -92,6 +92,11 @@ public class QSContainerImpl extends FrameLayout {
setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
}
+ @Override
+ public boolean hasOverlappingRendering() {
+ return false;
+ }
+
void onMediaVisibilityChanged(boolean qsVisible) {
mAnimateBottomOnNextLayout = qsVisible;
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java b/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java
index d54d3f21f0b6..9a889e0ff088 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java
@@ -210,6 +210,7 @@ public class QSDetail extends LinearLayout {
mDetailAdapter = adapter;
listener = mHideGridContentWhenDone;
setVisibility(View.VISIBLE);
+ updateDetailText();
} else {
if (wasShowingDetail) {
Dependency.get(MetricsLogger.class).hidden(mDetailAdapter.getMetricsCategory());
@@ -223,7 +224,6 @@ public class QSDetail extends LinearLayout {
mQsPanelController.setGridContentVisibility(true);
mQsPanelCallback.onScanStateChanged(false);
}
- updateDetailText();
sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
if (mShouldAnimate) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooterView.java b/packages/SystemUI/src/com/android/systemui/qs/QSFooterView.java
index 3d8784b29e4c..eb7b115700a7 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFooterView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooterView.java
@@ -75,6 +75,8 @@ public class QSFooterView extends FrameLayout {
private TouchAnimator mSettingsCogAnimator;
private View mActionsContainer;
+ private View mTunerIcon;
+ private int mTunerIconTranslation;
private OnClickListener mExpandClickListener;
@@ -107,6 +109,7 @@ public class QSFooterView extends FrameLayout {
mActionsContainer = requireViewById(R.id.qs_footer_actions_container);
mEditContainer = findViewById(R.id.qs_footer_actions_edit_container);
mBuildText = findViewById(R.id.build);
+ mTunerIcon = requireViewById(R.id.tuner_icon);
// RenderThread is doing more harm than good when touching the header (to expand quick
// settings), so disable it for this view
@@ -166,6 +169,9 @@ public class QSFooterView extends FrameLayout {
private void updateResources() {
updateFooterAnimator();
+ mTunerIconTranslation = mContext.getResources()
+ .getDimensionPixelOffset(R.dimen.qs_footer_tuner_icon_translation);
+ mTunerIcon.setTranslationX(isLayoutRtl() ? -mTunerIconTranslation : mTunerIconTranslation);
}
private void updateFooterAnimator() {
@@ -274,8 +280,7 @@ public class QSFooterView extends FrameLayout {
private void updateVisibilities(boolean isTunerEnabled) {
mSettingsContainer.setVisibility(mQsDisabled ? View.GONE : View.VISIBLE);
- mSettingsContainer.findViewById(R.id.tuner_icon).setVisibility(
- isTunerEnabled ? View.VISIBLE : View.INVISIBLE);
+ mTunerIcon.setVisibility(isTunerEnabled ? View.VISIBLE : View.INVISIBLE);
final boolean isDemo = UserManager.isDeviceInDemoMode(mContext);
mMultiUserSwitch.setVisibility(showUserSwitcher() ? View.VISIBLE : View.GONE);
if (mEditContainer != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooterViewController.java b/packages/SystemUI/src/com/android/systemui/qs/QSFooterViewController.java
index f1f4e16206a1..52e05a4fd6c5 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFooterViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooterViewController.java
@@ -32,6 +32,7 @@ import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.R;
+import com.android.systemui.globalactions.GlobalActionsDialogLite;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.qs.dagger.QSScope;
import com.android.systemui.settings.UserTracker;
@@ -68,6 +69,7 @@ public class QSFooterViewController extends ViewController<QSFooterView> impleme
private final PageIndicator mPageIndicator;
private final View mPowerMenuLite;
private final boolean mShowPMLiteButton;
+ private GlobalActionsDialogLite mGlobalActionsDialog;
private final UserInfoController.OnUserInfoChangedListener mOnUserInfoChangedListener =
new UserInfoController.OnUserInfoChangedListener() {
@@ -115,6 +117,8 @@ public class QSFooterViewController extends ViewController<QSFooterView> impleme
} else {
startSettingsActivity();
}
+ } else if (v == mPowerMenuLite) {
+ mGlobalActionsDialog.showOrHideDialog(false, true);
}
}
};
@@ -129,7 +133,8 @@ public class QSFooterViewController extends ViewController<QSFooterView> impleme
QSPanelController qsPanelController, QSDetailDisplayer qsDetailDisplayer,
QuickQSPanelController quickQSPanelController,
TunerService tunerService, MetricsLogger metricsLogger,
- @Named(PM_LITE_ENABLED) boolean showPMLiteButton) {
+ @Named(PM_LITE_ENABLED) boolean showPMLiteButton,
+ GlobalActionsDialogLite globalActionsDialog) {
super(view);
mUserManager = userManager;
mUserInfoController = userInfoController;
@@ -149,11 +154,15 @@ public class QSFooterViewController extends ViewController<QSFooterView> impleme
mPageIndicator = mView.findViewById(R.id.footer_page_indicator);
mPowerMenuLite = mView.findViewById(R.id.pm_lite);
mShowPMLiteButton = showPMLiteButton;
+ mGlobalActionsDialog = globalActionsDialog;
}
@Override
protected void onViewAttached() {
- if (!mShowPMLiteButton) {
+ if (mShowPMLiteButton) {
+ mPowerMenuLite.setVisibility(View.VISIBLE);
+ mPowerMenuLite.setOnClickListener(mSettingsOnClickListener);
+ } else {
mPowerMenuLite.setVisibility(View.GONE);
}
mView.addOnLayoutChangeListener(
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
index 7c9f0b082d8f..cb58eff650c1 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
@@ -132,7 +132,8 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
Bundle savedInstanceState) {
inflater = mInjectionInflater.injectable(
- inflater.cloneInContext(new ContextThemeWrapper(getContext(), R.style.qs_theme)));
+ inflater.cloneInContext(new ContextThemeWrapper(getContext(),
+ R.style.Theme_SystemUI_QuickSettings)));
return inflater.inflate(R.layout.qs_panel, container, false);
}
@@ -190,7 +191,7 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
(v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
boolean sizeChanged = (oldTop - oldBottom) != (top - bottom);
if (sizeChanged) {
- setQsExpansion(mLastQSExpansion, mLastQSExpansion);
+ setQsExpansion(mLastQSExpansion, mLastHeaderTranslation);
}
});
}
@@ -395,6 +396,12 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
@Override
public void setQsExpansion(float expansion, float headerTranslation) {
if (DEBUG) Log.d(TAG, "setQSExpansion " + expansion + " " + headerTranslation);
+
+ if (mQSAnimator != null) {
+ final boolean showQSOnLockscreen = expansion > 0;
+ final boolean showQSUnlocked = headerTranslation == 0;
+ mQSAnimator.startAlphaAnimation(showQSOnLockscreen || showQSUnlocked);
+ }
mContainer.setExpansion(expansion);
final float translationScaleY = expansion - 1;
boolean onKeyguardAndExpanded = isKeyguardShowing() && !mShowCollapsedOnKeyguard;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
index 86836f935aba..e41a0389e8c1 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
@@ -202,11 +202,10 @@ public abstract class QSPanelControllerBase<T extends QSPanel> extends ViewContr
private void addTile(final QSTile tile, boolean collapsedView) {
final TileRecord r = new TileRecord();
r.tile = tile;
- r.tileView = mHost.createTileView(tile, collapsedView);
+ r.tileView = mHost.createTileView(getContext(), tile, collapsedView);
mView.addTile(r);
mRecords.add(r);
mCachedSpecs = getTilesSpecs();
-
}
/** */
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
index 0a9c12fa4a13..525bad8a0e25 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
@@ -23,7 +23,6 @@ import android.os.Handler;
import android.os.Looper;
import android.os.UserHandle;
import android.os.UserManager;
-import android.provider.Settings;
import android.provider.Settings.Secure;
import android.service.quicksettings.Tile;
import android.text.TextUtils;
@@ -57,6 +56,7 @@ import com.android.systemui.statusbar.phone.StatusBarIconController;
import com.android.systemui.tuner.TunerService;
import com.android.systemui.tuner.TunerService.Tunable;
import com.android.systemui.util.leak.GarbageMonitor;
+import com.android.systemui.util.settings.SecureSettings;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -79,6 +79,7 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
private static final int MAX_QS_INSTANCE_ID = 1 << 20;
+ public static final int POSITION_AT_END = -1;
public static final String TILES_SETTING = Secure.QS_TILES;
private final Context mContext;
@@ -101,6 +102,7 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D
private final Optional<StatusBar> mStatusBarOptional;
private Context mUserContext;
private UserTracker mUserTracker;
+ private SecureSettings mSecureSettings;
@Inject
public QSTileHost(Context context,
@@ -116,7 +118,8 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D
Optional<StatusBar> statusBarOptional,
QSLogger qsLogger,
UiEventLogger uiEventLogger,
- UserTracker userTracker) {
+ UserTracker userTracker,
+ SecureSettings secureSettings) {
mIconController = iconController;
mContext = context;
mUserContext = context;
@@ -135,6 +138,7 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D
pluginManager.addPluginListener(this, QSFactory.class, true);
mDumpManager.registerDumpable(TAG, this);
mUserTracker = userTracker;
+ mSecureSettings = secureSettings;
mainHandler.post(() -> {
// This is technically a hack to avoid circular dependency of
@@ -343,19 +347,43 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D
if (mAutoTiles != null) mAutoTiles.unmarkTileAsAutoAdded(spec);
}
+ /**
+ * Add a tile to the end
+ *
+ * @param spec string matching a pre-defined tilespec
+ */
public void addTile(String spec) {
- changeTileSpecs(tileSpecs-> !tileSpecs.contains(spec) && tileSpecs.add(spec));
+ addTile(spec, POSITION_AT_END);
+ }
+
+ /**
+ * Add a tile into the requested spot, or at the end if the position is greater than the number
+ * of tiles.
+ * @param spec string matching a pre-defined tilespec
+ * @param requestPosition -1 for end, 0 for beginning, or X for insertion at position X
+ */
+ public void addTile(String spec, int requestPosition) {
+ changeTileSpecs(tileSpecs -> {
+ if (tileSpecs.contains(spec)) return false;
+
+ int size = tileSpecs.size();
+ if (requestPosition == POSITION_AT_END || requestPosition >= size) {
+ tileSpecs.add(spec);
+ } else {
+ tileSpecs.add(requestPosition, spec);
+ }
+ return true;
+ });
}
- private void saveTilesToSettings(List<String> tileSpecs) {
- Settings.Secure.putStringForUser(mContext.getContentResolver(), TILES_SETTING,
- TextUtils.join(",", tileSpecs), null /* tag */,
- false /* default */, mCurrentUser, true /* overrideable by restore */);
+ void saveTilesToSettings(List<String> tileSpecs) {
+ mSecureSettings.putStringForUser(TILES_SETTING, TextUtils.join(",", tileSpecs),
+ null /* tag */, false /* default */, mCurrentUser,
+ true /* overrideable by restore */);
}
private void changeTileSpecs(Predicate<List<String>> changeFunction) {
- final String setting = Settings.Secure.getStringForUser(mContext.getContentResolver(),
- TILES_SETTING, mCurrentUser);
+ final String setting = mSecureSettings.getStringForUser(TILES_SETTING, mCurrentUser);
final List<String> tileSpecs = loadTileSpecs(mContext, setting);
if (changeFunction.test(tileSpecs)) {
saveTilesToSettings(tileSpecs);
@@ -423,9 +451,15 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D
return null;
}
- public QSTileView createTileView(QSTile tile, boolean collapsedView) {
+ /**
+ * Create a view for a tile, iterating over all possible {@link QSFactory}.
+ *
+ * @see QSFactory#createTileView
+ */
+ public QSTileView createTileView(Context themedContext, QSTile tile, boolean collapsedView) {
for (int i = 0; i < mQsFactories.size(); i++) {
- QSTileView view = mQsFactories.get(i).createTileView(tile, collapsedView);
+ QSTileView view = mQsFactories.get(i)
+ .createTileView(themedContext, tile, collapsedView);
if (view != null) {
return view;
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
index 8aaf84b7d9f9..b661e2b6ae57 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
@@ -17,9 +17,6 @@ package com.android.systemui.qs;
import static android.app.StatusBarManager.DISABLE2_QUICK_SETTINGS;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
-import static com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN;
-import static com.android.systemui.statusbar.StatusBarIconView.STATE_ICON;
-
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
@@ -41,7 +38,7 @@ import com.android.systemui.BatteryMeterView;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
import com.android.systemui.qs.QSDetail.Callback;
-import com.android.systemui.statusbar.StatusBarMobileView;
+import com.android.systemui.statusbar.StatusBarIconView;
import com.android.systemui.statusbar.phone.StatusBarIconController.TintedIconManager;
import com.android.systemui.statusbar.phone.StatusBarWindowView;
import com.android.systemui.statusbar.phone.StatusIconContainer;
@@ -91,7 +88,7 @@ public class QuickStatusBarHeader extends FrameLayout {
public QuickStatusBarHeader(Context context, AttributeSet attrs) {
super(context, attrs);
- mMobileSlotName = context.getString(com.android.internal.R.string.status_bar_mobile);
+ mMobileSlotName = context.getString(com.android.internal.R.string.status_bar_no_calling);
}
/**
@@ -221,8 +218,8 @@ public class QuickStatusBarHeader extends FrameLayout {
}
private void updateAlphaAnimator() {
- StatusBarMobileView icon =
- ((StatusBarMobileView) mIconContainer.getViewForSlot(mMobileSlotName));
+ StatusBarIconView icon =
+ ((StatusBarIconView) mIconContainer.getViewForSlot(mMobileSlotName));
TouchAnimator.Builder builder = new TouchAnimator.Builder()
.addFloat(mQSCarriers, "alpha", 0, 1)
.addFloat(mDatePrivacyView, "alpha", 0, mDatePrivacyAlpha);
@@ -231,14 +228,12 @@ public class QuickStatusBarHeader extends FrameLayout {
builder.setListener(new TouchAnimator.ListenerAdapter() {
@Override
public void onAnimationAtEnd() {
- icon.forceHidden(true);
- icon.setVisibleState(STATE_HIDDEN);
+ mIconContainer.addIgnoredSlot(mMobileSlotName);
}
@Override
public void onAnimationStarted() {
- icon.forceHidden(false);
- icon.setVisibleState(STATE_ICON);
+ mIconContainer.removeIgnoredSlot(mMobileSlotName);
}
});
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/SignalTileView.java b/packages/SystemUI/src/com/android/systemui/qs/SignalTileView.java
index ce90fc107b82..b609df5e77fe 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/SignalTileView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/SignalTileView.java
@@ -134,8 +134,9 @@ public class SignalTileView extends QSIconViewImpl {
mSignal.setPaddingRelative(0, 0, 0, 0);
}
final boolean shouldAnimate = allowAnimations && isShown();
- setVisibility(mIn, shouldAnimate, s.activityIn);
- setVisibility(mOut, shouldAnimate, s.activityOut);
+ // Do not show activity indicators
+// setVisibility(mIn, shouldAnimate, s.activityIn);
+// setVisibility(mOut, shouldAnimate, s.activityOut);
}
private void setVisibility(View view, boolean shown, boolean visible) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
index dce081f21581..6f789d3c8541 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
@@ -63,7 +63,7 @@ public class QSCustomizer extends LinearLayout {
private boolean mIsShowingNavBackdrop;
public QSCustomizer(Context context, AttributeSet attrs) {
- super(new ContextThemeWrapper(context, R.style.edit_theme), attrs);
+ super(new ContextThemeWrapper(context, R.style.Theme_SystemUI_QuickSettings_Edit), attrs);
LayoutInflater.from(getContext()).inflate(R.layout.qs_customize_panel_content, this);
mClipper = new QSDetailClipper(findViewById(R.id.customize_container));
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
index e9abef42a282..08aa599d34b7 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
@@ -728,7 +728,7 @@ public class TileAdapter extends RecyclerView.Adapter<Holder> implements TileSta
GridLayoutManager lm = ((GridLayoutManager) parent.getLayoutManager());
SpanSizeLookup span = lm.getSpanSizeLookup();
ViewHolder holder = parent.getChildViewHolder(view);
- int column = span.getSpanIndex(holder.getLayoutPosition(), lm.getSpanCount());
+ int column = span.getSpanIndex(holder.getBindingAdapterPosition(), lm.getSpanCount());
if (view instanceof TextView) {
super.getItemOffsets(outRect, view, parent, state);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java
index d41bd7ad4d3c..75a7e8e09afa 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java
@@ -32,6 +32,7 @@ import com.android.systemui.statusbar.phone.AutoTileManager;
import com.android.systemui.statusbar.phone.ManagedProfileController;
import com.android.systemui.statusbar.policy.CastController;
import com.android.systemui.statusbar.policy.DataSaverController;
+import com.android.systemui.statusbar.policy.DeviceControlsController;
import com.android.systemui.statusbar.policy.HotspotController;
import com.android.systemui.util.settings.SecureSettings;
@@ -61,6 +62,7 @@ public interface QSModule {
NightDisplayListener nightDisplayListener,
CastController castController,
ReduceBrightColorsController reduceBrightColorsController,
+ DeviceControlsController deviceControlsController,
@Named(RBC_AVAILABLE) boolean isReduceBrightColorsAvailable) {
AutoTileManager manager = new AutoTileManager(
context,
@@ -74,6 +76,7 @@ public interface QSModule {
nightDisplayListener,
castController,
reduceBrightColorsController,
+ deviceControlsController,
isReduceBrightColorsAvailable
);
manager.init();
diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistHandleBehavior.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/HeightOverrideable.kt
index 2eda3d784382..866fa097d6fd 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/AssistHandleBehavior.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/HeightOverrideable.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2019 The Android Open Source Project
+ * Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,12 +14,16 @@
* limitations under the License.
*/
-package com.android.systemui.assist;
+package com.android.systemui.qs.tileimpl
-public enum AssistHandleBehavior {
+interface HeightOverrideable {
+ companion object {
+ const val NO_OVERRIDE = -1
+ }
- TEST,
- OFF,
- LIKE_HOME,
- REMINDER_EXP;
-}
+ var heightOverride: Int
+
+ fun resetOverride() {
+ heightOverride = NO_OVERRIDE
+ }
+} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
index ba349c6273d9..9b0536c595ad 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
@@ -19,9 +19,7 @@ import static com.android.systemui.qs.dagger.QSFlagsModule.QS_LABELS_FLAG;
import android.content.Context;
import android.os.Build;
import android.util.Log;
-import android.view.ContextThemeWrapper;
-import com.android.systemui.R;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.plugins.qs.QSFactory;
import com.android.systemui.plugins.qs.QSIconView;
@@ -251,8 +249,7 @@ public class QSFactoryImpl implements QSFactory {
}
@Override
- public QSTileView createTileView(QSTile tile, boolean collapsedView) {
- Context context = new ContextThemeWrapper(mQsHostLazy.get().getContext(), R.style.qs_theme);
+ public QSTileView createTileView(Context context, QSTile tile, boolean collapsedView) {
QSIconView icon = tile.createTileView(context);
if (mSideLabels) {
return new QSTileViewHorizontal(context, icon, collapsedView);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileView.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileView.java
index c7ed89ba49b1..3d5a709a5fdd 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileView.java
@@ -43,7 +43,7 @@ public class QSTileView extends QSTileBaseView {
protected TextView mLabel;
protected TextView mSecondLine;
private ImageView mPadLock;
- private int mState;
+ protected int mState;
protected ViewGroup mLabelContainer;
private View mExpandIndicator;
private View mExpandSpace;
@@ -133,14 +133,7 @@ public class QSTileView extends QSTileBaseView {
protected void handleStateChanged(QSTile.State state) {
super.handleStateChanged(state);
if (!Objects.equals(mLabel.getText(), state.label) || mState != state.state) {
- ColorStateList labelColor;
- if (state.state == Tile.STATE_ACTIVE) {
- labelColor = mColorLabelActive;
- } else if (state.state == Tile.STATE_INACTIVE) {
- labelColor = mColorLabelInactive;
- } else {
- labelColor = mColorLabelUnavailable;
- }
+ ColorStateList labelColor = getLabelColor(state.state);
changeLabelColor(labelColor);
mState = state.state;
mLabel.setText(state.label);
@@ -163,6 +156,15 @@ public class QSTileView extends QSTileBaseView {
mPadLock.setVisibility(state.disabledByPolicy ? View.VISIBLE : View.GONE);
}
+ protected final ColorStateList getLabelColor(int state) {
+ if (state == Tile.STATE_ACTIVE) {
+ return mColorLabelActive;
+ } else if (state == Tile.STATE_INACTIVE) {
+ return mColorLabelInactive;
+ }
+ return mColorLabelUnavailable;
+ }
+
protected void changeLabelColor(ColorStateList color) {
mLabel.setTextColor(color);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewHorizontal.kt b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewHorizontal.kt
index 32285cf797e4..7a8b2c696fa0 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewHorizontal.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewHorizontal.kt
@@ -24,6 +24,8 @@ import android.graphics.drawable.Drawable
import android.graphics.drawable.RippleDrawable
import android.service.quicksettings.Tile.STATE_ACTIVE
import android.view.Gravity
+import android.view.View
+import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.RelativeLayout
import com.android.systemui.R
@@ -35,12 +37,14 @@ open class QSTileViewHorizontal(
context: Context,
icon: QSIconView,
collapsed: Boolean
-) : QSTileView(context, icon, collapsed) {
+) : QSTileView(context, icon, collapsed), HeightOverrideable {
protected var colorBackgroundDrawable: Drawable? = null
private var paintColor = Color.WHITE
private var paintAnimator: ValueAnimator? = null
private var labelAnimator: ValueAnimator? = null
+ private var mSideView: ImageView = ImageView(mContext)
+ override var heightOverride: Int = HeightOverrideable.NO_OVERRIDE
init {
orientation = HORIZONTAL
@@ -55,7 +59,23 @@ open class QSTileViewHorizontal(
val iconSize = context.resources.getDimensionPixelSize(R.dimen.qs_icon_size)
addView(mIcon, 0, LayoutParams(iconSize, iconSize))
+ mSideView.visibility = View.GONE
+ addView(
+ mSideView,
+ -1,
+ LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT).apply {
+ gravity = Gravity.CENTER_VERTICAL
+ })
+
mColorLabelActive = ColorStateList.valueOf(getColorForState(getContext(), STATE_ACTIVE))
+ changeLabelColor(getLabelColor(mState)) // Matches the default state of the tile
+ }
+
+ override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
+ super.onLayout(changed, l, t, r, b)
+ if (heightOverride != HeightOverrideable.NO_OVERRIDE) {
+ bottom = top + heightOverride
+ }
}
override fun createLabel() {
@@ -113,14 +133,13 @@ open class QSTileViewHorizontal(
if (allowAnimations) {
animateBackground(newColor)
} else {
- if (newColor != paintColor) {
- clearBackgroundAnimator()
- colorBackgroundDrawable?.setTintList(ColorStateList.valueOf(newColor))?.also {
- paintColor = newColor
- }
+ clearBackgroundAnimator()
+ colorBackgroundDrawable?.setTintList(ColorStateList.valueOf(newColor))?.also {
paintColor = newColor
}
+ paintColor = newColor
}
+ loadSideViewDrawableIfNecessary(state)
}
private fun animateBackground(newBackgroundColor: Int) {
@@ -173,5 +192,21 @@ open class QSTileViewHorizontal(
labelAnimator?.cancel()?.also { labelAnimator = null }
}
+ private fun loadSideViewDrawableIfNecessary(state: QSTile.State) {
+ if (state.sideViewDrawable != null) {
+ (mSideView.layoutParams as MarginLayoutParams).apply {
+ marginStart =
+ context.resources.getDimensionPixelSize(R.dimen.qs_label_container_margin)
+ }
+ mSideView.setImageDrawable(state.sideViewDrawable)
+ mSideView.visibility = View.VISIBLE
+ mSideView.adjustViewBounds = true
+ mSideView.scaleType = ImageView.ScaleType.FIT_CENTER
+ } else {
+ mSideView.setImageDrawable(null)
+ mSideView.visibility = GONE
+ }
+ }
+
override fun handleExpand(dualTarget: Boolean) {}
} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CameraToggleTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CameraToggleTile.java
index 0d73a5a97706..5b986b6fece9 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CameraToggleTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CameraToggleTile.java
@@ -66,7 +66,7 @@ public class CameraToggleTile extends SensorPrivacyToggleTile {
return getHost().getContext().getPackageManager().hasSystemFeature(FEATURE_CAMERA_TOGGLE)
&& whitelistIpcs(() -> DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY,
"camera_toggle_enabled",
- false));
+ true));
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt
index a74a50e7d6c2..a2c7633c4746 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt
@@ -39,8 +39,6 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.qs.QSHost
import com.android.systemui.qs.logging.QSLogger
import com.android.systemui.qs.tileimpl.QSTileImpl
-import com.android.systemui.statusbar.FeatureFlags
-import com.android.systemui.util.settings.GlobalSettings
import java.util.concurrent.atomic.AtomicBoolean
import javax.inject.Inject
@@ -53,9 +51,7 @@ class DeviceControlsTile @Inject constructor(
statusBarStateController: StatusBarStateController,
activityStarter: ActivityStarter,
qsLogger: QSLogger,
- private val controlsComponent: ControlsComponent,
- private val featureFlags: FeatureFlags,
- globalSettings: GlobalSettings
+ private val controlsComponent: ControlsComponent
) : QSTileImpl<QSTile.State>(
host,
backgroundLooper,
@@ -67,11 +63,6 @@ class DeviceControlsTile @Inject constructor(
qsLogger
) {
- companion object {
- const val SETTINGS_FLAG = "controls_lockscreen"
- }
-
- private val controlsLockscreen = globalSettings.getInt(SETTINGS_FLAG, 0) != 0
private var hasControlsApps = AtomicBoolean(false)
private val intent = Intent(Settings.ACTION_DEVICE_CONTROLS_SETTINGS)
@@ -92,9 +83,7 @@ class DeviceControlsTile @Inject constructor(
}
override fun isAvailable(): Boolean {
- return featureFlags.isKeyguardLayoutEnabled &&
- controlsLockscreen &&
- controlsComponent.getControlsController().isPresent
+ return controlsComponent.getControlsController().isPresent
}
override fun newTileState(): QSTile.State {
@@ -114,7 +103,7 @@ class DeviceControlsTile @Inject constructor(
val i = Intent().apply {
component = ComponentName(mContext, ControlsActivity::class.java)
addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK)
- putExtra(ControlsUiController.BACK_TO_GLOBAL_ACTIONS, false)
+ putExtra(ControlsUiController.EXTRA_ANIMATE, true)
}
mContext.startActivity(i)
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/MicrophoneToggleTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/MicrophoneToggleTile.java
index b8d879226f55..42bd77b354bb 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/MicrophoneToggleTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/MicrophoneToggleTile.java
@@ -67,7 +67,7 @@ public class MicrophoneToggleTile extends SensorPrivacyToggleTile {
.hasSystemFeature(FEATURE_MICROPHONE_TOGGLE)
&& whitelistIpcs(() -> DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY,
"mic_toggle_enabled",
- false));
+ true));
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java
index 60c5d1cafde9..bf9655837b25 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java
@@ -20,11 +20,20 @@ import static android.provider.Settings.Secure.NFC_PAYMENT_DEFAULT_COMPONENT;
import android.content.Intent;
import android.content.pm.PackageManager;
+import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.os.Looper;
+import android.service.quickaccesswallet.GetWalletCardsError;
+import android.service.quickaccesswallet.GetWalletCardsRequest;
+import android.service.quickaccesswallet.GetWalletCardsResponse;
import android.service.quickaccesswallet.QuickAccessWalletClient;
+import android.service.quickaccesswallet.WalletCard;
import android.service.quicksettings.Tile;
+import android.util.Log;
+import androidx.annotation.NonNull;
+
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.MetricsLogger;
import com.android.systemui.R;
import com.android.systemui.dagger.qualifiers.Background;
@@ -40,20 +49,30 @@ import com.android.systemui.statusbar.FeatureFlags;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.settings.SecureSettings;
+import java.util.List;
+import java.util.concurrent.Executor;
+
import javax.inject.Inject;
/** Quick settings tile: Quick access wallet **/
public class QuickAccessWalletTile extends QSTileImpl<QSTile.State> {
+ private static final String TAG = "QuickAccessWalletTile";
private static final String FEATURE_CHROME_OS = "org.chromium.arc";
+
private final CharSequence mLabel = mContext.getString(R.string.wallet_title);
+ private final WalletCardRetriever mCardRetriever = new WalletCardRetriever();
// TODO(b/180959290): Re-create the QAW Client when the default NFC payment app changes.
private final QuickAccessWalletClient mQuickAccessWalletClient;
private final KeyguardStateController mKeyguardStateController;
private final PackageManager mPackageManager;
private final SecureSettings mSecureSettings;
+ private final Executor mExecutor;
private final FeatureFlags mFeatureFlags;
+ @VisibleForTesting Drawable mCardViewDrawable;
+ private boolean mHasCard;
+
@Inject
public QuickAccessWalletTile(
QSHost host,
@@ -68,6 +87,7 @@ public class QuickAccessWalletTile extends QSTileImpl<QSTile.State> {
KeyguardStateController keyguardStateController,
PackageManager packageManager,
SecureSettings secureSettings,
+ @Background Executor executor,
FeatureFlags featureFlags) {
super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
statusBarStateController, activityStarter, qsLogger);
@@ -75,6 +95,7 @@ public class QuickAccessWalletTile extends QSTileImpl<QSTile.State> {
mKeyguardStateController = keyguardStateController;
mPackageManager = packageManager;
mSecureSettings = secureSettings;
+ mExecutor = executor;
mFeatureFlags = featureFlags;
}
@@ -87,6 +108,14 @@ public class QuickAccessWalletTile extends QSTileImpl<QSTile.State> {
}
@Override
+ protected void handleSetListening(boolean listening) {
+ super.handleSetListening(listening);
+ if (listening) {
+ queryWalletCards();
+ }
+ }
+
+ @Override
protected void handleClick() {
mActivityStarter.postStartActivityDismissingKeyguard(
mQuickAccessWalletClient.createWalletIntent(), /* delay= */ 0);
@@ -100,14 +129,27 @@ public class QuickAccessWalletTile extends QSTileImpl<QSTile.State> {
state.icon = ResourceIcon.get(R.drawable.ic_qs_wallet);
boolean isDeviceLocked = !mKeyguardStateController.isUnlocked();
if (mQuickAccessWalletClient.isWalletFeatureAvailable()) {
- state.state = isDeviceLocked ? Tile.STATE_INACTIVE : Tile.STATE_ACTIVE;
- state.secondaryLabel = isDeviceLocked
- ? null
- : mContext.getString(R.string.wallet_secondary_label);
+ if (mHasCard) {
+ if (isDeviceLocked) {
+ state.state = Tile.STATE_INACTIVE;
+ state.secondaryLabel =
+ mContext.getString(R.string.wallet_secondary_label_device_locked);
+ } else {
+ state.state = Tile.STATE_ACTIVE;
+ state.secondaryLabel =
+ mContext.getString(R.string.wallet_secondary_label_active);
+ }
+ } else {
+ state.state = Tile.STATE_INACTIVE;
+ state.secondaryLabel = mContext.getString(R.string.wallet_secondary_label_no_card);
+ }
state.stateDescription = state.secondaryLabel;
} else {
state.state = Tile.STATE_UNAVAILABLE;
}
+ if (!isDeviceLocked) {
+ state.sideViewDrawable = mCardViewDrawable;
+ }
}
@Override
@@ -133,4 +175,43 @@ public class QuickAccessWalletTile extends QSTileImpl<QSTile.State> {
CharSequence qawLabel = mQuickAccessWalletClient.getServiceLabel();
return qawLabel == null ? mLabel : qawLabel;
}
+
+ private void queryWalletCards() {
+ int cardWidth =
+ mContext.getResources().getDimensionPixelSize(R.dimen.wallet_tile_card_view_width);
+ int cardHeight =
+ mContext.getResources().getDimensionPixelSize(R.dimen.wallet_tile_card_view_height);
+ int iconSizePx = mContext.getResources().getDimensionPixelSize(R.dimen.wallet_icon_size);
+ GetWalletCardsRequest request =
+ new GetWalletCardsRequest(cardWidth, cardHeight, iconSizePx, /* maxCards= */ 2);
+ mQuickAccessWalletClient.getWalletCards(mExecutor, request, mCardRetriever);
+ }
+
+ private class WalletCardRetriever implements
+ QuickAccessWalletClient.OnWalletCardsRetrievedCallback {
+
+ @Override
+ public void onWalletCardsRetrieved(@NonNull GetWalletCardsResponse response) {
+ Log.i(TAG, "Successfully retrieved wallet cards.");
+ List<WalletCard> cards = response.getWalletCards();
+ if (cards.isEmpty()) {
+ Log.d(TAG, "No wallet cards exist.");
+ mCardViewDrawable = null;
+ mHasCard = false;
+ refreshState();
+ return;
+ }
+ mCardViewDrawable = cards.get(0).getCardImage().loadDrawable(mContext);
+ mHasCard = true;
+ refreshState();
+ }
+
+ @Override
+ public void onWalletCardRetrievalError(@NonNull GetWalletCardsError error) {
+ Log.w(TAG, "Error retrieve wallet cards");
+ mCardViewDrawable = null;
+ mHasCard = false;
+ refreshState();
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index b0a3f437b5ec..afbb197c4a12 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -58,9 +58,12 @@ import android.os.IBinder;
import android.os.Looper;
import android.os.PatternMatcher;
import android.os.RemoteException;
+import android.os.SystemClock;
import android.os.UserHandle;
import android.util.Log;
+import android.view.InputDevice;
import android.view.InputMonitor;
+import android.view.KeyCharacterMap;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.Surface;
@@ -240,6 +243,36 @@ public class OverviewProxyService extends CurrentUserTracker implements
}
@Override
+ public void onBackPressed() throws RemoteException {
+ if (!verifyCaller("onBackPressed")) {
+ return;
+ }
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mHandler.post(() -> {
+ sendEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK);
+ sendEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BACK);
+
+ notifyBackAction(true, -1, -1, true, false);
+ });
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ private boolean sendEvent(int action, int code) {
+ long when = SystemClock.uptimeMillis();
+ final KeyEvent ev = new KeyEvent(when, when, action, code, 0 /* repeat */,
+ 0 /* metaState */, KeyCharacterMap.VIRTUAL_KEYBOARD, 0 /* scancode */,
+ KeyEvent.FLAG_FROM_SYSTEM | KeyEvent.FLAG_VIRTUAL_HARD_KEY,
+ InputDevice.SOURCE_KEYBOARD);
+
+ ev.setDisplayId(mContext.getDisplay().getDisplayId());
+ return InputManager.getInstance()
+ .injectInputEvent(ev, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC);
+ }
+
+ @Override
public void onOverviewShown(boolean fromHome) {
if (!verifyCaller("onOverviewShown")) {
return;
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java b/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java
index 04d199645b24..9ce0eebfd441 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java
@@ -411,19 +411,22 @@ public class LongScreenshotActivity extends Activity {
float imageRatio = bounds.width() / (float) bounds.height();
int previewWidth = mPreview.getWidth() - mPreview.getPaddingLeft()
- mPreview.getPaddingRight();
- float viewRatio = previewWidth / (float) mPreview.getHeight();
+ int previewHeight = mPreview.getHeight() - mPreview.getPaddingTop()
+ - mPreview.getPaddingBottom();
+ float viewRatio = previewWidth / (float) previewHeight;
if (imageRatio > viewRatio) {
// Image is full width and height is constrained, compute extra padding to inform
// CropView
- float imageHeight = mPreview.getHeight() * viewRatio / imageRatio;
- int extraPadding = (int) (mPreview.getHeight() - imageHeight) / 2;
- mCropView.setExtraPadding(extraPadding, extraPadding);
+ float imageHeight = previewHeight * viewRatio / imageRatio;
+ int extraPadding = (int) (previewHeight - imageHeight) / 2;
+ mCropView.setExtraPadding(extraPadding + mPreview.getPaddingTop(),
+ extraPadding + mPreview.getPaddingBottom());
mCropView.setImageWidth(previewWidth);
} else {
// Image is full height
- mCropView.setExtraPadding(0, 0);
- mCropView.setImageWidth((int) (mPreview.getHeight() * imageRatio));
+ mCropView.setExtraPadding(mPreview.getPaddingTop(), mPreview.getPaddingBottom());
+ mCropView.setImageWidth((int) (previewHeight * imageRatio));
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureClient.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureClient.java
index 926d5c4701aa..2863074bee0a 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureClient.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureClient.java
@@ -83,6 +83,13 @@ public class ScrollCaptureClient {
int getMaxTiles();
/**
+ * @return the maximum combined capture height for this session, in pixels.
+ */
+ default int getMaxHeight() {
+ return getMaxTiles() * getTileHeight();
+ }
+
+ /**
* @return the height of each image tile
*/
int getTileHeight();
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java
index d3dd048a989e..bbcfdbd99bef 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java
@@ -27,6 +27,7 @@ import android.view.ScrollCaptureResponse;
import androidx.concurrent.futures.CallbackToFutureAdapter;
import androidx.concurrent.futures.CallbackToFutureAdapter.Completer;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.screenshot.ScrollCaptureClient.CaptureResult;
import com.android.systemui.screenshot.ScrollCaptureClient.Session;
@@ -139,6 +140,11 @@ public class ScrollCaptureController {
mImageTileSet = imageTileSet;
}
+ @VisibleForTesting
+ float getTargetTopSizeRatio() {
+ return IDEAL_PORTION_ABOVE;
+ }
+
/**
* Run scroll capture. Performs a batch capture, collecting image tiles.
*
@@ -197,7 +203,7 @@ public class ScrollCaptureController {
&& result.captured.height() < result.requested.height();
boolean finish = false;
- if (partialResult || emptyResult) {
+ if (emptyResult) {
// Potentially reached a vertical boundary. Extend in the other direction.
if (mFinishOnBoundary) {
Log.d(TAG, "Partial/empty: finished!");
@@ -211,12 +217,12 @@ public class ScrollCaptureController {
Log.d(TAG, "Partial/empty: cleared, switch direction to finish");
}
} else {
- // Got the full requested result, but may have got enough bitmap data now
+ // Got a non-empty result, but may already have enough bitmap data now
int expectedTiles = mImageTileSet.size() + 1;
if (expectedTiles >= mSession.getMaxTiles()) {
Log.d(TAG, "Hit max tiles: finished");
- // If we ever hit the max tiles, we've got enough bitmap data to finish (even if we
- // weren't sure we'd finish on this pass).
+ // If we ever hit the max tiles, we've got enough bitmap data to finish
+ // (even if we weren't sure we'd finish on this pass).
finish = true;
} else {
if (mScrollingUp && !mFinishOnBoundary) {
@@ -253,10 +259,18 @@ public class ScrollCaptureController {
return;
}
- // Partial or empty results caused the direction the flip, so we can reliably use the
- // requested edges to determine the next top.
- int nextTop = (mScrollingUp) ? result.requested.top - mSession.getTileHeight()
- : result.requested.bottom;
+ int nextTop;
+ if (emptyResult) {
+ // An empty result caused the direction the flip,
+ // so use the requested edges to determine the next top.
+ nextTop = (mScrollingUp)
+ ? result.requested.top - mSession.getTileHeight()
+ : result.requested.bottom;
+ } else {
+ nextTop = (mScrollingUp)
+ ? result.captured.top - mSession.getTileHeight()
+ : result.captured.bottom;
+ }
requestNextTile(nextTop);
}
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java
index bdb392608455..357256cba131 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java
@@ -58,10 +58,9 @@ public class BrightnessController implements ToggleSlider.Listener {
private static final int SLIDER_ANIMATION_DURATION = 3000;
private static final int MSG_UPDATE_SLIDER = 1;
- private static final int MSG_SET_CHECKED = 2;
- private static final int MSG_ATTACH_LISTENER = 3;
- private static final int MSG_DETACH_LISTENER = 4;
- private static final int MSG_VR_MODE_CHANGED = 5;
+ private static final int MSG_ATTACH_LISTENER = 2;
+ private static final int MSG_DETACH_LISTENER = 3;
+ private static final int MSG_VR_MODE_CHANGED = 4;
private static final Uri BRIGHTNESS_MODE_URI =
Settings.System.getUriFor(Settings.System.SCREEN_BRIGHTNESS_MODE);
@@ -80,7 +79,6 @@ public class BrightnessController implements ToggleSlider.Listener {
private final int mDisplayId;
private final Context mContext;
private final ToggleSlider mControl;
- private final boolean mAutomaticAvailable;
private final DisplayManager mDisplayManager;
private final CurrentUserTracker mUserTracker;
private final IVrManager mVrManager;
@@ -219,16 +217,12 @@ public class BrightnessController implements ToggleSlider.Listener {
private final Runnable mUpdateModeRunnable = new Runnable() {
@Override
public void run() {
- if (mAutomaticAvailable) {
- int automatic;
- automatic = Settings.System.getIntForUser(mContext.getContentResolver(),
- Settings.System.SCREEN_BRIGHTNESS_MODE,
- Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL,
- UserHandle.USER_CURRENT);
- mAutomatic = automatic != Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL;
- } else {
- mHandler.obtainMessage(MSG_SET_CHECKED, 0).sendToTarget();
- }
+ int automatic;
+ automatic = Settings.System.getIntForUser(mContext.getContentResolver(),
+ Settings.System.SCREEN_BRIGHTNESS_MODE,
+ Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL,
+ UserHandle.USER_CURRENT);
+ mAutomatic = automatic != Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL;
}
};
@@ -266,9 +260,6 @@ public class BrightnessController implements ToggleSlider.Listener {
case MSG_UPDATE_SLIDER:
updateSlider(Float.intBitsToFloat(msg.arg1), msg.arg2 != 0);
break;
- case MSG_SET_CHECKED:
- mControl.setChecked(msg.arg1 != 0);
- break;
case MSG_ATTACH_LISTENER:
mControl.setOnChangedListener(BrightnessController.this);
break;
@@ -312,9 +303,6 @@ public class BrightnessController implements ToggleSlider.Listener {
mDefaultBacklightForVr = pm.getBrightnessConstraint(
PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_DEFAULT_VR);
-
- mAutomaticAvailable = context.getResources().getBoolean(
- com.android.internal.R.bool.config_automatic_brightness_available);
mDisplayManager = context.getSystemService(DisplayManager.class);
mVrManager = IVrManager.Stub.asInterface(ServiceManager.getService(
Context.VR_SERVICE));
@@ -339,8 +327,7 @@ public class BrightnessController implements ToggleSlider.Listener {
}
@Override
- public void onChanged(boolean tracking, boolean automatic,
- int value, boolean stopTracking) {
+ public void onChanged(boolean tracking, int value, boolean stopTracking) {
if (mExternalChange) return;
if (mSliderAnimator != null) {
@@ -398,12 +385,6 @@ public class BrightnessController implements ToggleSlider.Listener {
});
}
- private void setMode(int mode) {
- Settings.System.putIntForUser(mContext.getContentResolver(),
- Settings.System.SCREEN_BRIGHTNESS_MODE, mode,
- mUserTracker.getCurrentUserId());
- }
-
private void setBrightness(float brightness) {
mDisplayManager.setTemporaryBrightness(mDisplayId, brightness);
}
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessControllerSettings.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessControllerSettings.java
deleted file mode 100644
index 3c7d78c928fa..000000000000
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessControllerSettings.java
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.settings.brightness;
-
-import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.statusbar.FeatureFlags;
-
-import javax.inject.Inject;
-
-/**
- * Settings for prototyping thick brightness slider
- */
-@SysUISingleton
-public class BrightnessControllerSettings {
-
- private static final String THICK_BRIGHTNESS_SLIDER = "sysui_thick_brightness";
- private final FeatureFlags mFeatureFlags;
-
- @Inject
- public BrightnessControllerSettings(FeatureFlags featureFlags) {
- mFeatureFlags = featureFlags;
- }
-
- // Changing this setting between zero and non-zero may crash systemui down the line. Better to
- // restart systemui after changing it.
- /** */
- boolean useThickSlider() {
- return mFeatureFlags.useNewBrightnessSlider();
- }
-
- /** */
- boolean useMirrorOnThickSlider() {
- return !useThickSlider();
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSlider.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSlider.java
index 5d964a47cf29..db8205783173 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSlider.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSlider.java
@@ -21,7 +21,6 @@ import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
-import android.widget.CompoundButton;
import android.widget.SeekBar;
import androidx.annotation.Nullable;
@@ -52,7 +51,6 @@ public class BrightnessSlider extends ViewController<View> implements ToggleSlid
private final BrightnessSliderView mBrightnessSliderView;
private BrightnessMirrorController mMirrorController;
private boolean mTracking;
- private final boolean mUseMirror;
private final FalsingManager mFalsingManager;
private final Gefingerpoken mOnInterceptListener = new Gefingerpoken() {
@@ -75,11 +73,9 @@ public class BrightnessSlider extends ViewController<View> implements ToggleSlid
BrightnessSlider(
View rootView,
BrightnessSliderView brightnessSliderView,
- boolean useMirror,
FalsingManager falsingManager) {
super(rootView);
mBrightnessSliderView = brightnessSliderView;
- mUseMirror = useMirror;
mFalsingManager = falsingManager;
}
@@ -97,14 +93,12 @@ public class BrightnessSlider extends ViewController<View> implements ToggleSlid
@Override
protected void onViewAttached() {
mBrightnessSliderView.setOnSeekBarChangeListener(mSeekListener);
- mBrightnessSliderView.setOnCheckedChangeListener(mCheckListener);
mBrightnessSliderView.setOnInterceptListener(mOnInterceptListener);
}
@Override
protected void onViewDetached() {
mBrightnessSliderView.setOnSeekBarChangeListener(null);
- mBrightnessSliderView.setOnCheckedChangeListener(null);
mBrightnessSliderView.setOnDispatchTouchEventListener(null);
mBrightnessSliderView.setOnInterceptListener(null);
}
@@ -134,7 +128,6 @@ public class BrightnessSlider extends ViewController<View> implements ToggleSlid
private void setMirror(ToggleSlider toggleSlider) {
mMirror = toggleSlider;
if (mMirror != null) {
- mMirror.setChecked(mBrightnessSliderView.isChecked());
mMirror.setMax(mBrightnessSliderView.getMax());
mMirror.setValue(mBrightnessSliderView.getValue());
mBrightnessSliderView.setOnDispatchTouchEventListener(this::mirrorTouchEvent);
@@ -152,7 +145,6 @@ public class BrightnessSlider extends ViewController<View> implements ToggleSlid
*/
@Override
public void setMirrorControllerAndMirror(BrightnessMirrorController c) {
- if (!mUseMirror) return;
mMirrorController = c;
if (c != null) {
setMirror(c.getToggleSlider());
@@ -169,16 +161,6 @@ public class BrightnessSlider extends ViewController<View> implements ToggleSlid
}
@Override
- public void setChecked(boolean checked) {
- mBrightnessSliderView.setChecked(checked);
- }
-
- @Override
- public boolean isChecked() {
- return mBrightnessSliderView.isChecked();
- }
-
- @Override
public void setMax(int max) {
mBrightnessSliderView.setMax(max);
if (mMirror != null) {
@@ -209,7 +191,7 @@ public class BrightnessSlider extends ViewController<View> implements ToggleSlid
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
if (mListener != null) {
- mListener.onChanged(mTracking, isChecked(), progress, false);
+ mListener.onChanged(mTracking, progress, false);
}
}
@@ -218,12 +200,9 @@ public class BrightnessSlider extends ViewController<View> implements ToggleSlid
mTracking = true;
if (mListener != null) {
- mListener.onChanged(mTracking, isChecked(),
- getValue(), false);
+ mListener.onChanged(mTracking, getValue(), false);
}
- setChecked(false);
-
if (mMirrorController != null) {
mMirrorController.showMirror();
mMirrorController.setLocation((View) mBrightnessSliderView.getParent());
@@ -235,8 +214,7 @@ public class BrightnessSlider extends ViewController<View> implements ToggleSlid
mTracking = false;
if (mListener != null) {
- mListener.onChanged(mTracking, isChecked(),
- getValue(), true);
+ mListener.onChanged(mTracking, getValue(), true);
}
if (mMirrorController != null) {
@@ -245,35 +223,15 @@ public class BrightnessSlider extends ViewController<View> implements ToggleSlid
}
};
- private final CompoundButton.OnCheckedChangeListener mCheckListener =
- new CompoundButton.OnCheckedChangeListener() {
- @Override
- public void onCheckedChanged(CompoundButton toggle, boolean checked) {
- enableSlider(!checked);
-
- if (mListener != null) {
- mListener.onChanged(mTracking, checked, getValue(), false);
- }
-
- if (mMirror != null) {
- mMirror.setChecked(checked);
- }
- }
- };
-
/**
* Creates a {@link BrightnessSlider} with its associated view.
- *
- * The views inflated are determined by {@link BrightnessControllerSettings#useThickSlider()}.
*/
public static class Factory {
- BrightnessControllerSettings mSettings;
private final FalsingManager mFalsingManager;
@Inject
- public Factory(BrightnessControllerSettings settings, FalsingManager falsingManager) {
- mSettings = settings;
+ public Factory(FalsingManager falsingManager) {
mFalsingManager = falsingManager;
}
@@ -288,20 +246,18 @@ public class BrightnessSlider extends ViewController<View> implements ToggleSlid
int layout = getLayout();
ViewGroup root = (ViewGroup) LayoutInflater.from(context)
.inflate(layout, viewRoot, false);
- return fromTree(root, mSettings.useMirrorOnThickSlider());
+ return fromTree(root);
}
- private BrightnessSlider fromTree(ViewGroup root, boolean useMirror) {
+ private BrightnessSlider fromTree(ViewGroup root) {
BrightnessSliderView v = root.requireViewById(R.id.brightness_slider);
- return new BrightnessSlider(root, v, useMirror, mFalsingManager);
+ return new BrightnessSlider(root, v, mFalsingManager);
}
/** Get the layout to inflate based on what slider to use */
private int getLayout() {
- return mSettings.useThickSlider()
- ? R.layout.quick_settings_brightness_dialog_thick
- : R.layout.quick_settings_brightness_dialog;
+ return R.layout.quick_settings_brightness_dialog;
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderView.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderView.java
index 5b71c626bb22..dbd6758b090d 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderView.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderView.java
@@ -17,18 +17,13 @@
package com.android.systemui.settings.brightness;
import android.content.Context;
-import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
-import android.widget.CompoundButton;
-import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.FrameLayout;
import android.widget.SeekBar.OnSeekBarChangeListener;
-import android.widget.TextView;
import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
import com.android.settingslib.RestrictedLockUtils;
import com.android.systemui.Gefingerpoken;
@@ -37,23 +32,11 @@ import com.android.systemui.R;
/**
* {@code FrameLayout} used to show and manipulate a {@link ToggleSeekBar}.
*
- * It can additionally control a {@link CompoundButton} and display a label. For the class to work,
- * add children before inflation with the following ids:
- * <ul>
- * <li>{@code @id/slider} of type {@link ToggleSeekBar}</li>
- * <li>{@code @id/toggle} of type {@link CompoundButton} (optional)</li>
- * <li>{@code @id/label} of type {@link TextView} (optional)</li>
- * </ul>
*/
public class BrightnessSliderView extends FrameLayout {
- @Nullable
- private CompoundButton mToggle;
@NonNull
private ToggleSeekBar mSlider;
- @Nullable
- private TextView mLabel;
- private final CharSequence mText;
private DispatchTouchEventListener mListener;
private Gefingerpoken mOnInterceptListener;
@@ -62,31 +45,15 @@ public class BrightnessSliderView extends FrameLayout {
}
public BrightnessSliderView(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public BrightnessSliderView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
-
- final TypedArray a = context.obtainStyledAttributes(
- attrs, R.styleable.ToggleSliderView, defStyle, 0);
- mText = a.getString(R.styleable.ToggleSliderView_text);
-
- a.recycle();
+ super(context, attrs);
}
// Inflated from quick_settings_brightness_dialog or quick_settings_brightness_dialog_thick
@Override
protected void onFinishInflate() {
super.onFinishInflate();
- mToggle = findViewById(R.id.toggle);
mSlider = requireViewById(R.id.slider);
-
- mLabel = findViewById(R.id.label);
- if (mLabel != null) {
- mLabel.setText(mText);
- }
mSlider.setAccessibilityLabel(getContentDescription().toString());
}
@@ -125,25 +92,11 @@ public class BrightnessSliderView extends FrameLayout {
}
/**
- * Attaches a listener to the {@link CompoundButton} in the view (if present) so changes to its
- * state can be observed
- * @param checkListener use {@code null} to remove listener
- */
- public void setOnCheckedChangeListener(OnCheckedChangeListener checkListener) {
- if (mToggle != null) {
- mToggle.setOnCheckedChangeListener(checkListener);
- }
- }
-
- /**
* Enforces admin rules for toggling auto-brightness and changing value of brightness
* @param admin
* @see ToggleSeekBar#setEnforcedAdmin
*/
public void setEnforcedAdmin(RestrictedLockUtils.EnforcedAdmin admin) {
- if (mToggle != null) {
- mToggle.setEnabled(admin == null);
- }
mSlider.setEnabled(admin == null);
mSlider.setEnforcedAdmin(admin);
}
@@ -157,26 +110,6 @@ public class BrightnessSliderView extends FrameLayout {
}
/**
- * Sets the state of the {@link CompoundButton} if present
- * @param checked
- */
- public void setChecked(boolean checked) {
- if (mToggle != null) {
- mToggle.setChecked(checked);
- }
- }
-
- /**
- * @return the state of the {@link CompoundButton} if present, or {@code true} if not.
- */
- public boolean isChecked() {
- if (mToggle != null) {
- return mToggle.isChecked();
- }
- return true;
- }
-
- /**
* @return the maximum value of the {@link ToggleSeekBar}.
*/
public int getMax() {
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/ToggleSlider.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/ToggleSlider.java
index 71e4818c605a..a988c7aeb436 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/ToggleSlider.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/ToggleSlider.java
@@ -23,8 +23,7 @@ import com.android.systemui.statusbar.policy.BrightnessMirrorController;
public interface ToggleSlider {
interface Listener {
- void onChanged(boolean tracking, boolean automatic, int value,
- boolean stopTracking);
+ void onChanged(boolean tracking, int value, boolean stopTracking);
}
void setEnforcedAdmin(RestrictedLockUtils.EnforcedAdmin admin);
@@ -32,8 +31,6 @@ public interface ToggleSlider {
boolean mirrorTouchEvent(MotionEvent ev);
void setOnChangedListener(Listener l);
- default void setChecked(boolean checked) {}
- default boolean isChecked() { return false; }
void setMax(int max);
int getMax();
void setValue(int value);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java b/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java
index 90c3dfcee5bf..f51fbedebad2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java
@@ -62,11 +62,6 @@ public class FeatureFlags {
return mFlagReader.isEnabled(R.bool.flag_keyguard_layout);
}
- /** b/178485354 */
- public boolean useNewBrightnessSlider() {
- return mFlagReader.isEnabled(R.bool.flag_brightness_slider);
- }
-
public boolean useNewLockscreenAnimations() {
return mFlagReader.isEnabled(R.bool.flag_lockscreen_animations);
}
@@ -98,4 +93,8 @@ public class FeatureFlags {
public boolean isChargingRippleEnabled() {
return mFlagReader.isEnabled(R.bool.flag_charging_ripple);
}
+
+ public boolean isOngoingCallStatusBarChipEnabled() {
+ return mFlagReader.isEnabled(R.bool.flag_ongoing_call_status_bar_chip);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGroupingUtil.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGroupingUtil.java
index e090d0b13205..1c5df4119a24 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGroupingUtil.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGroupingUtil.java
@@ -161,22 +161,22 @@ public class NotificationGroupingUtil {
private void sanitizeTopLineViews(ExpandableNotificationRow row) {
if (row.isSummaryWithChildren()) {
- sanitizeTopLine(row.getNotificationViewWrapper().getNotificationHeader());
+ sanitizeTopLine(row.getNotificationViewWrapper().getNotificationHeader(), row);
return;
}
final NotificationContentView layout = row.getPrivateLayout();
- sanitizeChild(layout.getContractedChild());
- sanitizeChild(layout.getHeadsUpChild());
- sanitizeChild(layout.getExpandedChild());
+ sanitizeChild(layout.getContractedChild(), row);
+ sanitizeChild(layout.getHeadsUpChild(), row);
+ sanitizeChild(layout.getExpandedChild(), row);
}
- private void sanitizeChild(View child) {
+ private void sanitizeChild(View child, ExpandableNotificationRow row) {
if (child != null) {
- sanitizeTopLine(child.findViewById(R.id.notification_top_line));
+ sanitizeTopLine(child.findViewById(R.id.notification_top_line), row);
}
}
- private void sanitizeTopLine(ViewGroup rowHeader) {
+ private void sanitizeTopLine(ViewGroup rowHeader, ExpandableNotificationRow row) {
if (rowHeader == null) {
return;
}
@@ -195,7 +195,7 @@ public class NotificationGroupingUtil {
}
// in case no view is visible we make sure the time is visible
int timeVisibility = !hasVisibleText
- || mRow.getEntry().getSbn().getNotification().showsTime()
+ || row.getEntry().getSbn().getNotification().showsTime()
? View.VISIBLE : View.GONE;
time.setVisibility(timeVisibility);
View left = null;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
index c565a271bdd7..5782d38a9281 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
@@ -24,6 +24,7 @@ import android.annotation.MainThread;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.Notification;
+import android.app.smartspace.SmartspaceTarget;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
@@ -248,6 +249,11 @@ public class NotificationMediaManager implements Dumpable {
}
@Override
+ public void onSmartspaceMediaDataLoaded(@NonNull String key,
+ @NonNull SmartspaceTarget data) {
+ }
+
+ @Override
public void onMediaDataRemoved(@NonNull String key) {
mNotifPipeline.getAllNotifs()
.stream()
@@ -260,6 +266,9 @@ public class NotificationMediaManager implements Dumpable {
getDismissedByUserStats(entry));
});
}
+
+ @Override
+ public void onSmartspaceMediaDataRemoved(@NonNull String key) {}
});
}
@@ -313,6 +322,11 @@ public class NotificationMediaManager implements Dumpable {
}
@Override
+ public void onSmartspaceMediaDataLoaded(@NonNull String key,
+ @NonNull SmartspaceTarget data) {
+ }
+
+ @Override
public void onMediaDataRemoved(@NonNull String key) {
NotificationEntry entry = mEntryManager.getPendingOrActiveNotif(key);
if (entry != null) {
@@ -323,6 +337,9 @@ public class NotificationMediaManager implements Dumpable {
NotificationListenerService.REASON_CANCEL);
}
}
+
+ @Override
+ public void onSmartspaceMediaDataRemoved(@NonNull String key) {}
});
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
index 3daa2b3e1e64..f57fd21526b9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
@@ -173,7 +173,7 @@ public class NotificationShelf extends ActivatableNotificationView implements
getFullyClosedTranslation());
viewState.zTranslation = ambientState.getBaseZHeight();
viewState.clipTopAmount = 0;
- viewState.alpha = 1;
+ viewState.alpha = 1f - ambientState.getHideAmount();
viewState.belowSpeedBump = mHostLayoutController.getSpeedBumpIndex() == 0;
viewState.hideSensitive = false;
viewState.xTranslation = getTranslationX();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/charging/ChargingRippleView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/charging/ChargingRippleView.kt
index 05af08e0287a..77b418670ca6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/charging/ChargingRippleView.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/charging/ChargingRippleView.kt
@@ -20,13 +20,14 @@ import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.animation.ValueAnimator
import android.content.Context
+import android.content.res.Configuration
import android.graphics.Canvas
import android.graphics.Paint
import android.graphics.PointF
import android.util.AttributeSet
import android.view.View
-private const val RIPPLE_SPARKLE_STRENGTH: Float = 0.4f
+private const val RIPPLE_SPARKLE_STRENGTH: Float = 0.3f
/**
* Expanding ripple effect that shows when charging begins.
@@ -41,7 +42,7 @@ class ChargingRippleView(context: Context?, attrs: AttributeSet?) : View(context
set(value) { rippleShader.radius = value }
var origin: PointF = PointF()
set(value) { rippleShader.origin = value }
- var duration: Long = 1500
+ var duration: Long = 1750
init {
rippleShader.color = defaultColor
@@ -51,6 +52,16 @@ class ChargingRippleView(context: Context?, attrs: AttributeSet?) : View(context
visibility = View.GONE
}
+ override fun onConfigurationChanged(newConfig: Configuration?) {
+ rippleShader.pixelDensity = resources.displayMetrics.density
+ super.onConfigurationChanged(newConfig)
+ }
+
+ override fun onAttachedToWindow() {
+ rippleShader.pixelDensity = resources.displayMetrics.density
+ super.onAttachedToWindow()
+ }
+
fun startRipple() {
if (rippleInProgress) {
return // Ignore if ripple effect is already playing
@@ -59,9 +70,10 @@ class ChargingRippleView(context: Context?, attrs: AttributeSet?) : View(context
animator.duration = duration
animator.addUpdateListener { animator ->
val now = animator.currentPlayTime
- val phase = now / 30000f
- rippleShader.progress = animator.animatedValue as Float
- rippleShader.noisePhase = phase
+ val progress = animator.animatedValue as Float
+ rippleShader.progress = progress
+ rippleShader.distortionStrength = 1 - progress
+ rippleShader.time = now.toFloat()
invalidate()
}
animator.addListener(object : AnimatorListenerAdapter() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/charging/RippleShader.kt b/packages/SystemUI/src/com/android/systemui/statusbar/charging/RippleShader.kt
index d400205af50b..146046b33375 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/charging/RippleShader.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/charging/RippleShader.kt
@@ -18,6 +18,7 @@ package com.android.systemui.statusbar.charging
import android.graphics.Color
import android.graphics.PointF
import android.graphics.RuntimeShader
+import android.util.MathUtils
/**
* Shader class that renders an expanding charging ripple effect. A charging ripple contains
@@ -33,7 +34,15 @@ class RippleShader internal constructor() : RuntimeShader(SHADER, false) {
private const val SHADER_UNIFORMS = """uniform vec2 in_origin;
uniform float in_progress;
uniform float in_maxRadius;
- uniform float in_noisePhase;
+ uniform float in_time;
+ uniform float in_distort_radial;
+ uniform float in_distort_xy;
+ uniform float in_radius;
+ uniform float in_fadeSparkle;
+ uniform float in_fadeCircle;
+ uniform float in_fadeRing;
+ uniform float in_blur;
+ uniform float in_pixelDensity;
uniform vec4 in_color;
uniform float in_sparkle_strength;"""
private const val SHADER_LIB = """float triangleNoise(vec2 n) {
@@ -52,12 +61,13 @@ class RippleShader internal constructor() : RuntimeShader(SHADER, false) {
float n = triangleNoise(uv);
float s = 0.0;
for (float i = 0; i < 4; i += 1) {
- float l = i * 0.25;
- float h = l + 0.005;
- float o = abs(sin(0.1 * PI * (t + i)));
- s += threshold(n + o, l, h);
+ float l = i * 0.01;
+ float h = l + 0.1;
+ float o = smoothstep(n - l, h, n);
+ o *= abs(sin(PI * o * (t + 0.55 * i)));
+ s += o;
}
- return saturate(s);
+ return s;
}
float softCircle(vec2 uv, vec2 xy, float radius, float blur) {
@@ -67,71 +77,98 @@ class RippleShader internal constructor() : RuntimeShader(SHADER, false) {
}
float softRing(vec2 uv, vec2 xy, float radius, float blur) {
- float thickness = 0.4;
- float circle_outer = softCircle(uv, xy,
- radius + thickness * radius * 0.5, blur);
- float circle_inner = softCircle(uv, xy,
- radius - thickness * radius * 0.5, blur);
+ float thickness_half = radius * 0.25;
+ float circle_outer = softCircle(uv, xy, radius + thickness_half, blur);
+ float circle_inner = softCircle(uv, xy, radius - thickness_half, blur);
return circle_outer - circle_inner;
}
- float subProgress(float start, float end, float progress) {
- float sub = clamp(progress, start, end);
- return (sub - start) / (end - start);
- }
-
- float smoothstop2(float t) {
- return 1 - (1 - t) * (1 - t);
+ vec2 distort(vec2 p, vec2 origin, float time,
+ float distort_amount_radial, float distort_amount_xy) {
+ float2 distance = origin - p;
+ float angle = atan(distance.y, distance.x);
+ return p + vec2(sin(angle * 8 + time * 0.003 + 1.641),
+ cos(angle * 5 + 2.14 + time * 0.00412)) * distort_amount_radial
+ + vec2(sin(p.x * 0.01 + time * 0.00215 + 0.8123),
+ cos(p.y * 0.01 + time * 0.005931)) * distort_amount_xy;
}"""
private const val SHADER_MAIN = """vec4 main(vec2 p) {
- float fadeIn = subProgress(0., 0.1, in_progress);
- float fadeOutNoise = subProgress(0.8, 1., in_progress);
- float fadeOutRipple = subProgress(0.7, 1., in_progress);
- float fadeCircle = subProgress(0., 0.5, in_progress);
- float radius = smoothstop2(in_progress) * in_maxRadius;
- float sparkleRing = softRing(p, in_origin, radius, 0.5);
- float sparkleAlpha = min(fadeIn, 1. - fadeOutNoise);
- float sparkle = sparkles(p, in_noisePhase) * sparkleRing * sparkleAlpha;
- float circle = softCircle(p, in_origin, radius * 1.2, 0.5)
- * (1 - fadeCircle);
- float fadeRipple = min(fadeIn, 1.-fadeOutRipple);
- float rippleAlpha = softRing(p, in_origin, radius, 0.5)
- * fadeRipple * in_color.a;
- vec4 ripple = in_color * max(circle, rippleAlpha) * 0.3;
+ vec2 p_distorted = distort(p, in_origin, in_time, in_distort_radial,
+ in_distort_xy);
+
+ // Draw shapes
+ float sparkleRing = softRing(p_distorted, in_origin, in_radius, in_blur);
+ float sparkle = sparkles(p - mod(p, in_pixelDensity * 0.8), in_time * 0.00175)
+ * sparkleRing * in_fadeSparkle;
+ float circle = softCircle(p_distorted, in_origin, in_radius * 1.2, in_blur);
+ float rippleAlpha = max(circle * in_fadeCircle,
+ softRing(p_distorted, in_origin, in_radius, in_blur) * in_fadeRing) * 0.45;
+ vec4 ripple = in_color * rippleAlpha;
return mix(ripple, vec4(sparkle), sparkle * in_sparkle_strength);
}"""
private const val SHADER = SHADER_UNIFORMS + SHADER_LIB + SHADER_MAIN
+
+ private fun subProgress(start: Float, end: Float, progress: Float): Float {
+ val min = Math.min(start, end)
+ val max = Math.max(start, end)
+ val sub = Math.min(Math.max(progress, min), max)
+ return (sub - start) / (end - start)
+ }
}
/**
* Maximum radius of the ripple.
*/
var radius: Float = 0.0f
- set(value) { setUniform("in_maxRadius", value) }
+ set(value) {
+ field = value
+ setUniform("in_maxRadius", value)
+ }
/**
* Origin coordinate of the ripple.
*/
var origin: PointF = PointF()
- set(value) { setUniform("in_origin", floatArrayOf(value.x, value.y)) }
+ set(value) {
+ field = value
+ setUniform("in_origin", floatArrayOf(value.x, value.y))
+ }
/**
* Progress of the ripple. Float value between [0, 1].
*/
var progress: Float = 0.0f
- set(value) { setUniform("in_progress", value) }
+ set(value) {
+ field = value
+ setUniform("in_progress", value)
+ setUniform("in_radius",
+ (1 - (1 - value) * (1 - value) * (1 - value))* radius)
+ setUniform("in_blur", MathUtils.lerp(1.25f, 0.5f, value))
+
+ val fadeIn = subProgress(0f, 0.1f, value)
+ val fadeOutNoise = subProgress(0.4f, 1f, value)
+ val fadeOutRipple = subProgress(0.3f, 1f, value)
+ val fadeCircle = subProgress(0f, 0.2f, value)
+ setUniform("in_fadeSparkle", Math.min(fadeIn, 1 - fadeOutNoise))
+ setUniform("in_fadeCircle", 1 - fadeCircle)
+ setUniform("in_fadeRing", Math.min(fadeIn, 1 - fadeOutRipple))
+ }
/**
- * Continuous offset used as noise phase.
+ * Play time since the start of the effect.
*/
- var noisePhase: Float = 0.0f
- set(value) { setUniform("in_noisePhase", value) }
+ var time: Float = 0.0f
+ set(value) {
+ field = value
+ setUniform("in_time", value)
+ }
/**
* A hex value representing the ripple color, in the format of ARGB
*/
var color: Int = 0xffffff.toInt()
set(value) {
+ field = value
val color = Color.valueOf(value)
setUniform("in_color", floatArrayOf(color.red(),
color.green(), color.blue(), color.alpha()))
@@ -143,5 +180,24 @@ class RippleShader internal constructor() : RuntimeShader(SHADER, false) {
* it's opaque white and looks the most grainy.
*/
var sparkleStrength: Float = 0.0f
- set(value) { setUniform("in_sparkle_strength", value) }
+ set(value) {
+ field = value
+ setUniform("in_sparkle_strength", value)
+ }
+
+ /**
+ * Distortion strength of the ripple. Expected value between[0, 1].
+ */
+ var distortionStrength: Float = 0.0f
+ set(value) {
+ field = value
+ setUniform("in_distort_radial", 75 * progress * value)
+ setUniform("in_distort_xy", 75 * value)
+ }
+
+ var pixelDensity: Float = 1.0f
+ set(value) {
+ field = value
+ setUniform("in_pixelDensity", value)
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java
index efd0519d6608..14c73b5cbb4c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java
@@ -49,6 +49,7 @@ import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import com.android.systemui.statusbar.notification.collection.inflation.LowPriorityInflationHelper;
import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager;
+import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
import com.android.systemui.statusbar.notification.stack.ForegroundServiceSectionController;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.phone.ManagedProfileController;
@@ -57,6 +58,7 @@ import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.statusbar.phone.StatusBarIconController;
import com.android.systemui.statusbar.phone.StatusBarIconControllerImpl;
import com.android.systemui.statusbar.phone.StatusBarRemoteInputCallback;
+import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController;
import com.android.systemui.statusbar.policy.RemoteInputUriController;
import com.android.systemui.tracing.ProtoTracer;
import com.android.systemui.util.DeviceConfigProxy;
@@ -226,4 +228,16 @@ public interface StatusBarDependenciesModule {
@Binds
StatusBarIconController provideStatusBarIconController(
StatusBarIconControllerImpl controllerImpl);
+
+ /**
+ */
+ @Provides
+ @SysUISingleton
+ static OngoingCallController provideOngoingCallController(
+ CommonNotifCollection notifCollection, FeatureFlags featureFlags) {
+ OngoingCallController ongoingCallController =
+ new OngoingCallController(notifCollection, featureFlags);
+ ongoingCallController.init();
+ return ongoingCallController;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionListener.java
index 75d772e8a1c5..db0c1745f565 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionListener.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionListener.java
@@ -57,7 +57,7 @@ public interface NotifCollectionListener {
* Called whenever a notification with the same key as an existing notification is posted. By
* the time this listener is called, the entry's SBN and Ranking will already have been updated.
*/
- default void onEntryUpdated(NotificationEntry entry) {
+ default void onEntryUpdated(@NonNull NotificationEntry entry) {
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
index c3ccba4c3771..a0b0b3dc57bd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
@@ -873,14 +873,19 @@ public class NotificationContentView extends FrameLayout {
}
public void setBackgroundTintColor(int color) {
+ boolean colorized = mNotificationEntry.getSbn().getNotification().isColorized();
if (mExpandedSmartReplyView != null) {
- boolean colorized = mNotificationEntry.getSbn().getNotification().isColorized();
mExpandedSmartReplyView.setBackgroundTintColor(color, colorized);
}
if (mHeadsUpSmartReplyView != null) {
- boolean colorized = mNotificationEntry.getSbn().getNotification().isColorized();
mHeadsUpSmartReplyView.setBackgroundTintColor(color, colorized);
}
+ if (mExpandedRemoteInput != null) {
+ mExpandedRemoteInput.setBackgroundTintColor(color, colorized);
+ }
+ if (mHeadsUpRemoteInput != null) {
+ mHeadsUpRemoteInput.setBackgroundTintColor(color, colorized);
+ }
}
public int getVisibleType() {
@@ -1243,8 +1248,7 @@ public class NotificationContentView extends FrameLayout {
View actionContainerCandidate = view.findViewById(
com.android.internal.R.id.actions_container);
if (actionContainerCandidate instanceof FrameLayout) {
- RemoteInputView existing = (RemoteInputView)
- view.findViewWithTag(RemoteInputView.VIEW_TAG);
+ RemoteInputView existing = view.findViewWithTag(RemoteInputView.VIEW_TAG);
if (existing != null) {
existing.onNotificationUpdateOrReset();
@@ -1292,13 +1296,9 @@ public class NotificationContentView extends FrameLayout {
}
}
if (existing != null) {
- if (entry.getSbn().getNotification().isColorized()) {
- existing.setBackgroundTintColor(
- entry.getSbn().getNotification().color, true);
- } else {
- existing.setBackgroundTintColor(
- entry.getRow().getCurrentBackgroundTint(), false);
- }
+ int backgroundColor = entry.getRow().getCurrentBackgroundTint();
+ boolean colorized = mNotificationEntry.getSbn().getNotification().isColorized();
+ existing.setBackgroundTintColor(backgroundColor, colorized);
}
return existing;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
index b4ab8cf817dd..8ba036ce03c3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
@@ -346,9 +346,22 @@ public class NotificationConversationInfo extends LinearLayout implements
}
private void bindIcon(boolean important) {
+ Drawable person = mIconFactory.getBaseIconDrawable(mShortcutInfo);
+ if (person == null) {
+ person = mContext.getDrawable(R.drawable.ic_person).mutate();
+ TypedArray ta = mContext.obtainStyledAttributes(new int[]{android.R.attr.colorAccent});
+ int colorAccent = ta.getColor(0, 0);
+ ta.recycle();
+ person.setTint(colorAccent);
+ }
ImageView image = findViewById(R.id.conversation_icon);
- image.setImageDrawable(mIconFactory.getConversationDrawable(
- mShortcutInfo, mPackageName, mAppUid, important));
+ image.setImageDrawable(person);
+
+ ImageView app = findViewById(R.id.conversation_icon_badge_icon);
+ app.setImageDrawable(mIconFactory.getAppBadge(
+ mPackageName, UserHandle.getUserId(mSbn.getUid())));
+
+ findViewById(R.id.conversation_icon_badge_ring).setVisibility(important ? VISIBLE : GONE);
}
private void bindPackage() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
index 1d307364d661..5f3933b827c4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
@@ -105,7 +105,6 @@ public class NotificationChildrenContainer extends ViewGroup {
private ViewGroup mCurrentHeader;
private boolean mIsConversation;
- private boolean mTintWithThemeAccent;
private boolean mShowGroupCountInExpander;
private boolean mShowDividersWhenExpanded;
private boolean mHideDividersDuringExpand;
@@ -149,8 +148,6 @@ public class NotificationChildrenContainer extends ViewGroup {
com.android.internal.R.dimen.notification_content_margin);
mEnableShadowOnChildNotifications =
res.getBoolean(R.bool.config_enableShadowOnChildNotifications);
- mTintWithThemeAccent =
- res.getBoolean(com.android.internal.R.bool.config_tintNotificationsWithTheme);
mShowGroupCountInExpander =
res.getBoolean(R.bool.config_showNotificationGroupCountInExpander);
mShowDividersWhenExpanded =
@@ -1223,14 +1220,11 @@ public class NotificationChildrenContainer extends ViewGroup {
return;
}
int color = mContainingNotification.getNotificationColor();
- if (mTintWithThemeAccent) {
- // We're using the theme accent, color with the accent color instead of the notif color
- Resources.Theme theme = new ContextThemeWrapper(mContext,
- com.android.internal.R.style.Theme_DeviceDefault_DayNight).getTheme();
- TypedArray ta = theme.obtainStyledAttributes(
- new int[]{com.android.internal.R.attr.colorAccent});
+ Resources.Theme theme = new ContextThemeWrapper(mContext,
+ com.android.internal.R.style.Theme_DeviceDefault_DayNight).getTheme();
+ try (TypedArray ta = theme.obtainStyledAttributes(
+ new int[]{com.android.internal.R.attr.colorAccent})) {
color = ta.getColor(0, color);
- ta.recycle();
}
mHybridGroupManager.setOverflowNumberColor(mOverflowNumber, color);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index f6c1b1c50fee..40c0b895400e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -1245,7 +1245,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
}
if (mAmbientState.isHiddenAtAll()) {
- clipToOutline = true;
+ clipToOutline = false;
invalidateOutline();
if (isFullyHidden()) {
setClipBounds(null);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
index d7a98bdf2715..bbdbe809a8d8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
@@ -320,6 +320,7 @@ public class StackScrollAlgorithm {
}
int childHeight = getMaxAllowedChildHeight(child);
childViewState.yTranslation = currentYPosition;
+ childViewState.alpha = 1f - ambientState.getHideAmount();
boolean isFooterView = child instanceof FooterView;
boolean isEmptyShadeView = child instanceof EmptyShadeView;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java
index 204dd9f5e58c..88e5364cd55f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java
@@ -36,6 +36,7 @@ import com.android.systemui.statusbar.policy.CastController;
import com.android.systemui.statusbar.policy.CastController.CastDevice;
import com.android.systemui.statusbar.policy.DataSaverController;
import com.android.systemui.statusbar.policy.DataSaverController.Listener;
+import com.android.systemui.statusbar.policy.DeviceControlsController;
import com.android.systemui.statusbar.policy.HotspotController;
import com.android.systemui.statusbar.policy.HotspotController.Callback;
import com.android.systemui.util.UserAwareController;
@@ -58,6 +59,7 @@ public class AutoTileManager implements UserAwareController {
public static final String WORK = "work";
public static final String NIGHT = "night";
public static final String CAST = "cast";
+ public static final String DEVICE_CONTROLS = "controls";
public static final String BRIGHTNESS = "reduce_brightness";
static final String SETTING_SEPARATOR = ":";
@@ -74,6 +76,7 @@ public class AutoTileManager implements UserAwareController {
private final ManagedProfileController mManagedProfileController;
private final NightDisplayListener mNightDisplayListener;
private final CastController mCastController;
+ private final DeviceControlsController mDeviceControlsController;
private final ReduceBrightColorsController mReduceBrightColorsController;
private final boolean mIsReduceBrightColorsAvailable;
private final ArrayList<AutoAddSetting> mAutoAddSettingList = new ArrayList<>();
@@ -88,6 +91,7 @@ public class AutoTileManager implements UserAwareController {
NightDisplayListener nightDisplayListener,
CastController castController,
ReduceBrightColorsController reduceBrightColorsController,
+ DeviceControlsController deviceControlsController,
@Named(RBC_AVAILABLE) boolean isReduceBrightColorsAvailable) {
mContext = context;
mHost = host;
@@ -102,6 +106,7 @@ public class AutoTileManager implements UserAwareController {
mCastController = castController;
mReduceBrightColorsController = reduceBrightColorsController;
mIsReduceBrightColorsAvailable = isReduceBrightColorsAvailable;
+ mDeviceControlsController = deviceControlsController;
}
/**
@@ -138,6 +143,9 @@ public class AutoTileManager implements UserAwareController {
if (!mAutoTracker.isAdded(BRIGHTNESS) && mIsReduceBrightColorsAvailable) {
mReduceBrightColorsController.addCallback(mReduceBrightColorsCallback);
}
+ if (!mAutoTracker.isAdded(DEVICE_CONTROLS)) {
+ mDeviceControlsController.setCallback(mDeviceControlsCallback);
+ }
int settingsN = mAutoAddSettingList.size();
for (int i = 0; i < settingsN; i++) {
@@ -158,6 +166,7 @@ public class AutoTileManager implements UserAwareController {
mReduceBrightColorsController.removeCallback(mReduceBrightColorsCallback);
}
mCastController.removeCallback(mCastCallback);
+ mDeviceControlsController.removeCallback();
int settingsN = mAutoAddSettingList.size();
for (int i = 0; i < settingsN; i++) {
mAutoAddSettingList.get(i).setListening(false);
@@ -274,6 +283,17 @@ public class AutoTileManager implements UserAwareController {
}
};
+ private final DeviceControlsController.Callback mDeviceControlsCallback =
+ new DeviceControlsController.Callback() {
+ @Override
+ public void onControlsAvailable(int position) {
+ if (mAutoTracker.isAdded(DEVICE_CONTROLS)) return;
+ mHost.addTile(DEVICE_CONTROLS, position);
+ mAutoTracker.setTileAdded(DEVICE_CONTROLS);
+ mHandler.post(() -> mDeviceControlsController.removeCallback());
+ }
+ };
+
@VisibleForTesting
final NightDisplayListener.Callback mNightDisplayCallback =
new NightDisplayListener.Callback() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java
index 562d0ec06a63..f64a0e03698a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java
@@ -37,6 +37,7 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.phone.StatusBarIconController.DarkIconManager;
+import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController;
import com.android.systemui.statusbar.policy.EncryptionHelper;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.policy.NetworkController;
@@ -45,6 +46,8 @@ import com.android.systemui.statusbar.policy.NetworkController.SignalCallback;
import java.util.ArrayList;
import java.util.List;
+import javax.inject.Inject;
+
/**
* Contains the collapsed status bar and handles hiding/showing based on disable flags
* and keyguard state. Also manages lifecycle to make sure the views it contains are being
@@ -72,6 +75,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue
private DarkIconManager mDarkIconManager;
private View mOperatorNameFrame;
private CommandQueue mCommandQueue;
+ private OngoingCallController mOngoingCallController;
private List<String> mBlockedIcons = new ArrayList<>();
@@ -82,6 +86,11 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue
}
};
+ @Inject
+ public CollapsedStatusBarFragment(OngoingCallController ongoingCallController) {
+ mOngoingCallController = ongoingCallController;
+ }
+
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -110,7 +119,6 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue
mDarkIconManager.setShouldLog(true);
mBlockedIcons.add(getString(com.android.internal.R.string.status_bar_volume));
mBlockedIcons.add(getString(com.android.internal.R.string.status_bar_alarm_clock));
- mBlockedIcons.add(getString(com.android.internal.R.string.status_bar_call_strength));
mDarkIconManager.setBlockList(mBlockedIcons);
Dependency.get(StatusBarIconController.class).addIconGroup(mDarkIconManager);
mSystemIconArea = mStatusBar.findViewById(R.id.system_icon_area);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
index c23f1ad6f9c9..86ef0a727831 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
@@ -24,15 +24,20 @@ import android.os.UserHandle;
import android.provider.Settings;
import android.util.MathUtils;
+import androidx.annotation.NonNull;
+
+import com.android.systemui.Dumpable;
import com.android.systemui.R;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.doze.AlwaysOnDisplayPolicy;
import com.android.systemui.doze.DozeScreenState;
+import com.android.systemui.dump.DumpManager;
import com.android.systemui.statusbar.FeatureFlags;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.tuner.TunerService;
+import java.io.FileDescriptor;
import java.io.PrintWriter;
import javax.inject.Inject;
@@ -42,7 +47,7 @@ import javax.inject.Inject;
*/
@SysUISingleton
public class DozeParameters implements TunerService.Tunable,
- com.android.systemui.plugins.statusbar.DozeParameters {
+ com.android.systemui.plugins.statusbar.DozeParameters, Dumpable {
private static final int MAX_DURATION = 60 * 1000;
public static final boolean FORCE_NO_BLANKING =
SystemProperties.getBoolean("debug.force_no_blanking", false);
@@ -68,11 +73,13 @@ public class DozeParameters implements TunerService.Tunable,
PowerManager powerManager,
BatteryController batteryController,
TunerService tunerService,
+ DumpManager dumpManager,
FeatureFlags featureFlags) {
mResources = resources;
mAmbientDisplayConfiguration = ambientDisplayConfiguration;
mAlwaysOnPolicy = alwaysOnDisplayPolicy;
mBatteryController = batteryController;
+ dumpManager.registerDumpable("DozeParameters", this);
mControlScreenOffAnimation = !getDisplayNeedsBlanking();
mPowerManager = powerManager;
@@ -85,20 +92,6 @@ public class DozeParameters implements TunerService.Tunable,
Settings.Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED);
}
- public void dump(PrintWriter pw) {
- pw.println(" DozeParameters:");
- pw.print(" getDisplayStateSupported(): "); pw.println(getDisplayStateSupported());
- pw.print(" getPulseDuration(): "); pw.println(getPulseDuration());
- pw.print(" getPulseInDuration(): "); pw.println(getPulseInDuration());
- pw.print(" getPulseInVisibleDuration(): "); pw.println(getPulseVisibleDuration());
- pw.print(" getPulseOutDuration(): "); pw.println(getPulseOutDuration());
- pw.print(" getPulseOnSigMotion(): "); pw.println(getPulseOnSigMotion());
- pw.print(" getVibrateOnSigMotion(): "); pw.println(getVibrateOnSigMotion());
- pw.print(" getVibrateOnPickup(): "); pw.println(getVibrateOnPickup());
- pw.print(" getProxCheckBeforePulse(): "); pw.println(getProxCheckBeforePulse());
- pw.print(" getPickupVibrationThreshold(): "); pw.println(getPickupVibrationThreshold());
- }
-
public boolean getDisplayStateSupported() {
return getBoolean("doze.display.supported", R.bool.doze_display_state_supported);
}
@@ -144,6 +137,16 @@ public class DozeParameters implements TunerService.Tunable,
return getBoolean("doze.pulse.proxcheck", R.bool.doze_proximity_check_before_pulse);
}
+ /**
+ * @return true if we should only register for sensors that use the proximity sensor when the
+ * display state is {@link android.view.Display.STATE_OFF},
+ * {@link android.view.Display.STATE_DOZE} or {@link android.view.Display.STATE_DOZE_SUSPEND}
+ */
+ public boolean getSelectivelyRegisterSensorsUsingProx() {
+ return getBoolean("doze.prox.selectively_register",
+ R.bool.doze_selectively_register_prox);
+ }
+
public int getPickupVibrationThreshold() {
return getInt("doze.pickup.vibration.threshold", R.integer.doze_pickup_vibration_threshold);
}
@@ -233,8 +236,38 @@ public class DozeParameters implements TunerService.Tunable,
return mResources.getBoolean(R.bool.doze_double_tap_reports_touch_coordinates);
}
+ /**
+ * Whether the single tap sensor uses the proximity sensor.
+ */
+ public boolean singleTapUsesProx() {
+ return mResources.getBoolean(R.bool.doze_single_tap_uses_prox);
+ }
+
+ /**
+ * Whether the long press sensor uses the proximity sensor.
+ */
+ public boolean longPressUsesProx() {
+ return mResources.getBoolean(R.bool.doze_long_press_uses_prox);
+ }
+
@Override
public void onTuningChanged(String key, String newValue) {
mDozeAlwaysOn = mAmbientDisplayConfiguration.alwaysOnEnabled(UserHandle.USER_CURRENT);
}
+
+ @Override
+ public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) {
+ pw.print("getDisplayStateSupported(): "); pw.println(getDisplayStateSupported());
+ pw.print("getPulseDuration(): "); pw.println(getPulseDuration());
+ pw.print("getPulseInDuration(): "); pw.println(getPulseInDuration());
+ pw.print("getPulseInVisibleDuration(): "); pw.println(getPulseVisibleDuration());
+ pw.print("getPulseOutDuration(): "); pw.println(getPulseOutDuration());
+ pw.print("getPulseOnSigMotion(): "); pw.println(getPulseOnSigMotion());
+ pw.print("getVibrateOnSigMotion(): "); pw.println(getVibrateOnSigMotion());
+ pw.print("getVibrateOnPickup(): "); pw.println(getVibrateOnPickup());
+ pw.print("getProxCheckBeforePulse(): "); pw.println(getProxCheckBeforePulse());
+ pw.print("getPickupVibrationThreshold(): "); pw.println(getPickupVibrationThreshold());
+ pw.print("getSelectivelyRegisterSensorsUsingProx(): ");
+ pw.println(getSelectivelyRegisterSensorsUsingProx());
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
index 6b5a908fc09b..4b72b7f2f78a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
@@ -74,6 +74,7 @@ import com.android.systemui.Dependency;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
import com.android.systemui.assist.AssistManager;
+import com.android.systemui.camera.CameraIntents;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.IntentButtonProvider;
import com.android.systemui.plugins.IntentButtonProvider.IntentButton;
@@ -111,11 +112,6 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL
private static final String RIGHT_BUTTON_PLUGIN
= "com.android.systemui.action.PLUGIN_LOCKSCREEN_RIGHT_BUTTON";
- private static final Intent SECURE_CAMERA_INTENT =
- new Intent(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE)
- .addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
- public static final Intent INSECURE_CAMERA_INTENT =
- new Intent(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA);
private static final Intent PHONE_INTENT = new Intent(Intent.ACTION_DIAL);
private static final int DOZE_ANIMATION_STAGGER_DELAY = 48;
private static final int DOZE_ANIMATION_ELEMENT_DURATION = 250;
@@ -502,7 +498,7 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL
intent.putExtra(EXTRA_CAMERA_LAUNCH_SOURCE, source);
boolean wouldLaunchResolverActivity = mActivityIntentHelper.wouldLaunchResolverActivity(
intent, KeyguardUpdateMonitor.getCurrentUser());
- if (intent == SECURE_CAMERA_INTENT && !wouldLaunchResolverActivity) {
+ if (CameraIntents.isSecureCameraIntent(intent) && !wouldLaunchResolverActivity) {
AsyncTask.execute(new Runnable() {
@Override
public void run() {
@@ -862,7 +858,11 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL
public Intent getIntent() {
boolean canDismissLs = mKeyguardStateController.canDismissLockScreen();
boolean secure = mKeyguardStateController.isMethodSecure();
- return (secure && !canDismissLs) ? SECURE_CAMERA_INTENT : INSECURE_CAMERA_INTENT;
+ if (secure && !canDismissLs) {
+ return CameraIntents.getSecureCameraIntent(getContext());
+ } else {
+ return CameraIntents.getInsecureCameraIntent(getContext());
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
index 6b69103f6030..5ff9b703924e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
@@ -555,6 +555,13 @@ public class KeyguardBouncer {
pw.println(" mIsAnimatingAway: " + mIsAnimatingAway);
}
+ /** Update keyguard position based on a tapped X coordinate. */
+ public void updateKeyguardPosition(float x) {
+ if (mKeyguardViewController != null) {
+ mKeyguardViewController.updateKeyguardPosition(x);
+ }
+ }
+
public interface BouncerExpansionCallback {
void onFullyShown();
void onStartingToHide();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt
index 242bd0a29d2f..707135c3d95b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt
@@ -124,7 +124,7 @@ open class KeyguardBypassController : Dumpable {
biometricSourceType: BiometricSourceType,
isStrongBiometric: Boolean
): Boolean {
- if (bypassEnabled) {
+ if (biometricSourceType == BiometricSourceType.FACE && bypassEnabled) {
val can = canBypass()
if (!can && (isPulseExpanding || qSExpanded)) {
pendingUnlock = PendingUnlock(biometricSourceType, isStrongBiometric)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
index 2d760e6fc176..3f8e9d372d82 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
@@ -189,7 +189,6 @@ public class KeyguardStatusBarView extends RelativeLayout
Resources r = getResources();
mBlockedIcons.add(r.getString(com.android.internal.R.string.status_bar_volume));
mBlockedIcons.add(r.getString(com.android.internal.R.string.status_bar_alarm_clock));
- mBlockedIcons.add(r.getString(com.android.internal.R.string.status_bar_call_strength));
}
private void updateVisibilities() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
index d3da0bce0a15..555df5c37ee3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
@@ -2625,8 +2625,7 @@ public class NotificationPanelViewController extends PanelViewController {
return;
}
mSectionPadding = padding;
- mQsFrame.setTranslationY(padding);
- mNotificationStackScrollLayoutController.setSectionPadding(padding);
+ // TODO(b/172289889) update overscroll to spec
}
@Override
@@ -3495,6 +3494,12 @@ public class NotificationPanelViewController extends PanelViewController {
updateHorizontalPanelPosition(event.getX());
handled = true;
}
+
+ if (event.getActionMasked() == MotionEvent.ACTION_DOWN && isFullyExpanded()
+ && mStatusBarKeyguardViewManager.isShowing()) {
+ mStatusBarKeyguardViewManager.updateKeyguardPosition(event.getX());
+ }
+
handled |= super.onTouch(v, event);
return !mDozing || mPulsing || handled || showingOrAnimatingAltAuth;
}
@@ -3538,7 +3543,8 @@ public class NotificationPanelViewController extends PanelViewController {
mStatusBarStateController,
mUpdateMonitor,
mAuthController,
- mStatusBarKeyguardViewManager);
+ mStatusBarKeyguardViewManager,
+ mKeyguardStateController);
mDisabledUdfpsController.init();
} else if (mDisabledUdfpsController != null && !udfpsEnrolled) {
mDisabledUdfpsController.destroy();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowView.java
index af595b60daa1..72f169564c10 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowView.java
@@ -49,7 +49,6 @@ import android.view.WindowInsets;
import android.view.WindowInsetsController;
import android.widget.FrameLayout;
-import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.view.FloatingActionMode;
import com.android.internal.widget.FloatingToolbar;
import com.android.systemui.R;
@@ -174,7 +173,11 @@ public class NotificationShadeWindowView extends FrameLayout {
public boolean dispatchTouchEvent(MotionEvent ev) {
Boolean result = mInteractionEventHandler.handleDispatchTouchEvent(ev);
- return result != null ? result : super.dispatchTouchEvent(ev);
+ result = result != null ? result : super.dispatchTouchEvent(ev);
+
+ mInteractionEventHandler.dispatchTouchEventComplete();
+
+ return result;
}
@Override
@@ -346,6 +349,12 @@ public class NotificationShadeWindowView extends FrameLayout {
Boolean handleDispatchTouchEvent(MotionEvent ev);
/**
+ * Called after all dispatching is done.
+ */
+
+ void dispatchTouchEventComplete();
+
+ /**
* Returns if the view should intercept the touch event.
*
* The touch event may still be interecepted if
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java
index 5595ae7ed820..2ff7c9933edf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java
@@ -292,6 +292,11 @@ public class NotificationShadeWindowViewController {
}
@Override
+ public void dispatchTouchEventComplete() {
+ mFalsingCollector.onMotionEventComplete();
+ }
+
+ @Override
public boolean shouldInterceptTouchEvent(MotionEvent ev) {
if (mStatusBarStateController.isDozing() && !mService.isPulsing()
&& !mDockManager.isDocked()) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index 4d42f4537b57..380000ee9b4e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -150,6 +150,7 @@ import com.android.systemui.SystemUI;
import com.android.systemui.accessibility.floatingmenu.AccessibilityFloatingMenuController;
import com.android.systemui.assist.AssistManager;
import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.camera.CameraIntents;
import com.android.systemui.charging.WirelessChargingAnimation;
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.colorextraction.SysuiColorExtractor;
@@ -224,6 +225,7 @@ import com.android.systemui.statusbar.notification.stack.NotificationStackScroll
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
import com.android.systemui.statusbar.phone.dagger.StatusBarComponent;
import com.android.systemui.statusbar.phone.dagger.StatusBarPhoneModule;
+import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.BrightnessMirrorController;
import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -237,6 +239,7 @@ import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
import com.android.systemui.statusbar.policy.RemoteInputQuickSettingsDisabler;
import com.android.systemui.statusbar.policy.UserInfoControllerImpl;
import com.android.systemui.statusbar.policy.UserSwitcherController;
+import com.android.systemui.tuner.TunerService;
import com.android.systemui.volume.VolumeComponent;
import com.android.systemui.wmshell.BubblesManager;
import com.android.wm.shell.bubbles.Bubbles;
@@ -423,6 +426,7 @@ public class StatusBar extends SystemUI implements DemoMode,
private final DismissCallbackRegistry mDismissCallbackRegistry;
private final DemoModeController mDemoModeController;
private NotificationsController mNotificationsController;
+ private final OngoingCallController mOngoingCallController;
// expanded notifications
// the sliding/resizing panel within the notification window
@@ -787,6 +791,8 @@ public class StatusBar extends SystemUI implements DemoMode,
NotificationIconAreaController notificationIconAreaController,
BrightnessSlider.Factory brightnessSliderFactory,
WiredChargingRippleController chargingRippleAnimationController,
+ OngoingCallController ongoingCallController,
+ TunerService tunerService,
FeatureFlags featureFlags) {
super(context);
mNotificationsController = notificationsController;
@@ -866,8 +872,18 @@ public class StatusBar extends SystemUI implements DemoMode,
mNotificationIconAreaController = notificationIconAreaController;
mBrightnessSliderFactory = brightnessSliderFactory;
mChargingRippleAnimationController = chargingRippleAnimationController;
+ mOngoingCallController = ongoingCallController;
mFeatureFlags = featureFlags;
+ tunerService.addTunable(
+ (key, newValue) -> {
+ if (key.equals(Settings.Secure.DOZE_ALWAYS_ON)) {
+ updateLightRevealScrimVisibility();
+ }
+ },
+ Settings.Secure.DOZE_ALWAYS_ON
+ );
+
mExpansionChangedListeners = new ArrayList<>();
mBubbleExpandListener =
@@ -1152,7 +1168,8 @@ public class StatusBar extends SystemUI implements DemoMode,
checkBarModes();
}).getFragmentManager()
.beginTransaction()
- .replace(R.id.status_bar_container, new CollapsedStatusBarFragment(),
+ .replace(R.id.status_bar_container,
+ new CollapsedStatusBarFragment(mOngoingCallController),
CollapsedStatusBarFragment.TAG)
.commit();
@@ -1210,15 +1227,7 @@ public class StatusBar extends SystemUI implements DemoMode,
mLightRevealScrim = mNotificationShadeWindowView.findViewById(R.id.light_reveal_scrim);
mChargingRippleAnimationController.setViewHost(mNotificationShadeWindowView);
-
-
- if (mFeatureFlags.useNewLockscreenAnimations()
- && (mDozeParameters.getAlwaysOn() || mDozeParameters.isQuickPickupEnabled())) {
- mLightRevealScrim.setVisibility(View.VISIBLE);
- mLightRevealScrim.setRevealEffect(LiftReveal.INSTANCE);
- } else {
- mLightRevealScrim.setVisibility(View.GONE);
- }
+ updateLightRevealScrimVisibility();
mNotificationPanelViewController.initDependencies(
this,
@@ -2607,7 +2616,8 @@ public class StatusBar extends SystemUI implements DemoMode,
" (auto: " + UiModeManager.MODE_NIGHT_AUTO +
", yes: " + UiModeManager.MODE_NIGHT_YES +
", no: " + UiModeManager.MODE_NIGHT_NO + ")");
- final boolean lightWpTheme = mContext.getThemeResId() == R.style.Theme_SystemUI_Light;
+ final boolean lightWpTheme = mContext.getThemeResId()
+ == R.style.Theme_SystemUI_LightWallpaper;
pw.println(" light wallpaper theme: " + lightWpTheme);
if (mKeyguardIndicationController != null) {
@@ -2662,6 +2672,12 @@ public class StatusBar extends SystemUI implements DemoMode,
for (Map.Entry<String, ?> entry : Prefs.getAll(mContext).entrySet()) {
pw.print(" "); pw.print(entry.getKey()); pw.print("="); pw.println(entry.getValue());
}
+
+ pw.println("Camera gesture intents:");
+ pw.println(" Insecure camera: " + CameraIntents.getInsecureCameraIntent(mContext));
+ pw.println(" Secure camera: " + CameraIntents.getSecureCameraIntent(mContext));
+ pw.println(" Override package: "
+ + String.valueOf(CameraIntents.getOverrideCameraPackage(mContext)));
}
public static void dumpBarTransitions(PrintWriter pw, String var, BarTransitions transitions) {
@@ -2734,7 +2750,7 @@ public class StatusBar extends SystemUI implements DemoMode,
null /* remoteAnimation */));
options.setDisallowEnterPictureInPictureWhileLaunching(
disallowEnterPictureInPictureWhileLaunching);
- if (intent == KeyguardBottomAreaView.INSECURE_CAMERA_INTENT) {
+ if (CameraIntents.isInsecureCameraIntent(intent)) {
// Normally an activity will set it's requested rotation
// animation on its window. However when launching an activity
// causes the orientation to change this is too late. In these cases
@@ -2964,7 +2980,7 @@ public class StatusBar extends SystemUI implements DemoMode,
}
mPowerButtonReveal = new PowerButtonReveal(mContext.getResources().getDimensionPixelSize(
- R.dimen.global_actions_top_padding));
+ com.android.systemui.R.dimen.physical_power_button_center_screen_location_y));
}
// Visibility reporting
@@ -3482,7 +3498,8 @@ public class StatusBar extends SystemUI implements DemoMode,
// Lock wallpaper defines the color of the majority of the views, hence we'll use it
// to set our default theme.
final boolean lockDarkText = mColorExtractor.getNeutralColors().supportsDarkText();
- final int themeResId = lockDarkText ? R.style.Theme_SystemUI_Light : R.style.Theme_SystemUI;
+ final int themeResId = lockDarkText ? R.style.Theme_SystemUI_LightWallpaper
+ : R.style.Theme_SystemUI;
if (mContext.getThemeResId() != themeResId) {
mContext.setTheme(themeResId);
mConfigurationController.notifyThemeChanged();
@@ -4024,7 +4041,8 @@ public class StatusBar extends SystemUI implements DemoMode,
}
if (!mStatusBarKeyguardViewManager.isShowing()) {
- startActivityDismissingKeyguard(KeyguardBottomAreaView.INSECURE_CAMERA_INTENT,
+ final Intent cameraIntent = CameraIntents.getInsecureCameraIntent(mContext);
+ startActivityDismissingKeyguard(cameraIntent,
false /* onlyProvisioned */, true /* dismissShade */,
true /* disallowEnterPictureInPictureWhileLaunching */, null /* callback */, 0);
} else {
@@ -4640,4 +4658,19 @@ public class StatusBar extends SystemUI implements DemoMode,
public void removeExpansionChangedListener(@NonNull ExpansionChangedListener listener) {
mExpansionChangedListeners.remove(listener);
}
+
+ private void updateLightRevealScrimVisibility() {
+ if (mLightRevealScrim == null) {
+ // status bar may not be inflated yet
+ return;
+ }
+
+ if (mFeatureFlags.useNewLockscreenAnimations()
+ && (mDozeParameters.getAlwaysOn() || mDozeParameters.isQuickPickupEnabled())) {
+ mLightRevealScrim.setVisibility(View.VISIBLE);
+ mLightRevealScrim.setRevealEffect(LiftReveal.INSTANCE);
+ } else {
+ mLightRevealScrim.setVisibility(View.GONE);
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
index 93b83d3cbcbd..89e701606c1a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
@@ -74,10 +74,6 @@ public interface StatusBarIconController {
/**
* Display the no calling & SMS icons.
*/
- void setCallStrengthIcons(String slot, List<CallIndicatorIconState> states);
- /**
- * Display the no calling & SMS icons.
- */
void setNoCallingIcons(String slot, List<CallIndicatorIconState> states);
public void setIconVisibility(String slot, boolean b);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
index 75900a2bffa1..068ded32bbdb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
@@ -224,33 +224,6 @@ public class StatusBarIconControllerImpl extends StatusBarIconList implements Tu
}
/**
- * Accept a list of CallIndicatorIconStates, and show the call strength icons.
- * @param slot StatusBar slot for the call strength icons
- * @param states All of the no Calling & SMS icon states
- */
- @Override
- public void setCallStrengthIcons(String slot, List<CallIndicatorIconState> states) {
- Slot callStrengthSlot = getSlot(slot);
- int callStrengthSlotIndex = getSlotIndex(slot);
- Collections.reverse(states);
- for (CallIndicatorIconState state : states) {
- if (!state.isNoCalling) {
- StatusBarIconHolder holder = callStrengthSlot.getHolderForTag(state.subId);
- if (holder == null) {
- holder = StatusBarIconHolder.fromCallIndicatorState(mContext, state);
- setIcon(callStrengthSlotIndex, holder);
- } else {
- holder.setIcon(new StatusBarIcon(UserHandle.SYSTEM, mContext.getPackageName(),
- Icon.createWithResource(mContext, state.callStrengthResId), 0, 0,
- state.callStrengthDescription));
- setIcon(callStrengthSlotIndex, holder);
- }
- }
- setIconVisibility(slot, !state.isNoCalling, state.subId);
- }
- }
-
- /**
* Accept a list of CallIndicatorIconStates, and show the no calling icons.
* @param slot StatusBar slot for the no calling icons
* @param states All of the no Calling & SMS icon states
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java
index af342dd31a76..4ca71f081c0e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java
@@ -86,11 +86,9 @@ public class StatusBarIconHolder {
Context context,
CallIndicatorIconState state) {
StatusBarIconHolder holder = new StatusBarIconHolder();
- int resId = state.isNoCalling ? state.noCallingResId : state.callStrengthResId;
- String contentDescription = state.isNoCalling
- ? state.noCallingDescription : state.callStrengthDescription;
holder.mIcon = new StatusBarIcon(UserHandle.SYSTEM, context.getPackageName(),
- Icon.createWithResource(context, resId), 0, 0, contentDescription);
+ Icon.createWithResource(context, state.noCallingResId),
+ 0, 0, state.noCallingDescription);
holder.mTag = state.subId;
return holder;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index 7ee7aa4100d4..ef2444eba814 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -479,7 +479,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
if (mAlternateAuthInterceptor != null) {
mAfterKeyguardGoneAction = r;
mKeyguardGoneCancelAction = cancelAction;
- if (mAlternateAuthInterceptor.showAlternativeAuthMethod()) {
+ if (mAlternateAuthInterceptor.showAlternateAuthBouncer()) {
mStatusBar.updateScrimController();
}
return;
@@ -529,7 +529,8 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
* Stop showing any alternate auth methods
*/
public void resetAlternateAuth() {
- if (mAlternateAuthInterceptor != null && mAlternateAuthInterceptor.resetForceShow()) {
+ if (mAlternateAuthInterceptor != null
+ && mAlternateAuthInterceptor.hideAlternateAuthBouncer()) {
mStatusBar.updateScrimController();
}
}
@@ -1141,15 +1142,22 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
public boolean isShowingAlternateAuth() {
return mAlternateAuthInterceptor != null
- && mAlternateAuthInterceptor.isShowingAlternateAuth();
+ && mAlternateAuthInterceptor.isShowingAlternateAuthBouncer();
}
public boolean isShowingAlternateAuthOrAnimating() {
return mAlternateAuthInterceptor != null
- && (mAlternateAuthInterceptor.isShowingAlternateAuth()
+ && (mAlternateAuthInterceptor.isShowingAlternateAuthBouncer()
|| mAlternateAuthInterceptor.isAnimating());
}
+ /** Update keyguard position based on a tapped X coordinate. */
+ public void updateKeyguardPosition(float x) {
+ if (mBouncer != null) {
+ mBouncer.updateKeyguardPosition(x);
+ }
+ }
+
private static class DismissWithActionRequest {
final OnDismissAction dismissAction;
final Runnable cancelAction;
@@ -1167,24 +1175,25 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
/**
* Delegate used to send show/reset events to an alternate authentication method instead of the
- * bouncer.
+ * regular pin/pattern/password bouncer.
*/
public interface AlternateAuthInterceptor {
/**
- * @return whether alternative auth method was newly shown
+ * Show alternate authentication bouncer.
+ * @return whether alternate auth method was newly shown
*/
- boolean showAlternativeAuthMethod();
+ boolean showAlternateAuthBouncer();
/**
- * reset the state to the default (only keyguard showing, no auth methods showing)
- * @return whether alternative auth method was newly hidden
+ * Hide alternate authentication bouncer
+ * @return whether the alternate auth method was newly hidden
*/
- boolean resetForceShow();
+ boolean hideAlternateAuthBouncer();
/**
- * @return true if alternative auth method is showing
+ * @return true if the alternate auth bouncer is showing
*/
- boolean isShowingAlternateAuth();
+ boolean isShowingAlternateAuthBouncer();
/**
* print information for the alternate auth interceptor registered
@@ -1192,12 +1201,12 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
void dump(PrintWriter pw);
/**
- * @return true if the new auth method is currently animating in or out.
+ * @return true if the new auth method bouncer is currently animating in or out.
*/
boolean isAnimating();
/**
- * Set whether qs is currently expanded
+ * Set whether qs is currently expanded.
*/
void setQsExpanded(boolean expanded);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java
index 52b32a2dee9b..6ec7c4a8f94b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java
@@ -22,7 +22,6 @@ import android.telephony.SubscriptionInfo;
import android.util.ArraySet;
import android.util.Log;
-import com.android.settingslib.mobile.TelephonyIcons;
import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.statusbar.policy.NetworkController;
@@ -50,7 +49,6 @@ public class StatusBarSignalPolicy implements NetworkControllerImpl.SignalCallba
private final String mSlotEthernet;
private final String mSlotVpn;
private final String mSlotNoCalling;
- private final String mSlotCallStrength;
private final Context mContext;
private final StatusBarIconController mIconController;
@@ -84,8 +82,6 @@ public class StatusBarSignalPolicy implements NetworkControllerImpl.SignalCallba
mSlotEthernet = mContext.getString(com.android.internal.R.string.status_bar_ethernet);
mSlotVpn = mContext.getString(com.android.internal.R.string.status_bar_vpn);
mSlotNoCalling = mContext.getString(com.android.internal.R.string.status_bar_no_calling);
- mSlotCallStrength =
- mContext.getString(com.android.internal.R.string.status_bar_call_strength);
mActivityEnabled = mContext.getResources().getBoolean(R.bool.config_showActivity);
mIconController = iconController;
@@ -211,14 +207,9 @@ public class StatusBarSignalPolicy implements NetworkControllerImpl.SignalCallba
if (statusIcon.icon == R.drawable.ic_qs_no_calling_sms) {
state.isNoCalling = statusIcon.visible;
state.noCallingDescription = statusIcon.contentDescription;
- } else {
- state.callStrengthResId = statusIcon.icon;
- state.callStrengthDescription = statusIcon.contentDescription;
+ mIconController.setNoCallingIcons(mSlotNoCalling,
+ CallIndicatorIconState.copyStates(mCallIndicatorStates));
}
- mIconController.setCallStrengthIcons(mSlotCallStrength,
- CallIndicatorIconState.copyStates(mCallIndicatorStates));
- mIconController.setNoCallingIcons(mSlotNoCalling,
- CallIndicatorIconState.copyStates(mCallIndicatorStates));
}
@Override
@@ -306,7 +297,6 @@ public class StatusBarSignalPolicy implements NetworkControllerImpl.SignalCallba
mIconController.removeAllIconsForSlot(mSlotMobile);
mIconController.removeAllIconsForSlot(mSlotNoCalling);
- mIconController.removeAllIconsForSlot(mSlotCallStrength);
mMobileStates.clear();
List<CallIndicatorIconState> noCallingStates = new ArrayList<CallIndicatorIconState>();
noCallingStates.addAll(mCallIndicatorStates);
@@ -421,15 +411,12 @@ public class StatusBarSignalPolicy implements NetworkControllerImpl.SignalCallba
public static class CallIndicatorIconState {
public boolean isNoCalling;
public int noCallingResId;
- public int callStrengthResId;
public int subId;
public String noCallingDescription;
- public String callStrengthDescription;
private CallIndicatorIconState(int subId) {
this.subId = subId;
this.noCallingResId = R.drawable.ic_qs_no_calling_sms;
- this.callStrengthResId = TelephonyIcons.MOBILE_CALL_STRENGTH_ICONS[0];
}
@Override
@@ -441,26 +428,21 @@ public class StatusBarSignalPolicy implements NetworkControllerImpl.SignalCallba
CallIndicatorIconState that = (CallIndicatorIconState) o;
return isNoCalling == that.isNoCalling
&& noCallingResId == that.noCallingResId
- && callStrengthResId == that.callStrengthResId
&& subId == that.subId
- && noCallingDescription == that.noCallingDescription
- && callStrengthDescription == that.callStrengthDescription;
+ && noCallingDescription == that.noCallingDescription;
}
@Override
public int hashCode() {
- return Objects.hash(isNoCalling, noCallingResId,
- callStrengthResId, subId, noCallingDescription, callStrengthDescription);
+ return Objects.hash(isNoCalling, noCallingResId, subId, noCallingDescription);
}
private void copyTo(CallIndicatorIconState other) {
other.isNoCalling = isNoCalling;
other.noCallingResId = noCallingResId;
- other.callStrengthResId = callStrengthResId;
other.subId = subId;
other.noCallingDescription = noCallingDescription;
- other.callStrengthDescription = callStrengthDescription;
}
private static List<CallIndicatorIconState> copyStates(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
index 4f32712e81fa..2c2779e53e16 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
@@ -90,6 +90,7 @@ import com.android.systemui.statusbar.phone.StatusBarIconController;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
import com.android.systemui.statusbar.phone.StatusBarNotificationActivityStarter;
import com.android.systemui.statusbar.phone.StatusBarTouchableRegionManager;
+import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
@@ -99,6 +100,7 @@ import com.android.systemui.statusbar.policy.NetworkController;
import com.android.systemui.statusbar.policy.RemoteInputQuickSettingsDisabler;
import com.android.systemui.statusbar.policy.UserInfoControllerImpl;
import com.android.systemui.statusbar.policy.UserSwitcherController;
+import com.android.systemui.tuner.TunerService;
import com.android.systemui.volume.VolumeComponent;
import com.android.systemui.wmshell.BubblesManager;
import com.android.wm.shell.bubbles.Bubbles;
@@ -206,6 +208,8 @@ public interface StatusBarPhoneModule {
NotificationIconAreaController notificationIconAreaController,
BrightnessSlider.Factory brightnessSliderFactory,
WiredChargingRippleController chargingRippleAnimationController,
+ OngoingCallController ongoingCallController,
+ TunerService tunerService,
FeatureFlags featureFlags) {
return new StatusBar(
context,
@@ -288,6 +292,8 @@ public interface StatusBarPhoneModule {
notificationIconAreaController,
brightnessSliderFactory,
chargingRippleAnimationController,
+ ongoingCallController,
+ tunerService,
featureFlags);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
new file mode 100644
index 000000000000..60d3ea3a7093
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.phone.ongoingcall
+
+import android.app.Notification
+import android.app.Notification.CallStyle.CALL_TYPE_ONGOING
+import android.util.Log
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.FeatureFlags
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection
+import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
+import javax.inject.Inject
+
+/**
+ * A controller to handle the ongoing call chip in the collapsed status bar.
+ */
+@SysUISingleton
+class OngoingCallController @Inject constructor(
+ private val notifCollection: CommonNotifCollection,
+ private val featureFlags: FeatureFlags
+) {
+
+ private val notifListener = object : NotifCollectionListener {
+ override fun onEntryUpdated(entry: NotificationEntry) {
+ if (isOngoingCallNotification(entry) && DEBUG) {
+ Log.d(TAG, "Ongoing call notification updated")
+ }
+ }
+
+ override fun onEntryRemoved(entry: NotificationEntry, reason: Int) {
+ if (isOngoingCallNotification(entry) && DEBUG) {
+ Log.d(TAG, "Ongoing call notification removed")
+ }
+ }
+ }
+
+ fun init() {
+ if (featureFlags.isOngoingCallStatusBarChipEnabled) {
+ notifCollection.addCollectionListener(notifListener)
+ }
+ }
+}
+
+private fun isOngoingCallNotification(entry: NotificationEntry): Boolean {
+ val extras = entry.sbn.notification.extras
+ val callStyleTemplateName = Notification.CallStyle::class.java.name
+ return extras.getString(Notification.EXTRA_TEMPLATE) == callStyleTemplateName &&
+ extras.getInt(Notification.EXTRA_CALL_TYPE, -1) == CALL_TYPE_ONGOING
+}
+
+private const val TAG = "OngoingCallController"
+private val DEBUG = Log.isLoggable(TAG, Log.DEBUG)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/CallbackHandler.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/CallbackHandler.java
index b96cb5e36c82..7ac6d63430d6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/CallbackHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/CallbackHandler.java
@@ -21,6 +21,7 @@ import android.os.Message;
import android.telephony.SubscriptionInfo;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.statusbar.policy.NetworkController.EmergencyListener;
import com.android.systemui.statusbar.policy.NetworkController.IconState;
import com.android.systemui.statusbar.policy.NetworkController.MobileDataIndicators;
@@ -32,6 +33,8 @@ import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.List;
+import javax.inject.Inject;
+
/**
* Implements network listeners and forwards the calls along onto other listeners but on
@@ -60,12 +63,9 @@ public class CallbackHandler extends Handler implements EmergencyListener, Signa
private int mHistoryIndex;
private String mLastCallback;
- public CallbackHandler() {
- super(Looper.getMainLooper());
- }
-
+ @Inject
@VisibleForTesting
- CallbackHandler(Looper looper) {
+ CallbackHandler(@Main Looper looper) {
super(looper);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java
index 499d1e420272..97d344ad6b63 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java
@@ -322,7 +322,7 @@ public class Clock extends TextView implements
// Update text color based when shade scrim changes color.
public void onColorsChanged(boolean lightTheme) {
final Context context = new ContextThemeWrapper(mContext,
- lightTheme ? R.style.Theme_SystemUI_Light : R.style.Theme_SystemUI);
+ lightTheme ? R.style.Theme_SystemUI_LightWallpaper : R.style.Theme_SystemUI);
setTextColor(Utils.getColorAttrDefaultColor(context, R.attr.wallpaperTextColor));
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceControlsController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceControlsController.kt
new file mode 100644
index 000000000000..b21189802c19
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceControlsController.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.policy
+
+/**
+ * Supports adding a DeviceControls QS tile
+ */
+interface DeviceControlsController {
+ interface Callback {
+ /**
+ * If controls become available, initiate this callback with the desired position
+ */
+ fun onControlsAvailable(position: Int)
+ }
+
+ /** Add callback, supporting only a single callback at once */
+ fun setCallback(callback: Callback)
+
+ /** Remove any existing callback, if any */
+ fun removeCallback()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceControlsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceControlsControllerImpl.kt
new file mode 100644
index 000000000000..d3907ae9a150
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceControlsControllerImpl.kt
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.policy
+
+import android.content.ComponentName
+import android.content.Context
+import android.content.SharedPreferences
+import android.util.Log
+
+import com.android.systemui.R
+import com.android.systemui.controls.ControlsServiceInfo
+import com.android.systemui.controls.dagger.ControlsComponent
+import com.android.systemui.controls.management.ControlsListingController
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.qs.QSTileHost.POSITION_AT_END
+import com.android.systemui.settings.UserContextProvider
+import com.android.systemui.statusbar.policy.DeviceControlsController.Callback
+
+import javax.inject.Inject
+
+/**
+ * Watches for Device Controls QS Tile activation, which can happen in two ways:
+ * <ol>
+ * <li>Migration from Power Menu - For existing Android 11 users, create a tile in a high
+ * priority position.
+ * <li>Device controls service becomes available - For non-migrated users, create a tile and
+ * place at the end of active tiles, and initiate seeding where possible.
+ * </ol>
+ */
+@SysUISingleton
+public class DeviceControlsControllerImpl @Inject constructor(
+ private val context: Context,
+ private val controlsComponent: ControlsComponent,
+ private val userContextProvider: UserContextProvider
+) : DeviceControlsController {
+
+ private var callback: Callback? = null
+ internal var position: Int? = null
+
+ private val listingCallback = object : ControlsListingController.ControlsListingCallback {
+ override fun onServicesUpdated(serviceInfos: List<ControlsServiceInfo>) {
+ if (!serviceInfos.isEmpty()) {
+ seedFavorites(serviceInfos)
+ }
+ }
+ }
+
+ companion object {
+ private const val TAG = "DeviceControlsControllerImpl"
+ internal const val QS_PRIORITY_POSITION = 3
+ internal const val QS_DEFAULT_POSITION = POSITION_AT_END
+
+ internal const val PREFS_CONTROLS_SEEDING_COMPLETED = "SeedingCompleted"
+ internal const val PREFS_CONTROLS_FILE = "controls_prefs"
+ private const val SEEDING_MAX = 2
+ }
+
+ private fun checkMigrationToQs() {
+ controlsComponent.getControlsController().ifPresent {
+ if (!it.getFavorites().isEmpty()) {
+ position = QS_PRIORITY_POSITION
+ }
+ }
+ }
+
+ /**
+ * This migration logic assumes that something like [AutoTileManager] is tracking state
+ * externally, and won't call this method after receiving a response via
+ * [Callback#onControlsAvailable], once per user. Otherwise the calculated position may be
+ * incorrect.
+ */
+ override fun setCallback(callback: Callback) {
+ // Treat any additional call as a reset before recalculating
+ removeCallback()
+
+ checkMigrationToQs()
+ controlsComponent.getControlsListingController().ifPresent {
+ it.addCallback(listingCallback)
+ }
+
+ this.callback = callback
+ fireControlsAvailable()
+ }
+
+ override fun removeCallback() {
+ position = null
+ callback = null
+ controlsComponent.getControlsListingController().ifPresent {
+ it.removeCallback(listingCallback)
+ }
+ }
+
+ private fun fireControlsAvailable() {
+ position?.let {
+ Log.i(TAG, "Setting DeviceControlsTile position: $it")
+ callback?.onControlsAvailable(it)
+ }
+ }
+
+ /**
+ * See if any available control service providers match one of the preferred components. If
+ * they do, and there are no current favorites for that component, query the preferred
+ * component for a limited number of suggested controls.
+ */
+ private fun seedFavorites(serviceInfos: List<ControlsServiceInfo>) {
+ val preferredControlsPackages = context.getResources().getStringArray(
+ R.array.config_controlsPreferredPackages)
+
+ val prefs = userContextProvider.userContext.getSharedPreferences(
+ PREFS_CONTROLS_FILE, Context.MODE_PRIVATE)
+ val seededPackages = prefs.getStringSet(PREFS_CONTROLS_SEEDING_COMPLETED, emptySet())
+
+ val controlsController = controlsComponent.getControlsController().get()
+ val componentsToSeed = mutableListOf<ComponentName>()
+ var i = 0
+ while (i < Math.min(SEEDING_MAX, preferredControlsPackages.size)) {
+ val pkg = preferredControlsPackages[i]
+ serviceInfos.forEach {
+ if (pkg.equals(it.componentName.packageName) && !seededPackages.contains(pkg)) {
+ if (controlsController.countFavoritesForComponent(it.componentName) > 0) {
+ // When there are existing controls but no saved preference, assume it
+ // is out of sync, perhaps through a device restore, and update the
+ // preference
+ addPackageToSeededSet(prefs, pkg)
+ } else {
+ componentsToSeed.add(it.componentName)
+ }
+ }
+ }
+ i++
+ }
+
+ if (componentsToSeed.isEmpty()) return
+
+ controlsController.seedFavoritesForComponents(
+ componentsToSeed,
+ { response ->
+ Log.d(TAG, "Controls seeded: $response")
+ if (response.accepted) {
+ addPackageToSeededSet(prefs, response.packageName)
+ if (position == null) {
+ position = QS_DEFAULT_POSITION
+ }
+ fireControlsAvailable()
+
+ controlsComponent.getControlsListingController().ifPresent {
+ it.removeCallback(listingCallback)
+ }
+ }
+ })
+ }
+
+ private fun addPackageToSeededSet(prefs: SharedPreferences, pkg: String) {
+ val seededPackages = prefs.getStringSet(PREFS_CONTROLS_SEEDING_COMPLETED, emptySet())
+ val updatedPkgs = seededPackages.toMutableSet()
+ updatedPkgs.add(pkg)
+ prefs.edit().putStringSet(PREFS_CONTROLS_SEEDING_COMPLETED, updatedPkgs).apply()
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java
index fd37d89a24ec..d52ea890af7e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java
@@ -224,35 +224,29 @@ public class KeyguardQsUserSwitchController extends ViewController<UserAvatarVie
mView.setContentDescription(contentDescription);
}
- if (mCurrentUser.picture == null) {
- mView.setDrawableWithBadge(getDrawable(mCurrentUser).mutate(),
- mCurrentUser.resolveId());
- } else {
- int avatarSize =
- (int) mResources.getDimension(R.dimen.kg_framed_avatar_size);
- Drawable drawable = new CircleFramedDrawable(mCurrentUser.picture, avatarSize);
- drawable.setColorFilter(
- mCurrentUser.isSwitchToEnabled ? null
- : mAdapter.getDisabledUserAvatarColorFilter());
- mView.setDrawableWithBadge(drawable, mCurrentUser.info.id);
- }
+ mView.setDrawableWithBadge(getCurrentUserIcon().mutate(), mCurrentUser.resolveId());
}
- Drawable getDrawable(UserSwitcherController.UserRecord item) {
+ Drawable getCurrentUserIcon() {
Drawable drawable;
- if (item.isCurrent && item.isGuest) {
- drawable = mContext.getDrawable(R.drawable.ic_avatar_guest_user);
- } else {
- drawable = mAdapter.getIconDrawable(mContext, item);
- }
-
- int iconColorRes;
- if (item.isSwitchToEnabled) {
- iconColorRes = R.color.kg_user_switcher_avatar_icon_color;
+ if (mCurrentUser.picture == null) {
+ if (mCurrentUser.isCurrent && mCurrentUser.isGuest) {
+ drawable = mContext.getDrawable(R.drawable.ic_avatar_guest_user);
+ } else {
+ drawable = mAdapter.getIconDrawable(mContext, mCurrentUser);
+ }
+ int iconColorRes;
+ if (mCurrentUser.isSwitchToEnabled) {
+ iconColorRes = R.color.kg_user_switcher_avatar_icon_color;
+ } else {
+ iconColorRes = R.color.kg_user_switcher_restricted_avatar_icon_color;
+ }
+ drawable.setTint(mResources.getColor(iconColorRes, mContext.getTheme()));
} else {
- iconColorRes = R.color.kg_user_switcher_restricted_avatar_icon_color;
+ int avatarSize =
+ (int) mResources.getDimension(R.dimen.kg_framed_avatar_size);
+ drawable = new CircleFramedDrawable(mCurrentUser.picture, avatarSize);
}
- drawable.setTint(mResources.getColor(iconColorRes, mContext.getTheme()));
Drawable bg = mContext.getDrawable(R.drawable.kg_bg_avatar);
drawable = new LayerDrawable(new Drawable[]{bg, drawable});
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
index 24a0a07f3300..09ea186fb638 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
@@ -87,6 +87,7 @@ import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Locale;
+import java.util.concurrent.Executor;
import javax.inject.Inject;
@@ -173,6 +174,8 @@ public class NetworkControllerImpl extends BroadcastReceiver
private NetworkCapabilities mLastDefaultNetworkCapabilities;
// Handler that all broadcasts are received on.
private final Handler mReceiverHandler;
+ private final Looper mBgLooper;
+ private final Executor mBgExecutor;
// Handler that all callbacks are made on.
private final CallbackHandler mCallbackHandler;
@@ -203,6 +206,9 @@ public class NetworkControllerImpl extends BroadcastReceiver
public NetworkControllerImpl(
Context context,
@Background Looper bgLooper,
+ @Background Executor bgExecutor,
+ SubscriptionManager subscriptionManager,
+ CallbackHandler callbackHandler,
DeviceProvisionedController deviceProvisionedController,
BroadcastDispatcher broadcastDispatcher,
ConnectivityManager connectivityManager,
@@ -217,8 +223,11 @@ public class NetworkControllerImpl extends BroadcastReceiver
telephonyListenerManager,
wifiManager,
networkScoreManager,
- SubscriptionManager.from(context), Config.readConfig(context), bgLooper,
- new CallbackHandler(),
+ subscriptionManager,
+ Config.readConfig(context),
+ bgLooper,
+ bgExecutor,
+ callbackHandler,
accessPointController,
new DataUsageController(context),
new SubscriptionDefaults(),
@@ -235,6 +244,7 @@ public class NetworkControllerImpl extends BroadcastReceiver
WifiManager wifiManager,
NetworkScoreManager networkScoreManager,
SubscriptionManager subManager, Config config, Looper bgLooper,
+ Executor bgExecutor,
CallbackHandler callbackHandler,
AccessPointControllerImpl accessPointController,
DataUsageController dataUsageController,
@@ -246,6 +256,8 @@ public class NetworkControllerImpl extends BroadcastReceiver
mTelephonyListenerManager = telephonyListenerManager;
mConfig = config;
mReceiverHandler = new Handler(bgLooper);
+ mBgLooper = bgLooper;
+ mBgExecutor = bgExecutor;
mCallbackHandler = callbackHandler;
mDataSaverController = new DataSaverControllerImpl(context);
mBroadcastDispatcher = broadcastDispatcher;
@@ -384,21 +396,23 @@ public class NetworkControllerImpl extends BroadcastReceiver
// TODO: Move off of the deprecated CONNECTIVITY_ACTION broadcast and rely on callbacks
// exclusively for status bar icons.
mConnectivityManager.registerDefaultNetworkCallback(callback, mReceiverHandler);
- // Register the listener on our bg looper
+ // Run the listener on our bg looper
mPhoneStateListener = subId -> {
- // For data switching from A to B, we assume B is validated for up to 2 seconds iff:
- // 1) A and B are in the same subscription group e.g. CBRS data switch. And
- // 2) A was validated before the switch.
- // This is to provide smooth transition for UI without showing cross during data
- // switch.
- if (keepCellularValidationBitInSwitch(mActiveMobileDataSubscription, subId)) {
- if (DEBUG) Log.d(TAG, ": mForceCellularValidated to true.");
- mForceCellularValidated = true;
- mReceiverHandler.removeCallbacks(mClearForceValidated);
- mReceiverHandler.postDelayed(mClearForceValidated, 2000);
- }
- mActiveMobileDataSubscription = subId;
- doUpdateMobileControllers();
+ mBgExecutor.execute(() -> {
+ // For data switching from A to B, we assume B is validated for up to 2 seconds if:
+ // 1) A and B are in the same subscription group e.g. CBRS data switch. And
+ // 2) A was validated before the switch.
+ // This is to provide smooth transition for UI without showing cross during data
+ // switch.
+ if (keepCellularValidationBitInSwitch(mActiveMobileDataSubscription, subId)) {
+ if (DEBUG) Log.d(TAG, ": mForceCellularValidated to true.");
+ mForceCellularValidated = true;
+ mReceiverHandler.removeCallbacks(mClearForceValidated);
+ mReceiverHandler.postDelayed(mClearForceValidated, 2000);
+ }
+ mActiveMobileDataSubscription = subId;
+ doUpdateMobileControllers();
+ });
};
mDemoModeController.addCallback(this);
@@ -436,7 +450,7 @@ public class NetworkControllerImpl extends BroadcastReceiver
mobileSignalController.registerFiveGStateListener(mFiveGServiceClient);
}
if (mSubscriptionListener == null) {
- mSubscriptionListener = new SubListener();
+ mSubscriptionListener = new SubListener(mBgLooper);
}
mSubscriptionManager.addOnSubscriptionsChangedListener(mSubscriptionListener);
mTelephonyListenerManager.addActiveDataSubscriptionIdListener(mPhoneStateListener);
@@ -1348,6 +1362,10 @@ public class NetworkControllerImpl extends BroadcastReceiver
}
private class SubListener extends OnSubscriptionsChangedListener {
+ SubListener(Looper looper) {
+ super(looper);
+ }
+
@Override
public void onSubscriptionsChanged() {
updateMobileControllers();
@@ -1358,10 +1376,5 @@ public class NetworkControllerImpl extends BroadcastReceiver
* Used to register listeners from the BG Looper, this way the PhoneStateListeners that
* get created will also run on the BG Looper.
*/
- private final Runnable mRegisterListeners = new Runnable() {
- @Override
- public void run() {
- registerListeners();
- }
- };
+ private final Runnable mRegisterListeners = () -> registerListeners();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
index 5a78ea82ab04..6843eb509b86 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
@@ -34,6 +34,7 @@ import android.content.pm.ShortcutManager;
import android.content.res.ColorStateList;
import android.content.res.TypedArray;
import android.graphics.Color;
+import android.graphics.PorterDuff;
import android.graphics.Rect;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
@@ -157,52 +158,48 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
/**
* The remote view needs to adapt to colorized notifications when set
* It overrides the background of itself as well as all of its childern
- * @param color colorized notification color
+ * @param backgroundColor colorized notification color
*/
- public void setBackgroundTintColor(int color, boolean colorized) {
- if (colorized == mColorized && color == mTint) return;
+ public void setBackgroundTintColor(final int backgroundColor, boolean colorized) {
+ if (colorized == mColorized && backgroundColor == mTint) return;
mColorized = colorized;
- mTint = color;
- final int[][] states = new int[][]{
- new int[]{com.android.internal.R.attr.state_enabled},
- new int[]{},
- };
- final int[] colors;
+ mTint = backgroundColor;
+ final int editBgColor;
+ final int accentColor;
+ final int textColor;
+ final int hintTextColor;
if (colorized) {
- final boolean dark = !ContrastColorUtil.isColorLight(color);
- final int finalColor = dark
- ? Color.WHITE
- : Color.BLACK;
- colors = new int[]{
- finalColor,
- finalColor & 0x4DFFFFFF // %30 opacity
- };
- mEditText.setUniformBackgroundTintColor(color);
- mEditText.setUniformForegroundColor(finalColor);
-
+ final boolean dark = !ContrastColorUtil.isColorLight(backgroundColor);
+ final int foregroundColor = dark ? Color.WHITE : Color.BLACK;
+ editBgColor = backgroundColor;
+ accentColor = foregroundColor;
+ textColor = foregroundColor;
+ hintTextColor = ColorUtils.setAlphaComponent(foregroundColor, 0x99);
} else {
- mEditText.setTextColor(mContext.getColor(R.color.remote_input_text));
- mEditText.setHintTextColor(mContext.getColorStateList(R.color.remote_input_hint));
- TypedArray ta = getContext().getTheme().obtainStyledAttributes(new int[]{
+ textColor = mContext.getColor(R.color.remote_input_text);
+ hintTextColor = mContext.getColor(R.color.remote_input_hint);
+ try (TypedArray ta = getContext().getTheme().obtainStyledAttributes(new int[]{
com.android.internal.R.attr.colorAccent,
com.android.internal.R.attr.colorBackgroundFloating,
- });
- int colorAccent = ta.getColor(0, 0);
- int colorBackgroundFloating = ta.getColor(1, 0);
- ta.recycle();
- mEditText.setTextBackgroundColors(colorAccent, colorBackgroundFloating);
- colors = new int[]{
- colorAccent,
- colorBackgroundFloating & 0x4DFFFFFF // %30 opacity
- };
- }
- mEditText.setBackgroundColor(color);
- final ColorStateList tint = new ColorStateList(states, colors);
- mSendButton.setImageTintList(tint);
- mProgressBar.setProgressTintList(tint);
- mProgressBar.setIndeterminateTintList(tint);
- mProgressBar.setSecondaryProgressTintList(tint);
- setBackgroundColor(color);
+ })) {
+ accentColor = ta.getColor(0, textColor);
+ editBgColor = ta.getColor(1, backgroundColor);
+ }
+ }
+ mEditText.setAllColors(backgroundColor, editBgColor,
+ accentColor, textColor, hintTextColor);
+ final ColorStateList accentTint = new ColorStateList(new int[][]{
+ new int[]{com.android.internal.R.attr.state_enabled},
+ new int[]{},
+ }, new int[]{
+ accentColor,
+ accentColor & 0x4DFFFFFF // %30 opacity
+ });
+ mSendButton.setImageTintList(accentTint);
+ mProgressBar.setProgressTintList(accentTint);
+ mProgressBar.setIndeterminateTintList(accentTint);
+ mProgressBar.setSecondaryProgressTintList(accentTint);
+ setBackgroundColor(backgroundColor);
}
@Override
@@ -796,20 +793,6 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
}
}
- protected void setUniformBackgroundTintColor(int color) {
- mBackgroundColor.setColor(color);
- mTextBackground.setColor(color);
- }
-
- protected void setUniformForegroundColor(int color) {
- int stroke = getContext().getResources()
- .getDimensionPixelSize(R.dimen.remote_input_view_text_stroke);
- mTextBackground.setStroke(stroke, color);
- setTextColor(color);
- setHintTextColor(ColorUtils.setAlphaComponent(color, 0x99));
- setTextCursorDrawable(null);
- }
-
@Override
public void getFocusedRect(Rect r) {
super.getFocusedRect(r);
@@ -938,11 +921,17 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
return remainingItems;
}
- protected void setTextBackgroundColors(int strokeColor, int textBackground) {
- mTextBackground.setColor(textBackground);
+ protected void setAllColors(int backgroundColor, int editBackgroundColor,
+ int accentColor, int textColor, int hintTextColor) {
+ setBackgroundColor(backgroundColor);
+ mBackgroundColor.setColor(backgroundColor);
+ mTextBackground.setColor(editBackgroundColor);
int stroke = getContext().getResources()
.getDimensionPixelSize(R.dimen.remote_input_view_text_stroke);
- mTextBackground.setStroke(stroke, strokeColor);
+ mTextBackground.setStroke(stroke, accentColor);
+ setTextColor(textColor);
+ setHintTextColor(hintTextColor);
+ getTextCursorDrawable().setColorFilter(accentColor, PorterDuff.Mode.SRC_IN);
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserInfoControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserInfoControllerImpl.java
index 9eaee22b54ec..f42e3885fe62 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserInfoControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserInfoControllerImpl.java
@@ -143,7 +143,7 @@ public class UserInfoControllerImpl implements UserInfoController {
final int userId = userInfo.id;
final boolean isGuest = userInfo.isGuest();
final String userName = userInfo.name;
- final boolean lightIcon = mContext.getThemeResId() != R.style.Theme_SystemUI_Light;
+ final boolean lightIcon = mContext.getThemeResId() != R.style.Theme_SystemUI_LightWallpaper;
final Resources res = mContext.getResources();
final int avatarSize = Math.max(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java
index 7a4b912d4071..766602261099 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java
@@ -26,6 +26,8 @@ import com.android.systemui.statusbar.policy.BluetoothController;
import com.android.systemui.statusbar.policy.BluetoothControllerImpl;
import com.android.systemui.statusbar.policy.CastController;
import com.android.systemui.statusbar.policy.CastControllerImpl;
+import com.android.systemui.statusbar.policy.DeviceControlsController;
+import com.android.systemui.statusbar.policy.DeviceControlsControllerImpl;
import com.android.systemui.statusbar.policy.ExtensionController;
import com.android.systemui.statusbar.policy.ExtensionControllerImpl;
import com.android.systemui.statusbar.policy.FlashlightController;
@@ -44,8 +46,6 @@ import com.android.systemui.statusbar.policy.RotationLockController;
import com.android.systemui.statusbar.policy.RotationLockControllerImpl;
import com.android.systemui.statusbar.policy.SecurityController;
import com.android.systemui.statusbar.policy.SecurityControllerImpl;
-import com.android.systemui.statusbar.policy.SensorPrivacyController;
-import com.android.systemui.statusbar.policy.SensorPrivacyControllerImpl;
import com.android.systemui.statusbar.policy.UserInfoController;
import com.android.systemui.statusbar.policy.UserInfoControllerImpl;
import com.android.systemui.statusbar.policy.ZenModeController;
@@ -115,6 +115,11 @@ public interface StatusBarPolicyModule {
/** */
@Binds
+ DeviceControlsController provideDeviceControlsController(
+ DeviceControlsControllerImpl controllerImpl);
+
+ /** */
+ @Binds
NetworkController.AccessPointController provideAccessPointController(
AccessPointControllerImpl accessPointControllerImpl);
diff --git a/packages/SystemUI/src/com/android/systemui/telephony/TelephonyCallback.java b/packages/SystemUI/src/com/android/systemui/telephony/TelephonyCallback.java
index 95216c559420..728907fd2d36 100644
--- a/packages/SystemUI/src/com/android/systemui/telephony/TelephonyCallback.java
+++ b/packages/SystemUI/src/com/android/systemui/telephony/TelephonyCallback.java
@@ -54,9 +54,11 @@ class TelephonyCallback extends android.telephony.TelephonyCallback
@Override
public void onActiveDataSubscriptionIdChanged(int subId) {
- mActiveDataSubscriptionIdListeners.forEach(listener -> {
- listener.onActiveDataSubscriptionIdChanged(subId);
- });
+ List<ActiveDataSubscriptionIdListener> listeners;
+ synchronized (mActiveDataSubscriptionIdListeners) {
+ listeners = new ArrayList<>(mActiveDataSubscriptionIdListeners);
+ }
+ listeners.forEach(listener -> listener.onActiveDataSubscriptionIdChanged(subId));
}
void addActiveDataSubscriptionIdListener(ActiveDataSubscriptionIdListener listener) {
@@ -69,9 +71,11 @@ class TelephonyCallback extends android.telephony.TelephonyCallback
@Override
public void onCallStateChanged(int state) {
- mCallStateListeners.forEach(listener -> {
- listener.onCallStateChanged(state);
- });
+ List<CallStateListener> listeners;
+ synchronized (mCallStateListeners) {
+ listeners = new ArrayList<>(mCallStateListeners);
+ }
+ listeners.forEach(listener -> listener.onCallStateChanged(state));
}
void addCallStateListener(CallStateListener listener) {
@@ -84,9 +88,11 @@ class TelephonyCallback extends android.telephony.TelephonyCallback
@Override
public void onServiceStateChanged(@NonNull ServiceState serviceState) {
- mServiceStateListeners.forEach(listener -> {
- listener.onServiceStateChanged(serviceState);
- });
+ List<ServiceStateListener> listeners;
+ synchronized (mServiceStateListeners) {
+ listeners = new ArrayList<>(mServiceStateListeners);
+ }
+ listeners.forEach(listener -> listener.onServiceStateChanged(serviceState));
}
void addServiceStateListener(ServiceStateListener listener) {
diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java
index 278663b270bc..3c1e12327d8f 100644
--- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java
+++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java
@@ -15,6 +15,7 @@
*/
package com.android.systemui.theme;
+import android.annotation.AnyThread;
import android.content.om.FabricatedOverlay;
import android.content.om.OverlayIdentifier;
import android.content.om.OverlayInfo;
@@ -159,44 +160,48 @@ public class ThemeOverlayApplier implements Dumpable {
FabricatedOverlay[] pendingCreation,
int currentUser,
Set<UserHandle> managedProfiles) {
- // Disable all overlays that have not been specified in the user setting.
- final Set<String> overlayCategoriesToDisable = new HashSet<>(THEME_CATEGORIES);
- final Set<String> targetPackagesToQuery = overlayCategoriesToDisable.stream()
- .map(category -> mCategoryToTargetPackage.get(category))
- .collect(Collectors.toSet());
- final List<OverlayInfo> overlays = new ArrayList<>();
- targetPackagesToQuery.forEach(targetPackage -> overlays.addAll(mOverlayManager
- .getOverlayInfosForTarget(targetPackage, UserHandle.SYSTEM)));
- final List<Pair<String, String>> overlaysToDisable = overlays.stream()
- .filter(o ->
- mTargetPackageToCategories.get(o.targetPackageName).contains(o.category))
- .filter(o -> overlayCategoriesToDisable.contains(o.category))
- .filter(o -> !categoryToPackage.containsValue(new OverlayIdentifier(o.packageName)))
- .filter(o -> o.isEnabled())
- .map(o -> new Pair<>(o.category, o.packageName))
- .collect(Collectors.toList());
-
- OverlayManagerTransaction.Builder transaction = getTransactionBuilder();
- if (pendingCreation != null) {
- for (FabricatedOverlay overlay : pendingCreation) {
- transaction.registerFabricatedOverlay(overlay);
+ mExecutor.execute(() -> {
+
+ // Disable all overlays that have not been specified in the user setting.
+ final Set<String> overlayCategoriesToDisable = new HashSet<>(THEME_CATEGORIES);
+ final Set<String> targetPackagesToQuery = overlayCategoriesToDisable.stream()
+ .map(category -> mCategoryToTargetPackage.get(category))
+ .collect(Collectors.toSet());
+ final List<OverlayInfo> overlays = new ArrayList<>();
+ targetPackagesToQuery.forEach(targetPackage -> overlays.addAll(mOverlayManager
+ .getOverlayInfosForTarget(targetPackage, UserHandle.SYSTEM)));
+ final List<Pair<String, String>> overlaysToDisable = overlays.stream()
+ .filter(o ->
+ mTargetPackageToCategories.get(o.targetPackageName).contains(
+ o.category))
+ .filter(o -> overlayCategoriesToDisable.contains(o.category))
+ .filter(o -> !categoryToPackage.containsValue(
+ new OverlayIdentifier(o.packageName)))
+ .filter(o -> o.isEnabled())
+ .map(o -> new Pair<>(o.category, o.packageName))
+ .collect(Collectors.toList());
+
+ OverlayManagerTransaction.Builder transaction = getTransactionBuilder();
+ if (pendingCreation != null) {
+ for (FabricatedOverlay overlay : pendingCreation) {
+ transaction.registerFabricatedOverlay(overlay);
+ }
}
- }
- for (Pair<String, String> packageToDisable : overlaysToDisable) {
- OverlayIdentifier overlayInfo = new OverlayIdentifier(packageToDisable.second);
- setEnabled(transaction, overlayInfo, packageToDisable.first, currentUser,
- managedProfiles, false);
- }
+ for (Pair<String, String> packageToDisable : overlaysToDisable) {
+ OverlayIdentifier overlayInfo = new OverlayIdentifier(packageToDisable.second);
+ setEnabled(transaction, overlayInfo, packageToDisable.first, currentUser,
+ managedProfiles, false);
+ }
- for (String category : THEME_CATEGORIES) {
- if (categoryToPackage.containsKey(category)) {
- OverlayIdentifier overlayInfo = categoryToPackage.get(category);
- setEnabled(transaction, overlayInfo, category, currentUser, managedProfiles, true);
+ for (String category : THEME_CATEGORIES) {
+ if (categoryToPackage.containsKey(category)) {
+ OverlayIdentifier overlayInfo = categoryToPackage.get(category);
+ setEnabled(transaction, overlayInfo, category, currentUser, managedProfiles,
+ true);
+ }
}
- }
- mExecutor.execute(() -> {
try {
mOverlayManager.commit(transaction.build());
} catch (SecurityException | IllegalStateException e) {
@@ -210,6 +215,7 @@ public class ThemeOverlayApplier implements Dumpable {
return new OverlayManagerTransaction.Builder();
}
+ @AnyThread
private void setEnabled(OverlayManagerTransaction.Builder transaction,
OverlayIdentifier identifier, String category, int currentUser,
Set<UserHandle> managedProfiles, boolean enabled) {
diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
index d317712ce87c..fc9a35d7d54c 100644
--- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
@@ -19,9 +19,9 @@ import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_AC
import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_SYSTEM_PALETTE;
import android.annotation.Nullable;
-import android.app.ActivityManager;
import android.app.WallpaperColors;
import android.app.WallpaperManager;
+import android.app.WallpaperManager.OnColorsChangedListener;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -49,8 +49,10 @@ import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.FeatureFlags;
-import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.statusbar.policy.DeviceProvisionedController;
+import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener;
import com.android.systemui.util.settings.SecureSettings;
import org.json.JSONException;
@@ -92,8 +94,9 @@ public class ThemeOverlayController extends SystemUI implements Dumpable {
private final Executor mMainExecutor;
private final Handler mBgHandler;
private final WallpaperManager mWallpaperManager;
- private final KeyguardStateController mKeyguardStateController;
private final boolean mIsMonetEnabled;
+ private final UserTracker mUserTracker;
+ private DeviceProvisionedController mDeviceProvisionedController;
private WallpaperColors mSystemColors;
// If fabricated overlays were already created for the current theme.
private boolean mNeedsOverlayCreation;
@@ -107,17 +110,76 @@ public class ThemeOverlayController extends SystemUI implements Dumpable {
private FabricatedOverlay mNeutralOverlay;
// If wallpaper color event will be accepted and change the UI colors.
private boolean mAcceptColorEvents = true;
+ // Defers changing themes until Setup Wizard is done.
+ private boolean mDeferredThemeEvaluation;
+
+ private final DeviceProvisionedListener mDeviceProvisionedListener =
+ new DeviceProvisionedListener() {
+ @Override
+ public void onUserSetupChanged() {
+ if (!mDeviceProvisionedController.isCurrentUserSetup()) {
+ return;
+ }
+ if (!mDeferredThemeEvaluation) {
+ return;
+ }
+ Log.i(TAG, "Applying deferred theme");
+ mDeferredThemeEvaluation = false;
+ reevaluateSystemTheme(true /* forceReload */);
+ }
+ };
+
+ private final OnColorsChangedListener mOnColorsChangedListener = (wallpaperColors, which) -> {
+ if (!mAcceptColorEvents) {
+ Log.i(TAG, "Wallpaper color event rejected: " + wallpaperColors);
+ return;
+ }
+ if (wallpaperColors != null) {
+ mAcceptColorEvents = false;
+ }
+
+ if ((which & WallpaperManager.FLAG_SYSTEM) != 0) {
+ mSystemColors = wallpaperColors;
+ if (DEBUG) {
+ Log.d(TAG, "got new lock colors: " + wallpaperColors + " where: " + which);
+ }
+ }
+
+ if (mDeviceProvisionedController != null
+ && !mDeviceProvisionedController.isCurrentUserSetup()) {
+ Log.i(TAG, "Wallpaper color event deferred until setup is finished: "
+ + wallpaperColors);
+ mDeferredThemeEvaluation = true;
+ return;
+ }
+ reevaluateSystemTheme(false /* forceReload */);
+ };
+
+ private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (Intent.ACTION_USER_SWITCHED.equals(intent.getAction())
+ || Intent.ACTION_MANAGED_PROFILE_ADDED.equals(intent.getAction())) {
+ if (DEBUG) Log.d(TAG, "Updating overlays for user switch / profile added.");
+ reevaluateSystemTheme(true /* forceReload */);
+ } else if (Intent.ACTION_WALLPAPER_CHANGED.equals(intent.getAction())) {
+ mAcceptColorEvents = true;
+ Log.i(TAG, "Allowing color events again");
+ }
+ }
+ };
@Inject
public ThemeOverlayController(Context context, BroadcastDispatcher broadcastDispatcher,
@Background Handler bgHandler, @Main Executor mainExecutor,
@Background Executor bgExecutor, ThemeOverlayApplier themeOverlayApplier,
SecureSettings secureSettings, WallpaperManager wallpaperManager,
- UserManager userManager, KeyguardStateController keyguardStateController,
- DumpManager dumpManager, FeatureFlags featureFlags) {
+ UserManager userManager, DeviceProvisionedController deviceProvisionedController,
+ UserTracker userTracker, DumpManager dumpManager, FeatureFlags featureFlags) {
super(context);
mIsMonetEnabled = featureFlags.isMonetEnabled();
+ mDeviceProvisionedController = deviceProvisionedController;
mBroadcastDispatcher = broadcastDispatcher;
mUserManager = userManager;
mBgExecutor = bgExecutor;
@@ -126,7 +188,7 @@ public class ThemeOverlayController extends SystemUI implements Dumpable {
mThemeManager = themeOverlayApplier;
mSecureSettings = secureSettings;
mWallpaperManager = wallpaperManager;
- mKeyguardStateController = keyguardStateController;
+ mUserTracker = userTracker;
dumpManager.registerDumpable(TAG, this);
}
@@ -137,19 +199,8 @@ public class ThemeOverlayController extends SystemUI implements Dumpable {
filter.addAction(Intent.ACTION_USER_SWITCHED);
filter.addAction(Intent.ACTION_MANAGED_PROFILE_ADDED);
filter.addAction(Intent.ACTION_WALLPAPER_CHANGED);
- mBroadcastDispatcher.registerReceiver(new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- if (Intent.ACTION_USER_SWITCHED.equals(intent.getAction())
- || Intent.ACTION_MANAGED_PROFILE_ADDED.equals(intent.getAction())) {
- if (DEBUG) Log.d(TAG, "Updating overlays for user switch / profile added.");
- reevaluateSystemTheme(true /* forceReload */);
- } else if (Intent.ACTION_WALLPAPER_CHANGED.equals(intent.getAction())) {
- mAcceptColorEvents = true;
- Log.i(TAG, "Allowing color events again");
- }
- }
- }, filter, mMainExecutor, UserHandle.ALL);
+ mBroadcastDispatcher.registerReceiver(mBroadcastReceiver, filter, mMainExecutor,
+ UserHandle.ALL);
mSecureSettings.registerContentObserverForUser(
Settings.Secure.getUriFor(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES),
false,
@@ -158,12 +209,19 @@ public class ThemeOverlayController extends SystemUI implements Dumpable {
public void onChange(boolean selfChange, Collection<Uri> collection, int flags,
int userId) {
if (DEBUG) Log.d(TAG, "Overlay changed for user: " + userId);
- if (ActivityManager.getCurrentUser() == userId) {
- reevaluateSystemTheme(true /* forceReload */);
+ if (mUserTracker.getUserId() != userId) {
+ return;
}
+ if (!mDeviceProvisionedController.isUserSetup(userId)) {
+ Log.i(TAG, "Theme application deferred when setting changed.");
+ mDeferredThemeEvaluation = true;
+ return;
+ }
+ reevaluateSystemTheme(true /* forceReload */);
}
},
UserHandle.USER_ALL);
+ mDeviceProvisionedController.addCallback(mDeviceProvisionedListener);
// Upon boot, make sure we have the most up to date colors
mBgExecutor.execute(() -> {
@@ -174,23 +232,8 @@ public class ThemeOverlayController extends SystemUI implements Dumpable {
reevaluateSystemTheme(false /* forceReload */);
});
});
- mWallpaperManager.addOnColorsChangedListener((wallpaperColors, which) -> {
- if (!mAcceptColorEvents) {
- Log.i(TAG, "Wallpaper color event rejected: " + wallpaperColors);
- return;
- }
- if (wallpaperColors != null && mAcceptColorEvents) {
- mAcceptColorEvents = false;
- }
-
- if ((which & WallpaperManager.FLAG_SYSTEM) != 0) {
- mSystemColors = wallpaperColors;
- if (DEBUG) {
- Log.d(TAG, "got new lock colors: " + wallpaperColors + " where: " + which);
- }
- }
- reevaluateSystemTheme(false /* forceReload */);
- }, null, UserHandle.USER_ALL);
+ mWallpaperManager.addOnColorsChangedListener(mOnColorsChangedListener, null,
+ UserHandle.USER_ALL);
}
private void reevaluateSystemTheme(boolean forceReload) {
@@ -252,7 +295,7 @@ public class ThemeOverlayController extends SystemUI implements Dumpable {
}
private void updateThemeOverlays() {
- final int currentUser = ActivityManager.getCurrentUser();
+ final int currentUser = mUserTracker.getUserId();
final String overlayPackageJson = mSecureSettings.getStringForUser(
Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES,
currentUser);
@@ -360,5 +403,6 @@ public class ThemeOverlayController extends SystemUI implements Dumpable {
pw.println("mIsMonetEnabled=" + mIsMonetEnabled);
pw.println("mNeedsOverlayCreation=" + mNeedsOverlayCreation);
pw.println("mAcceptColorEvents=" + mAcceptColorEvents);
+ pw.println("mDeferredThemeEvaluation=" + mDeferredThemeEvaluation);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index 044d8285b079..a8a3d79c67cf 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -76,6 +76,7 @@ import android.view.ContextThemeWrapper;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.AccessibilityDelegate;
+import android.view.View.OnAttachStateChangeListener;
import android.view.ViewGroup;
import android.view.ViewPropertyAnimator;
import android.view.ViewStub;
@@ -95,6 +96,7 @@ import android.widget.SeekBar.OnSeekBarChangeListener;
import android.widget.TextView;
import android.widget.Toast;
+import com.android.internal.graphics.drawable.BackgroundBlurDrawable;
import com.android.settingslib.Utils;
import com.android.systemui.Dependency;
import com.android.systemui.Interpolators;
@@ -115,6 +117,7 @@ import com.android.systemui.util.RoundedCornerProgressDrawable;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
+import java.util.function.Consumer;
/**
* Visual presentation of the volume dialog.
@@ -214,6 +217,10 @@ public class VolumeDialogImpl implements VolumeDialog,
private ViewStub mODICaptionsTooltipViewStub;
private View mODICaptionsTooltipView = null;
+ private final boolean mUseBackgroundBlur;
+ private Consumer<Boolean> mCrossWindowBlurEnabledListener;
+ private BackgroundBlurDrawable mDialogRowsViewBackground;
+
public VolumeDialogImpl(Context context) {
mContext =
new ContextThemeWrapper(context, R.style.volume_dialog_theme);
@@ -233,6 +240,20 @@ public class VolumeDialogImpl implements VolumeDialog,
mContext.getResources().getInteger(R.integer.config_dialogShowAnimationDurationMs);
mDialogHideAnimationDurationMs =
mContext.getResources().getInteger(R.integer.config_dialogHideAnimationDurationMs);
+ mUseBackgroundBlur =
+ mContext.getResources().getBoolean(R.bool.config_volumeDialogUseBackgroundBlur);
+
+ if (mUseBackgroundBlur) {
+ final int dialogRowsViewColorAboveBlur = mContext.getColor(
+ R.color.volume_dialog_background_color_above_blur);
+ final int dialogRowsViewColorNoBlur = mContext.getColor(
+ R.color.volume_dialog_background_color);
+ mCrossWindowBlurEnabledListener = (enabled) -> {
+ mDialogRowsViewBackground.setColor(
+ enabled ? dialogRowsViewColorAboveBlur : dialogRowsViewColorNoBlur);
+ mDialogRowsView.invalidate();
+ };
+ }
mRingerDrawerItemSize = mContext.getResources().getDimensionPixelSize(
R.dimen.volume_ringer_drawer_item_size);
mShowVibrate = mController.hasVibrator();
@@ -280,11 +301,13 @@ public class VolumeDialogImpl implements VolumeDialog,
// the volume dialog container itself, so this is fine.
for (int i = 0; i < mDialogView.getChildCount(); i++) {
final View view = mDialogView.getChildAt(i);
+ final int[] locInWindow = new int[2];
+ view.getLocationInWindow(locInWindow);
mTouchableRegion.op(
- view.getLeft(),
- view.getTop(),
- view.getRight(),
- view.getBottom(),
+ locInWindow[0],
+ locInWindow[1],
+ locInWindow[0] + view.getWidth(),
+ locInWindow[1] + view.getHeight(),
Region.Op.UNION);
}
@@ -356,6 +379,32 @@ public class VolumeDialogImpl implements VolumeDialog,
});
mDialogRowsView = mDialog.findViewById(R.id.volume_dialog_rows);
+ if (mUseBackgroundBlur) {
+ mDialogView.addOnAttachStateChangeListener(new OnAttachStateChangeListener() {
+ @Override
+ public void onViewAttachedToWindow(View v) {
+ mWindow.getWindowManager().addCrossWindowBlurEnabledListener(
+ mCrossWindowBlurEnabledListener);
+
+ mDialogRowsViewBackground = v.getViewRootImpl().createBackgroundBlurDrawable();
+
+ final Resources resources = mContext.getResources();
+ mDialogRowsViewBackground.setCornerRadius(
+ mContext.getResources().getDimensionPixelSize(Utils.getThemeAttr(
+ mContext, android.R.attr.dialogCornerRadius)));
+ mDialogRowsViewBackground.setBlurRadius(resources.getDimensionPixelSize(
+ R.dimen.volume_dialog_background_blur_radius));
+ mDialogRowsView.setBackground(mDialogRowsViewBackground);
+ }
+
+ @Override
+ public void onViewDetachedFromWindow(View v) {
+ mWindow.getWindowManager().removeCrossWindowBlurEnabledListener(
+ mCrossWindowBlurEnabledListener);
+ }
+ });
+ }
+
mRinger = mDialog.findViewById(R.id.ringer);
if (mRinger != null) {
mRingerIcon = mRinger.findViewById(R.id.ringer_icon);
diff --git a/packages/SystemUI/src/com/android/systemui/wallet/dagger/WalletModule.java b/packages/SystemUI/src/com/android/systemui/wallet/dagger/WalletModule.java
new file mode 100644
index 000000000000..c1f5516f02bb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/wallet/dagger/WalletModule.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.wallet.dagger;
+
+import android.app.Activity;
+import android.content.Context;
+import android.service.quickaccesswallet.QuickAccessWalletClient;
+
+import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.wallet.ui.WalletActivity;
+
+import dagger.Binds;
+import dagger.Module;
+import dagger.Provides;
+import dagger.multibindings.ClassKey;
+import dagger.multibindings.IntoMap;
+
+
+/**
+ * Module for injecting classes in Wallet.
+ */
+@Module
+public abstract class WalletModule {
+
+ /** */
+ @Binds
+ @IntoMap
+ @ClassKey(WalletActivity.class)
+ public abstract Activity provideWalletActivity(WalletActivity activity);
+
+ /** */
+ @SysUISingleton
+ @Provides
+ public static QuickAccessWalletClient provideQuickAccessWalletClient(Context context) {
+ return QuickAccessWalletClient.create(context);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/wallet/ui/DotIndicatorDecoration.java b/packages/SystemUI/src/com/android/systemui/wallet/ui/DotIndicatorDecoration.java
new file mode 100644
index 000000000000..644addfef1d1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/wallet/ui/DotIndicatorDecoration.java
@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.wallet.ui;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.text.TextPaint;
+import android.util.MathUtils;
+import android.view.View;
+import android.widget.TextView;
+
+import androidx.annotation.ColorInt;
+import androidx.core.content.ContextCompat;
+import androidx.core.graphics.ColorUtils;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.android.systemui.R;
+
+final class DotIndicatorDecoration extends RecyclerView.ItemDecoration {
+ private final int mUnselectedRadius;
+ private final int mSelectedRadius;
+ private final int mDotMargin;
+ @ColorInt private final int mUnselectedColor;
+ @ColorInt private final int mSelectedColor;
+ private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+ private final TextPaint mTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
+ private WalletCardCarousel mCardCarousel;
+
+ DotIndicatorDecoration(Context context) {
+ super();
+
+ mUnselectedRadius =
+ context.getResources().getDimensionPixelSize(
+ R.dimen.card_carousel_dot_unselected_radius);
+ mSelectedRadius =
+ context.getResources().getDimensionPixelSize(
+ R.dimen.card_carousel_dot_selected_radius);
+ mDotMargin = context.getResources().getDimensionPixelSize(R.dimen.card_carousel_dot_margin);
+
+ TextView textView = new TextView(context);
+ mTextPaint.set(textView.getPaint());
+ // Text color is not copied from text appearance.
+ mTextPaint.setColor(ContextCompat.getColor(context, R.color.GM2_blue_600));
+
+ mUnselectedColor = ContextCompat.getColor(context, R.color.GM2_grey_300);
+ mSelectedColor = ContextCompat.getColor(context, R.color.GM2_blue_600);
+ }
+
+ @Override
+ public void getItemOffsets(
+ Rect rect, View view, RecyclerView recyclerView, RecyclerView.State state) {
+ super.getItemOffsets(rect, view, recyclerView, state);
+ if (recyclerView.getAdapter().getItemCount() > 1) {
+ rect.bottom =
+ view.getResources().getDimensionPixelSize(R.dimen.card_carousel_dot_offset);
+ }
+ }
+
+ @Override
+ public void onDrawOver(Canvas canvas, RecyclerView recyclerView, RecyclerView.State state) {
+ super.onDrawOver(canvas, recyclerView, state);
+
+ mCardCarousel = (WalletCardCarousel) recyclerView;
+ int itemCount = recyclerView.getAdapter().getItemCount();
+ if (itemCount <= 1) {
+ // Only shown if there are at least 2 items, and it's not a shimmer loader
+ return;
+ }
+ canvas.save();
+
+ float animationStartOffset = recyclerView.getWidth() / 6f;
+ // 0 when a card is still very prominent, ie. edgeToCenterDistance is greater than
+ // animationStartOffset
+ // 1 when the two cards are equidistant from the center ie. edgeToCenterDistance == 0
+ float interpolatedProgress =
+ 1 - Math.min(Math.abs(mCardCarousel.mEdgeToCenterDistance), animationStartOffset)
+ / animationStartOffset;
+
+ float totalWidth =
+ mDotMargin * (itemCount - 1)
+ + 2 * mUnselectedRadius * (itemCount - 2)
+ + 2 * mSelectedRadius;
+ // Translate the canvas so the drawing can always start at (0, 0) coordinates.
+ canvas.translate(
+ (recyclerView.getWidth() - totalWidth) / 2f,
+ recyclerView.getHeight() - mDotMargin);
+
+ int itemsDrawn = 0;
+ while (itemsDrawn < itemCount) {
+ // count up from 0 to itemCount - 1 if LTR; count down from itemCount - 1 to 0 if RTL.
+ int i = isLayoutLtr() ? itemsDrawn : itemCount - itemsDrawn - 1;
+
+ if (isSelectedItem(i)) {
+ drawSelectedDot(canvas, interpolatedProgress, i);
+ } else if (isNextItemInScrollingDirection(i)) {
+ drawFadingUnselectedDot(canvas, interpolatedProgress, i);
+ } else {
+ drawUnselectedDot(canvas);
+ }
+ canvas.translate(mDotMargin, 0);
+ itemsDrawn++;
+ }
+
+ canvas.restore();
+ this.mCardCarousel = null; // No need to hold a reference.
+ }
+
+ private void drawSelectedDot(Canvas canvas, float progress, int position) {
+ // Divide progress by 2 because the other half of the animation is done by
+ // drawFadingUnselectedDot.
+ mPaint.setColor(
+ getTransitionAdjustedColor(
+ ColorUtils.blendARGB(mSelectedColor, mUnselectedColor, progress / 2)));
+ float radius = MathUtils.lerp(mSelectedRadius, mUnselectedRadius, progress / 2);
+ canvas.drawCircle(radius, 0, radius, mPaint);
+ canvas.translate(radius * 2, 0);
+ }
+
+ private void drawFadingUnselectedDot(Canvas canvas, float progress, int position) {
+ // Divide progress by 2 because the first half of the animation is done by drawSelectedDot.
+ int blendedColor =
+ ColorUtils.blendARGB(
+ mUnselectedColor, mSelectedColor, progress / 2);
+ mPaint.setColor(getTransitionAdjustedColor(blendedColor));
+ float radius = MathUtils.lerp(mSelectedRadius, mUnselectedRadius, progress / 2);
+ canvas.drawCircle(radius, 0, radius, mPaint);
+ canvas.translate(radius * 2, 0);
+ }
+
+ private void drawUnselectedDot(Canvas canvas) {
+ mPaint.setColor(mUnselectedColor);
+ canvas.drawCircle(mUnselectedRadius, 0, mUnselectedRadius, mPaint);
+ canvas.translate(mUnselectedRadius * 2, 0);
+ }
+
+ private int getTransitionAdjustedColor(int color) {
+ int transitionAlphaOverride = 0xff;
+ return ColorUtils.setAlphaComponent(color, transitionAlphaOverride);
+ }
+
+ private boolean isSelectedItem(int position) {
+ return mCardCarousel.mCenteredAdapterPosition == position;
+ }
+
+ private boolean isNextItemInScrollingDirection(int position) {
+ if (isLayoutLtr()) {
+ return (mCardCarousel.mCenteredAdapterPosition + 1 == position
+ && mCardCarousel.mEdgeToCenterDistance >= 0f)
+ || (mCardCarousel.mCenteredAdapterPosition - 1 == position
+ && mCardCarousel.mEdgeToCenterDistance < 0f);
+ }
+ return (mCardCarousel.mCenteredAdapterPosition - 1 == position
+ && mCardCarousel.mEdgeToCenterDistance >= 0f)
+ || (mCardCarousel.mCenteredAdapterPosition + 1 == position
+ && mCardCarousel.mEdgeToCenterDistance < 0f);
+ }
+
+ private boolean isLayoutLtr() {
+ if (mCardCarousel == null) {
+ // Shouldn't happen, but assume LTR for now.
+ return true;
+ }
+ return mCardCarousel.getLayoutDirection() == View.LAYOUT_DIRECTION_LTR;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletActivity.java b/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletActivity.java
new file mode 100644
index 000000000000..ba063a8ba303
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletActivity.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.wallet.ui;
+
+import android.graphics.Color;
+import android.graphics.drawable.ColorDrawable;
+import android.os.Bundle;
+import android.os.Handler;
+import android.service.quickaccesswallet.QuickAccessWalletClient;
+import android.view.MenuItem;
+import android.view.Window;
+
+import androidx.annotation.NonNull;
+
+import com.android.systemui.R;
+import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.settings.UserTracker;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.util.LifecycleActivity;
+
+import java.util.concurrent.Executor;
+
+import javax.inject.Inject;
+
+/**
+ * Displays Wallet carousel screen inside an activity.
+ */
+public class WalletActivity extends LifecycleActivity {
+
+ private final QuickAccessWalletClient mQuickAccessWalletClient;
+ private final KeyguardStateController mKeyguardStateController;
+ private final ActivityStarter mActivityStarter;
+ private final Executor mExecutor;
+ private final Handler mHandler;
+ private final UserTracker mUserTracker;
+ private WalletScreenController mWalletScreenController;
+
+ @Inject
+ public WalletActivity(
+ QuickAccessWalletClient quickAccessWalletClient,
+ KeyguardStateController keyguardStateController,
+ ActivityStarter activityStarter,
+ @Background Executor executor,
+ @Background Handler handler,
+ UserTracker userTracker) {
+ mQuickAccessWalletClient = quickAccessWalletClient;
+ mKeyguardStateController = keyguardStateController;
+ mActivityStarter = activityStarter;
+ mExecutor = executor;
+ mHandler = handler;
+ mUserTracker = userTracker;
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ getWindow().requestFeature(Window.FEATURE_ACTION_BAR_OVERLAY);
+ super.onCreate(savedInstanceState);
+
+ setContentView(R.layout.quick_access_wallet);
+
+ getWindow().getDecorView().setBackgroundColor(getColor(R.color.wallet_white));
+ setTitle("");
+ getActionBar().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
+ getActionBar().setDisplayHomeAsUpEnabled(true);
+ getActionBar().setHomeAsUpIndicator(R.drawable.ic_close);
+ getActionBar().setHomeActionContentDescription(R.string.accessibility_desc_close);
+ WalletView walletView = requireViewById(R.id.wallet_view);
+ mWalletScreenController = new WalletScreenController(
+ this,
+ walletView,
+ mQuickAccessWalletClient,
+ mActivityStarter,
+ mExecutor,
+ mHandler,
+ mUserTracker,
+ !mKeyguardStateController.isUnlocked());
+ walletView.getWalletButton().setOnClickListener(
+ v -> mActivityStarter.startActivity(
+ mQuickAccessWalletClient.createWalletIntent(), true));
+ }
+
+ @Override
+ protected void onStart() {
+ super.onStart();
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(@NonNull MenuItem item) {
+ int itemId = item.getItemId();
+ if (itemId == android.R.id.home) {
+ finish();
+ return true;
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
+ @Override
+ protected void onDestroy() {
+ mWalletScreenController.onDismissed();
+ super.onDestroy();
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletCardCarousel.java b/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletCardCarousel.java
new file mode 100644
index 000000000000..21e55496d3e6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletCardCarousel.java
@@ -0,0 +1,473 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.wallet.ui;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.util.DisplayMetrics;
+import android.view.HapticFeedbackConstants;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.accessibility.AccessibilityEvent;
+import android.widget.ImageView;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.cardview.widget.CardView;
+import androidx.core.view.ViewCompat;
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.LinearSmoothScroller;
+import androidx.recyclerview.widget.PagerSnapHelper;
+import androidx.recyclerview.widget.RecyclerView;
+import androidx.recyclerview.widget.RecyclerViewAccessibilityDelegate;
+
+import com.android.systemui.R;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Card Carousel for displaying Quick Access Wallet cards.
+ */
+public class WalletCardCarousel extends RecyclerView {
+
+ // A negative card margin is required because card shrinkage pushes the cards too far apart
+ private static final float CARD_MARGIN_RATIO = -.03f;
+ // Size of the unselected card as a ratio to size of selected card.
+ private static final float UNSELECTED_CARD_SCALE = .83f;
+ private static final float CORNER_RADIUS_RATIO = 25f / 700f;
+ private static final float CARD_ASPECT_RATIO = 700f / 440f;
+ private static final float CARD_VIEW_WIDTH_RATIO = 0.69f;
+
+
+ static final int CARD_ANIM_ALPHA_DURATION = 100;
+ static final int CARD_ANIM_ALPHA_DELAY = 50;
+
+ private final Rect mSystemGestureExclusionZone = new Rect();
+ private WalletCardCarouselAdapter mWalletCardCarouselAdapter;
+ private int mExpectedViewWidth;
+ private int mCardMarginPx;
+ private int mCardWidthPx;
+ private int mCardHeightPx;
+ private float mCornerRadiusPx;
+ private int mTotalCardWidth;
+ private float mCardEdgeToCenterDistance;
+
+ private OnSelectionListener mSelectionListener;
+ private OnCardScrollListener mCardScrollListener;
+ // Adapter position of the child that is closest to the center of the recycler view, will also
+ // be used in DotIndicatorDecoration.
+ int mCenteredAdapterPosition = RecyclerView.NO_POSITION;
+ // Pixel distance, along y-axis, from the center of the recycler view to the nearest child, will
+ // also be used in DotIndicatorDecoration.
+ float mEdgeToCenterDistance = Float.MAX_VALUE;
+ private float mCardCenterToScreenCenterDistancePx = Float.MAX_VALUE;
+ // When card data is loaded, this many cards should be animated as data is bound to them.
+ private int mNumCardsToAnimate;
+ // When card data is loaded, this is the position of the leftmost card to be animated.
+ private int mCardAnimationStartPosition;
+ // When card data is loaded, the animations may be delayed so that other animations can complete
+ private int mExtraAnimationDelay;
+
+ interface OnSelectionListener {
+ /**
+ * The card was moved to the center, thus selecting it.
+ */
+ void onCardSelected(@NonNull WalletCardViewInfo card);
+
+ /**
+ * The card was clicked.
+ */
+ void onCardClicked(@NonNull WalletCardViewInfo card);
+
+ /**
+ * Cards should be re-queried due to a layout change
+ */
+ void queryWalletCards();
+ }
+
+ interface OnCardScrollListener {
+ void onCardScroll(WalletCardViewInfo centerCard, WalletCardViewInfo nextCard,
+ float percentDistanceFromCenter);
+ }
+
+ public WalletCardCarousel(Context context) {
+ this(context, null);
+ }
+
+ public WalletCardCarousel(Context context, @Nullable AttributeSet attributeSet) {
+ super(context, attributeSet);
+
+ setLayoutManager(new LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false));
+ addOnScrollListener(new CardCarouselScrollListener());
+ new CarouselSnapHelper().attachToRecyclerView(this);
+ mWalletCardCarouselAdapter = new WalletCardCarouselAdapter();
+ mWalletCardCarouselAdapter.setHasStableIds(true);
+ setAdapter(mWalletCardCarouselAdapter);
+ ViewCompat.setAccessibilityDelegate(this, new CardCarouselAccessibilityDelegate(this));
+
+ addItemDecoration(new DotIndicatorDecoration(getContext()));
+ }
+
+ /**
+ * We need to know the card width before we query cards. Card width depends on layout width.
+ * But the carousel isn't laid out until set to visible, which only happens after cards are
+ * returned. Setting the expected view width breaks the chicken-and-egg problem.
+ */
+ void setExpectedViewWidth(int width) {
+ if (mExpectedViewWidth == width) {
+ return;
+ }
+ mExpectedViewWidth = width;
+ Resources res = getResources();
+ DisplayMetrics metrics = res.getDisplayMetrics();
+ int screenWidth = Math.min(metrics.widthPixels, metrics.heightPixels);
+ mCardWidthPx = Math.round(Math.min(width, screenWidth) * CARD_VIEW_WIDTH_RATIO);
+ mCardHeightPx = Math.round(mCardWidthPx / CARD_ASPECT_RATIO);
+ mCornerRadiusPx = mCardWidthPx * CORNER_RADIUS_RATIO;
+ mCardMarginPx = Math.round(mCardWidthPx * CARD_MARGIN_RATIO);
+ mTotalCardWidth = mCardWidthPx + res.getDimensionPixelSize(R.dimen.card_margin) * 2;
+ mCardEdgeToCenterDistance = mTotalCardWidth / 2f;
+ updatePadding(width);
+ if (mSelectionListener != null) {
+ mSelectionListener.queryWalletCards();
+ }
+ }
+
+ @Override
+ public void onViewAdded(View child) {
+ super.onViewAdded(child);
+ LayoutParams layoutParams = (LayoutParams) child.getLayoutParams();
+ layoutParams.leftMargin = mCardMarginPx;
+ layoutParams.rightMargin = mCardMarginPx;
+ child.addOnLayoutChangeListener((v, l, t, r, b, ol, ot, or, ob) -> updateCardView(child));
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ super.onLayout(changed, left, top, right, bottom);
+ int width = getWidth();
+ if (mWalletCardCarouselAdapter.getItemCount() > 1 && width < mTotalCardWidth * 1.5) {
+ // When 2 or more cards are available but only one whole card can be shown on screen at
+ // a time, the entire carousel is opted out from system gesture to help users swipe
+ // between cards without accidentally performing the 'back' gesture. When there is only
+ // one card or when the carousel is large enough to accommodate several whole cards,
+ // there is no need to disable the back gesture since either the user can't swipe or has
+ // plenty of room with which to do so.
+ mSystemGestureExclusionZone.set(0, 0, width, getHeight());
+ setSystemGestureExclusionRects(Collections.singletonList(mSystemGestureExclusionZone));
+ }
+ if (width != mExpectedViewWidth) {
+ updatePadding(width);
+ }
+ }
+
+ void setExtraAnimationDelay(int extraAnimationDelay) {
+ mExtraAnimationDelay = extraAnimationDelay;
+ }
+
+ void setSelectionListener(OnSelectionListener selectionListener) {
+ mSelectionListener = selectionListener;
+ }
+
+ void setCardScrollListener(OnCardScrollListener scrollListener) {
+ mCardScrollListener = scrollListener;
+ }
+
+ int getCardWidthPx() {
+ return mCardWidthPx;
+ }
+
+ int getCardHeightPx() {
+ return mCardHeightPx;
+ }
+
+ /**
+ * Set card data. Returns true if carousel was empty, indicating that views will be animated
+ */
+ boolean setData(List<WalletCardViewInfo> data, int selectedIndex) {
+ boolean wasEmpty = mWalletCardCarouselAdapter.getItemCount() == 0;
+ mWalletCardCarouselAdapter.setData(data);
+ if (wasEmpty) {
+ scrollToPosition(selectedIndex);
+ mNumCardsToAnimate = numCardsOnScreen(data.size(), selectedIndex);
+ mCardAnimationStartPosition = Math.max(selectedIndex - 1, 0);
+ }
+ WalletCardViewInfo selectedCard = data.get(selectedIndex);
+ mCardScrollListener.onCardScroll(selectedCard, selectedCard, 0);
+ return wasEmpty;
+ }
+
+ @Override
+ public void scrollToPosition(int position) {
+ super.scrollToPosition(position);
+ mSelectionListener.onCardSelected(mWalletCardCarouselAdapter.mData.get(position));
+ }
+
+ /**
+ * The number of cards shown on screen when one of the cards is position in the center. This is
+ * also the num
+ */
+ private static int numCardsOnScreen(int numCards, int selectedIndex) {
+ if (numCards <= 2) {
+ return numCards;
+ }
+ // When there are 3 or more cards, 3 cards will be shown unless the first or last card is
+ // centered on screen.
+ return selectedIndex > 0 && selectedIndex < (numCards - 1) ? 3 : 2;
+ }
+
+ /**
+ * The padding pushes the first and last cards in the list to the center when they are
+ * selected.
+ */
+ private void updatePadding(int viewWidth) {
+ int paddingHorizontal = (viewWidth - mTotalCardWidth) / 2 - mCardMarginPx;
+ paddingHorizontal = Math.max(0, paddingHorizontal); // just in case
+ setPadding(paddingHorizontal, getPaddingTop(), paddingHorizontal, getPaddingBottom());
+
+ // re-center selected card after changing padding (if card is selected)
+ if (mWalletCardCarouselAdapter != null
+ && mWalletCardCarouselAdapter.getItemCount() > 0
+ && mCenteredAdapterPosition != NO_POSITION) {
+ ViewHolder viewHolder = findViewHolderForAdapterPosition(mCenteredAdapterPosition);
+ if (viewHolder != null) {
+ View cardView = viewHolder.itemView;
+ int cardCenter = (cardView.getLeft() + cardView.getRight()) / 2;
+ int viewCenter = (getLeft() + getRight()) / 2;
+ int scrollX = cardCenter - viewCenter;
+ scrollBy(scrollX, 0);
+ }
+ }
+ }
+
+ private void updateCardView(View view) {
+ WalletCardViewHolder viewHolder = (WalletCardViewHolder) view.getTag();
+ CardView cardView = viewHolder.mCardView;
+ float center = (float) getWidth() / 2f;
+ float viewCenter = (view.getRight() + view.getLeft()) / 2f;
+ float viewWidth = view.getWidth();
+ float position = (viewCenter - center) / viewWidth;
+ float scaleFactor = Math.max(UNSELECTED_CARD_SCALE, 1f - Math.abs(position));
+
+ cardView.setScaleX(scaleFactor);
+ cardView.setScaleY(scaleFactor);
+
+ // a card is the "centered card" until its edge has moved past the center of the recycler
+ // view. note that we also need to factor in the negative margin.
+ // Find the edge that is closer to the center.
+ int edgePosition =
+ viewCenter < center ? view.getRight() + mCardMarginPx
+ : view.getLeft() - mCardMarginPx;
+
+ if (Math.abs(viewCenter - center) < mCardCenterToScreenCenterDistancePx) {
+ int childAdapterPosition = getChildAdapterPosition(view);
+ if (childAdapterPosition == RecyclerView.NO_POSITION) {
+ return;
+ }
+ mCenteredAdapterPosition = getChildAdapterPosition(view);
+ mEdgeToCenterDistance = edgePosition - center;
+ mCardCenterToScreenCenterDistancePx = Math.abs(viewCenter - center);
+ }
+ }
+
+ private class CardCarouselScrollListener extends OnScrollListener {
+
+ private int mOldState = -1;
+
+ @Override
+ public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
+ if (newState == RecyclerView.SCROLL_STATE_IDLE && newState != mOldState) {
+ performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
+ }
+ mOldState = newState;
+ }
+
+ /**
+ * Callback method to be invoked when the RecyclerView has been scrolled. This will be
+ * called after the scroll has completed.
+ *
+ * <p>This callback will also be called if visible item range changes after a layout
+ * calculation. In that case, dx and dy will be 0.
+ *
+ * @param recyclerView The RecyclerView which scrolled.
+ * @param dx The amount of horizontal scroll.
+ * @param dy The amount of vertical scroll.
+ */
+ @Override
+ public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
+ mCenteredAdapterPosition = RecyclerView.NO_POSITION;
+ mEdgeToCenterDistance = Float.MAX_VALUE;
+ mCardCenterToScreenCenterDistancePx = Float.MAX_VALUE;
+ for (int i = 0; i < getChildCount(); i++) {
+ updateCardView(getChildAt(i));
+ }
+ if (mCenteredAdapterPosition == RecyclerView.NO_POSITION || dx == 0) {
+ return;
+ }
+
+ int nextAdapterPosition =
+ mCenteredAdapterPosition + (mEdgeToCenterDistance > 0 ? 1 : -1);
+ if (nextAdapterPosition < 0
+ || nextAdapterPosition >= mWalletCardCarouselAdapter.mData.size()) {
+ return;
+ }
+
+ // Update the label text based on the currently selected card and the next one
+ WalletCardViewInfo centerCard =
+ mWalletCardCarouselAdapter.mData.get(mCenteredAdapterPosition);
+ WalletCardViewInfo nextCard = mWalletCardCarouselAdapter.mData.get(nextAdapterPosition);
+ float percentDistanceFromCenter =
+ Math.abs(mEdgeToCenterDistance) / mCardEdgeToCenterDistance;
+ mCardScrollListener.onCardScroll(centerCard, nextCard, percentDistanceFromCenter);
+ }
+ }
+
+ private class CarouselSnapHelper extends PagerSnapHelper {
+
+ private static final float MILLISECONDS_PER_INCH = 200.0F;
+ private static final int MAX_SCROLL_ON_FLING_DURATION = 80; // ms
+
+ @Override
+ public View findSnapView(LayoutManager layoutManager) {
+ View view = super.findSnapView(layoutManager);
+ if (view == null) {
+ // implementation decides not to snap
+ return null;
+ }
+ WalletCardViewHolder viewHolder = (WalletCardViewHolder) view.getTag();
+ WalletCardViewInfo card = viewHolder.mCardViewInfo;
+ mSelectionListener.onCardSelected(card);
+ mCardScrollListener.onCardScroll(card, card, 0);
+ return view;
+ }
+
+ /**
+ * The default SnapScroller is a little sluggish
+ */
+ @Override
+ protected LinearSmoothScroller createScroller(LayoutManager layoutManager) {
+ return new LinearSmoothScroller(getContext()) {
+ @Override
+ protected void onTargetFound(View targetView, State state, Action action) {
+ int[] snapDistances = calculateDistanceToFinalSnap(layoutManager, targetView);
+ final int dx = snapDistances[0];
+ final int dy = snapDistances[1];
+ final int time = calculateTimeForDeceleration(
+ Math.max(Math.abs(dx), Math.abs(dy)));
+ if (time > 0) {
+ action.update(dx, dy, time, mDecelerateInterpolator);
+ }
+ }
+
+ @Override
+ protected float calculateSpeedPerPixel(DisplayMetrics displayMetrics) {
+ return MILLISECONDS_PER_INCH / displayMetrics.densityDpi;
+ }
+
+ @Override
+ protected int calculateTimeForScrolling(int dx) {
+ return Math.min(MAX_SCROLL_ON_FLING_DURATION,
+ super.calculateTimeForScrolling(dx));
+ }
+ };
+ }
+ }
+
+ private class WalletCardCarouselAdapter extends Adapter<WalletCardViewHolder> {
+
+ private List<WalletCardViewInfo> mData = Collections.EMPTY_LIST;
+
+ @NonNull
+ @Override
+ public WalletCardViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) {
+ LayoutInflater inflater = LayoutInflater.from(viewGroup.getContext());
+ View view = inflater.inflate(R.layout.wallet_card_view, viewGroup, false);
+ WalletCardViewHolder viewHolder = new WalletCardViewHolder(view);
+ CardView cardView = viewHolder.mCardView;
+ cardView.setRadius(mCornerRadiusPx);
+ ViewGroup.LayoutParams layoutParams = cardView.getLayoutParams();
+ layoutParams.width = mCardWidthPx;
+ layoutParams.height = mCardHeightPx;
+ view.setTag(viewHolder);
+ return viewHolder;
+ }
+
+ @Override
+ public void onBindViewHolder(@NonNull WalletCardViewHolder viewHolder, int position) {
+ WalletCardViewInfo cardViewInfo = mData.get(position);
+ viewHolder.mCardViewInfo = cardViewInfo;
+ if (cardViewInfo.getCardId().isEmpty()) {
+ viewHolder.mImageView.setScaleType(ImageView.ScaleType.CENTER);
+ }
+ viewHolder.mImageView.setImageDrawable(cardViewInfo.getCardDrawable());
+ viewHolder.mCardView.setContentDescription(cardViewInfo.getContentDescription());
+ viewHolder.mCardView.setOnClickListener(
+ v -> {
+ if (position != mCenteredAdapterPosition) {
+ smoothScrollToPosition(position);
+ } else {
+ mSelectionListener.onCardClicked(cardViewInfo);
+ }
+ });
+ if (mNumCardsToAnimate > 0 && (position - mCardAnimationStartPosition < 2)) {
+ mNumCardsToAnimate--;
+ int startDelay = (position - mCardAnimationStartPosition) * CARD_ANIM_ALPHA_DELAY
+ + mExtraAnimationDelay;
+ viewHolder.itemView.setAlpha(0f);
+ viewHolder.itemView.animate().alpha(1f)
+ .setStartDelay(Math.max(0, startDelay))
+ .setDuration(CARD_ANIM_ALPHA_DURATION).start();
+ }
+ }
+
+ @Override
+ public int getItemCount() {
+ return mData.size();
+ }
+
+ @Override
+ public long getItemId(int position) {
+ return mData.get(position).getCardId().hashCode();
+ }
+
+ void setData(List<WalletCardViewInfo> data) {
+ mData = data;
+ notifyDataSetChanged();
+ }
+ }
+
+ private class CardCarouselAccessibilityDelegate extends RecyclerViewAccessibilityDelegate {
+
+ private CardCarouselAccessibilityDelegate(@NonNull RecyclerView recyclerView) {
+ super(recyclerView);
+ }
+
+ @Override
+ public boolean onRequestSendAccessibilityEvent(
+ ViewGroup viewGroup, View view, AccessibilityEvent accessibilityEvent) {
+ int eventType = accessibilityEvent.getEventType();
+ if (eventType == AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED) {
+ scrollToPosition(getChildAdapterPosition(view));
+ }
+ return super.onRequestSendAccessibilityEvent(viewGroup, view, accessibilityEvent);
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletCardView.java b/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletCardView.java
new file mode 100644
index 000000000000..fc1adc37d09b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletCardView.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.wallet.ui;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.util.AttributeSet;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.cardview.widget.CardView;
+
+import com.android.systemui.R;
+
+/** Customized card view of the wallet card carousel. */
+public class WalletCardView extends CardView {
+ private final Paint mBorderPaint;
+
+ public WalletCardView(@NonNull Context context) {
+ this(context, null);
+ }
+
+ public WalletCardView(@NonNull Context context, @Nullable AttributeSet attrs) {
+ super(context, attrs);
+ mBorderPaint = new Paint();
+ mBorderPaint.setColor(context.getColor(R.color.wallet_card_border));
+ mBorderPaint.setStrokeWidth(
+ context.getResources().getDimension(R.dimen.wallet_card_border_width));
+ mBorderPaint.setStyle(Paint.Style.STROKE);
+ mBorderPaint.setAntiAlias(true);
+ }
+
+ @Override
+ public void draw(Canvas canvas) {
+ super.draw(canvas);
+ float radius = getRadius();
+ canvas.drawRoundRect(0, 0, getWidth(), getHeight(), radius, radius, mBorderPaint);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletCardViewHolder.java b/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletCardViewHolder.java
new file mode 100644
index 000000000000..3197976456e3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletCardViewHolder.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.wallet.ui;
+
+import android.view.View;
+import android.widget.ImageView;
+
+import androidx.cardview.widget.CardView;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.android.systemui.R;
+
+/**
+ * View holder for the quick access wallet card.
+ */
+class WalletCardViewHolder extends RecyclerView.ViewHolder {
+
+ final CardView mCardView;
+ final ImageView mImageView;
+ WalletCardViewInfo mCardViewInfo;
+
+ WalletCardViewHolder(View view) {
+ super(view);
+ mCardView = view.requireViewById(R.id.card);
+ mImageView = mCardView.requireViewById(R.id.card_image);
+ }
+
+}
diff --git a/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletCardViewInfo.java b/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletCardViewInfo.java
new file mode 100644
index 000000000000..669d6664b305
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletCardViewInfo.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.wallet.ui;
+
+import android.graphics.drawable.Drawable;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+interface WalletCardViewInfo {
+ String getCardId();
+
+ /**
+ * Image of the card.
+ */
+ @NonNull
+ Drawable getCardDrawable();
+
+ /**
+ * Content description for the card.
+ */
+ @Nullable
+ CharSequence getContentDescription();
+
+ /**
+ * Icon shown above the card.
+ */
+ @Nullable
+ Drawable getIcon();
+
+ /**
+ * Text shown above the card.
+ */
+ @NonNull
+ CharSequence getLabel();
+}
diff --git a/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletScreenController.java b/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletScreenController.java
new file mode 100644
index 000000000000..a93f0f0ba165
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletScreenController.java
@@ -0,0 +1,330 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.wallet.ui;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.content.res.Resources;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Icon;
+import android.os.Handler;
+import android.service.quickaccesswallet.GetWalletCardsError;
+import android.service.quickaccesswallet.GetWalletCardsRequest;
+import android.service.quickaccesswallet.GetWalletCardsResponse;
+import android.service.quickaccesswallet.QuickAccessWalletClient;
+import android.service.quickaccesswallet.SelectWalletCardRequest;
+import android.service.quickaccesswallet.WalletCard;
+import android.service.quickaccesswallet.WalletServiceEvent;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+
+import androidx.annotation.NonNull;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.R;
+import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.settings.UserTracker;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
+
+/** Controller for the wallet card carousel screen. */
+public class WalletScreenController implements
+ WalletCardCarousel.OnSelectionListener,
+ QuickAccessWalletClient.OnWalletCardsRetrievedCallback,
+ QuickAccessWalletClient.WalletServiceEventListener {
+
+ private static final String TAG = "WalletScreenCtrl";
+ private static final String PREFS_HAS_CARDS = "has_cards";
+ private static final String PREFS_WALLET_VIEW_HEIGHT = "wallet_view_height";
+ private static final int MAX_CARDS = 10;
+ private static final long SELECTION_DELAY_MILLIS = TimeUnit.SECONDS.toMillis(30);
+
+ private Context mContext;
+ private final QuickAccessWalletClient mWalletClient;
+ private final ActivityStarter mActivityStarter;
+ private final Executor mExecutor;
+ private final Handler mHandler;
+ private final Runnable mSelectionRunnable = this::selectCard;
+ private final SharedPreferences mPrefs;
+ private final WalletView mWalletView;
+ private final WalletCardCarousel mCardCarousel;
+
+ @VisibleForTesting String mSelectedCardId;
+ @VisibleForTesting boolean mIsDismissed;
+ private boolean mIsDeviceLocked;
+ private boolean mHasRegisteredListener;
+
+ public WalletScreenController(
+ Context context,
+ WalletView walletView,
+ QuickAccessWalletClient walletClient,
+ ActivityStarter activityStarter,
+ Executor executor,
+ Handler handler,
+ UserTracker userTracker,
+ boolean isDeviceLocked) {
+ mContext = context;
+ mWalletClient = walletClient;
+ mActivityStarter = activityStarter;
+ mExecutor = executor;
+ mHandler = handler;
+ mPrefs = userTracker.getUserContext().getSharedPreferences(TAG, Context.MODE_PRIVATE);
+ mWalletView = walletView;
+ mWalletView.setMinimumHeight(getExpectedMinHeight());
+ mWalletView.setLayoutParams(
+ new FrameLayout.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
+ mCardCarousel = mWalletView.getCardCarousel();
+ if (mCardCarousel != null) {
+ mCardCarousel.setSelectionListener(this);
+ }
+
+ if (!mPrefs.getBoolean(PREFS_HAS_CARDS, false)) {
+ // The empty state view is shown preemptively when cards were not returned last time
+ // to decrease perceived latency.
+ showEmptyStateView();
+ }
+ mIsDeviceLocked = isDeviceLocked;
+ }
+
+ /**
+ * Implements {@link QuickAccessWalletClient.OnWalletCardsRetrievedCallback}. Called when cards
+ * are retrieved successfully from the service. This is called on {@link #mExecutor}.
+ */
+ @Override
+ public void onWalletCardsRetrieved(@NonNull GetWalletCardsResponse response) {
+ if (mIsDismissed) {
+ return;
+ }
+ List<WalletCard> walletCards = response.getWalletCards();
+ List<WalletCardViewInfo> data = new ArrayList<>(walletCards.size());
+ for (WalletCard card : walletCards) {
+ data.add(new QAWalletCardViewInfo(mContext, card));
+ }
+ mHandler.post(() -> {
+ if (data.isEmpty()) {
+ showEmptyStateView();
+ } else {
+ mWalletView.showCardCarousel(data, response.getSelectedIndex(), mIsDeviceLocked);
+ }
+ // The empty state view will not be shown preemptively next time if cards were returned
+ mPrefs.edit().putBoolean(PREFS_HAS_CARDS, !data.isEmpty()).apply();
+ removeMinHeightAndRecordHeightOnLayout();
+ });
+ }
+
+ /**
+ * Implements {@link QuickAccessWalletClient.OnWalletCardsRetrievedCallback}. Called when there
+ * is an error during card retrieval. This will be run on the {@link #mExecutor}.
+ */
+ @Override
+ public void onWalletCardRetrievalError(@NonNull GetWalletCardsError error) {
+ if (mIsDismissed) {
+ return;
+ }
+ mHandler.post(() -> {
+ mWalletView.showErrorMessage(error.getMessage());
+ });
+ }
+
+ /**
+ * Implements {@link QuickAccessWalletClient.WalletServiceEventListener}. Called when the wallet
+ * application propagates an event, such as an NFC tap, to the quick access wallet view.
+ */
+ @Override
+ public void onWalletServiceEvent(WalletServiceEvent event) {
+ if (mIsDismissed) {
+ return;
+ }
+ switch (event.getEventType()) {
+ case WalletServiceEvent.TYPE_NFC_PAYMENT_STARTED:
+ onDismissed();
+ break;
+ case WalletServiceEvent.TYPE_WALLET_CARDS_UPDATED:
+ queryWalletCards();
+ break;
+ default:
+ Log.w(TAG, "onWalletServiceEvent: Unknown event type");
+ }
+ }
+
+ @Override
+ public void onCardSelected(@NonNull WalletCardViewInfo card) {
+ if (mIsDismissed) {
+ return;
+ }
+ mSelectedCardId = card.getCardId();
+ selectCard();
+ }
+
+ private void selectCard() {
+ mHandler.removeCallbacks(mSelectionRunnable);
+ String selectedCardId = mSelectedCardId;
+ if (mIsDismissed || selectedCardId == null) {
+ return;
+ }
+ mWalletClient.selectWalletCard(new SelectWalletCardRequest(selectedCardId));
+ // Re-selecting the card keeps the connection bound so we continue to get service events
+ // even if the user keeps it open for a long time.
+ mHandler.postDelayed(mSelectionRunnable, SELECTION_DELAY_MILLIS);
+ }
+
+
+
+ @Override
+ public void onCardClicked(@NonNull WalletCardViewInfo cardInfo) {
+ if (!(cardInfo instanceof QAWalletCardViewInfo)
+ || ((QAWalletCardViewInfo) cardInfo).mWalletCard == null
+ || ((QAWalletCardViewInfo) cardInfo).mWalletCard.getPendingIntent() == null) {
+ return;
+ }
+ mActivityStarter.startActivity(
+ ((QAWalletCardViewInfo) cardInfo).mWalletCard.getPendingIntent().getIntent(),
+ true);
+ }
+
+ @Override
+ public void queryWalletCards() {
+ if (mIsDismissed) {
+ return;
+ }
+ if (!mHasRegisteredListener) {
+ // Listener is registered even when device is locked. Should only be registered once.
+ mWalletClient.addWalletServiceEventListener(this);
+ mHasRegisteredListener = true;
+ }
+
+ mWalletView.show();
+ mWalletView.hideErrorMessage();
+ int iconSizePx = mContext.getResources().getDimensionPixelSize(R.dimen.wallet_icon_size);
+ int cardWidthPx = mCardCarousel.getCardWidthPx();
+ int cardHeightPx = mCardCarousel.getCardHeightPx();
+ GetWalletCardsRequest request =
+ new GetWalletCardsRequest(cardWidthPx, cardHeightPx, iconSizePx, MAX_CARDS);
+ mWalletClient.getWalletCards(mExecutor, request, this);
+ }
+
+ void onDismissed() {
+ if (mIsDismissed) {
+ return;
+ }
+ mIsDismissed = true;
+ mSelectedCardId = null;
+ mHandler.removeCallbacks(mSelectionRunnable);
+ mWalletClient.notifyWalletDismissed();
+ mWalletClient.removeWalletServiceEventListener(this);
+ mWalletView.animateDismissal();
+ // clear refs to the Wallet Activity
+ mContext = null;
+ }
+
+ private void showEmptyStateView() {
+ Drawable logo = mWalletClient.getLogo();
+ CharSequence logoContentDesc = mWalletClient.getServiceLabel();
+ CharSequence label = mWalletClient.getShortcutLongLabel();
+ Intent intent = mWalletClient.createWalletIntent();
+ if (logo == null
+ || TextUtils.isEmpty(logoContentDesc)
+ || TextUtils.isEmpty(label)
+ || intent == null) {
+ Log.w(TAG, "QuickAccessWalletService manifest entry mis-configured");
+ // Issue is not likely to be resolved until manifest entries are enabled.
+ // Hide wallet feature until then.
+ mWalletView.hide();
+ mPrefs.edit().putInt(PREFS_WALLET_VIEW_HEIGHT, 0).apply();
+ } else {
+ logo.setTint(mContext.getColor(R.color.GM2_grey_900));
+ mWalletView.showEmptyStateView(
+ logo,
+ logoContentDesc,
+ label,
+ v -> mActivityStarter.startActivity(intent, true));
+ }
+ }
+
+ private int getExpectedMinHeight() {
+ int expectedHeight = mPrefs.getInt(PREFS_WALLET_VIEW_HEIGHT, -1);
+ if (expectedHeight == -1) {
+ Resources res = mContext.getResources();
+ expectedHeight = res.getDimensionPixelSize(R.dimen.min_wallet_empty_height);
+ }
+ return expectedHeight;
+ }
+
+ private void removeMinHeightAndRecordHeightOnLayout() {
+ mWalletView.setMinimumHeight(0);
+ mWalletView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
+ @Override
+ public void onLayoutChange(View v, int left, int top, int right, int bottom,
+ int oldLeft, int oldTop, int oldRight, int oldBottom) {
+ mWalletView.removeOnLayoutChangeListener(this);
+ mPrefs.edit().putInt(PREFS_WALLET_VIEW_HEIGHT, bottom - top).apply();
+ }
+ });
+ }
+
+ @VisibleForTesting
+ static class QAWalletCardViewInfo implements WalletCardViewInfo {
+
+ private final WalletCard mWalletCard;
+ private final Drawable mCardDrawable;
+ private final Drawable mIconDrawable;
+
+ /**
+ * Constructor is called on background executor, so it is safe to load drawables
+ * synchronously.
+ */
+ QAWalletCardViewInfo(Context context, WalletCard walletCard) {
+ mWalletCard = walletCard;
+ mCardDrawable = mWalletCard.getCardImage().loadDrawable(context);
+ Icon icon = mWalletCard.getCardIcon();
+ mIconDrawable = icon == null ? null : icon.loadDrawable(context);
+ }
+
+ @Override
+ public String getCardId() {
+ return mWalletCard.getCardId();
+ }
+
+ @Override
+ public Drawable getCardDrawable() {
+ return mCardDrawable;
+ }
+
+ @Override
+ public CharSequence getContentDescription() {
+ return mWalletCard.getContentDescription();
+ }
+
+ @Override
+ public Drawable getIcon() {
+ return mIconDrawable;
+ }
+
+ @Override
+ public CharSequence getLabel() {
+ return mWalletCard.getCardLabel();
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletView.java b/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletView.java
new file mode 100644
index 000000000000..d2f0720fa66b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletView.java
@@ -0,0 +1,242 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.wallet.ui;
+
+import static com.android.systemui.wallet.ui.WalletCardCarousel.CARD_ANIM_ALPHA_DELAY;
+import static com.android.systemui.wallet.ui.WalletCardCarousel.CARD_ANIM_ALPHA_DURATION;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.ViewGroup;
+import android.view.animation.AnimationUtils;
+import android.view.animation.Interpolator;
+import android.widget.Button;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.R;
+
+import java.util.List;
+
+/** Layout for the wallet screen. */
+public class WalletView extends FrameLayout implements WalletCardCarousel.OnCardScrollListener {
+
+ private static final int CAROUSEL_IN_ANIMATION_DURATION = 300;
+ private static final int CAROUSEL_OUT_ANIMATION_DURATION = 200;
+ private static final int CARD_LABEL_ANIM_DELAY = 133;
+ private static final int CONTACTLESS_ICON_SIZE = 90;
+
+ private final WalletCardCarousel mCardCarousel;
+ private final ImageView mIcon;
+ private final TextView mCardLabel;
+ private final Button mWalletButton;
+ private final Interpolator mInInterpolator;
+ private final Interpolator mOutInterpolator;
+ private final float mAnimationTranslationX;
+ private final ViewGroup mCardCarouselContainer;
+ private final TextView mErrorView;
+ private final ViewGroup mEmptyStateView;
+ private CharSequence mCenterCardText;
+
+ public WalletView(Context context) {
+ this(context, null);
+ }
+
+ public WalletView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ inflate(context, R.layout.wallet_fullscreen, this);
+ mCardCarouselContainer = requireViewById(R.id.card_carousel_container);
+ mCardCarousel = requireViewById(R.id.card_carousel);
+ mCardCarousel.setCardScrollListener(this);
+ mIcon = requireViewById(R.id.icon);
+ mCardLabel = requireViewById(R.id.label);
+ mWalletButton = requireViewById(R.id.wallet_button);
+ mErrorView = requireViewById(R.id.error_view);
+ mEmptyStateView = requireViewById(R.id.wallet_empty_state);
+ mInInterpolator =
+ AnimationUtils.loadInterpolator(context, android.R.interpolator.fast_out_slow_in);
+ mOutInterpolator =
+ AnimationUtils.loadInterpolator(context, android.R.interpolator.accelerate_cubic);
+ mAnimationTranslationX = mCardCarousel.getCardWidthPx() / 4f;
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ super.onLayout(changed, left, top, right, bottom);
+ mCardCarousel.setExpectedViewWidth(getWidth());
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ // Forward touch events to card carousel to allow for swiping outside carousel bounds.
+ return mCardCarousel.onTouchEvent(event) || super.onTouchEvent(event);
+ }
+
+ @Override
+ public void onCardScroll(WalletCardViewInfo centerCard, WalletCardViewInfo nextCard,
+ float percentDistanceFromCenter) {
+ CharSequence centerCardText = centerCard.getLabel();
+ Drawable icon = centerCard.getIcon();
+ if (icon != null) {
+ mIcon.setImageDrawable(resizeDrawable(getResources(), icon));
+ mIcon.setVisibility(VISIBLE);
+ } else {
+ mIcon.setVisibility(INVISIBLE);
+ }
+ if (!TextUtils.equals(mCenterCardText, centerCardText)) {
+ mCenterCardText = centerCardText;
+ mCardLabel.setText(centerCardText);
+ }
+ if (TextUtils.equals(centerCardText, nextCard.getLabel())) {
+ mCardLabel.setAlpha(1f);
+ } else {
+ mCardLabel.setAlpha(percentDistanceFromCenter);
+ mIcon.setAlpha(percentDistanceFromCenter);
+ }
+ }
+
+ void showCardCarousel(
+ List<WalletCardViewInfo> data, int selectedIndex, boolean isDeviceLocked) {
+ boolean shouldAnimate = mCardCarousel.setData(data, selectedIndex);
+ mCardCarouselContainer.setVisibility(VISIBLE);
+ mErrorView.setVisibility(GONE);
+ if (isDeviceLocked) {
+ // TODO(b/182964813): Add click action to prompt device unlock.
+ mWalletButton.setText(R.string.wallet_button_label_device_locked);
+ } else {
+ mWalletButton.setText(R.string.wallet_button_label_device_unlocked);
+ }
+ if (shouldAnimate) {
+ // If the empty state is visible, animate it away and delay the card carousel animation
+ int emptyStateAnimDelay = 0;
+ if (mEmptyStateView.getVisibility() == VISIBLE) {
+ emptyStateAnimDelay = CARD_ANIM_ALPHA_DURATION;
+ mEmptyStateView.animate()
+ .alpha(0)
+ .setDuration(emptyStateAnimDelay)
+ .setListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mEmptyStateView.setVisibility(GONE);
+ }
+ })
+ .start();
+ }
+ mCardLabel.setAlpha(0f);
+ mCardLabel.animate().alpha(1f)
+ .setStartDelay(CARD_LABEL_ANIM_DELAY + emptyStateAnimDelay)
+ .setDuration(CARD_ANIM_ALPHA_DURATION)
+ .start();
+ mCardCarousel.setExtraAnimationDelay(emptyStateAnimDelay);
+ mCardCarousel.setTranslationX(mAnimationTranslationX);
+ mCardCarousel.animate().translationX(0)
+ .setInterpolator(mInInterpolator)
+ .setDuration(CAROUSEL_IN_ANIMATION_DURATION)
+ .setStartDelay(emptyStateAnimDelay)
+ .start();
+ }
+ }
+
+ void animateDismissal() {
+ if (mCardCarouselContainer.getVisibility() != VISIBLE) {
+ return;
+ }
+ mCardCarousel.animate().translationX(mAnimationTranslationX)
+ .setInterpolator(mOutInterpolator)
+ .setDuration(CAROUSEL_OUT_ANIMATION_DURATION)
+ .start();
+ mCardCarouselContainer.animate()
+ .alpha(0f)
+ .setDuration(CARD_ANIM_ALPHA_DURATION)
+ .setStartDelay(CARD_ANIM_ALPHA_DELAY)
+ .start();
+ }
+
+ void showEmptyStateView(Drawable logo, CharSequence logoContentDescription, CharSequence label,
+ OnClickListener clickListener) {
+ mEmptyStateView.setVisibility(VISIBLE);
+ mErrorView.setVisibility(GONE);
+ mCardCarouselContainer.setVisibility(GONE);
+ ImageView logoView = mEmptyStateView.requireViewById(R.id.empty_state_icon);
+ logoView.setImageDrawable(logo);
+ logoView.setContentDescription(logoContentDescription);
+ mEmptyStateView.<TextView>requireViewById(R.id.empty_state_title).setText(label);
+ mEmptyStateView.setOnClickListener(clickListener);
+ }
+
+ void showErrorMessage(@Nullable CharSequence message) {
+ if (TextUtils.isEmpty(message)) {
+ message = getResources().getText(R.string.wallet_error_generic);
+ }
+ mErrorView.setText(message);
+ mErrorView.setVisibility(VISIBLE);
+ mCardCarouselContainer.setVisibility(GONE);
+ mEmptyStateView.setVisibility(GONE);
+ }
+
+ void hide() {
+ setVisibility(GONE);
+ }
+
+ void show() {
+ setVisibility(VISIBLE);
+ }
+
+ void hideErrorMessage() {
+ mErrorView.setVisibility(GONE);
+ }
+
+ WalletCardCarousel getCardCarousel() {
+ return mCardCarousel;
+ }
+
+ Button getWalletButton() {
+ return mWalletButton;
+ }
+
+ @VisibleForTesting
+ TextView getErrorView() {
+ return mErrorView;
+ }
+
+ @VisibleForTesting
+ ViewGroup getEmptyStateView() {
+ return mEmptyStateView;
+ }
+
+ @VisibleForTesting
+ ViewGroup getCardCarouselContainer() {
+ return mCardCarouselContainer;
+ }
+
+ private static Drawable resizeDrawable(Resources resources, Drawable drawable) {
+ Bitmap bitmap = ((BitmapDrawable) drawable).getBitmap();
+ return new BitmapDrawable(resources, Bitmap.createScaledBitmap(
+ bitmap, CONTACTLESS_ICON_SIZE, CONTACTLESS_ICON_SIZE, true));
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/TvPipModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/TvPipModule.java
index 82dad68f238c..2216a915b0d9 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/TvPipModule.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/TvPipModule.java
@@ -34,6 +34,7 @@ import com.android.wm.shell.pip.PipAnimationController;
import com.android.wm.shell.pip.PipBoundsAlgorithm;
import com.android.wm.shell.pip.PipBoundsState;
import com.android.wm.shell.pip.PipMediaController;
+import com.android.wm.shell.pip.PipSnapAlgorithm;
import com.android.wm.shell.pip.PipSurfaceTransactionHelper;
import com.android.wm.shell.pip.PipTaskOrganizer;
import com.android.wm.shell.pip.PipTransitionController;
@@ -85,9 +86,15 @@ public abstract class TvPipModule {
@WMSingleton
@Provides
- static PipBoundsAlgorithm providePipBoundsHandler(Context context,
- PipBoundsState pipBoundsState) {
- return new PipBoundsAlgorithm(context, pipBoundsState);
+ static PipSnapAlgorithm providePipSnapAlgorithm() {
+ return new PipSnapAlgorithm();
+ }
+
+ @WMSingleton
+ @Provides
+ static PipBoundsAlgorithm providePipBoundsAlgorithm(Context context,
+ PipBoundsState pipBoundsState, PipSnapAlgorithm pipSnapAlgorithm) {
+ return new PipBoundsAlgorithm(context, pipBoundsState, pipSnapAlgorithm);
}
@WMSingleton
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
index 4eb75ebe4553..74b79d57adcb 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
@@ -296,6 +296,11 @@ public final class WMShell extends SystemUI
}
oneHanded.stopOneHanded();
}
+
+ @Override
+ public void onUserSwitchComplete(int userId) {
+ oneHanded.onUserSwitch(userId);
+ }
};
mKeyguardUpdateMonitor.registerCallback(mOneHandedKeyguardCallback);
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java
index e403cfaee34a..45b0b59cd1d9 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java
@@ -416,6 +416,7 @@ public abstract class WMShellBaseModule {
static ShellInitImpl provideShellInitImpl(DisplayImeController displayImeController,
DragAndDropController dragAndDropController,
ShellTaskOrganizer shellTaskOrganizer,
+ Optional<BubbleController> bubblesOptional,
Optional<LegacySplitScreenController> legacySplitScreenOptional,
Optional<SplitScreenController> splitScreenOptional,
Optional<AppPairsController> appPairsOptional,
@@ -427,6 +428,7 @@ public abstract class WMShellBaseModule {
return new ShellInitImpl(displayImeController,
dragAndDropController,
shellTaskOrganizer,
+ bubblesOptional,
legacySplitScreenOptional,
splitScreenOptional,
appPairsOptional,
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellModule.java
index d5183f85ad13..33b2d67e462e 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellModule.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellModule.java
@@ -37,11 +37,13 @@ import com.android.wm.shell.common.TransactionPool;
import com.android.wm.shell.common.annotations.ChoreographerSfVsync;
import com.android.wm.shell.common.annotations.ShellMainThread;
import com.android.wm.shell.legacysplitscreen.LegacySplitScreenController;
+import com.android.wm.shell.onehanded.OneHandedController;
import com.android.wm.shell.pip.Pip;
import com.android.wm.shell.pip.PipAnimationController;
import com.android.wm.shell.pip.PipBoundsAlgorithm;
import com.android.wm.shell.pip.PipBoundsState;
import com.android.wm.shell.pip.PipMediaController;
+import com.android.wm.shell.pip.PipSnapAlgorithm;
import com.android.wm.shell.pip.PipSurfaceTransactionHelper;
import com.android.wm.shell.pip.PipTaskOrganizer;
import com.android.wm.shell.pip.PipTransition;
@@ -50,6 +52,7 @@ import com.android.wm.shell.pip.PipUiEventLogger;
import com.android.wm.shell.pip.phone.PhonePipMenuController;
import com.android.wm.shell.pip.phone.PipAppOpsListener;
import com.android.wm.shell.pip.phone.PipController;
+import com.android.wm.shell.pip.phone.PipMotionHelper;
import com.android.wm.shell.pip.phone.PipTouchHandler;
import com.android.wm.shell.transition.Transitions;
@@ -123,11 +126,12 @@ public class WMShellModule {
PipTouchHandler pipTouchHandler, PipTransitionController pipTransitionController,
WindowManagerShellWrapper windowManagerShellWrapper,
TaskStackListenerImpl taskStackListener,
+ Optional<OneHandedController> oneHandedController,
@ShellMainThread ShellExecutor mainExecutor) {
return Optional.ofNullable(PipController.create(context, displayController,
pipAppOpsListener, pipBoundsAlgorithm, pipBoundsState, pipMediaController,
phonePipMenuController, pipTaskOrganizer, pipTouchHandler, pipTransitionController,
- windowManagerShellWrapper, taskStackListener, mainExecutor));
+ windowManagerShellWrapper, taskStackListener, oneHandedController, mainExecutor));
}
@WMSingleton
@@ -138,9 +142,15 @@ public class WMShellModule {
@WMSingleton
@Provides
- static PipBoundsAlgorithm providesPipBoundsHandler(Context context,
- PipBoundsState pipBoundsState) {
- return new PipBoundsAlgorithm(context, pipBoundsState);
+ static PipSnapAlgorithm providePipSnapAlgorithm() {
+ return new PipSnapAlgorithm();
+ }
+
+ @WMSingleton
+ @Provides
+ static PipBoundsAlgorithm providesPipBoundsAlgorithm(Context context,
+ PipBoundsState pipBoundsState, PipSnapAlgorithm pipSnapAlgorithm) {
+ return new PipBoundsAlgorithm(context, pipBoundsState, pipSnapAlgorithm);
}
// Handler is used by Icon.loadDrawableAsync
@@ -160,12 +170,12 @@ public class WMShellModule {
PhonePipMenuController menuPhoneController, PipBoundsAlgorithm pipBoundsAlgorithm,
PipBoundsState pipBoundsState,
PipTaskOrganizer pipTaskOrganizer,
- PipTransitionController pipTransitionController,
+ PipMotionHelper pipMotionHelper,
FloatingContentCoordinator floatingContentCoordinator,
PipUiEventLogger pipUiEventLogger,
@ShellMainThread ShellExecutor mainExecutor) {
return new PipTouchHandler(context, menuPhoneController, pipBoundsAlgorithm,
- pipBoundsState, pipTaskOrganizer, pipTransitionController,
+ pipBoundsState, pipTaskOrganizer, pipMotionHelper,
floatingContentCoordinator, pipUiEventLogger, mainExecutor);
}
@@ -206,4 +216,16 @@ public class WMShellModule {
return new PipTransition(context, pipBoundsState, pipMenuController,
pipBoundsAlgorithm, pipAnimationController, transitions, shellTaskOrganizer);
}
+
+ @WMSingleton
+ @Provides
+ static PipMotionHelper providePipMotionHelper(Context context,
+ PipBoundsState pipBoundsState, PipTaskOrganizer pipTaskOrganizer,
+ PhonePipMenuController menuController, PipSnapAlgorithm pipSnapAlgorithm,
+ PipTransitionController pipTransitionController,
+ FloatingContentCoordinator floatingContentCoordinator) {
+ return new PipMotionHelper(context, pipBoundsState, pipTaskOrganizer,
+ menuController, pipSnapAlgorithm, pipTransitionController,
+ floatingContentCoordinator);
+ }
}