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/AnimatableClockController.java5
-rw-r--r--packages/SystemUI/src/com/android/keyguard/AnimatableClockView.java51
-rw-r--r--packages/SystemUI/src/com/android/keyguard/EmergencyButtonController.java9
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java6
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java50
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardMessageArea.java14
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java9
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java36
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java2
-rwxr-xr-xpackages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java140
-rw-r--r--packages/SystemUI/src/com/android/keyguard/LockIconView.java19
-rw-r--r--packages/SystemUI/src/com/android/keyguard/LockIconViewController.java241
-rw-r--r--packages/SystemUI/src/com/android/keyguard/NumPadButton.java13
-rw-r--r--packages/SystemUI/src/com/android/keyguard/NumPadKey.java13
-rw-r--r--packages/SystemUI/src/com/android/keyguard/PasswordTextView.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/Dependency.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuController.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuView.java141
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/ItemDelegateCompat.java141
-rw-r--r--packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java26
-rw-r--r--packages/SystemUI/src/com/android/systemui/backup/BackupHelper.kt14
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceToFingerprintView.java71
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java97
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java83
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthDialog.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt58
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/ModalityListener.java36
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/SidefpsController.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationView.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java209
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapter.java16
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollView.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollViewController.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsHapticsSimulator.kt94
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardDrawable.java127
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.java177
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsSurfaceView.java128
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.java72
-rw-r--r--packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/classifier/Classifier.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/classifier/DistanceClassifier.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/classifier/TypeClassifier.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/doze/AlwaysOnDisplayPolicy.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java32
-rw-r--r--packages/SystemUI/src/com/android/systemui/doze/util/BurnInHelper.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java17
-rw-r--r--packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java14
-rw-r--r--packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsInfoProvider.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsPopupMenu.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndication.java28
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java42
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt64
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaCarouselScrollHandler.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java22
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt13
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java44
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java17
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputGroupAdapter.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java36
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/gestural/RegionSamplingHelper.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/people/PeopleBackupFollowUpJob.java229
-rw-r--r--packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java27
-rw-r--r--packages/SystemUI/src/com/android/systemui/people/PeopleStoryIconFactory.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java444
-rw-r--r--packages/SystemUI/src/com/android/systemui/people/SharedPreferencesHelper.java59
-rw-r--r--packages/SystemUI/src/com/android/systemui/people/widget/LaunchConversationActivity.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/people/widget/PeopleBackupHelper.java508
-rw-r--r--packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java212
-rw-r--r--packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetPinnedReceiver.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetProvider.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/people/widget/PeopleTileKey.java33
-rw-r--r--packages/SystemUI/src/com/android/systemui/power/InattentiveSleepWarningView.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/privacy/television/PrivacyChipDrawable.java390
-rw-r--r--packages/SystemUI/src/com/android/systemui/privacy/television/TvOngoingPrivacyChip.java279
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java49
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java64
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSContainerImplController.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSFragment.java31
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSPanel.java72
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java49
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java33
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java14
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java18
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/TileLayout.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/carrier/CellSignalState.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrier.java24
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrierGroupController.java67
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java15
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tileimpl/IgnorableChildLinearLayout.kt17
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java22
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyToggleTile.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java15
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialog.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/CropView.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ImageExporter.java29
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java30
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/MagnifierView.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java150
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java243
-rw-r--r--packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt21
-rw-r--r--packages/SystemUI/src/com/android/systemui/sensorprivacy/television/TvUnblockSensorActivity.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderView.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/BlurUtils.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java30
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java59
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt64
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java35
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/StatusBarMobileView.java19
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/SysuiStatusBarStateController.java14
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/charging/ChargingRippleView.kt17
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FooterView.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java32
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java86
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextView.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java155
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java73
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java45
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java18
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java72
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java15
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java18
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt37
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallChronometer.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt61
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyController.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyControllerImpl.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java64
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java49
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java59
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityController.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java10
-rwxr-xr-xpackages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java23
-rw-r--r--packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/toast/SystemUIToast.java37
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/RoundedCornerProgressDrawable.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/sensors/SensorModule.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/sensors/ThresholdSensorImpl.java25
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java132
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/wallet/controller/QuickAccessWalletController.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/wallet/ui/WalletActivity.java20
-rw-r--r--packages/SystemUI/src/com/android/systemui/wallet/ui/WalletScreenController.java16
-rw-r--r--packages/SystemUI/src/com/android/systemui/wallet/ui/WalletUiEvent.java58
-rw-r--r--packages/SystemUI/src/com/android/systemui/wallet/ui/WalletView.java74
189 files changed, 5614 insertions, 1914 deletions
diff --git a/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java b/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java
index 08076c17dc3a..fcf4e4703ed3 100644
--- a/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java
+++ b/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java
@@ -94,7 +94,9 @@ public class AnimatableClockController extends ViewController<AnimatableClockVie
@Override
public void onBatteryLevelChanged(int level, boolean pluggedIn, boolean charging) {
if (mKeyguardShowing && !mIsCharging && charging) {
- mView.animateCharge(mIsDozing);
+ mView.animateCharge(() -> {
+ return mStatusBarStateController.isDozing();
+ });
}
mIsCharging = charging;
}
@@ -210,6 +212,7 @@ public class AnimatableClockController extends ViewController<AnimatableClockVie
} else {
mView.setLineSpacingScale(mDefaultLineSpacing);
}
+ mView.refreshFormat();
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/AnimatableClockView.java b/packages/SystemUI/src/com/android/keyguard/AnimatableClockView.java
index 63867c0f7308..ef3104a21708 100644
--- a/packages/SystemUI/src/com/android/keyguard/AnimatableClockView.java
+++ b/packages/SystemUI/src/com/android/keyguard/AnimatableClockView.java
@@ -19,9 +19,9 @@ package com.android.keyguard;
import android.annotation.FloatRange;
import android.annotation.IntRange;
import android.content.Context;
+import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Canvas;
-import android.icu.text.DateTimePatternGenerator;
import android.text.format.DateFormat;
import android.util.AttributeSet;
import android.widget.TextView;
@@ -30,6 +30,7 @@ import com.android.systemui.R;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import java.util.Calendar;
+import java.util.Locale;
import java.util.TimeZone;
import kotlin.Unit;
@@ -41,8 +42,6 @@ import kotlin.Unit;
public class AnimatableClockView extends TextView {
private static final CharSequence DOUBLE_LINE_FORMAT_12_HOUR = "hh\nmm";
private static final CharSequence DOUBLE_LINE_FORMAT_24_HOUR = "HH\nmm";
- private static final CharSequence SINGLE_LINE_FORMAT_12_HOUR = "h:mm";
- private static final CharSequence SINGLE_LINE_FORMAT_24_HOUR = "HH:mm";
private static final long DOZE_ANIM_DURATION = 300;
private static final long APPEAR_ANIM_DURATION = 350;
private static final long CHARGE_ANIM_DURATION_PHASE_0 = 500;
@@ -196,20 +195,20 @@ public class AnimatableClockView extends TextView {
null /* onAnimationEnd */);
}
- void animateCharge(boolean isDozing) {
+ void animateCharge(DozeStateGetter dozeStateGetter) {
if (mTextAnimator == null || mTextAnimator.isRunning()) {
// Skip charge animation if dozing animation is already playing.
return;
}
Runnable startAnimPhase2 = () -> setTextStyle(
- isDozing ? mDozingWeight : mLockScreenWeight/* weight */,
+ dozeStateGetter.isDozing() ? mDozingWeight : mLockScreenWeight/* weight */,
-1,
null,
true /* animate */,
CHARGE_ANIM_DURATION_PHASE_1,
0 /* delay */,
null /* onAnimationEnd */);
- setTextStyle(isDozing ? mLockScreenWeight : mDozingWeight/* weight */,
+ setTextStyle(dozeStateGetter.isDozing() ? mLockScreenWeight : mDozingWeight/* weight */,
-1,
null,
true /* animate */,
@@ -259,24 +258,50 @@ public class AnimatableClockView extends TextView {
}
void refreshFormat() {
+ Patterns.update(mContext);
+
final boolean use24HourFormat = DateFormat.is24HourFormat(getContext());
if (mIsSingleLine && use24HourFormat) {
- mFormat = SINGLE_LINE_FORMAT_24_HOUR;
+ mFormat = Patterns.sClockView24;
} else if (!mIsSingleLine && use24HourFormat) {
mFormat = DOUBLE_LINE_FORMAT_24_HOUR;
} else if (mIsSingleLine && !use24HourFormat) {
- mFormat = SINGLE_LINE_FORMAT_12_HOUR;
+ mFormat = Patterns.sClockView12;
} else {
mFormat = DOUBLE_LINE_FORMAT_12_HOUR;
}
- mDescFormat = getBestDateTimePattern(getContext(), use24HourFormat ? "Hm" : "hm");
+ mDescFormat = use24HourFormat ? Patterns.sClockView24 : Patterns.sClockView12;
refreshTime();
}
- private static String getBestDateTimePattern(Context context, String skeleton) {
- DateTimePatternGenerator dtpg = DateTimePatternGenerator.getInstance(
- context.getResources().getConfiguration().locale);
- return dtpg.getBestPattern(skeleton);
+ // DateFormat.getBestDateTimePattern is extremely expensive, and refresh is called often.
+ // This is an optimization to ensure we only recompute the patterns when the inputs change.
+ private static final class Patterns {
+ static String sClockView12;
+ static String sClockView24;
+ static String sCacheKey;
+
+ static void update(Context context) {
+ final Locale locale = Locale.getDefault();
+ final Resources res = context.getResources();
+ final String clockView12Skel = res.getString(R.string.clock_12hr_format);
+ final String clockView24Skel = res.getString(R.string.clock_24hr_format);
+ final String key = locale.toString() + clockView12Skel + clockView24Skel;
+ if (key.equals(sCacheKey)) return;
+ sClockView12 = DateFormat.getBestDateTimePattern(locale, clockView12Skel);
+
+ // CLDR insists on adding an AM/PM indicator even though it wasn't in the skeleton
+ // format. The following code removes the AM/PM indicator if we didn't want it.
+ if (!clockView12Skel.contains("a")) {
+ sClockView12 = sClockView12.replaceAll("a", "").trim();
+ }
+ sClockView24 = DateFormat.getBestDateTimePattern(locale, clockView24Skel);
+ sCacheKey = key;
+ }
+ }
+
+ interface DozeStateGetter {
+ boolean isDozing();
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/EmergencyButtonController.java b/packages/SystemUI/src/com/android/keyguard/EmergencyButtonController.java
index 920cf003a893..7064b8e22f2a 100644
--- a/packages/SystemUI/src/com/android/keyguard/EmergencyButtonController.java
+++ b/packages/SystemUI/src/com/android/keyguard/EmergencyButtonController.java
@@ -37,6 +37,7 @@ import androidx.annotation.Nullable;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.keyguard.dagger.KeyguardBouncerScope;
+import com.android.systemui.statusbar.phone.ShadeController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
import com.android.systemui.util.EmergencyDialerConstants;
@@ -54,6 +55,7 @@ public class EmergencyButtonController extends ViewController<EmergencyButton> {
private final TelephonyManager mTelephonyManager;
private final PowerManager mPowerManager;
private final ActivityTaskManager mActivityTaskManager;
+ private ShadeController mShadeController;
private final TelecomManager mTelecomManager;
private final MetricsLogger mMetricsLogger;
@@ -94,6 +96,7 @@ public class EmergencyButtonController extends ViewController<EmergencyButton> {
ConfigurationController configurationController,
KeyguardUpdateMonitor keyguardUpdateMonitor, TelephonyManager telephonyManager,
PowerManager powerManager, ActivityTaskManager activityTaskManager,
+ ShadeController shadeController,
@Nullable TelecomManager telecomManager, MetricsLogger metricsLogger) {
super(view);
mConfigurationController = configurationController;
@@ -101,6 +104,7 @@ public class EmergencyButtonController extends ViewController<EmergencyButton> {
mTelephonyManager = telephonyManager;
mPowerManager = powerManager;
mActivityTaskManager = activityTaskManager;
+ mShadeController = shadeController;
mTelecomManager = telecomManager;
mMetricsLogger = metricsLogger;
}
@@ -145,6 +149,7 @@ public class EmergencyButtonController extends ViewController<EmergencyButton> {
mPowerManager.userActivity(SystemClock.uptimeMillis(), true);
}
mActivityTaskManager.stopSystemLockTaskMode();
+ mShadeController.collapsePanel(false);
if (mTelecomManager != null && mTelecomManager.isInCall()) {
mTelecomManager.showInCallScreen(false);
if (mEmergencyButtonCallback != null) {
@@ -214,6 +219,7 @@ public class EmergencyButtonController extends ViewController<EmergencyButton> {
private final TelephonyManager mTelephonyManager;
private final PowerManager mPowerManager;
private final ActivityTaskManager mActivityTaskManager;
+ private ShadeController mShadeController;
@Nullable
private final TelecomManager mTelecomManager;
private final MetricsLogger mMetricsLogger;
@@ -222,6 +228,7 @@ public class EmergencyButtonController extends ViewController<EmergencyButton> {
public Factory(ConfigurationController configurationController,
KeyguardUpdateMonitor keyguardUpdateMonitor, TelephonyManager telephonyManager,
PowerManager powerManager, ActivityTaskManager activityTaskManager,
+ ShadeController shadeController,
@Nullable TelecomManager telecomManager, MetricsLogger metricsLogger) {
mConfigurationController = configurationController;
@@ -229,6 +236,7 @@ public class EmergencyButtonController extends ViewController<EmergencyButton> {
mTelephonyManager = telephonyManager;
mPowerManager = powerManager;
mActivityTaskManager = activityTaskManager;
+ mShadeController = shadeController;
mTelecomManager = telecomManager;
mMetricsLogger = metricsLogger;
}
@@ -237,6 +245,7 @@ public class EmergencyButtonController extends ViewController<EmergencyButton> {
public EmergencyButtonController create(EmergencyButton view) {
return new EmergencyButtonController(view, mConfigurationController,
mKeyguardUpdateMonitor, mTelephonyManager, mPowerManager, mActivityTaskManager,
+ mShadeController,
mTelecomManager, mMetricsLogger);
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
index ef052c4b81ac..a5b25097a56e 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
@@ -57,6 +57,7 @@ public class KeyguardClockSwitch extends RelativeLayout {
private View mKeyguardStatusArea;
/** Mutually exclusive with mKeyguardStatusArea */
private View mSmartspaceView;
+ private int mSmartspaceTopOffset;
/**
* Maintain state so that a newly connected plugin can be initialized.
@@ -96,6 +97,9 @@ public class KeyguardClockSwitch extends RelativeLayout {
mClockSwitchYAmount = mContext.getResources().getDimensionPixelSize(
R.dimen.keyguard_clock_switch_y_shift);
+
+ mSmartspaceTopOffset = mContext.getResources().getDimensionPixelSize(
+ R.dimen.keyguard_smartspace_top_offset);
}
/**
@@ -193,7 +197,7 @@ public class KeyguardClockSwitch extends RelativeLayout {
if (indexOfChild(in) == -1) addView(in);
direction = -1;
smartspaceYTranslation = mSmartspaceView == null ? 0
- : mClockFrame.getTop() - mSmartspaceView.getTop();
+ : mClockFrame.getTop() - mSmartspaceView.getTop() + mSmartspaceTopOffset;
} else {
in = mClockFrame;
out = mLargeClockFrame;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
index a28d1747f2fe..632919ae51e4 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
@@ -20,9 +20,7 @@ import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
import android.app.WallpaperManager;
-import android.content.res.Resources;
import android.text.TextUtils;
-import android.text.format.DateFormat;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.RelativeLayout;
@@ -189,10 +187,7 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
.getDimensionPixelSize(R.dimen.below_clock_padding_end);
mSmartspaceView.setPaddingRelative(startPadding, 0, endPadding, 0);
- // ... but above the large clock
- lp = new RelativeLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT);
- lp.addRule(RelativeLayout.BELOW, mSmartspaceView.getId());
- mLargeClockFrame.setLayoutParams(lp);
+ updateClockLayout();
View nic = mView.findViewById(
R.id.left_aligned_notification_icon_container);
@@ -235,6 +230,18 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
*/
public void onDensityOrFontScaleChanged() {
mView.onDensityOrFontScaleChanged();
+
+ updateClockLayout();
+ }
+
+ private void updateClockLayout() {
+ if (mSmartspaceController.isEnabled()) {
+ RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams(MATCH_PARENT,
+ MATCH_PARENT);
+ lp.topMargin = getContext().getResources().getDimensionPixelSize(
+ R.dimen.keyguard_large_clock_top_margin);
+ mLargeClockFrame.setLayoutParams(lp);
+ }
}
/**
@@ -358,37 +365,6 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
return mColorExtractor.getColors(WallpaperManager.FLAG_LOCK);
}
- // DateFormat.getBestDateTimePattern is extremely expensive, and refresh is called often.
- // This is an optimization to ensure we only recompute the patterns when the inputs change.
- private static final class Patterns {
- static String sClockView12;
- static String sClockView24;
- static String sCacheKey;
-
- static void update(Resources res) {
- final Locale locale = Locale.getDefault();
- final String clockView12Skel = res.getString(R.string.clock_12hr_format);
- final String clockView24Skel = res.getString(R.string.clock_24hr_format);
- final String key = locale.toString() + clockView12Skel + clockView24Skel;
- if (key.equals(sCacheKey)) return;
-
- sClockView12 = DateFormat.getBestDateTimePattern(locale, clockView12Skel);
- // CLDR insists on adding an AM/PM indicator even though it wasn't in the skeleton
- // format. The following code removes the AM/PM indicator if we didn't want it.
- if (!clockView12Skel.contains("a")) {
- sClockView12 = sClockView12.replaceAll("a", "").trim();
- }
-
- sClockView24 = DateFormat.getBestDateTimePattern(locale, clockView24Skel);
-
- // Use fancy colon.
- sClockView24 = sClockView24.replace(':', '\uee01');
- sClockView12 = sClockView12.replace(':', '\uee01');
-
- sCacheKey = key;
- }
- }
-
private int getCurrentLayoutDirection() {
return TextUtils.getLayoutDirectionFromLocale(Locale.getDefault());
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardMessageArea.java b/packages/SystemUI/src/com/android/keyguard/KeyguardMessageArea.java
index 568bea0e2d24..62411dbff5fd 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardMessageArea.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardMessageArea.java
@@ -54,6 +54,7 @@ public class KeyguardMessageArea extends TextView implements SecurityMessageDisp
private CharSequence mMessage;
private ColorStateList mNextMessageColorState = ColorStateList.valueOf(DEFAULT_COLOR);
private boolean mBouncerVisible;
+ private boolean mAltBouncerShowing;
public KeyguardMessageArea(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -144,7 +145,8 @@ public class KeyguardMessageArea extends TextView implements SecurityMessageDisp
void update() {
CharSequence status = mMessage;
- setVisibility(TextUtils.isEmpty(status) || !mBouncerVisible ? INVISIBLE : VISIBLE);
+ setVisibility(TextUtils.isEmpty(status) || (!mBouncerVisible && !mAltBouncerShowing)
+ ? INVISIBLE : VISIBLE);
setText(status);
ColorStateList colorState = mDefaultColorState;
if (mNextMessageColorState.getDefaultColor() != DEFAULT_COLOR) {
@@ -159,6 +161,16 @@ public class KeyguardMessageArea extends TextView implements SecurityMessageDisp
}
/**
+ * Set whether the alt bouncer is showing
+ */
+ void setAltBouncerShowing(boolean showing) {
+ if (mAltBouncerShowing != showing) {
+ mAltBouncerShowing = showing;
+ update();
+ }
+ }
+
+ /**
* Runnable used to delay accessibility announcements.
*/
private static class AnnounceRunnable implements Runnable {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java
index 6e40f025da50..51ded3fcafdf 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java
@@ -28,7 +28,7 @@ import javax.inject.Inject;
public class KeyguardMessageAreaController extends ViewController<KeyguardMessageArea> {
private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
private final ConfigurationController mConfigurationController;
-
+ private boolean mAltBouncerShowing;
private KeyguardUpdateMonitorCallback mInfoCallback = new KeyguardUpdateMonitorCallback() {
public void onFinishedGoingToSleep(int why) {
@@ -81,6 +81,13 @@ public class KeyguardMessageAreaController extends ViewController<KeyguardMessag
mKeyguardUpdateMonitor.removeCallback(mInfoCallback);
}
+ /**
+ * Set whether alt bouncer is showing
+ */
+ public void setAltBouncerShowing(boolean showing) {
+ mView.setAltBouncerShowing(showing);
+ }
+
public void setMessage(CharSequence s) {
mView.setMessage(s);
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java
index 2325b554d507..8fc4240e1054 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java
@@ -17,16 +17,20 @@
package com.android.keyguard;
import android.content.Context;
+import android.content.res.Configuration;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.AnimationUtils;
+import android.widget.LinearLayout;
import com.android.internal.jank.InteractionJankMonitor;
import com.android.settingslib.animation.AppearAnimationUtils;
import com.android.settingslib.animation.DisappearAnimationUtils;
import com.android.systemui.R;
+import java.util.List;
+
/**
* Displays a PIN pad for unlocking.
*/
@@ -64,6 +68,11 @@ public class KeyguardPINView extends KeyguardPinBasedInputView {
}
@Override
+ protected void onConfigurationChanged(Configuration newConfig) {
+ updateMargins();
+ }
+
+ @Override
protected void resetState() {
}
@@ -72,6 +81,33 @@ public class KeyguardPINView extends KeyguardPinBasedInputView {
return R.id.pinEntry;
}
+ private void updateMargins() {
+ int bottomMargin = mContext.getResources().getDimensionPixelSize(
+ R.dimen.num_pad_row_margin_bottom);
+
+ for (ViewGroup vg : List.of(mRow1, mRow2, mRow3)) {
+ ((LinearLayout.LayoutParams) vg.getLayoutParams()).setMargins(0, 0, 0, bottomMargin);
+ }
+
+ bottomMargin = mContext.getResources().getDimensionPixelSize(
+ R.dimen.num_pad_entry_row_margin_bottom);
+ ((LinearLayout.LayoutParams) mRow0.getLayoutParams()).setMargins(0, 0, 0, bottomMargin);
+
+ if (mEcaView != null) {
+ int ecaTopMargin = mContext.getResources().getDimensionPixelSize(
+ R.dimen.keyguard_eca_top_margin);
+ int ecaBottomMargin = mContext.getResources().getDimensionPixelSize(
+ R.dimen.keyguard_eca_bottom_margin);
+ ((LinearLayout.LayoutParams) mEcaView.getLayoutParams()).setMargins(0, ecaTopMargin,
+ 0, ecaBottomMargin);
+ }
+
+ View entryView = findViewById(R.id.pinEntry);
+ ViewGroup.LayoutParams lp = entryView.getLayoutParams();
+ lp.height = mContext.getResources().getDimensionPixelSize(R.dimen.keyguard_password_height);
+ entryView.setLayoutParams(lp);
+ }
+
@Override
protected void onFinishInflate() {
super.onFinishInflate();
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index 4827cab3b5c0..fde8213de0c6 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -173,7 +173,7 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
@Override
public void onSwipeUp() {
if (!mUpdateMonitor.isFaceDetectionRunning()) {
- mUpdateMonitor.requestFaceAuth();
+ mUpdateMonitor.requestFaceAuth(true);
mKeyguardSecurityCallback.userActivity();
showMessage(null, null);
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 7837f61dc289..0503b2e80d86 100755
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -71,6 +71,7 @@ import android.os.ServiceManager;
import android.os.Trace;
import android.os.UserHandle;
import android.os.UserManager;
+import android.os.Vibrator;
import android.provider.Settings;
import android.service.dreams.DreamService;
import android.service.dreams.IDreamManager;
@@ -85,6 +86,7 @@ import android.util.Log;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
+import androidx.annotation.Nullable;
import androidx.lifecycle.Observer;
import com.android.internal.annotations.VisibleForTesting;
@@ -95,6 +97,7 @@ import com.android.systemui.DejankUtils;
import com.android.systemui.Dumpable;
import com.android.systemui.R;
import com.android.systemui.biometrics.AuthController;
+import com.android.systemui.biometrics.UdfpsController;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Background;
@@ -283,6 +286,9 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
@VisibleForTesting
protected boolean mTelephonyCapable;
+ private final boolean mAcquiredHapticEnabled;
+ @Nullable private final Vibrator mVibrator;
+
// Device provisioning state
private boolean mDeviceProvisioned;
@@ -310,6 +316,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
private RingerModeTracker mRingerModeTracker;
private int mFingerprintRunningState = BIOMETRIC_STATE_STOPPED;
private int mFaceRunningState = BIOMETRIC_STATE_STOPPED;
+ private boolean mIsFaceAuthUserRequested;
private LockPatternUtils mLockPatternUtils;
private final IDreamManager mDreamManager;
private boolean mIsDreaming;
@@ -779,8 +786,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
}
if (msgId == FingerprintManager.FINGERPRINT_ERROR_LOCKOUT_PERMANENT) {
- mLockPatternUtils.requireStrongAuth(STRONG_AUTH_REQUIRED_AFTER_LOCKOUT,
- getCurrentUser());
+ mFingerprintLockedOutPermanent = true;
+ requireStrongAuthIfAllLockedOut();
}
if (msgId == FingerprintManager.FINGERPRINT_ERROR_LOCKOUT
@@ -801,6 +808,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
private void handleFingerprintLockoutReset() {
mFingerprintLockedOut = false;
+ mFingerprintLockedOutPermanent = false;
updateFingerprintListeningState();
}
@@ -961,8 +969,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
}
if (msgId == FaceManager.FACE_ERROR_LOCKOUT_PERMANENT) {
- mLockPatternUtils.requireStrongAuth(STRONG_AUTH_REQUIRED_AFTER_LOCKOUT,
- getCurrentUser());
+ mFaceLockedOutPermanent = true;
+ requireStrongAuthIfAllLockedOut();
}
for (int i = 0; i < mCallbacks.size(); i++) {
@@ -975,6 +983,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
}
private void handleFaceLockoutReset() {
+ mFaceLockedOutPermanent = false;
updateFaceListeningState();
}
@@ -1050,6 +1059,18 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
|| isSimPinSecure());
}
+ private void requireStrongAuthIfAllLockedOut() {
+ final boolean faceLock =
+ mFaceLockedOutPermanent || !shouldListenForFace();
+ final boolean fpLock =
+ mFingerprintLockedOutPermanent || !shouldListenForFingerprint(isUdfpsEnrolled());
+
+ if (faceLock && fpLock) {
+ Log.d(TAG, "All biometrics locked out - requiring strong auth");
+ mLockPatternUtils.requireStrongAuth(STRONG_AUTH_REQUIRED_AFTER_LOCKOUT,
+ getCurrentUser());
+ }
+ }
public boolean getUserCanSkipBouncer(int userId) {
return getUserHasTrust(userId) || getUserUnlockedWithBiometric(userId);
@@ -1333,46 +1354,74 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
handleFingerprintAuthenticated(userId, isStrongBiometric);
};
- private final FingerprintManager.AuthenticationCallback mFingerprintAuthenticationCallback
+ @VisibleForTesting
+ final FingerprintManager.AuthenticationCallback mFingerprintAuthenticationCallback
= new AuthenticationCallback() {
+ private boolean mPlayedAcquiredHaptic;
- @Override
- public void onAuthenticationFailed() {
- handleFingerprintAuthFailed();
- }
+ @Override
+ public void onAuthenticationFailed() {
+ handleFingerprintAuthFailed();
+ }
- @Override
- public void onAuthenticationSucceeded(AuthenticationResult result) {
- Trace.beginSection("KeyguardUpdateMonitor#onAuthenticationSucceeded");
- handleFingerprintAuthenticated(result.getUserId(), result.isStrongBiometric());
- Trace.endSection();
- }
+ @Override
+ public void onAuthenticationSucceeded(AuthenticationResult result) {
+ Trace.beginSection("KeyguardUpdateMonitor#onAuthenticationSucceeded");
+ handleFingerprintAuthenticated(result.getUserId(), result.isStrongBiometric());
+ Trace.endSection();
+
+ // on auth success, we sometimes never received an acquired haptic
+ if (!mPlayedAcquiredHaptic && isUdfpsEnrolled()) {
+ playAcquiredHaptic();
+ }
+ }
- @Override
- public void onAuthenticationHelp(int helpMsgId, CharSequence helpString) {
- handleFingerprintHelp(helpMsgId, helpString.toString());
- }
+ @Override
+ public void onAuthenticationHelp(int helpMsgId, CharSequence helpString) {
+ handleFingerprintHelp(helpMsgId, helpString.toString());
+ }
- @Override
- public void onAuthenticationError(int errMsgId, CharSequence errString) {
- handleFingerprintError(errMsgId, errString.toString());
- }
+ @Override
+ public void onAuthenticationError(int errMsgId, CharSequence errString) {
+ handleFingerprintError(errMsgId, errString.toString());
+ }
- @Override
- public void onAuthenticationAcquired(int acquireInfo) {
- handleFingerprintAcquired(acquireInfo);
- }
+ @Override
+ public void onAuthenticationAcquired(int acquireInfo) {
+ handleFingerprintAcquired(acquireInfo);
+ if (acquireInfo == FingerprintManager.FINGERPRINT_ACQUIRED_GOOD
+ && isUdfpsEnrolled()) {
+ mPlayedAcquiredHaptic = true;
+ playAcquiredHaptic();
+ }
+ }
- @Override
- public void onUdfpsPointerDown(int sensorId) {
- Log.d(TAG, "onUdfpsPointerDown, sensorId: " + sensorId);
- }
+ @Override
+ public void onUdfpsPointerDown(int sensorId) {
+ Log.d(TAG, "onUdfpsPointerDown, sensorId: " + sensorId);
+ mPlayedAcquiredHaptic = false;
+ }
- @Override
- public void onUdfpsPointerUp(int sensorId) {
- Log.d(TAG, "onUdfpsPointerUp, sensorId: " + sensorId);
+ @Override
+ public void onUdfpsPointerUp(int sensorId) {
+ Log.d(TAG, "onUdfpsPointerUp, sensorId: " + sensorId);
+ }
+ };
+
+ /**
+ * Play haptic to signal udfps fingeprrint acquired.
+ */
+ @VisibleForTesting
+ public void playAcquiredHaptic() {
+ if (mAcquiredHapticEnabled && mVibrator != null) {
+ String effect = Settings.Global.getString(
+ mContext.getContentResolver(),
+ "udfps_acquired_type");
+ mVibrator.vibrate(UdfpsController.getVibration(effect,
+ UdfpsController.EFFECT_TICK),
+ UdfpsController.VIBRATION_SONIFICATION_ATTRIBUTES);
}
- };
+ }
private final FaceManager.FaceDetectionCallback mFaceDetectionCallback
= (sensorId, userId, isStrongBiometric) -> {
@@ -1381,7 +1430,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
};
@VisibleForTesting
- FaceManager.AuthenticationCallback mFaceAuthenticationCallback
+ final FaceManager.AuthenticationCallback mFaceAuthenticationCallback
= new FaceManager.AuthenticationCallback() {
@Override
@@ -1418,6 +1467,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
private FaceManager mFaceManager;
private List<FaceSensorPropertiesInternal> mFaceSensorProperties;
private boolean mFingerprintLockedOut;
+ private boolean mFingerprintLockedOutPermanent;
+ private boolean mFaceLockedOutPermanent;
private TelephonyManager mTelephonyManager;
/**
@@ -1665,7 +1716,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
LockPatternUtils lockPatternUtils,
AuthController authController,
TelephonyListenerManager telephonyListenerManager,
- FeatureFlags featureFlags) {
+ FeatureFlags featureFlags,
+ @Nullable Vibrator vibrator) {
mContext = context;
mSubscriptionManager = SubscriptionManager.from(context);
mTelephonyListenerManager = telephonyListenerManager;
@@ -1680,6 +1732,9 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
mLockPatternUtils = lockPatternUtils;
mAuthController = authController;
dumpManager.registerDumpable(getClass().getName(), this);
+ mAcquiredHapticEnabled = Settings.Global.getInt(mContext.getContentResolver(),
+ "udfps_acquired", 0) == 1;
+ mVibrator = vibrator;
mHandler = new Handler(mainLooper) {
@Override
@@ -2059,12 +2114,18 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
/**
* Requests face authentication if we're on a state where it's allowed.
* This will re-trigger auth in case it fails.
+ * @param userInitiatedRequest true if the user explicitly requested face auth
*/
- public void requestFaceAuth() {
- if (DEBUG) Log.d(TAG, "requestFaceAuth()");
+ public void requestFaceAuth(boolean userInitiatedRequest) {
+ if (DEBUG) Log.d(TAG, "requestFaceAuth() userInitiated=" + userInitiatedRequest);
+ mIsFaceAuthUserRequested |= userInitiatedRequest;
updateFaceListeningState();
}
+ public boolean isFaceAuthUserRequested() {
+ return mIsFaceAuthUserRequested;
+ }
+
/**
* In case face auth is running, cancel it.
*/
@@ -2081,6 +2142,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
mHandler.removeCallbacks(mRetryFaceAuthentication);
boolean shouldListenForFace = shouldListenForFace();
if (mFaceRunningState == BIOMETRIC_STATE_RUNNING && !shouldListenForFace) {
+ mIsFaceAuthUserRequested = false;
stopListeningForFace();
} else if (mFaceRunningState != BIOMETRIC_STATE_RUNNING && shouldListenForFace) {
startListeningForFace();
diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconView.java b/packages/SystemUI/src/com/android/keyguard/LockIconView.java
index 423bd5626da9..c1d448db1e63 100644
--- a/packages/SystemUI/src/com/android/keyguard/LockIconView.java
+++ b/packages/SystemUI/src/com/android/keyguard/LockIconView.java
@@ -16,7 +16,6 @@
package com.android.keyguard;
-import android.annotation.NonNull;
import android.content.Context;
import android.graphics.PointF;
import android.graphics.RectF;
@@ -24,10 +23,17 @@ import android.util.AttributeSet;
import android.widget.FrameLayout;
import android.widget.ImageView;
+import androidx.annotation.NonNull;
+
+import com.android.systemui.Dumpable;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
/**
* A view positioned under the notification shade.
*/
-public class LockIconView extends ImageView {
+public class LockIconView extends ImageView implements Dumpable {
@NonNull private final RectF mSensorRect;
@NonNull private PointF mLockIconCenter = new PointF(0f, 0f);
private int mRadius;
@@ -37,7 +43,7 @@ public class LockIconView extends ImageView {
mSensorRect = new RectF();
}
- void setLocation(@NonNull PointF center, int radius) {
+ void setCenterLocation(@NonNull PointF center, int radius) {
mLockIconCenter = center;
mRadius = radius;
@@ -63,4 +69,11 @@ public class LockIconView extends ImageView {
float getLocationTop() {
return mLockIconCenter.y - mRadius;
}
+
+
+ @Override
+ public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) {
+ pw.println("Center in px (x, y)= (" + mLockIconCenter.x + ", " + mLockIconCenter.y + ")");
+ pw.println("Radius in pixels: " + mRadius);
+ }
}
diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
index ccc487925fe4..62cb4b9a33f5 100644
--- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
@@ -18,17 +18,21 @@ package com.android.keyguard;
import static android.hardware.biometrics.BiometricSourceType.FINGERPRINT;
-import static com.android.systemui.classifier.Classifier.DISABLED_UDFPS_AFFORDANCE;
+import static com.android.systemui.classifier.Classifier.LOCK_ICON;
import android.content.Context;
+import android.content.res.Configuration;
import android.graphics.PointF;
+import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.InsetDrawable;
import android.hardware.biometrics.BiometricSourceType;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
-import android.util.Log;
+import android.util.DisplayMetrics;
+import android.view.GestureDetector;
+import android.view.GestureDetector.SimpleOnGestureListener;
+import android.view.MotionEvent;
import android.view.View;
-import android.view.ViewGroup;
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.AccessibilityNodeInfo;
@@ -73,15 +77,10 @@ public class LockIconViewController extends ViewController<LockIconView> impleme
@NonNull private final AccessibilityManager mAccessibilityManager;
@NonNull private final ConfigurationController mConfigurationController;
@NonNull private final DelayableExecutor mExecutor;
-
- private boolean mHasUdfpsOrFaceAuthFeatures;
private boolean mUdfpsEnrolled;
- private boolean mFaceAuthEnrolled;
- @NonNull private final Drawable mButton;
@NonNull private final Drawable mUnlockIcon;
@NonNull private final Drawable mLockIcon;
- @NonNull private final CharSequence mDisabledLabel;
@NonNull private final CharSequence mUnlockedLabel;
@NonNull private final CharSequence mLockedLabel;
@@ -95,10 +94,18 @@ public class LockIconViewController extends ViewController<LockIconView> impleme
private boolean mUserUnlockedWithBiometric;
private Runnable mCancelDelayedUpdateVisibilityRunnable;
- private boolean mShowButton;
+ private boolean mHasUdfps;
+ private float mHeightPixels;
+ private float mWidthPixels;
+ private float mDensity;
+ private int mKgBottomAreaHeight;
+
private boolean mShowUnlockIcon;
private boolean mShowLockIcon;
+ private boolean mDownDetected;
+ private final Rect mSensorTouchLocation = new Rect();
+
@Inject
public LockIconViewController(
@Nullable LockIconView view,
@@ -125,8 +132,6 @@ public class LockIconViewController extends ViewController<LockIconView> impleme
mExecutor = executor;
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(
@@ -135,8 +140,6 @@ public class LockIconViewController extends ViewController<LockIconView> impleme
com.android.internal.R.drawable.ic_lock, context.getTheme()),
context.getResources().getDimensionPixelSize(
com.android.systemui.R.dimen.udfps_unlock_icon_inset));
- mDisabledLabel = context.getResources().getString(
- R.string.accessibility_udfps_disabled_button);
mUnlockedLabel = context.getResources().getString(R.string.accessibility_unlock_button);
mLockedLabel = context.getResources().getString(R.string.accessibility_lock_icon);
dumpManager.registerDumpable("LockIconViewController", this);
@@ -149,37 +152,11 @@ public class LockIconViewController extends ViewController<LockIconView> impleme
@Override
protected void onViewAttached() {
- // we check this here instead of onInit since the FingeprintManager + FaceManager may not
+ // we check this here instead of onInit since the FingerprintManager + FaceManager may not
// have started up yet onInit
- final boolean hasFaceAuth = mAuthController.getFaceAuthSensorLocation() != null;
- final boolean hasUdfps = mAuthController.getUdfpsSensorLocation() != null;
- mHasUdfpsOrFaceAuthFeatures = hasFaceAuth || hasUdfps;
- if (!mHasUdfpsOrFaceAuthFeatures) {
- // Posting since removing a view in the middle of onAttach can lead to a crash in the
- // iteration loop when the view isn't last
- mView.setVisibility(View.GONE);
- mView.post(() -> {
- mView.setVisibility(View.VISIBLE);
- ((ViewGroup) mView.getParent()).removeView(mView);
- });
- return;
- }
-
- if (hasUdfps) {
- FingerprintSensorPropertiesInternal props = mAuthController.getUdfpsProps().get(0);
- mView.setLocation(new PointF(props.sensorLocationX, props.sensorLocationY),
- props.sensorRadius);
- } else {
- int[] props = mView.getContext().getResources().getIntArray(
- com.android.systemui.R.array.config_lock_icon_props);
- if (props == null || props.length < 3) {
- Log.e("LockIconViewController", "lock icon position should be "
- + "setup in config under config_lock_icon_props");
- props = new int[]{0, 0, 0};
- }
- mView.setLocation(new PointF(props[0], props[1]), props[2]);
- }
+ mHasUdfps = mAuthController.getUdfpsSensorLocation() != null;
+ updateConfiguration();
updateKeyguardShowing();
mUserUnlockedWithBiometric = false;
mIsBouncerShowing = mKeyguardViewController.isBouncerShowing();
@@ -194,9 +171,7 @@ public class LockIconViewController extends ViewController<LockIconView> impleme
mKeyguardUpdateMonitor.registerCallback(mKeyguardUpdateMonitorCallback);
mStatusBarStateController.addCallback(mStatusBarStateListener);
mKeyguardStateController.addCallback(mKeyguardStateCallback);
- mAccessibilityManager.addTouchExplorationStateChangeListener(
- mTouchExplorationStateChangeListener);
-
+ mDownDetected = false;
updateVisibility();
}
@@ -206,8 +181,6 @@ public class LockIconViewController extends ViewController<LockIconView> impleme
mKeyguardUpdateMonitor.removeCallback(mKeyguardUpdateMonitorCallback);
mStatusBarStateController.removeCallback(mStatusBarStateListener);
mKeyguardStateController.removeCallback(mKeyguardStateCallback);
- mAccessibilityManager.removeTouchExplorationStateChangeListener(
- mTouchExplorationStateChangeListener);
if (mCancelDelayedUpdateVisibilityRunnable != null) {
mCancelDelayedUpdateVisibilityRunnable.run();
@@ -219,18 +192,6 @@ public class LockIconViewController extends ViewController<LockIconView> impleme
return mView.getLocationTop();
}
- private boolean onAffordanceClick() {
- if (mFalsingManager.isFalseTouch(DISABLED_UDFPS_AFFORDANCE)) {
- return false;
- }
-
- // pre-emptively set to true to hide view
- mIsBouncerShowing = true;
- updateVisibility();
- mKeyguardViewController.showBouncer(/* scrim */ true);
- return true;
- }
-
/**
* Set whether qs is expanded. When QS is expanded, don't show a DisabledUdfps affordance.
*/
@@ -245,32 +206,24 @@ public class LockIconViewController extends ViewController<LockIconView> impleme
mCancelDelayedUpdateVisibilityRunnable = null;
}
- if (!mIsKeyguardShowing || (!mUdfpsEnrolled && !mFaceAuthEnrolled)) {
+ if (!mIsKeyguardShowing) {
mView.setVisibility(View.INVISIBLE);
return;
}
- // these three states are mutually exclusive:
- mShowButton = mUdfpsEnrolled && !mCanDismissLockScreen && !mRunningFPS
- && !mUserUnlockedWithBiometric && isLockScreen();
- mShowUnlockIcon = mFaceAuthEnrolled & mCanDismissLockScreen && isLockScreen();
- mShowLockIcon = !mUdfpsEnrolled && !mCanDismissLockScreen && isLockScreen()
- && mFaceAuthEnrolled;
+ mShowLockIcon = !mCanDismissLockScreen && !mUserUnlockedWithBiometric && isLockScreen()
+ && (!mUdfpsEnrolled || !mRunningFPS);
+ mShowUnlockIcon = mCanDismissLockScreen && isLockScreen();
- updateClickListener();
final CharSequence prevContentDescription = mView.getContentDescription();
- if (mShowButton) {
- mView.setImageDrawable(mButton);
+ if (mShowLockIcon) {
+ mView.setImageDrawable(mLockIcon);
mView.setVisibility(View.VISIBLE);
- mView.setContentDescription(mDisabledLabel);
+ mView.setContentDescription(mLockedLabel);
} else if (mShowUnlockIcon) {
mView.setImageDrawable(mUnlockIcon);
mView.setVisibility(View.VISIBLE);
mView.setContentDescription(mUnlockedLabel);
- } else if (mShowLockIcon) {
- mView.setImageDrawable(mLockIcon);
- mView.setVisibility(View.VISIBLE);
- mView.setContentDescription(mLockedLabel);
} else {
mView.setVisibility(View.INVISIBLE);
mView.setContentDescription(null);
@@ -293,10 +246,12 @@ public class LockIconViewController extends ViewController<LockIconView> impleme
getResources().getString(R.string.accessibility_enter_hint));
public void onInitializeAccessibilityNodeInfo(View v, AccessibilityNodeInfo info) {
super.onInitializeAccessibilityNodeInfo(v, info);
- if (mShowButton || mShowLockIcon) {
- info.addAction(mAccessibilityAuthenticateHint);
- } else if (mShowUnlockIcon) {
- info.addAction(mAccessibilityEnterHint);
+ if (isClickable()) {
+ if (mShowLockIcon) {
+ info.addAction(mAccessibilityAuthenticateHint);
+ } else if (mShowUnlockIcon) {
+ info.addAction(mAccessibilityEnterHint);
+ }
}
}
};
@@ -308,18 +263,6 @@ public class LockIconViewController extends ViewController<LockIconView> impleme
&& mStatusBarState == StatusBarState.KEYGUARD;
}
- private void updateClickListener() {
- if (mView != null) {
- mView.setOnClickListener(v -> onAffordanceClick());
- if (mAccessibilityManager.isTouchExplorationEnabled()) {
- mView.setOnLongClickListener(null);
- mView.setLongClickable(false);
- } else {
- mView.setOnLongClickListener(v -> onAffordanceClick());
- }
- }
- }
-
private void updateKeyguardShowing() {
mIsKeyguardShowing = mKeyguardStateController.isShowing()
&& !mKeyguardStateController.isKeyguardGoingAway();
@@ -332,13 +275,39 @@ public class LockIconViewController extends ViewController<LockIconView> impleme
mLockIcon.setTint(color);
}
+ private void updateConfiguration() {
+ final DisplayMetrics metrics = mView.getContext().getResources().getDisplayMetrics();
+ mWidthPixels = metrics.widthPixels;
+ mHeightPixels = metrics.heightPixels;
+ mDensity = metrics.density;
+ mKgBottomAreaHeight = mView.getContext().getResources().getDimensionPixelSize(
+ R.dimen.keyguard_indication_margin_bottom)
+ + mView.getContext().getResources().getDimensionPixelSize(
+ R.dimen.keyguard_indication_bottom_padding);
+ updateLockIconLocation();
+ }
+
+ private void updateLockIconLocation() {
+ if (mHasUdfps) {
+ FingerprintSensorPropertiesInternal props = mAuthController.getUdfpsProps().get(0);
+ mView.setCenterLocation(new PointF(props.sensorLocationX, props.sensorLocationY),
+ props.sensorRadius);
+ } else {
+ final float distAboveKgBottomArea = 12 * mDensity;
+ final float radius = 36 * mDensity;
+ mView.setCenterLocation(
+ new PointF(mWidthPixels / 2,
+ mHeightPixels - mKgBottomAreaHeight - distAboveKgBottomArea
+ - radius / 2), (int) radius);
+ }
+
+ mView.getHitRect(mSensorTouchLocation);
+ }
+
@Override
public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) {
- pw.println("mHasUdfpsOrFaceAuthFeatures: " + mHasUdfpsOrFaceAuthFeatures);
pw.println("mUdfpsEnrolled: " + mUdfpsEnrolled);
- pw.println("mFaceAuthEnrolled: " + mFaceAuthEnrolled);
pw.println("mIsKeyguardShowing: " + mIsKeyguardShowing);
- pw.println(" mShowBouncerButton: " + mShowButton);
pw.println(" mShowUnlockIcon: " + mShowUnlockIcon);
pw.println(" mShowLockIcon: " + mShowLockIcon);
pw.println(" mIsDozing: " + mIsDozing);
@@ -348,6 +317,10 @@ public class LockIconViewController extends ViewController<LockIconView> impleme
pw.println(" mCanDismissLockScreen: " + mCanDismissLockScreen);
pw.println(" mStatusBarState: " + StatusBarState.toShortString(mStatusBarState));
pw.println(" mQsExpanded: " + mQsExpanded);
+
+ if (mView != null) {
+ mView.dump(fd, pw, args);
+ }
}
private StatusBarStateController.StateListener mStatusBarStateListener =
@@ -420,7 +393,6 @@ public class LockIconViewController extends ViewController<LockIconView> impleme
public void onKeyguardShowingChanged() {
updateKeyguardShowing();
mUdfpsEnrolled = mKeyguardUpdateMonitor.isUdfpsEnrolled();
- mFaceAuthEnrolled = mKeyguardUpdateMonitor.isFaceEnrolled();
updateVisibility();
}
@@ -447,10 +419,87 @@ public class LockIconViewController extends ViewController<LockIconView> impleme
public void onOverlayChanged() {
updateColors();
}
+
+ @Override
+ public void onConfigChanged(Configuration newConfig) {
+ updateConfiguration();
+ }
};
- private final AccessibilityManager.TouchExplorationStateChangeListener
- mTouchExplorationStateChangeListener = enabled -> updateClickListener();
+ private final GestureDetector mGestureDetector =
+ new GestureDetector(new SimpleOnGestureListener() {
+ public boolean onDown(MotionEvent e) {
+ if (!isClickable()) {
+ mDownDetected = false;
+ return false;
+ }
+
+ // intercept all following touches until we see MotionEvent.ACTION_CANCEL UP or
+ // MotionEvent.ACTION_UP (see #onTouchEvent)
+ mDownDetected = true;
+ return true;
+ }
+
+ public void onLongPress(MotionEvent e) {
+ if (!wasClickableOnDownEvent()) {
+ return;
+ }
+
+ onAffordanceClick();
+ }
+
+ public boolean onSingleTapUp(MotionEvent e) {
+ if (!wasClickableOnDownEvent()) {
+ return false;
+ }
+
+ onAffordanceClick();
+ return true;
+ }
+
+ private boolean wasClickableOnDownEvent() {
+ return mDownDetected;
+ }
+
+ private void onAffordanceClick() {
+ if (mFalsingManager.isFalseTouch(LOCK_ICON)) {
+ return;
+ }
+
+ // pre-emptively set to true to hide view
+ mIsBouncerShowing = true;
+ updateVisibility();
+ mKeyguardViewController.showBouncer(/* scrim */ true);
+ }
+ });
+
+ /**
+ * Send touch events to this view and handles it if the touch is within this view and we are
+ * in a 'clickable' state
+ * @return whether to intercept the touch event
+ */
+ public boolean onTouchEvent(MotionEvent event) {
+ if (mSensorTouchLocation.contains((int) event.getX(), (int) event.getY())
+ && mView.getVisibility() == View.VISIBLE) {
+ mGestureDetector.onTouchEvent(event);
+ }
+
+ // we continue to intercept all following touches until we see MotionEvent.ACTION_CANCEL UP
+ // or MotionEvent.ACTION_UP. this is to avoid passing the touch to NPV
+ // after the lock icon disappears on device entry
+ if (mDownDetected) {
+ if (event.getAction() == MotionEvent.ACTION_CANCEL
+ || event.getAction() == MotionEvent.ACTION_UP) {
+ mDownDetected = false;
+ }
+ return true;
+ }
+ return false;
+ }
+
+ private boolean isClickable() {
+ return mUdfpsEnrolled || mShowUnlockIcon;
+ }
/**
* Set the alpha of this view.
diff --git a/packages/SystemUI/src/com/android/keyguard/NumPadButton.java b/packages/SystemUI/src/com/android/keyguard/NumPadButton.java
index 0d31906d2f80..099e6f4b5341 100644
--- a/packages/SystemUI/src/com/android/keyguard/NumPadButton.java
+++ b/packages/SystemUI/src/com/android/keyguard/NumPadButton.java
@@ -17,6 +17,7 @@ package com.android.keyguard;
import android.content.Context;
import android.content.res.ColorStateList;
+import android.content.res.Configuration;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.RippleDrawable;
import android.graphics.drawable.VectorDrawable;
@@ -26,7 +27,6 @@ import android.view.MotionEvent;
import androidx.annotation.Nullable;
import com.android.settingslib.Utils;
-import com.android.systemui.R;
/**
* Similar to the {@link NumPadKey}, but displays an image.
@@ -35,6 +35,7 @@ public class NumPadButton extends AlphaOptimizedImageButton {
@Nullable
private NumPadAnimator mAnimator;
+ private int mOrientation;
public NumPadButton(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -49,13 +50,21 @@ public class NumPadButton extends AlphaOptimizedImageButton {
}
@Override
+ protected void onConfigurationChanged(Configuration newConfig) {
+ mOrientation = newConfig.orientation;
+ }
+
+ @Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
// Set width/height to the same value to ensure a smooth circle for the bg, but shrink
// the height to match the old pin bouncer
int width = getMeasuredWidth();
- int height = mAnimator == null ? (int) (width * .75f) : width;
+
+ boolean shortenHeight = mAnimator == null
+ || mOrientation == Configuration.ORIENTATION_LANDSCAPE;
+ int height = shortenHeight ? (int) (width * .66f) : width;
setMeasuredDimension(getMeasuredWidth(), height);
}
diff --git a/packages/SystemUI/src/com/android/keyguard/NumPadKey.java b/packages/SystemUI/src/com/android/keyguard/NumPadKey.java
index cffa630c6ec8..232c6fc99068 100644
--- a/packages/SystemUI/src/com/android/keyguard/NumPadKey.java
+++ b/packages/SystemUI/src/com/android/keyguard/NumPadKey.java
@@ -13,10 +13,10 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
package com.android.keyguard;
import android.content.Context;
+import android.content.res.Configuration;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.RippleDrawable;
@@ -52,6 +52,7 @@ public class NumPadKey extends ViewGroup {
@Nullable
private NumPadAnimator mAnimator;
+ private int mOrientation;
private View.OnClickListener mListener = new View.OnClickListener() {
@Override
@@ -139,6 +140,11 @@ public class NumPadKey extends ViewGroup {
}
}
+ @Override
+ protected void onConfigurationChanged(Configuration newConfig) {
+ mOrientation = newConfig.orientation;
+ }
+
/**
* Reload colors from resources.
**/
@@ -171,7 +177,10 @@ public class NumPadKey extends ViewGroup {
// Set width/height to the same value to ensure a smooth circle for the bg, but shrink
// the height to match the old pin bouncer
int width = getMeasuredWidth();
- int height = mAnimator == null ? (int) (width * .75f) : width;
+
+ boolean shortenHeight = mAnimator == null
+ || mOrientation == Configuration.ORIENTATION_LANDSCAPE;
+ int height = shortenHeight ? (int) (width * .66f) : width;
setMeasuredDimension(getMeasuredWidth(), height);
}
diff --git a/packages/SystemUI/src/com/android/keyguard/PasswordTextView.java b/packages/SystemUI/src/com/android/keyguard/PasswordTextView.java
index b80f8bd64dcf..b1597144d237 100644
--- a/packages/SystemUI/src/com/android/keyguard/PasswordTextView.java
+++ b/packages/SystemUI/src/com/android/keyguard/PasswordTextView.java
@@ -21,6 +21,7 @@ import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ValueAnimator;
import android.content.Context;
+import android.content.res.Configuration;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
@@ -87,7 +88,7 @@ public class PasswordTextView extends View {
/**
* The raw text size, will be multiplied by the scaled density when drawn
*/
- private final int mTextHeightRaw;
+ private int mTextHeightRaw;
private final int mGravity;
private ArrayList<CharState> mTextChars = new ArrayList<>();
private String mText = "";
@@ -147,6 +148,7 @@ public class PasswordTextView extends View {
} finally {
a.recycle();
}
+
mDrawPaint.setFlags(Paint.SUBPIXEL_TEXT_FLAG | Paint.ANTI_ALIAS_FLAG);
mDrawPaint.setTextAlign(Paint.Align.CENTER);
mDrawPaint.setTypeface(Typeface.create(
@@ -164,6 +166,12 @@ public class PasswordTextView extends View {
}
@Override
+ protected void onConfigurationChanged(Configuration newConfig) {
+ mTextHeightRaw = getContext().getResources().getInteger(
+ R.integer.scaled_password_text_size);
+ }
+
+ @Override
protected void onDraw(Canvas canvas) {
float totalDrawingWidth = getDrawingWidth();
float currentDrawPosition;
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index 67cf4812ba6b..104d711f46fb 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -73,6 +73,7 @@ import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.DevicePolicyManagerWrapper;
import com.android.systemui.shared.system.PackageManagerWrapper;
import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.FeatureFlags;
import com.android.systemui.statusbar.NotificationListener;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationMediaManager;
@@ -360,6 +361,7 @@ public class Dependency {
@Inject Lazy<PrivacyDotViewController> mPrivacyDotViewControllerLazy;
@Inject Lazy<EdgeBackGestureHandler> mEdgeBackGestureHandler;
@Inject Lazy<UiEventLogger> mUiEventLogger;
+ @Inject Lazy<FeatureFlags> mFeatureFlagsLazy;
@Inject
public Dependency() {
@@ -574,6 +576,7 @@ public class Dependency {
mProviders.put(PrivacyDotViewController.class, mPrivacyDotViewControllerLazy::get);
mProviders.put(EdgeBackGestureHandler.class, mEdgeBackGestureHandler::get);
mProviders.put(UiEventLogger.class, mUiEventLogger::get);
+ mProviders.put(FeatureFlags.class, mFeatureFlagsLazy::get);
Dependency.setInstance(this);
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuController.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuController.java
index 7cd43eff8e2a..cff6cf1f53f1 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuController.java
@@ -19,6 +19,7 @@ package com.android.systemui.accessibility.floatingmenu;
import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU;
import android.content.Context;
+import android.os.UserHandle;
import android.text.TextUtils;
import androidx.annotation.MainThread;
@@ -40,11 +41,11 @@ public class AccessibilityFloatingMenuController implements
AccessibilityButtonModeObserver.ModeChangedListener,
AccessibilityButtonTargetsObserver.TargetsChangedListener {
- private final Context mContext;
private final AccessibilityButtonModeObserver mAccessibilityButtonModeObserver;
private final AccessibilityButtonTargetsObserver mAccessibilityButtonTargetsObserver;
private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+ private Context mContext;
@VisibleForTesting
IAccessibilityFloatingMenu mFloatingMenu;
private int mBtnMode;
@@ -79,6 +80,7 @@ public class AccessibilityFloatingMenuController implements
@Override
public void onUserSwitchComplete(int userId) {
+ mContext = mContext.createContextAsUser(UserHandle.of(userId), /* flags= */ 0);
mBtnMode = mAccessibilityButtonModeObserver.getCurrentAccessibilityButtonMode();
mBtnTargets =
mAccessibilityButtonTargetsObserver.getCurrentAccessibilityButtonTargets();
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 5bb55222e09b..17818cd9c7ee 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuView.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuView.java
@@ -16,6 +16,7 @@
package com.android.systemui.accessibility.floatingmenu;
+import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
import static android.util.MathUtils.constrain;
import static android.util.MathUtils.sq;
import static android.view.WindowInsets.Type.ime;
@@ -38,7 +39,6 @@ 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;
@@ -49,8 +49,6 @@ import android.view.ViewGroup;
import android.view.WindowInsets;
import android.view.WindowManager;
import android.view.WindowMetrics;
-import android.view.accessibility.AccessibilityNodeInfo;
-import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
import android.view.animation.Animation;
import android.view.animation.OvershootInterpolator;
import android.view.animation.TranslateAnimation;
@@ -58,8 +56,10 @@ import android.widget.FrameLayout;
import androidx.annotation.DimenRes;
import androidx.annotation.NonNull;
+import androidx.core.view.AccessibilityDelegateCompat;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
+import androidx.recyclerview.widget.RecyclerViewAccessibilityDelegate;
import com.android.internal.accessibility.dialog.AccessibilityTarget;
import com.android.internal.annotations.VisibleForTesting;
@@ -200,6 +200,7 @@ public class AccessibilityFloatingMenuView extends FrameLayout
mListView = listView;
mWindowManager = context.getSystemService(WindowManager.class);
+ mLastConfiguration = new Configuration(getResources().getConfiguration());
mAdapter = new AccessibilityTargetAdapter(mTargets);
mUiHandler = createUiHandler();
mPosition = position;
@@ -243,7 +244,6 @@ public class AccessibilityFloatingMenuView extends FrameLayout
}
});
- mLastConfiguration = new Configuration(getResources().getConfiguration());
initListView();
updateStrokeWith(getResources().getConfiguration().uiMode, mAlignment);
@@ -331,54 +331,6 @@ 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) {
- fadeIn();
-
- final Rect bounds = getAvailableBounds();
- if (action == R.id.action_move_top_left) {
- setShapeType(ShapeType.OVAL);
- snapToLocation(bounds.left, bounds.top);
- return true;
- }
-
- if (action == R.id.action_move_top_right) {
- setShapeType(ShapeType.OVAL);
- snapToLocation(bounds.right, bounds.top);
- return true;
- }
-
- if (action == R.id.action_move_bottom_left) {
- setShapeType(ShapeType.OVAL);
- snapToLocation(bounds.left, bounds.bottom);
- return true;
- }
-
- if (action == R.id.action_move_bottom_right) {
- setShapeType(ShapeType.OVAL);
- snapToLocation(bounds.right, bounds.bottom);
- return true;
- }
-
- if (action == R.id.action_move_to_edge_and_hide) {
- setShapeType(ShapeType.HALF_OVAL);
- return true;
- }
-
- if (action == R.id.action_move_out_edge_and_show) {
- setShapeType(ShapeType.OVAL);
- return true;
- }
-
- return super.performAccessibilityAction(action, arguments);
- }
-
void show() {
if (isShowing()) {
return;
@@ -525,50 +477,15 @@ 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);
-
- final int moveEdgeId = mShapeType == ShapeType.OVAL
- ? R.id.action_move_to_edge_and_hide
- : R.id.action_move_out_edge_and_show;
- final int moveEdgeTextResId = mShapeType == ShapeType.OVAL
- ? R.string.accessibility_floating_button_action_move_to_edge_and_hide_to_half
- : R.string.accessibility_floating_button_action_move_out_edge_and_show;
- final AccessibilityAction moveToOrOutEdge =
- new AccessibilityAction(moveEdgeId, res.getString(moveEdgeTextResId));
- info.addAction(moveToOrOutEdge);
- }
-
private boolean onTouched(MotionEvent event) {
final int action = event.getAction();
final int currentX = (int) event.getX();
final int currentY = (int) event.getY();
+ final int marginStartEnd = getMarginStartEndWith(mLastConfiguration);
final Rect touchDelegateBounds =
- new Rect(mMargin, mMargin, mMargin + getLayoutWidth(), mMargin + getLayoutHeight());
+ new Rect(marginStartEnd, mMargin, marginStartEnd + getLayoutWidth(),
+ mMargin + getLayoutHeight());
if (action == MotionEvent.ACTION_DOWN
&& touchDelegateBounds.contains(currentX, currentY)) {
mIsDownInEnlargedTouchArea = true;
@@ -682,15 +599,22 @@ public class AccessibilityFloatingMenuView extends FrameLayout
mListView.setLayoutManager(layoutManager);
mListView.addOnItemTouchListener(this);
mListView.animate().setInterpolator(new OvershootInterpolator());
- updateListView();
+ mListView.setAccessibilityDelegateCompat(new RecyclerViewAccessibilityDelegate(mListView) {
+ @NonNull
+ @Override
+ public AccessibilityDelegateCompat getItemDelegate() {
+ return new ItemDelegateCompat(this,
+ AccessibilityFloatingMenuView.this);
+ }
+ });
+
+ updateListViewWith(mLastConfiguration);
addView(mListView);
}
- private void updateListView() {
- final LayoutParams layoutParams = (FrameLayout.LayoutParams) mListView.getLayoutParams();
- layoutParams.setMargins(mMargin, mMargin, mMargin, mMargin);
- mListView.setLayoutParams(layoutParams);
+ private void updateListViewWith(Configuration configuration) {
+ updateMarginWith(configuration);
final int elevation =
getResources().getDimensionPixelSize(R.dimen.accessibility_floating_menu_elevation);
@@ -719,13 +643,15 @@ public class AccessibilityFloatingMenuView extends FrameLayout
@Override
protected void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
+ mLastConfiguration.setTo(newConfig);
+
final int diff = newConfig.diff(mLastConfiguration);
if ((diff & ActivityInfo.CONFIG_LOCALE) != 0) {
updateAccessibilityTitle(mCurrentLayoutParams);
}
updateDimensions();
- updateListView();
+ updateListViewWith(newConfig);
updateItemViewWith(mSizeType);
updateColor();
updateStrokeWith(newConfig.uiMode, mAlignment);
@@ -733,8 +659,6 @@ public class AccessibilityFloatingMenuView extends FrameLayout
updateRadiusWith(mSizeType, mRadiusType, mTargets.size());
updateScrollModeWith(hasExceededMaxLayoutHeight());
setSystemGestureExclusion();
-
- mLastConfiguration.setTo(newConfig);
}
@VisibleForTesting
@@ -756,11 +680,11 @@ public class AccessibilityFloatingMenuView extends FrameLayout
}
private int getMinWindowX() {
- return -mMargin;
+ return -getMarginStartEndWith(mLastConfiguration);
}
private int getMaxWindowX() {
- return mScreenWidth - mMargin - getLayoutWidth();
+ return mScreenWidth - getMarginStartEndWith(mLastConfiguration) - getLayoutWidth();
}
private int getMaxWindowY() {
@@ -805,6 +729,15 @@ public class AccessibilityFloatingMenuView extends FrameLayout
return layoutBottomY > imeY ? (layoutBottomY - imeY) : 0;
}
+ private void updateMarginWith(Configuration configuration) {
+ // Avoid overlapping with system bars under landscape mode, update the margins of the menu
+ // to align the edge of system bars.
+ final int marginStartEnd = getMarginStartEndWith(configuration);
+ final LayoutParams layoutParams = (FrameLayout.LayoutParams) mListView.getLayoutParams();
+ layoutParams.setMargins(marginStartEnd, mMargin, marginStartEnd, mMargin);
+ mListView.setLayoutParams(layoutParams);
+ }
+
private void updateOffsetWith(@ShapeType int shapeType, @Alignment int side) {
final float halfWidth = getLayoutWidth() / 2.0f;
final float offset = (shapeType == ShapeType.OVAL) ? 0 : halfWidth;
@@ -896,6 +829,12 @@ public class AccessibilityFloatingMenuView extends FrameLayout
return (mPadding + mIconHeight) * mTargets.size() + mPadding;
}
+ private int getMarginStartEndWith(Configuration configuration) {
+ return configuration != null
+ && configuration.orientation == ORIENTATION_PORTRAIT
+ ? mMargin : 0;
+ }
+
private @DimenRes int getRadiusResId(@SizeType int sizeType, int itemCount) {
return sizeType == SizeType.SMALL
? getSmallSizeResIdWith(itemCount)
@@ -932,7 +871,7 @@ public class AccessibilityFloatingMenuView extends FrameLayout
}
private int getWindowWidth() {
- return mMargin * 2 + getLayoutWidth();
+ return getMarginStartEndWith(mLastConfiguration) * 2 + getLayoutWidth();
}
private int getWindowHeight() {
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/ItemDelegateCompat.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/ItemDelegateCompat.java
new file mode 100644
index 000000000000..93b0676b4930
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/ItemDelegateCompat.java
@@ -0,0 +1,141 @@
+/*
+ * 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.accessibility.floatingmenu;
+
+import android.content.res.Resources;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.view.View;
+
+import androidx.annotation.NonNull;
+import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
+import androidx.recyclerview.widget.RecyclerViewAccessibilityDelegate;
+
+import com.android.systemui.R;
+import com.android.systemui.accessibility.floatingmenu.AccessibilityFloatingMenuView.ShapeType;
+
+import java.lang.ref.WeakReference;
+
+/**
+ * An accessibility item delegate for the individual items of the list view
+ * {@link AccessibilityFloatingMenuView}.
+ */
+final class ItemDelegateCompat extends RecyclerViewAccessibilityDelegate.ItemDelegate {
+ private final WeakReference<AccessibilityFloatingMenuView> mMenuViewRef;
+
+ ItemDelegateCompat(@NonNull RecyclerViewAccessibilityDelegate recyclerViewDelegate,
+ AccessibilityFloatingMenuView menuView) {
+ super(recyclerViewDelegate);
+ this.mMenuViewRef = new WeakReference<>(menuView);
+ }
+
+ @Override
+ public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfoCompat info) {
+ super.onInitializeAccessibilityNodeInfo(host, info);
+
+ if (mMenuViewRef.get() == null) {
+ return;
+ }
+ final AccessibilityFloatingMenuView menuView = mMenuViewRef.get();
+
+ final Resources res = menuView.getResources();
+ final AccessibilityNodeInfoCompat.AccessibilityActionCompat moveTopLeft =
+ new AccessibilityNodeInfoCompat.AccessibilityActionCompat(R.id.action_move_top_left,
+ res.getString(
+ R.string.accessibility_floating_button_action_move_top_left));
+ info.addAction(moveTopLeft);
+
+ final AccessibilityNodeInfoCompat.AccessibilityActionCompat moveTopRight =
+ new AccessibilityNodeInfoCompat.AccessibilityActionCompat(
+ R.id.action_move_top_right,
+ res.getString(
+ R.string.accessibility_floating_button_action_move_top_right));
+ info.addAction(moveTopRight);
+
+ final AccessibilityNodeInfoCompat.AccessibilityActionCompat moveBottomLeft =
+ new AccessibilityNodeInfoCompat.AccessibilityActionCompat(
+ R.id.action_move_bottom_left,
+ res.getString(
+ R.string.accessibility_floating_button_action_move_bottom_left));
+ info.addAction(moveBottomLeft);
+
+ final AccessibilityNodeInfoCompat.AccessibilityActionCompat moveBottomRight =
+ new AccessibilityNodeInfoCompat.AccessibilityActionCompat(
+ R.id.action_move_bottom_right,
+ res.getString(
+ R.string.accessibility_floating_button_action_move_bottom_right));
+ info.addAction(moveBottomRight);
+
+ final int moveEdgeId = menuView.isOvalShape()
+ ? R.id.action_move_to_edge_and_hide
+ : R.id.action_move_out_edge_and_show;
+ final int moveEdgeTextResId = menuView.isOvalShape()
+ ? R.string.accessibility_floating_button_action_move_to_edge_and_hide_to_half
+ : R.string.accessibility_floating_button_action_move_out_edge_and_show;
+ final AccessibilityNodeInfoCompat.AccessibilityActionCompat moveToOrOutEdge =
+ new AccessibilityNodeInfoCompat.AccessibilityActionCompat(moveEdgeId,
+ res.getString(moveEdgeTextResId));
+ info.addAction(moveToOrOutEdge);
+ }
+
+ @Override
+ public boolean performAccessibilityAction(View host, int action, Bundle args) {
+ if (mMenuViewRef.get() == null) {
+ return super.performAccessibilityAction(host, action, args);
+ }
+ final AccessibilityFloatingMenuView menuView = mMenuViewRef.get();
+
+ menuView.fadeIn();
+
+ final Rect bounds = menuView.getAvailableBounds();
+ if (action == R.id.action_move_top_left) {
+ menuView.setShapeType(ShapeType.OVAL);
+ menuView.snapToLocation(bounds.left, bounds.top);
+ return true;
+ }
+
+ if (action == R.id.action_move_top_right) {
+ menuView.setShapeType(ShapeType.OVAL);
+ menuView.snapToLocation(bounds.right, bounds.top);
+ return true;
+ }
+
+ if (action == R.id.action_move_bottom_left) {
+ menuView.setShapeType(ShapeType.OVAL);
+ menuView.snapToLocation(bounds.left, bounds.bottom);
+ return true;
+ }
+
+ if (action == R.id.action_move_bottom_right) {
+ menuView.setShapeType(ShapeType.OVAL);
+ menuView.snapToLocation(bounds.right, bounds.bottom);
+ return true;
+ }
+
+ if (action == R.id.action_move_to_edge_and_hide) {
+ menuView.setShapeType(ShapeType.HALF_OVAL);
+ return true;
+ }
+
+ if (action == R.id.action_move_out_edge_and_show) {
+ menuView.setShapeType(ShapeType.OVAL);
+ return true;
+ }
+
+ return super.performAccessibilityAction(host, action, args);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java b/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java
index 534f93ec0e47..9676a57b2df9 100644
--- a/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java
@@ -64,7 +64,7 @@ import javax.inject.Inject;
*/
@SysUISingleton
public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsController,
- AppOpsManager.OnOpActiveChangedInternalListener,
+ AppOpsManager.OnOpActiveChangedListener,
AppOpsManager.OnOpNotedListener, IndividualSensorPrivacyController.Callback,
Dumpable {
@@ -359,11 +359,29 @@ public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsCon
mBGHandler.post(() -> notifySuscribersWorker(code, uid, packageName, active));
}
+ /**
+ * Required to override, delegate to other. Should not be called.
+ */
+ public void onOpActiveChanged(String op, int uid, String packageName, boolean active) {
+ onOpActiveChanged(op, uid, packageName, null, active,
+ AppOpsManager.ATTRIBUTION_FLAGS_NONE, AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE);
+ }
+
+ // Get active app ops, and check if their attributions are trusted
@Override
- public void onOpActiveChanged(int code, int uid, String packageName, boolean active) {
+ public void onOpActiveChanged(String op, int uid, String packageName, String attributionTag,
+ boolean active, int attributionFlags, int attributionChainId) {
+ int code = AppOpsManager.strOpToOp(op);
if (DEBUG) {
- Log.w(TAG, String.format("onActiveChanged(%d,%d,%s,%s", code, uid, packageName,
- Boolean.toString(active)));
+ Log.w(TAG, String.format("onActiveChanged(%d,%d,%s,%s,%d,%d)", code, uid, packageName,
+ Boolean.toString(active), attributionChainId, attributionFlags));
+ }
+ if (attributionChainId != AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE
+ && attributionFlags != AppOpsManager.ATTRIBUTION_FLAGS_NONE
+ && (attributionFlags & AppOpsManager.ATTRIBUTION_FLAG_ACCESSOR) == 0
+ && (attributionFlags & AppOpsManager.ATTRIBUTION_FLAG_TRUSTED) == 0) {
+ // if this attribution chain isn't trusted, and this isn't the accessor, do not show it.
+ return;
}
boolean activeChanged = updateActives(code, uid, packageName, active);
if (!activeChanged) return; // early return
diff --git a/packages/SystemUI/src/com/android/systemui/backup/BackupHelper.kt b/packages/SystemUI/src/com/android/systemui/backup/BackupHelper.kt
index fe31a7b75c06..c9e67715decb 100644
--- a/packages/SystemUI/src/com/android/systemui/backup/BackupHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/backup/BackupHelper.kt
@@ -29,6 +29,7 @@ import android.os.UserHandle
import android.util.Log
import com.android.systemui.controls.controller.AuxiliaryPersistenceWrapper
import com.android.systemui.controls.controller.ControlsFavoritePersistenceWrapper
+import com.android.systemui.people.widget.PeopleBackupHelper
/**
* Helper for backing up elements in SystemUI
@@ -45,18 +46,29 @@ class BackupHelper : BackupAgentHelper() {
private const val TAG = "BackupHelper"
internal const val CONTROLS = ControlsFavoritePersistenceWrapper.FILE_NAME
private const val NO_OVERWRITE_FILES_BACKUP_KEY = "systemui.files_no_overwrite"
+ private const val PEOPLE_TILES_BACKUP_KEY = "systemui.people.shared_preferences"
val controlsDataLock = Any()
const val ACTION_RESTORE_FINISHED = "com.android.systemui.backup.RESTORE_FINISHED"
private const val PERMISSION_SELF = "com.android.systemui.permission.SELF"
}
- override fun onCreate() {
+ override fun onCreate(userHandle: UserHandle, operationType: Int) {
super.onCreate()
// The map in mapOf is guaranteed to be order preserving
val controlsMap = mapOf(CONTROLS to getPPControlsFile(this))
NoOverwriteFileBackupHelper(controlsDataLock, this, controlsMap).also {
addHelper(NO_OVERWRITE_FILES_BACKUP_KEY, it)
}
+
+ // Conversations widgets backup only works for system user, because widgets' information is
+ // stored in system user's SharedPreferences files and we can't open those from other users.
+ if (!userHandle.isSystem) {
+ return
+ }
+
+ val keys = PeopleBackupHelper.getFilesToBackup()
+ addHelper(PEOPLE_TILES_BACKUP_KEY, PeopleBackupHelper(
+ this, userHandle, keys.toTypedArray()))
}
override fun onRestoreFinished() {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceToFingerprintView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceToFingerprintView.java
index 76fb49a730a3..205054d68280 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceToFingerprintView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceToFingerprintView.java
@@ -19,17 +19,19 @@ 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.Modality;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
+import android.os.Bundle;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.R;
@@ -87,11 +89,10 @@ public class AuthBiometricFaceToFingerprintView extends AuthBiometricFaceView {
}
}
- @Modality
- private int mActiveSensorType = TYPE_FACE;
-
- @Nullable
- private UdfpsDialogMeasureAdapter mUdfpsMeasureAdapter;
+ @Modality private int mActiveSensorType = TYPE_FACE;
+ @Nullable private ModalityListener mModalityListener;
+ @Nullable private FingerprintSensorPropertiesInternal mFingerprintSensorProps;
+ @Nullable private UdfpsDialogMeasureAdapter mUdfpsMeasureAdapter;
public AuthBiometricFaceToFingerprintView(Context context) {
super(context);
@@ -106,14 +107,21 @@ public class AuthBiometricFaceToFingerprintView extends AuthBiometricFaceView {
super(context, attrs, injector);
}
- void setFingerprintSensorProps(@NonNull FingerprintSensorPropertiesInternal sensorProps) {
- if (!sensorProps.isAnyUdfpsType()) {
- return;
- }
+ @Modality
+ int getActiveSensorType() {
+ return mActiveSensorType;
+ }
- if (mUdfpsMeasureAdapter == null || mUdfpsMeasureAdapter.getSensorProps() != sensorProps) {
- mUdfpsMeasureAdapter = new UdfpsDialogMeasureAdapter(this, sensorProps);
- }
+ boolean isFingerprintUdfps() {
+ return mFingerprintSensorProps.isAnyUdfpsType();
+ }
+
+ void setModalityListener(@NonNull ModalityListener listener) {
+ mModalityListener = listener;
+ }
+
+ void setFingerprintSensorProps(@NonNull FingerprintSensorPropertiesInternal sensorProps) {
+ mFingerprintSensorProps = sensorProps;
}
@Override
@@ -179,11 +187,16 @@ public class AuthBiometricFaceToFingerprintView extends AuthBiometricFaceView {
@Override
public void updateState(@BiometricState int newState) {
if (mState == STATE_HELP || mState == STATE_ERROR) {
+ @Modality final int currentType = mActiveSensorType;
mActiveSensorType = TYPE_FINGERPRINT;
setRequireConfirmation(false);
mConfirmButton.setEnabled(false);
mConfirmButton.setVisibility(View.GONE);
+
+ if (mModalityListener != null && currentType != mActiveSensorType) {
+ mModalityListener.onModalitySwitched(currentType, mActiveSensorType);
+ }
}
super.updateState(newState);
@@ -193,8 +206,34 @@ public class AuthBiometricFaceToFingerprintView extends AuthBiometricFaceView {
@NonNull
AuthDialog.LayoutParams onMeasureInternal(int width, int height) {
final AuthDialog.LayoutParams layoutParams = super.onMeasureInternal(width, height);
- return mUdfpsMeasureAdapter != null
- ? mUdfpsMeasureAdapter.onMeasureInternal(width, height, layoutParams)
+ return isFingerprintUdfps()
+ ? getUdfpsMeasureAdapter().onMeasureInternal(width, height, layoutParams)
: layoutParams;
}
+
+ @NonNull
+ private UdfpsDialogMeasureAdapter getUdfpsMeasureAdapter() {
+ if (mUdfpsMeasureAdapter == null
+ || mUdfpsMeasureAdapter.getSensorProps() != mFingerprintSensorProps) {
+ mUdfpsMeasureAdapter = new UdfpsDialogMeasureAdapter(this, mFingerprintSensorProps);
+ }
+ return mUdfpsMeasureAdapter;
+ }
+
+ @Override
+ public void onSaveState(@NonNull Bundle outState) {
+ super.onSaveState(outState);
+ outState.putInt(AuthDialog.KEY_BIOMETRIC_SENSOR_TYPE, mActiveSensorType);
+ outState.putParcelable(AuthDialog.KEY_BIOMETRIC_SENSOR_PROPS, mFingerprintSensorProps);
+ }
+
+ @Override
+ public void restoreState(@Nullable Bundle savedState) {
+ super.restoreState(savedState);
+ if (savedState != null) {
+ mActiveSensorType = savedState.getInt(AuthDialog.KEY_BIOMETRIC_SENSOR_TYPE, TYPE_FACE);
+ mFingerprintSensorProps =
+ savedState.getParcelable(AuthDialog.KEY_BIOMETRIC_SENSOR_PROPS);
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java
index f37495ef5f48..bebf813e1833 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java
@@ -593,11 +593,13 @@ public abstract class AuthBiometricView extends LinearLayout {
}
public void onSaveState(@NonNull Bundle outState) {
+ outState.putInt(AuthDialog.KEY_BIOMETRIC_CONFIRM_VISIBILITY,
+ mConfirmButton.getVisibility());
outState.putInt(AuthDialog.KEY_BIOMETRIC_TRY_AGAIN_VISIBILITY,
mTryAgainButton.getVisibility());
outState.putInt(AuthDialog.KEY_BIOMETRIC_STATE, mState);
outState.putString(AuthDialog.KEY_BIOMETRIC_INDICATOR_STRING,
- mIndicatorView.getText().toString());
+ mIndicatorView.getText() != null ? mIndicatorView.getText().toString() : "");
outState.putBoolean(AuthDialog.KEY_BIOMETRIC_INDICATOR_ERROR_SHOWING,
mHandler.hasCallbacks(mResetErrorRunnable));
outState.putBoolean(AuthDialog.KEY_BIOMETRIC_INDICATOR_HELP_SHOWING,
@@ -754,9 +756,15 @@ public abstract class AuthBiometricView extends LinearLayout {
// Restore as much state as possible first
updateState(mSavedState.getInt(AuthDialog.KEY_BIOMETRIC_STATE));
- // Restore positive button state
+ // Restore positive button(s) state
+ mConfirmButton.setVisibility(
+ mSavedState.getInt(AuthDialog.KEY_BIOMETRIC_CONFIRM_VISIBILITY));
+ if (mConfirmButton.getVisibility() == View.GONE) {
+ setRequireConfirmation(false);
+ }
mTryAgainButton.setVisibility(
mSavedState.getInt(AuthDialog.KEY_BIOMETRIC_TRY_AGAIN_VISIBILITY));
+
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
index c4f78e7782a2..3f61d3c6af9a 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
@@ -16,6 +16,7 @@
package com.android.systemui.biometrics;
+import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
import static android.hardware.biometrics.BiometricManager.BiometricMultiSensorMode;
import android.annotation.IntDef;
@@ -35,6 +36,7 @@ import android.os.IBinder;
import android.os.Looper;
import android.os.UserManager;
import android.util.Log;
+import android.view.Display;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.LayoutInflater;
@@ -354,6 +356,12 @@ public class AuthContainerView extends LinearLayout
(AuthBiometricFaceToFingerprintView) factory.inflate(
R.layout.auth_biometric_face_to_fingerprint_view, null, false);
faceToFingerprintView.setFingerprintSensorProps(fingerprintSensorProps);
+ faceToFingerprintView.setModalityListener(new ModalityListener() {
+ @Override
+ public void onModalitySwitched(int oldModality, int newModality) {
+ maybeUpdatePositionForUdfps(true /* invalidate */);
+ }
+ });
mBiometricView = faceToFingerprintView;
} else {
Log.e(TAG, "Fingerprint props not found for sensor ID: " + fingerprintSensorId);
@@ -469,6 +477,11 @@ public class AuthContainerView extends LinearLayout
}
@Override
+ public void onOrientationChanged() {
+ maybeUpdatePositionForUdfps(true /* invalidate */);
+ }
+
+ @Override
public void onAttachedToWindow() {
super.onAttachedToWindow();
onAttachedToWindowInternal();
@@ -487,32 +500,7 @@ public class AuthContainerView extends LinearLayout
+ mConfig.mPromptInfo.getAuthenticators());
}
- if (mBiometricView instanceof AuthBiometricUdfpsView) {
- final int displayRotation = getDisplay().getRotation();
- switch (displayRotation) {
- case Surface.ROTATION_0:
- 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);
- setScrollViewGravity(Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM);
- break;
- }
- }
+ maybeUpdatePositionForUdfps(false /* invalidate */);
if (mConfig.mSkipIntro) {
mContainerState = STATE_SHOWING;
@@ -557,6 +545,63 @@ public class AuthContainerView extends LinearLayout
}
}
+ private static boolean shouldUpdatePositionForUdfps(@NonNull View view) {
+ if (view instanceof AuthBiometricUdfpsView) {
+ return true;
+ }
+
+ if (view instanceof AuthBiometricFaceToFingerprintView) {
+ AuthBiometricFaceToFingerprintView faceToFingerprintView =
+ (AuthBiometricFaceToFingerprintView) view;
+ return faceToFingerprintView.getActiveSensorType() == TYPE_FINGERPRINT
+ && faceToFingerprintView.isFingerprintUdfps();
+ }
+
+ return false;
+ }
+
+ private boolean maybeUpdatePositionForUdfps(boolean invalidate) {
+ final Display display = getDisplay();
+ if (display == null) {
+ return false;
+ }
+ if (!shouldUpdatePositionForUdfps(mBiometricView)) {
+ return false;
+ }
+
+ final int displayRotation = display.getRotation();
+ switch (displayRotation) {
+ case Surface.ROTATION_0:
+ 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);
+ setScrollViewGravity(Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM);
+ break;
+ }
+
+ if (invalidate) {
+ mPanelView.invalidateOutline();
+ mBiometricView.requestLayout();
+ }
+
+ return true;
+ }
+
private void setScrollViewGravity(int gravity) {
final FrameLayout.LayoutParams params =
(FrameLayout.LayoutParams) mBiometricScrollView.getLayoutParams();
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index 7947241ff794..2aa89e86d5c8 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -30,7 +30,6 @@ import android.content.Intent;
import android.content.IntentFilter;
import android.content.res.Configuration;
import android.graphics.PointF;
-import android.graphics.RectF;
import android.hardware.biometrics.BiometricAuthenticator.Modality;
import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.BiometricManager.Authenticators;
@@ -49,7 +48,10 @@ import android.os.Handler;
import android.os.Looper;
import android.os.RemoteException;
import android.util.Log;
+import android.view.Display;
import android.view.MotionEvent;
+import android.view.OrientationEventListener;
+import android.view.Surface;
import android.view.WindowManager;
import com.android.internal.R;
@@ -97,17 +99,15 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks,
@VisibleForTesting
AuthDialog mCurrentDialog;
- private WindowManager mWindowManager;
- @Nullable
- private UdfpsController mUdfpsController;
- @Nullable
- private IUdfpsHbmListener mUdfpsHbmListener;
- @Nullable
- private SidefpsController mSidefpsController;
+ @NonNull private final WindowManager mWindowManager;
+ @Nullable private UdfpsController mUdfpsController;
+ @Nullable private IUdfpsHbmListener mUdfpsHbmListener;
+ @Nullable private SidefpsController mSidefpsController;
@VisibleForTesting
TaskStackListener mTaskStackListener;
@VisibleForTesting
IBiometricSysuiReceiver mReceiver;
+ @NonNull private final BiometricOrientationEventListener mOrientationListener;
@Nullable private final List<FaceSensorPropertiesInternal> mFaceProps;
@Nullable private List<FingerprintSensorPropertiesInternal> mFpProps;
@Nullable private List<FingerprintSensorPropertiesInternal> mUdfpsProps;
@@ -120,6 +120,46 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks,
}
}
+ private class BiometricOrientationEventListener extends OrientationEventListener {
+ @Surface.Rotation private int mLastRotation = ORIENTATION_UNKNOWN;
+
+ BiometricOrientationEventListener(Context context) {
+ super(context);
+
+ final Display display = context.getDisplay();
+ if (display != null) {
+ mLastRotation = display.getRotation();
+ }
+ }
+
+ @Override
+ public void onOrientationChanged(int orientation) {
+ if (orientation == ORIENTATION_UNKNOWN) {
+ return;
+ }
+
+ final Display display = mContext.getDisplay();
+ if (display == null) {
+ return;
+ }
+
+ final int rotation = display.getRotation();
+ if (mLastRotation != rotation) {
+ mLastRotation = rotation;
+
+ if (mCurrentDialog != null) {
+ mCurrentDialog.onOrientationChanged();
+ }
+ if (mUdfpsController != null) {
+ mUdfpsController.onOrientationChanged();
+ }
+ if (mSidefpsController != null) {
+ mSidefpsController.onOrientationChanged();
+ }
+ }
+ }
+ }
+
@NonNull
private final IFingerprintAuthenticatorsRegisteredCallback
mFingerprintAuthenticatorsRegisteredCallback =
@@ -192,6 +232,7 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks,
Log.w(TAG, "Evicting client due to: " + topPackage);
mCurrentDialog.dismissWithoutCallback(true /* animate */);
mCurrentDialog = null;
+
if (mReceiver != null) {
mReceiver.onDialogDismissed(
BiometricPrompt.DISMISSED_REASON_USER_CANCEL,
@@ -342,15 +383,6 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks,
/**
* @return where the UDFPS exists on the screen in pixels in portrait mode.
*/
- @Nullable public RectF getUdfpsRegion() {
- return mUdfpsController == null
- ? null
- : mUdfpsController.getSensorLocation();
- }
-
- /**
- * @return where the UDFPS exists on the screen in pixels in portrait mode.
- */
@Nullable public PointF getUdfpsSensorLocation() {
if (mUdfpsController == null) {
return null;
@@ -422,8 +454,10 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks,
}
@Inject
- public AuthController(Context context, CommandQueue commandQueue,
+ public AuthController(Context context,
+ CommandQueue commandQueue,
ActivityTaskManager activityTaskManager,
+ @NonNull WindowManager windowManager,
@Nullable FingerprintManager fingerprintManager,
@Nullable FaceManager faceManager,
Provider<UdfpsController> udfpsControllerFactory,
@@ -435,6 +469,9 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks,
mFaceManager = faceManager;
mUdfpsControllerFactory = udfpsControllerFactory;
mSidefpsControllerFactory = sidefpsControllerFactory;
+ mWindowManager = windowManager;
+ mOrientationListener = new BiometricOrientationEventListener(context);
+ mOrientationListener.enable();
mFaceProps = mFaceManager != null ? mFaceManager.getSensorPropertiesInternal() : null;
@@ -462,7 +499,6 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks,
@Override
public void start() {
mCommandQueue.addCallback(this);
- mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
if (mFingerprintManager != null) {
mFingerprintManager.addAuthenticatorsRegisteredCallback(
@@ -715,15 +751,6 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks,
@Override
protected void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
- // UdfpsController is not BiometricPrompt-specific. It can be active for keyguard or
- // enrollment.
- if (mUdfpsController != null) {
- mUdfpsController.onConfigurationChanged();
- }
-
- if (mSidefpsController != null) {
- mSidefpsController.onConfigurationChanged();
- }
// Save the state of the current dialog (buttons showing, etc)
if (mCurrentDialog != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialog.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialog.java
index cbd166e6e0b1..fa5213e94081 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialog.java
@@ -35,6 +35,7 @@ public interface AuthDialog {
String KEY_BIOMETRIC_SHOWING = "biometric_showing";
String KEY_CREDENTIAL_SHOWING = "credential_showing";
+ String KEY_BIOMETRIC_CONFIRM_VISIBILITY = "confirm_visibility";
String KEY_BIOMETRIC_TRY_AGAIN_VISIBILITY = "try_agian_visibility";
String KEY_BIOMETRIC_STATE = "state";
String KEY_BIOMETRIC_INDICATOR_STRING = "indicator_string"; // error / help / hint
@@ -42,6 +43,9 @@ public interface AuthDialog {
String KEY_BIOMETRIC_INDICATOR_HELP_SHOWING = "hint_is_temporary";
String KEY_BIOMETRIC_DIALOG_SIZE = "size";
+ String KEY_BIOMETRIC_SENSOR_TYPE = "sensor_type";
+ String KEY_BIOMETRIC_SENSOR_PROPS = "sensor_props";
+
int SIZE_UNKNOWN = 0;
/**
* Minimal UI, showing only biometric icon.
@@ -152,4 +156,12 @@ public interface AuthDialog {
* @return true if device credential is allowed.
*/
boolean isAllowDeviceCredentials();
+
+ /**
+ * Called when the device's orientation changed and the dialog may need to do another
+ * layout. This is most relevant to UDFPS since configuration changes are not sent by
+ * the framework in equivalent cases (landscape to reverse landscape) but the dialog
+ * must remain fixed on the physical sensor location.
+ */
+ void onOrientationChanged();
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
index 77cca2e3089c..1df8ad5e51fb 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
@@ -25,7 +25,6 @@ import com.android.keyguard.KeyguardUpdateMonitor
import com.android.keyguard.KeyguardUpdateMonitorCallback
import com.android.settingslib.Utils
import com.android.systemui.statusbar.CircleReveal
-import com.android.systemui.statusbar.LiftReveal
import com.android.systemui.statusbar.LightRevealEffect
import com.android.systemui.statusbar.NotificationShadeWindowController
import com.android.systemui.statusbar.commandline.Command
@@ -116,9 +115,6 @@ class AuthRippleController @Inject constructor(
/* end runnable */
Runnable {
notificationShadeWindowController.setForcePluginOpen(false, this)
- if (useCircleReveal) {
- lightRevealScrim?.revealEffect = LiftReveal
- }
},
/* circleReveal */
if (useCircleReveal) {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt
index dd73c4f8d071..95ea81003ecb 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt
@@ -23,11 +23,7 @@ import android.content.Context
import android.graphics.Canvas
import android.graphics.Paint
import android.graphics.PointF
-import android.media.AudioAttributes
-import android.os.VibrationEffect
-import android.os.Vibrator
import android.util.AttributeSet
-import android.util.MathUtils
import android.view.View
import android.view.animation.PathInterpolator
import com.android.internal.graphics.ColorUtils
@@ -36,26 +32,25 @@ import com.android.systemui.statusbar.charging.RippleShader
private const val RIPPLE_ANIMATION_DURATION: Long = 1533
private const val RIPPLE_SPARKLE_STRENGTH: Float = 0.4f
-private const val RIPPLE_VIBRATION_PRIMITIVE: Int = VibrationEffect.Composition.PRIMITIVE_LOW_TICK
-private const val RIPPLE_VIBRATION_SIZE: Int = 60
-private const val RIPPLE_VIBRATION_SCALE_START: Float = 0.6f
-private const val RIPPLE_VIBRATION_SCALE_DECAY: Float = -0.1f
/**
* Expanding ripple effect on the transition from biometric authentication success to showing
* launcher.
*/
class AuthRippleView(context: Context?, attrs: AttributeSet?) : View(context, attrs) {
- private val vibrator: Vibrator? = context?.getSystemService(Vibrator::class.java)
private var rippleInProgress: Boolean = false
private val rippleShader = RippleShader()
private val ripplePaint = Paint()
- private val rippleVibrationEffect = createVibrationEffect(vibrator)
- private val rippleVibrationAttrs =
- AudioAttributes.Builder()
- .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
- .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION)
- .build()
+ private var radius: Float = 0.0f
+ set(value) {
+ rippleShader.radius = value
+ field = value
+ }
+ private var origin: PointF = PointF()
+ set(value) {
+ rippleShader.origin = value
+ field = value
+ }
init {
rippleShader.color = 0xffffffff.toInt() // default color
@@ -66,8 +61,8 @@ class AuthRippleView(context: Context?, attrs: AttributeSet?) : View(context, at
}
fun setSensorLocation(location: PointF) {
- rippleShader.origin = location
- rippleShader.radius = maxOf(location.x, location.y, width - location.x, height - location.y)
+ origin = location
+ radius = maxOf(location.x, location.y, width - location.x, height - location.y)
.toFloat()
}
@@ -141,8 +136,6 @@ class AuthRippleView(context: Context?, attrs: AttributeSet?) : View(context, at
}
})
}
- // TODO (b/185124905): custom haptic TBD
- // vibrate()
animatorSet.start()
}
@@ -151,26 +144,11 @@ class AuthRippleView(context: Context?, attrs: AttributeSet?) : View(context, at
}
override fun onDraw(canvas: Canvas?) {
- // draw over the entire screen
- canvas?.drawRect(0f, 0f, width.toFloat(), height.toFloat(), ripplePaint)
- }
-
- private fun vibrate() {
- if (rippleVibrationEffect != null) {
- vibrator?.vibrate(rippleVibrationEffect, rippleVibrationAttrs)
- }
- }
-
- private fun createVibrationEffect(vibrator: Vibrator?): VibrationEffect? {
- if (vibrator?.areAllPrimitivesSupported(RIPPLE_VIBRATION_PRIMITIVE) == false) {
- return null
- }
- val composition = VibrationEffect.startComposition()
- for (i in 0 until RIPPLE_VIBRATION_SIZE) {
- val scale =
- RIPPLE_VIBRATION_SCALE_START * MathUtils.exp(RIPPLE_VIBRATION_SCALE_DECAY * i)
- composition.addPrimitive(RIPPLE_VIBRATION_PRIMITIVE, scale, 0 /* delay */)
- }
- return composition.compose()
+ // To reduce overdraw, we mask the effect to a circle whose radius is big enough to cover
+ // the active effect area. Values here should be kept in sync with the
+ // animation implementation in the ripple shader.
+ val maskRadius = (1 - (1 - rippleShader.progress) * (1 - rippleShader.progress) *
+ (1 - rippleShader.progress)) * radius * 1.5f
+ canvas?.drawCircle(origin.x, origin.y, maskRadius, ripplePaint)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ModalityListener.java b/packages/SystemUI/src/com/android/systemui/biometrics/ModalityListener.java
new file mode 100644
index 000000000000..c162f7dec452
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ModalityListener.java
@@ -0,0 +1,36 @@
+/*
+ * 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.hardware.biometrics.BiometricAuthenticator.Modality;
+
+/**
+ * Listener for events related to modality changes during operations.
+ *
+ * Used by views such as {@link AuthBiometricFaceToFingerprintView} that support fallback style
+ * authentication.
+ */
+public interface ModalityListener {
+
+ /**
+ * The modality has changed. Called after the transition has been fully completed.
+ *
+ * @param oldModality original modality
+ * @param newModality current modality
+ */
+ default void onModalitySwitched(@Modality int oldModality, @Modality int newModality) {}
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/SidefpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/SidefpsController.java
index a52296a71960..436e1e4e3116 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/SidefpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/SidefpsController.java
@@ -137,8 +137,7 @@ public class SidefpsController {
}
}
-
- void onConfigurationChanged() {
+ void onOrientationChanged() {
// If mView is null or if view is hidden, then return.
if (mView == null || !mIsVisible) {
return;
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationView.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationView.java
index 2d403f63c2cd..1f11894de55e 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationView.java
@@ -74,7 +74,10 @@ abstract class UdfpsAnimationView extends FrameLayout {
return false;
}
- protected void updateAlpha() {
+ /**
+ * @return current alpha
+ */
+ protected int updateAlpha() {
int alpha = calculateAlpha();
getDrawable().setAlpha(alpha);
@@ -84,6 +87,8 @@ abstract class UdfpsAnimationView extends FrameLayout {
} else {
((ViewGroup) getParent()).setVisibility(View.VISIBLE);
}
+
+ return alpha;
}
int calculateAlpha() {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index 11412f41f578..81e60f316bbd 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -16,6 +16,8 @@
package com.android.systemui.biometrics;
+import static android.os.VibrationEffect.Composition.PRIMITIVE_LOW_TICK;
+
import static com.android.internal.util.Preconditions.checkArgument;
import static com.android.internal.util.Preconditions.checkNotNull;
import static com.android.systemui.classifier.Classifier.UDFPS_AUTHENTICATION;
@@ -71,6 +73,7 @@ import com.android.systemui.statusbar.LockscreenShadeTransitionController;
import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
import com.android.systemui.util.concurrency.DelayableExecutor;
+import com.android.systemui.util.concurrency.Execution;
import java.util.Optional;
@@ -97,6 +100,7 @@ public class UdfpsController implements DozeReceiver {
private static final long MIN_TOUCH_LOG_INTERVAL = 50;
private final Context mContext;
+ private final Execution mExecution;
private final FingerprintManager mFingerprintManager;
@NonNull private final LayoutInflater mInflater;
private final WindowManager mWindowManager;
@@ -143,34 +147,25 @@ public class UdfpsController implements DozeReceiver {
@Nullable private Runnable mCancelAodTimeoutAction;
private boolean mScreenOn;
private Runnable mAodInterruptRunnable;
+ private boolean mOnFingerDown;
@VisibleForTesting
- static final AudioAttributes VIBRATION_SONIFICATION_ATTRIBUTES =
+ public static final AudioAttributes VIBRATION_SONIFICATION_ATTRIBUTES =
new AudioAttributes.Builder()
.setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
.setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION)
.build();
- private final VibrationEffect mEffectTick = VibrationEffect.get(VibrationEffect.EFFECT_TICK);
- private final VibrationEffect mEffectTextureTick =
+ public static final VibrationEffect EFFECT_TICK =
+ VibrationEffect.get(VibrationEffect.EFFECT_TICK);
+ private static final VibrationEffect EFFECT_TEXTURE_TICK =
VibrationEffect.get(VibrationEffect.EFFECT_TEXTURE_TICK);
@VisibleForTesting
- final VibrationEffect mEffectClick = VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
- private final VibrationEffect mEffectHeavy =
+ static final VibrationEffect EFFECT_CLICK = VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
+ private static final VibrationEffect EFFECT_HEAVY =
VibrationEffect.get(VibrationEffect.EFFECT_HEAVY_CLICK);
- private final VibrationEffect mDoubleClick =
+ private static final VibrationEffect EFFECT_DOUBLE_CLICK =
VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK);
- private final Runnable mAcquiredVibration = new Runnable() {
- @Override
- public void run() {
- if (mVibrator == null) {
- return;
- }
- String effect = Settings.Global.getString(mContext.getContentResolver(),
- "udfps_acquired_type");
- mVibrator.vibrate(getVibration(effect, mEffectTick), VIBRATION_SONIFICATION_ATTRIBUTES);
- }
- };
private final ScreenLifecycle.Observer mScreenObserver = new ScreenLifecycle.Observer() {
@Override
@@ -436,30 +431,7 @@ public class UdfpsController implements DozeReceiver {
mTouchLogTime = SystemClock.elapsedRealtime();
mPowerManager.userActivity(SystemClock.uptimeMillis(),
PowerManager.USER_ACTIVITY_EVENT_TOUCH, 0);
-
- // TODO: this should eventually be removed after ux testing
- if (mVibrator != null) {
- final ContentResolver contentResolver =
- mContext.getContentResolver();
- int startEnabled = Settings.Global.getInt(contentResolver,
- "udfps_start", 1);
- if (startEnabled > 0) {
- String startEffectSetting = Settings.Global.getString(
- contentResolver, "udfps_start_type");
- mVibrator.vibrate(getVibration(startEffectSetting,
- mEffectClick), VIBRATION_SONIFICATION_ATTRIBUTES);
- }
-
- int acquiredEnabled = Settings.Global.getInt(contentResolver,
- "udfps_acquired", 0);
- if (acquiredEnabled > 0) {
- int delay = Settings.Global.getInt(contentResolver,
- "udfps_acquired_delay", 500);
- mMainHandler.removeCallbacks(mAcquiredVibration);
- mMainHandler.postDelayed(mAcquiredVibration, delay);
- }
- }
-
+ playStartHaptic();
handled = true;
} else if (sinceLastLog >= MIN_TOUCH_LOG_INTERVAL) {
Log.v(TAG, "onTouch | finger move: " + touchInfo);
@@ -496,6 +468,7 @@ public class UdfpsController implements DozeReceiver {
@Inject
public UdfpsController(@NonNull Context context,
+ @NonNull Execution execution,
@NonNull LayoutInflater inflater,
@Nullable FingerprintManager fingerprintManager,
@NonNull WindowManager windowManager,
@@ -512,8 +485,10 @@ public class UdfpsController implements DozeReceiver {
@NonNull LockscreenShadeTransitionController lockscreenShadeTransitionController,
@NonNull ScreenLifecycle screenLifecycle,
@Nullable Vibrator vibrator,
+ @NonNull UdfpsHapticsSimulator udfpsHapticsSimulator,
@NonNull Optional<UdfpsHbmProvider> hbmProvider) {
mContext = context;
+ mExecution = execution;
// TODO (b/185124905): inject main handler and vibrator once done prototyping
mMainHandler = new Handler(Looper.getMainLooper());
mVibrator = vibrator;
@@ -557,6 +532,29 @@ public class UdfpsController implements DozeReceiver {
final IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
context.registerReceiver(mBroadcastReceiver, filter);
+
+ udfpsHapticsSimulator.setUdfpsController(this);
+ }
+
+ /**
+ * Play haptic to signal udfps scanning started.
+ */
+ @VisibleForTesting
+ public void playStartHaptic() {
+ if (mVibrator != null) {
+ final ContentResolver contentResolver =
+ mContext.getContentResolver();
+ // TODO: these settings checks should eventually be removed after ux testing
+ // (b/185124905)
+ int startEnabled = Settings.Global.getInt(contentResolver,
+ "udfps_start", 1);
+ if (startEnabled > 0) {
+ String startEffectSetting = Settings.Global.getString(
+ contentResolver, "udfps_start_type");
+ mVibrator.vibrate(getVibration(startEffectSetting,
+ EFFECT_CLICK), VIBRATION_SONIFICATION_ATTRIBUTES);
+ }
+ }
}
private int getCoreLayoutParamFlags() {
@@ -599,8 +597,10 @@ public class UdfpsController implements DozeReceiver {
}
private void updateOverlay() {
+ mExecution.assertIsMainThread();
+
if (mServerRequest != null) {
- showUdfpsOverlay(mServerRequest.mRequestReason);
+ showUdfpsOverlay(mServerRequest);
} else {
hideUdfpsOverlay();
}
@@ -650,7 +650,7 @@ public class UdfpsController implements DozeReceiver {
return mCoreLayoutParams;
}
- void onConfigurationChanged() {
+ void onOrientationChanged() {
// When the configuration changes it's almost always necessary to destroy and re-create
// the overlay's window to pass it the new LayoutParams.
// Hiding the overlay will destroy its window. It's safe to hide the overlay regardless
@@ -661,36 +661,38 @@ public class UdfpsController implements DozeReceiver {
updateOverlay();
}
- private void showUdfpsOverlay(int reason) {
- mFgExecutor.execute(() -> {
- if (mView == null) {
- try {
- Log.v(TAG, "showUdfpsOverlay | adding window reason=" + reason);
- mView = (UdfpsView) mInflater.inflate(R.layout.udfps_view, null, false);
- mView.setSensorProperties(mSensorProps);
- mView.setHbmProvider(mHbmProvider);
- UdfpsAnimationViewController animation = inflateUdfpsAnimation(reason);
- animation.init();
- mView.setAnimationViewController(animation);
-
- // This view overlaps the sensor area, so prevent it from being selectable
- // during a11y.
- if (reason == IUdfpsOverlayController.REASON_ENROLL_FIND_SENSOR
- || reason == IUdfpsOverlayController.REASON_ENROLL_ENROLLING) {
- mView.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
- }
+ private void showUdfpsOverlay(@NonNull ServerRequest request) {
+ mExecution.assertIsMainThread();
- mWindowManager.addView(mView, computeLayoutParams(animation));
- mAccessibilityManager.addTouchExplorationStateChangeListener(
- mTouchExplorationStateChangeListener);
- updateTouchListener();
- } catch (RuntimeException e) {
- Log.e(TAG, "showUdfpsOverlay | failed to add window", e);
+ final int reason = request.mRequestReason;
+ if (mView == null) {
+ try {
+ Log.v(TAG, "showUdfpsOverlay | adding window reason=" + reason);
+ mView = (UdfpsView) mInflater.inflate(R.layout.udfps_view, null, false);
+ mOnFingerDown = false;
+ mView.setSensorProperties(mSensorProps);
+ mView.setHbmProvider(mHbmProvider);
+ UdfpsAnimationViewController animation = inflateUdfpsAnimation(reason);
+ animation.init();
+ mView.setAnimationViewController(animation);
+
+ // This view overlaps the sensor area, so prevent it from being selectable
+ // during a11y.
+ if (reason == IUdfpsOverlayController.REASON_ENROLL_FIND_SENSOR
+ || reason == IUdfpsOverlayController.REASON_ENROLL_ENROLLING) {
+ mView.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
}
- } else {
- Log.v(TAG, "showUdfpsOverlay | the overlay is already showing");
+
+ mWindowManager.addView(mView, computeLayoutParams(animation));
+ mAccessibilityManager.addTouchExplorationStateChangeListener(
+ mTouchExplorationStateChangeListener);
+ updateTouchListener();
+ } catch (RuntimeException e) {
+ Log.e(TAG, "showUdfpsOverlay | failed to add window", e);
}
- });
+ } else {
+ Log.v(TAG, "showUdfpsOverlay | the overlay is already showing");
+ }
}
private UdfpsAnimationViewController inflateUdfpsAnimation(int reason) {
@@ -750,22 +752,22 @@ public class UdfpsController implements DozeReceiver {
}
private void hideUdfpsOverlay() {
- mFgExecutor.execute(() -> {
- if (mView != null) {
- Log.v(TAG, "hideUdfpsOverlay | removing window");
- // Reset the controller back to its starting state.
- onFingerUp();
- mWindowManager.removeView(mView);
- mView.setOnTouchListener(null);
- mView.setOnHoverListener(null);
- mView.setAnimationViewController(null);
- mAccessibilityManager.removeTouchExplorationStateChangeListener(
- mTouchExplorationStateChangeListener);
- mView = null;
- } else {
- Log.v(TAG, "hideUdfpsOverlay | the overlay is already hidden");
- }
- });
+ mExecution.assertIsMainThread();
+
+ if (mView != null) {
+ Log.v(TAG, "hideUdfpsOverlay | removing window");
+ // Reset the controller back to its starting state.
+ onFingerUp();
+ mWindowManager.removeView(mView);
+ mView.setOnTouchListener(null);
+ mView.setOnHoverListener(null);
+ mView.setAnimationViewController(null);
+ mAccessibilityManager.removeTouchExplorationStateChangeListener(
+ mTouchExplorationStateChangeListener);
+ mView = null;
+ } else {
+ Log.v(TAG, "hideUdfpsOverlay | the overlay is already hidden");
+ }
}
/**
@@ -820,12 +822,13 @@ public class UdfpsController implements DozeReceiver {
mIsAodInterruptActive = false;
}
- // This method can be called from the UI thread.
private void onFingerDown(int x, int y, float minor, float major) {
+ mExecution.assertIsMainThread();
if (mView == null) {
Log.w(TAG, "Null view in onFingerDown");
return;
}
+ mOnFingerDown = true;
mFingerprintManager.onPointerDown(mSensorProps.sensorId, x, y, minor, major);
Trace.endAsyncSection("UdfpsController.e2e.onPointerDown", 0);
Trace.beginAsyncSection("UdfpsController.e2e.startIllumination", 0);
@@ -835,39 +838,51 @@ public class UdfpsController implements DozeReceiver {
});
}
- // This method can be called from the UI thread.
private void onFingerUp() {
+ mExecution.assertIsMainThread();
mActivePointerId = -1;
mGoodCaptureReceived = false;
- mMainHandler.removeCallbacks(mAcquiredVibration);
if (mView == null) {
Log.w(TAG, "Null view in onFingerUp");
return;
}
- mFingerprintManager.onPointerUp(mSensorProps.sensorId);
+ if (mOnFingerDown) {
+ mFingerprintManager.onPointerUp(mSensorProps.sensorId);
+ }
+ mOnFingerDown = false;
if (mView.isIlluminationRequested()) {
mView.stopIllumination();
}
}
-
- private VibrationEffect getVibration(String effect, VibrationEffect defaultEffect) {
+ /**
+ * get vibration to play given string
+ * used for testing purposes (b/185124905)
+ */
+ public static VibrationEffect getVibration(String effect, VibrationEffect defaultEffect) {
if (TextUtils.isEmpty(effect)) {
return defaultEffect;
}
switch (effect.toLowerCase()) {
case "click":
- return mEffectClick;
+ return EFFECT_CLICK;
case "heavy":
- return mEffectHeavy;
+ return EFFECT_HEAVY;
case "texture_tick":
- return mEffectTextureTick;
+ return EFFECT_TEXTURE_TICK;
case "tick":
- return mEffectTick;
+ return EFFECT_TICK;
case "double_tap":
- return mDoubleClick;
+ return EFFECT_DOUBLE_CLICK;
default:
+ try {
+ int primitive = Integer.parseInt(effect);
+ if (primitive <= PRIMITIVE_LOW_TICK && primitive > -1) {
+ return VibrationEffect.startComposition().addPrimitive(primitive).compose();
+ }
+ } catch (NumberFormatException e) {
+ }
return defaultEffect;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapter.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapter.java
index 1ad2b9ca856c..7ccfb865cd5a 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapter.java
@@ -180,17 +180,25 @@ public class UdfpsDialogMeasureAdapter {
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.
+ } else if (child.getId() == R.id.space_above_icon) {
+ // Adjust the width and height of the top spacer if necessary.
+ final int newTopSpacerHeight = child.getLayoutParams().height
+ - Math.min(bottomSpacerHeight, 0);
+ child.measure(
+ MeasureSpec.makeMeasureSpec(remeasuredWidth, MeasureSpec.EXACTLY),
+ MeasureSpec.makeMeasureSpec(newTopSpacerHeight, MeasureSpec.EXACTLY));
+ } else if (child.getId() == R.id.button_bar) {
+ // Adjust the width of the button bar while preserving its height.
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.
+ final int newBottomSpacerHeight = Math.max(bottomSpacerHeight, 0);
child.measure(
MeasureSpec.makeMeasureSpec(remeasuredWidth, MeasureSpec.EXACTLY),
- MeasureSpec.makeMeasureSpec(bottomSpacerHeight, MeasureSpec.EXACTLY));
+ MeasureSpec.makeMeasureSpec(newBottomSpacerHeight, MeasureSpec.EXACTLY));
} else {
// Use the remeasured width for all other child views.
child.measure(
@@ -208,7 +216,7 @@ public class UdfpsDialogMeasureAdapter {
private int getViewHeightPx(@IdRes int viewId) {
final View view = mView.findViewById(viewId);
- return view != null ? view.getMeasuredHeight() : 0;
+ return view != null && view.getVisibility() != View.GONE ? view.getMeasuredHeight() : 0;
}
private int getDialogMarginPx() {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollView.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollView.java
index 7f4d7fe01e90..2cdf49d6fc3c 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollView.java
@@ -43,11 +43,6 @@ public class UdfpsEnrollView extends UdfpsAnimationView {
}
@Override
- protected void updateAlpha() {
- super.updateAlpha();
- }
-
- @Override
protected void onFinishInflate() {
mFingerprintView = findViewById(R.id.udfps_enroll_animation_fp_view);
mFingerprintView.setImageDrawable(mFingerprintDrawable);
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollViewController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollViewController.java
index 91cc149be800..3dab010d917c 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollViewController.java
@@ -71,12 +71,6 @@ public class UdfpsEnrollViewController extends UdfpsAnimationViewController<Udfp
}
}
- @Override
- protected void onViewDetached() {
- super.onViewDetached();
- mEnrollHelper.setListener(null);
- }
-
@NonNull
@Override
public PointF getTouchTranslation() {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsHapticsSimulator.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsHapticsSimulator.kt
new file mode 100644
index 000000000000..ea2bbfad1b74
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsHapticsSimulator.kt
@@ -0,0 +1,94 @@
+/*
+ * 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.media.AudioAttributes
+import android.os.VibrationEffect
+import android.os.Vibrator
+
+import com.android.keyguard.KeyguardUpdateMonitor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.commandline.Command
+import com.android.systemui.statusbar.commandline.CommandRegistry
+
+import java.io.PrintWriter
+
+import javax.inject.Inject
+
+/**
+ * Used to simulate haptics that may be used for udfps authentication.
+ */
+@SysUISingleton
+class UdfpsHapticsSimulator @Inject constructor(
+ commandRegistry: CommandRegistry,
+ val vibrator: Vibrator?,
+ val keyguardUpdateMonitor: KeyguardUpdateMonitor
+) : Command {
+ val sonificationEffects =
+ AudioAttributes.Builder()
+ .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
+ .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION)
+ .build()
+ var udfpsController: UdfpsController? = null
+
+ init {
+ commandRegistry.registerCommand("udfps-haptic") { this }
+ }
+
+ override fun execute(pw: PrintWriter, args: List<String>) {
+ if (args.isEmpty()) {
+ invalidCommand(pw)
+ } else {
+ when (args[0]) {
+ "start" -> {
+ udfpsController?.playStartHaptic()
+ }
+ "acquired" -> {
+ keyguardUpdateMonitor.playAcquiredHaptic()
+ }
+ "success" -> {
+ // needs to be kept up to date with AcquisitionClient#SUCCESS_VIBRATION_EFFECT
+ vibrator?.vibrate(
+ VibrationEffect.get(VibrationEffect.EFFECT_CLICK),
+ sonificationEffects)
+ }
+ "error" -> {
+ // needs to be kept up to date with AcquisitionClient#ERROR_VIBRATION_EFFECT
+ vibrator?.vibrate(
+ VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK),
+ sonificationEffects)
+ }
+ else -> invalidCommand(pw)
+ }
+ }
+ }
+
+ override fun help(pw: PrintWriter) {
+ pw.println("Usage: adb shell cmd statusbar udfps-haptic <haptic>")
+ pw.println("Available commands:")
+ pw.println(" start")
+ pw.println(" acquired")
+ pw.println(" success, always plays CLICK haptic")
+ pw.println(" error, always plays DOUBLE_CLICK haptic")
+ }
+
+ fun invalidCommand(pw: PrintWriter) {
+ pw.println("invalid command")
+ help(pw)
+ }
+} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardDrawable.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardDrawable.java
deleted file mode 100644
index 889409351a8c..000000000000
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardDrawable.java
+++ /dev/null
@@ -1,127 +0,0 @@
-/*
- * 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 com.android.systemui.doze.util.BurnInHelperKt.getBurnInOffset;
-
-import android.animation.ValueAnimator;
-import android.content.Context;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.util.MathUtils;
-
-import androidx.annotation.NonNull;
-
-import com.android.internal.graphics.ColorUtils;
-import com.android.settingslib.Utils;
-import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
-import com.android.systemui.doze.DozeReceiver;
-
-/**
- * UDFPS animations that should be shown when authenticating on keyguard.
- */
-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;
-
- // AOD anti-burn-in offsets
- private final int mMaxBurnInOffsetX;
- private final int mMaxBurnInOffsetY;
- private float mInterpolatedDarkAmount;
- 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;
-
- 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.wallpaperTextColorAccent);
- mAmbientDisplayColor = Color.WHITE;
-
- updateIcon();
- }
-
- private void updateIcon() {
- mBurnInOffsetX = MathUtils.lerp(0f,
- getBurnInOffset(mMaxBurnInOffsetX * 2, true /* xAxis */)
- - mMaxBurnInOffsetX,
- mInterpolatedDarkAmount);
- mBurnInOffsetY = MathUtils.lerp(0f,
- getBurnInOffset(mMaxBurnInOffsetY * 2, false /* xAxis */)
- - mMaxBurnInOffsetY,
- mInterpolatedDarkAmount);
-
- mFingerprintDrawable.setTint(ColorUtils.blendARGB(mLockScreenColor,
- mAmbientDisplayColor, mInterpolatedDarkAmount));
- setStrokeWidth(MathUtils.lerp(DEFAULT_STROKE_WIDTH, DEFAULT_AOD_STROKE_WIDTH,
- mInterpolatedDarkAmount));
- invalidateSelf();
- }
-
- @Override
- public void dozeTimeTick() {
- updateIcon();
- }
-
- @Override
- public void draw(@NonNull Canvas canvas) {
- 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;
- updateIcon();
- }
-
- void setLockScreenColor(int color) {
- if (mLockScreenColor == color) return;
- mLockScreenColor = color;
- updateIcon();
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.java
index 804e2ab00bde..ec96af3ef500 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.java
@@ -16,6 +16,9 @@
package com.android.systemui.biometrics;
+import static com.android.systemui.doze.util.BurnInHelperKt.getBurnInOffset;
+import static com.android.systemui.doze.util.BurnInHelperKt.getBurnInProgressOffset;
+
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
@@ -23,7 +26,10 @@ import android.animation.ArgbEvaluator;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.content.Context;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffColorFilter;
import android.util.AttributeSet;
+import android.util.MathUtils;
import android.view.View;
import android.widget.ImageView;
@@ -34,12 +40,17 @@ import com.android.systemui.R;
import com.android.systemui.animation.Interpolators;
import com.android.systemui.statusbar.StatusBarState;
+import com.airbnb.lottie.LottieAnimationView;
+import com.airbnb.lottie.LottieProperty;
+import com.airbnb.lottie.model.KeyPath;
/**
* View corresponding with udfps_keyguard_view.xml
*/
public class UdfpsKeyguardView extends UdfpsAnimationView {
- private final UdfpsKeyguardDrawable mFingerprintDrawable;
- private ImageView mFingerprintView;
+ private UdfpsDrawable mFingerprintDrawable; // placeholder
+ private LottieAnimationView mAodFp;
+ private LottieAnimationView mLockScreenFp;
+ private int mUdfpsBouncerColor;
private int mWallpaperTextColor;
private int mStatusBarState;
@@ -52,16 +63,31 @@ public class UdfpsKeyguardView extends UdfpsAnimationView {
private AnimatorSet mAnimatorSet;
private int mAlpha; // 0-255
+ // AOD anti-burn-in offsets
+ private final int mMaxBurnInOffsetX;
+ private final int mMaxBurnInOffsetY;
+ private float mBurnInOffsetX;
+ private float mBurnInOffsetY;
+ private float mBurnInProgress;
+ private float mInterpolatedDarkAmount;
+
+ private ValueAnimator mHintAnimator;
+
public UdfpsKeyguardView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
- mFingerprintDrawable = new UdfpsKeyguardDrawable(mContext);
+ mFingerprintDrawable = new UdfpsFpDrawable(context);
+
+ mMaxBurnInOffsetX = context.getResources()
+ .getDimensionPixelSize(R.dimen.udfps_burn_in_offset_x);
+ mMaxBurnInOffsetY = context.getResources()
+ .getDimensionPixelSize(R.dimen.udfps_burn_in_offset_y);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
- mFingerprintView = findViewById(R.id.udfps_keyguard_animation_fp_view);
- mFingerprintView.setForeground(mFingerprintDrawable);
+ mAodFp = findViewById(R.id.udfps_aod_fp);
+ mLockScreenFp = findViewById(R.id.udfps_lockscreen_fp);
mBgProtection = findViewById(R.id.udfps_keyguard_fp_bg);
@@ -69,7 +95,16 @@ public class UdfpsKeyguardView extends UdfpsAnimationView {
R.attr.wallpaperTextColorAccent);
mTextColorPrimary = Utils.getColorAttrDefaultColor(mContext,
android.R.attr.textColorPrimary);
+
+ // requires call to invalidate to update the color (see #updateColor)
+ mLockScreenFp.addValueCallback(
+ new KeyPath("**"), LottieProperty.COLOR_FILTER,
+ frameInfo -> new PorterDuffColorFilter(getColor(), PorterDuff.Mode.SRC_ATOP)
+ );
mUdfpsRequested = false;
+
+ mHintAnimator = ObjectAnimator.ofFloat(mLockScreenFp, "progress", 1f, 0f, 1f);
+ mHintAnimator.setDuration(4000);
}
@Override
@@ -89,10 +124,27 @@ public class UdfpsKeyguardView extends UdfpsAnimationView {
@Override
public boolean dozeTimeTick() {
- mFingerprintDrawable.dozeTimeTick();
+ updateBurnInOffsets();
return true;
}
+ private void updateBurnInOffsets() {
+ mBurnInOffsetX = MathUtils.lerp(0f,
+ getBurnInOffset(mMaxBurnInOffsetX * 2, true /* xAxis */)
+ - mMaxBurnInOffsetX, mInterpolatedDarkAmount);
+ mBurnInOffsetY = MathUtils.lerp(0f,
+ getBurnInOffset(mMaxBurnInOffsetY * 2, false /* xAxis */)
+ - mMaxBurnInOffsetY, mInterpolatedDarkAmount);
+ mBurnInProgress = MathUtils.lerp(0f, getBurnInProgressOffset(), mInterpolatedDarkAmount);
+
+ mAodFp.setTranslationX(mBurnInOffsetX);
+ mAodFp.setTranslationY(mBurnInOffsetY);
+ mAodFp.setProgress(mBurnInProgress);
+
+ mLockScreenFp.setTranslationX(mBurnInOffsetX);
+ mLockScreenFp.setTranslationY(mBurnInOffsetY);
+ }
+
void requestUdfps(boolean request, int color) {
if (request) {
mUdfpsRequestedColor = color;
@@ -105,22 +157,31 @@ public class UdfpsKeyguardView extends UdfpsAnimationView {
void setStatusBarState(int statusBarState) {
mStatusBarState = statusBarState;
- updateColor();
}
void updateColor() {
- mFingerprintView.setAlpha(1f);
- mFingerprintDrawable.setLockScreenColor(getColor());
+ mLockScreenFp.invalidate();
+ }
+
+ private boolean showingUdfpsBouncer() {
+ return mBgProtection.getVisibility() == View.VISIBLE;
}
+
private int getColor() {
- if (mUdfpsRequested && mUdfpsRequestedColor != -1) {
+ if (isUdfpsColorRequested()) {
return mUdfpsRequestedColor;
+ } else if (showingUdfpsBouncer()) {
+ return mUdfpsBouncerColor;
} else {
return mWallpaperTextColor;
}
}
+ private boolean isUdfpsColorRequested() {
+ return mUdfpsRequested && mUdfpsRequestedColor != -1;
+ }
+
/**
* @param alpha between 0 and 255
*/
@@ -130,6 +191,13 @@ public class UdfpsKeyguardView extends UdfpsAnimationView {
}
@Override
+ protected int updateAlpha() {
+ int alpha = super.updateAlpha();
+ mLockScreenFp.setImageAlpha(alpha);
+ return alpha;
+ }
+
+ @Override
int calculateAlpha() {
if (mPauseAuth) {
return 0;
@@ -138,18 +206,31 @@ public class UdfpsKeyguardView extends UdfpsAnimationView {
}
void onDozeAmountChanged(float linear, float eased) {
- mFingerprintDrawable.onDozeAmountChanged(linear, eased);
+ mHintAnimator.cancel();
+ mInterpolatedDarkAmount = eased;
+ updateBurnInOffsets();
+ mLockScreenFp.setProgress(1f - mInterpolatedDarkAmount);
+ mAodFp.setAlpha(mInterpolatedDarkAmount);
+
+ if (linear == 1f) {
+ mLockScreenFp.setVisibility(View.INVISIBLE);
+ } else {
+ mLockScreenFp.setVisibility(View.VISIBLE);
+ }
}
void animateHint() {
- mFingerprintDrawable.animateHint();
+ if (!isShadeLocked() && !mUdfpsRequested && mAlpha == 255
+ && mLockScreenFp.isVisibleToUser()) {
+ mHintAnimator.start();
+ }
}
/**
* Animates in the bg protection circle behind the fp icon to highlight the icon.
*/
void animateUdfpsBouncer(Runnable onEndAnimation) {
- if (mBgProtection.getVisibility() == View.VISIBLE && mBgProtection.getAlpha() == 1f) {
+ if (showingUdfpsBouncer() && mBgProtection.getAlpha() == 1f) {
// already fully highlighted, don't re-animate
return;
}
@@ -157,19 +238,6 @@ public class UdfpsKeyguardView extends UdfpsAnimationView {
if (mAnimatorSet != null) {
mAnimatorSet.cancel();
}
- ValueAnimator fpIconAnim;
- if (isShadeLocked()) {
- // set color and fade in since we weren't showing before
- mFingerprintDrawable.setLockScreenColor(mTextColorPrimary);
- fpIconAnim = ObjectAnimator.ofFloat(mFingerprintView, View.ALPHA, 0f, 1f);
- } else {
- // update icon color
- fpIconAnim = new ValueAnimator();
- fpIconAnim.setIntValues(getColor(), mTextColorPrimary);
- fpIconAnim.setEvaluator(new ArgbEvaluator());
- fpIconAnim.addUpdateListener(valueAnimator -> mFingerprintDrawable.setLockScreenColor(
- (Integer) valueAnimator.getAnimatedValue()));
- }
mAnimatorSet = new AnimatorSet();
mAnimatorSet.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
@@ -181,11 +249,31 @@ public class UdfpsKeyguardView extends UdfpsAnimationView {
}
});
+ ValueAnimator fpIconColorAnim;
+ if (isShadeLocked()) {
+ // set color and fade in since we weren't showing before
+ mUdfpsBouncerColor = mTextColorPrimary;
+ fpIconColorAnim = ValueAnimator.ofInt(0, 255);
+ fpIconColorAnim.addUpdateListener(valueAnimator ->
+ mLockScreenFp.setImageAlpha((int) valueAnimator.getAnimatedValue()));
+ } else {
+ // update icon color
+ fpIconColorAnim = new ValueAnimator();
+ fpIconColorAnim.setIntValues(
+ isUdfpsColorRequested() ? mUdfpsRequestedColor : mWallpaperTextColor,
+ mTextColorPrimary);
+ fpIconColorAnim.setEvaluator(ArgbEvaluator.getInstance());
+ fpIconColorAnim.addUpdateListener(valueAnimator -> {
+ mUdfpsBouncerColor = (int) valueAnimator.getAnimatedValue();
+ updateColor();
+ });
+ }
+
mAnimatorSet.playTogether(
ObjectAnimator.ofFloat(mBgProtection, View.ALPHA, 0f, 1f),
ObjectAnimator.ofFloat(mBgProtection, View.SCALE_X, 0f, 1f),
ObjectAnimator.ofFloat(mBgProtection, View.SCALE_Y, 0f, 1f),
- fpIconAnim);
+ fpIconColorAnim);
mAnimatorSet.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
@@ -197,15 +285,11 @@ public class UdfpsKeyguardView extends UdfpsAnimationView {
mAnimatorSet.start();
}
- private boolean isShadeLocked() {
- return mStatusBarState == StatusBarState.SHADE_LOCKED;
- }
-
/**
* Animates out the bg protection circle behind the fp icon to unhighlight the icon.
*/
void animateAwayUdfpsBouncer(@Nullable Runnable onEndAnimation) {
- if (mBgProtection.getVisibility() == View.GONE) {
+ if (!showingUdfpsBouncer()) {
// already hidden
return;
}
@@ -213,17 +297,25 @@ public class UdfpsKeyguardView extends UdfpsAnimationView {
if (mAnimatorSet != null) {
mAnimatorSet.cancel();
}
- ValueAnimator fpIconAnim;
+
+ ValueAnimator fpIconColorAnim;
if (isShadeLocked()) {
// fade out
- fpIconAnim = ObjectAnimator.ofFloat(mFingerprintView, View.ALPHA, 1f, 0f);
+ mUdfpsBouncerColor = mTextColorPrimary;
+ fpIconColorAnim = ValueAnimator.ofInt(255, 0);
+ fpIconColorAnim.addUpdateListener(valueAnimator ->
+ mLockScreenFp.setImageAlpha((int) valueAnimator.getAnimatedValue()));
} else {
// update icon color
- fpIconAnim = new ValueAnimator();
- fpIconAnim.setIntValues(mTextColorPrimary, getColor());
- fpIconAnim.setEvaluator(new ArgbEvaluator());
- fpIconAnim.addUpdateListener(valueAnimator -> mFingerprintDrawable.setLockScreenColor(
- (Integer) valueAnimator.getAnimatedValue()));
+ fpIconColorAnim = new ValueAnimator();
+ fpIconColorAnim.setIntValues(
+ mTextColorPrimary,
+ isUdfpsColorRequested() ? mUdfpsRequestedColor : mWallpaperTextColor);
+ fpIconColorAnim.setEvaluator(ArgbEvaluator.getInstance());
+ fpIconColorAnim.addUpdateListener(valueAnimator -> {
+ mUdfpsBouncerColor = (int) valueAnimator.getAnimatedValue();
+ updateColor();
+ });
}
mAnimatorSet = new AnimatorSet();
@@ -231,7 +323,7 @@ public class UdfpsKeyguardView extends UdfpsAnimationView {
ObjectAnimator.ofFloat(mBgProtection, View.ALPHA, 1f, 0f),
ObjectAnimator.ofFloat(mBgProtection, View.SCALE_X, 1f, 0f),
ObjectAnimator.ofFloat(mBgProtection, View.SCALE_Y, 1f, 0f),
- fpIconAnim);
+ fpIconColorAnim);
mAnimatorSet.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
mAnimatorSet.setDuration(500);
@@ -244,10 +336,15 @@ public class UdfpsKeyguardView extends UdfpsAnimationView {
}
}
});
+
mAnimatorSet.start();
}
boolean isAnimating() {
return mAnimatorSet != null && mAnimatorSet.isRunning();
}
+
+ private boolean isShadeLocked() {
+ return mStatusBarState == StatusBarState.SHADE_LOCKED;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java
index 35ca470df523..819de538c840 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java
@@ -316,6 +316,14 @@ public class UdfpsKeyguardViewController extends UdfpsAnimationViewController<Ud
}
}
+ public void onBiometricError(int msgId, String errString,
+ 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) {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsSurfaceView.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsSurfaceView.java
index f8be35ab6cd8..77fad35d32d4 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsSurfaceView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsSurfaceView.java
@@ -23,43 +23,36 @@ import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.RectF;
-import android.os.Build;
-import android.os.UserHandle;
-import android.provider.Settings;
import android.util.AttributeSet;
import android.util.Log;
import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
-import com.android.systemui.biometrics.UdfpsHbmTypes.HbmType;
-
/**
- * Under-display fingerprint sensor Surface View. The surface should be used for HBM-specific things
- * only. All other animations should be done on the other view.
+ * Surface View for providing the Global High-Brightness Mode (GHBM) illumination for UDFPS.
*/
-public class UdfpsSurfaceView extends SurfaceView implements UdfpsIlluminator {
+public class UdfpsSurfaceView extends SurfaceView implements SurfaceHolder.Callback {
private static final String TAG = "UdfpsSurfaceView";
- private static final String SETTING_HBM_TYPE =
- "com.android.systemui.biometrics.UdfpsSurfaceView.hbmType";
- private static final @HbmType int DEFAULT_HBM_TYPE = UdfpsHbmTypes.GLOBAL_HBM;
/**
- * This is used instead of {@link android.graphics.drawable.Drawable}, because the latter has
- * several abstract methods that are not used here but require implementation.
+ * Notifies {@link UdfpsView} when to enable GHBM illumination.
*/
- private interface SimpleDrawable {
- void draw(Canvas canvas);
+ interface GhbmIlluminationListener {
+ /**
+ * @param surface the surface for which GHBM should be enabled.
+ * @param onIlluminatedRunnable a runnable that should be run after GHBM is enabled.
+ */
+ void enableGhbm(@NonNull Surface surface, @Nullable Runnable onIlluminatedRunnable);
}
@NonNull private final SurfaceHolder mHolder;
@NonNull private final Paint mSensorPaint;
- @NonNull private final SimpleDrawable mIlluminationDotDrawable;
- private final int mOnIlluminatedDelayMs;
- private final @HbmType int mHbmType;
- @NonNull private RectF mSensorRect;
- @Nullable private UdfpsHbmProvider mHbmProvider;
+ @Nullable private GhbmIlluminationListener mGhbmIlluminationListener;
+ @Nullable private Runnable mOnIlluminatedRunnable;
+ boolean mAwaitingSurfaceToStartIllumination;
+ boolean mHasValidSurface;
public UdfpsSurfaceView(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -71,82 +64,77 @@ public class UdfpsSurfaceView extends SurfaceView implements UdfpsIlluminator {
setZOrderOnTop(true);
mHolder = getHolder();
+ mHolder.addCallback(this);
mHolder.setFormat(PixelFormat.RGBA_8888);
- mSensorRect = new RectF();
mSensorPaint = new Paint(0 /* flags */);
mSensorPaint.setAntiAlias(true);
mSensorPaint.setARGB(255, 255, 255, 255);
mSensorPaint.setStyle(Paint.Style.FILL);
+ }
- mIlluminationDotDrawable = canvas -> {
- canvas.drawOval(mSensorRect, mSensorPaint);
- };
-
- mOnIlluminatedDelayMs = mContext.getResources().getInteger(
- com.android.internal.R.integer.config_udfps_illumination_transition_ms);
-
- if (Build.IS_ENG || Build.IS_USERDEBUG) {
- mHbmType = Settings.Secure.getIntForUser(mContext.getContentResolver(),
- SETTING_HBM_TYPE, DEFAULT_HBM_TYPE, UserHandle.USER_CURRENT);
- } else {
- mHbmType = DEFAULT_HBM_TYPE;
+ @Override public void surfaceCreated(SurfaceHolder holder) {
+ mHasValidSurface = true;
+ if (mAwaitingSurfaceToStartIllumination) {
+ doIlluminate(mOnIlluminatedRunnable);
+ mOnIlluminatedRunnable = null;
+ mAwaitingSurfaceToStartIllumination = false;
}
}
@Override
- public void setHbmProvider(@Nullable UdfpsHbmProvider hbmProvider) {
- mHbmProvider = hbmProvider;
+ public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
+ // Unused.
}
- @Override
- public void startIllumination(@Nullable Runnable onIlluminatedRunnable) {
- if (mHbmProvider != null) {
- final Surface surface =
- (mHbmType == UdfpsHbmTypes.GLOBAL_HBM) ? mHolder.getSurface() : null;
-
- final Runnable onHbmEnabled = () -> {
- if (mHbmType == UdfpsHbmTypes.GLOBAL_HBM) {
- drawImmediately(mIlluminationDotDrawable);
- }
- if (onIlluminatedRunnable != null) {
- // No framework API can reliably tell when a frame reaches the panel. A timeout
- // is the safest solution.
- postDelayed(onIlluminatedRunnable, mOnIlluminatedDelayMs);
- } else {
- Log.w(TAG, "startIllumination | onIlluminatedRunnable is null");
- }
- };
-
- mHbmProvider.enableHbm(mHbmType, surface, onHbmEnabled);
- } else {
- Log.e(TAG, "startIllumination | mHbmProvider is null");
- }
+ @Override public void surfaceDestroyed(SurfaceHolder holder) {
+ mHasValidSurface = false;
}
- @Override
- public void stopIllumination() {
- if (mHbmProvider != null) {
- final Runnable onHbmDisabled =
- (mHbmType == UdfpsHbmTypes.GLOBAL_HBM) ? this::invalidate : null;
- mHbmProvider.disableHbm(onHbmDisabled);
+ void setGhbmIlluminationListener(@Nullable GhbmIlluminationListener listener) {
+ mGhbmIlluminationListener = listener;
+ }
+
+ /**
+ * Note: there is no corresponding method to stop GHBM illumination. It is expected that
+ * {@link UdfpsView} will hide this view, which would destroy the surface and remove the
+ * illumination dot.
+ */
+ void startGhbmIllumination(@Nullable Runnable onIlluminatedRunnable) {
+ if (mGhbmIlluminationListener == null) {
+ Log.e(TAG, "startIllumination | mGhbmIlluminationListener is null");
+ return;
+ }
+
+ if (mHasValidSurface) {
+ doIlluminate(onIlluminatedRunnable);
} else {
- Log.e(TAG, "stopIllumination | mHbmProvider is null");
+ mAwaitingSurfaceToStartIllumination = true;
+ mOnIlluminatedRunnable = onIlluminatedRunnable;
}
}
- void onSensorRectUpdated(@NonNull RectF sensorRect) {
- mSensorRect = sensorRect;
+ private void doIlluminate(@Nullable Runnable onIlluminatedRunnable) {
+ if (mGhbmIlluminationListener == null) {
+ Log.e(TAG, "doIlluminate | mGhbmIlluminationListener is null");
+ return;
+ }
+
+ mGhbmIlluminationListener.enableGhbm(mHolder.getSurface(), onIlluminatedRunnable);
}
/**
- * Immediately draws the provided drawable on this SurfaceView's surface.
+ * Immediately draws the illumination dot on this SurfaceView's surface.
*/
- private void drawImmediately(@NonNull SimpleDrawable drawable) {
+ void drawIlluminationDot(@NonNull RectF sensorRect) {
+ if (!mHasValidSurface) {
+ Log.e(TAG, "drawIlluminationDot | the surface is destroyed or was never created.");
+ return;
+ }
Canvas canvas = null;
try {
canvas = mHolder.lockCanvas();
- drawable.draw(canvas);
+ canvas.drawOval(sensorRect, mSensorPaint);
} finally {
// Make sure the surface is never left in a bad state.
if (canvas != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.java
index 5e5584cf30ea..15f77ffc08fd 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.java
@@ -26,14 +26,19 @@ import android.graphics.Paint;
import android.graphics.PointF;
import android.graphics.RectF;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
+import android.os.Build;
+import android.os.UserHandle;
+import android.provider.Settings;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
+import android.view.Surface;
import android.view.View;
import android.widget.FrameLayout;
import com.android.systemui.R;
+import com.android.systemui.biometrics.UdfpsHbmTypes.HbmType;
import com.android.systemui.doze.DozeReceiver;
/**
@@ -43,18 +48,25 @@ import com.android.systemui.doze.DozeReceiver;
public class UdfpsView extends FrameLayout implements DozeReceiver, UdfpsIlluminator {
private static final String TAG = "UdfpsView";
+ private static final String SETTING_HBM_TYPE =
+ "com.android.systemui.biometrics.UdfpsSurfaceView.hbmType";
+ private static final @HbmType int DEFAULT_HBM_TYPE = UdfpsHbmTypes.LOCAL_HBM;
+
private static final int DEBUG_TEXT_SIZE_PX = 32;
@NonNull private final RectF mSensorRect;
@NonNull private final Paint mDebugTextPaint;
+ private final float mSensorTouchAreaCoefficient;
+ private final int mOnIlluminatedDelayMs;
+ private final @HbmType int mHbmType;
- @NonNull private UdfpsSurfaceView mHbmSurfaceView;
+ // Only used for UdfpsHbmTypes.GLOBAL_HBM.
+ @Nullable private UdfpsSurfaceView mGhbmView;
+ // Can be different for enrollment, BiometricPrompt, Keyguard, etc.
@Nullable private UdfpsAnimationViewController mAnimationViewController;
-
// Used to obtain the sensor location.
@NonNull private FingerprintSensorPropertiesInternal mSensorProps;
-
- private final float mSensorTouchAreaCoefficient;
+ @Nullable private UdfpsHbmProvider mHbmProvider;
@Nullable private String mDebugMessage;
private boolean mIlluminationRequested;
@@ -81,7 +93,15 @@ public class UdfpsView extends FrameLayout implements DozeReceiver, UdfpsIllumin
mDebugTextPaint.setColor(Color.BLUE);
mDebugTextPaint.setTextSize(DEBUG_TEXT_SIZE_PX);
- mIlluminationRequested = false;
+ mOnIlluminatedDelayMs = mContext.getResources().getInteger(
+ com.android.internal.R.integer.config_udfps_illumination_transition_ms);
+
+ if (Build.IS_ENG || Build.IS_USERDEBUG) {
+ mHbmType = Settings.Secure.getIntForUser(mContext.getContentResolver(),
+ SETTING_HBM_TYPE, DEFAULT_HBM_TYPE, UserHandle.USER_CURRENT);
+ } else {
+ mHbmType = DEFAULT_HBM_TYPE;
+ }
}
// Don't propagate any touch events to the child views.
@@ -93,7 +113,9 @@ public class UdfpsView extends FrameLayout implements DozeReceiver, UdfpsIllumin
@Override
protected void onFinishInflate() {
- mHbmSurfaceView = findViewById(R.id.hbm_view);
+ if (mHbmType == UdfpsHbmTypes.GLOBAL_HBM) {
+ mGhbmView = findViewById(R.id.hbm_view);
+ }
}
void setSensorProperties(@NonNull FingerprintSensorPropertiesInternal properties) {
@@ -102,7 +124,7 @@ public class UdfpsView extends FrameLayout implements DozeReceiver, UdfpsIllumin
@Override
public void setHbmProvider(@Nullable UdfpsHbmProvider hbmProvider) {
- mHbmSurfaceView.setHbmProvider(hbmProvider);
+ mHbmProvider = hbmProvider;
}
@Override
@@ -125,7 +147,6 @@ public class UdfpsView extends FrameLayout implements DozeReceiver, UdfpsIllumin
2 * mSensorProps.sensorRadius + paddingX,
2 * mSensorProps.sensorRadius + paddingY);
- mHbmSurfaceView.onSensorRectUpdated(new RectF(mSensorRect));
if (mAnimationViewController != null) {
mAnimationViewController.onSensorRectUpdated(new RectF(mSensorRect));
}
@@ -204,8 +225,32 @@ public class UdfpsView extends FrameLayout implements DozeReceiver, UdfpsIllumin
if (mAnimationViewController != null) {
mAnimationViewController.onIlluminationStarting();
}
- mHbmSurfaceView.setVisibility(View.VISIBLE);
- mHbmSurfaceView.startIllumination(onIlluminatedRunnable);
+
+ if (mGhbmView != null) {
+ mGhbmView.setGhbmIlluminationListener(this::doIlluminate);
+ mGhbmView.setVisibility(View.VISIBLE);
+ mGhbmView.startGhbmIllumination(onIlluminatedRunnable);
+ } else {
+ doIlluminate(null /* surface */, onIlluminatedRunnable);
+ }
+ }
+
+ private void doIlluminate(@Nullable Surface surface, @Nullable Runnable onIlluminatedRunnable) {
+ if (mGhbmView != null && surface == null) {
+ Log.e(TAG, "doIlluminate | surface must be non-null for GHBM");
+ }
+ mHbmProvider.enableHbm(mHbmType, surface, () -> {
+ if (mGhbmView != null) {
+ mGhbmView.drawIlluminationDot(mSensorRect);
+ }
+ if (onIlluminatedRunnable != null) {
+ // No framework API can reliably tell when a frame reaches the panel. A timeout
+ // is the safest solution.
+ postDelayed(onIlluminatedRunnable, mOnIlluminatedDelayMs);
+ } else {
+ Log.w(TAG, "doIlluminate | onIlluminatedRunnable is null");
+ }
+ });
}
@Override
@@ -214,7 +259,10 @@ public class UdfpsView extends FrameLayout implements DozeReceiver, UdfpsIllumin
if (mAnimationViewController != null) {
mAnimationViewController.onIlluminationStopped();
}
- mHbmSurfaceView.setVisibility(View.INVISIBLE);
- mHbmSurfaceView.stopIllumination();
+ if (mGhbmView != null) {
+ mGhbmView.setGhbmIlluminationListener(null);
+ mGhbmView.setVisibility(View.INVISIBLE);
+ }
+ mHbmProvider.disableHbm(null /* onHbmDisabled */);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java b/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java
index 020401ecd2f8..809e7a70d66c 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java
@@ -25,6 +25,7 @@ import android.net.Uri;
import android.os.Build;
import android.util.IndentingPrintWriter;
import android.util.Log;
+import android.view.accessibility.AccessibilityManager;
import androidx.annotation.NonNull;
@@ -70,6 +71,7 @@ public class BrightLineFalsingManager implements FalsingManager {
private final DoubleTapClassifier mDoubleTapClassifier;
private final HistoryTracker mHistoryTracker;
private final KeyguardStateController mKeyguardStateController;
+ private AccessibilityManager mAccessibilityManager;
private final boolean mTestHarness;
private final MetricsLogger mMetricsLogger;
private int mIsFalseTouchCalls;
@@ -175,6 +177,7 @@ public class BrightLineFalsingManager implements FalsingManager {
@Named(BRIGHT_LINE_GESTURE_CLASSIFERS) Set<FalsingClassifier> classifiers,
SingleTapClassifier singleTapClassifier, DoubleTapClassifier doubleTapClassifier,
HistoryTracker historyTracker, KeyguardStateController keyguardStateController,
+ AccessibilityManager accessibilityManager,
@TestHarness boolean testHarness) {
mDataProvider = falsingDataProvider;
mDockManager = dockManager;
@@ -184,6 +187,7 @@ public class BrightLineFalsingManager implements FalsingManager {
mDoubleTapClassifier = doubleTapClassifier;
mHistoryTracker = historyTracker;
mKeyguardStateController = keyguardStateController;
+ mAccessibilityManager = accessibilityManager;
mTestHarness = testHarness;
mDataProvider.addSessionListener(mSessionListener);
@@ -328,7 +332,8 @@ public class BrightLineFalsingManager implements FalsingManager {
|| !mKeyguardStateController.isShowing()
|| mTestHarness
|| mDataProvider.isJustUnlockedWithFace()
- || mDockManager.isDocked();
+ || mDockManager.isDocked()
+ || mAccessibilityManager.isEnabled();
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/Classifier.java b/packages/SystemUI/src/com/android/systemui/classifier/Classifier.java
index ffdcff2ef2b0..f18413be0134 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/Classifier.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/Classifier.java
@@ -41,7 +41,7 @@ public abstract class Classifier {
public static final int SHADE_DRAG = 11;
public static final int QS_COLLAPSE = 12;
public static final int UDFPS_AUTHENTICATION = 13;
- public static final int DISABLED_UDFPS_AFFORDANCE = 14;
+ public static final int LOCK_ICON = 14;
public static final int QS_SWIPE = 15;
public static final int BACK_GESTURE = 16;
@@ -61,7 +61,7 @@ public abstract class Classifier {
QS_COLLAPSE,
BRIGHTNESS_SLIDER,
UDFPS_AUTHENTICATION,
- DISABLED_UDFPS_AFFORDANCE,
+ LOCK_ICON,
QS_SWIPE,
BACK_GESTURE
})
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/DistanceClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/DistanceClassifier.java
index 229801074246..d0fe1c37d4fa 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/DistanceClassifier.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/DistanceClassifier.java
@@ -155,7 +155,7 @@ class DistanceClassifier extends FalsingClassifier {
|| interactionType == SHADE_DRAG
|| interactionType == QS_COLLAPSE
|| interactionType == Classifier.UDFPS_AUTHENTICATION
- || interactionType == Classifier.DISABLED_UDFPS_AFFORDANCE
+ || interactionType == Classifier.LOCK_ICON
|| interactionType == Classifier.QS_SWIPE) {
return Result.passed(0);
}
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/TypeClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/TypeClassifier.java
index c2ad7e6387d6..f040712706cd 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/TypeClassifier.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/TypeClassifier.java
@@ -46,7 +46,7 @@ public class TypeClassifier extends FalsingClassifier {
@Classifier.InteractionType int interactionType,
double historyBelief, double historyConfidence) {
if (interactionType == Classifier.UDFPS_AUTHENTICATION
- || interactionType == Classifier.DISABLED_UDFPS_AFFORDANCE) {
+ || interactionType == Classifier.LOCK_ICON) {
return Result.passed(0);
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java b/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java
index d85c9a718871..c97a30e6e13e 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java
@@ -77,6 +77,7 @@ import com.android.systemui.shared.system.TaskStackChangeListeners;
import com.android.systemui.shared.system.WindowManagerWrapper;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
+import com.android.systemui.statusbar.NotificationShadeDepthController;
import com.android.systemui.statusbar.phone.AutoHideController;
import com.android.systemui.statusbar.phone.ConfigurationControllerImpl;
import com.android.systemui.statusbar.phone.ShadeController;
@@ -227,6 +228,7 @@ public class DependencyProvider {
Lazy<StatusBar> statusBarLazy,
ShadeController shadeController,
NotificationRemoteInputManager notificationRemoteInputManager,
+ NotificationShadeDepthController notificationShadeDepthController,
SystemActions systemActions,
@Main Handler mainHandler,
UiEventLogger uiEventLogger,
@@ -253,6 +255,7 @@ public class DependencyProvider {
statusBarLazy,
shadeController,
notificationRemoteInputManager,
+ notificationShadeDepthController,
systemActions,
mainHandler,
uiEventLogger,
diff --git a/packages/SystemUI/src/com/android/systemui/doze/AlwaysOnDisplayPolicy.java b/packages/SystemUI/src/com/android/systemui/doze/AlwaysOnDisplayPolicy.java
index 60ee806d0a9f..735b3cd4b824 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/AlwaysOnDisplayPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/AlwaysOnDisplayPolicy.java
@@ -60,6 +60,13 @@ public class AlwaysOnDisplayPolicy {
public int defaultDozeBrightness;
/**
+ * Integer used to dim the screen just before the screen turns off.
+ *
+ * @see R.integer.config_screenBrightnessDim
+ */
+ public int dimBrightness;
+
+ /**
* Integer array to map ambient brightness type to real screen brightness.
*
* @see Settings.Global#ALWAYS_ON_DISPLAY_CONSTANTS
@@ -175,6 +182,8 @@ public class AlwaysOnDisplayPolicy {
DEFAULT_WALLPAPER_VISIBILITY_MS);
defaultDozeBrightness = resources.getInteger(
com.android.internal.R.integer.config_screenBrightnessDoze);
+ dimBrightness = resources.getInteger(
+ com.android.internal.R.integer.config_screenBrightnessDim);
screenBrightnessArray = mParser.getIntArray(KEY_SCREEN_BRIGHTNESS_ARRAY,
resources.getIntArray(
R.array.config_doze_brightness_sensor_to_brightness));
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java
index 92494cf5b546..470d2f364c1c 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java
@@ -24,6 +24,7 @@ import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.os.Handler;
+import android.os.PowerManager;
import android.os.SystemProperties;
import android.os.Trace;
import android.os.UserHandle;
@@ -33,6 +34,8 @@ import android.view.Display;
import com.android.systemui.doze.dagger.BrightnessSensor;
import com.android.systemui.doze.dagger.DozeScope;
import com.android.systemui.doze.dagger.WrappedService;
+import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.statusbar.phone.DozeParameters;
import com.android.systemui.util.sensors.AsyncSensorManager;
import java.io.PrintWriter;
@@ -58,8 +61,11 @@ public class DozeScreenBrightness extends BroadcastReceiver implements DozeMachi
private final Handler mHandler;
private final SensorManager mSensorManager;
private final Optional<Sensor> mLightSensorOptional;
+ private final WakefulnessLifecycle mWakefulnessLifecycle;
+ private final DozeParameters mDozeParameters;
private final int[] mSensorToBrightness;
private final int[] mSensorToScrimOpacity;
+ private final int mScreenBrightnessDim;
private boolean mRegistered;
private int mDefaultDozeBrightness;
@@ -79,15 +85,20 @@ public class DozeScreenBrightness extends BroadcastReceiver implements DozeMachi
public DozeScreenBrightness(Context context, @WrappedService DozeMachine.Service service,
AsyncSensorManager sensorManager,
@BrightnessSensor Optional<Sensor> lightSensorOptional, DozeHost host, Handler handler,
- AlwaysOnDisplayPolicy alwaysOnDisplayPolicy) {
+ AlwaysOnDisplayPolicy alwaysOnDisplayPolicy,
+ WakefulnessLifecycle wakefulnessLifecycle,
+ DozeParameters dozeParameters) {
mContext = context;
mDozeService = service;
mSensorManager = sensorManager;
mLightSensorOptional = lightSensorOptional;
+ mWakefulnessLifecycle = wakefulnessLifecycle;
+ mDozeParameters = dozeParameters;
mDozeHost = host;
mHandler = handler;
mDefaultDozeBrightness = alwaysOnDisplayPolicy.defaultDozeBrightness;
+ mScreenBrightnessDim = alwaysOnDisplayPolicy.dimBrightness;
mSensorToBrightness = alwaysOnDisplayPolicy.screenBrightnessArray;
mSensorToScrimOpacity = alwaysOnDisplayPolicy.dimmingScrimArray;
}
@@ -178,7 +189,9 @@ public class DozeScreenBrightness extends BroadcastReceiver implements DozeMachi
}
private void resetBrightnessToDefault() {
- mDozeService.setDozeScreenBrightness(clampToUserSetting(mDefaultDozeBrightness));
+ mDozeService.setDozeScreenBrightness(
+ clampToDimBrightnessForScreenOff(
+ clampToUserSetting(mDefaultDozeBrightness)));
mDozeHost.setAodDimmingScrim(0f);
}
//TODO: brightnessfloat change usages to float.
@@ -189,6 +202,21 @@ public class DozeScreenBrightness extends BroadcastReceiver implements DozeMachi
return Math.min(brightness, userSetting);
}
+ /**
+ * Clamp the brightness to the dim brightness value used by PowerManagerService just before the
+ * device times out and goes to sleep, if we are sleeping from a timeout. This ensures that we
+ * don't raise the brightness back to the user setting before playing the screen off animation.
+ */
+ private int clampToDimBrightnessForScreenOff(int brightness) {
+ if (mDozeParameters.shouldControlUnlockedScreenOff()
+ && mWakefulnessLifecycle.getLastSleepReason()
+ == PowerManager.GO_TO_SLEEP_REASON_TIMEOUT) {
+ return Math.min(mScreenBrightnessDim, brightness);
+ } else {
+ return brightness;
+ }
+ }
+
private void setLightSensorEnabled(boolean enabled) {
if (enabled && !mRegistered && mLightSensorOptional.isPresent()) {
// Wait until we get an event from the sensor until indicating ready.
diff --git a/packages/SystemUI/src/com/android/systemui/doze/util/BurnInHelper.kt b/packages/SystemUI/src/com/android/systemui/doze/util/BurnInHelper.kt
index 73abf4519f73..15e3f3a6b1e9 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/util/BurnInHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/doze/util/BurnInHelper.kt
@@ -22,6 +22,7 @@ private const val MILLIS_PER_MINUTES = 1000 * 60f
private const val BURN_IN_PREVENTION_PERIOD_Y = 521f
private const val BURN_IN_PREVENTION_PERIOD_X = 83f
private const val BURN_IN_PREVENTION_PERIOD_SCALE = 180f
+private const val BURN_IN_PREVENTION_PERIOD_PROGRESS = 120f
/**
* Returns the translation offset that should be used to avoid burn in at
@@ -37,6 +38,15 @@ fun getBurnInOffset(amplitude: Int, xAxis: Boolean): Int {
}
/**
+ * Returns a progress offset (between 0f and 1.0f) that should be used to avoid burn in at
+ * the current time.
+ */
+fun getBurnInProgressOffset(): Float {
+ return zigzag(System.currentTimeMillis() / MILLIS_PER_MINUTES,
+ 1f, BURN_IN_PREVENTION_PERIOD_PROGRESS)
+}
+
+/**
* Returns a value to scale a view in order to avoid burn in.
*/
fun getBurnInScale(): Float {
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
index bb44b09f1bce..bc4ced452630 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
@@ -71,7 +71,6 @@ 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.statusbar.NotificationShadeDepthController;
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -103,7 +102,6 @@ public class GlobalActionsDialog extends GlobalActionsDialogLite
private final LockPatternUtils mLockPatternUtils;
private final KeyguardStateController mKeyguardStateController;
- private final NotificationShadeDepthController mDepthController;
private final SysUiState mSysUiState;
private final ActivityStarter mActivityStarter;
private final SysuiColorExtractor mSysuiColorExtractor;
@@ -164,7 +162,6 @@ public class GlobalActionsDialog extends GlobalActionsDialogLite
IActivityManager iActivityManager,
@Nullable TelecomManager telecomManager,
MetricsLogger metricsLogger,
- NotificationShadeDepthController depthController,
SysuiColorExtractor colorExtractor,
IStatusBarService statusBarService,
NotificationShadeWindowController notificationShadeWindowController,
@@ -196,7 +193,6 @@ public class GlobalActionsDialog extends GlobalActionsDialogLite
iActivityManager,
telecomManager,
metricsLogger,
- depthController,
colorExtractor,
statusBarService,
notificationShadeWindowController,
@@ -212,7 +208,6 @@ public class GlobalActionsDialog extends GlobalActionsDialogLite
mLockPatternUtils = lockPatternUtils;
mKeyguardStateController = keyguardStateController;
- mDepthController = depthController;
mSysuiColorExtractor = colorExtractor;
mStatusBarService = statusBarService;
mNotificationShadeWindowController = notificationShadeWindowController;
@@ -267,9 +262,8 @@ public class GlobalActionsDialog extends GlobalActionsDialogLite
protected ActionsDialogLite createDialog() {
initDialogItems();
- mDepthController.setShowingHomeControls(true);
ActionsDialog dialog = new ActionsDialog(getContext(), mAdapter, mOverflowAdapter,
- this::getWalletViewController, mDepthController, mSysuiColorExtractor,
+ this::getWalletViewController, mSysuiColorExtractor,
mStatusBarService, mNotificationShadeWindowController,
mSysUiState, this::onRotate, isKeyguardShowing(), mPowerAdapter, getEventLogger(),
getStatusBar());
@@ -336,16 +330,15 @@ public class GlobalActionsDialog extends GlobalActionsDialogLite
ActionsDialog(Context context, MyAdapter adapter, MyOverflowAdapter overflowAdapter,
Provider<GlobalActionsPanelPlugin.PanelViewController> walletFactory,
- NotificationShadeDepthController depthController,
SysuiColorExtractor sysuiColorExtractor, IStatusBarService statusBarService,
NotificationShadeWindowController notificationShadeWindowController,
SysUiState sysuiState, Runnable onRotateCallback, boolean keyguardShowing,
MyPowerOptionsAdapter powerAdapter, UiEventLogger uiEventLogger,
StatusBar statusBar) {
super(context, com.android.systemui.R.style.Theme_SystemUI_Dialog_GlobalActions,
- adapter, overflowAdapter, depthController, sysuiColorExtractor,
- statusBarService, notificationShadeWindowController, sysuiState,
- onRotateCallback, keyguardShowing, powerAdapter, uiEventLogger, null,
+ adapter, overflowAdapter, sysuiColorExtractor, statusBarService,
+ notificationShadeWindowController, sysuiState, onRotateCallback,
+ keyguardShowing, powerAdapter, uiEventLogger, null,
statusBar);
mWalletFactory = walletFactory;
@@ -494,8 +487,6 @@ public class GlobalActionsDialog extends GlobalActionsDialogLite
float animatedValue = animation.getAnimatedFraction();
int alpha = (int) (animatedValue * mScrimAlpha * 255);
mBackgroundDrawable.setAlpha(alpha);
- mDepthController.updateGlobalDialogVisibility(animatedValue,
- mGlobalActionsLayout);
});
ObjectAnimator xAnimator =
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
index f30d6b13ad02..5acb3038b91b 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
@@ -119,7 +119,6 @@ import com.android.systemui.model.SysUiState;
import com.android.systemui.plugins.GlobalActions.GlobalActionsManager;
import com.android.systemui.plugins.GlobalActionsPanelPlugin;
import com.android.systemui.scrim.ScrimDrawable;
-import com.android.systemui.statusbar.NotificationShadeDepthController;
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -190,7 +189,6 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene
private final TelecomManager mTelecomManager;
private final MetricsLogger mMetricsLogger;
private final UiEventLogger mUiEventLogger;
- private final NotificationShadeDepthController mDepthController;
private final SysUiState mSysUiState;
private final GlobalActionsInfoProvider mInfoProvider;
@@ -329,7 +327,6 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene
IActivityManager iActivityManager,
@Nullable TelecomManager telecomManager,
MetricsLogger metricsLogger,
- NotificationShadeDepthController depthController,
SysuiColorExtractor colorExtractor,
IStatusBarService statusBarService,
NotificationShadeWindowController notificationShadeWindowController,
@@ -362,7 +359,6 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene
mMetricsLogger = metricsLogger;
mUiEventLogger = uiEventLogger;
mInfoProvider = infoProvider;
- mDepthController = depthController;
mSysuiColorExtractor = colorExtractor;
mStatusBarService = statusBarService;
mNotificationShadeWindowController = notificationShadeWindowController;
@@ -652,11 +648,9 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene
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,
+ mAdapter, mOverflowAdapter, mSysuiColorExtractor,
mStatusBarService, mNotificationShadeWindowController,
mSysUiState, this::onRotate, mKeyguardShowing, mPowerAdapter, mUiEventLogger,
mInfoProvider, mStatusBar);
@@ -2125,7 +2119,6 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene
protected boolean mShowing;
protected float mScrimAlpha;
protected final NotificationShadeWindowController mNotificationShadeWindowController;
- protected final NotificationShadeDepthController mDepthController;
protected final SysUiState mSysUiState;
private ListPopupWindow mOverflowPopup;
private Dialog mPowerOptionsDialog;
@@ -2181,7 +2174,6 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene
ActionsDialogLite(Context context, int themeRes, MyAdapter adapter,
MyOverflowAdapter overflowAdapter,
- NotificationShadeDepthController depthController,
SysuiColorExtractor sysuiColorExtractor, IStatusBarService statusBarService,
NotificationShadeWindowController notificationShadeWindowController,
SysUiState sysuiState, Runnable onRotateCallback, boolean keyguardShowing,
@@ -2192,7 +2184,6 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene
mAdapter = adapter;
mOverflowAdapter = overflowAdapter;
mPowerOptionsAdapter = powerAdapter;
- mDepthController = depthController;
mColorExtractor = sysuiColorExtractor;
mStatusBarService = statusBarService;
mNotificationShadeWindowController = notificationShadeWindowController;
@@ -2409,7 +2400,6 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene
float animatedValue = animation.getAnimatedFraction();
int alpha = (int) (animatedValue * mScrimAlpha * 255);
mBackgroundDrawable.setAlpha(alpha);
- mDepthController.updateGlobalDialogVisibility(animatedValue, mGlobalActionsLayout);
});
ObjectAnimator xAnimator =
@@ -2439,7 +2429,6 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene
float animatedValue = 1f - animation.getAnimatedFraction();
int alpha = (int) (animatedValue * mScrimAlpha * 255);
mBackgroundDrawable.setAlpha(alpha);
- mDepthController.updateGlobalDialogVisibility(animatedValue, mGlobalActionsLayout);
});
float xOffset = mGlobalActionsLayout.getAnimationOffsetX();
@@ -2476,7 +2465,6 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene
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();
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java
index e37d3d586ccc..a641ad4b338b 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java
@@ -30,7 +30,6 @@ import android.widget.ProgressBar;
import android.widget.TextView;
import com.android.internal.R;
-import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.settingslib.Utils;
import com.android.systemui.plugins.GlobalActions;
import com.android.systemui.scrim.ScrimDrawable;
@@ -51,7 +50,6 @@ public class GlobalActionsImpl implements GlobalActions, CommandQueue.Callbacks
private final KeyguardStateController mKeyguardStateController;
private final DeviceProvisionedController mDeviceProvisionedController;
private final BlurUtils mBlurUtils;
- private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
private final CommandQueue mCommandQueue;
private GlobalActionsDialogLite mGlobalActionsDialog;
private boolean mDisabled;
@@ -60,15 +58,13 @@ public class GlobalActionsImpl implements GlobalActions, CommandQueue.Callbacks
public GlobalActionsImpl(Context context, CommandQueue commandQueue,
Lazy<GlobalActionsDialogLite> globalActionsDialogLazy, BlurUtils blurUtils,
KeyguardStateController keyguardStateController,
- DeviceProvisionedController deviceProvisionedController,
- KeyguardUpdateMonitor keyguardUpdateMonitor) {
+ DeviceProvisionedController deviceProvisionedController) {
mContext = context;
mGlobalActionsDialogLazy = globalActionsDialogLazy;
mKeyguardStateController = keyguardStateController;
mDeviceProvisionedController = deviceProvisionedController;
mCommandQueue = commandQueue;
mBlurUtils = blurUtils;
- mKeyguardUpdateMonitor = keyguardUpdateMonitor;
mCommandQueue.addCallback(this);
}
@@ -87,7 +83,6 @@ public class GlobalActionsImpl implements GlobalActions, CommandQueue.Callbacks
mGlobalActionsDialog = mGlobalActionsDialogLazy.get();
mGlobalActionsDialog.showOrHideDialog(mKeyguardStateController.isShowing(),
mDeviceProvisionedController.isDeviceProvisioned());
- mKeyguardUpdateMonitor.requestFaceAuth();
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsInfoProvider.kt b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsInfoProvider.kt
index 17b532a643cd..25837e3aacdf 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsInfoProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsInfoProvider.kt
@@ -24,7 +24,6 @@ import android.service.quickaccesswallet.QuickAccessWalletClient
import android.util.Log
import android.view.LayoutInflater
import android.view.ViewGroup
-import android.widget.ImageView
import android.widget.TextView
import com.android.systemui.R
import com.android.systemui.controls.controller.ControlsController
@@ -76,8 +75,7 @@ class GlobalActionsInfoProvider @Inject constructor(
val message = view.findViewById<TextView>(R.id.global_actions_change_message)
message?.setText(context.getString(R.string.global_actions_change_description, walletTitle))
- val button = view.findViewById<ImageView>(R.id.global_actions_change_button)
- button.setOnClickListener { _ ->
+ view.setOnClickListener { _ ->
dismissParent.run()
activityStarter.postStartActivityDismissingKeyguard(pendingIntent)
}
@@ -119,4 +117,4 @@ class GlobalActionsInfoProvider @Inject constructor(
val count = sharedPrefs.getInt(KEY_VIEW_COUNT, 0)
sharedPrefs.edit().putInt(KEY_VIEW_COUNT, count + 1).apply()
}
-} \ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsPopupMenu.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsPopupMenu.java
index ac4fc62bf1c9..d1a103e3a8fa 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsPopupMenu.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsPopupMenu.java
@@ -22,7 +22,6 @@ import android.content.res.Resources;
import android.util.LayoutDirection;
import android.view.View;
import android.view.View.MeasureSpec;
-import android.view.WindowManager;
import android.widget.AdapterView;
import android.widget.ListAdapter;
import android.widget.ListPopupWindow;
@@ -49,11 +48,9 @@ public class GlobalActionsPopupMenu extends ListPopupWindow {
mContext = context;
Resources res = mContext.getResources();
setBackgroundDrawable(
- res.getDrawable(R.drawable.rounded_bg_full, context.getTheme()));
+ res.getDrawable(R.drawable.global_actions_popup_bg, context.getTheme()));
mIsDropDownMode = isDropDownMode;
- // required to show above the global actions dialog
- setWindowLayoutType(WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY);
setInputMethodMode(INPUT_METHOD_NOT_NEEDED);
setModal(true);
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndication.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndication.java
index 2873cd36409d..9b83b75cec22 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndication.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndication.java
@@ -40,18 +40,22 @@ public class KeyguardIndication {
private final View.OnClickListener mOnClickListener;
@Nullable
private final Drawable mBackground;
+ @Nullable
+ private final Long mMinVisibilityMillis; // in milliseconds
private KeyguardIndication(
CharSequence message,
ColorStateList textColor,
Drawable icon,
View.OnClickListener onClickListener,
- Drawable background) {
+ Drawable background,
+ Long minVisibilityMillis) {
mMessage = message;
mTextColor = textColor;
mIcon = icon;
mOnClickListener = onClickListener;
mBackground = background;
+ mMinVisibilityMillis = minVisibilityMillis;
}
/**
@@ -89,6 +93,14 @@ public class KeyguardIndication {
return mBackground;
}
+ /**
+ * Minimum time to show text in milliseconds.
+ * @return null if unspecified
+ */
+ public @Nullable Long getMinVisibilityMillis() {
+ return mMinVisibilityMillis;
+ }
+
@Override
public String toString() {
String str = "KeyguardIndication{";
@@ -96,6 +108,7 @@ public class KeyguardIndication {
if (mIcon != null) str += " mIcon=" + mIcon;
if (mOnClickListener != null) str += " mOnClickListener=" + mOnClickListener;
if (mBackground != null) str += " mBackground=" + mBackground;
+ if (mMinVisibilityMillis != null) str += " mMinVisibilityMillis=" + mMinVisibilityMillis;
str += "}";
return str;
}
@@ -109,6 +122,7 @@ public class KeyguardIndication {
private View.OnClickListener mOnClickListener;
private ColorStateList mTextColor;
private Drawable mBackground;
+ private Long mMinVisibilityMillis;
public Builder() { }
@@ -155,6 +169,15 @@ public class KeyguardIndication {
}
/**
+ * Optional. Set a required minimum visibility time in milliseconds for the text
+ * to show.
+ */
+ public Builder setMinVisibilityMillis(Long minVisibilityMillis) {
+ this.mMinVisibilityMillis = minVisibilityMillis;
+ return this;
+ }
+
+ /**
* Build the KeyguardIndication.
*/
public KeyguardIndication build() {
@@ -166,7 +189,8 @@ public class KeyguardIndication {
}
return new KeyguardIndication(
- mMessage, mTextColor, mIcon, mOnClickListener, mBackground);
+ mMessage, mTextColor, mIcon, mOnClickListener, mBackground,
+ mMinVisibilityMillis);
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java
index fc5f3b8ae994..2d215e0f1f62 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java
@@ -23,7 +23,6 @@ import android.text.TextUtils;
import androidx.annotation.IntDef;
-import com.android.settingslib.Utils;
import com.android.systemui.Dumpable;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -164,15 +163,14 @@ public class KeyguardIndicationRotateTextViewController extends
* Transient messages:
* - show immediately
* - will continue to be in the rotation of messages shown until hideTransient is called.
- * - can be presented with an "error" color if isError is true
*/
- public void showTransient(CharSequence newIndication, boolean isError) {
+ public void showTransient(CharSequence newIndication) {
+ final long inAnimationDuration = 600L; // see KeyguardIndicationTextView.getYInDuration
updateIndication(INDICATION_TYPE_TRANSIENT,
new KeyguardIndication.Builder()
.setMessage(newIndication)
- .setTextColor(isError
- ? Utils.getColorError(getContext())
- : mInitialTextColorState)
+ .setMinVisibilityMillis(2000L + inAnimationDuration)
+ .setTextColor(mInitialTextColorState)
.build(),
/* showImmediately */true);
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
index 70837385b858..62b92cb33f5c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
@@ -73,7 +73,7 @@ public class KeyguardService extends Service {
"persist.wm.enable_remote_keyguard_animation";
private static final int sEnableRemoteKeyguardAnimation =
- SystemProperties.getInt(ENABLE_REMOTE_KEYGUARD_ANIMATION_PROPERTY, 1);
+ SystemProperties.getInt(ENABLE_REMOTE_KEYGUARD_ANIMATION_PROPERTY, 0);
/**
* @see #ENABLE_REMOTE_KEYGUARD_ANIMATION_PROPERTY
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
index 38d153e38ca9..941f2c6f4282 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
@@ -31,6 +31,7 @@ import com.android.keyguard.KeyguardViewController
import com.android.systemui.animation.Interpolators
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.shared.system.smartspace.SmartspaceTransitionController
+import com.android.systemui.statusbar.FeatureFlags
import com.android.systemui.statusbar.policy.KeyguardStateController
import dagger.Lazy
import javax.inject.Inject
@@ -89,7 +90,8 @@ class KeyguardUnlockAnimationController @Inject constructor(
private val keyguardStateController: KeyguardStateController,
private val keyguardViewMediator: Lazy<KeyguardViewMediator>,
private val keyguardViewController: KeyguardViewController,
- private val smartspaceTransitionController: SmartspaceTransitionController
+ private val smartspaceTransitionController: SmartspaceTransitionController,
+ private val featureFlags: FeatureFlags
) : KeyguardStateController.Callback {
/**
@@ -346,6 +348,10 @@ class KeyguardUnlockAnimationController @Inject constructor(
* keyguard visible.
*/
private fun updateKeyguardViewMediatorIfThresholdsReached() {
+ if (!featureFlags.isNewKeyguardSwipeAnimationEnabled) {
+ return
+ }
+
val dismissAmount = keyguardStateController.dismissAmount
// Hide the keyguard if we're fully dismissed, or if we're swiping to dismiss and have
@@ -382,6 +388,10 @@ class KeyguardUnlockAnimationController @Inject constructor(
* know if it needs to do something as a result.
*/
private fun updateSmartSpaceTransition() {
+ if (!featureFlags.isSmartSpaceSharedElementTransitionEnabled) {
+ return
+ }
+
val dismissAmount = keyguardStateController.dismissAmount
// If we've begun a swipe, and are capable of doing the SmartSpace transition, start it!
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 9f0f3fb1b899..8a9fc2685a71 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -176,7 +176,7 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable,
private static final int KEYGUARD_DISPLAY_TIMEOUT_DELAY_DEFAULT = 30000;
private static final long KEYGUARD_DONE_PENDING_TIMEOUT_MS = 3000;
- private static final boolean DEBUG = true;
+ private static final boolean DEBUG = KeyguardConstants.DEBUG;
private static final boolean DEBUG_SIM_STATES = KeyguardConstants.DEBUG_SIM_STATES;
private final static String TAG = "KeyguardViewMediator";
@@ -1001,12 +1001,6 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable,
mPowerGestureIntercepted = false;
mGoingToSleep = true;
- // Reset keyguard going away state so we can start listening for fingerprint. We
- // explicitly DO NOT want to call
- // mKeyguardViewControllerLazy.get().setKeyguardGoingAwayState(false)
- // here, since that will mess with the device lock state.
- mUpdateMonitor.dispatchKeyguardGoingAway(false);
-
// Lock immediately based on setting if secure (user has a pin/pattern/password).
// This also "locks" the device when not secure to provide easy access to the
// camera while preventing unwanted input.
@@ -1044,7 +1038,15 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable,
playSounds(true);
}
}
+
mUpdateMonitor.dispatchStartedGoingToSleep(offReason);
+
+ // Reset keyguard going away state so we can start listening for fingerprint. We
+ // explicitly DO NOT want to call
+ // mKeyguardViewControllerLazy.get().setKeyguardGoingAwayState(false)
+ // here, since that will mess with the device lock state.
+ mUpdateMonitor.dispatchKeyguardGoingAway(false);
+
notifyStartedGoingToSleep();
}
@@ -1707,8 +1709,8 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable,
* Disable notification shade background blurs until the keyguard is dismissed.
* (Used during app launch animations)
*/
- public void disableBlursUntilHidden() {
- mNotificationShadeDepthController.get().setIgnoreShadeBlurUntilHidden(true);
+ public void setBlursDisabledForAppLaunch(boolean disabled) {
+ mNotificationShadeDepthController.get().setBlursDisabledForAppLaunch(disabled);
}
public boolean isSecure() {
@@ -2191,6 +2193,15 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable,
if (!mHiding
&& !mSurfaceBehindRemoteAnimationRequested
&& !mKeyguardStateController.isFlingingToDismissKeyguardDuringSwipeGesture()) {
+ if (finishedCallback != null) {
+ // There will not execute animation, send a finish callback to ensure the remote
+ // animation won't hanging there.
+ try {
+ finishedCallback.onAnimationFinished();
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Failed to call onAnimationFinished", e);
+ }
+ }
setShowingLocked(mShowing, true /* force */);
return;
}
@@ -2208,12 +2219,6 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable,
mDrawnCallback = null;
}
- // only play "unlock" noises if not on a call (since the incall UI
- // disables the keyguard)
- if (TelephonyManager.EXTRA_STATE_IDLE.equals(mPhoneState)) {
- playSounds(false);
- }
-
LatencyTracker.getInstance(mContext)
.onActionEnd(LatencyTracker.ACTION_LOCKSCREEN_UNLOCK);
@@ -2331,6 +2336,13 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable,
}
private void onKeyguardExitFinished() {
+ // only play "unlock" noises if not on a call (since the incall UI
+ // disables the keyguard)
+ if (TelephonyManager.EXTRA_STATE_IDLE.equals(mPhoneState)) {
+ Log.i("TEST", "playSounds: false");
+ playSounds(false);
+ }
+
setShowingLocked(false);
mWakeAndUnlocking = false;
mDismissCallbackRegistry.notifyDismissSucceeded();
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
index 3957a60c5b45..2facf3dedd18 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
@@ -12,10 +12,12 @@ import android.view.View
import android.view.ViewGroup
import android.widget.LinearLayout
import androidx.annotation.VisibleForTesting
+import com.android.systemui.Dumpable
import com.android.systemui.R
import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.dump.DumpManager
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.qs.PageIndicator
@@ -26,6 +28,9 @@ import com.android.systemui.util.Utils
import com.android.systemui.util.animation.UniqueObjectHostView
import com.android.systemui.util.animation.requiresRemeasuring
import com.android.systemui.util.concurrency.DelayableExecutor
+import com.android.systemui.util.time.SystemClock
+import java.io.FileDescriptor
+import java.io.PrintWriter
import java.util.TreeMap
import javax.inject.Inject
import javax.inject.Provider
@@ -45,12 +50,14 @@ class MediaCarouselController @Inject constructor(
private val visualStabilityManager: VisualStabilityManager,
private val mediaHostStatesManager: MediaHostStatesManager,
private val activityStarter: ActivityStarter,
+ private val systemClock: SystemClock,
@Main executor: DelayableExecutor,
private val mediaManager: MediaDataManager,
configurationController: ConfigurationController,
falsingCollector: FalsingCollector,
- falsingManager: FalsingManager
-) {
+ falsingManager: FalsingManager,
+ dumpManager: DumpManager
+) : Dumpable {
/**
* The current width of the carousel
*/
@@ -164,6 +171,7 @@ class MediaCarouselController @Inject constructor(
lateinit var updateUserVisibility: () -> Unit
init {
+ dumpManager.registerDumpable(TAG, this)
mediaFrame = inflateMediaCarousel()
mediaCarousel = mediaFrame.requireViewById(R.id.media_carousel_scroller)
pageIndicator = mediaFrame.requireViewById(R.id.media_page_indicator)
@@ -204,7 +212,7 @@ class MediaCarouselController @Inject constructor(
isSsReactivated: Boolean
) {
if (addOrUpdatePlayer(key, oldKey, data)) {
- MediaPlayerData.getMediaPlayer(key, null)?.let {
+ MediaPlayerData.getMediaPlayer(key)?.let {
logSmartspaceCardReported(759, // SMARTSPACE_CARD_RECEIVED
it.mInstanceId,
/* isRecommendationCard */ false,
@@ -241,7 +249,7 @@ class MediaCarouselController @Inject constructor(
if (DEBUG) Log.d(TAG, "Loading Smartspace media update")
if (data.isActive) {
addSmartspaceMediaRecommendations(key, data, shouldPrioritize)
- MediaPlayerData.getMediaPlayer(key, null)?.let {
+ MediaPlayerData.getMediaPlayer(key)?.let {
logSmartspaceCardReported(759, // SMARTSPACE_CARD_RECEIVED
it.mInstanceId,
/* isRecommendationCard */ true,
@@ -344,7 +352,8 @@ class MediaCarouselController @Inject constructor(
// Returns true if new player is added
private fun addOrUpdatePlayer(key: String, oldKey: String?, data: MediaData): Boolean {
val dataCopy = data.copy(backgroundColor = bgColor)
- val existingPlayer = MediaPlayerData.getMediaPlayer(key, oldKey)
+ MediaPlayerData.moveIfExists(oldKey, key)
+ val existingPlayer = MediaPlayerData.getMediaPlayer(key)
val curVisibleMediaKey = MediaPlayerData.playerKeys()
.elementAtOrNull(mediaCarouselScrollHandler.visibleMediaIndex)
if (existingPlayer == null) {
@@ -357,12 +366,12 @@ class MediaCarouselController @Inject constructor(
newPlayer.playerViewHolder?.player?.setLayoutParams(lp)
newPlayer.bindPlayer(dataCopy, key)
newPlayer.setListening(currentlyExpanded)
- MediaPlayerData.addMediaPlayer(key, dataCopy, newPlayer)
+ MediaPlayerData.addMediaPlayer(key, dataCopy, newPlayer, systemClock)
updatePlayerToState(newPlayer, noAnimation = true)
reorderAllPlayers(curVisibleMediaKey)
} else {
existingPlayer.bindPlayer(dataCopy, key)
- MediaPlayerData.addMediaPlayer(key, dataCopy, existingPlayer)
+ MediaPlayerData.addMediaPlayer(key, dataCopy, existingPlayer, systemClock)
if (visualStabilityManager.isReorderingAllowed || shouldScrollToActivePlayer) {
reorderAllPlayers(curVisibleMediaKey)
} else {
@@ -386,7 +395,7 @@ class MediaCarouselController @Inject constructor(
shouldPrioritize: Boolean
) {
if (DEBUG) Log.d(TAG, "Updating smartspace target in carousel")
- if (MediaPlayerData.getMediaPlayer(key, null) != null) {
+ if (MediaPlayerData.getMediaPlayer(key) != null) {
Log.w(TAG, "Skip adding smartspace target in carousel")
return
}
@@ -406,7 +415,7 @@ class MediaCarouselController @Inject constructor(
newRecs.bindRecommendation(data.copy(backgroundColor = bgColor))
val curVisibleMediaKey = MediaPlayerData.playerKeys()
.elementAtOrNull(mediaCarouselScrollHandler.visibleMediaIndex)
- MediaPlayerData.addMediaRecommendation(key, data, newRecs, shouldPrioritize)
+ MediaPlayerData.addMediaRecommendation(key, data, newRecs, shouldPrioritize, systemClock)
updatePlayerToState(newRecs, noAnimation = true)
reorderAllPlayers(curVisibleMediaKey)
updatePageIndicator()
@@ -418,7 +427,7 @@ class MediaCarouselController @Inject constructor(
}
}
- private fun removePlayer(
+ fun removePlayer(
key: String,
dismissMediaData: Boolean = true,
dismissRecommendation: Boolean = true
@@ -745,6 +754,15 @@ class MediaCarouselController @Inject constructor(
}
mediaManager.onSwipeToDismiss()
}
+
+ override fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<out String>) {
+ pw.apply {
+ println("keysNeedRemoval: $keysNeedRemoval")
+ println("playerKeys: ${MediaPlayerData.playerKeys()}")
+ println("smartspaceMediaData: ${MediaPlayerData.smartspaceMediaData}")
+ println("shouldPrioritizeSs: ${MediaPlayerData.shouldPrioritizeSs}")
+ }
+ }
}
@VisibleForTesting
@@ -774,9 +792,9 @@ internal object MediaPlayerData {
private val mediaPlayers = TreeMap<MediaSortKey, MediaControlPanel>(comparator)
private val mediaData: MutableMap<String, MediaSortKey> = mutableMapOf()
- fun addMediaPlayer(key: String, data: MediaData, player: MediaControlPanel) {
+ fun addMediaPlayer(key: String, data: MediaData, player: MediaControlPanel, clock: SystemClock) {
removeMediaPlayer(key)
- val sortKey = MediaSortKey(isSsMediaRec = false, data, System.currentTimeMillis())
+ val sortKey = MediaSortKey(isSsMediaRec = false, data, clock.currentTimeMillis())
mediaData.put(key, sortKey)
mediaPlayers.put(sortKey, player)
}
@@ -785,23 +803,29 @@ internal object MediaPlayerData {
key: String,
data: SmartspaceMediaData,
player: MediaControlPanel,
- shouldPrioritize: Boolean
+ shouldPrioritize: Boolean,
+ clock: SystemClock
) {
shouldPrioritizeSs = shouldPrioritize
removeMediaPlayer(key)
- val sortKey = MediaSortKey(isSsMediaRec = true, EMPTY, System.currentTimeMillis())
+ val sortKey = MediaSortKey(isSsMediaRec = true, EMPTY, clock.currentTimeMillis())
mediaData.put(key, sortKey)
mediaPlayers.put(sortKey, player)
smartspaceMediaData = data
}
- fun getMediaPlayer(key: String, oldKey: String?): MediaControlPanel? {
- // If the key was changed, update entry
- oldKey?.let {
- if (it != key) {
- mediaData.remove(it)?.let { sortKey -> mediaData.put(key, sortKey) }
- }
+ fun moveIfExists(oldKey: String?, newKey: String) {
+ if (oldKey == null || oldKey == newKey) {
+ return
+ }
+
+ mediaData.remove(oldKey)?.let {
+ removeMediaPlayer(newKey)
+ mediaData.put(newKey, it)
}
+ }
+
+ fun getMediaPlayer(key: String): MediaControlPanel? {
return mediaData.get(key)?.let { mediaPlayers.get(it) }
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselScrollHandler.kt b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselScrollHandler.kt
index b0d4cb1c9818..cbcec9531ce4 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselScrollHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselScrollHandler.kt
@@ -236,7 +236,7 @@ class MediaCarouselScrollHandler(
}
private fun updateSettingsPresentation() {
- if (showsSettingsButton) {
+ if (showsSettingsButton && settingsButton.width > 0) {
val settingsOffset = MathUtils.map(
0.0f,
getMaxTranslation().toFloat(),
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
index 19a67e95a496..902e8c28a85d 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
@@ -347,10 +347,11 @@ public class MediaControlPanel {
artistText.setText(data.getArtist());
// Transfer chip
- mPlayerViewHolder.getSeamless().setVisibility(View.VISIBLE);
+ ViewGroup seamlessView = mPlayerViewHolder.getSeamless();
+ seamlessView.setVisibility(View.VISIBLE);
setVisibleAndAlpha(collapsedSet, R.id.media_seamless, true /*visible */);
setVisibleAndAlpha(expandedSet, R.id.media_seamless, true /*visible */);
- mPlayerViewHolder.getSeamless().setOnClickListener(v -> {
+ seamlessView.setOnClickListener(v -> {
mMediaOutputDialogFactory.create(data.getPackageName(), true);
});
@@ -374,9 +375,9 @@ public class MediaControlPanel {
collapsedSet.setAlpha(seamlessId, seamlessAlpha);
// Disable clicking on output switcher for resumption controls.
mPlayerViewHolder.getSeamless().setEnabled(!data.getResumption());
+ String deviceString = null;
if (showFallback) {
iconView.setImageDrawable(null);
- deviceName.setText(null);
} else if (device != null) {
Drawable icon = device.getIcon();
iconView.setVisibility(View.VISIBLE);
@@ -387,13 +388,16 @@ public class MediaControlPanel {
} else {
iconView.setImageDrawable(icon);
}
- deviceName.setText(device.getName());
+ deviceString = device.getName();
} else {
// Reset to default
Log.w(TAG, "device is null. Not binding output chip.");
iconView.setVisibility(View.GONE);
- deviceName.setText(com.android.internal.R.string.ext_media_seamless_action);
+ deviceString = mContext.getString(
+ com.android.internal.R.string.ext_media_seamless_action);
}
+ deviceName.setText(deviceString);
+ seamlessView.setContentDescription(deviceString);
List<Integer> actionsWhenCollapsed = data.getActionsToShowInCompact();
// Media controls
@@ -451,8 +455,12 @@ public class MediaControlPanel {
if (mKey != null) {
closeGuts();
- mMediaDataManagerLazy.get().dismissMediaData(mKey,
- MediaViewController.GUTS_ANIMATION_DURATION + 100);
+ if (!mMediaDataManagerLazy.get().dismissMediaData(mKey,
+ MediaViewController.GUTS_ANIMATION_DURATION + 100)) {
+ Log.w(TAG, "Manager failed to dismiss media " + mKey);
+ // Remove directly from carousel to let user recover - TODO(b/190799184)
+ mMediaCarouselController.removePlayer(key, false, false);
+ }
} else {
Log.w(TAG, "Dismiss media with null notification. Token uid="
+ data.getToken().getUid());
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
index df1b07f3e0b2..0a28b47923da 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
@@ -430,7 +430,11 @@ class MediaDataManager(
notifyMediaDataRemoved(key)
}
- fun dismissMediaData(key: String, delay: Long) {
+ /**
+ * Dismiss a media entry. Returns false if the key was not found.
+ */
+ fun dismissMediaData(key: String, delay: Long): Boolean {
+ val existed = mediaEntries[key] != null
backgroundExecutor.execute {
mediaEntries[key]?.let { mediaData ->
if (mediaData.isLocalSession) {
@@ -442,6 +446,7 @@ class MediaDataManager(
}
}
foregroundExecutor.executeDelayed({ removeEntry(key) }, delay)
+ return existed
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt
index 186f961ff1b6..fb601e310702 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt
@@ -537,8 +537,19 @@ class MediaHierarchyManager @Inject constructor(
) {
val desiredLocation = calculateLocation()
if (desiredLocation != this.desiredLocation || forceStateUpdate) {
- if (this.desiredLocation >= 0) {
+ if (this.desiredLocation >= 0 && desiredLocation != this.desiredLocation) {
+ // Only update previous location when it actually changes
previousLocation = this.desiredLocation
+ } else if (forceStateUpdate) {
+ val onLockscreen = (!bypassController.bypassEnabled &&
+ (statusbarState == StatusBarState.KEYGUARD ||
+ statusbarState == StatusBarState.FULLSCREEN_USER_SWITCHER))
+ if (desiredLocation == LOCATION_QS && previousLocation == LOCATION_LOCKSCREEN &&
+ !onLockscreen) {
+ // If media active state changed and the device is now unlocked, update the
+ // previous location so we animate between the correct hosts
+ previousLocation = LOCATION_QQS
+ }
}
val isNewView = this.desiredLocation == -1
this.desiredLocation = desiredLocation
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
index 0d5faff65aab..391dff634dab 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
@@ -46,7 +46,7 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter {
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
private ViewGroup mConnectedItem;
- private boolean mInclueDynamicGroup;
+ private boolean mIncludeDynamicGroup;
public MediaOutputAdapter(MediaOutputController controller) {
super(controller);
@@ -56,7 +56,6 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter {
public MediaDeviceBaseViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup,
int viewType) {
super.onCreateViewHolder(viewGroup, viewType);
-
return new MediaDeviceViewHolder(mHolderView);
}
@@ -66,7 +65,7 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter {
if (position == size && mController.isZeroMode()) {
viewHolder.onBind(CUSTOMIZED_ITEM_PAIR_NEW, false /* topMargin */,
true /* bottomMargin */);
- } else if (mInclueDynamicGroup) {
+ } else if (mIncludeDynamicGroup) {
if (position == 0) {
viewHolder.onBind(CUSTOMIZED_ITEM_DYNAMIC_GROUP, true /* topMargin */,
false /* bottomMargin */);
@@ -76,11 +75,12 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter {
// from "position - 1".
viewHolder.onBind(((List<MediaDevice>) (mController.getMediaDevices()))
.get(position - 1),
- false /* topMargin */, position == size /* bottomMargin */);
+ false /* topMargin */, position == size /* bottomMargin */, position);
}
} else if (position < size) {
viewHolder.onBind(((List<MediaDevice>) (mController.getMediaDevices())).get(position),
- position == 0 /* topMargin */, position == (size - 1) /* bottomMargin */);
+ position == 0 /* topMargin */, position == (size - 1) /* bottomMargin */,
+ position);
} else if (DEBUG) {
Log.d(TAG, "Incorrect position: " + position);
}
@@ -88,8 +88,8 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter {
@Override
public int getItemCount() {
- mInclueDynamicGroup = mController.getSelectedMediaDevice().size() > 1;
- if (mController.isZeroMode() || mInclueDynamicGroup) {
+ mIncludeDynamicGroup = mController.getSelectedMediaDevice().size() > 1;
+ if (mController.isZeroMode() || mIncludeDynamicGroup) {
// Add extra one for "pair new" or dynamic group
return mController.getMediaDevices().size() + 1;
}
@@ -120,15 +120,17 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter {
}
@Override
- void onBind(MediaDevice device, boolean topMargin, boolean bottomMargin) {
- super.onBind(device, topMargin, bottomMargin);
- final boolean currentlyConnected = !mInclueDynamicGroup && isCurrentlyConnected(device);
+ void onBind(MediaDevice device, boolean topMargin, boolean bottomMargin, int position) {
+ super.onBind(device, topMargin, bottomMargin, position);
+ final boolean currentlyConnected = !mIncludeDynamicGroup
+ && isCurrentlyConnected(device);
if (currentlyConnected) {
mConnectedItem = mContainerLayout;
}
mBottomDivider.setVisibility(View.GONE);
mCheckBox.setVisibility(View.GONE);
- if (currentlyConnected && mController.isActiveRemoteDevice(device)) {
+ if (currentlyConnected && mController.isActiveRemoteDevice(device)
+ && mController.getSelectableMediaDevice().size() > 0) {
// Init active device layout
mDivider.setVisibility(View.VISIBLE);
mDivider.setTransitionAlpha(1);
@@ -140,6 +142,9 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter {
mDivider.setVisibility(View.GONE);
mAddIcon.setVisibility(View.GONE);
}
+ if (mCurrentActivePosition == position) {
+ mCurrentActivePosition = -1;
+ }
if (mController.isTransferring()) {
if (device.getState() == MediaDeviceState.STATE_CONNECTING
&& !mController.hasAdjustVolumeUserRestriction()) {
@@ -160,6 +165,7 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter {
setTwoLineLayout(device, true /* bFocused */, true /* showSeekBar */,
false /* showProgressBar */, false /* showSubtitle */);
initSeekbar(device);
+ mCurrentActivePosition = position;
} else {
setSingleLineLayout(getItemTitle(device), false /* bFocused */);
mContainerLayout.setOnClickListener(v -> onItemClick(v, device));
@@ -186,11 +192,16 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter {
mConnectedItem = mContainerLayout;
mBottomDivider.setVisibility(View.GONE);
mCheckBox.setVisibility(View.GONE);
- mDivider.setVisibility(View.VISIBLE);
- mDivider.setTransitionAlpha(1);
- mAddIcon.setVisibility(View.VISIBLE);
- mAddIcon.setTransitionAlpha(1);
- mAddIcon.setOnClickListener(v -> onEndItemClick());
+ if (mController.getSelectableMediaDevice().size() > 0) {
+ mDivider.setVisibility(View.VISIBLE);
+ mDivider.setTransitionAlpha(1);
+ mAddIcon.setVisibility(View.VISIBLE);
+ mAddIcon.setTransitionAlpha(1);
+ mAddIcon.setOnClickListener(v -> onEndItemClick());
+ } else {
+ mDivider.setVisibility(View.GONE);
+ mAddIcon.setVisibility(View.GONE);
+ }
mTitleIcon.setImageDrawable(getSpeakerDrawable());
final CharSequence sessionName = mController.getSessionName();
final CharSequence title = TextUtils.isEmpty(sessionName)
@@ -206,6 +217,7 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter {
return;
}
+ mCurrentActivePosition = -1;
playSwitchingAnim(mConnectedItem, view);
mController.connectDevice(device);
device.setState(MediaDeviceState.STATE_CONNECTING);
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
index bcef43c93be4..0890841eb4fb 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
@@ -64,10 +64,12 @@ public abstract class MediaOutputBaseAdapter extends
Context mContext;
View mHolderView;
boolean mIsDragging;
+ int mCurrentActivePosition;
public MediaOutputBaseAdapter(MediaOutputController controller) {
mController = controller;
mIsDragging = false;
+ mCurrentActivePosition = -1;
}
@Override
@@ -99,6 +101,10 @@ public abstract class MediaOutputBaseAdapter extends
return mIsAnimating;
}
+ int getCurrentActivePosition() {
+ return mCurrentActivePosition;
+ }
+
/**
* ViewHolder for binding device view.
*/
@@ -136,7 +142,7 @@ public abstract class MediaOutputBaseAdapter extends
mCheckBox = view.requireViewById(R.id.check_box);
}
- void onBind(MediaDevice device, boolean topMargin, boolean bottomMargin) {
+ void onBind(MediaDevice device, boolean topMargin, boolean bottomMargin, int position) {
mDeviceId = device.getId();
ThreadUtils.postOnBackgroundThread(() -> {
Icon icon = mController.getDeviceIconCompat(device).toIcon(mContext);
@@ -214,6 +220,9 @@ public abstract class MediaOutputBaseAdapter extends
}
void initSeekbar(MediaDevice device) {
+ if (!mController.isVolumeControlEnabled(device)) {
+ disableSeekBar();
+ }
mSeekBar.setMax(device.getMaxVolume());
mSeekBar.setMin(0);
final int currentVolume = device.getCurrentVolume();
@@ -242,6 +251,7 @@ public abstract class MediaOutputBaseAdapter extends
}
void initSessionSeekbar() {
+ disableSeekBar();
mSeekBar.setMax(mController.getSessionVolumeMax());
mSeekBar.setMin(0);
final int currentVolume = mController.getSessionVolume();
@@ -330,5 +340,10 @@ public abstract class MediaOutputBaseAdapter extends
PorterDuff.Mode.SRC_IN));
return BluetoothUtils.buildAdvancedDrawable(mContext, drawable);
}
+
+ private void disableSeekBar() {
+ mSeekBar.setEnabled(false);
+ mSeekBar.setOnTouchListener((v, event) -> true);
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
index 8a9a6e694658..cdcdf9a1d4de 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
@@ -174,7 +174,12 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog implements
mHeaderTitle.setGravity(Gravity.NO_GRAVITY);
}
if (!mAdapter.isDragging() && !mAdapter.isAnimating()) {
- mAdapter.notifyDataSetChanged();
+ int currentActivePosition = mAdapter.getCurrentActivePosition();
+ if (currentActivePosition >= 0) {
+ mAdapter.notifyItemChanged(currentActivePosition);
+ } else {
+ mAdapter.notifyDataSetChanged();
+ }
}
// Show when remote media session is available
mStopButton.setVisibility(getStopButtonVisibility());
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
index 8fee9253a421..5293c8850267 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
@@ -465,6 +465,10 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback {
|| features.contains(MediaRoute2Info.FEATURE_REMOTE_GROUP_PLAYBACK));
}
+ boolean isVolumeControlEnabled(@NonNull MediaDevice device) {
+ return !device.getFeatures().contains(MediaRoute2Info.FEATURE_REMOTE_GROUP_PLAYBACK);
+ }
+
private final MediaController.Callback mCb = new MediaController.Callback() {
@Override
public void onMetadataChanged(MediaMetadata metadata) {
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputGroupAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputGroupAdapter.java
index 24e076bb22f1..968c3506f39f 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputGroupAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputGroupAdapter.java
@@ -68,7 +68,7 @@ public class MediaOutputGroupAdapter extends MediaOutputBaseAdapter {
final int size = mGroupMediaDevices.size();
if (newPosition < size) {
viewHolder.onBind(mGroupMediaDevices.get(newPosition), false /* topMargin */,
- newPosition == (size - 1) /* bottomMargin */);
+ newPosition == (size - 1) /* bottomMargin */, position);
return;
}
if (DEBUG) {
@@ -94,8 +94,8 @@ public class MediaOutputGroupAdapter extends MediaOutputBaseAdapter {
}
@Override
- void onBind(MediaDevice device, boolean topMargin, boolean bottomMargin) {
- super.onBind(device, topMargin, bottomMargin);
+ void onBind(MediaDevice device, boolean topMargin, boolean bottomMargin, int position) {
+ super.onBind(device, topMargin, bottomMargin, position);
mDivider.setVisibility(View.GONE);
mAddIcon.setVisibility(View.GONE);
mBottomDivider.setVisibility(View.GONE);
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
index c6d7e7c46abb..26f38ddd5919 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
@@ -136,6 +136,7 @@ import com.android.systemui.statusbar.AutoHideUiElement;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.CommandQueue.Callbacks;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
+import com.android.systemui.statusbar.NotificationShadeDepthController;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
import com.android.systemui.statusbar.phone.AutoHideController;
@@ -201,6 +202,7 @@ public class NavigationBar implements View.OnAttachStateChangeListener,
private final NavigationBarOverlayController mNavbarOverlayController;
private final UiEventLogger mUiEventLogger;
private final UserTracker mUserTracker;
+ private final NotificationShadeDepthController mNotificationShadeDepthController;
private Bundle mSavedState;
private NavigationBarView mNavigationBarView;
@@ -234,7 +236,6 @@ public class NavigationBar implements View.OnAttachStateChangeListener,
private boolean mTransientShown;
private int mNavBarMode = NAV_BAR_MODE_3BUTTON;
- private int mA11yBtnMode;
private LightBarController mLightBarController;
private AutoHideController mAutoHideController;
@@ -439,6 +440,25 @@ public class NavigationBar implements View.OnAttachStateChangeListener,
}
};
+ private final NotificationShadeDepthController.DepthListener mDepthListener =
+ new NotificationShadeDepthController.DepthListener() {
+ boolean mHasBlurs;
+
+ @Override
+ public void onWallpaperZoomOutChanged(float zoomOut) {
+ }
+
+ @Override
+ public void onBlurRadiusChanged(int radius) {
+ boolean hasBlurs = radius != 0;
+ if (hasBlurs == mHasBlurs) {
+ return;
+ }
+ mHasBlurs = hasBlurs;
+ mNavigationBarView.setWindowHasBlurs(hasBlurs);
+ }
+ };
+
public NavigationBar(Context context,
WindowManager windowManager,
Lazy<AssistManager> assistManagerLazy,
@@ -458,6 +478,7 @@ public class NavigationBar implements View.OnAttachStateChangeListener,
Optional<Recents> recentsOptional, Lazy<StatusBar> statusBarLazy,
ShadeController shadeController,
NotificationRemoteInputManager notificationRemoteInputManager,
+ NotificationShadeDepthController notificationShadeDepthController,
SystemActions systemActions,
@Main Handler mainHandler,
NavigationBarOverlayController navbarOverlayController,
@@ -488,10 +509,10 @@ public class NavigationBar implements View.OnAttachStateChangeListener,
mNavbarOverlayController = navbarOverlayController;
mUiEventLogger = uiEventLogger;
mUserTracker = userTracker;
+ mNotificationShadeDepthController = notificationShadeDepthController;
mNavBarMode = mNavigationModeController.addListener(this);
mAccessibilityButtonModeObserver.addListener(this);
- mA11yBtnMode = mAccessibilityButtonModeObserver.getCurrentAccessibilityButtonMode();
}
public NavigationBarView getView() {
@@ -572,6 +593,7 @@ public class NavigationBar implements View.OnAttachStateChangeListener,
mIsCurrentUserSetup = mDeviceProvisionedController.isCurrentUserSetup();
mDeviceProvisionedController.addCallback(mUserSetupListener);
+ mNotificationShadeDepthController.addListener(mDepthListener);
setAccessibilityFloatingMenuModeIfNeeded();
@@ -588,6 +610,7 @@ public class NavigationBar implements View.OnAttachStateChangeListener,
mAccessibilityManagerWrapper.removeCallback(mAccessibilityListener);
mContentResolver.unregisterContentObserver(mAssistContentObserver);
mDeviceProvisionedController.removeCallback(mUserSetupListener);
+ mNotificationShadeDepthController.removeListener(mDepthListener);
DeviceConfig.removeOnPropertiesChangedListener(mOnPropertiesChangedListener);
}
@@ -1379,8 +1402,9 @@ public class NavigationBar implements View.OnAttachStateChangeListener,
private void setAccessibilityFloatingMenuModeIfNeeded() {
if (QuickStepContract.isGesturalMode(mNavBarMode)) {
- Settings.Secure.putInt(mContentResolver, Settings.Secure.ACCESSIBILITY_BUTTON_MODE,
- ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU);
+ Settings.Secure.putIntForUser(mContentResolver,
+ Settings.Secure.ACCESSIBILITY_BUTTON_MODE,
+ ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU, UserHandle.USER_CURRENT);
}
}
@@ -1441,7 +1465,8 @@ public class NavigationBar implements View.OnAttachStateChangeListener,
// If accessibility button is floating menu mode, click and long click state should be
// disabled.
- if (mA11yBtnMode == ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU) {
+ if (mAccessibilityButtonModeObserver.getCurrentAccessibilityButtonMode()
+ == ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU) {
return 0;
}
@@ -1552,7 +1577,6 @@ public class NavigationBar implements View.OnAttachStateChangeListener,
@Override
public void onAccessibilityButtonModeChanged(int mode) {
- mA11yBtnMode = mode;
updateAccessibilityServicesState(mAccessibilityManager);
}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
index 53592101c3ea..b9e9240b354a 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
@@ -61,6 +61,7 @@ import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.CommandQueue.Callbacks;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
+import com.android.systemui.statusbar.NotificationShadeDepthController;
import com.android.systemui.statusbar.phone.BarTransitions.TransitionMode;
import com.android.systemui.statusbar.phone.ShadeController;
import com.android.systemui.statusbar.phone.StatusBar;
@@ -115,6 +116,7 @@ public class NavigationBarController implements Callbacks,
private final DisplayManager mDisplayManager;
private final NavigationBarOverlayController mNavBarOverlayController;
private final TaskbarDelegate mTaskbarDelegate;
+ private final NotificationShadeDepthController mNotificationShadeDepthController;
private int mNavMode;
private boolean mIsTablet;
private final UserTracker mUserTracker;
@@ -149,6 +151,7 @@ public class NavigationBarController implements Callbacks,
Lazy<StatusBar> statusBarLazy,
ShadeController shadeController,
NotificationRemoteInputManager notificationRemoteInputManager,
+ NotificationShadeDepthController notificationShadeDepthController,
SystemActions systemActions,
@Main Handler mainHandler,
UiEventLogger uiEventLogger,
@@ -175,6 +178,7 @@ public class NavigationBarController implements Callbacks,
mStatusBarLazy = statusBarLazy;
mShadeController = shadeController;
mNotificationRemoteInputManager = notificationRemoteInputManager;
+ mNotificationShadeDepthController = notificationShadeDepthController;
mSystemActions = systemActions;
mUiEventLogger = uiEventLogger;
mHandler = mainHandler;
@@ -362,6 +366,7 @@ public class NavigationBarController implements Callbacks,
mStatusBarLazy,
mShadeController,
mNotificationRemoteInputManager,
+ mNotificationShadeDepthController,
mSystemActions,
mHandler,
mNavBarOverlayController,
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
index 7af4853dd3f2..4816f1cf8d6a 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
@@ -413,6 +413,13 @@ public class NavigationBarView extends FrameLayout implements
return super.onTouchEvent(event);
}
+ /**
+ * If we're blurring the shade window.
+ */
+ public void setWindowHasBlurs(boolean hasBlurs) {
+ mRegionSamplingHelper.setWindowHasBlurs(hasBlurs);
+ }
+
void onTransientStateChanged(boolean isTransient) {
mEdgeBackGestureHandler.onNavBarTransientStateChanged(isTransient);
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/RegionSamplingHelper.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/RegionSamplingHelper.java
index 70117eb6d2f0..560d89af8e92 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/RegionSamplingHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/RegionSamplingHelper.java
@@ -65,6 +65,7 @@ public class RegionSamplingHelper implements View.OnAttachStateChangeListener,
private final float mLuminanceChangeThreshold;
private boolean mFirstSamplingAfterStart;
private boolean mWindowVisible;
+ private boolean mWindowHasBlurs;
private SurfaceControl mRegisteredStopLayer = null;
private ViewTreeObserver.OnDrawListener mUpdateOnDraw = new ViewTreeObserver.OnDrawListener() {
@Override
@@ -153,6 +154,7 @@ public class RegionSamplingHelper implements View.OnAttachStateChangeListener,
boolean isSamplingEnabled = mSamplingEnabled
&& !mSamplingRequestBounds.isEmpty()
&& mWindowVisible
+ && !mWindowHasBlurs
&& (mSampledView.isAttachedToWindow() || mFirstSamplingAfterStart);
if (isSamplingEnabled) {
ViewRootImpl viewRootImpl = mSampledView.getViewRootImpl();
@@ -225,6 +227,14 @@ public class RegionSamplingHelper implements View.OnAttachStateChangeListener,
updateSamplingListener();
}
+ /**
+ * If we're blurring the shade window.
+ */
+ public void setWindowHasBlurs(boolean hasBlurs) {
+ mWindowHasBlurs = hasBlurs;
+ updateSamplingListener();
+ }
+
public void dump(PrintWriter pw) {
pw.println("RegionSamplingHelper:");
pw.println(" sampleView isAttached: " + mSampledView.isAttachedToWindow());
@@ -238,6 +248,7 @@ public class RegionSamplingHelper implements View.OnAttachStateChangeListener,
pw.println(" mLastMedianLuma: " + mLastMedianLuma);
pw.println(" mCurrentMedianLuma: " + mCurrentMedianLuma);
pw.println(" mWindowVisible: " + mWindowVisible);
+ pw.println(" mWindowHasBlurs: " + mWindowHasBlurs);
pw.println(" mWaitingOnDraw: " + mWaitingOnDraw);
pw.println(" mRegisteredStopLayer: " + mRegisteredStopLayer);
pw.println(" mIsDestroyed: " + mIsDestroyed);
diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleBackupFollowUpJob.java b/packages/SystemUI/src/com/android/systemui/people/PeopleBackupFollowUpJob.java
new file mode 100644
index 000000000000..3bc1f30ea321
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/people/PeopleBackupFollowUpJob.java
@@ -0,0 +1,229 @@
+/*
+ * 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.people;
+
+import static com.android.systemui.people.PeopleSpaceUtils.DEBUG;
+import static com.android.systemui.people.PeopleSpaceUtils.EMPTY_STRING;
+import static com.android.systemui.people.PeopleSpaceUtils.removeSharedPreferencesStorageForTile;
+import static com.android.systemui.people.widget.PeopleBackupHelper.isReadyForRestore;
+
+import android.app.job.JobInfo;
+import android.app.job.JobParameters;
+import android.app.job.JobScheduler;
+import android.app.job.JobService;
+import android.app.people.IPeopleManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.content.pm.PackageManager;
+import android.os.PersistableBundle;
+import android.os.ServiceManager;
+import android.preference.PreferenceManager;
+import android.util.Log;
+
+import androidx.annotation.VisibleForTesting;
+
+import com.android.systemui.people.widget.PeopleBackupHelper;
+import com.android.systemui.people.widget.PeopleTileKey;
+
+import java.time.Duration;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Follow-up job that runs after a Conversations widgets restore operation. Check if shortcuts that
+ * were not available before are now available. If any shortcut doesn't become available after
+ * 1 day, we clean up its storage.
+ */
+public class PeopleBackupFollowUpJob extends JobService {
+ private static final String TAG = "PeopleBackupFollowUpJob";
+ private static final String START_DATE = "start_date";
+
+ /** Follow-up job id. */
+ public static final int JOB_ID = 74823873;
+
+ private static final long JOB_PERIODIC_DURATION = Duration.ofHours(6).toMillis();
+ private static final long CLEAN_UP_STORAGE_AFTER_DURATION = Duration.ofHours(48).toMillis();
+
+ /** SharedPreferences file name for follow-up specific storage.*/
+ public static final String SHARED_FOLLOW_UP = "shared_follow_up";
+
+ private final Object mLock = new Object();
+ private Context mContext;
+ private PackageManager mPackageManager;
+ private IPeopleManager mIPeopleManager;
+ private JobScheduler mJobScheduler;
+
+ /** Schedules a PeopleBackupFollowUpJob every 2 hours. */
+ public static void scheduleJob(Context context) {
+ JobScheduler jobScheduler = context.getSystemService(JobScheduler.class);
+ PersistableBundle bundle = new PersistableBundle();
+ bundle.putLong(START_DATE, System.currentTimeMillis());
+ JobInfo jobInfo = new JobInfo
+ .Builder(JOB_ID, new ComponentName(context, PeopleBackupFollowUpJob.class))
+ .setPeriodic(JOB_PERIODIC_DURATION)
+ .setExtras(bundle)
+ .build();
+ jobScheduler.schedule(jobInfo);
+ }
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ mContext = getApplicationContext();
+ mPackageManager = getApplicationContext().getPackageManager();
+ mIPeopleManager = IPeopleManager.Stub.asInterface(
+ ServiceManager.getService(Context.PEOPLE_SERVICE));
+ mJobScheduler = mContext.getSystemService(JobScheduler.class);
+
+ }
+
+ /** Sets necessary managers for testing. */
+ @VisibleForTesting
+ public void setManagers(Context context, PackageManager packageManager,
+ IPeopleManager iPeopleManager, JobScheduler jobScheduler) {
+ mContext = context;
+ mPackageManager = packageManager;
+ mIPeopleManager = iPeopleManager;
+ mJobScheduler = jobScheduler;
+ }
+
+ @Override
+ public boolean onStartJob(JobParameters params) {
+ if (DEBUG) Log.d(TAG, "Starting job.");
+ synchronized (mLock) {
+ SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this);
+ SharedPreferences.Editor editor = sp.edit();
+ SharedPreferences followUp = this.getSharedPreferences(
+ SHARED_FOLLOW_UP, Context.MODE_PRIVATE);
+ SharedPreferences.Editor followUpEditor = followUp.edit();
+
+ // Remove from SHARED_FOLLOW_UP storage all widgets that are now ready to be updated.
+ Map<String, Set<String>> remainingWidgets =
+ processFollowUpFile(followUp, followUpEditor);
+
+ // Check if all widgets were restored or if enough time elapsed to cancel the job.
+ long start = params.getExtras().getLong(START_DATE);
+ long now = System.currentTimeMillis();
+ if (shouldCancelJob(remainingWidgets, start, now)) {
+ cancelJobAndClearRemainingWidgets(remainingWidgets, followUpEditor, sp);
+ }
+
+ editor.apply();
+ followUpEditor.apply();
+ }
+
+ // Ensure all widgets modified from SHARED_FOLLOW_UP storage are now updated.
+ PeopleBackupHelper.updateWidgets(mContext);
+ return false;
+ }
+
+ /**
+ * Iterates through follow-up file entries and checks which shortcuts are now available.
+ * Returns a map of shortcuts that should be checked at a later time.
+ */
+ public Map<String, Set<String>> processFollowUpFile(SharedPreferences followUp,
+ SharedPreferences.Editor followUpEditor) {
+ Map<String, Set<String>> remainingWidgets = new HashMap<>();
+ Map<String, ?> all = followUp.getAll();
+ for (Map.Entry<String, ?> entry : all.entrySet()) {
+ String key = entry.getKey();
+
+ PeopleTileKey peopleTileKey = PeopleTileKey.fromString(key);
+ boolean restored = isReadyForRestore(mIPeopleManager, mPackageManager, peopleTileKey);
+ if (restored) {
+ if (DEBUG) Log.d(TAG, "Removing key from follow-up: " + key);
+ followUpEditor.remove(key);
+ continue;
+ }
+
+ if (DEBUG) Log.d(TAG, "Key should not be restored yet, try later: " + key);
+ try {
+ remainingWidgets.put(entry.getKey(), (Set<String>) entry.getValue());
+ } catch (Exception e) {
+ Log.e(TAG, "Malformed entry value: " + entry.getValue());
+ }
+ }
+ return remainingWidgets;
+ }
+
+ /** Returns whether all shortcuts were restored or if enough time elapsed to cancel the job. */
+ public boolean shouldCancelJob(Map<String, Set<String>> remainingWidgets,
+ long start, long now) {
+ if (remainingWidgets.isEmpty()) {
+ if (DEBUG) Log.d(TAG, "All widget storage was successfully restored.");
+ return true;
+ }
+
+ boolean oneDayHasPassed = (now - start) > CLEAN_UP_STORAGE_AFTER_DURATION;
+ if (oneDayHasPassed) {
+ if (DEBUG) {
+ Log.w(TAG, "One or more widgets were not properly restored, "
+ + "but cancelling job because it has been a day.");
+ }
+ return true;
+ }
+ if (DEBUG) Log.d(TAG, "There are still non-restored widgets, run job again.");
+ return false;
+ }
+
+ /** Cancels job and removes storage of any shortcut that was not restored. */
+ public void cancelJobAndClearRemainingWidgets(Map<String, Set<String>> remainingWidgets,
+ SharedPreferences.Editor followUpEditor, SharedPreferences sp) {
+ if (DEBUG) Log.d(TAG, "Cancelling follow up job.");
+ removeUnavailableShortcutsFromSharedStorage(remainingWidgets, sp);
+ followUpEditor.clear();
+ mJobScheduler.cancel(JOB_ID);
+ }
+
+ private void removeUnavailableShortcutsFromSharedStorage(Map<String,
+ Set<String>> remainingWidgets, SharedPreferences sp) {
+ for (Map.Entry<String, Set<String>> entry : remainingWidgets.entrySet()) {
+ PeopleTileKey peopleTileKey = PeopleTileKey.fromString(entry.getKey());
+ if (!PeopleTileKey.isValid(peopleTileKey)) {
+ Log.e(TAG, "Malformed peopleTileKey in follow-up file: " + entry.getKey());
+ continue;
+ }
+ Set<String> widgetIds;
+ try {
+ widgetIds = (Set<String>) entry.getValue();
+ } catch (Exception e) {
+ Log.e(TAG, "Malformed widget ids in follow-up file: " + e);
+ continue;
+ }
+ for (String id : widgetIds) {
+ int widgetId;
+ try {
+ widgetId = Integer.parseInt(id);
+ } catch (NumberFormatException ex) {
+ Log.e(TAG, "Malformed widget id in follow-up file: " + ex);
+ continue;
+ }
+
+ String contactUriString = sp.getString(String.valueOf(widgetId), EMPTY_STRING);
+ removeSharedPreferencesStorageForTile(
+ mContext, peopleTileKey, widgetId, contactUriString);
+ }
+ }
+ }
+
+ @Override
+ public boolean onStopJob(JobParameters params) {
+ return false;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java
index d9e2648750a4..93a3f81fdd6b 100644
--- a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java
@@ -89,7 +89,7 @@ public class PeopleSpaceActivity extends Activity {
// 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);
+ LinearLayout item = findViewById(android.R.id.background);
GradientDrawable shape = (GradientDrawable) item.getBackground();
final TypedArray ta = mContext.getTheme().obtainStyledAttributes(
new int[]{com.android.internal.R.attr.colorSurface});
diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java
index 917a060f1f1d..c01d6dcd7d64 100644
--- a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java
+++ b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java
@@ -25,6 +25,7 @@ import static com.android.systemui.people.NotificationHelper.shouldMatchNotifica
import android.annotation.Nullable;
import android.app.Notification;
+import android.app.backup.BackupManager;
import android.app.people.ConversationChannel;
import android.app.people.IPeopleManager;
import android.app.people.PeopleSpaceTile;
@@ -74,7 +75,8 @@ import java.util.stream.Stream;
/** Utils class for People Space. */
public class PeopleSpaceUtils {
/** Turns on debugging information about People Space. */
- public static final boolean DEBUG = true;
+ public static final boolean DEBUG = false;
+
public static final String PACKAGE_NAME = "package_name";
public static final String USER_ID = "user_id";
public static final String SHORTCUT_ID = "shortcut_id";
@@ -89,7 +91,7 @@ public class PeopleSpaceUtils {
/** Returns stored widgets for the conversation specified. */
public static Set<String> getStoredWidgetIds(SharedPreferences sp, PeopleTileKey key) {
- if (!key.isValid()) {
+ if (!PeopleTileKey.isValid(key)) {
return new HashSet<>();
}
return new HashSet<>(sp.getStringSet(key.toString(), new HashSet<>()));
@@ -97,19 +99,16 @@ public class PeopleSpaceUtils {
/** Sets all relevant storage for {@code appWidgetId} association to {@code tile}. */
public static void setSharedPreferencesStorageForTile(Context context, PeopleTileKey key,
- int appWidgetId, Uri contactUri) {
- if (!key.isValid()) {
+ int appWidgetId, Uri contactUri, BackupManager backupManager) {
+ if (!PeopleTileKey.isValid(key)) {
Log.e(TAG, "Not storing for invalid key");
return;
}
// Write relevant persisted storage.
SharedPreferences widgetSp = context.getSharedPreferences(String.valueOf(appWidgetId),
Context.MODE_PRIVATE);
- SharedPreferences.Editor widgetEditor = widgetSp.edit();
- widgetEditor.putString(PeopleSpaceUtils.PACKAGE_NAME, key.getPackageName());
- widgetEditor.putString(PeopleSpaceUtils.SHORTCUT_ID, key.getShortcutId());
- widgetEditor.putInt(PeopleSpaceUtils.USER_ID, key.getUserId());
- widgetEditor.apply();
+ SharedPreferencesHelper.setPeopleTileKey(widgetSp, key);
+
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context);
SharedPreferences.Editor editor = sp.edit();
String contactUriString = contactUri == null ? EMPTY_STRING : contactUri.toString();
@@ -117,14 +116,18 @@ public class PeopleSpaceUtils {
// Don't overwrite existing widgets with the same key.
addAppWidgetIdForKey(sp, editor, appWidgetId, key.toString());
- addAppWidgetIdForKey(sp, editor, appWidgetId, contactUriString);
+ if (!TextUtils.isEmpty(contactUriString)) {
+ addAppWidgetIdForKey(sp, editor, appWidgetId, contactUriString);
+ }
editor.apply();
+ backupManager.dataChanged();
}
/** Removes stored data when tile is deleted. */
public static void removeSharedPreferencesStorageForTile(Context context, PeopleTileKey key,
int widgetId, String contactUriString) {
// Delete widgetId mapping to key.
+ if (DEBUG) Log.d(TAG, "Removing widget info from sharedPrefs");
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context);
SharedPreferences.Editor editor = sp.edit();
editor.remove(String.valueOf(widgetId));
@@ -230,7 +233,7 @@ public class PeopleSpaceUtils {
*/
public static PeopleSpaceTile augmentTileFromNotification(Context context, PeopleSpaceTile tile,
PeopleTileKey key, NotificationEntry notificationEntry, int messagesCount,
- Optional<Integer> appWidgetId) {
+ Optional<Integer> appWidgetId, BackupManager backupManager) {
if (notificationEntry == null || notificationEntry.getSbn().getNotification() == null) {
if (DEBUG) Log.d(TAG, "Tile key: " + key.toString() + ". Notification is null");
return removeNotificationFields(tile);
@@ -246,7 +249,7 @@ public class PeopleSpaceUtils {
Uri contactUri = Uri.parse(uriFromNotification);
// Update storage.
setSharedPreferencesStorageForTile(context, new PeopleTileKey(tile), appWidgetId.get(),
- contactUri);
+ contactUri, backupManager);
// Update cached tile in-memory.
updatedTile.setContactUri(contactUri);
}
diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleStoryIconFactory.java b/packages/SystemUI/src/com/android/systemui/people/PeopleStoryIconFactory.java
index 96aeb60ae93c..4ee951f3cdb1 100644
--- a/packages/SystemUI/src/com/android/systemui/people/PeopleStoryIconFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/people/PeopleStoryIconFactory.java
@@ -211,7 +211,8 @@ class PeopleStoryIconFactory implements AutoCloseable {
@Override
public void setColorFilter(ColorFilter colorFilter) {
- // unimplemented
+ if (mAvatar != null) mAvatar.setColorFilter(colorFilter);
+ if (mBadgeIcon != null) mBadgeIcon.setColorFilter(colorFilter);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java b/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java
index 0e17c8bfcab5..7ab4b6f200ed 100644
--- a/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java
@@ -29,6 +29,9 @@ import static android.appwidget.AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT;
import static android.appwidget.AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH;
import static android.appwidget.AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT;
import static android.appwidget.AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH;
+import static android.appwidget.AppWidgetManager.OPTION_APPWIDGET_SIZES;
+import static android.util.TypedValue.COMPLEX_UNIT_DIP;
+import static android.util.TypedValue.COMPLEX_UNIT_PX;
import static com.android.systemui.people.PeopleSpaceUtils.STARRED_CONTACT;
import static com.android.systemui.people.PeopleSpaceUtils.VALID_CONTACT;
@@ -41,25 +44,33 @@ import android.app.people.ConversationStatus;
import android.app.people.PeopleSpaceTile;
import android.content.Context;
import android.content.Intent;
-import android.content.res.Configuration;
import android.graphics.Bitmap;
+import android.graphics.ColorMatrix;
+import android.graphics.ColorMatrixColorFilter;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
+import android.graphics.text.LineBreaker;
import android.net.Uri;
import android.os.Bundle;
import android.os.UserHandle;
+import android.text.StaticLayout;
+import android.text.TextPaint;
import android.text.TextUtils;
import android.util.IconDrawableFactory;
import android.util.Log;
import android.util.Pair;
+import android.util.SizeF;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.View;
import android.widget.RemoteViews;
import android.widget.TextView;
+import androidx.annotation.DimenRes;
+import androidx.annotation.Px;
import androidx.core.graphics.drawable.RoundedBitmapDrawable;
import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory;
+import androidx.core.math.MathUtils;
import com.android.internal.annotations.VisibleForTesting;
import com.android.launcher3.icons.FastBitmapDrawable;
@@ -75,8 +86,10 @@ import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.Locale;
+import java.util.Map;
import java.util.Objects;
import java.util.Optional;
+import java.util.function.Function;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
@@ -84,7 +97,7 @@ import java.util.stream.Collectors;
/** Functions that help creating the People tile layouts. */
public class PeopleTileViewHelper {
/** Turns on debugging information about People Space. */
- public static final boolean DEBUG = true;
+ private static final boolean DEBUG = PeopleSpaceUtils.DEBUG;
private static final String TAG = "PeopleTileView";
private static final int DAYS_IN_A_WEEK = 7;
@@ -103,16 +116,22 @@ public class PeopleTileViewHelper {
private static final int MIN_MEDIUM_VERTICAL_PADDING = 4;
private static final int MAX_MEDIUM_PADDING = 16;
private static final int FIXED_HEIGHT_DIMENS_FOR_MEDIUM_CONTENT_BEFORE_PADDING = 8 + 4;
- private static final int FIXED_HEIGHT_DIMENS_FOR_SMALL = 6 + 4 + 8;
- private static final int FIXED_WIDTH_DIMENS_FOR_SMALL = 4 + 4;
+ private static final int FIXED_HEIGHT_DIMENS_FOR_SMALL_VERTICAL = 6 + 4 + 8;
+ private static final int FIXED_WIDTH_DIMENS_FOR_SMALL_VERTICAL = 4 + 4;
+ private static final int FIXED_HEIGHT_DIMENS_FOR_SMALL_HORIZONTAL = 6 + 4;
+ private static final int FIXED_WIDTH_DIMENS_FOR_SMALL_HORIZONTAL = 8 + 8;
private static final int MESSAGES_COUNT_OVERFLOW = 6;
+ private static final CharSequence EMOJI_CAKE = "\ud83c\udf82";
+
private static final Pattern DOUBLE_EXCLAMATION_PATTERN = Pattern.compile("[!][!]+");
private static final Pattern DOUBLE_QUESTION_PATTERN = Pattern.compile("[?][?]+");
private static final Pattern ANY_DOUBLE_MARK_PATTERN = Pattern.compile("[!?][!?]+");
private static final Pattern MIXED_MARK_PATTERN = Pattern.compile("![?].*|.*[?]!");
+ static final String BRIEF_PAUSE_ON_TALKBACK = "\n\n";
+
// This regex can be used to match Unicode emoji characters and character sequences. It's from
// the official Unicode site (https://unicode.org/reports/tr51/#EBNF_and_Regex) with minor
// changes to fit our needs. It should be updated once new emoji categories are added.
@@ -163,28 +182,64 @@ public class PeopleTileViewHelper {
private Locale mLocale;
private NumberFormat mIntegerFormat;
- public PeopleTileViewHelper(Context context, @Nullable PeopleSpaceTile tile,
- int appWidgetId, Bundle options, PeopleTileKey key) {
+ PeopleTileViewHelper(Context context, @Nullable PeopleSpaceTile tile,
+ int appWidgetId, int width, int height, PeopleTileKey key) {
mContext = context;
mTile = tile;
mKey = key;
mAppWidgetId = appWidgetId;
mDensity = mContext.getResources().getDisplayMetrics().density;
- int display = mContext.getResources().getConfiguration().orientation;
- mWidth = display == Configuration.ORIENTATION_PORTRAIT
- ? options.getInt(OPTION_APPWIDGET_MIN_WIDTH,
- getSizeInDp(R.dimen.default_width)) : options.getInt(
- OPTION_APPWIDGET_MAX_WIDTH,
- getSizeInDp(R.dimen.default_width));
- mHeight = display == Configuration.ORIENTATION_PORTRAIT ? options.getInt(
- OPTION_APPWIDGET_MAX_HEIGHT,
- getSizeInDp(R.dimen.default_height))
- : options.getInt(OPTION_APPWIDGET_MIN_HEIGHT,
- getSizeInDp(R.dimen.default_height));
+ mWidth = width;
+ mHeight = height;
mLayoutSize = getLayoutSize();
}
- public RemoteViews getViews() {
+ /**
+ * Creates a {@link RemoteViews} for the specified arguments. The RemoteViews will support all
+ * the sizes present in {@code options.}.
+ */
+ public static RemoteViews createRemoteViews(Context context, @Nullable PeopleSpaceTile tile,
+ int appWidgetId, Bundle options, PeopleTileKey key) {
+ List<SizeF> widgetSizes = getWidgetSizes(context, options);
+ Map<SizeF, RemoteViews> sizeToRemoteView =
+ widgetSizes
+ .stream()
+ .distinct()
+ .collect(Collectors.toMap(
+ Function.identity(),
+ size -> new PeopleTileViewHelper(
+ context, tile, appWidgetId,
+ (int) size.getWidth(),
+ (int) size.getHeight(),
+ key)
+ .getViews()));
+ return new RemoteViews(sizeToRemoteView);
+ }
+
+ private static List<SizeF> getWidgetSizes(Context context, Bundle options) {
+ float density = context.getResources().getDisplayMetrics().density;
+ List<SizeF> widgetSizes = options.getParcelableArrayList(OPTION_APPWIDGET_SIZES);
+ // If the full list of sizes was provided in the options bundle, use that.
+ if (widgetSizes != null && !widgetSizes.isEmpty()) return widgetSizes;
+
+ // Otherwise, create a list using the portrait/landscape sizes.
+ int defaultWidth = getSizeInDp(context, R.dimen.default_width, density);
+ int defaultHeight = getSizeInDp(context, R.dimen.default_height, density);
+ widgetSizes = new ArrayList<>(2);
+
+ int portraitWidth = options.getInt(OPTION_APPWIDGET_MIN_WIDTH, defaultWidth);
+ int portraitHeight = options.getInt(OPTION_APPWIDGET_MAX_HEIGHT, defaultHeight);
+ widgetSizes.add(new SizeF(portraitWidth, portraitHeight));
+
+ int landscapeWidth = options.getInt(OPTION_APPWIDGET_MAX_WIDTH, defaultWidth);
+ int landscapeHeight = options.getInt(OPTION_APPWIDGET_MIN_HEIGHT, defaultHeight);
+ widgetSizes.add(new SizeF(landscapeWidth, landscapeHeight));
+
+ return widgetSizes;
+ }
+
+ @VisibleForTesting
+ RemoteViews getViews() {
RemoteViews viewsForTile = getViewForTile();
int maxAvatarSize = getMaxAvatarSize(viewsForTile);
RemoteViews views = setCommonRemoteViewsFields(viewsForTile, maxAvatarSize);
@@ -197,12 +252,16 @@ public class PeopleTileViewHelper {
*/
private RemoteViews getViewForTile() {
if (DEBUG) Log.d(TAG, "Creating view for tile key: " + mKey.toString());
- if (mTile == null || mTile.isPackageSuspended() || mTile.isUserQuieted()
- || isDndBlockingTileData(mTile)) {
+ if (mTile == null || mTile.isPackageSuspended() || mTile.isUserQuieted()) {
if (DEBUG) Log.d(TAG, "Create suppressed view: " + mTile);
return createSuppressedView();
}
+ if (isDndBlockingTileData(mTile)) {
+ if (DEBUG) Log.d(TAG, "Create dnd view");
+ return createDndRemoteViews().mRemoteViews;
+ }
+
if (Objects.equals(mTile.getNotificationCategory(), CATEGORY_MISSED_CALL)) {
if (DEBUG) Log.d(TAG, "Create missed call view");
return createMissedCallRemoteViews();
@@ -236,7 +295,9 @@ public class PeopleTileViewHelper {
return createLastInteractionRemoteViews();
}
- private boolean isDndBlockingTileData(PeopleSpaceTile tile) {
+ private static boolean isDndBlockingTileData(@Nullable PeopleSpaceTile tile) {
+ if (tile == null) return false;
+
int notificationPolicyState = tile.getNotificationPolicyState();
if ((notificationPolicyState & PeopleSpaceTile.SHOW_CONVERSATIONS) != 0) {
// Not in DND, or all conversations
@@ -273,11 +334,8 @@ public class PeopleTileViewHelper {
R.layout.people_tile_suppressed_layout);
}
Drawable appIcon = mContext.getDrawable(R.drawable.ic_conversation_icon);
- Bitmap appIconAsBitmap = convertDrawableToBitmap(appIcon);
- FastBitmapDrawable drawable = new FastBitmapDrawable(appIconAsBitmap);
- drawable.setIsDisabled(true);
- Bitmap convertedBitmap = convertDrawableToBitmap(drawable);
- views.setImageViewBitmap(R.id.icon, convertedBitmap);
+ Bitmap disabledBitmap = convertDrawableToDisabledBitmap(appIcon);
+ views.setImageViewBitmap(R.id.icon, disabledBitmap);
return views;
}
@@ -350,7 +408,8 @@ public class PeopleTileViewHelper {
return LAYOUT_LARGE;
}
// Small layout used below a certain minimum mWidth with any mHeight.
- if (mWidth >= getSizeInDp(R.dimen.required_width_for_medium)) {
+ if (mHeight >= getSizeInDp(R.dimen.required_height_for_medium)
+ && mWidth >= getSizeInDp(R.dimen.required_width_for_medium)) {
int spaceAvailableForPadding =
mHeight - (getSizeInDp(R.dimen.avatar_size_for_medium)
+ 4 + getLineHeightFromResource(
@@ -383,10 +442,15 @@ public class PeopleTileViewHelper {
// Calculate adaptive avatar size for remaining layouts.
if (layoutId == R.layout.people_tile_small) {
- int avatarHeightSpace = mHeight - (FIXED_HEIGHT_DIMENS_FOR_SMALL + Math.max(18,
+ int avatarHeightSpace = mHeight - (FIXED_HEIGHT_DIMENS_FOR_SMALL_VERTICAL + Math.max(18,
getLineHeightFromResource(
R.dimen.name_text_size_for_small)));
- int avatarWidthSpace = mWidth - FIXED_WIDTH_DIMENS_FOR_SMALL;
+ int avatarWidthSpace = mWidth - FIXED_WIDTH_DIMENS_FOR_SMALL_VERTICAL;
+ avatarSize = Math.min(avatarHeightSpace, avatarWidthSpace);
+ }
+ if (layoutId == R.layout.people_tile_small_horizontal) {
+ int avatarHeightSpace = mHeight - FIXED_HEIGHT_DIMENS_FOR_SMALL_HORIZONTAL;
+ int avatarWidthSpace = mWidth - FIXED_WIDTH_DIMENS_FOR_SMALL_HORIZONTAL;
avatarSize = Math.min(avatarHeightSpace, avatarWidthSpace);
}
@@ -413,6 +477,11 @@ public class PeopleTileViewHelper {
int avatarWidthSpace = mWidth - (14 + 14);
avatarSize = Math.min(avatarHeightSpace, avatarWidthSpace);
}
+
+ if (isDndBlockingTileData(mTile) && mLayoutSize != LAYOUT_SMALL) {
+ avatarSize = createDndRemoteViews().mAvatarSize;
+ }
+
return Math.min(avatarSize,
getSizeInDp(R.dimen.max_people_avatar_size));
}
@@ -426,15 +495,33 @@ public class PeopleTileViewHelper {
boolean isAvailable =
mTile.getStatuses() != null && mTile.getStatuses().stream().anyMatch(
c -> c.getAvailability() == AVAILABILITY_AVAILABLE);
+
+ int startPadding;
if (isAvailable) {
views.setViewVisibility(R.id.availability, View.VISIBLE);
+ startPadding = mContext.getResources().getDimensionPixelSize(
+ R.dimen.availability_dot_shown_padding);
} else {
views.setViewVisibility(R.id.availability, View.GONE);
+ startPadding = mContext.getResources().getDimensionPixelSize(
+ R.dimen.availability_dot_missing_padding);
}
+ boolean isLeftToRight = TextUtils.getLayoutDirectionFromLocale(Locale.getDefault())
+ == View.LAYOUT_DIRECTION_LTR;
+ views.setViewPadding(R.id.padding_before_availability,
+ isLeftToRight ? startPadding : 0, 0, isLeftToRight ? 0 : startPadding,
+ 0);
- views.setBoolean(R.id.image, "setClipToOutline", true);
+ boolean hasNewStory = getHasNewStory(mTile);
views.setImageViewBitmap(R.id.person_icon,
- getPersonIconBitmap(mContext, mTile, maxAvatarSize));
+ getPersonIconBitmap(mContext, mTile, maxAvatarSize, hasNewStory));
+ if (hasNewStory) {
+ views.setContentDescription(R.id.person_icon,
+ mContext.getString(R.string.new_story_status_content_description,
+ mTile.getUserName()));
+ } else {
+ views.setContentDescription(R.id.person_icon, null);
+ }
return views;
} catch (Exception e) {
Log.e(TAG, "Failed to set common fields: " + e);
@@ -442,7 +529,17 @@ public class PeopleTileViewHelper {
return views;
}
+ private static boolean getHasNewStory(PeopleSpaceTile tile) {
+ return tile.getStatuses() != null && tile.getStatuses().stream().anyMatch(
+ c -> c.getActivity() == ACTIVITY_NEW_STORY);
+ }
+
private RemoteViews setLaunchIntents(RemoteViews views) {
+ if (!PeopleTileKey.isValid(mKey) || mTile == null) {
+ if (DEBUG) Log.d(TAG, "Skipping launch intent, Null tile or invalid key: " + mKey);
+ return views;
+ }
+
try {
Intent activityIntent = new Intent(mContext, LaunchConversationActivity.class);
activityIntent.addFlags(
@@ -460,7 +557,7 @@ public class PeopleTileViewHelper {
PeopleSpaceWidgetProvider.EXTRA_NOTIFICATION_KEY,
mTile.getNotificationKey());
}
- views.setOnClickPendingIntent(R.id.item, PendingIntent.getActivity(
+ views.setOnClickPendingIntent(android.R.id.background, PendingIntent.getActivity(
mContext,
mAppWidgetId,
activityIntent,
@@ -473,6 +570,87 @@ public class PeopleTileViewHelper {
return views;
}
+ private RemoteViewsAndSizes createDndRemoteViews() {
+ RemoteViews views = new RemoteViews(mContext.getPackageName(), getViewForDndRemoteViews());
+
+ int mediumAvatarSize = getSizeInDp(R.dimen.avatar_size_for_medium_empty);
+ int maxAvatarSize = getSizeInDp(R.dimen.max_people_avatar_size);
+
+ String text = mContext.getString(R.string.paused_by_dnd);
+ views.setTextViewText(R.id.text_content, text);
+
+ int textSizeResId =
+ mLayoutSize == LAYOUT_LARGE
+ ? R.dimen.content_text_size_for_large
+ : R.dimen.content_text_size_for_medium;
+ float textSizePx = mContext.getResources().getDimension(textSizeResId);
+ views.setTextViewTextSize(R.id.text_content, COMPLEX_UNIT_PX, textSizePx);
+ int lineHeight = getLineHeightFromResource(textSizeResId);
+
+ int avatarSize;
+ if (mLayoutSize == LAYOUT_MEDIUM) {
+ int maxTextHeight = mHeight - 16;
+ views.setInt(R.id.text_content, "setMaxLines", maxTextHeight / lineHeight);
+ avatarSize = mediumAvatarSize;
+ } else {
+ int outerPadding = 16;
+ int outerPaddingTop = outerPadding - 2;
+ int outerPaddingPx = dpToPx(outerPadding);
+ int outerPaddingTopPx = dpToPx(outerPaddingTop);
+ int iconSize =
+ getSizeInDp(
+ mLayoutSize == LAYOUT_SMALL
+ ? R.dimen.regular_predefined_icon
+ : R.dimen.largest_predefined_icon);
+ int heightWithoutIcon = mHeight - 2 * outerPadding - iconSize;
+ int paddingBetweenElements =
+ getSizeInDp(R.dimen.padding_between_suppressed_layout_items);
+ int maxTextWidth = mWidth - outerPadding * 2;
+ int maxTextHeight = heightWithoutIcon - mediumAvatarSize - paddingBetweenElements * 2;
+
+ int availableAvatarHeight;
+ int textHeight = estimateTextHeight(text, textSizeResId, maxTextWidth);
+ if (textHeight <= maxTextHeight && mLayoutSize == LAYOUT_LARGE) {
+ // If the text will fit, then display it and deduct its height from the space we
+ // have for the avatar.
+ availableAvatarHeight = heightWithoutIcon - textHeight - paddingBetweenElements * 2;
+ views.setViewVisibility(R.id.text_content, View.VISIBLE);
+ views.setInt(R.id.text_content, "setMaxLines", maxTextHeight / lineHeight);
+ views.setContentDescription(R.id.predefined_icon, null);
+ int availableAvatarWidth = mWidth - outerPadding * 2;
+ avatarSize =
+ MathUtils.clamp(
+ /* value= */ Math.min(availableAvatarWidth, availableAvatarHeight),
+ /* min= */ dpToPx(10),
+ /* max= */ maxAvatarSize);
+ views.setViewPadding(
+ android.R.id.background,
+ outerPaddingPx,
+ outerPaddingTopPx,
+ outerPaddingPx,
+ outerPaddingPx);
+ views.setViewLayoutWidth(R.id.predefined_icon, iconSize, COMPLEX_UNIT_DIP);
+ views.setViewLayoutHeight(R.id.predefined_icon, iconSize, COMPLEX_UNIT_DIP);
+ } else {
+ // If expected to use LAYOUT_LARGE, but we found we do not have space for the
+ // text as calculated above, re-assign the view to the small layout.
+ if (mLayoutSize != LAYOUT_SMALL) {
+ views = new RemoteViews(mContext.getPackageName(), R.layout.people_tile_small);
+ }
+ avatarSize = getMaxAvatarSize(views);
+ views.setViewVisibility(R.id.messages_count, View.GONE);
+ views.setViewVisibility(R.id.name, View.GONE);
+ // If we don't show the dnd text, set it as the content description on the icon
+ // for a11y.
+ views.setContentDescription(R.id.predefined_icon, text);
+ }
+ views.setViewVisibility(R.id.predefined_icon, View.VISIBLE);
+ views.setImageViewResource(R.id.predefined_icon, R.drawable.ic_qs_dnd_on);
+ }
+
+ return new RemoteViewsAndSizes(views, avatarSize);
+ }
+
private RemoteViews createMissedCallRemoteViews() {
RemoteViews views = setViewForContentLayout(new RemoteViews(mContext.getPackageName(),
getLayoutForContent()));
@@ -480,14 +658,16 @@ public class PeopleTileViewHelper {
views.setViewVisibility(R.id.text_content, View.VISIBLE);
views.setViewVisibility(R.id.messages_count, View.GONE);
setMaxLines(views, false);
- views.setTextViewText(R.id.text_content, mTile.getNotificationContent());
+ CharSequence content = mTile.getNotificationContent();
+ views.setTextViewText(R.id.text_content, content);
+ setContentDescriptionForNotificationTextContent(views, content, mTile.getUserName());
views.setColorAttr(R.id.text_content, "setTextColor", android.R.attr.colorError);
views.setColorAttr(R.id.predefined_icon, "setColorFilter", android.R.attr.colorError);
views.setImageViewResource(R.id.predefined_icon, R.drawable.ic_phone_missed);
if (mLayoutSize == LAYOUT_LARGE) {
views.setInt(R.id.content, "setGravity", Gravity.BOTTOM);
- views.setViewLayoutHeightDimen(R.id.predefined_icon, R.dimen.large_predefined_icon);
- views.setViewLayoutWidthDimen(R.id.predefined_icon, R.dimen.large_predefined_icon);
+ views.setViewLayoutHeightDimen(R.id.predefined_icon, R.dimen.larger_predefined_icon);
+ views.setViewLayoutWidthDimen(R.id.predefined_icon, R.dimen.larger_predefined_icon);
}
setAvailabilityDotPadding(views, R.dimen.availability_dot_notification_padding);
return views;
@@ -501,12 +681,16 @@ public class PeopleTileViewHelper {
if (image != null) {
// TODO: Use NotificationInlineImageCache
views.setImageViewUri(R.id.image, image);
+ String newImageDescription = mContext.getString(
+ R.string.new_notification_image_content_description, mTile.getUserName());
+ views.setContentDescription(R.id.image, newImageDescription);
views.setViewVisibility(R.id.image, View.VISIBLE);
views.setViewVisibility(R.id.text_content, View.GONE);
- views.setImageViewResource(R.id.predefined_icon, R.drawable.ic_photo_camera);
} else {
setMaxLines(views, !TextUtils.isEmpty(sender));
CharSequence content = mTile.getNotificationContent();
+ setContentDescriptionForNotificationTextContent(views, content,
+ sender != null ? sender : mTile.getUserName());
views = decorateBackground(views, content);
views.setColorAttr(R.id.text_content, "setTextColor", android.R.attr.textColorPrimary);
views.setTextViewText(R.id.text_content, mTile.getNotificationContent());
@@ -536,6 +720,16 @@ public class PeopleTileViewHelper {
return views;
}
+ private void setContentDescriptionForNotificationTextContent(RemoteViews views,
+ CharSequence content, CharSequence sender) {
+ String newTextDescriptionWithNotificationContent = mContext.getString(
+ R.string.new_notification_text_content_description, sender, content);
+ int idForContentDescription =
+ mLayoutSize == LAYOUT_SMALL ? R.id.predefined_icon : R.id.text_content;
+ views.setContentDescription(idForContentDescription,
+ newTextDescriptionWithNotificationContent);
+ }
+
// Some messaging apps only include up to 6 messages in their notifications.
private String getMessagesCountText(int count) {
if (count >= MESSAGES_COUNT_OVERFLOW) {
@@ -564,6 +758,11 @@ public class PeopleTileViewHelper {
views.setViewVisibility(R.id.predefined_icon, View.VISIBLE);
views.setTextViewText(R.id.text_content, statusText);
+ if (status.getActivity() == ACTIVITY_BIRTHDAY
+ || status.getActivity() == ACTIVITY_UPCOMING_BIRTHDAY) {
+ setEmojiBackground(views, EMOJI_CAKE);
+ }
+
Icon statusIcon = status.getIcon();
if (statusIcon != null) {
// No text content styled text on medium or large.
@@ -588,9 +787,56 @@ public class PeopleTileViewHelper {
}
setAvailabilityDotPadding(views, R.dimen.availability_dot_status_padding);
views.setImageViewResource(R.id.predefined_icon, getDrawableForStatus(status));
+ CharSequence descriptionForStatus =
+ getContentDescriptionForStatus(status);
+ CharSequence customContentDescriptionForStatus = mContext.getString(
+ R.string.new_status_content_description, mTile.getUserName(), descriptionForStatus);
+ switch (mLayoutSize) {
+ case LAYOUT_LARGE:
+ views.setContentDescription(R.id.text_content,
+ customContentDescriptionForStatus);
+ break;
+ case LAYOUT_MEDIUM:
+ views.setContentDescription(statusIcon == null ? R.id.text_content : R.id.name,
+ customContentDescriptionForStatus);
+ break;
+ case LAYOUT_SMALL:
+ views.setContentDescription(R.id.predefined_icon,
+ customContentDescriptionForStatus);
+ break;
+ }
return views;
}
+ private CharSequence getContentDescriptionForStatus(ConversationStatus status) {
+ CharSequence name = mTile.getUserName();
+ if (!TextUtils.isEmpty(status.getDescription())) {
+ return status.getDescription();
+ }
+ switch (status.getActivity()) {
+ case ACTIVITY_NEW_STORY:
+ return mContext.getString(R.string.new_story_status_content_description,
+ name);
+ case ACTIVITY_ANNIVERSARY:
+ return mContext.getString(R.string.anniversary_status_content_description, name);
+ case ACTIVITY_UPCOMING_BIRTHDAY:
+ return mContext.getString(R.string.upcoming_birthday_status_content_description,
+ name);
+ case ACTIVITY_BIRTHDAY:
+ return mContext.getString(R.string.birthday_status_content_description, name);
+ case ACTIVITY_LOCATION:
+ return mContext.getString(R.string.location_status_content_description, name);
+ case ACTIVITY_GAME:
+ return mContext.getString(R.string.game_status);
+ case ACTIVITY_VIDEO:
+ return mContext.getString(R.string.video_status);
+ case ACTIVITY_AUDIO:
+ return mContext.getString(R.string.audio_status);
+ default:
+ return EMPTY_STRING;
+ }
+ }
+
private int getDrawableForStatus(ConversationStatus status) {
switch (status.getActivity()) {
case ACTIVITY_NEW_STORY:
@@ -798,6 +1044,11 @@ public class PeopleTileViewHelper {
private RemoteViews setViewForContentLayout(RemoteViews views) {
views = decorateBackground(views, "");
+ views.setContentDescription(R.id.predefined_icon, null);
+ views.setContentDescription(R.id.text_content, null);
+ views.setContentDescription(R.id.name, null);
+ views.setContentDescription(R.id.image, null);
+ views.setAccessibilityTraversalAfter(R.id.text_content, R.id.name);
if (mLayoutSize == LAYOUT_SMALL) {
views.setViewVisibility(R.id.predefined_icon, View.VISIBLE);
views.setViewVisibility(R.id.name, View.GONE);
@@ -884,7 +1135,7 @@ public class PeopleTileViewHelper {
return R.layout.people_tile_large_empty;
case LAYOUT_SMALL:
default:
- return R.layout.people_tile_small;
+ return getLayoutSmallByHeight();
}
}
@@ -896,7 +1147,7 @@ public class PeopleTileViewHelper {
return R.layout.people_tile_large_with_notification_content;
case LAYOUT_SMALL:
default:
- return R.layout.people_tile_small;
+ return getLayoutSmallByHeight();
}
}
@@ -908,20 +1159,43 @@ public class PeopleTileViewHelper {
return R.layout.people_tile_large_with_status_content;
case LAYOUT_SMALL:
default:
- return R.layout.people_tile_small;
+ return getLayoutSmallByHeight();
}
}
+ private int getViewForDndRemoteViews() {
+ switch (mLayoutSize) {
+ case LAYOUT_MEDIUM:
+ return R.layout.people_tile_with_suppression_detail_content_horizontal;
+ case LAYOUT_LARGE:
+ return R.layout.people_tile_with_suppression_detail_content_vertical;
+ case LAYOUT_SMALL:
+ default:
+ return getLayoutSmallByHeight();
+ }
+ }
+
+ private int getLayoutSmallByHeight() {
+ if (mHeight >= getSizeInDp(R.dimen.required_height_for_medium)) {
+ return R.layout.people_tile_small;
+ }
+ return R.layout.people_tile_small_horizontal;
+ }
+
/** 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);
+ public static Bitmap getPersonIconBitmap(Context context, PeopleSpaceTile tile,
+ int maxAvatarSize) {
+ boolean hasNewStory = getHasNewStory(tile);
+ return getPersonIconBitmap(context, tile, maxAvatarSize, hasNewStory);
+ }
+ /** Returns a bitmap with the user icon and package icon. */
+ private static Bitmap getPersonIconBitmap(
+ Context context, PeopleSpaceTile tile, int maxAvatarSize, boolean hasNewStory) {
Icon icon = tile.getUserIcon();
if (icon == null) {
- return null;
+ Drawable placeholder = context.getDrawable(R.drawable.ic_avatar_with_badge);
+ return convertDrawableToDisabledBitmap(placeholder);
}
PeopleStoryIconFactory storyIcon = new PeopleStoryIconFactory(context,
context.getPackageManager(),
@@ -932,6 +1206,14 @@ public class PeopleTileViewHelper {
Drawable personDrawable = storyIcon.getPeopleTileDrawable(roundedDrawable,
tile.getPackageName(), getUserId(tile), tile.isImportantConversation(),
hasNewStory);
+
+ if (isDndBlockingTileData(tile)) {
+ // If DND is blocking the conversation, then display the icon in grayscale.
+ ColorMatrix colorMatrix = new ColorMatrix();
+ colorMatrix.setSaturation(0);
+ personDrawable.setColorFilter(new ColorMatrixColorFilter(colorMatrix));
+ }
+
return convertDrawableToBitmap(personDrawable);
}
@@ -960,4 +1242,76 @@ public class PeopleTileViewHelper {
return context.getString(R.string.over_two_weeks_timestamp);
}
}
+
+ /**
+ * Estimates the height (in dp) which the text will have given the text size and the available
+ * width. Returns Integer.MAX_VALUE if the estimation couldn't be obtained, as this is intended
+ * to be used an estimate of the maximum.
+ */
+ private int estimateTextHeight(
+ CharSequence text,
+ @DimenRes int textSizeResId,
+ int availableWidthDp) {
+ StaticLayout staticLayout = buildStaticLayout(text, textSizeResId, availableWidthDp);
+ if (staticLayout == null) {
+ // Return max value (rather than e.g. -1) so the value can be used with <= bound checks.
+ return Integer.MAX_VALUE;
+ }
+ return pxToDp(staticLayout.getHeight());
+ }
+
+ /**
+ * Builds a StaticLayout for the text given the text size and available width. This can be used
+ * to obtain information about how TextView will lay out the text. Returns null if any error
+ * occurred creating a TextView.
+ */
+ @Nullable
+ private StaticLayout buildStaticLayout(
+ CharSequence text,
+ @DimenRes int textSizeResId,
+ int availableWidthDp) {
+ try {
+ TextView textView = new TextView(mContext);
+ textView.setTextSize(TypedValue.COMPLEX_UNIT_PX,
+ mContext.getResources().getDimension(textSizeResId));
+ textView.setTextAppearance(android.R.style.TextAppearance_DeviceDefault);
+ TextPaint paint = textView.getPaint();
+ return StaticLayout.Builder.obtain(
+ text, 0, text.length(), paint, dpToPx(availableWidthDp))
+ // Simple break strategy avoids hyphenation unless there's a single word longer
+ // than the line width. We use this break strategy so that we consider text to
+ // "fit" only if it fits in a nice way (i.e. without hyphenation in the middle
+ // of words).
+ .setBreakStrategy(LineBreaker.BREAK_STRATEGY_SIMPLE)
+ .build();
+ } catch (Exception e) {
+ Log.e(TAG, "Could not create static layout: " + e);
+ return null;
+ }
+ }
+
+ private int dpToPx(float dp) {
+ return (int) (dp * mDensity);
+ }
+
+ private int pxToDp(@Px float px) {
+ return (int) (px / mDensity);
+ }
+
+ private static final class RemoteViewsAndSizes {
+ final RemoteViews mRemoteViews;
+ final int mAvatarSize;
+
+ RemoteViewsAndSizes(RemoteViews remoteViews, int avatarSize) {
+ mRemoteViews = remoteViews;
+ mAvatarSize = avatarSize;
+ }
+ }
+
+ private static Bitmap convertDrawableToDisabledBitmap(Drawable icon) {
+ Bitmap appIconAsBitmap = convertDrawableToBitmap(icon);
+ FastBitmapDrawable drawable = new FastBitmapDrawable(appIconAsBitmap);
+ drawable.setIsDisabled(true);
+ return convertDrawableToBitmap(drawable);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/people/SharedPreferencesHelper.java b/packages/SystemUI/src/com/android/systemui/people/SharedPreferencesHelper.java
new file mode 100644
index 000000000000..aef08fb421d8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/people/SharedPreferencesHelper.java
@@ -0,0 +1,59 @@
+/*
+ * 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.people;
+
+import static com.android.systemui.people.PeopleSpaceUtils.INVALID_USER_ID;
+import static com.android.systemui.people.PeopleSpaceUtils.PACKAGE_NAME;
+import static com.android.systemui.people.PeopleSpaceUtils.SHORTCUT_ID;
+import static com.android.systemui.people.PeopleSpaceUtils.USER_ID;
+
+import android.content.SharedPreferences;
+
+import com.android.systemui.people.widget.PeopleTileKey;
+
+/** Helper class for Conversations widgets SharedPreferences storage. */
+public class SharedPreferencesHelper {
+ /** Clears all storage from {@code sp}. */
+ public static void clear(SharedPreferences sp) {
+ SharedPreferences.Editor editor = sp.edit();
+ editor.clear();
+ editor.apply();
+ }
+
+ /** Sets {@code sp}'s storage to identify a {@link PeopleTileKey}. */
+ public static void setPeopleTileKey(SharedPreferences sp, PeopleTileKey key) {
+ setPeopleTileKey(sp, key.getShortcutId(), key.getUserId(), key.getPackageName());
+ }
+
+ /** Sets {@code sp}'s storage to identify a {@link PeopleTileKey}. */
+ public static void setPeopleTileKey(SharedPreferences sp, String shortcutId, int userId,
+ String packageName) {
+ SharedPreferences.Editor editor = sp.edit();
+ editor.putString(SHORTCUT_ID, shortcutId);
+ editor.putInt(USER_ID, userId);
+ editor.putString(PACKAGE_NAME, packageName);
+ editor.apply();
+ }
+
+ /** Returns a {@link PeopleTileKey} based on storage from {@code sp}. */
+ public static PeopleTileKey getPeopleTileKey(SharedPreferences sp) {
+ String shortcutId = sp.getString(SHORTCUT_ID, null);
+ String packageName = sp.getString(PACKAGE_NAME, null);
+ int userId = sp.getInt(USER_ID, INVALID_USER_ID);
+ return new PeopleTileKey(shortcutId, userId, packageName);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/people/widget/LaunchConversationActivity.java b/packages/SystemUI/src/com/android/systemui/people/widget/LaunchConversationActivity.java
index b031637e4016..79318d69837d 100644
--- a/packages/SystemUI/src/com/android/systemui/people/widget/LaunchConversationActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/people/widget/LaunchConversationActivity.java
@@ -152,7 +152,7 @@ public class LaunchConversationActivity extends Activity {
launcherApps.startShortcut(
packageName, tileId, null, null, userHandle);
} catch (Exception e) {
- Log.e(TAG, "Exception:" + e);
+ Log.e(TAG, "Exception launching shortcut:" + e);
}
} else {
if (DEBUG) Log.d(TAG, "Trying to launch conversation with null shortcutInfo.");
diff --git a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleBackupHelper.java b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleBackupHelper.java
new file mode 100644
index 000000000000..d8c96dd182b4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleBackupHelper.java
@@ -0,0 +1,508 @@
+/*
+ * 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.people.widget;
+
+import static com.android.systemui.people.PeopleBackupFollowUpJob.SHARED_FOLLOW_UP;
+import static com.android.systemui.people.PeopleSpaceUtils.DEBUG;
+import static com.android.systemui.people.PeopleSpaceUtils.INVALID_USER_ID;
+import static com.android.systemui.people.PeopleSpaceUtils.USER_ID;
+
+import android.app.backup.BackupDataInputStream;
+import android.app.backup.BackupDataOutput;
+import android.app.backup.SharedPreferencesBackupHelper;
+import android.app.people.IPeopleManager;
+import android.appwidget.AppWidgetManager;
+import android.content.ComponentName;
+import android.content.ContentProvider;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.net.Uri;
+import android.os.ParcelFileDescriptor;
+import android.os.ServiceManager;
+import android.os.UserHandle;
+import android.preference.PreferenceManager;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.people.PeopleBackupFollowUpJob;
+import com.android.systemui.people.SharedPreferencesHelper;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+/**
+ * Helper class to backup and restore Conversations widgets storage.
+ * It is used by SystemUI's BackupHelper agent.
+ * TODO(b/192334798): Lock access to storage using PeopleSpaceWidgetManager's lock.
+ */
+public class PeopleBackupHelper extends SharedPreferencesBackupHelper {
+ private static final String TAG = "PeopleBackupHelper";
+
+ public static final String ADD_USER_ID_TO_URI = "add_user_id_to_uri_";
+ public static final String SHARED_BACKUP = "shared_backup";
+
+ private final Context mContext;
+ private final UserHandle mUserHandle;
+ private final PackageManager mPackageManager;
+ private final IPeopleManager mIPeopleManager;
+ private final AppWidgetManager mAppWidgetManager;
+
+ /**
+ * Types of entries stored in the default SharedPreferences file for Conversation widgets.
+ * Widget ID corresponds to a pair [widgetId, contactURI].
+ * PeopleTileKey corresponds to a pair [PeopleTileKey, {widgetIds}].
+ * Contact URI corresponds to a pair [Contact URI, {widgetIds}].
+ */
+ enum SharedFileEntryType {
+ UNKNOWN,
+ WIDGET_ID,
+ PEOPLE_TILE_KEY,
+ CONTACT_URI
+ }
+
+ /**
+ * Returns the file names that should be backed up and restored by SharedPreferencesBackupHelper
+ * infrastructure.
+ */
+ public static List<String> getFilesToBackup() {
+ return Collections.singletonList(SHARED_BACKUP);
+ }
+
+ public PeopleBackupHelper(Context context, UserHandle userHandle,
+ String[] sharedPreferencesKey) {
+ super(context, sharedPreferencesKey);
+ mContext = context;
+ mUserHandle = userHandle;
+ mPackageManager = context.getPackageManager();
+ mIPeopleManager = IPeopleManager.Stub.asInterface(
+ ServiceManager.getService(Context.PEOPLE_SERVICE));
+ mAppWidgetManager = AppWidgetManager.getInstance(context);
+ }
+
+ @VisibleForTesting
+ public PeopleBackupHelper(Context context, UserHandle userHandle,
+ String[] sharedPreferencesKey, PackageManager packageManager,
+ IPeopleManager peopleManager) {
+ super(context, sharedPreferencesKey);
+ mContext = context;
+ mUserHandle = userHandle;
+ mPackageManager = packageManager;
+ mIPeopleManager = peopleManager;
+ mAppWidgetManager = AppWidgetManager.getInstance(context);
+ }
+
+ /**
+ * Reads values from default storage, backs them up appropriately to a specified backup file,
+ * and calls super's performBackup, which backs up the values of the backup file.
+ */
+ @Override
+ public void performBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
+ ParcelFileDescriptor newState) {
+ if (DEBUG) Log.d(TAG, "Backing up conversation widgets, writing to: " + SHARED_BACKUP);
+ // Open default value for readings values.
+ SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mContext);
+ if (sp.getAll().isEmpty()) {
+ if (DEBUG) Log.d(TAG, "No information to be backed up, finishing.");
+ return;
+ }
+
+ // Open backup file for writing.
+ SharedPreferences backupSp = mContext.getSharedPreferences(
+ SHARED_BACKUP, Context.MODE_PRIVATE);
+ SharedPreferences.Editor backupEditor = backupSp.edit();
+ backupEditor.clear();
+
+ // Fetch Conversations widgets corresponding to this user.
+ List<String> existingWidgets = getExistingWidgetsForUser(mUserHandle.getIdentifier());
+ if (existingWidgets.isEmpty()) {
+ if (DEBUG) Log.d(TAG, "No existing Conversations widgets, returning.");
+ return;
+ }
+
+ // Writes each entry to backup file.
+ sp.getAll().entrySet().forEach(entry -> backupKey(entry, backupEditor, existingWidgets));
+ backupEditor.apply();
+
+ super.performBackup(oldState, data, newState);
+ }
+
+ /**
+ * Restores backed up values to backup file via super's restoreEntity, then transfers them
+ * back to regular storage. Restore operations for each users are done in sequence, so we can
+ * safely use the same backup file names.
+ */
+ @Override
+ public void restoreEntity(BackupDataInputStream data) {
+ if (DEBUG) Log.d(TAG, "Restoring Conversation widgets.");
+ super.restoreEntity(data);
+
+ // Open backup file for reading values.
+ SharedPreferences backupSp = mContext.getSharedPreferences(
+ SHARED_BACKUP, Context.MODE_PRIVATE);
+
+ // Open default file and follow-up file for writing.
+ SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mContext);
+ SharedPreferences.Editor editor = sp.edit();
+ SharedPreferences followUp = mContext.getSharedPreferences(
+ SHARED_FOLLOW_UP, Context.MODE_PRIVATE);
+ SharedPreferences.Editor followUpEditor = followUp.edit();
+
+ // Writes each entry back to default value.
+ boolean shouldScheduleJob = false;
+ for (Map.Entry<String, ?> entry : backupSp.getAll().entrySet()) {
+ boolean restored = restoreKey(entry, editor, followUpEditor, backupSp);
+ if (!restored) {
+ shouldScheduleJob = true;
+ }
+ }
+
+ editor.apply();
+ followUpEditor.apply();
+ SharedPreferencesHelper.clear(backupSp);
+
+ // If any of the widgets is not yet available, schedule a follow-up job to check later.
+ if (shouldScheduleJob) {
+ if (DEBUG) Log.d(TAG, "At least one shortcut is not available, scheduling follow-up.");
+ PeopleBackupFollowUpJob.scheduleJob(mContext);
+ }
+
+ updateWidgets(mContext);
+ }
+
+ /** Backs up an entry from default file to backup file. */
+ public void backupKey(Map.Entry<String, ?> entry, SharedPreferences.Editor backupEditor,
+ List<String> existingWidgets) {
+ String key = entry.getKey();
+ if (TextUtils.isEmpty(key)) {
+ return;
+ }
+
+ SharedFileEntryType entryType = getEntryType(entry);
+ switch(entryType) {
+ case WIDGET_ID:
+ backupWidgetIdKey(key, String.valueOf(entry.getValue()), backupEditor,
+ existingWidgets);
+ break;
+ case PEOPLE_TILE_KEY:
+ backupPeopleTileKey(key, (Set<String>) entry.getValue(), backupEditor,
+ existingWidgets);
+ break;
+ case CONTACT_URI:
+ backupContactUriKey(key, (Set<String>) entry.getValue(), backupEditor);
+ break;
+ case UNKNOWN:
+ default:
+ Log.w(TAG, "Key not identified, skipping: " + key);
+ }
+ }
+
+ /**
+ * Tries to restore an entry from backup file to default file.
+ * Returns true if restore is finished, false if it needs to be checked later.
+ */
+ boolean restoreKey(Map.Entry<String, ?> entry, SharedPreferences.Editor editor,
+ SharedPreferences.Editor followUpEditor, SharedPreferences backupSp) {
+ String key = entry.getKey();
+ SharedFileEntryType keyType = getEntryType(entry);
+ int storedUserId = backupSp.getInt(ADD_USER_ID_TO_URI + key, INVALID_USER_ID);
+ switch (keyType) {
+ case WIDGET_ID:
+ restoreWidgetIdKey(key, String.valueOf(entry.getValue()), editor, storedUserId);
+ return true;
+ case PEOPLE_TILE_KEY:
+ return restorePeopleTileKeyAndCorrespondingWidgetFile(
+ key, (Set<String>) entry.getValue(), editor, followUpEditor);
+ case CONTACT_URI:
+ restoreContactUriKey(key, (Set<String>) entry.getValue(), editor, storedUserId);
+ return true;
+ case UNKNOWN:
+ default:
+ Log.e(TAG, "Key not identified, skipping:" + key);
+ return true;
+ }
+ }
+
+ /**
+ * Backs up a [widgetId, contactURI] pair, if widget id corresponds to current user.
+ * If contact URI has a user id, stores it so it can be re-added on restore.
+ */
+ private void backupWidgetIdKey(String key, String uriString, SharedPreferences.Editor editor,
+ List<String> existingWidgets) {
+ if (!existingWidgets.contains(key)) {
+ if (DEBUG) Log.d(TAG, "Widget: " + key + " does't correspond to this user, skipping.");
+ return;
+ }
+ Uri uri = Uri.parse(uriString);
+ if (ContentProvider.uriHasUserId(uri)) {
+ if (DEBUG) Log.d(TAG, "Contact URI value has user ID, removing from: " + uri);
+ int userId = ContentProvider.getUserIdFromUri(uri);
+ editor.putInt(ADD_USER_ID_TO_URI + key, userId);
+ uri = ContentProvider.getUriWithoutUserId(uri);
+ }
+ if (DEBUG) Log.d(TAG, "Backing up widgetId key: " + key + " . Value: " + uri.toString());
+ editor.putString(key, uri.toString());
+ }
+
+ /** Restores a [widgetId, contactURI] pair, and a potential {@code storedUserId}. */
+ private void restoreWidgetIdKey(String key, String uriString, SharedPreferences.Editor editor,
+ int storedUserId) {
+ Uri uri = Uri.parse(uriString);
+ if (storedUserId != INVALID_USER_ID) {
+ uri = ContentProvider.createContentUriForUser(uri, UserHandle.of(storedUserId));
+ if (DEBUG) Log.d(TAG, "UserId was removed from URI on back up, re-adding as:" + uri);
+
+ }
+ if (DEBUG) Log.d(TAG, "Restoring widgetId key: " + key + " . Value: " + uri.toString());
+ editor.putString(key, uri.toString());
+ }
+
+ /**
+ * Backs up a [PeopleTileKey, {widgetIds}] pair, if PeopleTileKey's user is the same as current
+ * user, stripping out the user id.
+ */
+ private void backupPeopleTileKey(String key, Set<String> widgetIds,
+ SharedPreferences.Editor editor, List<String> existingWidgets) {
+ PeopleTileKey peopleTileKey = PeopleTileKey.fromString(key);
+ if (peopleTileKey.getUserId() != mUserHandle.getIdentifier()) {
+ if (DEBUG) Log.d(TAG, "PeopleTileKey corresponds to different user, skipping backup.");
+ return;
+ }
+
+ Set<String> filteredWidgets = widgetIds.stream()
+ .filter(id -> existingWidgets.contains(id))
+ .collect(Collectors.toSet());
+ if (filteredWidgets.isEmpty()) {
+ return;
+ }
+
+ peopleTileKey.setUserId(INVALID_USER_ID);
+ if (DEBUG) {
+ Log.d(TAG, "Backing up PeopleTileKey key: " + peopleTileKey.toString() + ". Value: "
+ + filteredWidgets);
+ }
+ editor.putStringSet(peopleTileKey.toString(), filteredWidgets);
+ }
+
+ /**
+ * Restores a [PeopleTileKey, {widgetIds}] pair, restoring the user id. Checks if the
+ * corresponding shortcut exists, and if not, we should schedule a follow up to check later.
+ * Also restores corresponding [widgetId, PeopleTileKey], which is not backed up since the
+ * information can be inferred from this.
+ * Returns true if restore is finished, false if we should check if shortcut is available later.
+ */
+ private boolean restorePeopleTileKeyAndCorrespondingWidgetFile(String key,
+ Set<String> widgetIds, SharedPreferences.Editor editor,
+ SharedPreferences.Editor followUpEditor) {
+ PeopleTileKey peopleTileKey = PeopleTileKey.fromString(key);
+ // Should never happen, as type of key has been checked.
+ if (peopleTileKey == null) {
+ if (DEBUG) Log.d(TAG, "PeopleTileKey key to be restored is null, skipping.");
+ return true;
+ }
+
+ peopleTileKey.setUserId(mUserHandle.getIdentifier());
+ if (!PeopleTileKey.isValid(peopleTileKey)) {
+ if (DEBUG) Log.d(TAG, "PeopleTileKey key to be restored is not valid, skipping.");
+ return true;
+ }
+
+ boolean restored = isReadyForRestore(
+ mIPeopleManager, mPackageManager, peopleTileKey);
+ if (!restored) {
+ if (DEBUG) Log.d(TAG, "Adding key to follow-up storage: " + peopleTileKey.toString());
+ // Follow-up file stores shortcuts that need to be checked later, and possibly wiped
+ // from our storage.
+ followUpEditor.putStringSet(peopleTileKey.toString(), widgetIds);
+ }
+
+ if (DEBUG) {
+ Log.d(TAG, "Restoring PeopleTileKey key: " + peopleTileKey.toString() + " . Value: "
+ + widgetIds);
+ }
+ editor.putStringSet(peopleTileKey.toString(), widgetIds);
+ restoreWidgetIdFiles(mContext, widgetIds, peopleTileKey);
+ return restored;
+ }
+
+ /**
+ * Backs up a [contactURI, {widgetIds}] pair. If contactURI contains a userId, we back up
+ * this entry in the corresponding user. If it doesn't, we back it up as user 0.
+ * If contact URI has a user id, stores it so it can be re-added on restore.
+ * We do not take existing widgets for this user into consideration.
+ */
+ private void backupContactUriKey(String key, Set<String> widgetIds,
+ SharedPreferences.Editor editor) {
+ Uri uri = Uri.parse(String.valueOf(key));
+ if (ContentProvider.uriHasUserId(uri)) {
+ int userId = ContentProvider.getUserIdFromUri(uri);
+ if (DEBUG) Log.d(TAG, "Contact URI has user Id: " + userId);
+ if (userId == mUserHandle.getIdentifier()) {
+ uri = ContentProvider.getUriWithoutUserId(uri);
+ if (DEBUG) {
+ Log.d(TAG, "Backing up contactURI key: " + uri.toString() + " . Value: "
+ + widgetIds);
+ }
+ editor.putInt(ADD_USER_ID_TO_URI + uri.toString(), userId);
+ editor.putStringSet(uri.toString(), widgetIds);
+ } else {
+ if (DEBUG) Log.d(TAG, "ContactURI corresponds to different user, skipping.");
+ }
+ } else if (mUserHandle.isSystem()) {
+ if (DEBUG) {
+ Log.d(TAG, "Backing up contactURI key: " + uri.toString() + " . Value: "
+ + widgetIds);
+ }
+ editor.putStringSet(uri.toString(), widgetIds);
+ }
+ }
+
+ /** Restores a [contactURI, {widgetIds}] pair, and a potential {@code storedUserId}. */
+ private void restoreContactUriKey(String key, Set<String> widgetIds,
+ SharedPreferences.Editor editor, int storedUserId) {
+ Uri uri = Uri.parse(key);
+ if (storedUserId != INVALID_USER_ID) {
+ uri = ContentProvider.createContentUriForUser(uri, UserHandle.of(storedUserId));
+ if (DEBUG) Log.d(TAG, "UserId was removed from URI on back up, re-adding as:" + uri);
+ }
+ if (DEBUG) {
+ Log.d(TAG, "Restoring contactURI key: " + uri.toString() + " . Value: " + widgetIds);
+ }
+ editor.putStringSet(uri.toString(), widgetIds);
+ }
+
+ /** Restores the widget-specific files that contain PeopleTileKey information. */
+ public static void restoreWidgetIdFiles(Context context, Set<String> widgetIds,
+ PeopleTileKey key) {
+ for (String id : widgetIds) {
+ if (DEBUG) Log.d(TAG, "Restoring widget Id file: " + id + " . Value: " + key);
+ SharedPreferences dest = context.getSharedPreferences(id, Context.MODE_PRIVATE);
+ SharedPreferencesHelper.setPeopleTileKey(dest, key);
+ }
+ }
+
+ private List<String> getExistingWidgetsForUser(int userId) {
+ List<String> existingWidgets = new ArrayList<>();
+ int[] ids = mAppWidgetManager.getAppWidgetIds(
+ new ComponentName(mContext, PeopleSpaceWidgetProvider.class));
+ for (int id : ids) {
+ String idString = String.valueOf(id);
+ SharedPreferences sp = mContext.getSharedPreferences(idString, Context.MODE_PRIVATE);
+ if (sp.getInt(USER_ID, INVALID_USER_ID) == userId) {
+ existingWidgets.add(idString);
+ }
+ }
+ if (DEBUG) Log.d(TAG, "Existing widgets: " + existingWidgets);
+ return existingWidgets;
+ }
+
+ /**
+ * Returns whether {@code key} corresponds to a shortcut that is ready for restore, either
+ * because it is available or because it never will be. If not ready, we schedule a job to check
+ * again later.
+ */
+ public static boolean isReadyForRestore(IPeopleManager peopleManager,
+ PackageManager packageManager, PeopleTileKey key) {
+ if (DEBUG) Log.d(TAG, "Checking if we should schedule a follow up job : " + key);
+ if (!PeopleTileKey.isValid(key)) {
+ if (DEBUG) Log.d(TAG, "Key is invalid, should not follow up.");
+ return true;
+ }
+
+ try {
+ PackageInfo info = packageManager.getPackageInfoAsUser(
+ key.getPackageName(), 0, key.getUserId());
+ } catch (PackageManager.NameNotFoundException e) {
+ if (DEBUG) Log.d(TAG, "Package is not installed, should follow up.");
+ return false;
+ }
+
+ try {
+ boolean isConversation = peopleManager.isConversation(
+ key.getPackageName(), key.getUserId(), key.getShortcutId());
+ if (DEBUG) {
+ Log.d(TAG, "Checked if shortcut exists, should follow up: " + !isConversation);
+ }
+ return isConversation;
+ } catch (Exception e) {
+ if (DEBUG) Log.d(TAG, "Error checking if backed up info is a shortcut.");
+ return false;
+ }
+ }
+
+ /** Parses default file {@code entry} to determine the entry's type.*/
+ public static SharedFileEntryType getEntryType(Map.Entry<String, ?> entry) {
+ String key = entry.getKey();
+ if (key == null) {
+ return SharedFileEntryType.UNKNOWN;
+ }
+
+ try {
+ int id = Integer.parseInt(key);
+ try {
+ String contactUri = (String) entry.getValue();
+ } catch (Exception e) {
+ Log.w(TAG, "Malformed value, skipping:" + entry.getValue());
+ return SharedFileEntryType.UNKNOWN;
+ }
+ return SharedFileEntryType.WIDGET_ID;
+ } catch (NumberFormatException ignored) { }
+
+ try {
+ Set<String> widgetIds = (Set<String>) entry.getValue();
+ } catch (Exception e) {
+ Log.w(TAG, "Malformed value, skipping:" + entry.getValue());
+ return SharedFileEntryType.UNKNOWN;
+ }
+
+ PeopleTileKey peopleTileKey = PeopleTileKey.fromString(key);
+ if (peopleTileKey != null) {
+ return SharedFileEntryType.PEOPLE_TILE_KEY;
+ }
+
+ try {
+ Uri uri = Uri.parse(key);
+ return SharedFileEntryType.CONTACT_URI;
+ } catch (Exception e) {
+ return SharedFileEntryType.UNKNOWN;
+ }
+ }
+
+ /** Sends a broadcast to update the existing Conversation widgets. */
+ public static void updateWidgets(Context context) {
+ int[] widgetIds = AppWidgetManager.getInstance(context)
+ .getAppWidgetIds(new ComponentName(context, PeopleSpaceWidgetProvider.class));
+ if (DEBUG) {
+ for (int id : widgetIds) {
+ Log.d(TAG, "Calling update to widget: " + id);
+ }
+ }
+ if (widgetIds != null && widgetIds.length != 0) {
+ Intent intent = new Intent(context, PeopleSpaceWidgetProvider.class);
+ intent.setAction(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
+ intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, widgetIds);
+ context.sendBroadcast(intent);
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java
index 4085df9a8093..3320fbda6fe5 100644
--- a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java
+++ b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java
@@ -22,6 +22,7 @@ import static android.app.NotificationManager.INTERRUPTION_FILTER_ALL;
import static android.app.NotificationManager.INTERRUPTION_FILTER_NONE;
import static android.app.NotificationManager.INTERRUPTION_FILTER_PRIORITY;
import static android.content.Intent.ACTION_BOOT_COMPLETED;
+import static android.content.Intent.ACTION_PACKAGE_ADDED;
import static android.content.Intent.ACTION_PACKAGE_REMOVED;
import static android.service.notification.ZenPolicy.CONVERSATION_SENDERS_ANYONE;
@@ -29,6 +30,7 @@ import static com.android.systemui.people.NotificationHelper.getContactUri;
import static com.android.systemui.people.NotificationHelper.getHighestPriorityNotification;
import static com.android.systemui.people.NotificationHelper.shouldFilterOut;
import static com.android.systemui.people.NotificationHelper.shouldMatchNotificationByUri;
+import static com.android.systemui.people.PeopleBackupFollowUpJob.SHARED_FOLLOW_UP;
import static com.android.systemui.people.PeopleSpaceUtils.EMPTY_STRING;
import static com.android.systemui.people.PeopleSpaceUtils.INVALID_USER_ID;
import static com.android.systemui.people.PeopleSpaceUtils.PACKAGE_NAME;
@@ -38,6 +40,7 @@ import static com.android.systemui.people.PeopleSpaceUtils.augmentTileFromNotifi
import static com.android.systemui.people.PeopleSpaceUtils.getMessagesCount;
import static com.android.systemui.people.PeopleSpaceUtils.getNotificationsByUri;
import static com.android.systemui.people.PeopleSpaceUtils.removeNotificationFields;
+import static com.android.systemui.people.widget.PeopleBackupHelper.getEntryType;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -46,6 +49,8 @@ import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Person;
+import android.app.backup.BackupManager;
+import android.app.job.JobScheduler;
import android.app.people.ConversationChannel;
import android.app.people.IPeopleManager;
import android.app.people.PeopleManager;
@@ -84,8 +89,10 @@ import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.people.NotificationHelper;
+import com.android.systemui.people.PeopleBackupFollowUpJob;
import com.android.systemui.people.PeopleSpaceUtils;
import com.android.systemui.people.PeopleTileViewHelper;
+import com.android.systemui.people.SharedPreferencesHelper;
import com.android.systemui.statusbar.NotificationListener;
import com.android.systemui.statusbar.NotificationListener.NotificationHandler;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
@@ -93,11 +100,13 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.wm.shell.bubbles.Bubbles;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.Executor;
@@ -126,6 +135,7 @@ public class PeopleSpaceWidgetManager {
private Optional<Bubbles> mBubblesOptional;
private UserManager mUserManager;
private PeopleSpaceWidgetManager mManager;
+ private BackupManager mBackupManager;
public UiEventLogger mUiEventLogger = new UiEventLoggerImpl();
private NotificationManager mNotificationManager;
private BroadcastDispatcher mBroadcastDispatcher;
@@ -164,6 +174,7 @@ public class PeopleSpaceWidgetManager {
ServiceManager.getService(Context.NOTIFICATION_SERVICE));
mBubblesOptional = bubblesOptional;
mUserManager = userManager;
+ mBackupManager = new BackupManager(context);
mNotificationManager = notificationManager;
mManager = this;
mBroadcastDispatcher = broadcastDispatcher;
@@ -189,6 +200,7 @@ public class PeopleSpaceWidgetManager {
null /* executor */, UserHandle.ALL);
IntentFilter perAppFilter = new IntentFilter(ACTION_PACKAGE_REMOVED);
+ perAppFilter.addAction(ACTION_PACKAGE_ADDED);
perAppFilter.addDataScheme("package");
// BroadcastDispatcher doesn't allow data schemes.
mContext.registerReceiver(mBaseBroadcastReceiver, perAppFilter);
@@ -224,7 +236,7 @@ public class PeopleSpaceWidgetManager {
AppWidgetManager appWidgetManager, IPeopleManager iPeopleManager,
PeopleManager peopleManager, LauncherApps launcherApps,
NotificationEntryManager notificationEntryManager, PackageManager packageManager,
- Optional<Bubbles> bubblesOptional, UserManager userManager,
+ Optional<Bubbles> bubblesOptional, UserManager userManager, BackupManager backupManager,
INotificationManager iNotificationManager, NotificationManager notificationManager,
@Background Executor executor) {
mContext = context;
@@ -236,6 +248,7 @@ public class PeopleSpaceWidgetManager {
mPackageManager = packageManager;
mBubblesOptional = bubblesOptional;
mUserManager = userManager;
+ mBackupManager = backupManager;
mINotificationManager = iNotificationManager;
mNotificationManager = notificationManager;
mManager = this;
@@ -257,8 +270,6 @@ public class PeopleSpaceWidgetManager {
if (DEBUG) Log.d(TAG, "no widgets to update");
return;
}
-
- if (DEBUG) Log.d(TAG, "updating " + widgetIds.length + " widgets: " + widgetIds);
synchronized (mLock) {
updateSingleConversationWidgets(widgetIds);
}
@@ -274,6 +285,7 @@ public class PeopleSpaceWidgetManager {
public void updateSingleConversationWidgets(int[] appWidgetIds) {
Map<Integer, PeopleSpaceTile> widgetIdToTile = new HashMap<>();
for (int appWidgetId : appWidgetIds) {
+ if (DEBUG) Log.d(TAG, "Updating widget: " + appWidgetId);
PeopleSpaceTile tile = getTileForExistingWidget(appWidgetId);
if (tile == null) {
Log.e(TAG, "Matching conversation not found for shortcut ID");
@@ -293,14 +305,16 @@ public class PeopleSpaceWidgetManager {
private void updateAppWidgetViews(int appWidgetId, PeopleSpaceTile tile, Bundle options) {
PeopleTileKey key = getKeyFromStorageByWidgetId(appWidgetId);
if (DEBUG) Log.d(TAG, "Widget: " + appWidgetId + " for: " + key.toString());
- if (!key.isValid()) {
+
+ if (!PeopleTileKey.isValid(key)) {
Log.e(TAG, "Cannot update invalid widget");
return;
}
- RemoteViews views = new PeopleTileViewHelper(mContext, tile, appWidgetId,
- options, key).getViews();
+ RemoteViews views = PeopleTileViewHelper.createRemoteViews(mContext, tile, appWidgetId,
+ options, key);
// Tell the AppWidgetManager to perform an update on the current app widget.
+ if (DEBUG) Log.d(TAG, "Calling update widget for widgetId: " + appWidgetId);
mAppWidgetManager.updateAppWidget(appWidgetId, views);
}
@@ -314,6 +328,9 @@ public class PeopleSpaceWidgetManager {
/** Updates tile in app widget options and the current view. */
public void updateAppWidgetOptionsAndView(int appWidgetId, PeopleSpaceTile tile) {
+ if (tile == null) {
+ if (DEBUG) Log.w(TAG, "Storing null tile");
+ }
synchronized (mTiles) {
mTiles.put(appWidgetId, tile);
}
@@ -368,7 +385,7 @@ public class PeopleSpaceWidgetManager {
@Nullable
public PeopleSpaceTile getTileFromPersistentStorage(PeopleTileKey key, int appWidgetId) throws
PackageManager.NameNotFoundException {
- if (!key.isValid()) {
+ if (!PeopleTileKey.isValid(key)) {
Log.e(TAG, "PeopleTileKey invalid: " + key.toString());
return null;
}
@@ -382,7 +399,7 @@ public class PeopleSpaceWidgetManager {
ConversationChannel channel = mIPeopleManager.getConversation(
key.getPackageName(), key.getUserId(), key.getShortcutId());
if (channel == null) {
- Log.d(TAG, "Could not retrieve conversation from storage");
+ if (DEBUG) Log.d(TAG, "Could not retrieve conversation from storage");
return null;
}
@@ -430,7 +447,8 @@ public class PeopleSpaceWidgetManager {
try {
PeopleTileKey key = new PeopleTileKey(
sbn.getShortcutId(), sbn.getUser().getIdentifier(), sbn.getPackageName());
- if (!key.isValid()) {
+ if (!PeopleTileKey.isValid(key)) {
+ Log.d(TAG, "Sbn doesn't contain valid PeopleTileKey: " + key.toString());
return;
}
int[] widgetIds = mAppWidgetManager.getAppWidgetIds(
@@ -561,14 +579,14 @@ public class PeopleSpaceWidgetManager {
if (DEBUG) Log.d(TAG, "Augmenting tile from notification, key: " + key.toString());
return augmentTileFromNotification(mContext, tile, key, highestPriority, messagesCount,
- appWidgetId);
+ appWidgetId, mBackupManager);
}
/** Returns an augmented tile for an existing widget. */
@Nullable
public Optional<PeopleSpaceTile> getAugmentedTileForExistingWidget(int widgetId,
Map<PeopleTileKey, Set<NotificationEntry>> notifications) {
- Log.d(TAG, "Augmenting tile for existing widget: " + widgetId);
+ if (DEBUG) Log.d(TAG, "Augmenting tile for existing widget: " + widgetId);
PeopleSpaceTile tile = getTileForExistingWidget(widgetId);
if (tile == null) {
if (DEBUG) {
@@ -588,7 +606,7 @@ public class PeopleSpaceWidgetManager {
/** Returns stored widgets for the conversation specified. */
public Set<String> getMatchingKeyWidgetIds(PeopleTileKey key) {
- if (!key.isValid()) {
+ if (!PeopleTileKey.isValid(key)) {
return new HashSet<>();
}
return new HashSet<>(mSharedPrefs.getStringSet(key.toString(), new HashSet<>()));
@@ -776,7 +794,7 @@ public class PeopleSpaceWidgetManager {
// PeopleTileKey arguments.
if (DEBUG) Log.d(TAG, "onAppWidgetOptionsChanged called for widget: " + appWidgetId);
PeopleTileKey optionsKey = AppWidgetOptionsHelper.getPeopleTileKeyFromBundle(newOptions);
- if (optionsKey.isValid()) {
+ if (PeopleTileKey.isValid(optionsKey)) {
if (DEBUG) {
Log.d(TAG, "PeopleTileKey was present in Options, shortcutId: "
+ optionsKey.getShortcutId());
@@ -808,7 +826,7 @@ public class PeopleSpaceWidgetManager {
existingKeyIfStored = getKeyFromStorageByWidgetId(appWidgetId);
}
// Delete previous storage if the widget already existed and is just reconfigured.
- if (existingKeyIfStored.isValid()) {
+ if (PeopleTileKey.isValid(existingKeyIfStored)) {
if (DEBUG) Log.d(TAG, "Remove previous storage for widget: " + appWidgetId);
deleteWidgets(new int[]{appWidgetId});
} else {
@@ -820,7 +838,7 @@ public class PeopleSpaceWidgetManager {
synchronized (mLock) {
if (DEBUG) Log.d(TAG, "Add storage for : " + key.toString());
PeopleSpaceUtils.setSharedPreferencesStorageForTile(mContext, key, appWidgetId,
- tile.getContactUri());
+ tile.getContactUri(), mBackupManager);
}
if (DEBUG) Log.d(TAG, "Ensure listener is registered for widget: " + appWidgetId);
registerConversationListenerIfNeeded(appWidgetId, key);
@@ -838,7 +856,7 @@ public class PeopleSpaceWidgetManager {
/** Registers a conversation listener for {@code appWidgetId} if not already registered. */
public void registerConversationListenerIfNeeded(int widgetId, PeopleTileKey key) {
// Retrieve storage needed for registration.
- if (!key.isValid()) {
+ if (!PeopleTileKey.isValid(key)) {
if (DEBUG) Log.w(TAG, "Could not register listener for widget: " + widgetId);
return;
}
@@ -887,7 +905,7 @@ public class PeopleSpaceWidgetManager {
widgetSp.getString(SHORTCUT_ID, null),
widgetSp.getInt(USER_ID, INVALID_USER_ID),
widgetSp.getString(PACKAGE_NAME, null));
- if (!key.isValid()) {
+ if (!PeopleTileKey.isValid(key)) {
if (DEBUG) Log.e(TAG, "Could not delete " + widgetId);
return;
}
@@ -1031,8 +1049,8 @@ public class PeopleSpaceWidgetManager {
Optional.empty());
if (DEBUG) Log.i(TAG, "Returning tile preview for shortcutId: " + shortcutId);
- return new PeopleTileViewHelper(mContext, augmentedTile, 0, options,
- new PeopleTileKey(augmentedTile)).getViews();
+ return PeopleTileViewHelper.createRemoteViews(mContext, augmentedTile, 0, options,
+ new PeopleTileKey(augmentedTile));
}
protected final BroadcastReceiver mBaseBroadcastReceiver = new BroadcastReceiver() {
@@ -1053,6 +1071,7 @@ public class PeopleSpaceWidgetManager {
return;
}
for (int appWidgetId : appWidgetIds) {
+ if (DEBUG) Log.d(TAG, "Updating widget from broadcast, widget id: " + appWidgetId);
PeopleSpaceTile existingTile = null;
PeopleSpaceTile updatedTile = null;
try {
@@ -1060,7 +1079,7 @@ public class PeopleSpaceWidgetManager {
existingTile = getTileForExistingWidgetThrowing(appWidgetId);
if (existingTile == null) {
Log.e(TAG, "Matching conversation not found for shortcut ID");
- return;
+ continue;
}
updatedTile = getTileWithCurrentState(existingTile, entryPoint);
updateAppWidgetOptionsAndView(appWidgetId, updatedTile);
@@ -1068,6 +1087,14 @@ public class PeopleSpaceWidgetManager {
} catch (PackageManager.NameNotFoundException e) {
// Delete data for uninstalled widgets.
Log.e(TAG, "Package no longer found for tile: " + e);
+ JobScheduler jobScheduler = mContext.getSystemService(JobScheduler.class);
+ if (jobScheduler != null
+ && jobScheduler.getPendingJob(PeopleBackupFollowUpJob.JOB_ID) != null) {
+ if (DEBUG) {
+ Log.d(TAG, "Device was recently restored, wait before deleting storage.");
+ }
+ continue;
+ }
synchronized (mLock) {
updateAppWidgetOptionsAndView(appWidgetId, updatedTile);
}
@@ -1185,4 +1212,149 @@ public class PeopleSpaceWidgetManager {
return PeopleSpaceTile.BLOCK_CONVERSATIONS;
}
}
+
+ /**
+ * Modifies widgets storage after a restore operation, since widget ids get remapped on restore.
+ * This is guaranteed to run after the PeopleBackupHelper restore operation.
+ */
+ public void remapWidgets(int[] oldWidgetIds, int[] newWidgetIds) {
+ if (DEBUG) {
+ Log.d(TAG, "Remapping widgets, old: " + Arrays.toString(oldWidgetIds) + ". new: "
+ + Arrays.toString(newWidgetIds));
+ }
+
+ Map<String, String> widgets = new HashMap<>();
+ for (int i = 0; i < oldWidgetIds.length; i++) {
+ widgets.put(String.valueOf(oldWidgetIds[i]), String.valueOf(newWidgetIds[i]));
+ }
+
+ remapWidgetFiles(widgets);
+ remapSharedFile(widgets);
+ remapFollowupFile(widgets);
+
+ int[] widgetIds = mAppWidgetManager.getAppWidgetIds(
+ new ComponentName(mContext, PeopleSpaceWidgetProvider.class));
+ Bundle b = new Bundle();
+ b.putBoolean(AppWidgetManager.OPTION_APPWIDGET_RESTORE_COMPLETED, true);
+ for (int id : widgetIds) {
+ if (DEBUG) Log.d(TAG, "Setting widget as restored, widget id:" + id);
+ mAppWidgetManager.updateAppWidgetOptions(id, b);
+ }
+
+ updateWidgets(widgetIds);
+ }
+
+ /** Remaps widget ids in widget specific files. */
+ public void remapWidgetFiles(Map<String, String> widgets) {
+ if (DEBUG) Log.d(TAG, "Remapping widget files");
+ Map<String, PeopleTileKey> remapped = new HashMap<>();
+ for (Map.Entry<String, String> entry : widgets.entrySet()) {
+ String from = String.valueOf(entry.getKey());
+ String to = String.valueOf(entry.getValue());
+ if (Objects.equals(from, to)) {
+ continue;
+ }
+
+ SharedPreferences src = mContext.getSharedPreferences(from, Context.MODE_PRIVATE);
+ PeopleTileKey key = SharedPreferencesHelper.getPeopleTileKey(src);
+ if (PeopleTileKey.isValid(key)) {
+ if (DEBUG) {
+ Log.d(TAG, "Moving PeopleTileKey: " + key.toString() + " from file: "
+ + from + ", to file: " + to);
+ }
+ remapped.put(to, key);
+ SharedPreferencesHelper.clear(src);
+ } else {
+ if (DEBUG) Log.d(TAG, "Widget file has invalid key: " + key);
+ }
+ }
+ for (Map.Entry<String, PeopleTileKey> entry : remapped.entrySet()) {
+ SharedPreferences dest = mContext.getSharedPreferences(
+ entry.getKey(), Context.MODE_PRIVATE);
+ SharedPreferencesHelper.setPeopleTileKey(dest, entry.getValue());
+ }
+ }
+
+ /** Remaps widget ids in default shared storage. */
+ public void remapSharedFile(Map<String, String> widgets) {
+ if (DEBUG) Log.d(TAG, "Remapping shared file");
+ SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mContext);
+ SharedPreferences.Editor editor = sp.edit();
+ Map<String, ?> all = sp.getAll();
+ for (Map.Entry<String, ?> entry : all.entrySet()) {
+ String key = entry.getKey();
+ PeopleBackupHelper.SharedFileEntryType keyType = getEntryType(entry);
+ if (DEBUG) Log.d(TAG, "Remapping key:" + key);
+ switch (keyType) {
+ case WIDGET_ID:
+ String newId = widgets.get(key);
+ if (TextUtils.isEmpty(newId)) {
+ Log.w(TAG, "Key is widget id without matching new id, skipping: " + key);
+ break;
+ }
+ if (DEBUG) Log.d(TAG, "Key is widget id: " + key + ", replace with: " + newId);
+ try {
+ editor.putString(newId, (String) entry.getValue());
+ } catch (Exception e) {
+ Log.e(TAG, "Malformed entry value: " + entry.getValue());
+ }
+ editor.remove(key);
+ break;
+ case PEOPLE_TILE_KEY:
+ case CONTACT_URI:
+ Set<String> oldWidgetIds;
+ try {
+ oldWidgetIds = (Set<String>) entry.getValue();
+ } catch (Exception e) {
+ Log.e(TAG, "Malformed entry value: " + entry.getValue());
+ editor.remove(key);
+ break;
+ }
+ Set<String> newWidgets = getNewWidgets(oldWidgetIds, widgets);
+ if (DEBUG) {
+ Log.d(TAG, "Key is PeopleTileKey or contact URI: " + key
+ + ", replace values with new ids: " + newWidgets);
+ }
+ editor.putStringSet(key, newWidgets);
+ break;
+ case UNKNOWN:
+ Log.e(TAG, "Key not identified:" + key);
+ }
+ }
+ editor.apply();
+ }
+
+ /** Remaps widget ids in follow-up job file. */
+ public void remapFollowupFile(Map<String, String> widgets) {
+ if (DEBUG) Log.d(TAG, "Remapping follow up file");
+ SharedPreferences followUp = mContext.getSharedPreferences(
+ SHARED_FOLLOW_UP, Context.MODE_PRIVATE);
+ SharedPreferences.Editor followUpEditor = followUp.edit();
+ Map<String, ?> followUpAll = followUp.getAll();
+ for (Map.Entry<String, ?> entry : followUpAll.entrySet()) {
+ String key = entry.getKey();
+ Set<String> oldWidgetIds;
+ try {
+ oldWidgetIds = (Set<String>) entry.getValue();
+ } catch (Exception e) {
+ Log.e(TAG, "Malformed entry value: " + entry.getValue());
+ followUpEditor.remove(key);
+ continue;
+ }
+ Set<String> newWidgets = getNewWidgets(oldWidgetIds, widgets);
+ if (DEBUG) {
+ Log.d(TAG, "Follow up key: " + key + ", replace with new ids: " + newWidgets);
+ }
+ followUpEditor.putStringSet(key, newWidgets);
+ }
+ followUpEditor.apply();
+ }
+
+ private Set<String> getNewWidgets(Set<String> oldWidgets, Map<String, String> widgetsMapping) {
+ return oldWidgets
+ .stream()
+ .map(widgetsMapping::get)
+ .filter(id -> !TextUtils.isEmpty(id))
+ .collect(Collectors.toSet());
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetPinnedReceiver.java b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetPinnedReceiver.java
index a28da43a80b6..c4be197504be 100644
--- a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetPinnedReceiver.java
+++ b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetPinnedReceiver.java
@@ -75,7 +75,7 @@ public class PeopleSpaceWidgetPinnedReceiver extends BroadcastReceiver {
String packageName = intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME);
int userId = intent.getIntExtra(Intent.EXTRA_USER_ID, INVALID_USER_ID);
PeopleTileKey key = new PeopleTileKey(shortcutId, userId, packageName);
- if (!key.isValid()) {
+ if (!PeopleTileKey.isValid(key)) {
if (DEBUG) Log.w(TAG, "Skipping: key is not valid: " + key.toString());
return;
}
diff --git a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetProvider.java b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetProvider.java
index 3522b76e6460..36939b735a07 100644
--- a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetProvider.java
@@ -70,6 +70,13 @@ public class PeopleSpaceWidgetProvider extends AppWidgetProvider {
mPeopleSpaceWidgetManager.deleteWidgets(appWidgetIds);
}
+ @Override
+ public void onRestored(Context context, int[] oldWidgetIds, int[] newWidgetIds) {
+ super.onRestored(context, oldWidgetIds, newWidgetIds);
+ ensurePeopleSpaceWidgetManagerInitialized();
+ mPeopleSpaceWidgetManager.remapWidgets(oldWidgetIds, newWidgetIds);
+ }
+
private void ensurePeopleSpaceWidgetManagerInitialized() {
mPeopleSpaceWidgetManager.init();
}
diff --git a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleTileKey.java b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleTileKey.java
index 319df85b4872..6e6ca254dee0 100644
--- a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleTileKey.java
+++ b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleTileKey.java
@@ -25,6 +25,8 @@ import android.text.TextUtils;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import java.util.Objects;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
/** Class that encapsulates fields identifying a Conversation. */
public class PeopleTileKey {
@@ -32,6 +34,8 @@ public class PeopleTileKey {
private int mUserId;
private String mPackageName;
+ private static final Pattern KEY_PATTERN = Pattern.compile("(.+)/(-?\\d+)/(\\p{L}.*)");
+
public PeopleTileKey(String shortcutId, int userId, String packageName) {
mShortcutId = shortcutId;
mUserId = userId;
@@ -66,8 +70,12 @@ public class PeopleTileKey {
return mPackageName;
}
+ public void setUserId(int userId) {
+ mUserId = userId;
+ }
+
/** Returns whether PeopleTileKey is valid/well-formed. */
- public boolean isValid() {
+ private boolean validate() {
return !TextUtils.isEmpty(mShortcutId) && !TextUtils.isEmpty(mPackageName) && mUserId >= 0;
}
@@ -88,10 +96,31 @@ public class PeopleTileKey {
*/
@Override
public String toString() {
- if (!isValid()) return EMPTY_STRING;
return mShortcutId + "/" + mUserId + "/" + mPackageName;
}
+ /** Parses {@code key} into a {@link PeopleTileKey}. */
+ public static PeopleTileKey fromString(String key) {
+ if (key == null) {
+ return null;
+ }
+ Matcher m = KEY_PATTERN.matcher(key);
+ if (m.find()) {
+ try {
+ int userId = Integer.parseInt(m.group(2));
+ return new PeopleTileKey(m.group(1), userId, m.group(3));
+ } catch (NumberFormatException e) {
+ return null;
+ }
+ }
+ return null;
+ }
+
+ /** Returns whether {@code key} is a valid {@link PeopleTileKey}. */
+ public static boolean isValid(PeopleTileKey key) {
+ return key != null && key.validate();
+ }
+
@Override
public boolean equals(Object other) {
if (this == other) {
diff --git a/packages/SystemUI/src/com/android/systemui/power/InattentiveSleepWarningView.java b/packages/SystemUI/src/com/android/systemui/power/InattentiveSleepWarningView.java
index 1ed98c0a8f90..03d1f15bf379 100644
--- a/packages/SystemUI/src/com/android/systemui/power/InattentiveSleepWarningView.java
+++ b/packages/SystemUI/src/com/android/systemui/power/InattentiveSleepWarningView.java
@@ -93,6 +93,8 @@ public class InattentiveSleepWarningView extends FrameLayout {
setAlpha(1f);
setVisibility(View.VISIBLE);
mWindowManager.addView(this, getLayoutParams(mWindowToken));
+ announceForAccessibility(
+ getContext().getString(R.string.inattentive_sleep_warning_message));
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/television/PrivacyChipDrawable.java b/packages/SystemUI/src/com/android/systemui/privacy/television/PrivacyChipDrawable.java
new file mode 100644
index 000000000000..e5479badcb0a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/privacy/television/PrivacyChipDrawable.java
@@ -0,0 +1,390 @@
+/*
+ * 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.privacy.television;
+
+import android.animation.Animator;
+import android.animation.AnimatorInflater;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.ColorFilter;
+import android.graphics.Paint;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.drawable.Drawable;
+import android.util.Log;
+
+import androidx.annotation.Keep;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.systemui.R;
+
+/**
+ * Drawable that can go from being the background of the privacy icons to a small dot.
+ * The icons are not included.
+ */
+public class PrivacyChipDrawable extends Drawable {
+
+ private static final String TAG = PrivacyChipDrawable.class.getSimpleName();
+ private static final boolean DEBUG = false;
+
+ private float mWidth;
+ private float mHeight;
+ private float mMarginEnd;
+ private float mRadius;
+ private int mDotAlpha;
+ private int mBgAlpha;
+
+ private float mTargetWidth;
+ private final int mMinWidth;
+ private final int mIconWidth;
+ private final int mIconPadding;
+ private final int mBgWidth;
+ private final int mBgHeight;
+ private final int mBgRadius;
+ private final int mDotSize;
+
+ private final AnimatorSet mFadeIn;
+ private final AnimatorSet mFadeOut;
+ private final AnimatorSet mCollapse;
+ private final AnimatorSet mExpand;
+ private Animator mWidthAnimator;
+
+ private final Paint mChipPaint;
+ private final Paint mBgPaint;
+
+ private boolean mIsRtl;
+
+ private boolean mIsExpanded = true;
+
+ private PrivacyChipDrawableListener mListener;
+
+ interface PrivacyChipDrawableListener {
+ void onFadeOutFinished();
+ }
+
+ public PrivacyChipDrawable(Context context) {
+ mChipPaint = new Paint();
+ mChipPaint.setStyle(Paint.Style.FILL);
+ mChipPaint.setColor(context.getColor(R.color.privacy_circle));
+ mChipPaint.setAlpha(mDotAlpha);
+ mChipPaint.setFlags(Paint.ANTI_ALIAS_FLAG);
+
+ mBgPaint = new Paint();
+ mBgPaint.setStyle(Paint.Style.FILL);
+ mBgPaint.setColor(context.getColor(R.color.privacy_chip_dot_bg_tint));
+ mBgPaint.setAlpha(mBgAlpha);
+ mBgPaint.setFlags(Paint.ANTI_ALIAS_FLAG);
+
+ mBgWidth = context.getResources().getDimensionPixelSize(R.dimen.privacy_chip_dot_bg_width);
+ mBgHeight = context.getResources().getDimensionPixelSize(
+ R.dimen.privacy_chip_dot_bg_height);
+ mBgRadius = context.getResources().getDimensionPixelSize(
+ R.dimen.privacy_chip_dot_bg_radius);
+
+ mMinWidth = context.getResources().getDimensionPixelSize(R.dimen.privacy_chip_min_width);
+ mIconWidth = context.getResources().getDimensionPixelSize(R.dimen.privacy_chip_icon_size);
+ mIconPadding = context.getResources().getDimensionPixelSize(
+ R.dimen.privacy_chip_icon_margin_in_between);
+ mDotSize = context.getResources().getDimensionPixelSize(R.dimen.privacy_chip_dot_size);
+
+ mWidth = mMinWidth;
+ mHeight = context.getResources().getDimensionPixelSize(R.dimen.privacy_chip_height);
+ mRadius = context.getResources().getDimensionPixelSize(R.dimen.privacy_chip_radius);
+
+ mExpand = (AnimatorSet) AnimatorInflater.loadAnimator(context,
+ R.anim.tv_privacy_chip_expand);
+ mExpand.setTarget(this);
+
+ mCollapse = (AnimatorSet) AnimatorInflater.loadAnimator(context,
+ R.anim.tv_privacy_chip_collapse);
+ mCollapse.setTarget(this);
+
+ mFadeIn = (AnimatorSet) AnimatorInflater.loadAnimator(context,
+ R.anim.tv_privacy_chip_fade_in);
+ mFadeIn.setTarget(this);
+
+ mFadeOut = (AnimatorSet) AnimatorInflater.loadAnimator(context,
+ R.anim.tv_privacy_chip_fade_out);
+ mFadeOut.setTarget(this);
+ mFadeOut.addListener(new Animator.AnimatorListener() {
+ private boolean mCancelled;
+
+ @Override
+ public void onAnimationStart(Animator animation) {
+ mCancelled = false;
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ if (!mCancelled && mListener != null) {
+ if (DEBUG) Log.d(TAG, "Fade-out complete");
+ mListener.onFadeOutFinished();
+ }
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ mCancelled = true;
+ }
+
+ @Override
+ public void onAnimationRepeat(Animator animation) {
+ // no-op
+ }
+ });
+ }
+
+ /**
+ * Pass null to remove listener.
+ */
+ public void setListener(@Nullable PrivacyChipDrawableListener listener) {
+ this.mListener = listener;
+ }
+
+ /**
+ * Call once the view that is showing the drawable is visible to start fading the chip in.
+ */
+ public void startInitialFadeIn() {
+ if (DEBUG) Log.d(TAG, "initial fade-in");
+ mFadeIn.start();
+ }
+
+ @Override
+ public void draw(@NonNull Canvas canvas) {
+ Rect bounds = getBounds();
+
+ int centerVertical = (bounds.bottom - bounds.top) / 2;
+ // Dot background
+ RectF bgBounds = new RectF(
+ mIsRtl ? bounds.left : bounds.right - mBgWidth,
+ centerVertical - mBgHeight / 2f,
+ mIsRtl ? bounds.left + mBgWidth : bounds.right,
+ centerVertical + mBgHeight / 2f);
+ if (DEBUG) Log.v(TAG, "bg: " + bgBounds.toShortString());
+ canvas.drawRoundRect(bgBounds, mBgRadius, mBgRadius, mBgPaint);
+
+ // Icon background / dot
+ RectF greenBounds = new RectF(
+ mIsRtl ? bounds.left + mMarginEnd : bounds.right - mWidth - mMarginEnd,
+ centerVertical - mHeight / 2,
+ mIsRtl ? bounds.left + mWidth + mMarginEnd : bounds.right - mMarginEnd,
+ centerVertical + mHeight / 2);
+ if (DEBUG) Log.v(TAG, "green: " + greenBounds.toShortString());
+ canvas.drawRoundRect(greenBounds, mRadius, mRadius, mChipPaint);
+ }
+
+ private void animateToNewTargetWidth(float width) {
+ if (DEBUG) Log.d(TAG, "new target width: " + width);
+ if (width != mTargetWidth) {
+ mTargetWidth = width;
+ Animator newWidthAnimator = ObjectAnimator.ofFloat(this, "width", mTargetWidth);
+ newWidthAnimator.start();
+ if (mWidthAnimator != null) {
+ mWidthAnimator.cancel();
+ }
+ mWidthAnimator = newWidthAnimator;
+ }
+ }
+
+ private void expand() {
+ if (DEBUG) Log.d(TAG, "expanding");
+ if (mIsExpanded) {
+ return;
+ }
+ mIsExpanded = true;
+
+ mExpand.start();
+ mCollapse.cancel();
+ }
+
+ /**
+ * Starts the animation to a dot.
+ */
+ public void collapse() {
+ if (DEBUG) Log.d(TAG, "collapsing");
+ if (!mIsExpanded) {
+ return;
+ }
+ mIsExpanded = false;
+
+ animateToNewTargetWidth(mDotSize);
+ mCollapse.start();
+ mExpand.cancel();
+ }
+
+ /**
+ * Fades out the view if 0 icons are to be shown, expands the chip if it has been collapsed and
+ * makes the width of the chip adjust to the amount of icons to be shown.
+ * Should not be called when only the order of the icons was changed as the chip will expand
+ * again without there being any real update.
+ *
+ * @param iconCount Can be 0 to fade out the chip.
+ */
+ public void updateIcons(int iconCount) {
+ if (DEBUG) Log.d(TAG, "updating icons: " + iconCount);
+
+ // calculate chip size and use it for end value of animation that is specified in code,
+ // not xml
+ if (iconCount == 0) {
+ // fade out if there are no icons
+ mFadeOut.start();
+
+ mWidthAnimator.cancel();
+ mFadeIn.cancel();
+ mExpand.cancel();
+ mCollapse.cancel();
+ return;
+ }
+
+ mFadeOut.cancel();
+ expand();
+ animateToNewTargetWidth(mMinWidth + (iconCount - 1) * (mIconWidth + mIconPadding));
+ }
+
+ @Override
+ public void setAlpha(int alpha) {
+ setDotAlpha(alpha);
+ setBgAlpha(alpha);
+ }
+
+ @Override
+ public int getAlpha() {
+ return mDotAlpha;
+ }
+
+ /**
+ * Set alpha value the green part of the chip.
+ */
+ @Keep
+ public void setDotAlpha(int alpha) {
+ if (DEBUG) Log.v(TAG, "dot alpha updated to: " + alpha);
+ mDotAlpha = alpha;
+ mChipPaint.setAlpha(alpha);
+ }
+
+ @Keep
+ public int getDotAlpha() {
+ return mDotAlpha;
+ }
+
+ /**
+ * Set alpha value of the background of the chip.
+ */
+ @Keep
+ public void setBgAlpha(int alpha) {
+ if (DEBUG) Log.v(TAG, "bg alpha updated to: " + alpha);
+ mBgAlpha = alpha;
+ mBgPaint.setAlpha(alpha);
+ }
+
+ @Keep
+ public int getBgAlpha() {
+ return mBgAlpha;
+ }
+
+ @Override
+ public void setColorFilter(@Nullable ColorFilter colorFilter) {
+ // no-op
+ }
+
+ @Override
+ public int getOpacity() {
+ return PixelFormat.TRANSLUCENT;
+ }
+
+ /**
+ * The radius of the green part of the chip, not the background.
+ */
+ @Keep
+ public void setRadius(float radius) {
+ mRadius = radius;
+ invalidateSelf();
+ }
+
+ /**
+ * @return The radius of the green part of the chip, not the background.
+ */
+ @Keep
+ public float getRadius() {
+ return mRadius;
+ }
+
+ /**
+ * Height of the green part of the chip, not including the background.
+ */
+ @Keep
+ public void setHeight(float height) {
+ mHeight = height;
+ invalidateSelf();
+ }
+
+ /**
+ * @return Height of the green part of the chip, not including the background.
+ */
+ @Keep
+ public float getHeight() {
+ return mHeight;
+ }
+
+ /**
+ * Width of the green part of the chip, not including the background.
+ */
+ @Keep
+ public void setWidth(float width) {
+ mWidth = width;
+ invalidateSelf();
+ }
+
+ /**
+ * @return Width of the green part of the chip, not including the background.
+ */
+ @Keep
+ public float getWidth() {
+ return mWidth;
+ }
+
+ /**
+ * Margin at the end of the green part of the chip, so that it will be placed in the middle of
+ * the rounded rectangle in the background.
+ */
+ @Keep
+ public void setMarginEnd(float marginEnd) {
+ mMarginEnd = marginEnd;
+ invalidateSelf();
+ }
+
+ /**
+ * @return Margin at the end of the green part of the chip, so that it will be placed in the
+ * middle of the rounded rectangle in the background.
+ */
+ @Keep
+ public float getMarginEnd() {
+ return mMarginEnd;
+ }
+
+ /**
+ * Sets the layout direction.
+ */
+ public void setRtl(boolean isRtl) {
+ mIsRtl = isRtl;
+ }
+
+}
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/television/TvOngoingPrivacyChip.java b/packages/SystemUI/src/com/android/systemui/privacy/television/TvOngoingPrivacyChip.java
index 5ab7bd88e49b..e4f5cde37f1d 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/television/TvOngoingPrivacyChip.java
+++ b/packages/SystemUI/src/com/android/systemui/privacy/television/TvOngoingPrivacyChip.java
@@ -25,9 +25,10 @@ import android.annotation.IntDef;
import android.annotation.UiThread;
import android.content.Context;
import android.content.res.Resources;
-import android.graphics.Color;
import android.graphics.PixelFormat;
import android.graphics.drawable.Drawable;
+import android.os.Handler;
+import android.os.Looper;
import android.util.Log;
import android.view.Gravity;
import android.view.LayoutInflater;
@@ -38,15 +39,20 @@ import android.view.WindowManager;
import android.widget.ImageView;
import android.widget.LinearLayout;
+import androidx.annotation.NonNull;
+
import com.android.systemui.R;
import com.android.systemui.SystemUI;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.privacy.PrivacyChipBuilder;
import com.android.systemui.privacy.PrivacyItem;
import com.android.systemui.privacy.PrivacyItemController;
+import com.android.systemui.privacy.PrivacyType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
import javax.inject.Inject;
@@ -56,9 +62,10 @@ import javax.inject.Inject;
* recording audio, accessing the camera or accessing the location.
*/
@SysUISingleton
-public class TvOngoingPrivacyChip extends SystemUI implements PrivacyItemController.Callback {
+public class TvOngoingPrivacyChip extends SystemUI implements PrivacyItemController.Callback,
+ PrivacyChipDrawable.PrivacyChipDrawableListener {
private static final String TAG = "TvOngoingPrivacyChip";
- static final boolean DEBUG = false;
+ private static final boolean DEBUG = false;
// This title is used in CameraMicIndicatorsPermissionTest and
// RecognitionServiceMicIndicatorTest.
@@ -68,7 +75,8 @@ public class TvOngoingPrivacyChip extends SystemUI implements PrivacyItemControl
@IntDef(prefix = {"STATE_"}, value = {
STATE_NOT_SHOWN,
STATE_APPEARING,
- STATE_SHOWN,
+ STATE_EXPANDED,
+ STATE_COLLAPSED,
STATE_DISAPPEARING
})
public @interface State {
@@ -76,46 +84,58 @@ public class TvOngoingPrivacyChip extends SystemUI implements PrivacyItemControl
private static final int STATE_NOT_SHOWN = 0;
private static final int STATE_APPEARING = 1;
- private static final int STATE_SHOWN = 2;
- private static final int STATE_DISAPPEARING = 3;
+ private static final int STATE_EXPANDED = 2;
+ private static final int STATE_COLLAPSED = 3;
+ private static final int STATE_DISAPPEARING = 4;
- private static final int ANIMATION_DURATION_MS = 200;
+ private static final int EXPANDED_DURATION_MS = 4000;
+ public final int mAnimationDurationMs;
private final Context mContext;
private final PrivacyItemController mPrivacyItemController;
- private View mIndicatorView;
+ private ViewGroup mIndicatorView;
private boolean mViewAndWindowAdded;
private ObjectAnimator mAnimator;
private boolean mMicCameraIndicatorFlagEnabled;
- private boolean mLocationIndicatorEnabled;
- private List<PrivacyItem> mPrivacyItems;
+ private boolean mAllIndicatorsEnabled;
+
+ @NonNull
+ private List<PrivacyItem> mPrivacyItems = Collections.emptyList();
private LinearLayout mIconsContainer;
private final int mIconSize;
private final int mIconMarginStart;
+ private PrivacyChipDrawable mChipDrawable;
+
+ private final Handler mUiThreadHandler = new Handler(Looper.getMainLooper());
+ private final Runnable mCollapseRunnable = this::collapseChip;
+
@State
private int mState = STATE_NOT_SHOWN;
@Inject
public TvOngoingPrivacyChip(Context context, PrivacyItemController privacyItemController) {
super(context);
- Log.d(TAG, "Privacy chip running without id");
+ if (DEBUG) Log.d(TAG, "Privacy chip running");
mContext = context;
mPrivacyItemController = privacyItemController;
Resources res = mContext.getResources();
- mIconMarginStart = Math.round(res.getDimension(R.dimen.privacy_chip_icon_margin));
+ mIconMarginStart = Math.round(
+ res.getDimension(R.dimen.privacy_chip_icon_margin_in_between));
mIconSize = res.getDimensionPixelSize(R.dimen.privacy_chip_icon_size);
+ mAnimationDurationMs = res.getInteger(R.integer.privacy_chip_animation_millis);
+
mMicCameraIndicatorFlagEnabled = privacyItemController.getMicCameraAvailable();
- mLocationIndicatorEnabled = privacyItemController.getLocationAvailable();
+ mAllIndicatorsEnabled = privacyItemController.getAllIndicatorsAvailable();
if (DEBUG) {
Log.d(TAG, "micCameraIndicators: " + mMicCameraIndicatorFlagEnabled);
- Log.d(TAG, "locationIndicators: " + mLocationIndicatorEnabled);
+ Log.d(TAG, "allIndicators: " + mAllIndicatorsEnabled);
}
}
@@ -125,69 +145,145 @@ public class TvOngoingPrivacyChip extends SystemUI implements PrivacyItemControl
}
@Override
- public void onPrivacyItemsChanged(List<PrivacyItem> privacyItems) {
+ public void onPrivacyItemsChanged(@NonNull List<PrivacyItem> privacyItems) {
if (DEBUG) Log.d(TAG, "PrivacyItemsChanged");
- mPrivacyItems = privacyItems;
- updateUI();
+
+ List<PrivacyItem> updatedPrivacyItems = new ArrayList<>(privacyItems);
+ // Never show the location indicator on tv.
+ if (updatedPrivacyItems.removeIf(
+ privacyItem -> privacyItem.getPrivacyType() == PrivacyType.TYPE_LOCATION)) {
+ if (DEBUG) Log.v(TAG, "Removed the location item");
+ }
+
+ if (isChipDisabled()) {
+ fadeOutIndicator();
+ mPrivacyItems = updatedPrivacyItems;
+ return;
+ }
+
+ // Do they have the same elements? (order doesn't matter)
+ if (updatedPrivacyItems.size() == mPrivacyItems.size()
+ && mPrivacyItems.containsAll(updatedPrivacyItems)) {
+ if (DEBUG) Log.d(TAG, "List wasn't updated");
+ return;
+ }
+
+ mPrivacyItems = updatedPrivacyItems;
+ updateChip();
+ }
+
+ private void updateChip() {
+ if (DEBUG) Log.d(TAG, mPrivacyItems.size() + " privacy items");
+
+ if (mPrivacyItems.isEmpty()) {
+ if (DEBUG) Log.d(TAG, "removing indicator (state: " + stateToString(mState) + ")");
+ fadeOutIndicator();
+ return;
+ }
+
+ if (DEBUG) Log.d(TAG, "Current state: " + stateToString(mState));
+ switch (mState) {
+ case STATE_NOT_SHOWN:
+ createAndShowIndicator();
+ break;
+ case STATE_APPEARING:
+ case STATE_EXPANDED:
+ updateIcons();
+ collapseLater();
+ break;
+ case STATE_COLLAPSED:
+ case STATE_DISAPPEARING:
+ mState = STATE_EXPANDED;
+ updateIcons();
+ animateIconAppearance();
+ break;
+ }
+ }
+
+ /**
+ * Collapse the chip EXPANDED_DURATION_MS from now.
+ */
+ private void collapseLater() {
+ mUiThreadHandler.removeCallbacks(mCollapseRunnable);
+ if (DEBUG) Log.d(TAG, "chip will collapse in " + EXPANDED_DURATION_MS + "ms");
+ mUiThreadHandler.postDelayed(mCollapseRunnable, EXPANDED_DURATION_MS);
+ }
+
+ private void collapseChip() {
+ if (DEBUG) Log.d(TAG, "collapseChip");
+
+ if (mState != STATE_EXPANDED) {
+ return;
+ }
+ mState = STATE_COLLAPSED;
+
+ if (mChipDrawable != null) {
+ mChipDrawable.collapse();
+ }
+ animateIconDisappearance();
}
@Override
public void onFlagMicCameraChanged(boolean flag) {
if (DEBUG) Log.d(TAG, "mic/camera indicators enabled: " + flag);
mMicCameraIndicatorFlagEnabled = flag;
+ updateChipOnFlagChanged();
}
@Override
- public void onFlagLocationChanged(boolean flag) {
- if (DEBUG) Log.d(TAG, "location indicators enabled: " + flag);
- mLocationIndicatorEnabled = flag;
+ public void onFlagAllChanged(boolean flag) {
+ if (DEBUG) Log.d(TAG, "all indicators enabled: " + flag);
+ mAllIndicatorsEnabled = flag;
+ updateChipOnFlagChanged();
}
- private void updateUI() {
- if (DEBUG) Log.d(TAG, mPrivacyItems.size() + " privacy items");
+ private boolean isChipDisabled() {
+ return !(mMicCameraIndicatorFlagEnabled || mAllIndicatorsEnabled);
+ }
- if ((mMicCameraIndicatorFlagEnabled || mLocationIndicatorEnabled)
- && !mPrivacyItems.isEmpty()) {
- if (mState == STATE_NOT_SHOWN || mState == STATE_DISAPPEARING) {
- showIndicator();
- } else {
- if (DEBUG) Log.d(TAG, "only updating icons");
- PrivacyChipBuilder builder = new PrivacyChipBuilder(mContext, mPrivacyItems);
- setIcons(builder.generateIcons(), mIconsContainer);
- mIconsContainer.requestLayout();
- }
+ private void updateChipOnFlagChanged() {
+ if (isChipDisabled()) {
+ fadeOutIndicator();
} else {
- hideIndicatorIfNeeded();
+ updateChip();
}
}
@UiThread
- private void hideIndicatorIfNeeded() {
+ private void fadeOutIndicator() {
if (mState == STATE_NOT_SHOWN || mState == STATE_DISAPPEARING) return;
+ mUiThreadHandler.removeCallbacks(mCollapseRunnable);
+
if (mViewAndWindowAdded) {
mState = STATE_DISAPPEARING;
- animateDisappearance();
+ animateIconDisappearance();
} else {
// Appearing animation has not started yet, as we were still waiting for the View to be
// laid out.
mState = STATE_NOT_SHOWN;
removeIndicatorView();
}
+ if (mChipDrawable != null) {
+ mChipDrawable.updateIcons(0);
+ }
}
@UiThread
- private void showIndicator() {
+ private void createAndShowIndicator() {
mState = STATE_APPEARING;
+ if (mIndicatorView != null || mViewAndWindowAdded) {
+ removeIndicatorView();
+ }
+
// Inflate the indicator view
- mIndicatorView = LayoutInflater.from(mContext).inflate(
+ mIndicatorView = (ViewGroup) LayoutInflater.from(mContext).inflate(
R.layout.tv_ongoing_privacy_chip, null);
- // 1. Set alpha to 0.
+ // 1. Set icon alpha to 0.
// 2. Wait until the window is shown and the view is laid out.
// 3. Start a "fade in" (alpha) animation.
- mIndicatorView.setAlpha(0f);
mIndicatorView
.getViewTreeObserver()
.addOnGlobalLayoutListener(
@@ -196,20 +292,35 @@ public class TvOngoingPrivacyChip extends SystemUI implements PrivacyItemControl
public void onGlobalLayout() {
// State could have changed to NOT_SHOWN (if all the recorders are
// already gone)
- if (mState != STATE_APPEARING) return;
+ if (mState != STATE_APPEARING) {
+ return;
+ }
mViewAndWindowAdded = true;
// Remove the observer
mIndicatorView.getViewTreeObserver().removeOnGlobalLayoutListener(
this);
- animateAppearance();
+ animateIconAppearance();
+ mChipDrawable.startInitialFadeIn();
}
});
+ final boolean isRtl = mContext.getResources().getConfiguration().getLayoutDirection()
+ == View.LAYOUT_DIRECTION_RTL;
+ if (DEBUG) Log.d(TAG, "is RTL: " + isRtl);
+
+ mChipDrawable = new PrivacyChipDrawable(mContext);
+ mChipDrawable.setListener(this);
+ mChipDrawable.setRtl(isRtl);
+ ImageView chipBackground = mIndicatorView.findViewById(R.id.chip_drawable);
+ if (chipBackground != null) {
+ chipBackground.setImageDrawable(mChipDrawable);
+ }
+
mIconsContainer = mIndicatorView.findViewById(R.id.icons_container);
- PrivacyChipBuilder builder = new PrivacyChipBuilder(mContext, mPrivacyItems);
- setIcons(builder.generateIcons(), mIconsContainer);
+ mIconsContainer.setAlpha(0f);
+ updateIcons();
final WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(
WRAP_CONTENT,
@@ -217,19 +328,19 @@ public class TvOngoingPrivacyChip extends SystemUI implements PrivacyItemControl
WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
PixelFormat.TRANSLUCENT);
- layoutParams.gravity = Gravity.TOP | Gravity.END;
+ layoutParams.gravity = Gravity.TOP | (isRtl ? Gravity.LEFT : Gravity.RIGHT);
layoutParams.setTitle(LAYOUT_PARAMS_TITLE);
layoutParams.packageName = mContext.getPackageName();
final WindowManager windowManager = mContext.getSystemService(WindowManager.class);
windowManager.addView(mIndicatorView, layoutParams);
-
}
- private void setIcons(List<Drawable> icons, ViewGroup iconsContainer) {
- iconsContainer.removeAllViews();
+ private void updateIcons() {
+ List<Drawable> icons = new PrivacyChipBuilder(mContext, mPrivacyItems).generateIcons();
+ mIconsContainer.removeAllViews();
for (int i = 0; i < icons.size(); i++) {
Drawable icon = icons.get(i);
- icon.mutate().setTint(Color.WHITE);
+ icon.mutate().setTint(mContext.getColor(R.color.privacy_icon_tint));
ImageView imageView = new ImageView(mContext);
imageView.setImageDrawable(icon);
imageView.setScaleType(ImageView.ScaleType.FIT_CENTER);
@@ -241,22 +352,25 @@ public class TvOngoingPrivacyChip extends SystemUI implements PrivacyItemControl
imageView.setLayoutParams(layoutParams);
}
}
+ if (mChipDrawable != null) {
+ mChipDrawable.updateIcons(icons.size());
+ }
}
- private void animateAppearance() {
- animateAlphaTo(1f);
+ private void animateIconAppearance() {
+ animateIconAlphaTo(1f);
}
- private void animateDisappearance() {
- animateAlphaTo(0f);
+ private void animateIconDisappearance() {
+ animateIconAlphaTo(0f);
}
- private void animateAlphaTo(final float endValue) {
+ private void animateIconAlphaTo(float endValue) {
if (mAnimator == null) {
if (DEBUG) Log.d(TAG, "set up animator");
mAnimator = new ObjectAnimator();
- mAnimator.setTarget(mIndicatorView);
+ mAnimator.setTarget(mIconsContainer);
mAnimator.setProperty(View.ALPHA);
mAnimator.addListener(new AnimatorListenerAdapter() {
boolean mCancelled;
@@ -280,7 +394,7 @@ public class TvOngoingPrivacyChip extends SystemUI implements PrivacyItemControl
// and then onAnimationEnd(...). We, however, only want to proceed here if the
// animation ended "naturally".
if (!mCancelled) {
- onAnimationFinished();
+ onIconAnimationFinished();
}
}
});
@@ -289,19 +403,37 @@ public class TvOngoingPrivacyChip extends SystemUI implements PrivacyItemControl
mAnimator.cancel();
}
- final float currentValue = mIndicatorView.getAlpha();
+ final float currentValue = mIconsContainer.getAlpha();
+ if (currentValue == endValue) {
+ if (DEBUG) Log.d(TAG, "alpha not changing");
+ return;
+ }
if (DEBUG) Log.d(TAG, "animate alpha to " + endValue + " from " + currentValue);
- mAnimator.setDuration((int) (Math.abs(currentValue - endValue) * ANIMATION_DURATION_MS));
+ mAnimator.setDuration(mAnimationDurationMs);
mAnimator.setFloatValues(endValue);
mAnimator.start();
}
- private void onAnimationFinished() {
- if (DEBUG) Log.d(TAG, "onAnimationFinished");
+ @Override
+ public void onFadeOutFinished() {
+ if (DEBUG) Log.d(TAG, "drawable fade-out finished");
+
+ if (mState == STATE_DISAPPEARING) {
+ removeIndicatorView();
+ mState = STATE_NOT_SHOWN;
+ }
+ }
+
+ private void onIconAnimationFinished() {
+ if (DEBUG) Log.d(TAG, "onAnimationFinished (icon fade)");
+
+ if (mState == STATE_APPEARING || mState == STATE_EXPANDED) {
+ collapseLater();
+ }
if (mState == STATE_APPEARING) {
- mState = STATE_SHOWN;
+ mState = STATE_EXPANDED;
} else if (mState == STATE_DISAPPEARING) {
removeIndicatorView();
mState = STATE_NOT_SHOWN;
@@ -312,14 +444,39 @@ public class TvOngoingPrivacyChip extends SystemUI implements PrivacyItemControl
if (DEBUG) Log.d(TAG, "removeIndicatorView");
final WindowManager windowManager = mContext.getSystemService(WindowManager.class);
- if (windowManager != null) {
+ if (windowManager != null && mIndicatorView != null) {
windowManager.removeView(mIndicatorView);
}
mIndicatorView = null;
mAnimator = null;
+ if (mChipDrawable != null) {
+ mChipDrawable.setListener(null);
+ mChipDrawable = null;
+ }
+
mViewAndWindowAdded = false;
}
+ /**
+ * Used in debug logs.
+ */
+ private String stateToString(@State int state) {
+ switch (state) {
+ case STATE_NOT_SHOWN:
+ return "NOT_SHOWN";
+ case STATE_APPEARING:
+ return "APPEARING";
+ case STATE_EXPANDED:
+ return "EXPANDED";
+ case STATE_COLLAPSED:
+ return "COLLAPSED";
+ case STATE_DISAPPEARING:
+ return "DISAPPEARING";
+ default:
+ return "INVALID";
+ }
+ }
+
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
index 14bf8ab78e2c..a3180738fa60 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
@@ -81,6 +81,7 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha
private QSExpansionPathInterpolator mQSExpansionPathInterpolator;
private TouchAnimator mFirstPageAnimator;
private TouchAnimator mFirstPageDelayedAnimator;
+ private TouchAnimator mTranslationXAnimator;
private TouchAnimator mTranslationYAnimator;
private TouchAnimator mNonfirstPageAnimator;
private TouchAnimator mNonfirstPageDelayedAnimator;
@@ -139,9 +140,11 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha
updateAnimators();
}
-
- public void onQsScrollingChanged() {
- // Lazily update animators whenever the scrolling changes
+ /**
+ * Request an update to the animators. This will update them lazily next time the position
+ * is changed.
+ */
+ public void requestAnimatorUpdate() {
mNeedsAnimatorUpdate = true;
}
@@ -223,18 +226,25 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha
View qqsView,
View qsView,
View commonParent,
+ int xOffset,
int yOffset,
int[] temp,
- TouchAnimator.Builder animatorBuilder
+ TouchAnimator.Builder animatorBuilderX,
+ TouchAnimator.Builder animatorBuilderY
) {
getRelativePosition(temp, qqsView, commonParent);
- int qqsPos = temp[1];
+ int qqsPosX = temp[0];
+ int qqsPosY = temp[1];
getRelativePosition(temp, qsView, commonParent);
- int qsPos = temp[1];
-
- int diff = qsPos - qqsPos - yOffset;
- animatorBuilder.addFloat(qqsView, "translationY", 0, diff);
- animatorBuilder.addFloat(qsView, "translationY", -diff, 0);
+ int qsPosX = temp[0];
+ int qsPosY = temp[1];
+
+ int xDiff = qsPosX - qqsPosX - xOffset;
+ animatorBuilderX.addFloat(qqsView, "translationX", 0, xDiff);
+ animatorBuilderX.addFloat(qsView, "translationX", -xDiff, 0);
+ int yDiff = qsPosY - qqsPosY - yOffset;
+ animatorBuilderY.addFloat(qqsView, "translationY", 0, yDiff);
+ animatorBuilderY.addFloat(qsView, "translationY", -yDiff, 0);
mAllViews.add(qqsView);
mAllViews.add(qsView);
}
@@ -243,6 +253,7 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha
mNeedsAnimatorUpdate = false;
TouchAnimator.Builder firstPageBuilder = new Builder();
TouchAnimator.Builder translationYBuilder = new Builder();
+ TouchAnimator.Builder translationXBuilder = new Builder();
Collection<QSTile> tiles = mHost.getTiles();
int count = 0;
@@ -289,6 +300,7 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha
getRelativePosition(loc1, quickTileView, view);
getRelativePosition(loc2, tileView, view);
int yOffset = loc2[1] - loc1[1];
+ int xOffset = loc2[0] - loc1[0];
// Offset the translation animation on the views
// (that goes from 0 to getOffsetTranslation)
@@ -299,6 +311,9 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha
translationYBuilder.addFloat(tileView, "translationY",
-offsetWithQSBHTranslation, 0);
+ translationXBuilder.addFloat(quickTileView, "translationX", 0, xOffset);
+ translationXBuilder.addFloat(tileView, "translationX", -xOffset, 0);
+
if (mQQSTileHeightAnimator == null) {
mQQSTileHeightAnimator = new HeightExpansionAnimator(this,
quickTileView.getHeight(), tileView.getHeight());
@@ -312,8 +327,10 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha
quickTileView.getIcon(),
tileView.getIcon(),
view,
+ xOffset,
yOffset,
loc1,
+ translationXBuilder,
translationYBuilder
);
@@ -322,8 +339,10 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha
quickTileView.getLabelContainer(),
tileView.getLabelContainer(),
view,
+ xOffset,
yOffset,
loc1,
+ translationXBuilder,
translationYBuilder
);
@@ -332,8 +351,10 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha
quickTileView.getSecondaryIcon(),
tileView.getSecondaryIcon(),
view,
+ xOffset,
yOffset,
loc1,
+ translationXBuilder,
translationYBuilder
);
@@ -364,6 +385,7 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha
mOtherTilesExpandAnimator.addView(tileView);
tileView.setClipChildren(true);
tileView.setClipToPadding(true);
+ firstPageBuilder.addFloat(tileView.getSecondaryLabel(), "alpha", 0, 1);
}
mAllViews.add(tileView);
@@ -398,10 +420,16 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha
// Fade in the security footer and the divider as we reach the final position
builder = new Builder().setStartDelay(EXPANDED_TILE_DELAY);
builder.addFloat(mSecurityFooter.getView(), "alpha", 0, 1);
+ if (mQsPanelController.shouldUseHorizontalLayout()
+ && mQsPanelController.mMediaHost.hostView != null) {
+ builder.addFloat(mQsPanelController.mMediaHost.hostView, "alpha", 0, 1);
+ }
mAllPagesDelayedAnimator = builder.build();
mAllViews.add(mSecurityFooter.getView());
translationYBuilder.setInterpolator(mQSExpansionPathInterpolator.getYInterpolator());
+ translationXBuilder.setInterpolator(mQSExpansionPathInterpolator.getXInterpolator());
mTranslationYAnimator = translationYBuilder.build();
+ mTranslationXAnimator = translationXBuilder.build();
if (mQQSTileHeightAnimator != null) {
mQQSTileHeightAnimator.setInterpolator(
mQSExpansionPathInterpolator.getYInterpolator());
@@ -474,6 +502,7 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha
mFirstPageAnimator.setPosition(position);
mFirstPageDelayedAnimator.setPosition(position);
mTranslationYAnimator.setPosition(position);
+ mTranslationXAnimator.setPosition(position);
if (mQQSTileHeightAnimator != null) {
mQQSTileHeightAnimator.setPosition(position);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
index 6660081006cd..e9b19e5cfa6f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
@@ -28,12 +28,8 @@ import android.view.View;
import android.view.WindowInsets;
import android.widget.FrameLayout;
-import androidx.dynamicanimation.animation.FloatPropertyCompat;
-import androidx.dynamicanimation.animation.SpringForce;
-
import com.android.systemui.R;
import com.android.systemui.qs.customize.QSCustomizer;
-import com.android.wm.shell.animation.PhysicsAnimator;
/**
* Wrapper view with background which contains {@link QSPanel} and {@link QuickStatusBarHeader}
@@ -41,26 +37,10 @@ import com.android.wm.shell.animation.PhysicsAnimator;
public class QSContainerImpl extends FrameLayout {
private final Point mSizePoint = new Point();
- private static final FloatPropertyCompat<QSContainerImpl> BACKGROUND_BOTTOM =
- new FloatPropertyCompat<QSContainerImpl>("backgroundBottom") {
- @Override
- public float getValue(QSContainerImpl qsImpl) {
- return qsImpl.getBackgroundBottom();
- }
-
- @Override
- public void setValue(QSContainerImpl background, float value) {
- background.setBackgroundBottom((int) value);
- }
- };
- private static final PhysicsAnimator.SpringConfig BACKGROUND_SPRING
- = new PhysicsAnimator.SpringConfig(SpringForce.STIFFNESS_MEDIUM,
- SpringForce.DAMPING_RATIO_LOW_BOUNCY);
private int mFancyClippingTop;
private int mFancyClippingBottom;
private final float[] mFancyClippingRadii = new float[] {0, 0, 0, 0, 0, 0, 0, 0};
private final Path mFancyClippingPath = new Path();
- private int mBackgroundBottom = 0;
private int mHeightOverride = -1;
private View mQSDetail;
private QuickStatusBarHeader mHeader;
@@ -71,7 +51,6 @@ public class QSContainerImpl extends FrameLayout {
private int mSideMargins;
private boolean mQsDisabled;
private int mContentPadding = -1;
- private boolean mAnimateBottomOnNextLayout;
private int mNavBarInset = 0;
private boolean mClippingEnabled;
@@ -86,11 +65,6 @@ public class QSContainerImpl extends FrameLayout {
mQSDetail = findViewById(R.id.qs_detail);
mHeader = findViewById(R.id.header);
mQSCustomizer = findViewById(R.id.qs_customize);
- mHeader.getHeaderQsPanel().setMediaVisibilityChangedListener((visible) -> {
- if (mHeader.getHeaderQsPanel().isShown()) {
- mAnimateBottomOnNextLayout = true;
- }
- });
setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
}
@@ -99,20 +73,6 @@ public class QSContainerImpl extends FrameLayout {
return false;
}
- void onMediaVisibilityChanged(boolean qsVisible) {
- mAnimateBottomOnNextLayout = qsVisible;
- }
-
- private void setBackgroundBottom(int value) {
- // We're saving the bottom separately since otherwise the bottom would be overridden in
- // the layout and the animation wouldn't properly start at the old position.
- mBackgroundBottom = value;
- }
-
- private float getBackgroundBottom() {
- return mBackgroundBottom;
- }
-
@Override
protected void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
@@ -186,8 +146,7 @@ public class QSContainerImpl extends FrameLayout {
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
- updateExpansion(mAnimateBottomOnNextLayout /* animate */);
- mAnimateBottomOnNextLayout = false;
+ updateExpansion();
updateClippingPath();
}
@@ -230,31 +189,12 @@ public class QSContainerImpl extends FrameLayout {
}
public void updateExpansion() {
- updateExpansion(false /* animate */);
- }
-
- public void updateExpansion(boolean animate) {
int height = calculateContainerHeight();
int scrollBottom = calculateContainerBottom();
setBottom(getTop() + height);
mQSDetail.setBottom(getTop() + scrollBottom);
int qsDetailBottomMargin = ((MarginLayoutParams) mQSDetail.getLayoutParams()).bottomMargin;
mQSDetail.setBottom(getTop() + scrollBottom - qsDetailBottomMargin);
- updateBackgroundBottom(scrollBottom, animate);
- }
-
- private void updateBackgroundBottom(int height, boolean animated) {
- PhysicsAnimator<QSContainerImpl> physicsAnimator = PhysicsAnimator.getInstance(this);
- if (physicsAnimator.isPropertyAnimating(BACKGROUND_BOTTOM) || animated) {
- // An animation is running or we want to animate
- // Let's make sure to set the currentValue again, since the call below might only
- // start in the next frame and otherwise we'd flicker
- BACKGROUND_BOTTOM.setValue(this, BACKGROUND_BOTTOM.getValue(this));
- physicsAnimator.spring(BACKGROUND_BOTTOM, height, BACKGROUND_SPRING).start();
- } else {
- BACKGROUND_BOTTOM.setValue(this, height);
- }
-
}
protected int calculateContainerHeight() {
@@ -275,7 +215,7 @@ public class QSContainerImpl extends FrameLayout {
public void setExpansion(float expansion) {
mQsExpansion = expansion;
- mQSPanelContainer.setScrollingEnabled(expansion > 0.0f);
+ mQSPanelContainer.setScrollingEnabled(expansion > 0f);
updateExpansion();
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImplController.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImplController.java
index 3638395be29e..7d61991c910a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImplController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImplController.java
@@ -61,12 +61,6 @@ public class QSContainerImplController extends ViewController<QSContainerImpl> {
@Override
protected void onViewAttached() {
mView.updateResources(mQsPanelController, mQuickStatusBarHeaderController);
- mQsPanelController.setMediaVisibilityChangedListener((visible) -> {
- if (mQsPanelController.isShown()) {
- mView.onMediaVisibilityChanged(true);
- }
- });
-
mConfigurationController.addCallback(mConfigurationListener);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
index c28c649b0306..36b4ee987d99 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
@@ -53,6 +53,8 @@ import com.android.systemui.util.InjectionInflationController;
import com.android.systemui.util.LifecycleFragment;
import com.android.systemui.util.Utils;
+import java.util.function.Consumer;
+
import javax.inject.Inject;
import javax.inject.Named;
@@ -106,6 +108,7 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
private QSPanelController mQSPanelController;
private QuickQSPanelController mQuickQSPanelController;
private QSCustomizerController mQSCustomizerController;
+ private ScrollListener mScrollListener;
private FeatureFlags mFeatureFlags;
/**
* When true, QS will translate from outside the screen. It will be clipped with parallax
@@ -169,9 +172,12 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
});
mQSPanelScrollView.setOnScrollChangeListener(
(v, scrollX, scrollY, oldScrollX, oldScrollY) -> {
- // Lazily update animators whenever the scrolling changes
- mQSAnimator.onQsScrollingChanged();
- mHeader.setExpandedScrollAmount(scrollY);
+ // Lazily update animators whenever the scrolling changes
+ mQSAnimator.requestAnimatorUpdate();
+ mHeader.setExpandedScrollAmount(scrollY);
+ if (mScrollListener != null) {
+ mScrollListener.onQsPanelScrollChanged(scrollY);
+ }
});
mQSDetail = view.findViewById(R.id.qs_detail);
mHeader = view.findViewById(R.id.header);
@@ -209,6 +215,19 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
setQsExpansion(mLastQSExpansion, mLastHeaderTranslation);
}
});
+ mQSPanelController.setUsingHorizontalLayoutChangeListener(
+ () -> {
+ // The hostview may be faded out in the horizontal layout. Let's make sure to
+ // reset the alpha when switching layouts. This is fine since the animator will
+ // update the alpha if it's not supposed to be 1.0f
+ mQSPanelController.getMediaHost().getHostView().setAlpha(1.0f);
+ mQSAnimator.requestAnimatorUpdate();
+ });
+ }
+
+ @Override
+ public void setScrollListener(ScrollListener listener) {
+ mScrollListener = listener;
}
@Override
@@ -220,6 +239,7 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
}
mQSCustomizerController.setQs(null);
mQsDetailDisplayer.setQsPanelController(null);
+ mScrollListener = null;
}
@Override
@@ -281,6 +301,11 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
return mLastQSExpansion == 0.0f || mLastQSExpansion == -1;
}
+ @Override
+ public void setCollapsedMediaVisibilityChangedListener(Consumer<Boolean> listener) {
+ mQuickQSPanelController.setMediaVisibilityChangedListener(listener);
+ }
+
private void setEditLocation(View view) {
View edit = view.findViewById(android.R.id.edit);
int[] loc = edit.getLocationOnScreen();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index c70eaffcaeb6..7c7f56658919 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -46,7 +46,6 @@ import com.android.systemui.util.animation.UniqueObjectHostView;
import java.util.ArrayList;
import java.util.List;
-import java.util.function.Consumer;
/** View that represents the quick settings tile panel (when expanded/pulled down). **/
public class QSPanel extends LinearLayout implements Tunable {
@@ -99,13 +98,8 @@ public class QSPanel extends LinearLayout implements Tunable {
private LinearLayout mHorizontalLinearLayout;
protected LinearLayout mHorizontalContentContainer;
- // Only used with media
- private QSTileLayout mHorizontalTileLayout;
- protected QSTileLayout mRegularTileLayout;
protected QSTileLayout mTileLayout;
- private int mLastOrientation = -1;
private int mMediaTotalBottomMargin;
- private Consumer<Boolean> mMediaVisibilityChangedListener;
public QSPanel(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -121,8 +115,7 @@ public class QSPanel extends LinearLayout implements Tunable {
}
void initialize() {
- mRegularTileLayout = createRegularTileLayout();
- mTileLayout = mRegularTileLayout;
+ mTileLayout = getOrCreateTileLayout();
if (mUsingMediaPlayer) {
mHorizontalLinearLayout = new RemeasuringLinearLayout(mContext);
@@ -135,7 +128,6 @@ public class QSPanel extends LinearLayout implements Tunable {
mHorizontalContentContainer.setClipChildren(true);
mHorizontalContentContainer.setClipToPadding(false);
- mHorizontalTileLayout = createHorizontalTileLayout();
LayoutParams lp = new LayoutParams(0, LayoutParams.WRAP_CONTENT, 1);
int marginSize = (int) mContext.getResources().getDimension(R.dimen.qs_media_padding);
lp.setMarginStart(0);
@@ -148,12 +140,6 @@ public class QSPanel extends LinearLayout implements Tunable {
}
}
- protected void onMediaVisibilityChanged(Boolean visible) {
- if (mMediaVisibilityChangedListener != null) {
- mMediaVisibilityChangedListener.accept(visible);
- }
- }
-
/**
* Add brightness view above the tile layout.
*
@@ -184,17 +170,12 @@ public class QSPanel extends LinearLayout implements Tunable {
}
/** */
- public QSTileLayout createRegularTileLayout() {
- if (mRegularTileLayout == null) {
- mRegularTileLayout = (QSTileLayout) LayoutInflater.from(mContext)
+ public QSTileLayout getOrCreateTileLayout() {
+ if (mTileLayout == null) {
+ mTileLayout = (QSTileLayout) LayoutInflater.from(mContext)
.inflate(R.layout.qs_paged_tile_layout, this, false);
}
- return mRegularTileLayout;
- }
-
-
- protected QSTileLayout createHorizontalTileLayout() {
- return createRegularTileLayout();
+ return mTileLayout;
}
@Override
@@ -281,18 +262,18 @@ public class QSPanel extends LinearLayout implements Tunable {
* @param pageIndicator indicator to use for page scrolling
*/
public void setFooterPageIndicator(PageIndicator pageIndicator) {
- if (mRegularTileLayout instanceof PagedTileLayout) {
+ if (mTileLayout instanceof PagedTileLayout) {
mFooterPageIndicator = pageIndicator;
updatePageIndicator();
}
}
private void updatePageIndicator() {
- if (mRegularTileLayout instanceof PagedTileLayout) {
+ if (mTileLayout instanceof PagedTileLayout) {
if (mFooterPageIndicator != null) {
mFooterPageIndicator.setVisibility(View.GONE);
- ((PagedTileLayout) mRegularTileLayout).setPageIndicator(mFooterPageIndicator);
+ ((PagedTileLayout) mTileLayout).setPageIndicator(mFooterPageIndicator);
}
}
}
@@ -362,7 +343,7 @@ public class QSPanel extends LinearLayout implements Tunable {
return true;
}
- protected boolean needsDynamicRowsAndColumns() {
+ private boolean needsDynamicRowsAndColumns() {
return true;
}
@@ -667,10 +648,6 @@ public class QSPanel extends LinearLayout implements Tunable {
mHeaderContainer = headerContainer;
}
- public void setMediaVisibilityChangedListener(Consumer<Boolean> visibilityChangedListener) {
- mMediaVisibilityChangedListener = visibilityChangedListener;
- }
-
public boolean isListening() {
return mListening;
}
@@ -681,39 +658,20 @@ public class QSPanel extends LinearLayout implements Tunable {
}
protected void setPageMargin(int pageMargin) {
- if (mRegularTileLayout instanceof PagedTileLayout) {
- ((PagedTileLayout) mRegularTileLayout).setPageMargin(pageMargin);
- }
- if (mHorizontalTileLayout != mRegularTileLayout
- && mHorizontalTileLayout instanceof PagedTileLayout) {
- ((PagedTileLayout) mHorizontalTileLayout).setPageMargin(pageMargin);
+ if (mTileLayout instanceof PagedTileLayout) {
+ ((PagedTileLayout) mTileLayout).setPageMargin(pageMargin);
}
}
- void setUsingHorizontalLayout(boolean horizontal, ViewGroup mediaHostView, boolean force,
- UiEventLogger uiEventLogger) {
+ void setUsingHorizontalLayout(boolean horizontal, ViewGroup mediaHostView, boolean force) {
if (horizontal != mUsingHorizontalLayout || force) {
mUsingHorizontalLayout = horizontal;
- View visibleView = horizontal ? mHorizontalLinearLayout : (View) mRegularTileLayout;
- View hiddenView = horizontal ? (View) mRegularTileLayout : mHorizontalLinearLayout;
ViewGroup newParent = horizontal ? mHorizontalContentContainer : this;
- QSPanel.QSTileLayout newLayout = horizontal
- ? mHorizontalTileLayout : mRegularTileLayout;
- if (hiddenView != null
- && (mRegularTileLayout != mHorizontalTileLayout
- || hiddenView != mRegularTileLayout)) {
- // Only hide the view if the horizontal and the regular view are different,
- // otherwise its reattached.
- hiddenView.setVisibility(View.GONE);
- }
- visibleView.setVisibility(View.VISIBLE);
- switchAllContentToParent(newParent, newLayout);
+ switchAllContentToParent(newParent, mTileLayout);
reAttachMediaHost(mediaHostView, horizontal);
- mTileLayout = newLayout;
- newLayout.setListening(mListening, uiEventLogger);
if (needsDynamicRowsAndColumns()) {
- newLayout.setMinRows(horizontal ? 2 : 1);
- newLayout.setMaxColumns(horizontal ? 2 : 4);
+ mTileLayout.setMinRows(horizontal ? 2 : 1);
+ mTileLayout.setMaxColumns(horizontal ? 2 : 4);
}
updateMargins(mediaHostView);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
index ac92d4fe44e2..ae0f5104d20f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
@@ -45,8 +45,6 @@ import com.android.systemui.statusbar.FeatureFlags;
import com.android.systemui.statusbar.policy.BrightnessMirrorController;
import com.android.systemui.tuner.TunerService;
-import java.util.function.Consumer;
-
import javax.inject.Inject;
import javax.inject.Named;
@@ -149,14 +147,14 @@ public class QSPanelController extends QSPanelControllerBase<QSPanel> {
mBrightnessMirrorController.addCallback(mBrightnessMirrorListener);
}
- ((PagedTileLayout) mView.createRegularTileLayout())
+ ((PagedTileLayout) mView.getOrCreateTileLayout())
.setOnTouchListener(mTileLayoutTouchListener);
}
@Override
protected QSTileRevealController createTileRevealController() {
return mQsTileRevealControllerFactory.create(
- this, (PagedTileLayout) mView.createRegularTileLayout());
+ this, (PagedTileLayout) mView.getOrCreateTileLayout());
}
@Override
@@ -289,11 +287,6 @@ public class QSPanelController extends QSPanelControllerBase<QSPanel> {
mView.setPageListener(listener);
}
- /** */
- public void setMediaVisibilityChangedListener(Consumer<Boolean> visibilityChangedListener) {
- mView.setMediaVisibilityChangedListener(visibilityChangedListener);
- }
-
public boolean isShown() {
return mView.isShown();
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
index 170785ca7aab..4739a3f4c7d6 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
@@ -19,6 +19,8 @@ package com.android.systemui.qs;
import static com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import static com.android.systemui.qs.dagger.QSFragmentModule.QS_USING_MEDIA_PLAYER;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.content.ComponentName;
import android.content.res.Configuration;
import android.metrics.LogMaker;
@@ -42,6 +44,7 @@ import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collection;
+import java.util.function.Consumer;
import java.util.stream.Collectors;
import javax.inject.Named;
@@ -68,6 +71,8 @@ public abstract class QSPanelControllerBase<T extends QSPanel> extends ViewContr
protected final ArrayList<TileRecord> mRecords = new ArrayList<>();
private boolean mShouldUseSplitNotificationShade;
+ @Nullable
+ private Consumer<Boolean> mMediaVisibilityChangedListener;
private int mLastOrientation;
private String mCachedSpecs = "";
private QSTileRevealController mQsTileRevealController;
@@ -89,13 +94,18 @@ public abstract class QSPanelControllerBase<T extends QSPanel> extends ViewContr
};
private final Function1<Boolean, Unit> mMediaHostVisibilityListener = (visible) -> {
- mView.onMediaVisibilityChanged(visible);
+ if (mMediaVisibilityChangedListener != null) {
+ mMediaVisibilityChangedListener.accept(visible);
+ }
switchTileLayout(false);
return null;
};
private boolean mUsingHorizontalLayout;
+ @Nullable
+ private Runnable mUsingHorizontalLayoutChangedListener;
+
protected QSPanelControllerBase(
T view,
QSTileHost host,
@@ -128,6 +138,13 @@ public abstract class QSPanelControllerBase<T extends QSPanel> extends ViewContr
mQSLogger.logAllTilesChangeListening(mView.isListening(), mView.getDumpableTag(), "");
}
+ /**
+ * @return the media host for this panel
+ */
+ public MediaHost getMediaHost() {
+ return mMediaHost;
+ }
+
@Override
protected void onViewAttached() {
mQsTileRevealController = createTileRevealController();
@@ -136,7 +153,6 @@ public abstract class QSPanelControllerBase<T extends QSPanel> extends ViewContr
}
mMediaHost.addVisibilityChangeListener(mMediaHostVisibilityListener);
- mView.onMediaVisibilityChanged(mMediaHost.getVisible());
mView.addOnConfigurationChangedListener(mOnConfigurationChangedListener);
mHost.addCallback(mQSHostCallback);
setTiles();
@@ -291,20 +307,15 @@ public abstract class QSPanelControllerBase<T extends QSPanel> extends ViewContr
}
boolean switchTileLayout(boolean force) {
- /** Whether or not the QuickQSPanel currently contains a media player. */
+ /* Whether or not the panel currently contains a media player. */
boolean horizontal = shouldUseHorizontalLayout();
if (horizontal != mUsingHorizontalLayout || force) {
mUsingHorizontalLayout = horizontal;
- for (QSPanelControllerBase.TileRecord record : mRecords) {
- mView.removeTile(record);
- record.tile.removeCallback(record.callback);
- }
- mView.setUsingHorizontalLayout(mUsingHorizontalLayout, mMediaHost.getHostView(), force,
- mUiEventLogger);
+ mView.setUsingHorizontalLayout(mUsingHorizontalLayout, mMediaHost.getHostView(), force);
updateMediaDisappearParameters();
-
- setTiles();
-
+ if (mUsingHorizontalLayoutChangedListener != null) {
+ mUsingHorizontalLayoutChangedListener.run();
+ }
return true;
}
return false;
@@ -381,6 +392,20 @@ public abstract class QSPanelControllerBase<T extends QSPanel> extends ViewContr
return mView.getTileLayout();
}
+ /**
+ * Add a listener for when the media visibility changes.
+ */
+ public void setMediaVisibilityChangedListener(@NonNull Consumer<Boolean> listener) {
+ mMediaVisibilityChangedListener = listener;
+ }
+
+ /**
+ * Add a listener when the horizontal layout changes
+ */
+ public void setUsingHorizontalLayoutChangeListener(Runnable listener) {
+ mUsingHorizontalLayoutChangedListener = listener;
+ }
+
/** */
public static final class TileRecord extends QSPanel.Record {
public QSTile tile;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java b/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java
index 3a6f1d5a02ae..7f19d0e6c25c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java
@@ -185,15 +185,22 @@ class QSSecurityFooter implements OnClickListener, DialogInterface.OnClickListen
final boolean isProfileOwnerOfOrganizationOwnedDevice =
mSecurityController.isProfileOwnerOfOrganizationOwnedDevice();
final boolean isParentalControlsEnabled = mSecurityController.isParentalControlsEnabled();
+ final boolean isWorkProfileOn = mSecurityController.isWorkProfileOn();
+ final boolean hasDisclosableWorkProfilePolicy = hasCACertsInWorkProfile
+ || vpnNameWorkProfile != null || (hasWorkProfile && isNetworkLoggingEnabled);
// Update visibility of footer
- mIsVisible = (isDeviceManaged && !isDemoDevice) || hasCACerts || hasCACertsInWorkProfile
- || vpnName != null || vpnNameWorkProfile != null
- || isProfileOwnerOfOrganizationOwnedDevice || isParentalControlsEnabled
- || (hasWorkProfile && isNetworkLoggingEnabled);
+ mIsVisible = (isDeviceManaged && !isDemoDevice)
+ || hasCACerts
+ || vpnName != null
+ || isProfileOwnerOfOrganizationOwnedDevice
+ || isParentalControlsEnabled
+ || (hasDisclosableWorkProfilePolicy && isWorkProfileOn);
// Update the view to be untappable if the device is an organization-owned device with a
- // managed profile and there is no policy set which requires a privacy disclosure.
- if (mIsVisible && isProfileOwnerOfOrganizationOwnedDevice && !isNetworkLoggingEnabled
- && !hasCACertsInWorkProfile && vpnNameWorkProfile == null) {
+ // managed profile and there is either:
+ // a) no policy set which requires a privacy disclosure.
+ // b) a specific work policy set but the work profile is turned off.
+ if (mIsVisible && isProfileOwnerOfOrganizationOwnedDevice
+ && (!hasDisclosableWorkProfilePolicy || !isWorkProfileOn)) {
mRootView.setClickable(false);
mRootView.findViewById(R.id.footer_icon).setVisibility(View.GONE);
} else {
@@ -204,7 +211,8 @@ class QSSecurityFooter implements OnClickListener, DialogInterface.OnClickListen
mFooterTextContent = getFooterText(isDeviceManaged, hasWorkProfile,
hasCACerts, hasCACertsInWorkProfile, isNetworkLoggingEnabled, vpnName,
vpnNameWorkProfile, organizationName, workProfileOrganizationName,
- isProfileOwnerOfOrganizationOwnedDevice, isParentalControlsEnabled);
+ isProfileOwnerOfOrganizationOwnedDevice, isParentalControlsEnabled,
+ isWorkProfileOn);
// Update the icon
int footerIconId = R.drawable.ic_info_outline;
if (vpnName != null || vpnNameWorkProfile != null) {
@@ -236,7 +244,8 @@ class QSSecurityFooter implements OnClickListener, DialogInterface.OnClickListen
boolean hasCACerts, boolean hasCACertsInWorkProfile, boolean isNetworkLoggingEnabled,
String vpnName, String vpnNameWorkProfile, CharSequence organizationName,
CharSequence workProfileOrganizationName,
- boolean isProfileOwnerOfOrganizationOwnedDevice, boolean isParentalControlsEnabled) {
+ boolean isProfileOwnerOfOrganizationOwnedDevice, boolean isParentalControlsEnabled,
+ boolean isWorkProfileOn) {
if (isParentalControlsEnabled) {
return mContext.getString(R.string.quick_settings_disclosure_parental_controls);
}
@@ -280,7 +289,7 @@ class QSSecurityFooter implements OnClickListener, DialogInterface.OnClickListen
organizationName);
}
} // end if(isDeviceManaged)
- if (hasCACertsInWorkProfile) {
+ if (hasCACertsInWorkProfile && isWorkProfileOn) {
if (workProfileOrganizationName == null) {
return mContext.getString(
R.string.quick_settings_disclosure_managed_profile_monitoring);
@@ -295,7 +304,7 @@ class QSSecurityFooter implements OnClickListener, DialogInterface.OnClickListen
if (vpnName != null && vpnNameWorkProfile != null) {
return mContext.getString(R.string.quick_settings_disclosure_vpns);
}
- if (vpnNameWorkProfile != null) {
+ if (vpnNameWorkProfile != null && isWorkProfileOn) {
return mContext.getString(R.string.quick_settings_disclosure_managed_profile_named_vpn,
vpnNameWorkProfile);
}
@@ -308,7 +317,7 @@ class QSSecurityFooter implements OnClickListener, DialogInterface.OnClickListen
return mContext.getString(R.string.quick_settings_disclosure_named_vpn,
vpnName);
}
- if (hasWorkProfile && isNetworkLoggingEnabled) {
+ if (hasWorkProfile && isNetworkLoggingEnabled && isWorkProfileOn) {
return mContext.getString(
R.string.quick_settings_disclosure_managed_profile_network_activity);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
index 6ddf2a75f491..756ad9939886 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
@@ -27,7 +27,6 @@ import android.provider.Settings.Secure;
import android.service.quicksettings.Tile;
import android.text.TextUtils;
import android.util.ArraySet;
-import android.util.FeatureFlagUtils;
import android.util.Log;
import com.android.internal.logging.InstanceId;
@@ -52,6 +51,7 @@ import com.android.systemui.qs.external.TileServices;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.shared.plugins.PluginManager;
+import com.android.systemui.statusbar.FeatureFlags;
import com.android.systemui.statusbar.phone.AutoTileManager;
import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.statusbar.phone.StatusBarIconController;
@@ -123,7 +123,8 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D
UiEventLogger uiEventLogger,
UserTracker userTracker,
SecureSettings secureSettings,
- CustomTileStatePersister customTileStatePersister) {
+ CustomTileStatePersister customTileStatePersister
+ ) {
mIconController = iconController;
mContext = context;
mUserContext = context;
@@ -517,7 +518,7 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D
// --WiFiTile
// --CellularTIle
if (tiles.contains("internet") || tiles.contains("wifi") || tiles.contains("cell")) {
- if (FeatureFlagUtils.isEnabled(context, FeatureFlagUtils.SETTINGS_PROVIDER_MODEL)) {
+ if (FeatureFlags.isProviderModelSettingEnabled(context)) {
if (!tiles.contains("internet")) {
if (tiles.contains("wifi")) {
// Replace the WiFi with Internet, and remove the Cell
@@ -559,7 +560,7 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D
}
// TODO(b/174753536): Change the config file directly.
// Filter out unused tiles from the default QS config.
- if (FeatureFlagUtils.isEnabled(context, FeatureFlagUtils.SETTINGS_PROVIDER_MODEL)) {
+ if (FeatureFlags.isProviderModelSettingEnabled(context)) {
tiles.remove("cell");
tiles.remove("wifi");
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
index 659475d19277..4cd4048f7286 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
@@ -61,21 +61,10 @@ public class QuickQSPanel extends QSPanel {
}
@Override
- public TileLayout createRegularTileLayout() {
+ public TileLayout getOrCreateTileLayout() {
return new QQSSideLabelTileLayout(mContext);
}
- @Override
- protected QSTileLayout createHorizontalTileLayout() {
- TileLayout t = createRegularTileLayout();
- t.setMaxColumns(2);
- return t;
- }
-
- @Override
- protected boolean needsDynamicRowsAndColumns() {
- return false; // QQS always have the same layout
- }
@Override
protected boolean displayMediaMarginsOnMedia() {
@@ -191,6 +180,7 @@ public class QuickQSPanel extends QSPanel {
LayoutParams.WRAP_CONTENT);
setLayoutParams(lp);
setMaxColumns(4);
+ mLastRowPadding = true;
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
index 997b96626747..03a2c843a15e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
@@ -23,7 +23,6 @@ import android.content.res.Resources;
import android.graphics.Color;
import android.graphics.Rect;
import android.util.AttributeSet;
-import android.util.FeatureFlagUtils;
import android.util.Pair;
import android.view.DisplayCutout;
import android.view.View;
@@ -79,7 +78,6 @@ public class QuickStatusBarHeader extends FrameLayout {
private TintedIconManager mTintedIconManager;
private QSExpansionPathInterpolator mQSExpansionPathInterpolator;
- private int mStatusBarPaddingTop = 0;
private int mRoundedCornerPadding = 0;
private int mWaterfallTopInset;
private int mCutOutPaddingLeft;
@@ -88,11 +86,11 @@ public class QuickStatusBarHeader extends FrameLayout {
private float mKeyguardExpansionFraction;
private int mTextColorPrimary = Color.TRANSPARENT;
private int mTopViewMeasureHeight;
+ private boolean mProviderModel;
private final String mMobileSlotName;
private final String mNoCallingSlotName;
private final String mCallStrengthSlotName;
- private final boolean mProviderModel;
public QuickStatusBarHeader(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -100,11 +98,6 @@ public class QuickStatusBarHeader extends FrameLayout {
mNoCallingSlotName = context.getString(com.android.internal.R.string.status_bar_no_calling);
mCallStrengthSlotName =
context.getString(com.android.internal.R.string.status_bar_call_strength);
- if (FeatureFlagUtils.isEnabled(context, FeatureFlagUtils.SETTINGS_PROVIDER_MODEL)) {
- mProviderModel = true;
- } else {
- mProviderModel = false;
- }
}
/**
@@ -154,7 +147,9 @@ public class QuickStatusBarHeader extends FrameLayout {
}
void onAttach(TintedIconManager iconManager,
- QSExpansionPathInterpolator qsExpansionPathInterpolator) {
+ QSExpansionPathInterpolator qsExpansionPathInterpolator,
+ boolean providerModel) {
+ mProviderModel = providerModel;
mTintedIconManager = iconManager;
int fillColor = Utils.getColorAttrDefaultColor(getContext(),
android.R.attr.textColorPrimary);
@@ -209,7 +204,6 @@ public class QuickStatusBarHeader extends FrameLayout {
mRoundedCornerPadding = resources.getDimensionPixelSize(
R.dimen.rounded_corner_content_padding);
- mStatusBarPaddingTop = resources.getDimensionPixelSize(R.dimen.status_bar_padding_top);
int qsOffsetHeight = resources.getDimensionPixelSize(
com.android.internal.R.dimen.quick_qs_offset_height);
@@ -469,11 +463,11 @@ public class QuickStatusBarHeader extends FrameLayout {
}
mDatePrivacyView.setPadding(paddingLeft,
- mWaterfallTopInset + mStatusBarPaddingTop,
+ mWaterfallTopInset,
paddingRight,
0);
mClockIconsView.setPadding(paddingLeft,
- mWaterfallTopInset + mStatusBarPaddingTop,
+ mWaterfallTopInset,
paddingRight,
0);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java
index 76076f6c2761..fcf1302b8fb4 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java
@@ -37,6 +37,7 @@ import com.android.systemui.privacy.PrivacyItemController;
import com.android.systemui.privacy.logging.PrivacyLogger;
import com.android.systemui.qs.carrier.QSCarrierGroupController;
import com.android.systemui.qs.dagger.QSScope;
+import com.android.systemui.statusbar.FeatureFlags;
import com.android.systemui.statusbar.phone.StatusBarIconController;
import com.android.systemui.statusbar.phone.StatusIconContainer;
import com.android.systemui.statusbar.policy.Clock;
@@ -69,6 +70,7 @@ class QuickStatusBarHeaderController extends ViewController<QuickStatusBarHeader
private final PrivacyLogger mPrivacyLogger;
private final PrivacyDialogController mPrivacyDialogController;
private final QSExpansionPathInterpolator mQSExpansionPathInterpolator;
+ private final FeatureFlags mFeatureFlags;
private boolean mListening;
private boolean mMicCameraIndicatorsEnabled;
@@ -130,7 +132,8 @@ class QuickStatusBarHeaderController extends ViewController<QuickStatusBarHeader
PrivacyLogger privacyLogger,
SysuiColorExtractor colorExtractor,
PrivacyDialogController privacyDialogController,
- QSExpansionPathInterpolator qsExpansionPathInterpolator) {
+ QSExpansionPathInterpolator qsExpansionPathInterpolator,
+ FeatureFlags featureFlags) {
super(view);
mPrivacyItemController = privacyItemController;
mActivityStarter = activityStarter;
@@ -141,6 +144,7 @@ class QuickStatusBarHeaderController extends ViewController<QuickStatusBarHeader
mPrivacyLogger = privacyLogger;
mPrivacyDialogController = privacyDialogController;
mQSExpansionPathInterpolator = qsExpansionPathInterpolator;
+ mFeatureFlags = featureFlags;
mQSCarrierGroupController = qsCarrierGroupControllerBuilder
.setQSCarrierGroup(mView.findViewById(R.id.carrier_group))
@@ -150,7 +154,7 @@ class QuickStatusBarHeaderController extends ViewController<QuickStatusBarHeader
mClockView = mView.findViewById(R.id.clock);
mIconContainer = mView.findViewById(R.id.statusIcons);
- mIconManager = new StatusBarIconController.TintedIconManager(mIconContainer);
+ mIconManager = new StatusBarIconController.TintedIconManager(mIconContainer, mFeatureFlags);
mDemoModeReceiver = new ClockDemoModeReceiver(mClockView);
mColorExtractor = colorExtractor;
mOnColorsChangedListener = (extractor, which) -> {
@@ -174,7 +178,8 @@ class QuickStatusBarHeaderController extends ViewController<QuickStatusBarHeader
setChipVisibility(mPrivacyChip.getVisibility() == View.VISIBLE);
- mView.onAttach(mIconManager, mQSExpansionPathInterpolator);
+ mView.onAttach(mIconManager, mQSExpansionPathInterpolator,
+ mFeatureFlags.isCombinedStatusBarSignalIconsEnabled());
mDemoModeController.addCallback(mDemoModeReceiver);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java
index 3e558a962427..2b96a34967f4 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java
@@ -31,6 +31,7 @@ public class TileLayout extends ViewGroup implements QSTileLayout {
protected int mCellMarginVertical;
protected int mSidePadding;
protected int mRows = 1;
+ protected boolean mLastRowPadding = false;
protected final ArrayList<TileRecord> mRecords = new ArrayList<>();
protected boolean mListening;
@@ -167,6 +168,10 @@ public class TileLayout extends ViewGroup implements QSTileLayout {
}
int height = (mCellHeight + mCellMarginVertical) * mRows;
+ if (!mLastRowPadding) {
+ height -= mCellMarginVertical;
+ }
+
if (height < 0) height = 0;
setMeasuredDimension(width, height);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/carrier/CellSignalState.kt b/packages/SystemUI/src/com/android/systemui/qs/carrier/CellSignalState.kt
index 663f3f0e9ddb..2dac63905524 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/carrier/CellSignalState.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/carrier/CellSignalState.kt
@@ -26,7 +26,8 @@ data class CellSignalState(
@JvmField val mobileSignalIconId: Int = 0,
@JvmField val contentDescription: String? = null,
@JvmField val typeContentDescription: String? = null,
- @JvmField val roaming: Boolean = false
+ @JvmField val roaming: Boolean = false,
+ @JvmField val providerModelBehavior: Boolean = false
) {
/**
* Changes the visibility of this state by returning a copy with the visibility changed.
diff --git a/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrier.java b/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrier.java
index ae0b5d11db13..d6fa21646402 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrier.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrier.java
@@ -20,7 +20,6 @@ import android.content.Context;
import android.content.res.ColorStateList;
import android.text.TextUtils;
import android.util.AttributeSet;
-import android.util.FeatureFlagUtils;
import android.view.View;
import android.widget.ImageView;
import android.widget.LinearLayout;
@@ -39,7 +38,7 @@ public class QSCarrier extends LinearLayout {
private ImageView mMobileSignal;
private ImageView mMobileRoaming;
private CellSignalState mLastSignalState;
- private boolean mProviderModel;
+ private boolean mProviderModelInitialized = false;
public QSCarrier(Context context) {
super(context);
@@ -60,20 +59,10 @@ public class QSCarrier extends LinearLayout {
@Override
protected void onFinishInflate() {
super.onFinishInflate();
- if (FeatureFlagUtils.isEnabled(mContext, FeatureFlagUtils.SETTINGS_PROVIDER_MODEL)) {
- mProviderModel = true;
- } else {
- mProviderModel = false;
- }
mMobileGroup = findViewById(R.id.mobile_combo);
mMobileRoaming = findViewById(R.id.mobile_roaming);
mMobileSignal = findViewById(R.id.mobile_signal);
mCarrierText = findViewById(R.id.qs_carrier_text);
- if (mProviderModel) {
- mMobileSignal.setImageDrawable(mContext.getDrawable(R.drawable.ic_qs_no_calling_sms));
- } else {
- mMobileSignal.setImageDrawable(new SignalDrawable(mContext));
- }
}
/**
@@ -92,10 +81,19 @@ public class QSCarrier extends LinearLayout {
mMobileRoaming.setImageTintList(colorStateList);
mMobileSignal.setImageTintList(colorStateList);
- if (mProviderModel) {
+ if (state.providerModelBehavior) {
+ if (!mProviderModelInitialized) {
+ mProviderModelInitialized = true;
+ mMobileSignal.setImageDrawable(
+ mContext.getDrawable(R.drawable.ic_qs_no_calling_sms));
+ }
mMobileSignal.setImageDrawable(mContext.getDrawable(state.mobileSignalIconId));
mMobileSignal.setContentDescription(state.contentDescription);
} else {
+ if (!mProviderModelInitialized) {
+ mProviderModelInitialized = true;
+ mMobileSignal.setImageDrawable(new SignalDrawable(mContext));
+ }
mMobileSignal.setImageLevel(state.mobileSignalIconId);
StringBuilder contentDescription = new StringBuilder();
if (state.contentDescription != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrierGroupController.java b/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrierGroupController.java
index c49e0547e433..f23c0580c409 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrierGroupController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrierGroupController.java
@@ -27,7 +27,6 @@ import android.os.Message;
import android.provider.Settings;
import android.telephony.SubscriptionManager;
import android.text.TextUtils;
-import android.util.FeatureFlagUtils;
import android.util.Log;
import android.view.View;
import android.widget.TextView;
@@ -41,6 +40,7 @@ import com.android.systemui.R;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.statusbar.FeatureFlags;
import com.android.systemui.statusbar.policy.NetworkController;
import com.android.systemui.statusbar.policy.NetworkController.MobileDataIndicators;
import com.android.systemui.util.CarrierConfigTracker;
@@ -95,7 +95,8 @@ public class QSCarrierGroupController {
indicators.statusIcon.icon,
indicators.statusIcon.contentDescription,
indicators.typeContentDescription.toString(),
- indicators.roaming
+ indicators.roaming,
+ mProviderModel
);
mMainHandler.obtainMessage(H.MSG_UPDATE_STATE).sendToTarget();
}
@@ -120,18 +121,32 @@ public class QSCarrierGroupController {
if (statusIcon.icon == R.drawable.ic_qs_no_calling_sms) {
if (statusIcon.visible) {
- mInfos[slotIndex] = new CellSignalState(true,
- statusIcon.icon, statusIcon.contentDescription, "", false);
+ mInfos[slotIndex] = new CellSignalState(
+ true,
+ statusIcon.icon,
+ statusIcon.contentDescription,
+ "",
+ false,
+ mProviderModel);
} else {
// Whenever the no Calling & SMS state is cleared, switched to the last
// known call strength icon.
if (displayCallStrengthIcon) {
mInfos[slotIndex] = new CellSignalState(
- true, mLastSignalLevel[slotIndex],
- mLastSignalLevelDescription[slotIndex], "", false);
+ true,
+ mLastSignalLevel[slotIndex],
+ mLastSignalLevelDescription[slotIndex],
+ "",
+ false,
+ mProviderModel);
} else {
mInfos[slotIndex] = new CellSignalState(
- true, R.drawable.ic_qs_sim_card, "", "", false);
+ true,
+ R.drawable.ic_qs_sim_card,
+ "",
+ "",
+ false,
+ mProviderModel);
}
}
mMainHandler.obtainMessage(H.MSG_UPDATE_STATE).sendToTarget();
@@ -143,11 +158,21 @@ public class QSCarrierGroupController {
if (mInfos[slotIndex].mobileSignalIconId
!= R.drawable.ic_qs_no_calling_sms) {
if (displayCallStrengthIcon) {
- mInfos[slotIndex] = new CellSignalState(true, statusIcon.icon,
- statusIcon.contentDescription, "", false);
+ mInfos[slotIndex] = new CellSignalState(
+ true,
+ statusIcon.icon,
+ statusIcon.contentDescription,
+ "",
+ false,
+ mProviderModel);
} else {
mInfos[slotIndex] = new CellSignalState(
- true, R.drawable.ic_qs_sim_card, "", "", false);
+ true,
+ R.drawable.ic_qs_sim_card,
+ "",
+ "",
+ false,
+ mProviderModel);
}
mMainHandler.obtainMessage(H.MSG_UPDATE_STATE).sendToTarget();
}
@@ -182,8 +207,9 @@ public class QSCarrierGroupController {
@Background Handler bgHandler, @Main Looper mainLooper,
NetworkController networkController,
CarrierTextManager.Builder carrierTextManagerBuilder, Context context,
- CarrierConfigTracker carrierConfigTracker) {
- if (FeatureFlagUtils.isEnabled(context, FeatureFlagUtils.SETTINGS_PROVIDER_MODEL)) {
+ CarrierConfigTracker carrierConfigTracker, FeatureFlags featureFlags) {
+
+ if (featureFlags.isCombinedStatusBarSignalIconsEnabled()) {
mProviderModel = true;
} else {
mProviderModel = false;
@@ -217,9 +243,13 @@ public class QSCarrierGroupController {
mCarrierDividers[1] = view.getCarrierDivider2();
for (int i = 0; i < SIM_SLOTS; i++) {
- mInfos[i] = new CellSignalState(true, R.drawable.ic_qs_no_calling_sms,
+ mInfos[i] = new CellSignalState(
+ true,
+ R.drawable.ic_qs_no_calling_sms,
context.getText(AccessibilityContentDescriptions.NO_CALLING).toString(),
- "", false);
+ "",
+ false,
+ mProviderModel);
mLastSignalLevel[i] = TelephonyIcons.MOBILE_CALL_STRENGTH_ICONS[0];
mLastSignalLevelDescription[i] =
context.getText(AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH[0])
@@ -289,7 +319,8 @@ public class QSCarrierGroupController {
for (int i = 0; i < SIM_SLOTS; i++) {
if (mInfos[i].visible
&& mInfos[i].mobileSignalIconId == R.drawable.ic_qs_sim_card) {
- mInfos[i] = new CellSignalState(true, R.drawable.ic_blank, "", "", false);
+ mInfos[i] = new CellSignalState(true, R.drawable.ic_blank, "", "", false,
+ mProviderModel);
}
}
}
@@ -401,12 +432,13 @@ public class QSCarrierGroupController {
private final CarrierTextManager.Builder mCarrierTextControllerBuilder;
private final Context mContext;
private final CarrierConfigTracker mCarrierConfigTracker;
+ private final FeatureFlags mFeatureFlags;
@Inject
public Builder(ActivityStarter activityStarter, @Background Handler handler,
@Main Looper looper, NetworkController networkController,
CarrierTextManager.Builder carrierTextControllerBuilder, Context context,
- CarrierConfigTracker carrierConfigTracker) {
+ CarrierConfigTracker carrierConfigTracker, FeatureFlags featureFlags) {
mActivityStarter = activityStarter;
mHandler = handler;
mLooper = looper;
@@ -414,6 +446,7 @@ public class QSCarrierGroupController {
mCarrierTextControllerBuilder = carrierTextControllerBuilder;
mContext = context;
mCarrierConfigTracker = carrierConfigTracker;
+ mFeatureFlags = featureFlags;
}
public Builder setQSCarrierGroup(QSCarrierGroup view) {
@@ -424,7 +457,7 @@ public class QSCarrierGroupController {
public QSCarrierGroupController build() {
return new QSCarrierGroupController(mView, mActivityStarter, mHandler, mLooper,
mNetworkController, mCarrierTextControllerBuilder, mContext,
- mCarrierConfigTracker);
+ mCarrierConfigTracker, mFeatureFlags);
}
}
}
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 d017c74b4306..b904505b6469 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
@@ -567,6 +567,8 @@ public class TileAdapter extends RecyclerView.Adapter<Holder> implements TileSta
public void clearDrag() {
itemView.clearAnimation();
+ itemView.setScaleX(1);
+ itemView.setScaleY(1);
}
public void startDrag() {
@@ -812,5 +814,12 @@ public class TileAdapter extends RecyclerView.Adapter<Holder> implements TileSta
@Override
public void onSwiped(ViewHolder viewHolder, int direction) {
}
+
+ // Just in case, make sure to animate to base state.
+ @Override
+ public void clearView(@NonNull RecyclerView recyclerView, @NonNull ViewHolder viewHolder) {
+ ((Holder) viewHolder).stopDrag();
+ super.clearView(recyclerView, viewHolder);
+ }
};
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java b/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java
index d72f8e9ca1c0..3cb715cee8e9 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java
@@ -29,7 +29,6 @@ import android.service.quicksettings.Tile;
import android.service.quicksettings.TileService;
import android.text.TextUtils;
import android.util.ArraySet;
-import android.util.FeatureFlagUtils;
import android.widget.Button;
import com.android.systemui.R;
@@ -42,6 +41,7 @@ import com.android.systemui.qs.dagger.QSScope;
import com.android.systemui.qs.external.CustomTile;
import com.android.systemui.qs.tileimpl.QSTileImpl.DrawableIcon;
import com.android.systemui.settings.UserTracker;
+import com.android.systemui.statusbar.FeatureFlags;
import com.android.systemui.util.leak.GarbageMonitor;
import java.util.ArrayList;
@@ -63,17 +63,24 @@ public class TileQueryHelper {
private final Executor mBgExecutor;
private final Context mContext;
private final UserTracker mUserTracker;
+ private final FeatureFlags mFeatureFlags;
private TileStateListener mListener;
private boolean mFinished;
@Inject
- public TileQueryHelper(Context context, UserTracker userTracker,
- @Main Executor mainExecutor, @Background Executor bgExecutor) {
+ public TileQueryHelper(
+ Context context,
+ UserTracker userTracker,
+ @Main Executor mainExecutor,
+ @Background Executor bgExecutor,
+ FeatureFlags featureFlags
+ ) {
mContext = context;
mMainExecutor = mainExecutor;
mBgExecutor = bgExecutor;
mUserTracker = userTracker;
+ mFeatureFlags = featureFlags;
}
public void setListener(TileStateListener listener) {
@@ -115,7 +122,7 @@ public class TileQueryHelper {
final ArrayList<QSTile> tilesToAdd = new ArrayList<>();
// TODO(b/174753536): Move it into the config file.
- if (FeatureFlagUtils.isEnabled(mContext, FeatureFlagUtils.SETTINGS_PROVIDER_MODEL)) {
+ if (mFeatureFlags.isProviderModelSettingEnabled()) {
possibleTiles.remove("cell");
possibleTiles.remove("wifi");
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java
index aa51771864b2..1d791f5d632c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java
@@ -57,6 +57,12 @@ public class TileLifecycleManager extends BroadcastReceiver implements
private static final String TAG = "TileLifecycleManager";
+ private static final int META_DATA_QUERY_FLAGS =
+ PackageManager.GET_META_DATA
+ | PackageManager.MATCH_UNINSTALLED_PACKAGES
+ | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
+ | PackageManager.MATCH_DIRECT_BOOT_AWARE;
+
private static final int MSG_ON_ADDED = 0;
private static final int MSG_ON_REMOVED = 1;
private static final int MSG_ON_CLICK = 2;
@@ -130,7 +136,7 @@ public class TileLifecycleManager extends BroadcastReceiver implements
public boolean isActiveTile() {
try {
ServiceInfo info = mPackageManagerAdapter.getServiceInfo(mIntent.getComponent(),
- PackageManager.MATCH_UNINSTALLED_PACKAGES | PackageManager.GET_META_DATA);
+ META_DATA_QUERY_FLAGS);
return info.metaData != null
&& info.metaData.getBoolean(TileService.META_DATA_ACTIVE_TILE, false);
} catch (PackageManager.NameNotFoundException e) {
@@ -148,7 +154,7 @@ public class TileLifecycleManager extends BroadcastReceiver implements
public boolean isToggleableTile() {
try {
ServiceInfo info = mPackageManagerAdapter.getServiceInfo(mIntent.getComponent(),
- PackageManager.MATCH_UNINSTALLED_PACKAGES | PackageManager.GET_META_DATA);
+ META_DATA_QUERY_FLAGS);
return info.metaData != null
&& info.metaData.getBoolean(TileService.META_DATA_TOGGLEABLE_TILE, false);
} catch (PackageManager.NameNotFoundException e) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/IgnorableChildLinearLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/IgnorableChildLinearLayout.kt
index 2bac29846893..56bf3d59f648 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/IgnorableChildLinearLayout.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/IgnorableChildLinearLayout.kt
@@ -23,7 +23,7 @@ import android.widget.LinearLayout
/**
* [LinearLayout] that can ignore the last child for measuring.
*
- * The view is measured as regularlt, then if [ignoreLastView] is true:
+ * The view is measured as regularly, then if [ignoreLastView] is true:
* * In [LinearLayout.VERTICAL] orientation, the height of the last view is subtracted from the
* final measured height.
* * In [LinearLayout.HORIZONTAL] orientation, the width of the last view is subtracted from the
@@ -41,8 +41,21 @@ class IgnorableChildLinearLayout @JvmOverloads constructor(
var ignoreLastView = false
+ /**
+ * Forces [MeasureSpec.UNSPECIFIED] in the direction of layout
+ */
+ var forceUnspecifiedMeasure = false
+
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
- super.onMeasure(widthMeasureSpec, heightMeasureSpec)
+ val actualWidthSpec = if (forceUnspecifiedMeasure && orientation == HORIZONTAL) {
+ MeasureSpec.makeMeasureSpec(widthMeasureSpec, MeasureSpec.UNSPECIFIED)
+ } else widthMeasureSpec
+
+ val actualHeightSpec = if (forceUnspecifiedMeasure && orientation == VERTICAL) {
+ MeasureSpec.makeMeasureSpec(heightMeasureSpec, MeasureSpec.UNSPECIFIED)
+ } else heightMeasureSpec
+
+ super.onMeasure(actualWidthSpec, actualHeightSpec)
if (ignoreLastView && childCount > 0) {
val lastView = getChildAt(childCount - 1)
if (lastView.visibility != GONE) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
index d31e67c55777..70685a68e182 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
@@ -189,6 +189,11 @@ open class QSTileViewImpl @JvmOverloads constructor(
secondaryLabel = labelContainer.requireViewById(R.id.app_label)
if (collapsed) {
labelContainer.ignoreLastView = true
+ // Ideally, it'd be great if the parent could set this up when measuring just this child
+ // instead of the View class having to support this. However, due to the mysteries of
+ // LinearLayout's double measure pass, we cannot overwrite `measureChild` or any of its
+ // sibling methods to have special behavior for labelContainer.
+ labelContainer.forceUnspecifiedMeasure = true
secondaryLabel.alpha = 0f
// Do not marque in QQS
label.ellipsize = TextUtils.TruncateAt.END
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 98cd88af232f..82b6c0c1805d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java
@@ -73,6 +73,7 @@ public class QuickAccessWalletTile extends QSTileImpl<QSTile.State> {
private final QuickAccessWalletController mController;
private WalletCard mSelectedCard;
+ private boolean mIsWalletUpdating = true;
@VisibleForTesting Drawable mCardViewDrawable;
@Inject
@@ -110,7 +111,8 @@ public class QuickAccessWalletTile extends QSTileImpl<QSTile.State> {
super.handleSetListening(listening);
if (listening) {
mController.setupWalletChangeObservers(mCardRetriever, DEFAULT_PAYMENT_APP_CHANGE);
- if (!mController.getWalletClient().isWalletServiceAvailable()) {
+ if (!mController.getWalletClient().isWalletServiceAvailable()
+ || !mController.getWalletClient().isWalletFeatureAvailable()) {
Log.i(TAG, "QAW service is unavailable, recreating the wallet client.");
mController.reCreateWalletClient();
}
@@ -156,9 +158,14 @@ public class QuickAccessWalletTile extends QSTileImpl<QSTile.State> {
CharSequence label = mController.getWalletClient().getServiceLabel();
state.label = label == null ? mLabel : label;
state.contentDescription = state.label;
- state.icon = ResourceIcon.get(R.drawable.ic_wallet_lockscreen);
+ Drawable tileIcon = mController.getWalletClient().getTileIcon();
+ state.icon =
+ tileIcon == null
+ ? ResourceIcon.get(R.drawable.ic_wallet_lockscreen)
+ : new DrawableIcon(tileIcon);
boolean isDeviceLocked = !mKeyguardStateController.isUnlocked();
- if (mController.getWalletClient().isWalletServiceAvailable()) {
+ if (mController.getWalletClient().isWalletServiceAvailable()
+ && mController.getWalletClient().isWalletFeatureAvailable()) {
if (mSelectedCard != null) {
if (isDeviceLocked) {
state.state = Tile.STATE_INACTIVE;
@@ -172,7 +179,11 @@ public class QuickAccessWalletTile extends QSTileImpl<QSTile.State> {
}
} else {
state.state = Tile.STATE_INACTIVE;
- state.secondaryLabel = mContext.getString(R.string.wallet_secondary_label_no_card);
+ state.secondaryLabel =
+ mContext.getString(
+ mIsWalletUpdating
+ ? R.string.wallet_secondary_label_updating
+ : R.string.wallet_secondary_label_no_card);
state.sideViewCustomDrawable = null;
}
state.stateDescription = state.secondaryLabel;
@@ -218,6 +229,7 @@ public class QuickAccessWalletTile extends QSTileImpl<QSTile.State> {
@Override
public void onWalletCardsRetrieved(@NonNull GetWalletCardsResponse response) {
Log.i(TAG, "Successfully retrieved wallet cards.");
+ mIsWalletUpdating = false;
List<WalletCard> cards = response.getWalletCards();
if (cards.isEmpty()) {
Log.d(TAG, "No wallet cards exist.");
@@ -240,7 +252,7 @@ public class QuickAccessWalletTile extends QSTileImpl<QSTile.State> {
@Override
public void onWalletCardRetrievalError(@NonNull GetWalletCardsError error) {
- Log.w(TAG, "Error retrieve wallet cards");
+ mIsWalletUpdating = false;
mCardViewDrawable = null;
mSelectedCard = null;
refreshState();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyToggleTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyToggleTile.java
index f13576c2d4cc..bf72b7728232 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyToggleTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyToggleTile.java
@@ -16,6 +16,8 @@
package com.android.systemui.qs.tiles;
+import static android.hardware.SensorPrivacyManager.Sources.QS_TILE;
+
import android.content.Intent;
import android.hardware.SensorPrivacyManager.Sensors.Sensor;
import android.os.Handler;
@@ -87,12 +89,12 @@ public abstract class SensorPrivacyToggleTile extends QSTileImpl<QSTile.BooleanS
protected void handleClick(@Nullable View view) {
if (mKeyguard.isMethodSecure() && mKeyguard.isShowing()) {
mActivityStarter.postQSRunnableDismissingKeyguard(() -> {
- mSensorPrivacyController.setSensorBlocked(getSensorId(),
+ mSensorPrivacyController.setSensorBlocked(QS_TILE, getSensorId(),
!mSensorPrivacyController.isSensorBlocked(getSensorId()));
});
return;
}
- mSensorPrivacyController.setSensorBlocked(getSensorId(),
+ mSensorPrivacyController.setSensorBlocked(QS_TILE, getSensorId(),
!mSensorPrivacyController.isSensorBlocked(getSensorId()));
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
index c6b5eb7508af..5bb3413595ba 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
@@ -291,19 +291,24 @@ public class RecordingService extends Service implements MediaRecorder.OnInfoLis
? res.getString(R.string.screenrecord_ongoing_screen_only)
: res.getString(R.string.screenrecord_ongoing_screen_and_audio);
- Intent stopIntent = getNotificationIntent(this);
+ PendingIntent pendingIntent = PendingIntent.getService(
+ this,
+ REQUEST_CODE,
+ getNotificationIntent(this),
+ PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
+ Notification.Action stopAction = new Notification.Action.Builder(
+ Icon.createWithResource(this, R.drawable.ic_android),
+ getResources().getString(R.string.screenrecord_stop_label),
+ pendingIntent).build();
Notification.Builder builder = new Notification.Builder(this, CHANNEL_ID)
.setSmallIcon(R.drawable.ic_screenrecord)
.setContentTitle(notificationTitle)
- .setContentText(getResources().getString(R.string.screenrecord_stop_text))
.setUsesChronometer(true)
.setColorized(true)
.setColor(getResources().getColor(R.color.GM2_red_700))
.setOngoing(true)
.setForegroundServiceBehavior(Notification.FOREGROUND_SERVICE_IMMEDIATE)
- .setContentIntent(
- PendingIntent.getService(this, REQUEST_CODE, stopIntent,
- PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE))
+ .addAction(stopAction)
.addExtras(extras);
startForeground(NOTIFICATION_RECORDING_ID, builder.build());
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialog.java b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialog.java
index 57125f34731c..df766f3625e4 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialog.java
@@ -30,9 +30,9 @@ import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowManager;
import android.widget.ArrayAdapter;
-import android.widget.Button;
import android.widget.Spinner;
import android.widget.Switch;
+import android.widget.TextView;
import com.android.systemui.R;
import com.android.systemui.settings.UserContextProvider;
@@ -78,12 +78,12 @@ public class ScreenRecordDialog extends Activity {
setContentView(R.layout.screen_record_dialog);
- Button cancelBtn = findViewById(R.id.button_cancel);
+ TextView cancelBtn = findViewById(R.id.button_cancel);
cancelBtn.setOnClickListener(v -> {
finish();
});
- Button startBtn = findViewById(R.id.button_start);
+ TextView startBtn = findViewById(R.id.button_start);
startBtn.setOnClickListener(v -> {
requestScreenCapture();
finish();
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/CropView.java b/packages/SystemUI/src/com/android/systemui/screenshot/CropView.java
index 0a60f6da159e..a9cecaaf1f76 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/CropView.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/CropView.java
@@ -44,6 +44,7 @@ import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
import androidx.customview.widget.ExploreByTouchHelper;
import androidx.interpolator.view.animation.FastOutSlowInInterpolator;
+import com.android.internal.graphics.ColorUtils;
import com.android.systemui.R;
import java.util.List;
@@ -95,7 +96,9 @@ public class CropView extends View {
TypedArray t = context.getTheme().obtainStyledAttributes(
attrs, R.styleable.CropView, 0, 0);
mShadePaint = new Paint();
- mShadePaint.setColor(t.getColor(R.styleable.CropView_scrimColor, Color.TRANSPARENT));
+ int alpha = t.getInteger(R.styleable.CropView_scrimAlpha, 255);
+ int scrimColor = t.getColor(R.styleable.CropView_scrimColor, Color.TRANSPARENT);
+ mShadePaint.setColor(ColorUtils.setAlphaComponent(scrimColor, alpha));
mContainerBackgroundPaint = new Paint();
mContainerBackgroundPaint.setColor(t.getColor(R.styleable.CropView_containerBackgroundColor,
Color.TRANSPARENT));
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ImageExporter.java b/packages/SystemUI/src/com/android/systemui/screenshot/ImageExporter.java
index 4aead817fe8c..55602a98b8c5 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ImageExporter.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ImageExporter.java
@@ -111,30 +111,21 @@ class ImageExporter {
}
/**
- * Stores the given Bitmap to a temp file.
+ * Writes the given Bitmap to outputFile.
*/
- ListenableFuture<File> exportAsTempFile(Executor executor, Bitmap bitmap) {
+ ListenableFuture<File> exportToRawFile(Executor executor, Bitmap bitmap,
+ final File outputFile) {
return CallbackToFutureAdapter.getFuture(
(completer) -> {
executor.execute(() -> {
- File cachePath;
- try {
- cachePath = File.createTempFile("long_screenshot_cache_", ".tmp");
- try (FileOutputStream stream = new FileOutputStream(cachePath)) {
- bitmap.compress(mCompressFormat, mQuality, stream);
- } catch (IOException e) {
- if (cachePath.exists()) {
- //noinspection ResultOfMethodCallIgnored
- cachePath.delete();
- cachePath = null;
- }
- completer.setException(e);
- }
- if (cachePath != null) {
- completer.set(cachePath);
- }
+ try (FileOutputStream stream = new FileOutputStream(outputFile)) {
+ bitmap.compress(mCompressFormat, mQuality, stream);
+ completer.set(outputFile);
} catch (IOException e) {
- // Failed to create a new file
+ if (outputFile.exists()) {
+ //noinspection ResultOfMethodCallIgnored
+ outputFile.delete();
+ }
completer.setException(e);
}
});
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java b/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java
index f571e417c636..af0141c81d58 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java
@@ -80,6 +80,7 @@ public class LongScreenshotActivity extends Activity {
private View mSave;
private View mEdit;
private View mShare;
+ private View mDelete;
private CropView mCropView;
private MagnifierView mMagnifierView;
private ScrollCaptureResponse mScrollCaptureResponse;
@@ -120,6 +121,7 @@ public class LongScreenshotActivity extends Activity {
mSave = requireViewById(R.id.save);
mEdit = requireViewById(R.id.edit);
mShare = requireViewById(R.id.share);
+ mDelete = requireViewById(R.id.delete);
mCropView = requireViewById(R.id.crop_view);
mMagnifierView = requireViewById(R.id.magnifier);
mCropView.setCropInteractionListener(mMagnifierView);
@@ -130,6 +132,13 @@ public class LongScreenshotActivity extends Activity {
mEdit.setOnClickListener(this::onClicked);
mShare.setOnClickListener(this::onClicked);
+ // Only show the delete button if we have something to delete (should typically be the case)
+ if (getIntent().getData() != null) {
+ mDelete.setOnClickListener(this::onClicked);
+ } else {
+ mDelete.setVisibility(View.GONE);
+ }
+
mPreview.addOnLayoutChangeListener(
(v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) ->
updateImageDimensions());
@@ -200,7 +209,6 @@ public class LongScreenshotActivity extends Activity {
/ (float) mLongScreenshot.getHeight());
mEnterTransitionView.setImageDrawable(drawable);
-
mEnterTransitionView.getViewTreeObserver().addOnPreDrawListener(
new ViewTreeObserver.OnPreDrawListener() {
@Override
@@ -220,7 +228,6 @@ public class LongScreenshotActivity extends Activity {
mCropView.animateEntrance();
mCropView.setVisibility(View.VISIBLE);
setButtonsEnabled(true);
- mEnterTransitionView.setVisibility(View.GONE);
});
});
return true;
@@ -228,8 +235,8 @@ public class LongScreenshotActivity extends Activity {
});
// Immediately export to temp image file for saved state
- mCacheSaveFuture = mImageExporter.exportAsTempFile(mBackgroundExecutor,
- mLongScreenshot.toBitmap());
+ mCacheSaveFuture = mImageExporter.exportToRawFile(mBackgroundExecutor,
+ mLongScreenshot.toBitmap(), new File(getCacheDir(), "long_screenshot_cache.png"));
mCacheSaveFuture.addListener(() -> {
try {
// Get the temp file path to persist, used in onSavedInstanceState
@@ -321,6 +328,7 @@ public class LongScreenshotActivity extends Activity {
mSave.setEnabled(enabled);
mEdit.setEnabled(enabled);
mShare.setEnabled(enabled);
+ mDelete.setEnabled(enabled);
}
private void doEdit(Uri uri) {
@@ -334,11 +342,18 @@ public class LongScreenshotActivity extends Activity {
| Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
mTransitionView.setImageBitmap(mOutputBitmap);
- mTransitionView.setVisibility(View.VISIBLE);
mTransitionView.setTransitionName(
ChooserActivity.FIRST_IMAGE_PREVIEW_TRANSITION_NAME);
// TODO: listen for transition completing instead of finishing onStop
mTransitionStarted = true;
+ int[] locationOnScreen = new int[2];
+ mTransitionView.getLocationOnScreen(locationOnScreen);
+ int[] locationInWindow = new int[2];
+ mTransitionView.getLocationInWindow(locationInWindow);
+ int deltaX = locationOnScreen[0] - locationInWindow[0];
+ int deltaY = locationOnScreen[1] - locationInWindow[1];
+ mTransitionView.setX(mTransitionView.getX() - deltaX);
+ mTransitionView.setY(mTransitionView.getY() - deltaY);
startActivity(intent,
ActivityOptions.makeSceneTransitionAnimation(this, mTransitionView,
ChooserActivity.FIRST_IMAGE_PREVIEW_TRANSITION_NAME).toBundle());
@@ -368,6 +383,11 @@ public class LongScreenshotActivity extends Activity {
} else if (id == R.id.share) {
mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_SHARE);
startExport(PendingAction.SHARE);
+ } else if (id == R.id.delete) {
+ mBackgroundExecutor.execute(() -> {
+ getContentResolver().delete(getIntent().getData(), null);
+ finishAndRemoveTask();
+ });
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/MagnifierView.java b/packages/SystemUI/src/com/android/systemui/screenshot/MagnifierView.java
index 34b40f79836b..78737329750a 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/MagnifierView.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/MagnifierView.java
@@ -33,6 +33,7 @@ import android.view.ViewPropertyAnimator;
import androidx.annotation.Nullable;
+import com.android.internal.graphics.ColorUtils;
import com.android.systemui.R;
/**
@@ -83,7 +84,9 @@ public class MagnifierView extends View implements CropView.CropInteractionListe
TypedArray t = context.getTheme().obtainStyledAttributes(
attrs, R.styleable.MagnifierView, 0, 0);
mShadePaint = new Paint();
- mShadePaint.setColor(t.getColor(R.styleable.MagnifierView_scrimColor, Color.TRANSPARENT));
+ int alpha = t.getInteger(R.styleable.MagnifierView_scrimAlpha, 255);
+ int scrimColor = t.getColor(R.styleable.MagnifierView_scrimColor, Color.TRANSPARENT);
+ mShadePaint.setColor(ColorUtils.setAlphaComponent(scrimColor, alpha));
mHandlePaint = new Paint();
mHandlePaint.setColor(t.getColor(R.styleable.MagnifierView_handleColor, Color.BLACK));
mHandlePaint.setStrokeWidth(
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index 2e138362aedb..52b393f563b6 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -16,6 +16,7 @@
package com.android.systemui.screenshot;
+import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.view.WindowManager.LayoutParams.TYPE_SCREENSHOT;
@@ -33,6 +34,7 @@ import static java.util.Objects.requireNonNull;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.annotation.Nullable;
+import android.app.ActivityManager;
import android.app.ActivityOptions;
import android.app.ExitTransitionCoordinator;
import android.app.ExitTransitionCoordinator.ExitTransitionCallbacks;
@@ -253,13 +255,16 @@ public class ScreenshotController {
private final DisplayManager mDisplayManager;
private final ScrollCaptureController mScrollCaptureController;
private final LongScreenshotData mLongScreenshotHolder;
+ private final boolean mIsLowRamDevice;
private ScreenshotView mScreenshotView;
private Bitmap mScreenBitmap;
private SaveImageInBackgroundTask mSaveInBgTask;
+ private boolean mScreenshotTakenInPortrait;
private Animator mScreenshotAnimation;
private RequestCallback mCurrentRequestCallback;
+ private Uri mLatestUriSaved;
private final Handler mScreenshotHandler = new Handler(Looper.getMainLooper()) {
@Override
@@ -297,7 +302,8 @@ public class ScreenshotController {
ImageExporter imageExporter,
@Main Executor mainExecutor,
ScrollCaptureController scrollCaptureController,
- LongScreenshotData longScreenshotHolder) {
+ LongScreenshotData longScreenshotHolder,
+ ActivityManager activityManager) {
mScreenshotSmartActions = screenshotSmartActions;
mNotificationsController = screenshotNotificationsController;
mScrollCaptureClient = scrollCaptureClient;
@@ -306,6 +312,7 @@ public class ScreenshotController {
mMainExecutor = mainExecutor;
mScrollCaptureController = scrollCaptureController;
mLongScreenshotHolder = longScreenshotHolder;
+ mIsLowRamDevice = activityManager.isLowRamDevice();
mBgExecutor = Executors.newSingleThreadExecutor();
mDisplayManager = requireNonNull(context.getSystemService(DisplayManager.class));
@@ -484,11 +491,29 @@ public class ScreenshotController {
* Takes a screenshot of the current display and shows an animation.
*/
private void takeScreenshotInternal(Consumer<Uri> finisher, Rect crop) {
+ mScreenshotTakenInPortrait =
+ mContext.getResources().getConfiguration().orientation == ORIENTATION_PORTRAIT;
+
// copy the input Rect, since SurfaceControl.screenshot can mutate it
Rect screenRect = new Rect(crop);
+ Bitmap screenshot = captureScreenshot(crop);
+
+ if (screenshot == null) {
+ Log.e(TAG, "takeScreenshotInternal: Screenshot bitmap was null");
+ mNotificationsController.notifyScreenshotError(
+ R.string.screenshot_failed_to_capture_text);
+ if (mCurrentRequestCallback != null) {
+ mCurrentRequestCallback.reportError();
+ }
+ return;
+ }
+
+ saveScreenshot(screenshot, finisher, screenRect, Insets.NONE, true);
+ }
+
+ private Bitmap captureScreenshot(Rect crop) {
int width = crop.width();
int height = crop.height();
-
Bitmap screenshot = null;
final Display display = getDefaultDisplay();
final DisplayAddress address = display.getAddress();
@@ -509,18 +534,7 @@ public class ScreenshotController {
SurfaceControl.captureDisplay(captureArgs);
screenshot = screenshotBuffer == null ? null : screenshotBuffer.asBitmap();
}
-
- if (screenshot == null) {
- Log.e(TAG, "takeScreenshotInternal: Screenshot bitmap was null");
- mNotificationsController.notifyScreenshotError(
- R.string.screenshot_failed_to_capture_text);
- if (mCurrentRequestCallback != null) {
- mCurrentRequestCallback.reportError();
- }
- return;
- }
-
- saveScreenshot(screenshot, finisher, screenRect, Insets.NONE, true);
+ return screenshot;
}
private void saveScreenshot(Bitmap screenshot, Consumer<Uri> finisher, Rect screenRect,
@@ -533,7 +547,6 @@ public class ScreenshotController {
mAccessibilityManager.sendAccessibilityEvent(event);
}
-
if (mScreenshotView.isAttachedToWindow()) {
// if we didn't already dismiss for another reason
if (!mScreenshotView.isDismissing()) {
@@ -550,6 +563,7 @@ public class ScreenshotController {
.getWindowInsets().getDisplayCutout());
mScreenBitmap = screenshot;
+ mLatestUriSaved = null;
if (!isUserSetupComplete()) {
Log.w(TAG, "User setup not complete, displaying toast only");
@@ -617,6 +631,10 @@ public class ScreenshotController {
}
private void requestScrollCapture() {
+ if (!allowLongScreenshots()) {
+ Log.d(TAG, "Long screenshots not supported on this device");
+ return;
+ }
mScrollCaptureClient.setHostWindowToken(mWindow.getDecorView().getWindowToken());
if (mLastScrollCaptureRequest != null) {
mLastScrollCaptureRequest.cancel(true);
@@ -644,45 +662,61 @@ public class ScreenshotController {
final ScrollCaptureResponse response = mLastScrollCaptureResponse;
mScreenshotView.showScrollChip(/* onClick */ () -> {
- mScreenshotView.prepareScrollingTransition(response, mScreenBitmap);
- // Clear the reference to prevent close() in dismissScreenshot
- mLastScrollCaptureResponse = null;
- final ListenableFuture<ScrollCaptureController.LongScreenshot> future =
- mScrollCaptureController.run(response);
- future.addListener(() -> {
- ScrollCaptureController.LongScreenshot longScreenshot;
- try {
- longScreenshot = future.get();
- } catch (CancellationException | InterruptedException | ExecutionException e) {
- Log.e(TAG, "Exception", e);
- return;
- }
+ DisplayMetrics displayMetrics = new DisplayMetrics();
+ getDefaultDisplay().getRealMetrics(displayMetrics);
+ Bitmap newScreenshot = captureScreenshot(
+ new Rect(0, 0, displayMetrics.widthPixels, displayMetrics.heightPixels));
+
+ mScreenshotView.prepareScrollingTransition(response, mScreenBitmap, newScreenshot,
+ mScreenshotTakenInPortrait);
+ // delay starting scroll capture to make sure the scrim is up before the app moves
+ mScreenshotView.post(() -> {
+ // Clear the reference to prevent close() in dismissScreenshot
+ mLastScrollCaptureResponse = null;
+ final ListenableFuture<ScrollCaptureController.LongScreenshot> future =
+ mScrollCaptureController.run(response);
+ future.addListener(() -> {
+ ScrollCaptureController.LongScreenshot longScreenshot;
+
+ try {
+ longScreenshot = future.get();
+ } catch (CancellationException
+ | InterruptedException
+ | ExecutionException e) {
+ Log.e(TAG, "Exception", e);
+ mScreenshotView.restoreNonScrollingUi();
+ return;
+ }
- mLongScreenshotHolder.setLongScreenshot(longScreenshot);
- mLongScreenshotHolder.setTransitionDestinationCallback(
- (transitionDestination, onTransitionEnd) ->
- mScreenshotView.startLongScreenshotTransition(
- transitionDestination, onTransitionEnd,
- longScreenshot));
-
- final Intent intent = new Intent(mContext, LongScreenshotActivity.class);
- intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
-
- Pair<ActivityOptions, ExitTransitionCoordinator> transition =
- ActivityOptions.startSharedElementAnimation(mWindow,
- new ScreenshotExitTransitionCallbacksSupplier(false).get(),
- null);
- transition.second.startExit();
- mContext.startActivity(intent, transition.first.toBundle());
- RemoteAnimationAdapter runner = new RemoteAnimationAdapter(
- SCREENSHOT_REMOTE_RUNNER, 0, 0);
- try {
- WindowManagerGlobal.getWindowManagerService()
- .overridePendingAppTransitionRemote(runner, DEFAULT_DISPLAY);
- } catch (Exception e) {
- Log.e(TAG, "Error overriding screenshot app transition", e);
- }
- }, mMainExecutor);
+ if (longScreenshot.getHeight() == 0) {
+ mScreenshotView.restoreNonScrollingUi();
+ return;
+ }
+
+ mLongScreenshotHolder.setLongScreenshot(longScreenshot);
+ mLongScreenshotHolder.setTransitionDestinationCallback(
+ (transitionDestination, onTransitionEnd) ->
+ mScreenshotView.startLongScreenshotTransition(
+ transitionDestination, onTransitionEnd,
+ longScreenshot));
+
+ final Intent intent = new Intent(mContext, LongScreenshotActivity.class);
+ intent.setData(mLatestUriSaved);
+ intent.setFlags(
+ Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
+
+ mContext.startActivity(intent,
+ ActivityOptions.makeCustomAnimation(mContext, 0, 0).toBundle());
+ RemoteAnimationAdapter runner = new RemoteAnimationAdapter(
+ SCREENSHOT_REMOTE_RUNNER, 0, 0);
+ try {
+ WindowManagerGlobal.getWindowManagerService()
+ .overridePendingAppTransitionRemote(runner, DEFAULT_DISPLAY);
+ } catch (Exception e) {
+ Log.e(TAG, "Error overriding screenshot app transition", e);
+ }
+ }, mMainExecutor);
+ });
});
} catch (CancellationException e) {
// Ignore
@@ -857,6 +891,8 @@ public class ScreenshotController {
resetTimeout();
+ mLatestUriSaved = imageData.uri;
+
if (imageData.uri != null) {
mScreenshotHandler.post(() -> {
if (mScreenshotAnimation != null && mScreenshotAnimation.isRunning()) {
@@ -904,10 +940,12 @@ public class ScreenshotController {
*/
private Supplier<ActionTransition> getActionTransitionSupplier() {
return () -> {
+ View preview = mScreenshotView.getTransitionView();
+ preview.setX(preview.getX() - mScreenshotView.getStaticLeftMargin());
Pair<ActivityOptions, ExitTransitionCoordinator> transition =
ActivityOptions.startSharedElementAnimation(
mWindow, new ScreenshotExitTransitionCallbacksSupplier(true).get(),
- null, Pair.create(mScreenshotView.getScreenshotPreview(),
+ null, Pair.create(mScreenshotView.getTransitionView(),
ChooserActivity.FIRST_IMAGE_PREVIEW_TRANSITION_NAME));
transition.second.startExit();
@@ -967,6 +1005,10 @@ public class ScreenshotController {
return mDisplayManager.getDisplay(DEFAULT_DISPLAY);
}
+ private boolean allowLongScreenshots() {
+ return !mIsLowRamDevice;
+ }
+
/** Does the aspect ratio of the bitmap with insets removed match the bounds. */
private static boolean aspectRatiosMatch(Bitmap bitmap, Insets bitmapInsets,
Rect screenBounds) {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
index 669046591170..e9e62f26a10e 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
@@ -36,8 +36,10 @@ import android.app.ActivityManager;
import android.app.Notification;
import android.app.PendingIntent;
import android.content.Context;
+import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.graphics.Bitmap;
+import android.graphics.BlendMode;
import android.graphics.Color;
import android.graphics.Insets;
import android.graphics.Matrix;
@@ -135,11 +137,13 @@ public class ScreenshotView extends FrameLayout implements
private int mNavMode;
private boolean mOrientationPortrait;
private boolean mDirectionLTR;
+ private int mStaticLeftMargin;
private ScreenshotSelectorView mScreenshotSelectorView;
private ImageView mScrollingScrim;
private View mScreenshotStatic;
private ImageView mScreenshotPreview;
+ private View mTransitionView;
private View mScreenshotPreviewBorder;
private ImageView mScrollablePreview;
private ImageView mScreenshotFlash;
@@ -160,6 +164,7 @@ public class ScreenshotView extends FrameLayout implements
private GestureDetector mSwipeDetector;
private SwipeDismissHandler mSwipeDismissHandler;
private InputMonitorCompat mInputMonitor;
+ private boolean mShowScrollablePreview;
private final ArrayList<ScreenshotActionChip> mSmartChips = new ArrayList<>();
private PendingInteraction mPendingInteraction;
@@ -257,10 +262,10 @@ public class ScreenshotView extends FrameLayout implements
@Override // ViewTreeObserver.OnComputeInternalInsetsListener
public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo inoutInfo) {
inoutInfo.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
- inoutInfo.touchableRegion.set(getTouchRegion());
+ inoutInfo.touchableRegion.set(getTouchRegion(true));
}
- private Region getTouchRegion() {
+ private Region getTouchRegion(boolean includeScrim) {
Region touchRegion = new Region();
final Rect tmpRect = new Rect();
@@ -273,6 +278,11 @@ public class ScreenshotView extends FrameLayout implements
mDismissButton.getBoundsOnScreen(tmpRect);
touchRegion.op(tmpRect, Region.Op.UNION);
+ if (includeScrim && mScrollingScrim.getVisibility() == View.VISIBLE) {
+ mScrollingScrim.getBoundsOnScreen(tmpRect);
+ touchRegion.op(tmpRect, Region.Op.UNION);
+ }
+
if (QuickStepContract.isGesturalMode(mNavMode)) {
final WindowManager wm = mContext.getSystemService(WindowManager.class);
final WindowMetrics windowMetrics = wm.getCurrentWindowMetrics();
@@ -296,7 +306,7 @@ public class ScreenshotView extends FrameLayout implements
if (ev instanceof MotionEvent) {
MotionEvent event = (MotionEvent) ev;
if (event.getActionMasked() == MotionEvent.ACTION_DOWN
- && !getTouchRegion().contains(
+ && !getTouchRegion(false).contains(
(int) event.getRawX(), (int) event.getRawY())) {
mCallbacks.onTouchOutside();
}
@@ -313,6 +323,10 @@ public class ScreenshotView extends FrameLayout implements
@Override // ViewGroup
public boolean onInterceptTouchEvent(MotionEvent ev) {
+ // scrolling scrim should not be swipeable; return early if we're on the scrim
+ if (!getTouchRegion(false).contains((int) ev.getRawX(), (int) ev.getRawY())) {
+ return false;
+ }
// always pass through the down event so the swipe handler knows the initial state
if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) {
mSwipeDismissHandler.onTouch(this, ev);
@@ -325,6 +339,7 @@ public class ScreenshotView extends FrameLayout implements
mScrollingScrim = requireNonNull(findViewById(R.id.screenshot_scrolling_scrim));
mScreenshotStatic = requireNonNull(findViewById(R.id.global_screenshot_static));
mScreenshotPreview = requireNonNull(findViewById(R.id.global_screenshot_preview));
+ mTransitionView = requireNonNull(findViewById(R.id.screenshot_transition_view));
mScreenshotPreviewBorder = requireNonNull(
findViewById(R.id.global_screenshot_preview_border));
mScreenshotPreview.setClipToOutline(true);
@@ -370,12 +385,12 @@ public class ScreenshotView extends FrameLayout implements
requestFocus();
}
- View getScreenshotPreview() {
- return mScreenshotPreview;
+ View getTransitionView() {
+ return mTransitionView;
}
- View getScrollablePreview() {
- return mScrollablePreview;
+ int getStaticLeftMargin() {
+ return mStaticLeftMargin;
}
/**
@@ -416,6 +431,7 @@ public class ScreenshotView extends FrameLayout implements
Math.max(cutout.getSafeInsetRight(), waterfall.right), waterfall.bottom);
}
}
+ mStaticLeftMargin = p.leftMargin;
mScreenshotStatic.setLayoutParams(p);
mScreenshotStatic.requestLayout();
}
@@ -453,14 +469,6 @@ public class ScreenshotView extends FrameLayout implements
mCornerSizeX / (mOrientationPortrait ? bounds.width() : bounds.height());
final float currentScale = 1 / cornerScale;
- mScreenshotPreview.setScaleX(currentScale);
- mScreenshotPreview.setScaleY(currentScale);
-
- if (mAccessibilityManager.isEnabled()) {
- mDismissButton.setAlpha(0);
- mDismissButton.setVisibility(View.VISIBLE);
- }
-
AnimatorSet dropInAnimation = new AnimatorSet();
ValueAnimator flashInAnimator = ValueAnimator.ofFloat(0, 1);
flashInAnimator.setDuration(SCREENSHOT_FLASH_IN_DURATION_MS);
@@ -491,6 +499,20 @@ public class ScreenshotView extends FrameLayout implements
ValueAnimator toCorner = ValueAnimator.ofFloat(0, 1);
toCorner.setDuration(SCREENSHOT_TO_CORNER_Y_DURATION_MS);
+
+ toCorner.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ mScreenshotPreview.setScaleX(currentScale);
+ mScreenshotPreview.setScaleY(currentScale);
+ mScreenshotPreview.setVisibility(View.VISIBLE);
+ if (mAccessibilityManager.isEnabled()) {
+ mDismissButton.setAlpha(0);
+ mDismissButton.setVisibility(View.VISIBLE);
+ }
+ }
+ });
+
float xPositionPct =
SCREENSHOT_TO_CORNER_X_DURATION_MS / (float) SCREENSHOT_TO_CORNER_Y_DURATION_MS;
float dismissPct =
@@ -534,13 +556,6 @@ public class ScreenshotView extends FrameLayout implements
}
});
- toCorner.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationStart(Animator animation) {
- mScreenshotPreview.setVisibility(View.VISIBLE);
- }
- });
-
mScreenshotFlash.setAlpha(0f);
mScreenshotFlash.setVisibility(View.VISIBLE);
@@ -766,70 +781,130 @@ public class ScreenshotView extends FrameLayout implements
void startLongScreenshotTransition(Rect destination, Runnable onTransitionEnd,
ScrollCaptureController.LongScreenshot longScreenshot) {
- mScrollablePreview.setImageBitmap(longScreenshot.toBitmap());
- ValueAnimator anim = ValueAnimator.ofFloat(0, 1);
- float startX = mScrollablePreview.getX();
- float startY = mScrollablePreview.getY();
- int[] locInScreen = mScrollablePreview.getLocationOnScreen();
- destination.offset((int) startX - locInScreen[0], (int) startY - locInScreen[1]);
- mScrollablePreview.setPivotX(0);
- mScrollablePreview.setPivotY(0);
- mScrollablePreview.setAlpha(1f);
- float currentScale = mScrollablePreview.getWidth() / (float) longScreenshot.getWidth();
- Matrix matrix = new Matrix();
- matrix.setScale(currentScale, currentScale);
- matrix.postTranslate(
- longScreenshot.getLeft() * currentScale, longScreenshot.getTop() * currentScale);
- mScrollablePreview.setImageMatrix(matrix);
- float destinationScale = destination.width() / (float) mScrollablePreview.getWidth();
- anim.addUpdateListener(animation -> {
- float t = animation.getAnimatedFraction();
- mScrollingScrim.setAlpha(1 - t);
- float currScale = MathUtils.lerp(1, destinationScale, t);
- mScrollablePreview.setScaleX(currScale);
- mScrollablePreview.setScaleY(currScale);
- mScrollablePreview.setX(MathUtils.lerp(startX, destination.left, t));
- mScrollablePreview.setY(MathUtils.lerp(startY, destination.top, t));
- });
- anim.addListener(new AnimatorListenerAdapter() {
+ AnimatorSet animSet = new AnimatorSet();
+
+ ValueAnimator scrimAnim = ValueAnimator.ofFloat(0, 1);
+ scrimAnim.addUpdateListener(animation ->
+ mScrollingScrim.setAlpha(1 - animation.getAnimatedFraction()));
+
+ if (mShowScrollablePreview) {
+ mScrollablePreview.setImageBitmap(longScreenshot.toBitmap());
+ float startX = mScrollablePreview.getX();
+ float startY = mScrollablePreview.getY();
+ int[] locInScreen = mScrollablePreview.getLocationOnScreen();
+ destination.offset((int) startX - locInScreen[0], (int) startY - locInScreen[1]);
+ mScrollablePreview.setPivotX(0);
+ mScrollablePreview.setPivotY(0);
+ mScrollablePreview.setAlpha(1f);
+ float currentScale = mScrollablePreview.getWidth() / (float) longScreenshot.getWidth();
+ Matrix matrix = new Matrix();
+ matrix.setScale(currentScale, currentScale);
+ matrix.postTranslate(
+ longScreenshot.getLeft() * currentScale,
+ longScreenshot.getTop() * currentScale);
+ mScrollablePreview.setImageMatrix(matrix);
+ float destinationScale = destination.width() / (float) mScrollablePreview.getWidth();
+
+ ValueAnimator previewAnim = ValueAnimator.ofFloat(0, 1);
+ previewAnim.addUpdateListener(animation -> {
+ float t = animation.getAnimatedFraction();
+ float currScale = MathUtils.lerp(1, destinationScale, t);
+ mScrollablePreview.setScaleX(currScale);
+ mScrollablePreview.setScaleY(currScale);
+ mScrollablePreview.setX(MathUtils.lerp(startX, destination.left, t));
+ mScrollablePreview.setY(MathUtils.lerp(startY, destination.top, t));
+ });
+ ValueAnimator previewFadeAnim = ValueAnimator.ofFloat(1, 0);
+ previewFadeAnim.addUpdateListener(animation ->
+ mScrollablePreview.setAlpha(1 - animation.getAnimatedFraction()));
+ animSet.play(previewAnim).with(scrimAnim).before(previewFadeAnim);
+ previewAnim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ super.onAnimationEnd(animation);
+ onTransitionEnd.run();
+ }
+ });
+ } else {
+ // if we switched orientations between the original screenshot and the long screenshot
+ // capture, just fade out the scrim instead of running the preview animation
+ animSet.play(scrimAnim);
+ animSet.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ super.onAnimationEnd(animation);
+ onTransitionEnd.run();
+ }
+ });
+ }
+ animSet.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
- onTransitionEnd.run();
- mScrollablePreview.animate().alpha(0).setListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- super.onAnimationEnd(animation);
mCallbacks.onDismiss();
}
- });
- }
});
- anim.start();
+ animSet.start();
}
- void prepareScrollingTransition(ScrollCaptureResponse response, Bitmap screenBitmap) {
- mScrollingScrim.setImageBitmap(screenBitmap);
+ void prepareScrollingTransition(ScrollCaptureResponse response, Bitmap screenBitmap,
+ Bitmap newBitmap, boolean screenshotTakenInPortrait) {
+ mShowScrollablePreview = (screenshotTakenInPortrait == mOrientationPortrait);
+
+ mScrollingScrim.setImageBitmap(newBitmap);
mScrollingScrim.setVisibility(View.VISIBLE);
- Rect scrollableArea = scrollableAreaOnScreen(response);
- float scale = mCornerSizeX
- / (mOrientationPortrait ? screenBitmap.getWidth() : screenBitmap.getHeight());
- ConstraintLayout.LayoutParams params =
- (ConstraintLayout.LayoutParams) mScrollablePreview.getLayoutParams();
-
- params.width = (int) (scale * scrollableArea.width());
- params.height = (int) (scale * scrollableArea.height());
- Matrix matrix = new Matrix();
- matrix.setScale(scale, scale);
- matrix.postTranslate(-scrollableArea.left * scale, -scrollableArea.top * scale);
-
- mScrollablePreview.setTranslationX(scale * scrollableArea.left);
- mScrollablePreview.setTranslationY(scale * scrollableArea.top);
- mScrollablePreview.setImageMatrix(matrix);
-
- mScrollablePreview.setImageBitmap(screenBitmap);
- mScrollablePreview.setVisibility(View.VISIBLE);
- createScreenshotFadeDismissAnimation(true).start();
+
+ if (mShowScrollablePreview) {
+ Rect scrollableArea = scrollableAreaOnScreen(response);
+
+ float scale = mCornerSizeX
+ / (mOrientationPortrait ? screenBitmap.getWidth() : screenBitmap.getHeight());
+ ConstraintLayout.LayoutParams params =
+ (ConstraintLayout.LayoutParams) mScrollablePreview.getLayoutParams();
+
+ params.width = (int) (scale * scrollableArea.width());
+ params.height = (int) (scale * scrollableArea.height());
+ Matrix matrix = new Matrix();
+ matrix.setScale(scale, scale);
+ matrix.postTranslate(-scrollableArea.left * scale, -scrollableArea.top * scale);
+
+ mScrollablePreview.setTranslationX(scale
+ * (mDirectionLTR ? scrollableArea.left : scrollableArea.right - getWidth()));
+ mScrollablePreview.setTranslationY(scale * scrollableArea.top);
+ mScrollablePreview.setImageMatrix(matrix);
+ mScrollablePreview.setImageBitmap(screenBitmap);
+ mScrollablePreview.setVisibility(View.VISIBLE);
+ }
+ mDismissButton.setVisibility(View.GONE);
+ mActionsContainer.setVisibility(View.GONE);
+ mBackgroundProtection.setVisibility(View.GONE);
+ // set these invisible, but not gone, so that the views are laid out correctly
+ mActionsContainerBackground.setVisibility(View.INVISIBLE);
+ mScreenshotPreviewBorder.setVisibility(View.INVISIBLE);
+ mScreenshotPreview.setVisibility(View.INVISIBLE);
+ mScrollingScrim.setImageTintBlendMode(BlendMode.SRC_ATOP);
+ ValueAnimator anim = ValueAnimator.ofFloat(0, .3f);
+ anim.addUpdateListener(animation -> mScrollingScrim.setImageTintList(
+ ColorStateList.valueOf(Color.argb((float) animation.getAnimatedValue(), 0, 0, 0))));
+ anim.setDuration(200);
+ anim.start();
+ }
+
+ void restoreNonScrollingUi() {
+ mScrollChip.setVisibility(View.GONE);
+ mScrollablePreview.setVisibility(View.GONE);
+ mScrollingScrim.setVisibility(View.GONE);
+
+ if (mAccessibilityManager.isEnabled()) {
+ mDismissButton.setVisibility(View.VISIBLE);
+ }
+ mActionsContainer.setVisibility(View.VISIBLE);
+ mBackgroundProtection.setVisibility(View.VISIBLE);
+ mActionsContainerBackground.setVisibility(View.VISIBLE);
+ mScreenshotPreviewBorder.setVisibility(View.VISIBLE);
+ mScreenshotPreview.setVisibility(View.VISIBLE);
+ // reset the timeout
+ mCallbacks.onUserInteraction();
}
boolean isDismissing() {
@@ -845,10 +920,6 @@ public class ScreenshotView extends FrameLayout implements
}
private void animateDismissal(Animator dismissAnimation) {
- if (DEBUG_WINDOW) {
- Log.d(TAG, "removing OnComputeInternalInsetsListener");
- }
- getViewTreeObserver().removeOnComputeInternalInsetsListener(this);
mDismissAnimation = dismissAnimation;
mDismissAnimation.addListener(new AnimatorListenerAdapter() {
private boolean mCancelled = false;
@@ -904,12 +975,15 @@ public class ScreenshotView extends FrameLayout implements
mActionsContainer.setVisibility(View.GONE);
mBackgroundProtection.setAlpha(0f);
mDismissButton.setVisibility(View.GONE);
+ mScrollingScrim.setVisibility(View.GONE);
+ mScrollablePreview.setVisibility(View.GONE);
mScreenshotStatic.setTranslationX(0);
mScreenshotPreview.setTranslationY(0);
mScreenshotPreview.setContentDescription(
mContext.getResources().getString(R.string.screenshot_preview_description));
mScreenshotPreview.setOnClickListener(null);
mShareChip.setOnClickListener(null);
+ mScrollingScrim.setVisibility(View.GONE);
mEditChip.setOnClickListener(null);
mShareChip.setIsPending(false);
mEditChip.setIsPending(false);
@@ -932,7 +1006,7 @@ public class ScreenshotView extends FrameLayout implements
transition.action.actionIntent.send();
// fade out non-preview UI
- createScreenshotFadeDismissAnimation(false).start();
+ createScreenshotFadeDismissAnimation().start();
} catch (PendingIntent.CanceledException e) {
mPendingSharedTransition = false;
if (transition.onCancelRunnable != null) {
@@ -970,7 +1044,7 @@ public class ScreenshotView extends FrameLayout implements
return animSet;
}
- ValueAnimator createScreenshotFadeDismissAnimation(boolean fadePreview) {
+ ValueAnimator createScreenshotFadeDismissAnimation() {
ValueAnimator alphaAnim = ValueAnimator.ofFloat(0, 1);
alphaAnim.addUpdateListener(animation -> {
float alpha = 1 - animation.getAnimatedFraction();
@@ -979,9 +1053,6 @@ public class ScreenshotView extends FrameLayout implements
mActionsContainer.setAlpha(alpha);
mBackgroundProtection.setAlpha(alpha);
mScreenshotPreviewBorder.setAlpha(alpha);
- if (fadePreview) {
- mScreenshotPreview.setAlpha(alpha);
- }
});
alphaAnim.setDuration(600);
return alphaAnim;
diff --git a/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt b/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt
index e6d48676dc03..24775344240a 100644
--- a/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt
@@ -23,6 +23,7 @@ import android.content.res.Resources
import android.hardware.SensorPrivacyManager
import android.hardware.SensorPrivacyManager.EXTRA_ALL_SENSORS
import android.hardware.SensorPrivacyManager.EXTRA_SENSOR
+import android.hardware.SensorPrivacyManager.Sources.DIALOG
import android.os.Bundle
import android.os.Handler
import android.text.Html
@@ -32,6 +33,7 @@ import android.widget.ImageView
import com.android.internal.app.AlertActivity
import com.android.internal.widget.DialogTitle
import com.android.systemui.R
+import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.statusbar.phone.KeyguardDismissUtil
import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController
import com.android.systemui.statusbar.policy.KeyguardStateController
@@ -46,13 +48,15 @@ import javax.inject.Inject
class SensorUseStartedActivity @Inject constructor(
private val sensorPrivacyController: IndividualSensorPrivacyController,
private val keyguardStateController: KeyguardStateController,
- private val keyguardDismissUtil: KeyguardDismissUtil
+ private val keyguardDismissUtil: KeyguardDismissUtil,
+ @Background private val bgHandler: Handler
) : AlertActivity(), DialogInterface.OnClickListener {
companion object {
private val LOG_TAG = SensorUseStartedActivity::class.java.simpleName
private const val SUPPRESS_REMINDERS_REMOVAL_DELAY_MILLIS = 2000L
+ private const val UNLOCK_DELAY_MILLIS = 200L
private const val CAMERA = SensorPrivacyManager.Sensors.CAMERA
private const val MICROPHONE = SensorPrivacyManager.Sensors.MICROPHONE
@@ -179,9 +183,12 @@ class SensorUseStartedActivity @Inject constructor(
BUTTON_POSITIVE -> {
if (keyguardStateController.isMethodSecure && keyguardStateController.isShowing) {
keyguardDismissUtil.executeWhenUnlocked({
- disableSensorPrivacy()
+ bgHandler.postDelayed({
+ disableSensorPrivacy()
+ }, UNLOCK_DELAY_MILLIS)
+
false
- }, false, false)
+ }, false, true)
} else {
disableSensorPrivacy()
}
@@ -201,7 +208,7 @@ class SensorUseStartedActivity @Inject constructor(
sensorPrivacyController
.suppressSensorPrivacyReminders(sensorUsePackageName, false)
} else {
- Handler(mainLooper).postDelayed({
+ bgHandler.postDelayed({
sensorPrivacyController
.suppressSensorPrivacyReminders(sensorUsePackageName, false)
}, SUPPRESS_REMINDERS_REMOVAL_DELAY_MILLIS)
@@ -219,10 +226,10 @@ class SensorUseStartedActivity @Inject constructor(
private fun disableSensorPrivacy() {
if (sensor == ALL_SENSORS) {
- sensorPrivacyController.setSensorBlocked(MICROPHONE, false)
- sensorPrivacyController.setSensorBlocked(CAMERA, false)
+ sensorPrivacyController.setSensorBlocked(DIALOG, MICROPHONE, false)
+ sensorPrivacyController.setSensorBlocked(DIALOG, CAMERA, false)
} else {
- sensorPrivacyController.setSensorBlocked(sensor, false)
+ sensorPrivacyController.setSensorBlocked(DIALOG, sensor, false)
}
unsuppressImmediately = true
setResult(RESULT_OK)
diff --git a/packages/SystemUI/src/com/android/systemui/sensorprivacy/television/TvUnblockSensorActivity.java b/packages/SystemUI/src/com/android/systemui/sensorprivacy/television/TvUnblockSensorActivity.java
index 9d101effa99f..8cd3632b65ba 100644
--- a/packages/SystemUI/src/com/android/systemui/sensorprivacy/television/TvUnblockSensorActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/sensorprivacy/television/TvUnblockSensorActivity.java
@@ -18,6 +18,7 @@ package com.android.systemui.sensorprivacy.television;
import static android.hardware.SensorPrivacyManager.Sensors.CAMERA;
import static android.hardware.SensorPrivacyManager.Sensors.MICROPHONE;
+import static android.hardware.SensorPrivacyManager.Sources.OTHER;
import android.hardware.SensorPrivacyManager;
import android.os.Bundle;
@@ -119,10 +120,10 @@ public class TvUnblockSensorActivity extends TvBottomSheetActivity {
com.android.internal.R.string.sensor_privacy_start_use_dialog_turn_on_button);
unblockButton.setOnClickListener(v -> {
if (mSensor == ALL_SENSORS) {
- mSensorPrivacyController.setSensorBlocked(CAMERA, false);
- mSensorPrivacyController.setSensorBlocked(MICROPHONE, false);
+ mSensorPrivacyController.setSensorBlocked(OTHER, CAMERA, false);
+ mSensorPrivacyController.setSensorBlocked(OTHER, MICROPHONE, false);
} else {
- mSensorPrivacyController.setSensorBlocked(mSensor, false);
+ mSensorPrivacyController.setSensorBlocked(OTHER, mSensor, false);
}
});
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 8dd6c8926434..15aa2b730adf 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderView.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderView.java
@@ -180,6 +180,8 @@ public class BrightnessSliderView extends FrameLayout {
* Sets the scale for the progress bar (for brightness_progress_drawable.xml)
*
* This will only scale the thick progress bar and not the icon inside
+ *
+ * Used in {@link com.android.systemui.qs.QSAnimator}.
*/
public void setSliderScaleY(float scale) {
if (scale != mScale) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BlurUtils.kt b/packages/SystemUI/src/com/android/systemui/statusbar/BlurUtils.kt
index 89dda9c52651..aafeabc7c1a2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BlurUtils.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BlurUtils.kt
@@ -20,6 +20,7 @@ import android.view.CrossWindowBlurListeners.CROSS_WINDOW_BLUR_SUPPORTED
import android.app.ActivityManager
import android.content.res.Resources
+import android.os.SystemProperties
import android.util.IndentingPrintWriter
import android.util.MathUtils
import android.view.CrossWindowBlurListeners
@@ -100,7 +101,8 @@ open class BlurUtils @Inject constructor(
*/
open fun supportsBlursOnWindows(): Boolean {
return CROSS_WINDOW_BLUR_SUPPORTED && ActivityManager.isHighEndGfx() &&
- crossWindowBlurListeners.isCrossWindowBlurEnabled()
+ crossWindowBlurListeners.isCrossWindowBlurEnabled() &&
+ !SystemProperties.getBoolean("persist.sysui.disableBlur", false)
}
override fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<out String>) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java b/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java
index 7e676197ddad..5a4245853a6f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java
@@ -16,6 +16,9 @@
package com.android.systemui.statusbar;
+import android.content.Context;
+import android.util.FeatureFlagUtils;
+
import com.android.systemui.R;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.flags.FeatureFlagReader;
@@ -30,10 +33,12 @@ import javax.inject.Inject;
@SysUISingleton
public class FeatureFlags {
private final FeatureFlagReader mFlagReader;
+ private final Context mContext;
@Inject
- public FeatureFlags(FeatureFlagReader flagReader) {
+ public FeatureFlags(FeatureFlagReader flagReader, Context context) {
mFlagReader = flagReader;
+ mContext = context;
}
public boolean isNewNotifPipelineEnabled() {
@@ -84,4 +89,27 @@ public class FeatureFlags {
public boolean isSmartspaceDedupingEnabled() {
return isSmartspaceEnabled() && mFlagReader.isEnabled(R.bool.flag_smartspace_deduping);
}
+
+ public boolean isNewKeyguardSwipeAnimationEnabled() {
+ return mFlagReader.isEnabled(R.bool.flag_new_unlock_swipe_animation);
+ }
+
+ public boolean isSmartSpaceSharedElementTransitionEnabled() {
+ return mFlagReader.isEnabled(R.bool.flag_smartspace_shared_element_transition);
+ }
+
+ /** Whether or not to use the provider model behavior for the status bar icons */
+ public boolean isCombinedStatusBarSignalIconsEnabled() {
+ return mFlagReader.isEnabled(R.bool.flag_combined_status_bar_signal_icons);
+ }
+
+ /** System setting for provider model behavior */
+ public boolean isProviderModelSettingEnabled() {
+ return FeatureFlagUtils.isEnabled(mContext, FeatureFlagUtils.SETTINGS_PROVIDER_MODEL);
+ }
+
+ /** static method for the system setting */
+ public static boolean isProviderModelSettingEnabled(Context context) {
+ return FeatureFlagUtils.isEnabled(context, FeatureFlagUtils.SETTINGS_PROVIDER_MODEL);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index 0bb702f6c9e4..44399a126624 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -66,7 +66,6 @@ import com.android.internal.widget.LockPatternUtils;
import com.android.internal.widget.ViewClippingUtil;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.KeyguardUpdateMonitorCallback;
-import com.android.settingslib.Utils;
import com.android.settingslib.fuelgauge.BatteryStatus;
import com.android.systemui.R;
import com.android.systemui.animation.Interpolators;
@@ -130,7 +129,6 @@ public class KeyguardIndicationController implements KeyguardStateController.Cal
private String mRestingIndication;
private String mAlignmentIndication;
private CharSequence mTransientIndication;
- private boolean mTransientTextIsError;
protected ColorStateList mInitialTextColorState;
private boolean mVisible;
private boolean mHideTransientMessageOnScreenOff;
@@ -382,8 +380,7 @@ public class KeyguardIndicationController implements KeyguardStateController.Cal
private void updateTransient() {
if (!TextUtils.isEmpty(mTransientIndication)) {
- mRotateTextViewController.showTransient(mTransientIndication,
- mTransientTextIsError);
+ mRotateTextViewController.showTransient(mTransientIndication);
} else {
mRotateTextViewController.hideTransient();
}
@@ -421,7 +418,8 @@ public class KeyguardIndicationController implements KeyguardStateController.Cal
INDICATION_TYPE_ALIGNMENT,
new KeyguardIndication.Builder()
.setMessage(mAlignmentIndication)
- .setTextColor(Utils.getColorError(mContext))
+ .setTextColor(ColorStateList.valueOf(
+ mContext.getColor(R.color.misalignment_text_color)))
.build(),
true);
} else {
@@ -594,14 +592,13 @@ public class KeyguardIndicationController implements KeyguardStateController.Cal
boolean isError, boolean hideOnScreenOff) {
mTransientIndication = transientIndication;
mHideTransientMessageOnScreenOff = hideOnScreenOff && transientIndication != null;
- mTransientTextIsError = isError;
mHandler.removeMessages(MSG_HIDE_TRANSIENT);
mHandler.removeMessages(MSG_SWIPE_UP_TO_UNLOCK);
if (mDozing && !TextUtils.isEmpty(mTransientIndication)) {
// Make sure this doesn't get stuck and burns in. Acquire wakelock until its cleared.
mWakeLock.setAcquired(true);
- hideTransientIndicationDelayed(BaseKeyguardCallback.HIDE_DELAY_MS);
}
+ hideTransientIndicationDelayed(BaseKeyguardCallback.HIDE_DELAY_MS);
updateIndication(false);
}
@@ -800,18 +797,20 @@ public class KeyguardIndicationController implements KeyguardStateController.Cal
}
if (mStatusBarKeyguardViewManager.isBouncerShowing()) {
- String message = mContext.getString(R.string.keyguard_retry);
- mStatusBarKeyguardViewManager.showBouncerMessage(message, mInitialTextColorState);
+ if (mStatusBarKeyguardViewManager.isShowingAlternateAuth()) {
+ return; // udfps affordance is highlighted, no need to surface face auth error
+ } else {
+ String message = mContext.getString(R.string.keyguard_retry);
+ mStatusBarKeyguardViewManager.showBouncerMessage(message, mInitialTextColorState);
+ }
} else if (mKeyguardUpdateMonitor.isScreenOn()) {
showTransientIndication(mContext.getString(R.string.keyguard_unlock),
false /* isError */, true /* hideOnScreenOff */);
- hideTransientIndicationDelayed(BaseKeyguardCallback.HIDE_DELAY_MS);
}
}
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
pw.println("KeyguardIndicationController:");
- pw.println(" mTransientTextIsError: " + mTransientTextIsError);
pw.println(" mInitialTextColorState: " + mInitialTextColorState);
pw.println(" mPowerPluggedInWired: " + mPowerPluggedInWired);
pw.println(" mPowerPluggedIn: " + mPowerPluggedIn);
@@ -866,7 +865,6 @@ public class KeyguardIndicationController implements KeyguardStateController.Cal
if (mDozing) {
if (!wasPluggedIn && mPowerPluggedIn) {
showTransientIndication(computePowerIndication());
- hideTransientIndicationDelayed(HIDE_DELAY_MS);
} else if (wasPluggedIn && !mPowerPluggedIn) {
hideTransientIndication();
}
@@ -885,16 +883,20 @@ public class KeyguardIndicationController implements KeyguardStateController.Cal
.isUnlockingWithBiometricAllowed(true /* isStrongBiometric */)) {
return;
}
+
boolean showSwipeToUnlock =
msgId == KeyguardUpdateMonitor.BIOMETRIC_HELP_FACE_NOT_RECOGNIZED;
if (mStatusBarKeyguardViewManager.isBouncerShowing()) {
mStatusBarKeyguardViewManager.showBouncerMessage(helpString,
mInitialTextColorState);
} else if (mKeyguardUpdateMonitor.isScreenOn()) {
- showTransientIndication(helpString, false /* isError */, showSwipeToUnlock);
- if (!showSwipeToUnlock) {
- hideTransientIndicationDelayed(TRANSIENT_BIOMETRIC_ERROR_TIMEOUT);
+ if (biometricSourceType == BiometricSourceType.FACE
+ && shouldSuppressFaceMsgAndShowTryFingerprintMsg()) {
+ // suggest trying fingerprint
+ showTransientIndication(R.string.keyguard_try_fingerprint);
+ return;
}
+ showTransientIndication(helpString, false /* isError */, showSwipeToUnlock);
}
if (showSwipeToUnlock) {
mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_SWIPE_UP_TO_UNLOCK),
@@ -908,13 +910,27 @@ public class KeyguardIndicationController implements KeyguardStateController.Cal
if (shouldSuppressBiometricError(msgId, biometricSourceType, mKeyguardUpdateMonitor)) {
return;
}
+ if (biometricSourceType == BiometricSourceType.FACE
+ && shouldSuppressFaceMsgAndShowTryFingerprintMsg()
+ && !mStatusBarKeyguardViewManager.isBouncerShowing()
+ && mKeyguardUpdateMonitor.isScreenOn()) {
+ // suggest trying fingerprint
+ showTransientIndication(R.string.keyguard_try_fingerprint);
+ return;
+ }
if (msgId == FaceManager.FACE_ERROR_TIMEOUT) {
// The face timeout message is not very actionable, let's ask the user to
// manually retry.
if (!mStatusBarKeyguardViewManager.isBouncerShowing()
- && mKeyguardUpdateMonitor.isUdfpsEnrolled()) {
+ && mKeyguardUpdateMonitor.isUdfpsEnrolled()
+ && mKeyguardUpdateMonitor.isFingerprintDetectionRunning()) {
// suggest trying fingerprint
showTransientIndication(R.string.keyguard_try_fingerprint);
+ } else if (mStatusBarKeyguardViewManager.isShowingAlternateAuth()) {
+ mStatusBarKeyguardViewManager.showBouncerMessage(
+ mContext.getResources().getString(R.string.keyguard_try_fingerprint),
+ mInitialTextColorState
+ );
} else {
// suggest swiping up to unlock (try face auth again or swipe up to bouncer)
showSwipeUpToUnlock();
@@ -924,8 +940,6 @@ public class KeyguardIndicationController implements KeyguardStateController.Cal
} else if (mKeyguardUpdateMonitor.isScreenOn()) {
showTransientIndication(errString, /* isError */ true,
/* hideOnScreenOff */ true);
- // We want to keep this message around in case the screen was off
- hideTransientIndicationDelayed(HIDE_DELAY_MS);
} else {
mMessageToShowOnScreenOn = errString;
}
@@ -952,6 +966,15 @@ public class KeyguardIndicationController implements KeyguardStateController.Cal
|| msgId == FingerprintManager.FINGERPRINT_ERROR_USER_CANCELED);
}
+ private boolean shouldSuppressFaceMsgAndShowTryFingerprintMsg() {
+ // For dual biometric, don't show face auth messages unless face auth was explicitly
+ // requested by the user.
+ return mKeyguardUpdateMonitor.isFingerprintDetectionRunning()
+ && !mKeyguardUpdateMonitor.isFaceAuthUserRequested()
+ && mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed(
+ true /* isStrongBiometric */);
+ }
+
private boolean shouldSuppressFaceError(int msgId, KeyguardUpdateMonitor updateMonitor) {
// Only checking if unlocking with Biometric is allowed (no matter strong or non-strong
// as long as primary auth, i.e. PIN/pattern/password, is not required), so it's ok to
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt
index ec648ad519a3..538db6168408 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt
@@ -14,6 +14,7 @@ import android.graphics.Shader
import android.util.AttributeSet
import android.view.View
import com.android.systemui.animation.Interpolators
+import java.util.function.Consumer
/**
* Provides methods to modify the various properties of a [LightRevealScrim] to reveal between 0% to
@@ -51,7 +52,7 @@ object LiftReveal : LightRevealEffect {
private const val OVAL_INITIAL_WIDTH_PERCENT = 0.5f
/** The initial top value of the light oval, in percent of scrim height. */
- private const val OVAL_INITIAL_TOP_PERCENT = 1.05f
+ private const val OVAL_INITIAL_TOP_PERCENT = 1.1f
/** The initial bottom value of the light oval, in percent of scrim height. */
private const val OVAL_INITIAL_BOTTOM_PERCENT = 1.2f
@@ -148,6 +149,8 @@ class PowerButtonReveal(
*/
class LightRevealScrim(context: Context?, attrs: AttributeSet?) : View(context, attrs) {
+ lateinit var revealAmountListener: Consumer<Float>
+
/**
* How much of the underlying views are revealed, in percent. 0 means they will be completely
* obscured and 1 means they'll be fully visible.
@@ -158,6 +161,7 @@ class LightRevealScrim(context: Context?, attrs: AttributeSet?) : View(context,
field = value
revealEffect.setRevealAmountOnScrim(value, this)
+ revealAmountListener.accept(value)
invalidate()
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
index 0b67e7eeb132..4552138761c0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
@@ -369,7 +369,7 @@ public class NotificationRemoteInputManager implements Dumpable {
});
mSmartReplyController.setCallback((entry, reply) -> {
StatusBarNotification newSbn =
- rebuildNotificationWithRemoteInput(entry, reply, true /* showSpinner */,
+ rebuildNotificationWithRemoteInputInserted(entry, reply, true /* showSpinner */,
null /* mimeType */, null /* uri */);
mEntryManager.updateNotification(newSbn, null /* ranking */);
});
@@ -638,12 +638,12 @@ public class NotificationRemoteInputManager implements Dumpable {
@VisibleForTesting
StatusBarNotification rebuildNotificationForCanceledSmartReplies(
NotificationEntry entry) {
- return rebuildNotificationWithRemoteInput(entry, null /* remoteInputTest */,
+ return rebuildNotificationWithRemoteInputInserted(entry, null /* remoteInputTest */,
false /* showSpinner */, null /* mimeType */, null /* uri */);
}
@VisibleForTesting
- StatusBarNotification rebuildNotificationWithRemoteInput(NotificationEntry entry,
+ StatusBarNotification rebuildNotificationWithRemoteInputInserted(NotificationEntry entry,
CharSequence remoteInputText, boolean showSpinner, String mimeType, Uri uri) {
StatusBarNotification sbn = entry.getSbn();
@@ -746,7 +746,7 @@ public class NotificationRemoteInputManager implements Dumpable {
}
String remoteInputMimeType = entry.remoteInputMimeType;
Uri remoteInputUri = entry.remoteInputUri;
- StatusBarNotification newSbn = rebuildNotificationWithRemoteInput(entry,
+ StatusBarNotification newSbn = rebuildNotificationWithRemoteInputInserted(entry,
remoteInputText, false /* showSpinner */, remoteInputMimeType,
remoteInputUri);
entry.onRemoteInputInserted();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
index 452d69369d71..28bdd5fbeb8b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
@@ -21,6 +21,7 @@ import android.animation.AnimatorListenerAdapter
import android.animation.ValueAnimator
import android.app.WallpaperManager
import android.os.SystemClock
+import android.os.Trace
import android.util.IndentingPrintWriter
import android.util.Log
import android.util.MathUtils
@@ -93,8 +94,13 @@ class NotificationShadeDepthController @Inject constructor(
var shadeAnimation = DepthAnimation()
@VisibleForTesting
- var globalActionsSpring = DepthAnimation()
- var showingHomeControls: Boolean = false
+ var brightnessMirrorSpring = DepthAnimation()
+ var brightnessMirrorVisible: Boolean = false
+ set(value) {
+ field = value
+ brightnessMirrorSpring.animateTo(if (value) blurUtils.blurRadiusOfRatio(1f)
+ else 0)
+ }
var qsPanelExpansion = 0f
set(value) {
@@ -117,7 +123,7 @@ class NotificationShadeDepthController @Inject constructor(
* When launching an app from the shade, the animations progress should affect how blurry the
* shade is, overriding the expansion amount.
*/
- var ignoreShadeBlurUntilHidden: Boolean = false
+ var blursDisabledForAppLaunch: Boolean = false
set(value) {
if (field == value) {
return
@@ -128,6 +134,10 @@ class NotificationShadeDepthController @Inject constructor(
if (shadeSpring.radius == 0 && shadeAnimation.radius == 0) {
return
}
+ // Do not remove blurs when we're re-enabling them
+ if (!value) {
+ return
+ }
shadeSpring.animateTo(0)
shadeSpring.finishIfRunning()
@@ -169,30 +179,28 @@ class NotificationShadeDepthController @Inject constructor(
combinedBlur = max(combinedBlur, blurUtils.blurRadiusOfRatio(transitionToFullShadeProgress))
var shadeRadius = max(combinedBlur, wakeAndUnlockBlurRadius).toFloat()
- if (ignoreShadeBlurUntilHidden) {
- if (shadeRadius == 0f) {
- ignoreShadeBlurUntilHidden = false
- } else {
- shadeRadius = 0f
- }
+ if (blursDisabledForAppLaunch) {
+ shadeRadius = 0f
}
- // Home controls have black background, this means that we should not have blur when they
- // are fully visible, otherwise we'll enter Client Composition unnecessarily.
- var globalActionsRadius = globalActionsSpring.radius
- if (showingHomeControls) {
- globalActionsRadius = 0
- }
- var blur = max(shadeRadius.toInt(), globalActionsRadius)
+ var blur = shadeRadius.toInt()
// Make blur be 0 if it is necessary to stop blur effect.
- if (scrimsVisible || !blurUtils.supportsBlursOnWindows()) {
+ if (scrimsVisible) {
blur = 0
}
+ val zoomOut = blurUtils.ratioOfBlurRadius(blur)
- val opaque = scrimsVisible && !ignoreShadeBlurUntilHidden
+ if (!blurUtils.supportsBlursOnWindows()) {
+ blur = 0
+ }
+
+ // Brightness slider removes blur, but doesn't affect zooms
+ blur = (blur * (1f - brightnessMirrorSpring.ratio)).toInt()
+
+ val opaque = scrimsVisible && !blursDisabledForAppLaunch
+ Trace.traceCounter(Trace.TRACE_TAG_APP, "shade_blur_radius", blur)
blurUtils.applyBlur(blurRoot?.viewRootImpl ?: root.viewRootImpl, blur, opaque)
- val zoomOut = blurUtils.ratioOfBlurRadius(blur)
try {
if (root.isAttachedToWindow && root.windowToken != null) {
wallpaperManager.setWallpaperZoomOut(root.windowToken, zoomOut)
@@ -204,6 +212,7 @@ class NotificationShadeDepthController @Inject constructor(
}
listeners.forEach {
it.onWallpaperZoomOutChanged(zoomOut)
+ it.onBlurRadiusChanged(blur)
}
notificationShadeWindowController.setBackgroundBlurRadius(blur)
}
@@ -259,13 +268,9 @@ class NotificationShadeDepthController @Inject constructor(
if (isDozing) {
shadeSpring.finishIfRunning()
shadeAnimation.finishIfRunning()
- globalActionsSpring.finishIfRunning()
+ brightnessMirrorSpring.finishIfRunning()
}
}
-
- override fun onDozeAmountChanged(linear: Float, eased: Float) {
- wakeAndUnlockBlurRadius = blurUtils.blurRadiusOfRatio(eased)
- }
}
init {
@@ -414,19 +419,15 @@ class NotificationShadeDepthController @Inject constructor(
!keyguardStateController.isKeyguardFadingAway
}
- fun updateGlobalDialogVisibility(visibility: Float, dialogView: View?) {
- globalActionsSpring.animateTo(blurUtils.blurRadiusOfRatio(visibility), dialogView)
- }
-
override fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<out String>) {
IndentingPrintWriter(pw, " ").let {
it.println("StatusBarWindowBlurController:")
it.increaseIndent()
it.println("shadeRadius: ${shadeSpring.radius}")
it.println("shadeAnimation: ${shadeAnimation.radius}")
- it.println("globalActionsRadius: ${globalActionsSpring.radius}")
+ it.println("brightnessMirrorRadius: ${brightnessMirrorSpring.radius}")
it.println("wakeAndUnlockBlur: $wakeAndUnlockBlurRadius")
- it.println("ignoreShadeBlurUntilHidden: $ignoreShadeBlurUntilHidden")
+ it.println("blursDisabledForAppLaunch: $blursDisabledForAppLaunch")
}
}
@@ -511,5 +512,8 @@ class NotificationShadeDepthController @Inject constructor(
* Current wallpaper zoom out, where 0 is the closest, and 1 the farthest
*/
fun onWallpaperZoomOutChanged(zoomOut: Float)
+
+ @JvmDefault
+ fun onBlurRadiusChanged(blurRadius: Int) {}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java
index 045a1976d502..f0d779ce1e0f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java
@@ -182,6 +182,12 @@ public interface NotificationShadeWindowController extends RemoteInputController
default void setFaceAuthDisplayBrightness(float brightness) {}
/**
+ * How much {@link LightRevealScrim} obscures the UI.
+ * @param amount 0 when opaque, 1 when not transparent
+ */
+ default void setLightRevealScrimAmount(float amount) {}
+
+ /**
* Custom listener to pipe data back to plugins about whether or not the status bar would be
* collapsed if not for the plugin.
* TODO: Find cleaner way to do this.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
index 3c549f94ad0f..396d86bab825 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
@@ -158,14 +158,7 @@ public class NotificationViewHierarchyManager implements DynamicPrivacyControlle
final int N = activeNotifications.size();
for (int i = 0; i < N; i++) {
NotificationEntry ent = activeNotifications.get(i);
- final boolean isBubbleNotificationSuppressedFromShade = mBubblesOptional.isPresent()
- && mBubblesOptional.get().isBubbleNotificationSuppressedFromShade(
- ent.getKey(), ent.getSbn().getGroupKey());
- if (ent.isRowDismissed() || ent.isRowRemoved()
- || isBubbleNotificationSuppressedFromShade
- || mFgsSectionController.hasEntry(ent)) {
- // we don't want to update removed notifications because they could
- // temporarily become children if they were isolated before.
+ if (shouldSuppressActiveNotification(ent)) {
continue;
}
@@ -254,9 +247,11 @@ public class NotificationViewHierarchyManager implements DynamicPrivacyControlle
}
for (ExpandableNotificationRow viewToRemove : viewsToRemove) {
- if (mEntryManager.getPendingOrActiveNotif(viewToRemove.getEntry().getKey()) != null) {
+ NotificationEntry entry = viewToRemove.getEntry();
+ if (mEntryManager.getPendingOrActiveNotif(entry.getKey()) != null
+ && !shouldSuppressActiveNotification(entry)) {
// we are only transferring this notification to its parent, don't generate an
- // animation
+ // animation. If the notification is suppressed, this isn't a transfer.
mListContainer.setChildTransferInProgress(true);
}
if (viewToRemove.isSummaryWithChildren()) {
@@ -325,6 +320,23 @@ public class NotificationViewHierarchyManager implements DynamicPrivacyControlle
endUpdate();
}
+ /**
+ * Should a notification entry from the active list be suppressed and not show?
+ */
+ private boolean shouldSuppressActiveNotification(NotificationEntry ent) {
+ final boolean isBubbleNotificationSuppressedFromShade = mBubblesOptional.isPresent()
+ && mBubblesOptional.get().isBubbleNotificationSuppressedFromShade(
+ ent.getKey(), ent.getSbn().getGroupKey());
+ if (ent.isRowDismissed() || ent.isRowRemoved()
+ || isBubbleNotificationSuppressedFromShade
+ || mFgsSectionController.hasEntry(ent)) {
+ // we want to suppress removed notifications because they could
+ // temporarily become children if they were isolated before.
+ return true;
+ }
+ return false;
+ }
+
private void addNotificationChildrenAndSort() {
// Let's now add all notification children which are missing
boolean orderChanged = false;
@@ -423,7 +435,8 @@ public class NotificationViewHierarchyManager implements DynamicPrivacyControlle
final int N = mListContainer.getContainerChildCount();
int visibleNotifications = 0;
- boolean onKeyguard = mStatusBarStateController.getState() == StatusBarState.KEYGUARD;
+ boolean onKeyguard =
+ mStatusBarStateController.getCurrentOrUpcomingState() == StatusBarState.KEYGUARD;
Stack<ExpandableNotificationRow> stack = new Stack<>();
for (int i = N - 1; i >= 0; i--) {
View child = mListContainer.getContainerChildAt(i);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt b/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt
index 9765ace7179f..b34bfad499f8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt
@@ -179,7 +179,10 @@ constructor(
}
override fun onTouchEvent(event: MotionEvent): Boolean {
- if (!canHandleMotionEvent()) {
+ val finishExpanding = (event.action == MotionEvent.ACTION_CANCEL ||
+ event.action == MotionEvent.ACTION_UP) && isExpanding
+ if (!canHandleMotionEvent() && !finishExpanding) {
+ // We allow cancellations/finishing to still go through here to clean up the state
return false
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java b/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java
index 924eb263de50..b6aed23e64ee 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java
@@ -132,6 +132,8 @@ public class RemoteInputController {
public void removeRemoteInput(NotificationEntry entry, Object token) {
Objects.requireNonNull(entry);
if (entry.mRemoteEditImeVisible) return;
+ // If the view is being removed, this may be called even though we're not active
+ if (!isRemoteInputActive(entry)) return;
pruneWeakThenRemoveAndContains(null /* contains */, entry /* remove */, token);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarMobileView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarMobileView.java
index dbab8c2dab0e..5c3916c7b41d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarMobileView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarMobileView.java
@@ -26,7 +26,6 @@ import android.content.Context;
import android.content.res.ColorStateList;
import android.graphics.Rect;
import android.util.AttributeSet;
-import android.util.FeatureFlagUtils;
import android.util.Log;
import android.view.Gravity;
import android.view.LayoutInflater;
@@ -61,15 +60,22 @@ public class StatusBarMobileView extends FrameLayout implements DarkReceiver,
private int mVisibleState = -1;
private DualToneHandler mDualToneHandler;
private boolean mForceHidden;
-
+ private boolean mProviderModel;
private ImageView mVolte;
- public static StatusBarMobileView fromContext(Context context, String slot) {
+ /**
+ * Designated constructor
+ */
+ public static StatusBarMobileView fromContext(
+ Context context,
+ String slot,
+ boolean providerModel
+ ) {
LayoutInflater inflater = LayoutInflater.from(context);
StatusBarMobileView v = (StatusBarMobileView)
inflater.inflate(R.layout.status_bar_mobile_signal_group, null);
v.setSlot(slot);
- v.init();
+ v.init(providerModel);
v.setVisibleState(STATE_ICON);
return v;
}
@@ -102,12 +108,13 @@ public class StatusBarMobileView extends FrameLayout implements DarkReceiver,
outRect.bottom += translationY;
}
- private void init() {
+ private void init(boolean providerModel) {
+ mProviderModel = providerModel;
mDualToneHandler = new DualToneHandler(getContext());
mMobileGroup = findViewById(R.id.mobile_group);
mMobile = findViewById(R.id.mobile_signal);
mMobileType = findViewById(R.id.mobile_type);
- if (FeatureFlagUtils.isEnabled(getContext(), FeatureFlagUtils.SETTINGS_PROVIDER_MODEL)) {
+ if (mProviderModel) {
mMobileRoaming = findViewById(R.id.mobile_roaming_large);
} else {
mMobileRoaming = findViewById(R.id.mobile_roaming);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
index f8a1ff879e72..0725bf961e13 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
@@ -82,6 +82,7 @@ public class StatusBarStateControllerImpl implements SysuiStatusBarStateControll
private final UiEventLogger mUiEventLogger;
private int mState;
private int mLastState;
+ private int mUpcomingState;
private boolean mLeaveOpenOnKeyguardHide;
private boolean mKeyguardRequested;
@@ -169,6 +170,7 @@ public class StatusBarStateControllerImpl implements SysuiStatusBarStateControll
}
mLastState = mState;
mState = state;
+ mUpcomingState = state;
mUiEventLogger.log(StatusBarStateEvent.fromState(mState));
for (RankedListener rl : new ArrayList<>(mListeners)) {
rl.mListener.onStateChanged(mState);
@@ -184,6 +186,16 @@ public class StatusBarStateControllerImpl implements SysuiStatusBarStateControll
}
@Override
+ public void setUpcomingState(int nextState) {
+ mUpcomingState = nextState;
+ }
+
+ @Override
+ public int getCurrentOrUpcomingState() {
+ return mUpcomingState;
+ }
+
+ @Override
public boolean isDozing() {
return mIsDozing;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/SysuiStatusBarStateController.java b/packages/SystemUI/src/com/android/systemui/statusbar/SysuiStatusBarStateController.java
index 73f3d90bd4f8..25200501a916 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/SysuiStatusBarStateController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/SysuiStatusBarStateController.java
@@ -74,6 +74,20 @@ public interface SysuiStatusBarStateController extends StatusBarStateController
boolean setState(int state, boolean force);
/**
+ * Provides a hint that the status bar has started to transition to another
+ * {@link StatusBarState}. This suggests that a matching call to setState() with the same value
+ * will happen in the near future, although that may not happen if the animation is canceled,
+ * etc.
+ */
+ void setUpcomingState(int state);
+
+ /**
+ * If the status bar is in the process of transitioning to a new state, returns that state.
+ * Otherwise, returns the current state.
+ */
+ int getCurrentOrUpcomingState();
+
+ /**
* Update the dozing state from {@link StatusBar}'s perspective
* @param isDozing well, are we dozing?
* @return {@code true} if the state changed, else {@code false}
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 3196eba7d33a..4a467ce3c987 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/charging/ChargingRippleView.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/charging/ChargingRippleView.kt
@@ -39,9 +39,15 @@ class ChargingRippleView(context: Context?, attrs: AttributeSet?) : View(context
var rippleInProgress: Boolean = false
var radius: Float = 0.0f
- set(value) { rippleShader.radius = value }
+ set(value) {
+ rippleShader.radius = value
+ field = value
+ }
var origin: PointF = PointF()
- set(value) { rippleShader.origin = value }
+ set(value) {
+ rippleShader.origin = value
+ field = value
+ }
var duration: Long = 1750
init {
@@ -94,6 +100,11 @@ class ChargingRippleView(context: Context?, attrs: AttributeSet?) : View(context
}
override fun onDraw(canvas: Canvas?) {
- canvas?.drawRect(0f, 0f, width.toFloat(), height.toFloat(), ripplePaint)
+ // To reduce overdraw, we mask the effect to a circle whose radius is big enough to cover
+ // the active effect area. Values here should be kept in sync with the
+ // animation implementation in the ripple shader.
+ val maskRadius = (1 - (1 - rippleShader.progress) * (1 - rippleShader.progress) *
+ (1 - rippleShader.progress)) * radius * 1.5f
+ canvas?.drawCircle(origin.x, origin.y, maskRadius, ripplePaint)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
index 9f82152eb5ed..96b0e7819c7a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
@@ -51,6 +51,7 @@ import android.service.notification.NotificationListenerService.Ranking;
import android.service.notification.SnoozeCriterion;
import android.service.notification.StatusBarNotification;
import android.util.ArraySet;
+import android.view.ContentInfo;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -126,8 +127,12 @@ public final class NotificationEntry extends ListEntry {
public int targetSdk;
private long lastFullScreenIntentLaunchTime = NOT_LAUNCHED_YET;
public CharSequence remoteInputText;
+ // Mimetype and Uri used to display the image in the notification *after* it has been sent.
public String remoteInputMimeType;
public Uri remoteInputUri;
+ // ContentInfo used to keep the attachment permission alive until RemoteInput is sent or
+ // cancelled.
+ public ContentInfo remoteInputAttachment;
private Notification.BubbleMetadata mBubbleMetadata;
private ShortcutInfo mShortcutInfo;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index 76f9fe728f2f..93166f39ad62 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -2482,7 +2482,8 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
int intrinsicBefore = getIntrinsicHeight();
super.onLayout(changed, left, top, right, bottom);
- if (intrinsicBefore != getIntrinsicHeight() && intrinsicBefore != 0) {
+ if (intrinsicBefore != getIntrinsicHeight()
+ && (intrinsicBefore != 0 || getActualHeight() > 0)) {
notifyHeightChanged(true /* needsAnimation */);
}
if (mMenuRow != null && mMenuRow.getMenuView() != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FooterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FooterView.java
index 8e24890c8812..86c90c7bcb2e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FooterView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FooterView.java
@@ -25,6 +25,7 @@ import android.view.View;
import com.android.systemui.R;
import com.android.systemui.statusbar.notification.stack.ExpandableViewState;
+import com.android.systemui.statusbar.notification.stack.ViewState;
public class FooterView extends StackScrollerDecorView {
private final int mClearAllTopPadding;
@@ -122,6 +123,14 @@ public class FooterView extends StackScrollerDecorView {
public boolean hideContent;
@Override
+ public void copyFrom(ViewState viewState) {
+ super.copyFrom(viewState);
+ if (viewState instanceof FooterViewState) {
+ hideContent = ((FooterViewState) viewState).hideContent;
+ }
+ }
+
+ @Override
public void applyToView(View view) {
super.applyToView(view);
if (view instanceof FooterView) {
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 4e6d376919e9..4ad72027b046 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
@@ -77,7 +77,6 @@ import com.android.settingslib.Utils;
import com.android.systemui.Dumpable;
import com.android.systemui.ExpandHelper;
import com.android.systemui.R;
-import com.android.systemui.animation.ActivityLaunchAnimator;
import com.android.systemui.animation.Interpolators;
import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
import com.android.systemui.statusbar.CommandQueue;
@@ -143,7 +142,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
false /* default */);
// TODO(b/187291379) disable again before release
private static final boolean DEBUG_REMOVE_ANIMATION = SystemProperties.getBoolean(
- "persist.debug.nssl.dismiss", true /* default */);
+ "persist.debug.nssl.dismiss", false /* default */);
private static final float RUBBER_BAND_FACTOR_NORMAL = 0.35f;
private static final float RUBBER_BAND_FACTOR_AFTER_EXPAND = 0.15f;
@@ -201,6 +200,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
private int mPaddingBetweenElements;
private int mMaxTopPadding;
private int mTopPadding;
+ private boolean mAnimateNextTopPaddingChange;
private int mBottomMargin;
private int mBottomInset = 0;
private float mQsExpansionFraction;
@@ -682,8 +682,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
boolean showDismissView = mClearAllEnabled &&
mController.hasActiveClearableNotifications(ROWS_ALL);
RemoteInputController remoteInputController = mRemoteInputManager.getController();
- boolean showFooterView = (showDismissView || mController.hasActiveNotifications())
- && mEmptyShadeView.getVisibility() == GONE
+ boolean showFooterView = (showDismissView || getVisibleNotificationCount() > 0)
&& mStatusBarState != StatusBarState.KEYGUARD
&& !mUnlockedScreenOffAnimationController.isScreenOffAnimationPlaying()
&& (remoteInputController == null || !remoteInputController.isRemoteInputActive());
@@ -1212,16 +1211,18 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
@ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
private void setTopPadding(int topPadding, boolean animate) {
if (mTopPadding != topPadding) {
+ boolean shouldAnimate = animate || mAnimateNextTopPaddingChange;
mTopPadding = topPadding;
updateAlgorithmHeightAndPadding();
updateContentHeight();
- if (animate && mAnimationsEnabled && mIsExpanded) {
+ if (shouldAnimate && mAnimationsEnabled && mIsExpanded) {
mTopPaddingNeedsAnimation = true;
mNeedsAnimation = true;
}
updateStackPosition();
requestChildrenUpdate();
- notifyHeightChangeListener(null, animate);
+ notifyHeightChangeListener(null, shouldAnimate);
+ mAnimateNextTopPaddingChange = false;
}
}
@@ -2071,6 +2072,9 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
int scrollRange = Math.max(0, contentHeight - mMaxLayoutHeight);
int imeInset = getImeInset();
scrollRange += Math.min(imeInset, Math.max(0, contentHeight - (getHeight() - imeInset)));
+ if (scrollRange > 0) {
+ scrollRange = Math.max(getScrollAmountToScrollBoundary(), scrollRange);
+ }
return scrollRange;
}
@@ -3205,6 +3209,13 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
ignoreChildren);
mAnimationEvents.add(event);
mSwipedOutViews.remove(child);
+ if (DEBUG_REMOVE_ANIMATION) {
+ String key = "";
+ if (child instanceof ExpandableNotificationRow) {
+ key = ((ExpandableNotificationRow) child).getEntry().getKey();
+ }
+ Log.d(TAG, "created Remove Event - SwipedOut: " + childWasSwipedOut + " " + key);
+ }
}
mChildrenToRemoveAnimated.clear();
}
@@ -5256,6 +5267,8 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
mController.getNoticationRoundessManager()
.setViewsAffectedBySwipe(null, null, null,
getResources().getBoolean(R.bool.flag_notif_updates));
+ // Round bottom corners for notification right before shelf.
+ mShelf.updateAppearance();
}
void setTopHeadsUpEntry(NotificationEntry topEntry) {
@@ -5545,6 +5558,13 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
}
/**
+ * Request an animation whenever the toppadding changes next
+ */
+ public void animateNextTopPaddingChange() {
+ mAnimateNextTopPaddingChange = true;
+ }
+
+ /**
* A listener that is notified when the empty space below the notifications is clicked on
*/
@ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index e71f7dbb008b..09afedb6de59 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -1459,6 +1459,13 @@ public class NotificationStackScrollLayoutController {
}
/**
+ * Request an animation whenever the toppadding changes next
+ */
+ public void animateNextTopPaddingChange() {
+ mView.animateNextTopPaddingChange();
+ }
+
+ /**
* Enum for UiEvent logged from this class
*/
enum NotificationPanelEvent implements UiEventLogger.UiEventEnum {
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 8f4a71cf2563..3fc8b8d9aef1 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
@@ -27,7 +27,6 @@ import android.view.ViewGroup;
import com.android.systemui.R;
import com.android.systemui.animation.Interpolators;
import com.android.systemui.statusbar.NotificationShelf;
-import com.android.systemui.statusbar.notification.dagger.SilentHeader;
import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.ExpandableView;
@@ -37,9 +36,9 @@ import java.util.ArrayList;
import java.util.List;
/**
- * The Algorithm of the {@link com.android.systemui.statusbar.notification.stack
- * .NotificationStackScrollLayout} which can be queried for {@link com.android.systemui.statusbar
- * .stack.StackScrollState}
+ * The Algorithm of the
+ * {@link com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout} which can
+ * be queried for {@link StackScrollAlgorithmState}
*/
public class StackScrollAlgorithm {
@@ -96,7 +95,7 @@ public class StackScrollAlgorithm {
// First we reset the view states to their default values.
resetChildViewStates();
- initAlgorithmState(mHostView, algorithmState, ambientState);
+ initAlgorithmState(algorithmState, ambientState);
updatePositionsForState(algorithmState, ambientState);
updateZValuesForState(algorithmState, ambientState);
updateHeadsUpStates(algorithmState, ambientState);
@@ -216,19 +215,18 @@ public class StackScrollAlgorithm {
/**
* Initialize the algorithm state like updating the visible children.
*/
- private void initAlgorithmState(ViewGroup hostView, StackScrollAlgorithmState state,
- AmbientState ambientState) {
+ private void initAlgorithmState(StackScrollAlgorithmState state, AmbientState ambientState) {
state.scrollY = ambientState.getScrollY();
state.mCurrentYPosition = -state.scrollY;
state.mCurrentExpandedYPosition = -state.scrollY;
//now init the visible children and update paddings
- int childCount = hostView.getChildCount();
+ int childCount = mHostView.getChildCount();
state.visibleChildren.clear();
state.visibleChildren.ensureCapacity(childCount);
int notGoneIndex = 0;
for (int i = 0; i < childCount; i++) {
- ExpandableView v = (ExpandableView) hostView.getChildAt(i);
+ ExpandableView v = (ExpandableView) mHostView.getChildAt(i);
if (v.getVisibility() != View.GONE) {
if (v == ambientState.getShelf()) {
continue;
@@ -237,7 +235,7 @@ public class StackScrollAlgorithm {
if (v instanceof ExpandableNotificationRow) {
ExpandableNotificationRow row = (ExpandableNotificationRow) v;
- // handle the notgoneIndex for the children as well
+ // handle the notGoneIndex for the children as well
List<ExpandableNotificationRow> children = row.getAttachedChildren();
if (row.isSummaryWithChildren() && children != null) {
for (ExpandableNotificationRow childRow : children) {
@@ -401,34 +399,42 @@ public class StackScrollAlgorithm {
if (view instanceof FooterView) {
final boolean shadeClosed = !ambientState.isShadeExpanded();
final boolean isShelfShowing = algorithmState.firstViewInShelf != null;
-
- final float footerEnd = algorithmState.mCurrentExpandedYPosition
- + view.getIntrinsicHeight();
- final boolean noSpaceForFooter = footerEnd > ambientState.getStackEndHeight();
- ((FooterView.FooterViewState) viewState).hideContent =
- shadeClosed || isShelfShowing || noSpaceForFooter;
-
- } else if (view != ambientState.getTrackedHeadsUpRow()) {
- if (ambientState.isExpansionChanging()) {
- // Show all views. Views below the shelf will later be clipped (essentially hidden)
- // in NotificationShelf.
- viewState.hidden = false;
- viewState.inShelf = algorithmState.firstViewInShelf != null
- && i >= algorithmState.visibleChildren.indexOf(
- algorithmState.firstViewInShelf);
- } else if (ambientState.getShelf() != null) {
- // When pulsing (incoming notification on AOD), innerHeight is 0; clamp all
- // to shelf start, thereby hiding all notifications (except the first one, which we
- // later unhide in updatePulsingState)
- final int shelfStart = ambientState.getInnerHeight()
- - ambientState.getShelf().getIntrinsicHeight();
- viewState.yTranslation = Math.min(viewState.yTranslation, shelfStart);
- if (viewState.yTranslation >= shelfStart) {
- viewState.hidden = !view.isExpandAnimationRunning()
- && !view.hasExpandingChild();
- viewState.inShelf = true;
- // Notifications in the shelf cannot be visible HUNs.
- viewState.headsUpIsVisible = false;
+ if (shadeClosed) {
+ viewState.hidden = true;
+ } else {
+ final float footerEnd = algorithmState.mCurrentExpandedYPosition
+ + view.getIntrinsicHeight();
+ final boolean noSpaceForFooter = footerEnd > ambientState.getStackEndHeight();
+ ((FooterView.FooterViewState) viewState).hideContent =
+ isShelfShowing || noSpaceForFooter;
+ }
+ } else {
+ if (view != ambientState.getTrackedHeadsUpRow()) {
+ if (ambientState.isExpansionChanging()) {
+ // Show all views. Views below the shelf will later be clipped (essentially
+ // hidden) in NotificationShelf.
+ viewState.hidden = false;
+ viewState.inShelf = algorithmState.firstViewInShelf != null
+ && i >= algorithmState.visibleChildren.indexOf(
+ algorithmState.firstViewInShelf);
+ } else if (ambientState.getShelf() != null) {
+ // When pulsing (incoming notification on AOD), innerHeight is 0; clamp all
+ // to shelf start, thereby hiding all notifications (except the first one, which
+ // we later unhide in updatePulsingState)
+ final int stackBottom =
+ !ambientState.isShadeExpanded() || ambientState.isDozing()
+ ? ambientState.getInnerHeight()
+ : (int) ambientState.getStackHeight();
+ final int shelfStart =
+ stackBottom - ambientState.getShelf().getIntrinsicHeight();
+ viewState.yTranslation = Math.min(viewState.yTranslation, shelfStart);
+ if (viewState.yTranslation >= shelfStart) {
+ viewState.hidden = !view.isExpandAnimationRunning()
+ && !view.hasExpandingChild();
+ viewState.inShelf = true;
+ // Notifications in the shelf cannot be visible HUNs.
+ viewState.headsUpIsVisible = false;
+ }
}
}
@@ -484,7 +490,7 @@ public class StackScrollAlgorithm {
View previousChild) {
return sectionProvider.beginsSection(child, previousChild)
&& visibleIndex > 0
- && !(previousChild instanceof SilentHeader)
+ && !(previousChild instanceof SectionHeaderView)
&& !(child instanceof FooterView);
}
@@ -695,7 +701,7 @@ public class StackScrollAlgorithm {
this.mIsExpanded = isExpanded;
}
- public class StackScrollAlgorithmState {
+ public static class StackScrollAlgorithmState {
/**
* The scroll position of the algorithm (absolute scrolling).
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
index 20e6f60c9b17..917c79fc6de5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
@@ -588,7 +588,8 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp
return MODE_UNLOCK_COLLAPSING;
}
if (mKeyguardViewController.isShowing()) {
- if (mKeyguardViewController.bouncerIsOrWillBeShowing() && unlockingAllowed) {
+ if ((mKeyguardViewController.bouncerIsOrWillBeShowing()
+ || mKeyguardBypassController.getAltBouncerShowing()) && unlockingAllowed) {
if (bypass && mKeyguardBypassController.canPlaySubtleWindowAnimations()) {
return MODE_UNLOCK_FADING;
} else {
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 69360b290118..1361acb1e156 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java
@@ -42,6 +42,7 @@ import com.android.systemui.R;
import com.android.systemui.animation.Interpolators;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.FeatureFlags;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.events.SystemStatusAnimationCallback;
import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler;
@@ -93,6 +94,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue
private final SystemStatusAnimationScheduler mAnimationScheduler;
private final StatusBarLocationPublisher mLocationPublisher;
private NotificationIconAreaController mNotificationIconAreaController;
+ private final FeatureFlags mFeatureFlags;
private List<String> mBlockedIcons = new ArrayList<>();
@@ -115,12 +117,14 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue
OngoingCallController ongoingCallController,
SystemStatusAnimationScheduler animationScheduler,
StatusBarLocationPublisher locationPublisher,
- NotificationIconAreaController notificationIconAreaController
+ NotificationIconAreaController notificationIconAreaController,
+ FeatureFlags featureFlags
) {
mOngoingCallController = ongoingCallController;
mAnimationScheduler = animationScheduler;
mLocationPublisher = locationPublisher;
mNotificationIconAreaController = notificationIconAreaController;
+ mFeatureFlags = featureFlags;
}
@Override
@@ -150,7 +154,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue
mStatusBar.restoreHierarchyState(
savedInstanceState.getSparseParcelableArray(EXTRA_PANEL_STATE));
}
- mDarkIconManager = new DarkIconManager(view.findViewById(R.id.statusIcons));
+ mDarkIconManager = new DarkIconManager(view.findViewById(R.id.statusIcons), mFeatureFlags);
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));
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java
index 31965d4fc4cd..b4f8126042ce 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java
@@ -31,6 +31,7 @@ import com.android.systemui.R;
import com.android.systemui.demomode.DemoMode;
import com.android.systemui.plugins.DarkIconDispatcher;
import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
+import com.android.systemui.statusbar.FeatureFlags;
import com.android.systemui.statusbar.StatusBarIconView;
import com.android.systemui.statusbar.StatusBarMobileView;
import com.android.systemui.statusbar.StatusBarWifiView;
@@ -48,16 +49,22 @@ public class DemoStatusIcons extends StatusIconContainer implements DemoMode, Da
private final LinearLayout mStatusIcons;
private final ArrayList<StatusBarMobileView> mMobileViews = new ArrayList<>();
private final int mIconSize;
+ private final FeatureFlags mFeatureFlags;
private StatusBarWifiView mWifiView;
private boolean mDemoMode;
private int mColor;
- public DemoStatusIcons(LinearLayout statusIcons, int iconSize) {
+ public DemoStatusIcons(
+ LinearLayout statusIcons,
+ int iconSize,
+ FeatureFlags featureFlags
+ ) {
super(statusIcons.getContext());
mStatusIcons = statusIcons;
mIconSize = iconSize;
mColor = DarkIconDispatcher.DEFAULT_ICON_TINT;
+ mFeatureFlags = featureFlags;
if (statusIcons instanceof StatusIconContainer) {
setShouldRestrictIcons(((StatusIconContainer) statusIcons).isRestrictingIcons());
@@ -247,7 +254,8 @@ public class DemoStatusIcons extends StatusIconContainer implements DemoMode, Da
public void addMobileView(MobileIconState state) {
Log.d(TAG, "addMobileView: ");
- StatusBarMobileView view = StatusBarMobileView.fromContext(mContext, state.slot);
+ StatusBarMobileView view = StatusBarMobileView.fromContext(
+ mContext, state.slot, mFeatureFlags.isCombinedStatusBarSignalIconsEnabled());
view.applyMobileState(state);
view.setStaticDrawableColor(mColor);
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 c4d1abc1b74c..68024726c5de 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
@@ -223,8 +223,15 @@ public class DozeParameters implements TunerService.Tunable,
* then abruptly showing AOD.
*/
public boolean shouldControlUnlockedScreenOff() {
- return getAlwaysOn() && mFeatureFlags.useNewLockscreenAnimations()
- && mUnlockedScreenOffAnimationController.shouldPlayUnlockedScreenOffAnimation();
+ return mUnlockedScreenOffAnimationController.shouldPlayUnlockedScreenOffAnimation();
+ }
+
+ /**
+ * Whether we're capable of controlling the screen off animation if we want to. This isn't
+ * possible if AOD isn't even enabled or if the flag is disabled.
+ */
+ public boolean canControlUnlockedScreenOff() {
+ return getAlwaysOn() && mFeatureFlags.useNewLockscreenAnimations();
}
private boolean getBoolean(String propName, int resId) {
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 b7f7f5af78a9..077500f7bfb9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
@@ -459,6 +459,10 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL
mWalletButton.setVisibility(GONE);
mIndicationArea.setPadding(0, 0, 0, 0);
} else {
+ Drawable tileIcon = mQuickAccessWalletController.getWalletClient().getTileIcon();
+ if (tileIcon != null) {
+ mWalletButton.setImageDrawable(tileIcon);
+ }
mWalletButton.setVisibility(VISIBLE);
mWalletButton.setOnClickListener(this::onWalletClick);
mIndicationArea.setPadding(mIndicationPadding, 0, mIndicationPadding, 0);
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 b5e550a47022..d6ea4a828395 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
@@ -367,6 +367,10 @@ public class KeyguardBouncer {
return mShowingSoon || mExpansion != EXPANSION_HIDDEN && mExpansion != EXPANSION_VISIBLE;
}
+ public boolean getShowingSoon() {
+ return mShowingSoon;
+ }
+
/**
* @return {@code true} when bouncer's pre-hide animation already started but isn't completely
* hidden yet, {@code false} otherwise.
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 26c6fe980ee1..c9d08427c8ca 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt
@@ -82,6 +82,7 @@ open class KeyguardBypassController : Dumpable {
private set
var bouncerShowing: Boolean = false
+ var altBouncerShowing: Boolean = false
var launchingAffordance: Boolean = false
var qSExpanded = false
set(value) {
@@ -172,6 +173,7 @@ open class KeyguardBypassController : Dumpable {
if (bypassEnabled) {
return when {
bouncerShowing -> true
+ altBouncerShowing -> true
statusBarStateController.state != StatusBarState.KEYGUARD -> false
launchingAffordance -> false
isPulseExpanding || qSExpanded -> false
@@ -210,6 +212,7 @@ open class KeyguardBypassController : Dumpable {
pw.println(" bypassEnabled: $bypassEnabled")
pw.println(" canBypass: ${canBypass()}")
pw.println(" bouncerShowing: $bouncerShowing")
+ pw.println(" altBouncerShowing: $altBouncerShowing")
pw.println(" isPulseExpanding: $isPulseExpanding")
pw.println(" launchingAffordance: $launchingAffordance")
pw.println(" qSExpanded: $qSExpanded")
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
index fa5011e8802f..ad4213d212a0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
@@ -234,7 +234,9 @@ public class KeyguardClockPositionAlgorithm {
private int getClockY(float panelExpansion, float darkAmount) {
float clockYRegular = getExpandedPreferredClockY();
- float clockYBouncer = -mKeyguardStatusHeight;
+
+ // Dividing the height creates a smoother transition when the user swipes up to unlock
+ float clockYBouncer = -mKeyguardStatusHeight / 3.0f;
// Move clock up while collapsing the shade
float shadeExpansion = Interpolators.FAST_OUT_LINEAR_IN.getInterpolation(panelExpansion);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextView.java
index 96276f46d23d..178974327a75 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextView.java
@@ -38,7 +38,7 @@ import java.util.LinkedList;
* A view to show hints on Keyguard ("Swipe up to unlock", "Tap again to open").
*/
public class KeyguardIndicationTextView extends TextView {
- private static final long MSG_DURATION_MILLIS = 1500;
+ private static final long MSG_MIN_DURATION_MILLIS_DEFAULT = 1500;
private long mNextAnimationTime = 0;
private boolean mAnimationsEnabled = true;
private LinkedList<CharSequence> mMessages = new LinkedList<>();
@@ -104,8 +104,13 @@ public class KeyguardIndicationTextView extends TextView {
long delay = Math.max(0, mNextAnimationTime - timeInMillis);
setNextAnimationTime(timeInMillis + delay + getFadeOutDuration());
+ final long minDurationMillis =
+ (indication != null && indication.getMinVisibilityMillis() != null)
+ ? indication.getMinVisibilityMillis()
+ : MSG_MIN_DURATION_MILLIS_DEFAULT;
+
if (!text.equals("") || hasIcon) {
- setNextAnimationTime(mNextAnimationTime + MSG_DURATION_MILLIS);
+ setNextAnimationTime(mNextAnimationTime + minDurationMillis);
animSetBuilder.before(getInAnimator());
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt
index bfe0684b5411..ec2d0364a3a6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt
@@ -53,7 +53,7 @@ class KeyguardLiftController constructor(
// Not listening anymore since trigger events unregister themselves
isListening = false
updateListeningState()
- keyguardUpdateMonitor.requestFaceAuth()
+ keyguardUpdateMonitor.requestFaceAuth(true)
}
}
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 eef24200a882..e272d2713e2a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
@@ -50,6 +50,7 @@ import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.animation.Interpolators;
import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
+import com.android.systemui.statusbar.FeatureFlags;
import com.android.systemui.statusbar.events.SystemStatusAnimationCallback;
import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler;
import com.android.systemui.statusbar.phone.StatusBarIconController.TintedIconManager;
@@ -105,6 +106,7 @@ public class KeyguardStatusBarView extends RelativeLayout implements
private int mLayoutState = LAYOUT_NONE;
private SystemStatusAnimationScheduler mAnimationScheduler;
+ private FeatureFlags mFeatureFlags;
/**
* Draw this many pixels into the left/right side of the cutout to optimally use the space
@@ -142,6 +144,7 @@ public class KeyguardStatusBarView extends RelativeLayout implements
loadBlockList();
mBatteryController = Dependency.get(BatteryController.class);
mAnimationScheduler = Dependency.get(SystemStatusAnimationScheduler.class);
+ mFeatureFlags = Dependency.get(FeatureFlags.class);
}
@Override
@@ -364,7 +367,7 @@ public class KeyguardStatusBarView extends RelativeLayout implements
userInfoController.addCallback(this);
userInfoController.reloadUserInfo();
Dependency.get(ConfigurationController.class).addCallback(this);
- mIconManager = new TintedIconManager(findViewById(R.id.statusIcons));
+ mIconManager = new TintedIconManager(findViewById(R.id.statusIcons), mFeatureFlags);
mIconManager.setBlockList(mBlockedIcons);
Dependency.get(StatusBarIconController.class).addIconGroup(mIconManager);
mAnimationScheduler.addCallback(this);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
index 41af80e02b5a..cfe95e06fb61 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
@@ -71,6 +71,7 @@ public class NotificationIconAreaController implements
private final DozeParameters mDozeParameters;
private final Optional<Bubbles> mBubblesOptional;
private final StatusBarWindowController mStatusBarWindowController;
+ private final UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController;
private int mIconSize;
private int mIconHPadding;
@@ -119,7 +120,8 @@ public class NotificationIconAreaController implements
Optional<Bubbles> bubblesOptional,
DemoModeController demoModeController,
DarkIconDispatcher darkIconDispatcher,
- StatusBarWindowController statusBarWindowController) {
+ StatusBarWindowController statusBarWindowController,
+ UnlockedScreenOffAnimationController unlockedScreenOffAnimationController) {
mContrastColorUtil = ContrastColorUtil.getInstance(context);
mContext = context;
mStatusBarStateController = statusBarStateController;
@@ -133,6 +135,7 @@ public class NotificationIconAreaController implements
mDemoModeController = demoModeController;
mDemoModeController.addCallback(this);
mStatusBarWindowController = statusBarWindowController;
+ mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController;
notificationListener.addNotificationSettingsListener(mSettingsListener);
initializeNotificationAreaViews(context);
@@ -677,7 +680,12 @@ public class NotificationIconAreaController implements
}
boolean visible = mBypassController.getBypassEnabled()
|| mWakeUpCoordinator.getNotificationsFullyHidden();
- if (mStatusBarStateController.getState() != StatusBarState.KEYGUARD) {
+
+ // Hide the AOD icons if we're not in the KEYGUARD state unless the screen off animation is
+ // playing, in which case we want them to be visible since we're animating in the AOD UI and
+ // will be switching to KEYGUARD shortly.
+ if (mStatusBarStateController.getState() != StatusBarState.KEYGUARD
+ && !mUnlockedScreenOffAnimationController.isScreenOffAnimationPlaying()) {
visible = false;
}
if (visible && mWakeUpCoordinator.isPulseExpanding()) {
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 b4117604c908..c89f698f2656 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
@@ -123,6 +123,7 @@ import com.android.systemui.statusbar.KeyguardAffordanceView;
import com.android.systemui.statusbar.KeyguardIndicationController;
import com.android.systemui.statusbar.LockscreenShadeTransitionController;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
+import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.NotificationShadeDepthController;
import com.android.systemui.statusbar.NotificationShelfController;
import com.android.systemui.statusbar.PulseExpansionHandler;
@@ -182,6 +183,11 @@ public class NotificationPanelViewController extends PanelViewController {
private static final boolean DEBUG = false;
/**
+ * The parallax amount of the quick settings translation when dragging down the panel
+ */
+ private static final float QS_PARALLAX_AMOUNT = 0.175f;
+
+ /**
* Fling expanding QS.
*/
private static final int FLING_EXPAND = 0;
@@ -316,6 +322,7 @@ public class NotificationPanelViewController extends PanelViewController {
private final ScrimController mScrimController;
private final PrivacyDotViewController mPrivacyDotViewController;
private final QuickAccessWalletController mQuickAccessWalletController;
+ private final NotificationRemoteInputManager mRemoteInputManager;
// Maximum # notifications to show on Keyguard; extras will be collapsed in an overflow card.
// If there are exactly 1 + mMaxKeyguardNotifications, then still shows all notifications
@@ -377,7 +384,6 @@ public class NotificationPanelViewController extends PanelViewController {
private float mLastOverscroll;
private boolean mQsExpansionEnabledPolicy = true;
private boolean mQsExpansionEnabledAmbient = true;
- private boolean mQsExpansionEnabled = mQsExpansionEnabledPolicy && mQsExpansionEnabledAmbient;
private ValueAnimator mQsExpansionAnimator;
private FlingAnimationUtils mFlingAnimationUtils;
private int mStatusBarMinHeight;
@@ -548,6 +554,11 @@ public class NotificationPanelViewController extends PanelViewController {
private int mDistanceForQSFullShadeTransition;
/**
+ * The translation amount for QS for the full shade transition
+ */
+ private float mQsTranslationForFullShadeTransition;
+
+ /**
* The maximum overshoot allowed for the top padding for the full shade transition
*/
private int mMaxOverscrollAmountForPulse;
@@ -562,6 +573,11 @@ public class NotificationPanelViewController extends PanelViewController {
private long mNotificationBoundsAnimationDelay;
/**
+ * The duration of the notification bounds animation
+ */
+ private long mNotificationBoundsAnimationDuration;
+
+ /**
* Is this a collapse that started on the panel where we should allow the panel to intercept
*/
private boolean mIsPanelCollapseOnQQS;
@@ -588,7 +604,13 @@ public class NotificationPanelViewController extends PanelViewController {
* The animator for the qs clipping bounds.
*/
private ValueAnimator mQsClippingAnimation = null;
+
+ /**
+ * Is the current animator resetting the qs translation.
+ */
+ private boolean mIsQsTranslationResetAnimator;
private final Rect mKeyguardStatusAreaClipBounds = new Rect();
+ private final Region mQsInterceptRegion = new Region();
/**
* The alpha of the views which only show on the keyguard but not in shade / shade locked
@@ -697,7 +719,8 @@ public class NotificationPanelViewController extends PanelViewController {
@Main Executor uiExecutor,
SecureSettings secureSettings,
UnlockedScreenOffAnimationController unlockedScreenOffAnimationController,
- EmergencyButtonController.Factory emergencyButtonControllerFactory) {
+ EmergencyButtonController.Factory emergencyButtonControllerFactory,
+ NotificationRemoteInputManager remoteInputManager) {
super(view, falsingManager, dozeLog, keyguardStateController,
(SysuiStatusBarStateController) statusBarStateController, vibratorHelper,
statusBarKeyguardViewManager, latencyTracker, flingAnimationUtilsBuilder.get(),
@@ -791,6 +814,8 @@ public class NotificationPanelViewController extends PanelViewController {
mAuthController = authController;
mLockIconViewController = lockIconViewController;
mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController;
+ mRemoteInputManager = remoteInputManager;
+
int currentMode = navigationModeController.addListener(
mode -> mIsGestureNavigation = QuickStepContract.isGesturalMode(mode));
mIsGestureNavigation = QuickStepContract.isGesturalMode(currentMode);
@@ -1335,8 +1360,7 @@ public class NotificationPanelViewController extends PanelViewController {
: mNotificationShelfController.getIntrinsicHeight() + notificationPadding;
float lockIconPadding = 0;
- if (mLockIconViewController.getTop() != 0
- && (mUpdateMonitor.isUdfpsEnrolled() || mUpdateMonitor.isFaceEnrolled())) {
+ if (mLockIconViewController.getTop() != 0) {
lockIconPadding = mStatusBar.getDisplayHeight() - mLockIconViewController.getTop();
}
@@ -1466,9 +1490,8 @@ public class NotificationPanelViewController extends PanelViewController {
}
private void setQsExpansionEnabled() {
- mQsExpansionEnabled = mQsExpansionEnabledPolicy && mQsExpansionEnabledAmbient;
if (mQs == null) return;
- mQs.setHeaderClickable(mQsExpansionEnabled);
+ mQs.setHeaderClickable(isQsExpansionEnabled());
}
public void setQsExpansionEnabledPolicy(boolean qsExpansionEnabledPolicy) {
@@ -1537,8 +1560,13 @@ public class NotificationPanelViewController extends PanelViewController {
flingSettings(0 /* vel */, animateAway ? FLING_HIDE : FLING_COLLAPSE);
}
+ private boolean isQsExpansionEnabled() {
+ return mQsExpansionEnabledPolicy && mQsExpansionEnabledAmbient
+ && !mRemoteInputManager.getController().isRemoteInputActive();
+ }
+
public void expandWithQs() {
- if (mQsExpansionEnabled) {
+ if (isQsExpansionEnabled()) {
mQsExpandImmediate = true;
mNotificationStackScrollLayoutController.setShouldShowShelfOnly(true);
}
@@ -1803,7 +1831,7 @@ public class NotificationPanelViewController extends PanelViewController {
private boolean handleQsTouch(MotionEvent event) {
final int action = event.getActionMasked();
if (action == MotionEvent.ACTION_DOWN && getExpandedFraction() == 1f
- && mBarState != KEYGUARD && !mQsExpanded && mQsExpansionEnabled) {
+ && mBarState != KEYGUARD && !mQsExpanded && isQsExpansionEnabled()) {
// Down in the empty area while fully expanded - go to QS.
mQsTracking = true;
traceQsJank(true /* startTracing */, false /* wasCancelled */);
@@ -1825,7 +1853,7 @@ public class NotificationPanelViewController extends PanelViewController {
if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
mConflictingQsExpansionGesture = false;
}
- if (action == MotionEvent.ACTION_DOWN && isFullyCollapsed() && mQsExpansionEnabled) {
+ if (action == MotionEvent.ACTION_DOWN && isFullyCollapsed() && isQsExpansionEnabled()) {
mTwoFingerQsExpandPossible = true;
}
if (mTwoFingerQsExpandPossible && isOpenQsEvent(event) && event.getY(event.getActionIndex())
@@ -2049,7 +2077,7 @@ public class NotificationPanelViewController extends PanelViewController {
// When expanding QS, let's authenticate the user if possible,
// this will speed up notification actions.
if (height == 0) {
- mStatusBar.requestFaceAuth();
+ mStatusBar.requestFaceAuth(false);
}
}
@@ -2225,7 +2253,8 @@ public class NotificationPanelViewController extends PanelViewController {
private void onStackYChanged(boolean shouldAnimate) {
if (mQs != null) {
if (shouldAnimate) {
- mAnimateNextNotificationBounds = true;
+ animateNextNotificationBounds(StackStateAnimator.ANIMATION_DURATION_STANDARD,
+ 0 /* delay */);
mNotificationBoundsAnimationDelay = 0;
}
setQSClippingBounds();
@@ -2244,8 +2273,7 @@ public class NotificationPanelViewController extends PanelViewController {
private void updateQSExpansionEnabledAmbient() {
final float scrollRangeToTop = mAmbientState.getTopPadding() - mQuickQsOffsetHeight;
- mQsExpansionEnabledAmbient =
- mAmbientState.getScrollY() <= scrollRangeToTop && !mAmbientState.isShadeOpening();
+ mQsExpansionEnabledAmbient = mAmbientState.getScrollY() <= scrollRangeToTop;
setQsExpansionEnabled();
}
@@ -2304,8 +2332,7 @@ public class NotificationPanelViewController extends PanelViewController {
final int startBottom = mKeyguardStatusAreaClipBounds.bottom;
mQsClippingAnimation = ValueAnimator.ofFloat(0.0f, 1.0f);
mQsClippingAnimation.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
- mQsClippingAnimation.setDuration(
- StackStateAnimator.ANIMATION_DURATION_GO_TO_FULL_SHADE);
+ mQsClippingAnimation.setDuration(mNotificationBoundsAnimationDuration);
mQsClippingAnimation.setStartDelay(mNotificationBoundsAnimationDelay);
mQsClippingAnimation.addUpdateListener(animation -> {
float fraction = animation.getAnimatedFraction();
@@ -2324,6 +2351,7 @@ public class NotificationPanelViewController extends PanelViewController {
@Override
public void onAnimationEnd(Animator animation) {
mQsClippingAnimation = null;
+ mIsQsTranslationResetAnimator = false;
}
});
mQsClippingAnimation.start();
@@ -2347,7 +2375,18 @@ public class NotificationPanelViewController extends PanelViewController {
statusBarClipTop = top - mKeyguardStatusBar.getTop();
}
if (mQs != null) {
- mQs.setFancyClipping(top, bottom, radius, qsVisible
+ float qsTranslation = 0;
+ if (mTransitioningToFullShadeProgress > 0.0f || (mQsClippingAnimation != null
+ && mIsQsTranslationResetAnimator)) {
+ qsTranslation = (top - mQs.getHeader().getHeight()) * QS_PARALLAX_AMOUNT;
+ }
+ mQsTranslationForFullShadeTransition = qsTranslation;
+ updateQsFrameTranslation();
+ float currentTranslation = mQsFrame.getTranslationY();
+ mQs.setFancyClipping((
+ int) (top - currentTranslation),
+ (int) (bottom - currentTranslation),
+ radius, qsVisible
&& !mShouldUseSplitNotificationShade);
}
mKeyguardStatusViewController.setClipBounds(
@@ -2481,8 +2520,11 @@ public class NotificationPanelViewController extends PanelViewController {
* shade. 0.0f means we're not transitioning yet.
*/
public void setTransitionToFullShadeAmount(float pxAmount, boolean animate, long delay) {
- mAnimateNextNotificationBounds = animate && !mShouldUseSplitNotificationShade;
- mNotificationBoundsAnimationDelay = delay;
+ if (animate && !mShouldUseSplitNotificationShade) {
+ animateNextNotificationBounds(StackStateAnimator.ANIMATION_DURATION_GO_TO_FULL_SHADE,
+ delay);
+ mIsQsTranslationResetAnimator = mQsTranslationForFullShadeTransition > 0.0f;
+ }
float endPosition = 0;
if (pxAmount > 0.0f) {
@@ -2661,15 +2703,21 @@ public class NotificationPanelViewController extends PanelViewController {
* @return Whether we should intercept a gesture to open Quick Settings.
*/
private boolean shouldQuickSettingsIntercept(float x, float y, float yDiff) {
- if (!mQsExpansionEnabled || mCollapsedOnDown || (mKeyguardShowing
+ if (!isQsExpansionEnabled() || mCollapsedOnDown || (mKeyguardShowing
&& mKeyguardBypassController.getBypassEnabled())) {
return false;
}
View header = mKeyguardShowing || mQs == null ? mKeyguardStatusBar : mQs.getHeader();
- final boolean
- onHeader =
- x >= mQsFrame.getX() && x <= mQsFrame.getX() + mQsFrame.getWidth()
- && y >= header.getTop() && y <= header.getBottom();
+
+ mQsInterceptRegion.set(
+ /* left= */ (int) mQsFrame.getX(),
+ /* top= */ header.getTop(),
+ /* right= */ (int) mQsFrame.getX() + mQsFrame.getWidth(),
+ /* bottom= */ header.getBottom());
+ // Also allow QS to intercept if the touch is near the notch.
+ mStatusBarTouchableRegionManager.updateRegionForNotch(mQsInterceptRegion);
+ final boolean onHeader = mQsInterceptRegion.contains((int) x, (int) y);
+
if (mQsExpanded) {
return onHeader || (yDiff < 0 && isInQsArea(x, y));
} else {
@@ -2784,7 +2832,6 @@ public class NotificationPanelViewController extends PanelViewController {
private int calculatePanelHeightShade() {
int emptyBottomMargin = mNotificationStackScrollLayoutController.getEmptyBottomMargin();
int maxHeight = mNotificationStackScrollLayoutController.getHeight() - emptyBottomMargin;
- maxHeight += mNotificationStackScrollLayoutController.getTopPaddingOverflow();
if (mBarState == KEYGUARD) {
int minKeyguardPanelBottom = mClockPositionAlgorithm.getLockscreenStatusViewHeight()
@@ -2876,7 +2923,7 @@ public class NotificationPanelViewController extends PanelViewController {
float startHeight = -mQsExpansionHeight;
if (!mShouldUseSplitNotificationShade && mBarState == StatusBarState.SHADE) {
// Small parallax as we pull down and clip QS
- startHeight = -mQsExpansionHeight * 0.2f;
+ startHeight = -mQsExpansionHeight * QS_PARALLAX_AMOUNT;
}
if (mKeyguardBypassController.getBypassEnabled() && isOnKeyguard()) {
if (mNotificationStackScrollLayoutController.isPulseExpanding()) {
@@ -3058,10 +3105,15 @@ public class NotificationPanelViewController extends PanelViewController {
super.setOverExpansion(overExpansion);
// Translating the quick settings by half the overexpansion to center it in the background
// frame
- mQsFrame.setTranslationY(overExpansion / 2f);
+ updateQsFrameTranslation();
mNotificationStackScrollLayoutController.setOverExpansion(overExpansion);
}
+ private void updateQsFrameTranslation() {
+ float translation = mOverExpansion / 2.0f + mQsTranslationForFullShadeTransition;
+ mQsFrame.setTranslationY(translation);
+ }
+
@Override
protected void onTrackingStarted() {
mFalsingCollector.onTrackingStarted(!mKeyguardStateController.canDismissLockScreen());
@@ -3187,7 +3239,7 @@ public class NotificationPanelViewController extends PanelViewController {
case KEYGUARD:
if (!mDozingOnDown) {
if (mKeyguardBypassController.getBypassEnabled()) {
- mUpdateMonitor.requestFaceAuth();
+ mUpdateMonitor.requestFaceAuth(true);
} else {
mLockscreenGestureLogger.write(MetricsEvent.ACTION_LS_HINT,
0 /* lengthDp - N/A */, 0 /* velocityDp - N/A */);
@@ -3474,14 +3526,21 @@ public class NotificationPanelViewController extends PanelViewController {
return !isFullWidth() || !mShowIconsWhenExpanded;
}
+ public final QS.ScrollListener mScrollListener = scrollY -> {
+ if (scrollY > 0 && !mQsFullyExpanded) {
+ if (DEBUG) Log.d(TAG, "Scrolling while not expanded. Forcing expand");
+ // If we are scrolling QS, we should be fully expanded.
+ expandWithQs();
+ }
+ };
+
private final FragmentListener mFragmentListener = new FragmentListener() {
@Override
public void onFragmentViewCreated(String tag, Fragment fragment) {
mQs = (QS) fragment;
mQs.setPanelView(mHeightListener);
mQs.setExpandClickListener(mOnClickListener);
- mQs.setHeaderClickable(mQsExpansionEnabled);
- mQs.setTranslateWhileExpanding(mShouldUseSplitNotificationShade);
+ mQs.setHeaderClickable(isQsExpansionEnabled());
updateQSPulseExpansion();
mQs.setOverscrolling(mStackScrollerOverscrolling);
mQs.setTranslateWhileExpanding(mShouldUseSplitNotificationShade);
@@ -3495,8 +3554,16 @@ public class NotificationPanelViewController extends PanelViewController {
mHeightListener.onQsHeightChanged();
}
});
+ mQs.setCollapsedMediaVisibilityChangedListener((visible) -> {
+ if (mQs.getHeader().isShown()) {
+ animateNextNotificationBounds(StackStateAnimator.ANIMATION_DURATION_STANDARD,
+ 0 /* delay */);
+ mNotificationStackScrollLayoutController.animateNextTopPaddingChange();
+ }
+ });
mLockscreenShadeTransitionController.setQS(mQs);
mNotificationStackScrollLayoutController.setQsContainer((ViewGroup) mQs.getView());
+ mQs.setScrollListener(mScrollListener);
updateQsExpansion();
}
@@ -3511,6 +3578,12 @@ public class NotificationPanelViewController extends PanelViewController {
}
};
+ private void animateNextNotificationBounds(long duration, long delay) {
+ mAnimateNextNotificationBounds = true;
+ mNotificationBoundsAnimationDuration = duration;
+ mNotificationBoundsAnimationDelay = delay;
+ }
+
@Override
public void setTouchAndAnimationDisabled(boolean disabled) {
super.setTouchAndAnimationDisabled(disabled);
@@ -3832,8 +3905,13 @@ public class NotificationPanelViewController extends PanelViewController {
expand(true /* animate */);
}
initDownStates(event);
- if (!mIsExpanding && !shouldQuickSettingsIntercept(mDownX, mDownY, 0)
- && mPulseExpansionHandler.onTouchEvent(event)) {
+
+ // If pulse is expanding already, let's give it the touch. There are situations
+ // where the panel starts expanding even though we're also pulsing
+ boolean pulseShouldGetTouch = (!mIsExpanding
+ && !shouldQuickSettingsIntercept(mDownX, mDownY, 0))
+ || mPulseExpansionHandler.isExpanding();
+ if (pulseShouldGetTouch && mPulseExpansionHandler.onTouchEvent(event)) {
// We're expanding all the other ones shouldn't get this anymore
return true;
}
@@ -3865,6 +3943,10 @@ public class NotificationPanelViewController extends PanelViewController {
mStatusBarKeyguardViewManager.updateKeyguardPosition(event.getX());
}
+ if (mLockIconViewController.onTouchEvent(event)) {
+ return true;
+ }
+
handled |= super.onTouch(v, event);
return !mDozing || mPulsing || handled;
}
@@ -3954,7 +4036,7 @@ public class NotificationPanelViewController extends PanelViewController {
if (mQsExpanded) {
flingSettings(0 /* vel */, FLING_COLLAPSE, null /* onFinishRunnable */,
true /* isClick */);
- } else if (mQsExpansionEnabled) {
+ } else if (isQsExpansionEnabled()) {
mLockscreenGestureLogger.write(MetricsEvent.ACTION_SHADE_QS_TAP, 0, 0);
flingSettings(0 /* vel */, FLING_EXPAND, null /* onFinishRunnable */,
true /* isClick */);
@@ -3971,7 +4053,7 @@ public class NotificationPanelViewController extends PanelViewController {
return;
}
cancelQsAnimation();
- if (!mQsExpansionEnabled) {
+ if (!isQsExpansionEnabled()) {
amount = 0f;
}
float rounded = amount >= 1f ? amount : 0f;
@@ -3999,8 +4081,9 @@ public class NotificationPanelViewController extends PanelViewController {
setOverScrolling(false);
}
setQsExpansion(mQsExpansionHeight);
- flingSettings(!mQsExpansionEnabled && open ? 0f : velocity,
- open && mQsExpansionEnabled ? FLING_EXPAND : FLING_COLLAPSE, () -> {
+ boolean canExpand = isQsExpansionEnabled();
+ flingSettings(!canExpand && open ? 0f : velocity,
+ open && canExpand ? FLING_EXPAND : FLING_COLLAPSE, () -> {
setOverScrolling(false);
updateQsState();
}, false /* isClick */);
@@ -4362,6 +4445,8 @@ public class NotificationPanelViewController extends PanelViewController {
*/
public void showAodUi() {
setDozing(true /* dozing */, false /* animate */, null);
+ mStatusBarStateController.setUpcomingState(KEYGUARD);
+ mEntryManager.updateNotifications("showAodUi");
mStatusBarStateListener.onStateChanged(KEYGUARD);
mStatusBarStateListener.onDozeAmountChanged(1f, 1f);
setExpandedFraction(1f);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java
index c95879650049..022faf78b946 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java
@@ -38,8 +38,10 @@ import android.view.ViewGroup;
import android.view.WindowManager;
import android.view.WindowManager.LayoutParams;
+import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.Dumpable;
import com.android.systemui.R;
+import com.android.systemui.biometrics.AuthController;
import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dump.DumpManager;
@@ -83,9 +85,11 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW
private final LayoutParams mLpChanged;
private final boolean mKeyguardScreenRotation;
private final long mLockScreenDisplayTimeout;
- private final float mKeyguardRefreshRate;
+ private final float mKeyguardPreferredRefreshRate; // takes precedence over max
+ private final float mKeyguardMaxRefreshRate;
private final KeyguardViewMediator mKeyguardViewMediator;
private final KeyguardBypassController mKeyguardBypassController;
+ private final AuthController mAuthController;
private ViewGroup mNotificationShadeView;
private LayoutParams mLp;
private boolean mHasTopUi;
@@ -99,6 +103,7 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW
mCallbacks = Lists.newArrayList();
private final SysuiColorExtractor mColorExtractor;
+ private final UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController;
private float mFaceAuthDisplayBrightness = LayoutParams.BRIGHTNESS_OVERRIDE_NONE;
@Inject
@@ -110,7 +115,9 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW
KeyguardBypassController keyguardBypassController,
SysuiColorExtractor colorExtractor,
DumpManager dumpManager,
- KeyguardStateController keyguardStateController) {
+ KeyguardStateController keyguardStateController,
+ UnlockedScreenOffAnimationController unlockedScreenOffAnimationController,
+ AuthController authController) {
mContext = context;
mWindowManager = windowManager;
mActivityManager = activityManager;
@@ -121,7 +128,9 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW
mKeyguardViewMediator = keyguardViewMediator;
mKeyguardBypassController = keyguardBypassController;
mColorExtractor = colorExtractor;
+ mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController;
dumpManager.registerDumpable(getClass().getName(), this);
+ mAuthController = authController;
mLockScreenDisplayTimeout = context.getResources()
.getInteger(R.integer.config_lockScreenDisplayTimeout);
@@ -130,13 +139,25 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW
SysuiStatusBarStateController.RANK_STATUS_BAR_WINDOW_CONTROLLER);
configurationController.addCallback(this);
- Display.Mode[] supportedModes = context.getDisplay().getSupportedModes();
- Display.Mode currentMode = context.getDisplay().getMode();
+ float desiredPreferredRefreshRate = context.getResources()
+ .getInteger(R.integer.config_keyguardRefreshRate);
+ float actualPreferredRefreshRate = -1;
+ if (desiredPreferredRefreshRate > -1) {
+ for (Display.Mode displayMode : context.getDisplay().getSupportedModes()) {
+ if (Math.abs(displayMode.getRefreshRate() - desiredPreferredRefreshRate) <= .1) {
+ actualPreferredRefreshRate = displayMode.getRefreshRate();
+ break;
+ }
+ }
+ }
+
+ mKeyguardPreferredRefreshRate = actualPreferredRefreshRate;
+
// Running on the highest frame rate available can be expensive.
// Let's specify a preferred refresh rate, and allow higher FPS only when we
// know that we're not falsing (because we unlocked.)
- mKeyguardRefreshRate = context.getResources()
- .getInteger(R.integer.config_keyguardRefreshRate);
+ mKeyguardMaxRefreshRate = context.getResources()
+ .getInteger(R.integer.config_keyguardMaxRefreshRate);
}
/**
@@ -251,7 +272,7 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW
private void applyKeyguardFlags(State state) {
final boolean scrimsOccludingWallpaper =
- state.mScrimsVisibility == ScrimController.OPAQUE;
+ state.mScrimsVisibility == ScrimController.OPAQUE || state.mLightRevealScrimOpaque;
final boolean keyguardOrAod = state.mKeyguardShowing
|| (state.mDozing && mDozeParameters.getAlwaysOn());
if ((keyguardOrAod && !state.mBackdropShowing && !scrimsOccludingWallpaper)
@@ -271,12 +292,26 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW
mLpChanged.privateFlags &= ~LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
}
- if (mKeyguardRefreshRate > 0) {
+ if (mKeyguardPreferredRefreshRate > 0) {
+ boolean onKeyguard = state.mStatusBarState == StatusBarState.KEYGUARD
+ && !state.mKeyguardFadingAway && !state.mKeyguardGoingAway
+ && !state.mDozing;
+ if (onKeyguard
+ && mAuthController.isUdfpsEnrolled(KeyguardUpdateMonitor.getCurrentUser())) {
+ mLpChanged.preferredMaxDisplayRefreshRate = mKeyguardPreferredRefreshRate;
+ mLpChanged.preferredMinDisplayRefreshRate = mKeyguardPreferredRefreshRate;
+ } else {
+ mLpChanged.preferredMaxDisplayRefreshRate = 0;
+ mLpChanged.preferredMinDisplayRefreshRate = 0;
+ }
+ Trace.setCounter("display_set_preferred_refresh_rate",
+ (long) mKeyguardPreferredRefreshRate);
+ } else if (mKeyguardMaxRefreshRate > 0) {
boolean bypassOnKeyguard = mKeyguardBypassController.getBypassEnabled()
&& state.mStatusBarState == StatusBarState.KEYGUARD
&& !state.mKeyguardFadingAway && !state.mKeyguardGoingAway;
if (state.mDozing || bypassOnKeyguard) {
- mLpChanged.preferredMaxDisplayRefreshRate = mKeyguardRefreshRate;
+ mLpChanged.preferredMaxDisplayRefreshRate = mKeyguardMaxRefreshRate;
} else {
mLpChanged.preferredMaxDisplayRefreshRate = 0;
}
@@ -300,7 +335,11 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW
private void applyFocusableFlag(State state) {
boolean panelFocusable = state.mNotificationShadeFocusable && state.mPanelExpanded;
if (state.mBouncerShowing && (state.mKeyguardOccluded || state.mKeyguardNeedsInput)
- || ENABLE_REMOTE_INPUT && state.mRemoteInputActive) {
+ || ENABLE_REMOTE_INPUT && state.mRemoteInputActive
+ // Make the panel focusable if we're doing the screen off animation, since the light
+ // reveal scrim is drawing in the panel and should consume touch events so that they
+ // don't go to the app behind.
+ || mUnlockedScreenOffAnimationController.isScreenOffAnimationPlaying()) {
mLpChanged.flags &= ~LayoutParams.FLAG_NOT_FOCUSABLE;
mLpChanged.flags &= ~LayoutParams.FLAG_ALT_FOCUSABLE_IM;
} else if (state.isKeyguardShowingAndNotOccluded() || panelFocusable) {
@@ -563,6 +602,16 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW
}
@Override
+ public void setLightRevealScrimAmount(float amount) {
+ boolean lightRevealScrimOpaque = amount == 0;
+ if (mCurrentState.mLightRevealScrimOpaque == lightRevealScrimOpaque) {
+ return;
+ }
+ mCurrentState.mLightRevealScrimOpaque = lightRevealScrimOpaque;
+ apply(mCurrentState);
+ }
+
+ @Override
public void setWallpaperSupportsAmbientMode(boolean supportsAmbientMode) {
mCurrentState.mWallpaperSupportsAmbientMode = supportsAmbientMode;
apply(mCurrentState);
@@ -668,7 +717,8 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW
@Override
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
pw.println(TAG + ":");
- pw.println(" mKeyguardRefreshRate=" + mKeyguardRefreshRate);
+ pw.println(" mKeyguardMaxRefreshRate=" + mKeyguardMaxRefreshRate);
+ pw.println(" mKeyguardPreferredRefreshRate=" + mKeyguardPreferredRefreshRate);
pw.println(mCurrentState);
if (mNotificationShadeView != null && mNotificationShadeView.getViewRootImpl() != null) {
mNotificationShadeView.getViewRootImpl().dump(" ", pw);
@@ -727,6 +777,7 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW
boolean mKeyguardGoingAway;
boolean mQsExpanded;
boolean mHeadsUpShowing;
+ boolean mLightRevealScrimOpaque;
boolean mForceCollapsed;
boolean mForceDozeBrightness;
int mFaceAuthDisplayBrightness;
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 7f4dabd3f59f..b5d9bd67bd2d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java
@@ -26,6 +26,7 @@ import android.media.session.MediaSessionLegacyHelper;
import android.os.SystemClock;
import android.os.UserHandle;
import android.provider.Settings;
+import android.util.Log;
import android.view.GestureDetector;
import android.view.InputDevice;
import android.view.KeyEvent;
@@ -66,6 +67,7 @@ import javax.inject.Inject;
* Controller for {@link NotificationShadeWindowView}.
*/
public class NotificationShadeWindowViewController {
+ private static final String TAG = "NotifShadeWindowVC";
private final InjectionInflationController mInjectionInflationController;
private final NotificationWakeUpCoordinator mCoordinator;
private final PulseExpansionHandler mPulseExpansionHandler;
@@ -213,6 +215,10 @@ public class NotificationShadeWindowViewController {
mView.setInteractionEventHandler(new NotificationShadeWindowView.InteractionEventHandler() {
@Override
public Boolean handleDispatchTouchEvent(MotionEvent ev) {
+ if (mStatusBarView == null) {
+ Log.w(TAG, "Ignoring touch while statusBarView not yet set.");
+ return false;
+ }
boolean isDown = ev.getActionMasked() == MotionEvent.ACTION_DOWN;
boolean isUp = ev.getActionMasked() == MotionEvent.ACTION_UP;
boolean isCancel = ev.getActionMasked() == MotionEvent.ACTION_CANCEL;
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 c9d1083135ab..8eb22c63bbcf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -1185,7 +1185,8 @@ public class StatusBar extends SystemUI implements DemoMode,
mOngoingCallController,
mAnimationScheduler,
mStatusBarLocationPublisher,
- mNotificationIconAreaController),
+ mNotificationIconAreaController,
+ mFeatureFlags),
CollapsedStatusBarFragment.TAG)
.commit();
@@ -1243,6 +1244,8 @@ public class StatusBar extends SystemUI implements DemoMode,
mScrimController.attachViews(scrimBehind, notificationsScrim, scrimInFront, scrimForBubble);
mLightRevealScrim = mNotificationShadeWindowView.findViewById(R.id.light_reveal_scrim);
+ mLightRevealScrim.setRevealAmountListener(
+ mNotificationShadeWindowController::setLightRevealScrimAmount);
mUnlockedScreenOffAnimationController.initialize(this, mLightRevealScrim);
updateLightRevealScrimVisibility();
@@ -1723,9 +1726,9 @@ public class StatusBar extends SystemUI implements DemoMode,
/**
* Asks {@link KeyguardUpdateMonitor} to run face auth.
*/
- public void requestFaceAuth() {
+ public void requestFaceAuth(boolean userInitiatedRequest) {
if (!mKeyguardStateController.canDismissLockScreen()) {
- mKeyguardUpdateMonitor.requestFaceAuth();
+ mKeyguardUpdateMonitor.requestFaceAuth(userInitiatedRequest);
}
}
@@ -2112,8 +2115,8 @@ public class StatusBar extends SystemUI implements DemoMode,
}
@Override
- public void disableKeyguardBlurs() {
- mMainThreadHandler.post(mKeyguardViewMediator::disableBlursUntilHidden);
+ public void setBlursDisabledForAppLaunch(boolean disabled) {
+ mKeyguardViewMediator.setBlursDisabledForAppLaunch(disabled);
}
public boolean isDeviceInVrMode() {
@@ -3894,15 +3897,6 @@ public class StatusBar extends SystemUI implements DemoMode,
updateQsExpansionEnabled();
mKeyguardViewMediator.setDozing(mDozing);
- if ((isDozing && mWakefulnessLifecycle.getLastSleepReason()
- == PowerManager.GO_TO_SLEEP_REASON_POWER_BUTTON)
- || (!isDozing && mWakefulnessLifecycle.getLastWakeReason()
- == PowerManager.WAKE_REASON_POWER_BUTTON)) {
- mLightRevealScrim.setRevealEffect(mPowerButtonReveal);
- } else if (!(mLightRevealScrim.getRevealEffect() instanceof CircleReveal)) {
- mLightRevealScrim.setRevealEffect(LiftReveal.INSTANCE);
- }
-
mNotificationsController.requestNotificationUpdate("onDozingChanged");
updateDozingState();
mDozeServiceHost.updateDozing();
@@ -3911,6 +3905,27 @@ public class StatusBar extends SystemUI implements DemoMode,
Trace.endSection();
}
+ /**
+ * Updates the light reveal effect to reflect the reason we're waking or sleeping (for example,
+ * from the power button).
+ * @param wakingUp Whether we're updating because we're waking up (true) or going to sleep
+ * (false).
+ */
+ private void updateRevealEffect(boolean wakingUp) {
+ if (mLightRevealScrim == null) {
+ return;
+ }
+
+ if (wakingUp && mWakefulnessLifecycle.getLastWakeReason()
+ == PowerManager.WAKE_REASON_POWER_BUTTON
+ || !wakingUp && mWakefulnessLifecycle.getLastSleepReason()
+ == PowerManager.GO_TO_SLEEP_REASON_POWER_BUTTON) {
+ mLightRevealScrim.setRevealEffect(mPowerButtonReveal);
+ } else if (!(mLightRevealScrim.getRevealEffect() instanceof CircleReveal)) {
+ mLightRevealScrim.setRevealEffect(LiftReveal.INSTANCE);
+ }
+ }
+
public LightRevealScrim getLightRevealScrim() {
return mLightRevealScrim;
}
@@ -4043,6 +4058,7 @@ public class StatusBar extends SystemUI implements DemoMode,
public void onStartedGoingToSleep() {
String tag = "StatusBar#onStartedGoingToSleep";
DejankUtils.startDetectingBlockingIpcs(tag);
+ updateRevealEffect(false /* wakingUp */);
updateNotificationPanelTouchState();
notifyHeadsUpGoingToSleep();
dismissVolumeDialog();
@@ -4074,6 +4090,7 @@ public class StatusBar extends SystemUI implements DemoMode,
// This is intentionally below the stopDozing call above, since it avoids that we're
// unnecessarily animating the wakeUp transition. Animations should only be enabled
// once we fully woke up.
+ updateRevealEffect(true /* wakingUp */);
updateNotificationPanelTouchState();
mPulseExpansionHandler.onStartedWakingUp();
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..2c7553487067 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
@@ -38,6 +38,7 @@ import com.android.systemui.R;
import com.android.systemui.demomode.DemoModeCommandReceiver;
import com.android.systemui.plugins.DarkIconDispatcher;
import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
+import com.android.systemui.statusbar.FeatureFlags;
import com.android.systemui.statusbar.StatusBarIconView;
import com.android.systemui.statusbar.StatusBarMobileView;
import com.android.systemui.statusbar.StatusBarWifiView;
@@ -121,8 +122,8 @@ public interface StatusBarIconController {
private final DarkIconDispatcher mDarkIconDispatcher;
private int mIconHPadding;
- public DarkIconManager(LinearLayout linearLayout) {
- super(linearLayout);
+ public DarkIconManager(LinearLayout linearLayout, FeatureFlags featureFlags) {
+ super(linearLayout, featureFlags);
mIconHPadding = mContext.getResources().getDimensionPixelSize(
R.dimen.status_bar_icon_padding);
mDarkIconDispatcher = Dependency.get(DarkIconDispatcher.class);
@@ -182,8 +183,8 @@ public interface StatusBarIconController {
class TintedIconManager extends IconManager {
private int mColor;
- public TintedIconManager(ViewGroup group) {
- super(group);
+ public TintedIconManager(ViewGroup group, FeatureFlags featureFlags) {
+ super(group, featureFlags);
}
@Override
@@ -218,6 +219,7 @@ public interface StatusBarIconController {
* Turns info from StatusBarIconController into ImageViews in a ViewGroup.
*/
class IconManager implements DemoModeCommandReceiver {
+ private final FeatureFlags mFeatureFlags;
protected final ViewGroup mGroup;
protected final Context mContext;
protected final int mIconSize;
@@ -231,7 +233,8 @@ public interface StatusBarIconController {
protected ArrayList<String> mBlockList = new ArrayList<>();
- public IconManager(ViewGroup group) {
+ public IconManager(ViewGroup group, FeatureFlags featureFlags) {
+ mFeatureFlags = featureFlags;
mGroup = group;
mContext = group.getContext();
mIconSize = mContext.getResources().getDimensionPixelSize(
@@ -332,7 +335,8 @@ public interface StatusBarIconController {
}
private StatusBarMobileView onCreateStatusBarMobileView(String slot) {
- StatusBarMobileView view = StatusBarMobileView.fromContext(mContext, slot);
+ StatusBarMobileView view = StatusBarMobileView.fromContext(
+ mContext, slot, mFeatureFlags.isCombinedStatusBarSignalIconsEnabled());
return view;
}
@@ -452,7 +456,7 @@ public interface StatusBarIconController {
}
protected DemoStatusIcons createDemoStatusIcons() {
- return new DemoStatusIcons((LinearLayout) mGroup, mIconSize);
+ return new DemoStatusIcons((LinearLayout) mGroup, mIconSize, mFeatureFlags);
}
}
}
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 e8463992ed13..6f63b1780d68 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -42,6 +42,8 @@ import androidx.annotation.VisibleForTesting;
import com.android.internal.util.LatencyTracker;
import com.android.internal.widget.LockPatternUtils;
+import com.android.keyguard.KeyguardMessageArea;
+import com.android.keyguard.KeyguardMessageAreaController;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.KeyguardUpdateMonitorCallback;
import com.android.keyguard.KeyguardViewController;
@@ -50,6 +52,7 @@ import com.android.systemui.DejankUtils;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dock.DockManager;
import com.android.systemui.keyguard.FaceAuthScreenBrightnessController;
+import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.navigationbar.NavigationModeController;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.shared.system.QuickStepContract;
@@ -80,7 +83,7 @@ import javax.inject.Inject;
public class StatusBarKeyguardViewManager implements RemoteInputController.Callback,
StatusBarStateController.StateListener, ConfigurationController.ConfigurationListener,
PanelExpansionListener, NavigationModeController.ModeChangedListener,
- KeyguardViewController {
+ KeyguardViewController, WakefulnessLifecycle.Observer {
// When hiding the Keyguard with timing supplied from WindowManager, better be early than late.
private static final long HIDE_TIMING_CORRECTION_MS = - 16 * 3;
@@ -104,6 +107,10 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
private final NotificationShadeWindowController mNotificationShadeWindowController;
private final Optional<FaceAuthScreenBrightnessController> mFaceAuthScreenBrightnessController;
private final KeyguardBouncer.Factory mKeyguardBouncerFactory;
+ private final WakefulnessLifecycle mWakefulnessLifecycle;
+ private final UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController;
+ private final KeyguardMessageAreaController.Factory mKeyguardMessageAreaFactory;
+ private KeyguardMessageAreaController mKeyguardMessageAreaController;
private final BouncerExpansionCallback mExpansionCallback = new BouncerExpansionCallback() {
@Override
public void onFullyShown() {
@@ -189,6 +196,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
private boolean mLastPulsing;
private int mLastBiometricMode;
private boolean mQsExpanded;
+ private boolean mAnimatedToSleep;
private OnDismissAction mAfterKeyguardGoneAction;
private Runnable mKeyguardGoneCancelAction;
@@ -232,7 +240,10 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
KeyguardStateController keyguardStateController,
Optional<FaceAuthScreenBrightnessController> faceAuthScreenBrightnessController,
NotificationMediaManager notificationMediaManager,
- KeyguardBouncer.Factory keyguardBouncerFactory) {
+ KeyguardBouncer.Factory keyguardBouncerFactory,
+ WakefulnessLifecycle wakefulnessLifecycle,
+ UnlockedScreenOffAnimationController unlockedScreenOffAnimationController,
+ KeyguardMessageAreaController.Factory keyguardMessageAreaFactory) {
mContext = context;
mViewMediatorCallback = callback;
mLockPatternUtils = lockPatternUtils;
@@ -246,6 +257,9 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
mDockManager = dockManager;
mFaceAuthScreenBrightnessController = faceAuthScreenBrightnessController;
mKeyguardBouncerFactory = keyguardBouncerFactory;
+ mWakefulnessLifecycle = wakefulnessLifecycle;
+ mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController;
+ mKeyguardMessageAreaFactory = keyguardMessageAreaFactory;
}
@Override
@@ -263,6 +277,8 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
notificationPanelViewController.addExpansionListener(this);
mBypassController = bypassController;
mNotificationContainer = notificationContainer;
+ mKeyguardMessageAreaController = mKeyguardMessageAreaFactory.create(
+ KeyguardMessageArea.findSecurityMessageDisplay(container));
mFaceAuthScreenBrightnessController.ifPresent((it) -> {
View overlay = new View(mContext);
container.addView(overlay);
@@ -301,6 +317,20 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
mDockManager.addListener(mDockEventListener);
mIsDocked = mDockManager.isDocked();
}
+ mWakefulnessLifecycle.addObserver(new WakefulnessLifecycle.Observer() {
+ @Override
+ public void onFinishedWakingUp() {
+ mAnimatedToSleep = false;
+ updateStates();
+ }
+
+ @Override
+ public void onFinishedGoingToSleep() {
+ mAnimatedToSleep =
+ mUnlockedScreenOffAnimationController.isScreenOffAnimationPlaying();
+ updateStates();
+ }
+ });
}
@Override
@@ -390,9 +420,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
*/
public void showGenericBouncer(boolean scrimmed) {
if (mAlternateAuthInterceptor != null) {
- if (mAlternateAuthInterceptor.showAlternateAuthBouncer()) {
- mStatusBar.updateScrimController();
- }
+ updateAlternateAuthShowing(mAlternateAuthInterceptor.showAlternateAuthBouncer());
return;
}
@@ -459,9 +487,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
mKeyguardGoneCancelAction = null;
}
- if (mAlternateAuthInterceptor.showAlternateAuthBouncer()) {
- mStatusBar.updateScrimController();
- }
+ updateAlternateAuthShowing(mAlternateAuthInterceptor.showAlternateAuthBouncer());
return;
}
@@ -495,6 +521,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
@Override
public void reset(boolean hideBouncerWhenShowing) {
if (mShowing) {
+ mNotificationPanelViewController.closeQs();
if (mOccluded && !mDozing) {
mStatusBar.hideKeyguard();
if (hideBouncerWhenShowing || mBouncer.needsFullscreenBouncer()) {
@@ -513,9 +540,19 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
* Stop showing any alternate auth methods
*/
public void resetAlternateAuth(boolean forceUpdateScrim) {
- if ((mAlternateAuthInterceptor != null
+ final boolean updateScrim = (mAlternateAuthInterceptor != null
&& mAlternateAuthInterceptor.hideAlternateAuthBouncer())
- || forceUpdateScrim) {
+ || forceUpdateScrim;
+ updateAlternateAuthShowing(updateScrim);
+ }
+
+ private void updateAlternateAuthShowing(boolean updateScrim) {
+ if (mKeyguardMessageAreaController != null) {
+ mKeyguardMessageAreaController.setAltBouncerShowing(isShowingAlternateAuth());
+ }
+ mBypassController.setAltBouncerShowing(isShowingAlternateAuth());
+
+ if (updateScrim) {
mStatusBar.updateScrimController();
}
}
@@ -852,12 +889,12 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
@Override
public boolean isBouncerShowing() {
- return mBouncer.isShowing();
+ return mBouncer.isShowing() || isShowingAlternateAuth();
}
@Override
public boolean bouncerIsOrWillBeShowing() {
- return mBouncer.isShowing() || mBouncer.inTransit();
+ return mBouncer.isShowing() || mBouncer.getShowingSoon();
}
public boolean isFullscreenBouncer() {
@@ -981,7 +1018,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
boolean hideWhileDozing = mDozing && biometricMode != MODE_WAKE_AND_UNLOCK_PULSING;
boolean keyguardWithGestureNav = (keyguardShowing && !mDozing || mPulsing && !mIsDocked)
&& mGesturalNav;
- return (!keyguardShowing && !hideWhileDozing || mBouncer.isShowing()
+ return (!mAnimatedToSleep && !keyguardShowing && !hideWhileDozing || mBouncer.isShowing()
|| mRemoteInputActive || keyguardWithGestureNav
|| mGlobalActionsVisible);
}
@@ -1066,7 +1103,14 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
}
public void showBouncerMessage(String message, ColorStateList colorState) {
- mBouncer.showMessage(message, colorState);
+ if (isShowingAlternateAuth()) {
+ if (mKeyguardMessageAreaController != null) {
+ mKeyguardMessageAreaController.setNextMessageColor(colorState);
+ mKeyguardMessageAreaController.setMessage(message);
+ }
+ } else {
+ mBouncer.showMessage(message, colorState);
+ }
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java
index 983b296e006b..95712cd303f5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java
@@ -27,7 +27,6 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.IntentSender;
-import android.os.Handler;
import android.os.RemoteException;
import android.os.UserHandle;
import android.view.View;
@@ -35,6 +34,7 @@ import android.view.ViewParent;
import com.android.systemui.ActivityIntentHelper;
import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.ActionClickLogger;
@@ -50,6 +50,8 @@ import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
import com.android.systemui.statusbar.policy.KeyguardStateController;
+import java.util.concurrent.Executor;
+
import javax.inject.Inject;
/**
@@ -65,6 +67,7 @@ public class StatusBarRemoteInputCallback implements Callback, Callbacks,
private final Context mContext;
private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
private final ShadeController mShadeController;
+ private Executor mExecutor;
private final ActivityIntentHelper mActivityIntentHelper;
private final GroupExpansionManager mGroupExpansionManager;
private View mPendingWorkRemoteInputView;
@@ -74,7 +77,6 @@ public class StatusBarRemoteInputCallback implements Callback, Callbacks,
private final ActionClickLogger mActionClickLogger;
private int mDisabled2;
protected BroadcastReceiver mChallengeReceiver = new ChallengeReceiver();
- private Handler mMainHandler = new Handler();
/**
*/
@@ -89,10 +91,12 @@ public class StatusBarRemoteInputCallback implements Callback, Callbacks,
ActivityStarter activityStarter,
ShadeController shadeController,
CommandQueue commandQueue,
- ActionClickLogger clickLogger) {
+ ActionClickLogger clickLogger,
+ @Main Executor executor) {
mContext = context;
mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
mShadeController = shadeController;
+ mExecutor = executor;
mContext.registerReceiverAsUser(mChallengeReceiver, UserHandle.ALL,
new IntentFilter(ACTION_DEVICE_LOCKED_CHANGED), null, null);
mLockscreenUserManager = notificationLockscreenUserManager;
@@ -113,9 +117,10 @@ public class StatusBarRemoteInputCallback implements Callback, Callbacks,
boolean hasPendingRemoteInput = mPendingRemoteInputView != null;
if (state == StatusBarState.SHADE
&& (mStatusBarStateController.leaveOpenOnKeyguardHide() || hasPendingRemoteInput)) {
- if (!mStatusBarStateController.isKeyguardRequested()) {
+ if (!mStatusBarStateController.isKeyguardRequested()
+ && mKeyguardStateController.isUnlocked()) {
if (hasPendingRemoteInput) {
- mMainHandler.post(mPendingRemoteInputView::callOnClick);
+ mExecutor.execute(mPendingRemoteInputView::callOnClick);
}
mPendingRemoteInputView = null;
}
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 ae619462eae0..19ae79abf8e5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java
@@ -25,6 +25,7 @@ import android.util.Log;
import com.android.settingslib.mobile.TelephonyIcons;
import com.android.systemui.R;
import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.statusbar.FeatureFlags;
import com.android.systemui.statusbar.policy.NetworkController;
import com.android.systemui.statusbar.policy.NetworkController.IconState;
import com.android.systemui.statusbar.policy.NetworkController.MobileDataIndicators;
@@ -63,6 +64,7 @@ public class StatusBarSignalPolicy implements NetworkControllerImpl.SignalCallba
private final Handler mHandler = Handler.getMain();
private final CarrierConfigTracker mCarrierConfigTracker;
private final TunerService mTunerService;
+ private final FeatureFlags mFeatureFlags;
private boolean mHideAirplane;
private boolean mHideMobile;
@@ -82,9 +84,15 @@ public class StatusBarSignalPolicy implements NetworkControllerImpl.SignalCallba
private WifiIconState mWifiIconState = new WifiIconState();
@Inject
- public StatusBarSignalPolicy(Context context, StatusBarIconController iconController,
- CarrierConfigTracker carrierConfigTracker, NetworkController networkController,
- SecurityController securityController, TunerService tunerService) {
+ public StatusBarSignalPolicy(
+ Context context,
+ StatusBarIconController iconController,
+ CarrierConfigTracker carrierConfigTracker,
+ NetworkController networkController,
+ SecurityController securityController,
+ TunerService tunerService,
+ FeatureFlags featureFlags
+ ) {
mContext = context;
mIconController = iconController;
@@ -92,6 +100,7 @@ public class StatusBarSignalPolicy implements NetworkControllerImpl.SignalCallba
mNetworkController = networkController;
mSecurityController = securityController;
mTunerService = tunerService;
+ mFeatureFlags = featureFlags;
mSlotAirplane = mContext.getString(com.android.internal.R.string.status_bar_airplane);
mSlotMobile = mContext.getString(com.android.internal.R.string.status_bar_mobile);
@@ -366,6 +375,9 @@ public class StatusBarSignalPolicy implements NetworkControllerImpl.SignalCallba
@Override
public void setConnectivityStatus(boolean noDefaultNetwork, boolean noValidatedNetwork,
boolean noNetworksAvailable) {
+ if (!mFeatureFlags.isCombinedStatusBarSignalIconsEnabled()) {
+ return;
+ }
if (DEBUG) {
Log.d(TAG, "setConnectivityStatus: "
+ "noDefaultNetwork = " + noDefaultNetwork + ","
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java
index b859250a2442..d3d90639546a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java
@@ -223,7 +223,7 @@ public final class StatusBarTouchableRegionManager implements Dumpable {
}
}
- private void updateRegionForNotch(Region touchableRegion) {
+ void updateRegionForNotch(Region touchableRegion) {
WindowInsets windowInsets = mNotificationShadeWindowView.getRootWindowInsets();
if (windowInsets == null) {
Log.w(TAG, "StatusBarWindowView is not attached.");
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
index 9a04d39c4e9e..4167287a504e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
@@ -45,7 +45,8 @@ class UnlockedScreenOffAnimationController @Inject constructor(
private val wakefulnessLifecycle: WakefulnessLifecycle,
private val statusBarStateControllerImpl: StatusBarStateControllerImpl,
private val keyguardViewMediatorLazy: dagger.Lazy<KeyguardViewMediator>,
- private val keyguardStateController: KeyguardStateController
+ private val keyguardStateController: KeyguardStateController,
+ private val dozeParameters: dagger.Lazy<DozeParameters>
) : WakefulnessLifecycle.Observer {
private val handler = Handler()
@@ -55,9 +56,16 @@ class UnlockedScreenOffAnimationController @Inject constructor(
private var lightRevealAnimationPlaying = false
private var aodUiAnimationPlaying = false
+ /**
+ * The result of our decision whether to play the screen off animation in
+ * [onStartedGoingToSleep], or null if we haven't made that decision yet or aren't going to
+ * sleep.
+ */
+ private var decidedToAnimateGoingToSleep: Boolean? = null
+
private val lightRevealAnimator = ValueAnimator.ofFloat(1f, 0f).apply {
duration = LIGHT_REVEAL_ANIMATION_DURATION
- interpolator = Interpolators.FAST_OUT_SLOW_IN_REVERSE
+ interpolator = Interpolators.LINEAR
addUpdateListener { lightRevealScrim.revealAmount = it.animatedValue as Float }
addListener(object : AnimatorListenerAdapter() {
override fun onAnimationCancel(animation: Animator?) {
@@ -119,11 +127,17 @@ class UnlockedScreenOffAnimationController @Inject constructor(
// Run the callback given to us by the KeyguardVisibilityHelper.
after.run()
+
+ // Done going to sleep, reset this flag.
+ decidedToAnimateGoingToSleep = null
}
.start()
}
override fun onStartedWakingUp() {
+ // Waking up, so reset this flag.
+ decidedToAnimateGoingToSleep = null
+
lightRevealAnimator.cancel()
handler.removeCallbacksAndMessages(null)
}
@@ -146,7 +160,9 @@ class UnlockedScreenOffAnimationController @Inject constructor(
}
override fun onStartedGoingToSleep() {
- if (shouldPlayUnlockedScreenOffAnimation()) {
+ if (dozeParameters.get().shouldControlUnlockedScreenOff()) {
+ decidedToAnimateGoingToSleep = true
+
lightRevealAnimationPlaying = true
lightRevealAnimator.start()
@@ -156,6 +172,8 @@ class UnlockedScreenOffAnimationController @Inject constructor(
// Show AOD. That'll cause the KeyguardVisibilityHelper to call #animateInKeyguard.
statusBar.notificationPanelViewController.showAodUi()
}, ANIMATE_IN_KEYGUARD_DELAY)
+ } else {
+ decidedToAnimateGoingToSleep = false
}
}
@@ -164,6 +182,16 @@ class UnlockedScreenOffAnimationController @Inject constructor(
* on the current state of the device.
*/
fun shouldPlayUnlockedScreenOffAnimation(): Boolean {
+ // If we explicitly already decided not to play the screen off animation, then never change
+ // our mind.
+ if (decidedToAnimateGoingToSleep == false) {
+ return false
+ }
+
+ if (!dozeParameters.get().canControlUnlockedScreenOff()) {
+ return false
+ }
+
// We only play the unlocked screen off animation if we are... unlocked.
if (statusBarStateControllerImpl.state != StatusBarState.SHADE) {
return false
@@ -173,7 +201,8 @@ class UnlockedScreenOffAnimationController @Inject constructor(
// already expanded and showing notifications/QS, the animation looks really messy. For now,
// disable it if the notification panel is expanded.
if (!this::statusBar.isInitialized ||
- statusBar.notificationPanelViewController.isFullyExpanded) {
+ statusBar.notificationPanelViewController.isFullyExpanded ||
+ statusBar.notificationPanelViewController.isExpanding) {
return false
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallChronometer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallChronometer.kt
index 6e27caec9365..bb7ba4c4174f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallChronometer.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallChronometer.kt
@@ -20,6 +20,7 @@ import android.content.Context
import android.util.AttributeSet
import android.widget.Chronometer
+import androidx.annotation.UiThread
/**
* A [Chronometer] specifically for the ongoing call chip in the status bar.
@@ -46,10 +47,10 @@ class OngoingCallChronometer @JvmOverloads constructor(
// Minimum width that the text view can be. Corresponds with the largest number width seen so
// far.
- var minimumTextWidth: Int = 0
+ private var minimumTextWidth: Int = 0
// True if the text is too long for the space available, so the text should be hidden.
- var shouldHideText: Boolean = false
+ private var shouldHideText: Boolean = false
override fun setBase(base: Long) {
// These variables may have changed during the previous call, so re-set them before the new
@@ -60,6 +61,13 @@ class OngoingCallChronometer @JvmOverloads constructor(
super.setBase(base)
}
+ /** Sets whether this view should hide its text or not. */
+ @UiThread
+ fun setShouldHideText(shouldHideText: Boolean) {
+ this.shouldHideText = shouldHideText
+ requestLayout()
+ }
+
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
if (shouldHideText) {
setMeasuredDimension(0, 0)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
index b295f6659f81..16fa5da9e979 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
@@ -25,6 +25,7 @@ import android.content.Intent
import android.util.Log
import android.view.View
import android.widget.Chronometer
+import androidx.annotation.VisibleForTesting
import com.android.internal.jank.InteractionJankMonitor
import com.android.systemui.R
import com.android.systemui.animation.ActivityLaunchAnimator
@@ -85,7 +86,7 @@ class OngoingCallController @Inject constructor(
val newOngoingCallInfo = CallNotificationInfo(
entry.sbn.key,
entry.sbn.notification.`when`,
- entry.sbn.notification.contentIntent.intent,
+ entry.sbn.notification.contentIntent?.intent,
entry.sbn.uid,
entry.sbn.notification.extras.getInt(
Notification.EXTRA_CALL_TYPE, -1) == CALL_TYPE_ONGOING
@@ -122,6 +123,7 @@ class OngoingCallController @Inject constructor(
* Should only be called from [CollapsedStatusBarFragment].
*/
fun setChipView(chipView: View) {
+ tearDownChipView()
this.chipView = chipView
if (hasOngoingCall()) {
updateChip()
@@ -165,25 +167,33 @@ class OngoingCallController @Inject constructor(
val currentCallNotificationInfo = callNotificationInfo ?: return
val currentChipView = chipView
- val timeView =
- currentChipView?.findViewById<Chronometer>(R.id.ongoing_call_chip_time)
+ val timeView = currentChipView?.getTimeView()
val backgroundView =
currentChipView?.findViewById<View>(R.id.ongoing_call_chip_background)
if (currentChipView != null && timeView != null && backgroundView != null) {
- timeView.base = currentCallNotificationInfo.callStartTime -
- System.currentTimeMillis() +
- systemClock.elapsedRealtime()
- timeView.start()
-
- currentChipView.setOnClickListener {
- logger.logChipClicked()
- activityStarter.postStartActivityDismissingKeyguard(
- currentCallNotificationInfo.intent, 0,
- ActivityLaunchAnimator.Controller.fromView(
- backgroundView,
- InteractionJankMonitor.CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP)
- )
+ if (currentCallNotificationInfo.hasValidStartTime()) {
+ timeView.setShouldHideText(false)
+ timeView.base = currentCallNotificationInfo.callStartTime -
+ systemClock.currentTimeMillis() +
+ systemClock.elapsedRealtime()
+ timeView.start()
+ } else {
+ timeView.setShouldHideText(true)
+ timeView.stop()
+ }
+
+ currentCallNotificationInfo.intent?.let { intent ->
+ currentChipView.setOnClickListener {
+ logger.logChipClicked()
+ activityStarter.postStartActivityDismissingKeyguard(
+ intent,
+ 0,
+ ActivityLaunchAnimator.Controller.fromView(
+ backgroundView,
+ InteractionJankMonitor.CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP)
+ )
+ }
}
setUpUidObserver(currentCallNotificationInfo)
@@ -245,20 +255,35 @@ class OngoingCallController @Inject constructor(
private fun removeChip() {
callNotificationInfo = null
+ tearDownChipView()
mListeners.forEach { l -> l.onOngoingCallStateChanged(animate = true) }
if (uidObserver != null) {
iActivityManager.unregisterUidObserver(uidObserver)
}
}
+ /** Tear down anything related to the chip view to prevent leaks. */
+ @VisibleForTesting
+ fun tearDownChipView() = chipView?.getTimeView()?.stop()
+
+ private fun View.getTimeView(): OngoingCallChronometer? {
+ return this.findViewById(R.id.ongoing_call_chip_time)
+ }
+
private data class CallNotificationInfo(
val key: String,
val callStartTime: Long,
- val intent: Intent,
+ val intent: Intent?,
val uid: Int,
/** True if the call is currently ongoing (as opposed to incoming, screening, etc.). */
val isOngoing: Boolean
- )
+ ) {
+ /**
+ * Returns true if the notification information has a valid call start time.
+ * See b/192379214.
+ */
+ fun hasValidStartTime(): Boolean = callStartTime > 0
+ }
}
private fun isCallNotification(entry: NotificationEntry): Boolean {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java
index 399c8500ab48..a0edc7c494bc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java
@@ -74,11 +74,13 @@ public class BrightnessMirrorController
mBrightnessMirror.setVisibility(View.VISIBLE);
mVisibilityCallback.accept(true);
mNotificationPanel.setPanelAlpha(0, true /* animate */);
+ mDepthController.setBrightnessMirrorVisible(true);
}
public void hideMirror() {
mVisibilityCallback.accept(false);
mNotificationPanel.setPanelAlpha(255, true /* animate */);
+ mDepthController.setBrightnessMirrorVisible(false);
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
index 724a851107d5..081fe5a47626 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
@@ -33,6 +33,7 @@ import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.UiEvent;
import com.android.internal.logging.UiEventLogger;
import com.android.systemui.Dependency;
+import com.android.systemui.EventLogTags;
import com.android.systemui.R;
import com.android.systemui.statusbar.AlertingNotificationManager;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -175,6 +176,7 @@ public abstract class HeadsUpManager extends AlertingNotificationManager {
NotificationEntry entry = alertEntry.mEntry;
entry.setHeadsUp(true);
setEntryPinned((HeadsUpEntry) alertEntry, shouldHeadsUpBecomePinned(entry));
+ EventLogTags.writeSysuiHeadsUpStatus(entry.getKey(), 1 /* visible */);
for (OnHeadsUpChangedListener listener : mListeners) {
listener.onHeadsUpStateChanged(entry, true);
}
@@ -185,6 +187,7 @@ public abstract class HeadsUpManager extends AlertingNotificationManager {
NotificationEntry entry = alertEntry.mEntry;
entry.setHeadsUp(false);
setEntryPinned((HeadsUpEntry) alertEntry, false /* isPinned */);
+ EventLogTags.writeSysuiHeadsUpStatus(entry.getKey(), 0 /* visible */);
for (OnHeadsUpChangedListener listener : mListeners) {
listener.onHeadsUpStateChanged(entry, false);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyController.java
index d7d1e737661e..acfdda4cea49 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyController.java
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.policy;
import android.hardware.SensorPrivacyManager.Sensors.Sensor;
+import android.hardware.SensorPrivacyManager.Sources.Source;
public interface IndividualSensorPrivacyController extends
CallbackController<IndividualSensorPrivacyController.Callback> {
@@ -26,7 +27,7 @@ public interface IndividualSensorPrivacyController extends
boolean isSensorBlocked(@Sensor int sensor);
- void setSensorBlocked(@Sensor int sensor, boolean blocked);
+ void setSensorBlocked(@Source int source, @Sensor int sensor, boolean blocked);
void suppressSensorPrivacyReminders(String packageName, boolean suppress);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyControllerImpl.java
index f58a7c030b80..9807165f69d0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyControllerImpl.java
@@ -21,6 +21,7 @@ import static android.hardware.SensorPrivacyManager.Sensors.MICROPHONE;
import android.hardware.SensorPrivacyManager;
import android.hardware.SensorPrivacyManager.Sensors.Sensor;
+import android.hardware.SensorPrivacyManager.Sources.Source;
import android.util.ArraySet;
import android.util.SparseBooleanArray;
@@ -62,8 +63,8 @@ public class IndividualSensorPrivacyControllerImpl implements IndividualSensorPr
}
@Override
- public void setSensorBlocked(@Sensor int sensor, boolean blocked) {
- mSensorPrivacyManager.setSensorPrivacyForProfileGroup(sensor, blocked);
+ public void setSensorBlocked(@Source int source, @Sensor int sensor, boolean blocked) {
+ mSensorPrivacyManager.setSensorPrivacyForProfileGroup(source, sensor, blocked);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java
index 0af1469b6c7c..23db06f8c899 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java
@@ -51,7 +51,6 @@ import android.telephony.ims.ImsRegistrationAttributes;
import android.telephony.ims.RegistrationManager.RegistrationCallback;
import android.text.Html;
import android.text.TextUtils;
-import android.util.FeatureFlagUtils;
import android.util.Log;
import com.android.ims.ImsManager;
@@ -75,6 +74,7 @@ import com.android.systemui.R;
import com.android.systemui.statusbar.policy.FiveGServiceClient;
import com.android.systemui.statusbar.policy.FiveGServiceClient.FiveGServiceState;
import com.android.systemui.statusbar.policy.FiveGServiceClient.IFiveGStateListener;
+import com.android.systemui.statusbar.FeatureFlags;
import com.android.systemui.statusbar.policy.NetworkController.IconState;
import com.android.systemui.statusbar.policy.NetworkController.MobileDataIndicators;
import com.android.systemui.statusbar.policy.NetworkController.SignalCallback;
@@ -102,7 +102,8 @@ public class MobileSignalController extends SignalController<MobileState, Mobile
private final String mNetworkNameDefault;
private final String mNetworkNameSeparator;
private final ContentObserver mObserver;
- private final boolean mProviderModel;
+ private final boolean mProviderModelBehavior;
+ private final boolean mProviderModelSetting;
private final Handler mReceiverHandler;
private int mImsType = IMS_TYPE_WWAN;
// Save entire info for logging, we only use the id.
@@ -153,11 +154,19 @@ public class MobileSignalController extends SignalController<MobileState, Mobile
// TODO: Reduce number of vars passed in, if we have the NetworkController, probably don't
// need listener lists anymore.
- public MobileSignalController(Context context, Config config, boolean hasMobileData,
- TelephonyManager phone, CallbackHandler callbackHandler,
- NetworkControllerImpl networkController, SubscriptionInfo info,
- SubscriptionDefaults defaults, Looper receiverLooper,
- CarrierConfigTracker carrierConfigTracker) {
+ public MobileSignalController(
+ Context context,
+ Config config,
+ boolean hasMobileData,
+ TelephonyManager phone,
+ CallbackHandler callbackHandler,
+ NetworkControllerImpl networkController,
+ SubscriptionInfo info,
+ SubscriptionDefaults defaults,
+ Looper receiverLooper,
+ CarrierConfigTracker carrierConfigTracker,
+ FeatureFlags featureFlags
+ ) {
super("MobileSignalController(" + info.getSubscriptionId() + ")", context,
NetworkCapabilities.TRANSPORT_CELLULAR, callbackHandler,
networkController);
@@ -286,8 +295,8 @@ public class MobileSignalController extends SignalController<MobileState, Mobile
mImsMmTelManager = ImsMmTelManager.createForSubscriptionId(info.getSubscriptionId());
mMobileStatusTracker = new MobileStatusTracker(mPhone, receiverLooper,
info, mDefaults, mCallback);
- mProviderModel = FeatureFlagUtils.isEnabled(
- mContext, FeatureFlagUtils.SETTINGS_PROVIDER_MODEL);
+ mProviderModelBehavior = featureFlags.isCombinedStatusBarSignalIconsEnabled();
+ mProviderModelSetting = featureFlags.isProviderModelSettingEnabled();
}
public void setConfiguration(Config config) {
@@ -340,7 +349,7 @@ public class MobileSignalController extends SignalController<MobileState, Mobile
mContext.registerReceiver(mVolteSwitchObserver,
new IntentFilter("org.codeaurora.intent.action.ACTION_ENHANCE_4G_SWITCH"));
mFeatureConnector.connect();
- if (mProviderModel) {
+ if (mProviderModelBehavior) {
mReceiverHandler.post(mTryRegisterIms);
}
}
@@ -511,7 +520,7 @@ public class MobileSignalController extends SignalController<MobileState, Mobile
|| (mCurrentState.iconGroup == TelephonyIcons.NOT_DEFAULT_DATA))
&& mCurrentState.userSetup;
- if (mProviderModel) {
+ if (mProviderModelBehavior) {
// Show icon in QS when we are connected or data is disabled.
boolean showDataIcon = mCurrentState.dataConnected || dataDisabled;
@@ -555,13 +564,26 @@ public class MobileSignalController extends SignalController<MobileState, Mobile
IconState qsIcon = null;
CharSequence description = null;
// Only send data sim callbacks to QS.
- if (mCurrentState.dataSim) {
- qsTypeIcon =
- (showDataIcon || mConfig.alwaysShowDataRatIcon) ? icons.qsDataType : 0;
- qsIcon = new IconState(mCurrentState.enabled
- && !mCurrentState.isEmergency, getQsCurrentIconId(), contentDescription);
- description = mCurrentState.isEmergency ? null : mCurrentState.networkName;
+ if (mProviderModelSetting) {
+ if (mCurrentState.dataSim && mCurrentState.isDefault) {
+ qsTypeIcon =
+ (showDataIcon || mConfig.alwaysShowDataRatIcon) ? icons.qsDataType : 0;
+ qsIcon = new IconState(
+ mCurrentState.enabled && !mCurrentState.isEmergency,
+ getQsCurrentIconId(), contentDescription);
+ description = mCurrentState.isEmergency ? null : mCurrentState.networkName;
+ }
+ } else {
+ if (mCurrentState.dataSim) {
+ qsTypeIcon =
+ (showDataIcon || mConfig.alwaysShowDataRatIcon) ? icons.qsDataType : 0;
+ qsIcon = new IconState(
+ mCurrentState.enabled && !mCurrentState.isEmergency,
+ getQsCurrentIconId(), contentDescription);
+ description = mCurrentState.isEmergency ? null : mCurrentState.networkName;
+ }
}
+
boolean activityIn = mCurrentState.dataConnected
&& !mCurrentState.carrierNetworkChangeMode
&& mCurrentState.activityIn;
@@ -755,7 +777,7 @@ public class MobileSignalController extends SignalController<MobileState, Mobile
// 1. The first valid voice state has been received
// 2. The voice state has been changed and either the last or current state is
// ServiceState.STATE_IN_SERVICE
- if (mProviderModel
+ if (mProviderModelBehavior
&& lastVoiceState != currentVoiceState
&& (lastVoiceState == -1
|| (lastVoiceState == ServiceState.STATE_IN_SERVICE
@@ -829,7 +851,7 @@ public class MobileSignalController extends SignalController<MobileState, Mobile
}
void notifyWifiLevelChange(int level) {
- if (!mProviderModel) {
+ if (!mProviderModelBehavior) {
return;
}
mLastWlanLevel = level;
@@ -844,7 +866,7 @@ public class MobileSignalController extends SignalController<MobileState, Mobile
}
void notifyDefaultMobileLevelChange(int level) {
- if (!mProviderModel) {
+ if (!mProviderModelBehavior) {
return;
}
mLastWlanCrossSimLevel = level;
@@ -859,7 +881,7 @@ public class MobileSignalController extends SignalController<MobileState, Mobile
}
void notifyMobileLevelChangeIfNecessary(SignalStrength signalStrength) {
- if (!mProviderModel) {
+ if (!mProviderModelBehavior) {
return;
}
int newLevel = getSignalLevel(signalStrength);
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 06974b356914..740b52d332ce 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
@@ -51,7 +51,6 @@ import android.telephony.SubscriptionManager.OnSubscriptionsChangedListener;
import android.telephony.TelephonyCallback;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
-import android.util.FeatureFlagUtils;
import android.util.Log;
import android.util.MathUtils;
import android.util.SparseArray;
@@ -74,6 +73,7 @@ import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.demomode.DemoMode;
import com.android.systemui.demomode.DemoModeController;
import com.android.systemui.settings.CurrentUserTracker;
+import com.android.systemui.statusbar.FeatureFlags;
import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener;
import com.android.systemui.telephony.TelephonyListenerManager;
import com.android.systemui.util.CarrierConfigTracker;
@@ -123,9 +123,11 @@ public class NetworkControllerImpl extends BroadcastReceiver
private final BroadcastDispatcher mBroadcastDispatcher;
private final DemoModeController mDemoModeController;
private final Object mLock = new Object();
- private final boolean mProviderModel;
+ private final boolean mProviderModelBehavior;
+ private final boolean mProviderModelSetting;
private Config mConfig;
private final CarrierConfigTracker mCarrierConfigTracker;
+ private final FeatureFlags mFeatureFlags;
private TelephonyCallback.ActiveDataSubscriptionIdListener mPhoneStateListener;
private int mActiveMobileDataSubscription = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
@@ -220,7 +222,8 @@ public class NetworkControllerImpl extends BroadcastReceiver
NetworkScoreManager networkScoreManager,
AccessPointControllerImpl accessPointController,
DemoModeController demoModeController,
- CarrierConfigTracker carrierConfigTracker) {
+ CarrierConfigTracker carrierConfigTracker,
+ FeatureFlags featureFlags) {
this(context, connectivityManager,
telephonyManager,
telephonyListenerManager,
@@ -237,7 +240,8 @@ public class NetworkControllerImpl extends BroadcastReceiver
deviceProvisionedController,
broadcastDispatcher,
demoModeController,
- carrierConfigTracker);
+ carrierConfigTracker,
+ featureFlags);
mReceiverHandler.post(mRegisterListeners);
}
@@ -256,7 +260,9 @@ public class NetworkControllerImpl extends BroadcastReceiver
DeviceProvisionedController deviceProvisionedController,
BroadcastDispatcher broadcastDispatcher,
DemoModeController demoModeController,
- CarrierConfigTracker carrierConfigTracker) {
+ CarrierConfigTracker carrierConfigTracker,
+ FeatureFlags featureFlags
+ ) {
mContext = context;
mTelephonyListenerManager = telephonyListenerManager;
mConfig = config;
@@ -273,6 +279,7 @@ public class NetworkControllerImpl extends BroadcastReceiver
mHasMobileDataFeature = telephonyManager.isDataCapable();
mDemoModeController = demoModeController;
mCarrierConfigTracker = carrierConfigTracker;
+ mFeatureFlags = featureFlags;
// telephony
mPhone = telephonyManager;
@@ -293,7 +300,8 @@ public class NetworkControllerImpl extends BroadcastReceiver
}
});
mWifiSignalController = new WifiSignalController(mContext, mHasMobileDataFeature,
- mCallbackHandler, this, mWifiManager, mConnectivityManager, networkScoreManager);
+ mCallbackHandler, this, mWifiManager, mConnectivityManager, networkScoreManager,
+ mFeatureFlags);
mEthernetSignalController = new EthernetSignalController(mContext, mCallbackHandler, this);
@@ -422,8 +430,8 @@ public class NetworkControllerImpl extends BroadcastReceiver
};
mDemoModeController.addCallback(this);
- mProviderModel = FeatureFlagUtils.isEnabled(
- mContext, FeatureFlagUtils.SETTINGS_PROVIDER_MODEL);
+ mProviderModelBehavior = mFeatureFlags.isCombinedStatusBarSignalIconsEnabled();
+ mProviderModelSetting = mFeatureFlags.isProviderModelSettingEnabled();
}
private final Runnable mClearForceValidated = () -> {
@@ -697,7 +705,7 @@ public class NetworkControllerImpl extends BroadcastReceiver
cb.setIsAirplaneMode(new IconState(mAirplaneMode,
TelephonyIcons.FLIGHT_MODE_ICON, R.string.accessibility_airplane_mode, mContext));
cb.setNoSims(mHasNoSubs, mSimDetected);
- if (mProviderModel) {
+ if (mProviderModelBehavior) {
cb.setConnectivityStatus(mNoDefaultNetwork, !mInetCondition, mNoNetworksAvailable);
}
mWifiSignalController.notifyListeners(cb);
@@ -705,7 +713,7 @@ public class NetworkControllerImpl extends BroadcastReceiver
for (int i = 0; i < mMobileSignalControllers.size(); i++) {
MobileSignalController mobileSignalController = mMobileSignalControllers.valueAt(i);
mobileSignalController.notifyListeners(cb);
- if (mProviderModel) {
+ if (mProviderModelBehavior) {
mobileSignalController.refreshCallIndicator(cb);
}
}
@@ -806,7 +814,7 @@ public class NetworkControllerImpl extends BroadcastReceiver
for (int i = 0; i < mMobileSignalControllers.size(); i++) {
MobileSignalController controller = mMobileSignalControllers.valueAt(i);
controller.setConfiguration(mConfig);
- if (mProviderModel) {
+ if (mProviderModelBehavior) {
controller.refreshCallIndicator(mCallbackHandler);
}
}
@@ -922,7 +930,8 @@ public class NetworkControllerImpl extends BroadcastReceiver
MobileSignalController controller = new MobileSignalController(mContext, mConfig,
mHasMobileDataFeature, mPhone.createForSubscriptionId(subId),
mCallbackHandler, this, subscriptions.get(i),
- mSubDefaults, mReceiverHandler.getLooper(), mCarrierConfigTracker);
+ mSubDefaults, mReceiverHandler.getLooper(), mCarrierConfigTracker,
+ mFeatureFlags);
controller.setUserSetupComplete(mUserSetup);
mMobileSignalControllers.put(subId, controller);
if (subscriptions.get(i).getSimSlotIndex() == 0) {
@@ -1070,10 +1079,10 @@ public class NetworkControllerImpl extends BroadcastReceiver
|| mValidatedTransports.get(NetworkCapabilities.TRANSPORT_ETHERNET);
pushConnectivityToSignals();
- if (mProviderModel) {
+ if (mProviderModelBehavior) {
mNoDefaultNetwork = !mConnectedTransports.get(NetworkCapabilities.TRANSPORT_CELLULAR)
- && !mConnectedTransports.get(NetworkCapabilities.TRANSPORT_WIFI)
- && !mConnectedTransports.get(NetworkCapabilities.TRANSPORT_ETHERNET);
+ && !mConnectedTransports.get(NetworkCapabilities.TRANSPORT_WIFI)
+ && !mConnectedTransports.get(NetworkCapabilities.TRANSPORT_ETHERNET);
mCallbackHandler.setConnectivityStatus(mNoDefaultNetwork, !mInetCondition,
mNoNetworksAvailable);
for (int i = 0; i < mMobileSignalControllers.size(); i++) {
@@ -1081,6 +1090,13 @@ public class NetworkControllerImpl extends BroadcastReceiver
mobileSignalController.updateNoCallingState();
}
notifyAllListeners();
+ } else if (mProviderModelSetting) {
+ // TODO(b/191903788): Replace the flag name once the new flag is added.
+ mNoDefaultNetwork = !mConnectedTransports.get(NetworkCapabilities.TRANSPORT_CELLULAR)
+ && !mConnectedTransports.get(NetworkCapabilities.TRANSPORT_WIFI)
+ && !mConnectedTransports.get(NetworkCapabilities.TRANSPORT_ETHERNET);
+ mCallbackHandler.setConnectivityStatus(mNoDefaultNetwork, !mInetCondition,
+ mNoNetworksAvailable);
}
}
@@ -1388,7 +1404,8 @@ public class NetworkControllerImpl extends BroadcastReceiver
MobileSignalController controller = new MobileSignalController(mContext,
mConfig, mHasMobileDataFeature,
mPhone.createForSubscriptionId(info.getSubscriptionId()), mCallbackHandler, this,
- info, mSubDefaults, mReceiverHandler.getLooper(), mCarrierConfigTracker);
+ info, mSubDefaults, mReceiverHandler.getLooper(), mCarrierConfigTracker,
+ mFeatureFlags);
mMobileSignalControllers.put(id, controller);
controller.getState().userSetup = true;
return info;
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 e6c4e82cec7b..b18dfd2866c4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
@@ -20,7 +20,6 @@ import static android.view.WindowInsetsAnimation.Callback.DISPATCH_MODE_STOP;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
-import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.Notification;
import android.app.PendingIntent;
@@ -38,7 +37,6 @@ import android.graphics.Rect;
import android.graphics.drawable.GradientDrawable;
import android.net.Uri;
import android.os.Bundle;
-import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.UserHandle;
import android.text.Editable;
@@ -72,13 +70,13 @@ import android.widget.ProgressBar;
import android.widget.TextView;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import com.android.internal.graphics.ColorUtils;
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;
-import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.util.ContrastColorUtil;
import com.android.systemui.Dependency;
import com.android.systemui.R;
@@ -111,8 +109,8 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
private final SendButtonTextWatcher mTextWatcher;
private final TextView.OnEditorActionListener mEditorActionHandler;
- private final NotificationRemoteInputManager mRemoteInputManager;
private final UiEventLogger mUiEventLogger;
+ private final RemoteInputQuickSettingsDisabler mRemoteInputQuickSettingsDisabler;
private final List<OnFocusChangeListener> mEditTextFocusChangeListeners = new ArrayList<>();
private final List<OnSendRemoteInputListener> mOnSendListeners = new ArrayList<>();
private RemoteEditText mEditText;
@@ -123,9 +121,6 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
private RemoteInput[] mRemoteInputs;
private RemoteInput mRemoteInput;
private RemoteInputController mController;
- private RemoteInputQuickSettingsDisabler mRemoteInputQuickSettingsDisabler;
-
- private IStatusBarService mStatusBarManagerService;
private NotificationEntry mEntry;
@@ -134,7 +129,6 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
private int mRevealCx;
private int mRevealCy;
private int mRevealR;
- private ContentInfo mAttachment;
private boolean mColorized;
private int mTint;
@@ -143,7 +137,6 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
private NotificationViewWrapper mWrapper;
private Consumer<Boolean> mOnVisibilityChangedListener;
private NotificationRemoteInputManager.BouncerChecker mBouncerChecker;
- private LinearLayout mContentView;
private ImageView mDelete;
private ImageView mDeleteBg;
@@ -174,10 +167,7 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
mTextWatcher = new SendButtonTextWatcher();
mEditorActionHandler = new EditorActionHandler();
mRemoteInputQuickSettingsDisabler = Dependency.get(RemoteInputQuickSettingsDisabler.class);
- mRemoteInputManager = Dependency.get(NotificationRemoteInputManager.class);
mUiEventLogger = Dependency.get(UiEventLogger.class);
- mStatusBarManagerService = IStatusBarService.Stub.asInterface(
- ServiceManager.getService(Context.STATUS_BAR_SERVICE));
TypedArray ta = getContext().getTheme().obtainStyledAttributes(new int[]{
com.android.internal.R.attr.colorAccent,
com.android.internal.R.attr.colorSurface,
@@ -266,8 +256,8 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
mDeleteBg.setImageTintBlendMode(BlendMode.SRC_IN);
mDelete.setImageTintBlendMode(BlendMode.SRC_IN);
mDelete.setOnClickListener(v -> setAttachment(null));
- mContentView = findViewById(R.id.remote_input_content);
- mContentView.setBackground(mContentBackground);
+ LinearLayout contentView = findViewById(R.id.remote_input_content);
+ contentView.setBackground(mContentBackground);
mEditText = findViewById(R.id.remote_input_text);
mEditText.setInnerFocusable(false);
mEditText.setWindowInsetsAnimationCallback(
@@ -293,15 +283,19 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
}
private void setAttachment(ContentInfo item) {
- if (mAttachment != null) {
+ if (mEntry.remoteInputAttachment != null && mEntry.remoteInputAttachment != item) {
// We need to release permissions when sending the attachment to the target
// app or if it is deleted by the user. When sending to the target app, we
// can safely release permissions as soon as the call to
// `mController.grantInlineReplyUriPermission` is made (ie, after the grant
// to the target app has been created).
- mAttachment.releasePermissions();
+ mEntry.remoteInputAttachment.releasePermissions();
+ }
+ mEntry.remoteInputAttachment = item;
+ if (item != null) {
+ mEntry.remoteInputUri = item.getClip().getItemAt(0).getUri();
+ mEntry.remoteInputMimeType = item.getClip().getDescription().getMimeType(0);
}
- mAttachment = item;
View attachment = findViewById(R.id.remote_input_content_container);
ImageView iconView = findViewById(R.id.remote_input_attachment_image);
iconView.setImageDrawable(null);
@@ -323,10 +317,9 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
* @return returns intent with granted URI permissions that should be used immediately
*/
private Intent prepareRemoteInput() {
- if (mAttachment == null) return prepareRemoteInputFromText();
- return prepareRemoteInputFromData(
- mAttachment.getClip().getDescription().getMimeType(0),
- mAttachment.getClip().getItemAt(0).getUri());
+ return mEntry.remoteInputAttachment == null
+ ? prepareRemoteInputFromText()
+ : prepareRemoteInputFromData(mEntry.remoteInputMimeType, mEntry.remoteInputUri);
}
private Intent prepareRemoteInputFromText() {
@@ -337,7 +330,7 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
results);
mEntry.remoteInputText = mEditText.getText().toString();
- // TODO(b/188646667): store attachment to entry
+ setAttachment(null);
mEntry.remoteInputUri = null;
mEntry.remoteInputMimeType = null;
@@ -363,7 +356,8 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
RemoteInput.addResultsToIntent(mRemoteInputs, fillInIntent,
bundle);
- CharSequence attachmentText = mAttachment.getClip().getDescription().getLabel();
+ CharSequence attachmentText =
+ mEntry.remoteInputAttachment.getClip().getDescription().getLabel();
CharSequence attachmentLabel = TextUtils.isEmpty(attachmentText)
? mContext.getString(R.string.remote_input_image_insertion_text)
@@ -374,14 +368,11 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
: "\"" + attachmentLabel + "\" " + mEditText.getText();
mEntry.remoteInputText = fullText;
- // TODO(b/188646667): store attachment to entry
- mEntry.remoteInputMimeType = contentType;
- mEntry.remoteInputUri = data;
// mirror prepareRemoteInputFromText for text input
if (mEntry.editedSuggestionInfo == null) {
RemoteInput.setResultsSource(fillInIntent, RemoteInput.SOURCE_FREE_FORM_INPUT);
- } else if (mAttachment == null) {
+ } else if (mEntry.remoteInputAttachment == null) {
RemoteInput.setResultsSource(fillInIntent, RemoteInput.SOURCE_CHOICE);
}
@@ -437,6 +428,7 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
mEntry.getSbn().getUid(), mEntry.getSbn().getPackageName(),
mEntry.getSbn().getInstanceId());
}
+
setAttachment(null);
}
@@ -477,7 +469,6 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
private void onDefocus(boolean animate, boolean logClose) {
mController.removeRemoteInput(mEntry, mToken);
mEntry.remoteInputText = mEditText.getText();
- // TODO(b/188646667): store attachment to entry
// During removal, we get reattached and lose focus. Not hiding in that
// case to prevent flicker.
@@ -565,7 +556,7 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
mEntry.editedSuggestionInfo = editedSuggestionInfo;
if (editedSuggestionInfo != null) {
mEntry.remoteInputText = editedSuggestionInfo.originalText;
- // TODO(b/188646667): store attachment to entry
+ mEntry.remoteInputAttachment = null;
}
}
@@ -608,7 +599,7 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
mEditText.setSelection(mEditText.length());
mEditText.requestFocus();
mController.addRemoteInput(mEntry, mToken);
- // TODO(b/188646667): restore attachment from entry
+ setAttachment(mEntry.remoteInputAttachment);
mRemoteInputQuickSettingsDisabler.setRemoteInputActive(true);
@@ -631,7 +622,6 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
private void reset() {
mResetting = true;
mEntry.remoteInputTextWhenReset = SpannedString.valueOf(mEditText.getText());
- // TODO(b/188646667): store attachment at time of reset to entry
mEditText.getText().clear();
mEditText.setEnabled(true);
@@ -640,7 +630,7 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
mController.removeSpinning(mEntry.getKey(), mToken);
updateSendButton();
onDefocus(false /* animate */, false /* logClose */);
- // TODO(b/188646667): clear attachment
+ setAttachment(null);
mResetting = false;
}
@@ -657,7 +647,7 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
}
private void updateSendButton() {
- mSendButton.setEnabled(mEditText.length() != 0 || mAttachment != null);
+ mSendButton.setEnabled(mEditText.length() != 0 || mEntry.remoteInputAttachment != null);
}
public void close() {
@@ -857,7 +847,7 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
&& event.getAction() == KeyEvent.ACTION_DOWN;
if (isSoftImeEvent || isKeyboardEnterKey) {
- if (mEditText.length() > 0 || mAttachment != null) {
+ if (mEditText.length() > 0 || mEntry.remoteInputAttachment != null) {
sendRemoteInput(prepareRemoteInput());
}
// Consume action to prevent IME from closing.
@@ -928,7 +918,6 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
// our focus, so we'll need to save our text here.
if (mRemoteInputView != null) {
mRemoteInputView.mEntry.remoteInputText = getText();
- // TODO(b/188646667): store attachment to entry
}
}
return;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityController.java
index e76b8035cd59..2a93844acd5b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityController.java
@@ -28,6 +28,8 @@ public interface SecurityController extends CallbackController<SecurityControlle
boolean isDeviceManaged();
boolean hasProfileOwner();
boolean hasWorkProfile();
+ /** Whether the work profile is turned on. */
+ boolean isWorkProfileOn();
/** Whether this device is organization-owned with a work profile **/
boolean isProfileOwnerOfOrganizationOwnedDevice();
String getDeviceOwnerName();
@@ -57,7 +59,6 @@ public interface SecurityController extends CallbackController<SecurityControlle
/** Label for admin */
CharSequence getLabel(DeviceAdminInfo info);
-
public interface SecurityControllerCallback {
void onStateChanged();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
index 4afb86b1a810..3e661df802a6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
@@ -211,6 +211,12 @@ public class SecurityControllerImpl extends CurrentUserTracker implements Securi
}
@Override
+ public boolean isWorkProfileOn() {
+ final UserHandle userHandle = UserHandle.of(getWorkProfileUserId(mCurrentUserId));
+ return userHandle != null && !mUserManager.isQuietModeEnabled(userHandle);
+ }
+
+ @Override
public boolean isProfileOwnerOfOrganizationOwnedDevice() {
return mDevicePolicyManager.isOrganizationOwnedDeviceWithManagedProfile();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
index 1ebb9ddc0262..e2b6895e7039 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
@@ -671,6 +671,7 @@ public class UserSwitcherController implements Dumpable {
scheduleGuestCreation();
}
switchToUserId(targetUserId);
+ mUserManager.removeUser(currentUser.id);
}
} catch (RemoteException e) {
Log.e(TAG, "Couldn't remove guest because ActivityManager or WindowManager is dead");
@@ -1012,15 +1013,16 @@ public class UserSwitcherController implements Dumpable {
public ExitGuestDialog(Context context, int guestId, int targetId) {
super(context);
- setTitle(mGuestUserAutoCreated ? R.string.guest_reset_guest_dialog_title
+ setTitle(mGuestUserAutoCreated
+ ? com.android.settingslib.R.string.guest_reset_guest_dialog_title
: R.string.guest_exit_guest_dialog_title);
setMessage(context.getString(R.string.guest_exit_guest_dialog_message));
setButton(DialogInterface.BUTTON_NEGATIVE,
context.getString(android.R.string.cancel), this);
setButton(DialogInterface.BUTTON_POSITIVE,
- context.getString(
- mGuestUserAutoCreated ? R.string.guest_reset_guest_dialog_remove
- : R.string.guest_exit_guest_dialog_remove), this);
+ context.getString(mGuestUserAutoCreated
+ ? com.android.settingslib.R.string.guest_reset_guest_confirm_button
+ : R.string.guest_exit_guest_dialog_remove), this);
SystemUIDialog.setWindowOnTop(this);
setCanceledOnTouchOutside(false);
mGuestId = guestId;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java
index ea103e6f02a8..cb96bd80a90c 100755
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java
@@ -27,7 +27,6 @@ import android.net.NetworkScoreManager;
import android.net.wifi.WifiManager;
import android.text.Html;
import android.text.TextUtils;
-import android.util.FeatureFlagUtils;
import com.android.internal.annotations.VisibleForTesting;
import com.android.settingslib.AccessibilityContentDescriptions;
@@ -38,6 +37,7 @@ import com.android.settingslib.graph.SignalDrawable;
import com.android.settingslib.mobile.TelephonyIcons;
import com.android.settingslib.wifi.WifiStatusTracker;
import com.android.systemui.R;
+import com.android.systemui.statusbar.FeatureFlags;
import com.android.systemui.statusbar.policy.NetworkController.IconState;
import com.android.systemui.statusbar.policy.NetworkController.MobileDataIndicators;
import com.android.systemui.statusbar.policy.NetworkController.SignalCallback;
@@ -53,17 +53,22 @@ public class WifiSignalController extends
private final IconGroup mUnmergedWifiIconGroup = WifiIcons.UNMERGED_WIFI;
private final MobileIconGroup mCarrierMergedWifiIconGroup = TelephonyIcons.CARRIER_MERGED_WIFI;
private final WifiManager mWifiManager;
- private final boolean mProviderModel;
+ private final boolean mProviderModelSetting;
private final IconGroup mDefaultWifiIconGroup;
private final IconGroup mWifi4IconGroup;
private final IconGroup mWifi5IconGroup;
private final IconGroup mWifi6IconGroup;
- public WifiSignalController(Context context, boolean hasMobileDataFeature,
- CallbackHandler callbackHandler, NetworkControllerImpl networkController,
- WifiManager wifiManager, ConnectivityManager connectivityManager,
- NetworkScoreManager networkScoreManager) {
+ public WifiSignalController(
+ Context context,
+ boolean hasMobileDataFeature,
+ CallbackHandler callbackHandler,
+ NetworkControllerImpl networkController,
+ WifiManager wifiManager,
+ ConnectivityManager connectivityManager,
+ NetworkScoreManager networkScoreManager,
+ FeatureFlags featureFlags) {
super("WifiSignalController", context, NetworkCapabilities.TRANSPORT_WIFI,
callbackHandler, networkController);
mWifiManager = wifiManager;
@@ -125,8 +130,8 @@ public class WifiSignalController extends
);
mCurrentState.iconGroup = mLastState.iconGroup = mDefaultWifiIconGroup;
- mProviderModel = FeatureFlagUtils.isEnabled(
- mContext, FeatureFlagUtils.SETTINGS_PROVIDER_MODEL);
+ mProviderModelSetting = featureFlags.isProviderModelSettingEnabled();
+
}
@Override
@@ -163,7 +168,7 @@ public class WifiSignalController extends
if (mCurrentState.inetCondition == 0) {
contentDescription += ("," + mContext.getString(R.string.data_connection_no_internet));
}
- if (mProviderModel) {
+ if (mProviderModelSetting) {
IconState statusIcon = new IconState(
wifiVisible, getCurrentIconId(), contentDescription);
IconState qsIcon = null;
diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java
index e0ff88bdbfce..843630b35e17 100644
--- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java
+++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java
@@ -239,6 +239,14 @@ public class ThemeOverlayApplier implements Dumpable {
+ category + ": " + enabled);
}
+ OverlayInfo overlayInfo = mOverlayManager.getOverlayInfo(identifier,
+ UserHandle.of(currentUser));
+ if (overlayInfo == null) {
+ Log.i(TAG, "Won't enable " + identifier + ", it doesn't exist for user"
+ + currentUser);
+ return;
+ }
+
transaction.setEnabled(identifier, enabled, currentUser);
if (currentUser != UserHandle.SYSTEM.getIdentifier()
&& SYSTEM_USER_CATEGORIES.contains(category)) {
@@ -247,7 +255,7 @@ public class ThemeOverlayApplier implements Dumpable {
// Do not apply Launcher or Theme picker overlays to managed users. Apps are not
// installed in there.
- OverlayInfo overlayInfo = mOverlayManager.getOverlayInfo(identifier, UserHandle.SYSTEM);
+ overlayInfo = mOverlayManager.getOverlayInfo(identifier, UserHandle.SYSTEM);
if (overlayInfo == null || overlayInfo.targetPackageName.equals(mLauncherPackage)
|| overlayInfo.targetPackageName.equals(mThemePickerPackage)) {
return;
diff --git a/packages/SystemUI/src/com/android/systemui/toast/SystemUIToast.java b/packages/SystemUI/src/com/android/systemui/toast/SystemUIToast.java
index c5e35a497956..8b394bfe35b7 100644
--- a/packages/SystemUI/src/com/android/systemui/toast/SystemUIToast.java
+++ b/packages/SystemUI/src/com/android/systemui/toast/SystemUIToast.java
@@ -16,13 +16,18 @@
package com.android.systemui.toast;
+import static android.content.pm.ApplicationInfo.FLAG_SYSTEM;
+import static android.content.pm.ApplicationInfo.FLAG_UPDATED_SYSTEM_APP;
+
import android.animation.Animator;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.UserIdInt;
import android.app.Application;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Bitmap;
@@ -53,7 +58,7 @@ public class SystemUIToast implements ToastPlugin.Toast {
final ToastPlugin.Toast mPluginToast;
private final String mPackageName;
- private final int mUserId;
+ @UserIdInt private final int mUserId;
private final LayoutInflater mLayoutInflater;
final int mDefaultX = 0;
@@ -74,7 +79,7 @@ public class SystemUIToast implements ToastPlugin.Toast {
}
SystemUIToast(LayoutInflater layoutInflater, Context context, CharSequence text,
- ToastPlugin.Toast pluginToast, String packageName, int userId,
+ ToastPlugin.Toast pluginToast, String packageName, @UserIdInt int userId,
int orientation) {
mLayoutInflater = layoutInflater;
mContext = context;
@@ -248,6 +253,15 @@ public class SystemUIToast implements ToastPlugin.Toast {
return null;
}
+ final Context userContext;
+ try {
+ userContext = context.createPackageContextAsUser("android",
+ 0, new UserHandle(userId));
+ } catch (NameNotFoundException e) {
+ Log.e(TAG, "Could not create user package context");
+ return null;
+ }
+
final ApplicationsState appState =
ApplicationsState.getInstance((Application) context.getApplicationContext());
if (!appState.isUserAdded(userId)) {
@@ -255,9 +269,11 @@ public class SystemUIToast implements ToastPlugin.Toast {
+ "packageName=" + packageName);
return null;
}
+
+ final PackageManager packageManager = userContext.getPackageManager();
final AppEntry appEntry = appState.getEntry(packageName, userId);
if (appEntry == null || appEntry.info == null
- || !ApplicationsState.FILTER_DOWNLOADED_AND_LAUNCHER.filterApp(appEntry)) {
+ || !showApplicationIcon(appEntry.info, packageManager)) {
return null;
}
@@ -265,7 +281,20 @@ public class SystemUIToast implements ToastPlugin.Toast {
UserHandle user = UserHandle.getUserHandleForUid(appInfo.uid);
IconFactory iconFactory = IconFactory.obtain(context);
Bitmap iconBmp = iconFactory.createBadgedIconBitmap(
- appInfo.loadUnbadgedIcon(context.getPackageManager()), user, true).icon;
+ appInfo.loadUnbadgedIcon(packageManager), user, true).icon;
return new BitmapDrawable(context.getResources(), iconBmp);
}
+
+ private static boolean showApplicationIcon(ApplicationInfo appInfo,
+ PackageManager packageManager) {
+ if (hasFlag(appInfo.flags, FLAG_UPDATED_SYSTEM_APP)) {
+ return packageManager.getLaunchIntentForPackage(appInfo.packageName)
+ != null;
+ }
+ return !hasFlag(appInfo.flags, FLAG_SYSTEM);
+ }
+
+ private static boolean hasFlag(int flags, int flag) {
+ return (flags & flag) != 0;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/util/RoundedCornerProgressDrawable.kt b/packages/SystemUI/src/com/android/systemui/util/RoundedCornerProgressDrawable.kt
index dc86d5893adb..3b64f9f953c3 100644
--- a/packages/SystemUI/src/com/android/systemui/util/RoundedCornerProgressDrawable.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/RoundedCornerProgressDrawable.kt
@@ -16,6 +16,7 @@
package com.android.systemui.util
+import android.content.pm.ActivityInfo
import android.content.res.Resources
import android.graphics.Rect
import android.graphics.drawable.Drawable
@@ -64,6 +65,10 @@ class RoundedCornerProgressDrawable @JvmOverloads constructor(
return RoundedCornerState(super.getConstantState()!!)
}
+ override fun getChangingConfigurations(): Int {
+ return super.getChangingConfigurations() or ActivityInfo.CONFIG_DENSITY
+ }
+
private class RoundedCornerState(private val wrappedState: ConstantState) : ConstantState() {
override fun newDrawable(): Drawable {
return newDrawable(null, null)
diff --git a/packages/SystemUI/src/com/android/systemui/util/sensors/SensorModule.java b/packages/SystemUI/src/com/android/systemui/util/sensors/SensorModule.java
index 7f3756244629..11e7df8bd85f 100644
--- a/packages/SystemUI/src/com/android/systemui/util/sensors/SensorModule.java
+++ b/packages/SystemUI/src/com/android/systemui/util/sensors/SensorModule.java
@@ -36,12 +36,13 @@ public class SensorModule {
try {
return thresholdSensorBuilder
.setSensorDelay(SensorManager.SENSOR_DELAY_NORMAL)
- .setSensorResourceId(R.string.proximity_sensor_type)
+ .setSensorResourceId(R.string.proximity_sensor_type, true)
.setThresholdResourceId(R.dimen.proximity_sensor_threshold)
.setThresholdLatchResourceId(R.dimen.proximity_sensor_threshold_latch)
.build();
} catch (IllegalStateException e) {
- Sensor defaultSensor = sensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY);
+ Sensor defaultSensor = sensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY,
+ true);
return thresholdSensorBuilder
.setSensor(defaultSensor)
.setThresholdValue(defaultSensor != null ? defaultSensor.getMaximumRange() : 0)
@@ -55,7 +56,7 @@ public class SensorModule {
ThresholdSensorImpl.Builder thresholdSensorBuilder) {
try {
return thresholdSensorBuilder
- .setSensorResourceId(R.string.proximity_sensor_secondary_type)
+ .setSensorResourceId(R.string.proximity_sensor_secondary_type, true)
.setThresholdResourceId(R.dimen.proximity_sensor_secondary_threshold)
.setThresholdLatchResourceId(R.dimen.proximity_sensor_secondary_threshold_latch)
.build();
diff --git a/packages/SystemUI/src/com/android/systemui/util/sensors/ThresholdSensorImpl.java b/packages/SystemUI/src/com/android/systemui/util/sensors/ThresholdSensorImpl.java
index 31c307297066..d10cf9b180c3 100644
--- a/packages/SystemUI/src/com/android/systemui/util/sensors/ThresholdSensorImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/util/sensors/ThresholdSensorImpl.java
@@ -230,14 +230,16 @@ class ThresholdSensorImpl implements ThresholdSensor {
mExecution = execution;
}
-
Builder setSensorDelay(int sensorDelay) {
mSensorDelay = sensorDelay;
return this;
}
-
- Builder setSensorResourceId(int sensorResourceId) {
- setSensorType(mResources.getString(sensorResourceId));
+ /**
+ * If requiresWakeUp is false, the first sensor with sensorType (regardless of whether the
+ * sensor is a wakeup sensor or not) will be set.
+ */
+ Builder setSensorResourceId(int sensorResourceId, boolean requireWakeUp) {
+ setSensorType(mResources.getString(sensorResourceId), requireWakeUp);
return this;
}
@@ -259,8 +261,12 @@ class ThresholdSensorImpl implements ThresholdSensor {
return this;
}
- Builder setSensorType(String sensorType) {
- Sensor sensor = findSensorByType(sensorType);
+ /**
+ * If requiresWakeUp is false, the first sensor with sensorType (regardless of whether the
+ * sensor is a wakeup sensor or not) will be set.
+ */
+ Builder setSensorType(String sensorType, boolean requireWakeUp) {
+ Sensor sensor = findSensorByType(sensorType, requireWakeUp);
if (sensor != null) {
setSensor(sensor);
}
@@ -310,7 +316,8 @@ class ThresholdSensorImpl implements ThresholdSensor {
mThresholdValue, mThresholdLatchValue, mSensorDelay);
}
- private Sensor findSensorByType(String sensorType) {
+ @VisibleForTesting
+ Sensor findSensorByType(String sensorType, boolean requireWakeUp) {
if (sensorType.isEmpty()) {
return null;
}
@@ -320,7 +327,9 @@ class ThresholdSensorImpl implements ThresholdSensor {
for (Sensor s : sensorList) {
if (sensorType.equals(s.getStringType())) {
sensor = s;
- break;
+ if (!requireWakeUp || sensor.isWakeUpSensor()) {
+ break;
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
index ab4b1f10132c..e57059894786 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
@@ -128,7 +128,7 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa
private final MediaSessions mMediaSessions;
protected C mCallbacks = new C();
private final State mState = new State();
- protected final MediaSessionsCallbacks mMediaSessionsCallbacksW = new MediaSessionsCallbacks();
+ protected final MediaSessionsCallbacks mMediaSessionsCallbacksW;
private final Optional<Vibrator> mVibrator;
private final boolean mHasVibrator;
private boolean mShowA11yStream;
@@ -179,6 +179,7 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa
mWorkerLooper = theadFactory.buildLooperOnNewThread(
VolumeDialogControllerImpl.class.getSimpleName());
mWorker = new W(mWorkerLooper);
+ mMediaSessionsCallbacksW = new MediaSessionsCallbacks(mContext);
mMediaSessions = createMediaSessions(mContext, mWorkerLooper, mMediaSessionsCallbacksW);
mAudio = audioManager;
mNoMan = notificationManager;
@@ -1148,83 +1149,98 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa
private final HashMap<Token, Integer> mRemoteStreams = new HashMap<>();
private int mNextStream = DYNAMIC_STREAM_START_INDEX;
+ private final boolean mShowRemoteSessions;
+
+ public MediaSessionsCallbacks(Context context) {
+ mShowRemoteSessions = context.getResources().getBoolean(
+ com.android.internal.R.bool.config_volumeShowRemoteSessions);
+ }
@Override
public void onRemoteUpdate(Token token, String name, PlaybackInfo pi) {
- addStream(token, "onRemoteUpdate");
+ if (mShowRemoteSessions) {
+ addStream(token, "onRemoteUpdate");
- int stream = 0;
- synchronized (mRemoteStreams) {
- stream = mRemoteStreams.get(token);
- }
- Slog.d(TAG, "onRemoteUpdate: stream: " + stream + " volume: " + pi.getCurrentVolume());
- boolean changed = mState.states.indexOfKey(stream) < 0;
- final StreamState ss = streamStateW(stream);
- ss.dynamic = true;
- ss.levelMin = 0;
- ss.levelMax = pi.getMaxVolume();
- if (ss.level != pi.getCurrentVolume()) {
- ss.level = pi.getCurrentVolume();
- changed = true;
- }
- if (!Objects.equals(ss.remoteLabel, name)) {
- ss.name = -1;
- ss.remoteLabel = name;
- changed = true;
- }
- if (changed) {
- Log.d(TAG, "onRemoteUpdate: " + name + ": " + ss.level + " of " + ss.levelMax);
- mCallbacks.onStateChanged(mState);
+ int stream = 0;
+ synchronized (mRemoteStreams) {
+ stream = mRemoteStreams.get(token);
+ }
+ Slog.d(TAG,
+ "onRemoteUpdate: stream: " + stream + " volume: " + pi.getCurrentVolume());
+ boolean changed = mState.states.indexOfKey(stream) < 0;
+ final StreamState ss = streamStateW(stream);
+ ss.dynamic = true;
+ ss.levelMin = 0;
+ ss.levelMax = pi.getMaxVolume();
+ if (ss.level != pi.getCurrentVolume()) {
+ ss.level = pi.getCurrentVolume();
+ changed = true;
+ }
+ if (!Objects.equals(ss.remoteLabel, name)) {
+ ss.name = -1;
+ ss.remoteLabel = name;
+ changed = true;
+ }
+ if (changed) {
+ Log.d(TAG, "onRemoteUpdate: " + name + ": " + ss.level + " of " + ss.levelMax);
+ mCallbacks.onStateChanged(mState);
+ }
}
}
@Override
public void onRemoteVolumeChanged(Token token, int flags) {
- addStream(token, "onRemoteVolumeChanged");
- int stream = 0;
- synchronized (mRemoteStreams) {
- stream = mRemoteStreams.get(token);
- }
- final boolean showUI = shouldShowUI(flags);
- Slog.d(TAG, "onRemoteVolumeChanged: stream: " + stream + " showui? " + showUI);
- boolean changed = updateActiveStreamW(stream);
- if (showUI) {
- changed |= checkRoutedToBluetoothW(AudioManager.STREAM_MUSIC);
- }
- if (changed) {
- Slog.d(TAG, "onRemoteChanged: updatingState");
- mCallbacks.onStateChanged(mState);
- }
- if (showUI) {
- mCallbacks.onShowRequested(Events.SHOW_REASON_REMOTE_VOLUME_CHANGED);
+ if (mShowRemoteSessions) {
+ addStream(token, "onRemoteVolumeChanged");
+ int stream = 0;
+ synchronized (mRemoteStreams) {
+ stream = mRemoteStreams.get(token);
+ }
+ final boolean showUI = shouldShowUI(flags);
+ Slog.d(TAG, "onRemoteVolumeChanged: stream: " + stream + " showui? " + showUI);
+ boolean changed = updateActiveStreamW(stream);
+ if (showUI) {
+ changed |= checkRoutedToBluetoothW(AudioManager.STREAM_MUSIC);
+ }
+ if (changed) {
+ Slog.d(TAG, "onRemoteChanged: updatingState");
+ mCallbacks.onStateChanged(mState);
+ }
+ if (showUI) {
+ mCallbacks.onShowRequested(Events.SHOW_REASON_REMOTE_VOLUME_CHANGED);
+ }
}
}
@Override
public void onRemoteRemoved(Token token) {
- int stream = 0;
- synchronized (mRemoteStreams) {
- if (!mRemoteStreams.containsKey(token)) {
- Log.d(TAG, "onRemoteRemoved: stream doesn't exist, "
- + "aborting remote removed for token:" + token.toString());
- return;
+ if (mShowRemoteSessions) {
+ int stream = 0;
+ synchronized (mRemoteStreams) {
+ if (!mRemoteStreams.containsKey(token)) {
+ Log.d(TAG, "onRemoteRemoved: stream doesn't exist, "
+ + "aborting remote removed for token:" + token.toString());
+ return;
+ }
+ stream = mRemoteStreams.get(token);
}
- stream = mRemoteStreams.get(token);
- }
- mState.states.remove(stream);
- if (mState.activeStream == stream) {
- updateActiveStreamW(-1);
+ mState.states.remove(stream);
+ if (mState.activeStream == stream) {
+ updateActiveStreamW(-1);
+ }
+ mCallbacks.onStateChanged(mState);
}
- mCallbacks.onStateChanged(mState);
}
public void setStreamVolume(int stream, int level) {
- final Token t = findToken(stream);
- if (t == null) {
- Log.w(TAG, "setStreamVolume: No token found for stream: " + stream);
- return;
+ if (mShowRemoteSessions) {
+ final Token t = findToken(stream);
+ if (t == null) {
+ Log.w(TAG, "setStreamVolume: No token found for stream: " + stream);
+ return;
+ }
+ mMediaSessions.setVolume(t, level);
}
- mMediaSessions.setVolume(t, level);
}
private Token findToken(int stream) {
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index 407b248cee44..5de7846a820e 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -1127,7 +1127,12 @@ public class VolumeDialogImpl implements VolumeDialog,
.alpha(0.f)
.setStartDelay(0)
.setDuration(mDialogHideAnimationDurationMs)
- .withEndAction(() -> mODICaptionsTooltipView.setVisibility(INVISIBLE))
+ .withEndAction(() -> {
+ // It might have been nulled out by tryToRemoveCaptionsTooltip.
+ if (mODICaptionsTooltipView != null) {
+ mODICaptionsTooltipView.setVisibility(INVISIBLE);
+ }
+ })
.start();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/wallet/controller/QuickAccessWalletController.java b/packages/SystemUI/src/com/android/systemui/wallet/controller/QuickAccessWalletController.java
index 65f236b77a64..0ecc4e25047f 100644
--- a/packages/SystemUI/src/com/android/systemui/wallet/controller/QuickAccessWalletController.java
+++ b/packages/SystemUI/src/com/android/systemui/wallet/controller/QuickAccessWalletController.java
@@ -25,6 +25,7 @@ import android.provider.Settings;
import android.service.quickaccesswallet.GetWalletCardsRequest;
import android.service.quickaccesswallet.QuickAccessWalletClient;
import android.service.quickaccesswallet.QuickAccessWalletClientImpl;
+import android.util.Log;
import com.android.systemui.R;
import com.android.systemui.dagger.SysUISingleton;
@@ -142,6 +143,10 @@ public class QuickAccessWalletController {
*/
public void queryWalletCards(
QuickAccessWalletClient.OnWalletCardsRetrievedCallback cardsRetriever) {
+ if (!mQuickAccessWalletClient.isWalletFeatureAvailable()) {
+ Log.d(TAG, "QuickAccessWallet feature is not available.");
+ return;
+ }
int cardWidth =
mContext.getResources().getDimensionPixelSize(R.dimen.wallet_tile_card_view_width);
int cardHeight =
diff --git a/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletActivity.java b/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletActivity.java
index 2dcc43cf60dc..2b4b49b82df1 100644
--- a/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletActivity.java
@@ -34,10 +34,12 @@ import android.widget.Toolbar;
import androidx.annotation.NonNull;
+import com.android.internal.logging.UiEventLogger;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.KeyguardUpdateMonitorCallback;
import com.android.settingslib.Utils;
import com.android.systemui.R;
+import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.plugins.ActivityStarter;
@@ -65,9 +67,11 @@ public class WalletActivity extends LifecycleActivity implements
private final Executor mExecutor;
private final Handler mHandler;
private final FalsingManager mFalsingManager;
+ private FalsingCollector mFalsingCollector;
private final UserTracker mUserTracker;
private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
private final StatusBarKeyguardViewManager mKeyguardViewManager;
+ private final UiEventLogger mUiEventLogger;
private KeyguardUpdateMonitorCallback mKeyguardUpdateMonitorCallback;
private WalletScreenController mWalletScreenController;
@@ -82,18 +86,22 @@ public class WalletActivity extends LifecycleActivity implements
@Background Executor executor,
@Main Handler handler,
FalsingManager falsingManager,
+ FalsingCollector falsingCollector,
UserTracker userTracker,
KeyguardUpdateMonitor keyguardUpdateMonitor,
- StatusBarKeyguardViewManager keyguardViewManager) {
+ StatusBarKeyguardViewManager keyguardViewManager,
+ UiEventLogger uiEventLogger) {
mKeyguardStateController = keyguardStateController;
mKeyguardDismissUtil = keyguardDismissUtil;
mActivityStarter = activityStarter;
mExecutor = executor;
mHandler = handler;
mFalsingManager = falsingManager;
+ mFalsingCollector = falsingCollector;
mUserTracker = userTracker;
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
mKeyguardViewManager = keyguardViewManager;
+ mUiEventLogger = uiEventLogger;
}
@Override
@@ -125,7 +133,8 @@ public class WalletActivity extends LifecycleActivity implements
mUserTracker,
mFalsingManager,
mKeyguardUpdateMonitor,
- mKeyguardStateController);
+ mKeyguardStateController,
+ mUiEventLogger);
mKeyguardUpdateMonitorCallback = new KeyguardUpdateMonitorCallback() {
@Override
public void onBiometricRunningStateChanged(
@@ -136,7 +145,8 @@ public class WalletActivity extends LifecycleActivity implements
}
};
- walletView.getAppButton().setOnClickListener(
+ walletView.setFalsingCollector(mFalsingCollector);
+ walletView.setShowWalletAppOnClickListener(
v -> {
if (mWalletClient.createWalletIntent() == null) {
Log.w(TAG, "Unable to create wallet app intent.");
@@ -148,11 +158,14 @@ public class WalletActivity extends LifecycleActivity implements
}
if (mKeyguardStateController.isUnlocked()) {
+ mUiEventLogger.log(WalletUiEvent.QAW_SHOW_ALL);
mActivityStarter.startActivity(
mWalletClient.createWalletIntent(), true);
finish();
} else {
+ mUiEventLogger.log(WalletUiEvent.QAW_UNLOCK_FROM_SHOW_ALL_BUTTON);
mKeyguardDismissUtil.executeWhenUnlocked(() -> {
+ mUiEventLogger.log(WalletUiEvent.QAW_SHOW_ALL);
mActivityStarter.startActivity(
mWalletClient.createWalletIntent(), true);
finish();
@@ -170,6 +183,7 @@ public class WalletActivity extends LifecycleActivity implements
return;
}
+ mUiEventLogger.log(WalletUiEvent.QAW_UNLOCK_FROM_UNLOCK_BUTTON);
mKeyguardDismissUtil.executeWhenUnlocked(() -> false, false,
false);
});
diff --git a/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletScreenController.java b/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletScreenController.java
index ab8ad7779689..2e183b38a7dc 100644
--- a/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletScreenController.java
+++ b/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletScreenController.java
@@ -39,6 +39,7 @@ import android.widget.FrameLayout;
import androidx.annotation.NonNull;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.logging.UiEventLogger;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.R;
import com.android.systemui.plugins.ActivityStarter;
@@ -74,6 +75,7 @@ public class WalletScreenController implements
private final WalletView mWalletView;
private final WalletCardCarousel mCardCarousel;
private final FalsingManager mFalsingManager;
+ private final UiEventLogger mUiEventLogger;
@VisibleForTesting String mSelectedCardId;
@VisibleForTesting boolean mIsDismissed;
@@ -88,7 +90,8 @@ public class WalletScreenController implements
UserTracker userTracker,
FalsingManager falsingManager,
KeyguardUpdateMonitor keyguardUpdateMonitor,
- KeyguardStateController keyguardStateController) {
+ KeyguardStateController keyguardStateController,
+ UiEventLogger uiEventLogger) {
mContext = context;
mWalletClient = walletClient;
mActivityStarter = activityStarter;
@@ -97,6 +100,7 @@ public class WalletScreenController implements
mFalsingManager = falsingManager;
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
mKeyguardStateController = keyguardStateController;
+ mUiEventLogger = uiEventLogger;
mPrefs = userTracker.getUserContext().getSharedPreferences(TAG, Context.MODE_PRIVATE);
mWalletView = walletView;
mWalletView.setMinimumHeight(getExpectedMinHeight());
@@ -147,6 +151,7 @@ public class WalletScreenController implements
isUdfpsEnabled);
}
}
+ mUiEventLogger.log(WalletUiEvent.QAW_IMPRESSION);
removeMinHeightAndRecordHeightOnLayout();
});
}
@@ -180,6 +185,9 @@ public class WalletScreenController implements
if (mIsDismissed) {
return;
}
+ if (mSelectedCardId != null && !mSelectedCardId.equals(card.getCardId())) {
+ mUiEventLogger.log(WalletUiEvent.QAW_CHANGE_CARD);
+ }
mSelectedCardId = card.getCardId();
selectCard();
}
@@ -209,6 +217,12 @@ public class WalletScreenController implements
|| ((QAWalletCardViewInfo) cardInfo).mWalletCard.getPendingIntent() == null) {
return;
}
+
+ if (!mKeyguardStateController.isUnlocked()) {
+ mUiEventLogger.log(WalletUiEvent.QAW_UNLOCK_FROM_CARD_CLICK);
+ }
+ mUiEventLogger.log(WalletUiEvent.QAW_CLICK_CARD);
+
mActivityStarter.startActivity(
((QAWalletCardViewInfo) cardInfo).mWalletCard.getPendingIntent().getIntent(), true);
}
diff --git a/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletUiEvent.java b/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletUiEvent.java
new file mode 100644
index 000000000000..da3a5c619446
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletUiEvent.java
@@ -0,0 +1,58 @@
+/*
+ * 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 com.android.internal.logging.UiEvent;
+import com.android.internal.logging.UiEventLogger;
+
+/**
+ * Ui events for the Quick Access Wallet.
+ */
+public enum WalletUiEvent implements UiEventLogger.UiEventEnum {
+ @UiEvent(doc = "The default payment app is opened to show all payment cards.")
+ QAW_SHOW_ALL(860),
+
+ @UiEvent(doc = "The Quick Access Wallet homescreen is unlocked.")
+ QAW_UNLOCK_FROM_CARD_CLICK(861),
+
+ @UiEvent(doc = "The Quick Access Wallet center card is changed")
+ QAW_CHANGE_CARD(863),
+
+ @UiEvent(doc = "The Quick Access Wallet is opened.")
+ QAW_IMPRESSION(864),
+
+ @UiEvent(doc = "The Quick Access Wallet card is clicked")
+ QAW_CLICK_CARD(865),
+
+ @UiEvent(doc = "The Quick Access Wallet homescreen is unlocked via clicking the unlock button")
+ QAW_UNLOCK_FROM_UNLOCK_BUTTON(866),
+
+ @UiEvent(
+ doc = "The Quick Access Wallet homescreen is unlocked via clicking the show all button")
+ QAW_UNLOCK_FROM_SHOW_ALL_BUTTON(867);
+
+ private final int mId;
+
+ WalletUiEvent(int id) {
+ mId = id;
+ }
+
+ @Override
+ public int getId() {
+ return mId;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletView.java b/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletView.java
index 0c5347724035..420f84abe0dd 100644
--- a/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletView.java
+++ b/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletView.java
@@ -22,6 +22,7 @@ import static com.android.systemui.wallet.ui.WalletCardCarousel.CARD_ANIM_ALPHA_
import android.annotation.Nullable;
import android.app.PendingIntent;
import android.content.Context;
+import android.content.res.Configuration;
import android.graphics.drawable.Drawable;
import android.text.TextUtils;
import android.util.AttributeSet;
@@ -39,6 +40,7 @@ import android.widget.TextView;
import com.android.internal.annotations.VisibleForTesting;
import com.android.settingslib.Utils;
import com.android.systemui.R;
+import com.android.systemui.classifier.FalsingCollector;
import java.util.List;
@@ -54,6 +56,8 @@ public class WalletView extends FrameLayout implements WalletCardCarousel.OnCard
private final TextView mCardLabel;
// Displays at the bottom of the screen, allow user to enter the default wallet app.
private final Button mAppButton;
+ // Displays on the top right of the screen, allow user to enter the default wallet app.
+ private final Button mToolbarAppButton;
// Displays underneath the carousel, allow user to unlock device, verify card, etc.
private final Button mActionButton;
private final Interpolator mOutInterpolator;
@@ -61,10 +65,11 @@ public class WalletView extends FrameLayout implements WalletCardCarousel.OnCard
private final ViewGroup mCardCarouselContainer;
private final TextView mErrorView;
private final ViewGroup mEmptyStateView;
- private CharSequence mCenterCardText;
private boolean mIsDeviceLocked = false;
private boolean mIsUdfpsEnabled = false;
private OnClickListener mDeviceLockedActionOnClickListener;
+ private OnClickListener mShowWalletAppOnClickListener;
+ private FalsingCollector mFalsingCollector;
public WalletView(Context context) {
this(context, null);
@@ -79,6 +84,7 @@ public class WalletView extends FrameLayout implements WalletCardCarousel.OnCard
mIcon = requireViewById(R.id.icon);
mCardLabel = requireViewById(R.id.label);
mAppButton = requireViewById(R.id.wallet_app_button);
+ mToolbarAppButton = requireViewById(R.id.wallet_toolbar_app_button);
mActionButton = requireViewById(R.id.wallet_action_button);
mErrorView = requireViewById(R.id.error_view);
mEmptyStateView = requireViewById(R.id.wallet_empty_state);
@@ -94,6 +100,43 @@ public class WalletView extends FrameLayout implements WalletCardCarousel.OnCard
}
@Override
+ protected void onConfigurationChanged(Configuration newConfig) {
+ updateViewForOrientation(newConfig.orientation);
+ }
+
+ private void updateViewForOrientation(@Configuration.Orientation int orientation) {
+ if (orientation == Configuration.ORIENTATION_PORTRAIT) {
+ renderViewPortrait();
+ } else if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
+ renderViewLandscape();
+ }
+ ViewGroup.LayoutParams params = mCardCarouselContainer.getLayoutParams();
+ if (params instanceof MarginLayoutParams) {
+ ((MarginLayoutParams) params).topMargin =
+ getResources().getDimensionPixelSize(
+ R.dimen.wallet_card_carousel_container_top_margin);
+ }
+ }
+
+ private void renderViewPortrait() {
+ mAppButton.setVisibility(VISIBLE);
+ mToolbarAppButton.setVisibility(GONE);
+ mCardLabel.setVisibility(VISIBLE);
+ requireViewById(R.id.dynamic_placeholder).setVisibility(VISIBLE);
+
+ mAppButton.setOnClickListener(mShowWalletAppOnClickListener);
+ }
+
+ private void renderViewLandscape() {
+ mToolbarAppButton.setVisibility(VISIBLE);
+ mAppButton.setVisibility(GONE);
+ mCardLabel.setVisibility(GONE);
+ requireViewById(R.id.dynamic_placeholder).setVisibility(GONE);
+
+ mToolbarAppButton.setOnClickListener(mShowWalletAppOnClickListener);
+ }
+
+ @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);
@@ -137,10 +180,12 @@ public class WalletView extends FrameLayout implements WalletCardCarousel.OnCard
mIsDeviceLocked = isDeviceLocked;
mIsUdfpsEnabled = isUdfpsEnabled;
mCardCarouselContainer.setVisibility(VISIBLE);
+ mCardCarousel.setVisibility(VISIBLE);
mErrorView.setVisibility(GONE);
mEmptyStateView.setVisibility(GONE);
mIcon.setImageDrawable(getHeaderIcon(mContext, data.get(selectedIndex)));
mCardLabel.setText(getLabelText(data.get(selectedIndex)));
+ updateViewForOrientation(getResources().getConfiguration().orientation);
renderActionButton(data.get(selectedIndex), isDeviceLocked, mIsUdfpsEnabled);
if (shouldAnimate) {
animateViewsShown(mIcon, mCardLabel, mActionButton);
@@ -190,6 +235,10 @@ public class WalletView extends FrameLayout implements WalletCardCarousel.OnCard
mDeviceLockedActionOnClickListener = onClickListener;
}
+ void setShowWalletAppOnClickListener(OnClickListener onClickListener) {
+ mShowWalletAppOnClickListener = onClickListener;
+ }
+
void hide() {
setVisibility(GONE);
}
@@ -206,10 +255,6 @@ public class WalletView extends FrameLayout implements WalletCardCarousel.OnCard
return mCardCarousel;
}
- Button getAppButton() {
- return mAppButton;
- }
-
Button getActionButton() {
return mActionButton;
}
@@ -286,4 +331,23 @@ public class WalletView extends FrameLayout implements WalletCardCarousel.OnCard
String[] rawLabel = card.getLabel().toString().split("\\n");
return rawLabel.length == 2 ? rawLabel[1] : null;
}
+
+ @Override
+ public boolean dispatchTouchEvent(MotionEvent ev) {
+ if (mFalsingCollector != null) {
+ mFalsingCollector.onTouchEvent(ev);
+ }
+
+ boolean result = super.dispatchTouchEvent(ev);
+
+ if (mFalsingCollector != null) {
+ mFalsingCollector.onMotionEventComplete();
+ }
+
+ return result;
+ }
+
+ public void setFalsingCollector(FalsingCollector falsingCollector) {
+ mFalsingCollector = falsingCollector;
+ }
}