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/KeyguardAbsKeyInputView.java4
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java241
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java8
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java30
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java30
-rw-r--r--packages/SystemUI/src/com/android/keyguard/clock/BubbleClockController.java114
-rw-r--r--packages/SystemUI/src/com/android/keyguard/clock/ClockLayout.java101
-rw-r--r--packages/SystemUI/src/com/android/keyguard/clock/CrossFadeDarkController.java62
-rw-r--r--packages/SystemUI/src/com/android/keyguard/clock/ImageClock.java94
-rw-r--r--packages/SystemUI/src/com/android/keyguard/clock/StretchAnalogClock.java142
-rw-r--r--packages/SystemUI/src/com/android/keyguard/clock/StretchAnalogClockController.java115
-rw-r--r--packages/SystemUI/src/com/android/keyguard/clock/TypeClockController.java106
-rw-r--r--packages/SystemUI/src/com/android/keyguard/clock/TypographicClock.java110
-rw-r--r--packages/SystemUI/src/com/android/systemui/BatteryMeterView.java26
-rw-r--r--packages/SystemUI/src/com/android/systemui/Dependency.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/DependencyBinder.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/ForegroundServiceNotificationListener.java15
-rw-r--r--packages/SystemUI/src/com/android/systemui/HardwareUiLayout.java77
-rw-r--r--packages/SystemUI/src/com/android/systemui/MultiListLayout.java124
-rw-r--r--packages/SystemUI/src/com/android/systemui/SystemUIFactory.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/appops/AppOpItem.java13
-rw-r--r--packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java21
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogImpl.java141
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogView.java129
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/FaceDialogView.java286
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/FingerprintDialogView.java18
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/BadgeRenderer.java80
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/BadgedImageView.java129
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java345
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedViewContainer.java16
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java163
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java267
-rw-r--r--packages/SystemUI/src/com/android/systemui/classifier/FalsingManager.java13
-rw-r--r--packages/SystemUI/src/com/android/systemui/doze/DozeHost.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/doze/DozeLog.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java99
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java53
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/pip/phone/PipAppOpsListener.java17
-rw-r--r--packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyDialog.kt30
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java56
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyTile.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java15
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/AlertingNotificationManager.java25
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/AmbientPulseManager.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java22
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/FlingAnimationUtils.java15
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/HeadsUpStatusBarView.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationLifetimeExtender.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java22
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java39
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java59
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationUiAdjustment.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java69
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java52
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java694
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/SmartReplyController.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconContainer.java34
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateController.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationAlertingManager.java28
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationData.java1072
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryListener.java34
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java255
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationFilter.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationInterruptionStateProvider.java123
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationListController.java117
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationRowBinder.java27
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/VisibilityLocationProvider.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/VisualStabilityManager.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationData.java424
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java726
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java157
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java77
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java62
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridGroupManager.java33
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManager.java16
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java18
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInflater.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java17
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowInflaterTask.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java16
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java13
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationListContainer.java16
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java43
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java143
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java37
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java24
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardEnvironmentImpl.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationAssistantAction.java54
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBackAction.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationGestureAction.java23
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationPrototypeController.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java51
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java56
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java75
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java35
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java61
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStepAction.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStepController.java90
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java75
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java44
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java119
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java51
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java32
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java58
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java18
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/IconLogger.java29
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/IconLoggerImpl.java115
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/OnHeadsUpChangedListener.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java15
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyConstants.java42
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java23
-rw-r--r--packages/SystemUI/src/com/android/systemui/wallpaper/AodMaskView.java222
-rw-r--r--packages/SystemUI/src/com/android/systemui/wallpaper/ImageWallpaperFilter.java81
-rw-r--r--packages/SystemUI/src/com/android/systemui/wallpaper/ImageWallpaperTransformer.java173
-rw-r--r--packages/SystemUI/src/com/android/systemui/wallpaper/ScrimFilter.java67
-rw-r--r--packages/SystemUI/src/com/android/systemui/wallpaper/VignetteFilter.java124
140 files changed, 6169 insertions, 3647 deletions
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputView.java
index 2511f74e4ab6..658d9e3593c0 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputView.java
@@ -46,6 +46,7 @@ public abstract class KeyguardAbsKeyInputView extends LinearLayout
protected View mEcaView;
protected boolean mEnableHaptics;
private boolean mDismissing;
+ protected boolean mResumed;
private CountDownTimer mCountdownTimer = null;
// To avoid accidental lockout due to events while the device in in the pocket, ignore
@@ -264,6 +265,8 @@ public abstract class KeyguardAbsKeyInputView extends LinearLayout
@Override
public void onPause() {
+ mResumed = false;
+
if (mCountdownTimer != null) {
mCountdownTimer.cancel();
mCountdownTimer = null;
@@ -277,6 +280,7 @@ public abstract class KeyguardAbsKeyInputView extends LinearLayout
@Override
public void onResume(int reason) {
+ mResumed = true;
}
@Override
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
index 570d351a8b71..1aff3949a74b 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
@@ -1,9 +1,15 @@
package com.android.keyguard;
+import android.content.ContentResolver;
import android.content.Context;
+import android.database.ContentObserver;
import android.graphics.Paint;
import android.graphics.Paint.Style;
+import android.os.Handler;
+import android.os.Looper;
+import android.provider.Settings;
import android.util.AttributeSet;
+import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
@@ -12,18 +18,29 @@ import android.widget.TextClock;
import androidx.annotation.VisibleForTesting;
+import com.android.keyguard.clock.BubbleClockController;
+import com.android.keyguard.clock.StretchAnalogClockController;
+import com.android.keyguard.clock.TypeClockController;
import com.android.systemui.Dependency;
import com.android.systemui.plugins.ClockPlugin;
-import com.android.systemui.plugins.PluginListener;
-import com.android.systemui.shared.plugins.PluginManager;
+import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.StatusBarStateController;
+import com.android.systemui.statusbar.policy.ExtensionController;
+import com.android.systemui.statusbar.policy.ExtensionController.Extension;
import java.util.Objects;
import java.util.TimeZone;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
/**
* Switch to show plugin clock when plugin is connected, otherwise it will show default clock.
*/
public class KeyguardClockSwitch extends RelativeLayout {
+
+ private LayoutInflater mLayoutInflater;
+
+ private final ContentResolver mContentResolver;
/**
* Optional/alternative clock injected via plugin.
*/
@@ -45,43 +62,48 @@ public class KeyguardClockSwitch extends RelativeLayout {
* or not to show it below the alternate clock.
*/
private View mKeyguardStatusArea;
+ /**
+ * Used to select between plugin or default implementations of ClockPlugin interface.
+ */
+ private Extension<ClockPlugin> mClockExtension;
+ /**
+ * Consumer that accepts the a new ClockPlugin implementation when the Extension reloads.
+ */
+ private final Consumer<ClockPlugin> mClockPluginConsumer = plugin -> setClockPlugin(plugin);
+ /**
+ * Maintain state so that a newly connected plugin can be initialized.
+ */
+ private float mDarkAmount;
- private final PluginListener<ClockPlugin> mClockPluginListener =
- new PluginListener<ClockPlugin>() {
+ private final StatusBarStateController.StateListener mStateListener =
+ new StatusBarStateController.StateListener() {
@Override
- public void onPluginConnected(ClockPlugin plugin, Context pluginContext) {
- disconnectPlugin();
- View smallClockView = plugin.getView();
- if (smallClockView != null) {
- // For now, assume that the most recently connected plugin is the
- // selected clock face. In the future, the user should be able to
- // pick a clock face from the available plugins.
- mSmallClockFrame.addView(smallClockView, -1,
- new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
- ViewGroup.LayoutParams.WRAP_CONTENT));
- initPluginParams();
- mClockView.setVisibility(View.GONE);
- }
- View bigClockView = plugin.getBigClockView();
- if (bigClockView != null && mBigClockContainer != null) {
- mBigClockContainer.addView(bigClockView);
- mBigClockContainer.setVisibility(View.VISIBLE);
+ public void onStateChanged(int newState) {
+ if (mBigClockContainer == null) {
+ return;
}
- if (!plugin.shouldShowStatusArea()) {
- mKeyguardStatusArea.setVisibility(View.GONE);
+ if (newState == StatusBarState.SHADE) {
+ if (mBigClockContainer.getVisibility() == View.VISIBLE) {
+ mBigClockContainer.setVisibility(View.INVISIBLE);
+ }
+ } else {
+ if (mBigClockContainer.getVisibility() == View.INVISIBLE) {
+ mBigClockContainer.setVisibility(View.VISIBLE);
+ }
}
- mClockPlugin = plugin;
}
+ };
+ private final ContentObserver mContentObserver =
+ new ContentObserver(new Handler(Looper.getMainLooper())) {
@Override
- public void onPluginDisconnected(ClockPlugin plugin) {
- if (Objects.equals(plugin, mClockPlugin)) {
- disconnectPlugin();
- mClockView.setVisibility(View.VISIBLE);
- mKeyguardStatusArea.setVisibility(View.VISIBLE);
+ public void onChange(boolean selfChange) {
+ super.onChange(selfChange);
+ if (mClockExtension != null) {
+ mClockExtension.reload();
}
}
- };
+ };
public KeyguardClockSwitch(Context context) {
this(context, null);
@@ -89,6 +111,15 @@ public class KeyguardClockSwitch extends RelativeLayout {
public KeyguardClockSwitch(Context context, AttributeSet attrs) {
super(context, attrs);
+ mLayoutInflater = LayoutInflater.from(context);
+ mContentResolver = context.getContentResolver();
+ }
+
+ /**
+ * Returns if this view is presenting a custom clock, or the default implementation.
+ */
+ public boolean hasCustomClock() {
+ return mClockPlugin != null;
}
@Override
@@ -102,14 +133,88 @@ public class KeyguardClockSwitch extends RelativeLayout {
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
- Dependency.get(PluginManager.class).addPluginListener(mClockPluginListener,
- ClockPlugin.class);
+ mClockExtension = Dependency.get(ExtensionController.class).newExtension(ClockPlugin.class)
+ .withPlugin(ClockPlugin.class)
+ .withCallback(mClockPluginConsumer)
+ // Using withDefault even though this isn't the default as a workaround.
+ // ExtensionBulider doesn't provide the ability to supply a ClockPlugin
+ // instance based off of the value of a setting. Since multiple "default"
+ // can be provided, using a supplier that changes the settings value.
+ // A null return will cause Extension#reload to look at the next "default"
+ // supplier.
+ .withDefault(
+ new SettingsGattedSupplier(
+ mContentResolver,
+ Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE,
+ BubbleClockController.class.getName(),
+ () -> BubbleClockController.build(mLayoutInflater)))
+ .withDefault(
+ new SettingsGattedSupplier(
+ mContentResolver,
+ Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE,
+ StretchAnalogClockController.class.getName(),
+ () -> StretchAnalogClockController.build(mLayoutInflater)))
+ .withDefault(
+ new SettingsGattedSupplier(
+ mContentResolver,
+ Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE,
+ TypeClockController.class.getName(),
+ () -> TypeClockController.build(mLayoutInflater)))
+ .build();
+ mContentResolver.registerContentObserver(
+ Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE),
+ false, mContentObserver);
+ Dependency.get(StatusBarStateController.class).addCallback(mStateListener);
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
- Dependency.get(PluginManager.class).removePluginListener(mClockPluginListener);
+ mClockExtension.destroy();
+ mContentResolver.unregisterContentObserver(mContentObserver);
+ Dependency.get(StatusBarStateController.class).removeCallback(mStateListener);
+ }
+
+ private void setClockPlugin(ClockPlugin plugin) {
+ // Disconnect from existing plugin.
+ if (mClockPlugin != null) {
+ View smallClockView = mClockPlugin.getView();
+ if (smallClockView != null && smallClockView.getParent() == mSmallClockFrame) {
+ mSmallClockFrame.removeView(smallClockView);
+ }
+ if (mBigClockContainer != null) {
+ mBigClockContainer.removeAllViews();
+ mBigClockContainer.setVisibility(View.GONE);
+ }
+ mClockPlugin = null;
+ }
+ if (plugin == null) {
+ mClockView.setVisibility(View.VISIBLE);
+ mKeyguardStatusArea.setVisibility(View.VISIBLE);
+ return;
+ }
+ // Attach small and big clock views to hierarchy.
+ View smallClockView = plugin.getView();
+ if (smallClockView != null) {
+ mSmallClockFrame.addView(smallClockView, -1,
+ new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT));
+ mClockView.setVisibility(View.GONE);
+ }
+ View bigClockView = plugin.getBigClockView();
+ if (bigClockView != null && mBigClockContainer != null) {
+ mBigClockContainer.addView(bigClockView);
+ mBigClockContainer.setVisibility(View.VISIBLE);
+ }
+ // Hide default clock.
+ if (!plugin.shouldShowStatusArea()) {
+ mKeyguardStatusArea.setVisibility(View.GONE);
+ }
+ // Initialize plugin parameters.
+ mClockPlugin = plugin;
+ mClockPlugin.setStyle(getPaint().getStyle());
+ mClockPlugin.setTextColor(getCurrentTextColor());
+ mClockPlugin.setDarkAmount(mDarkAmount);
}
/**
@@ -150,10 +255,6 @@ public class KeyguardClockSwitch extends RelativeLayout {
mClockView.setShowCurrentUserTime(showCurrentUserTime);
}
- public void setElegantTextHeight(boolean elegant) {
- mClockView.setElegantTextHeight(elegant);
- }
-
public void setTextSize(int unit, float size) {
mClockView.setTextSize(unit, size);
}
@@ -171,6 +272,7 @@ public class KeyguardClockSwitch extends RelativeLayout {
* @param darkAmount Amount of transition to doze: 1f for doze and 0f for awake.
*/
public void setDarkAmount(float darkAmount) {
+ mDarkAmount = darkAmount;
if (mClockPlugin != null) {
mClockPlugin.setDarkAmount(darkAmount);
}
@@ -210,32 +312,53 @@ public class KeyguardClockSwitch extends RelativeLayout {
}
}
+ @VisibleForTesting (otherwise = VisibleForTesting.NONE)
+ Consumer<ClockPlugin> getClockPluginConsumer() {
+ return mClockPluginConsumer;
+ }
+
+ @VisibleForTesting (otherwise = VisibleForTesting.NONE)
+ StatusBarStateController.StateListener getStateListener() {
+ return mStateListener;
+ }
+
/**
- * When plugin changes, set all kept parameters into newer plugin.
+ * Supplier that only gets an instance when a settings value matches expected value.
*/
- private void initPluginParams() {
- if (mClockPlugin != null) {
- mClockPlugin.setStyle(getPaint().getStyle());
- mClockPlugin.setTextColor(getCurrentTextColor());
- }
- }
+ private static class SettingsGattedSupplier implements Supplier<ClockPlugin> {
- private void disconnectPlugin() {
- if (mClockPlugin != null) {
- View smallClockView = mClockPlugin.getView();
- if (smallClockView != null) {
- mSmallClockFrame.removeView(smallClockView);
- }
- if (mBigClockContainer != null) {
- mBigClockContainer.removeAllViews();
- mBigClockContainer.setVisibility(View.GONE);
- }
- mClockPlugin = null;
+ private final ContentResolver mContentResolver;
+ private final String mKey;
+ private final String mValue;
+ private final Supplier<ClockPlugin> mSupplier;
+
+ /**
+ * Constructs a supplier that changes secure setting key against value.
+ *
+ * @param contentResolver Used to look up settings value.
+ * @param key Settings key.
+ * @param value If the setting matches this values that get supplies a ClockPlugin
+ * instance.
+ * @param supplier Supplier of ClockPlugin instance, only used if the setting
+ * matches value.
+ */
+ SettingsGattedSupplier(ContentResolver contentResolver, String key, String value,
+ Supplier<ClockPlugin> supplier) {
+ mContentResolver = contentResolver;
+ mKey = key;
+ mValue = value;
+ mSupplier = supplier;
}
- }
- @VisibleForTesting (otherwise = VisibleForTesting.NONE)
- PluginListener getClockPluginListener() {
- return mClockPluginListener;
+ /**
+ * Returns null if the settings value doesn't match the expected value.
+ *
+ * A null return causes Extension#reload to skip this supplier and move to the next.
+ */
+ @Override
+ public ClockPlugin get() {
+ final String currentValue = Settings.Secure.getString(mContentResolver, mKey);
+ return Objects.equals(currentValue, mValue) ? mSupplier.get() : null;
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
index 41afa9a21128..64c5b1754fa8 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
@@ -18,6 +18,7 @@ package com.android.keyguard;
import android.content.Context;
import android.graphics.Rect;
+import android.os.UserHandle;
import android.text.Editable;
import android.text.InputType;
import android.text.TextUtils;
@@ -79,8 +80,14 @@ public class KeyguardPasswordView extends KeyguardAbsKeyInputView
@Override
protected void resetState() {
+ mPasswordEntry.setTextOperationUser(UserHandle.of(KeyguardUpdateMonitor.getCurrentUser()));
mSecurityMessageDisplay.setMessage("");
final boolean wasDisabled = mPasswordEntry.isEnabled();
+ // Don't set enabled password entry & showSoftInput when PasswordEntry is invisible or in
+ // pausing stage.
+ if (!mResumed || !mPasswordEntry.isVisibleToUser()) {
+ return;
+ }
setPasswordEntryEnabled(true);
setPasswordEntryInputEnabled(true);
if (wasDisabled) {
@@ -169,6 +176,7 @@ public class KeyguardPasswordView extends KeyguardAbsKeyInputView
Context.INPUT_METHOD_SERVICE);
mPasswordEntry = findViewById(getPasswordTextViewId());
+ mPasswordEntry.setTextOperationUser(UserHandle.of(KeyguardUpdateMonitor.getCurrentUser()));
mPasswordEntryDisabler = new TextViewInputDisabler(mPasswordEntry);
mPasswordEntry.setKeyListener(TextKeyListener.getInstance());
mPasswordEntry.setInputType(InputType.TYPE_CLASS_TEXT
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java
index 669e6fff525b..bac7844c024b 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java
@@ -90,7 +90,7 @@ public class KeyguardSliceView extends LinearLayout implements View.OnClickListe
*/
private Runnable mContentChangeListener;
private Slice mSlice;
- private boolean mPulsing;
+ private boolean mHasHeader;
public KeyguardSliceView(Context context) {
this(context, null, 0);
@@ -150,10 +150,18 @@ public class KeyguardSliceView extends LinearLayout implements View.OnClickListe
Dependency.get(ConfigurationController.class).removeCallback(this);
}
+ /**
+ * Returns whether the current visible slice has a title/header.
+ */
+ public boolean hasHeader() {
+ return mHasHeader;
+ }
+
private void showSlice() {
Trace.beginSection("KeyguardSliceView#showSlice");
- if (mPulsing || mSlice == null) {
+ if (mSlice == null) {
mRow.setVisibility(GONE);
+ mHasHeader = false;
if (mContentChangeListener != null) {
mContentChangeListener.run();
}
@@ -162,7 +170,7 @@ public class KeyguardSliceView extends LinearLayout implements View.OnClickListe
ListContent lc = new ListContent(getContext(), mSlice);
SliceContent headerContent = lc.getHeader();
- boolean hasHeader = headerContent != null
+ mHasHeader = headerContent != null
&& !headerContent.getSliceItem().hasHint(HINT_LIST_ITEM);
List<SliceContent> subItems = new ArrayList<>();
for (int i = 0; i < lc.getRowItems().size(); i++) {
@@ -177,7 +185,7 @@ public class KeyguardSliceView extends LinearLayout implements View.OnClickListe
mClickActions.clear();
final int subItemsCount = subItems.size();
final int blendedColor = getTextColor();
- final int startIndex = hasHeader ? 1 : 0; // First item is header; skip it
+ final int startIndex = mHasHeader ? 1 : 0; // First item is header; skip it
mRow.setVisibility(subItemsCount > 0 ? VISIBLE : GONE);
for (int i = startIndex; i < subItemsCount; i++) {
RowContent rc = (RowContent) subItems.get(i);
@@ -189,7 +197,7 @@ public class KeyguardSliceView extends LinearLayout implements View.OnClickListe
button = new KeyguardSliceButton(mContext);
button.setTextColor(blendedColor);
button.setTag(itemTag);
- final int viewIndex = i - (hasHeader ? 1 : 0);
+ final int viewIndex = i - (mHasHeader ? 1 : 0);
mRow.addView(button, viewIndex);
}
@@ -234,18 +242,6 @@ public class KeyguardSliceView extends LinearLayout implements View.OnClickListe
Trace.endSection();
}
- public void setPulsing(boolean pulsing, boolean animate) {
- mPulsing = pulsing;
- LayoutTransition transition = getLayoutTransition();
- if (!animate) {
- setLayoutTransition(null);
- }
- showSlice();
- if (!animate) {
- setLayoutTransition(transition);
- }
- }
-
public void setDarkAmount(float darkAmount) {
mDarkAmount = darkAmount;
mRow.setDarkAmount(darkAmount);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
index c6f172684686..bb549ada6830 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
@@ -72,7 +72,6 @@ public class KeyguardStatusView extends GridLayout implements
private ArraySet<View> mVisibleInDoze;
private boolean mPulsing;
- private boolean mWasPulsing;
private float mDarkAmount = 0;
private int mTextColor;
private int mLastLayoutHeight;
@@ -141,6 +140,13 @@ public class KeyguardStatusView extends GridLayout implements
onDensityOrFontScaleChanged();
}
+ /**
+ * If we're presenting a custom clock of just the default one.
+ */
+ public boolean hasCustomClock() {
+ return mClockView.hasCustomClock();
+ }
+
private void setEnableMarquee(boolean enabled) {
if (DEBUG) Log.v(TAG, "Schedule setEnableMarquee: " + (enabled ? "Enable" : "Disable"));
if (enabled) {
@@ -195,10 +201,6 @@ public class KeyguardStatusView extends GridLayout implements
updateOwnerInfo();
updateLogoutView();
updateDark();
-
- // Disable elegant text height because our fancy colon makes the ymin value huge for no
- // reason.
- mClockView.setElegantTextHeight(false);
}
/**
@@ -207,7 +209,7 @@ public class KeyguardStatusView extends GridLayout implements
private void onSliceContentChanged() {
LinearLayout.LayoutParams layoutParams =
(LinearLayout.LayoutParams) mClockView.getLayoutParams();
- layoutParams.bottomMargin = mPulsing ? mSmallClockPadding : 0;
+ layoutParams.bottomMargin = mKeyguardSlice.hasHeader() ? mSmallClockPadding : 0;
mClockView.setLayoutParams(layoutParams);
}
@@ -217,16 +219,16 @@ public class KeyguardStatusView extends GridLayout implements
@Override
public void onLayoutChange(View view, int left, int top, int right, int bottom,
int oldLeft, int oldTop, int oldRight, int oldBottom) {
- int heightOffset = mPulsing || mWasPulsing ? 0 : getHeight() - mLastLayoutHeight;
+ boolean smallClock = mKeyguardSlice.hasHeader();
+ int heightOffset = smallClock ? 0 : getHeight() - mLastLayoutHeight;
long duration = KeyguardSliceView.DEFAULT_ANIM_DURATION;
- long delay = mPulsing || mWasPulsing ? 0 : duration / 4;
- mWasPulsing = false;
+ long delay = smallClock ? 0 : duration / 4;
boolean shouldAnimate = mKeyguardSlice.getLayoutTransition() != null
&& mKeyguardSlice.getLayoutTransition().isRunning();
if (view == mClockView) {
- float clockScale = mPulsing ? mSmallClockScale : 1;
- Paint.Style style = mPulsing ? Paint.Style.FILL_AND_STROKE : Paint.Style.FILL;
+ float clockScale = smallClock ? mSmallClockScale : 1;
+ Paint.Style style = smallClock ? Paint.Style.FILL_AND_STROKE : Paint.Style.FILL;
mClockView.animate().cancel();
if (shouldAnimate) {
mClockView.setY(oldTop + heightOffset);
@@ -431,15 +433,11 @@ public class KeyguardStatusView extends GridLayout implements
}
}
- public void setPulsing(boolean pulsing, boolean animate) {
+ public void setPulsing(boolean pulsing) {
if (mPulsing == pulsing) {
return;
}
- if (mPulsing) {
- mWasPulsing = true;
- }
mPulsing = pulsing;
- mKeyguardSlice.setPulsing(pulsing, animate);
updateDozeVisibleViews();
}
diff --git a/packages/SystemUI/src/com/android/keyguard/clock/BubbleClockController.java b/packages/SystemUI/src/com/android/keyguard/clock/BubbleClockController.java
new file mode 100644
index 000000000000..db6127f1d573
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/clock/BubbleClockController.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.keyguard.clock;
+
+import android.graphics.Paint.Style;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.TextClock;
+
+import com.android.keyguard.R;
+import com.android.systemui.plugins.ClockPlugin;
+
+import java.util.TimeZone;
+
+/**
+ * Controller for Bubble clock that can appear on lock screen and AOD.
+ */
+public class BubbleClockController implements ClockPlugin {
+
+ /**
+ * Custom clock shown on AOD screen and behind stack scroller on lock.
+ */
+ private View mView;
+ private TextClock mDigitalClock;
+ private ImageClock mAnalogClock;
+
+ /**
+ * Small clock shown on lock screen above stack scroller.
+ */
+ private View mLockClockContainer;
+ private TextClock mLockClock;
+
+ /**
+ * Controller for transition to dark state.
+ */
+ private CrossFadeDarkController mDarkController;
+
+ private BubbleClockController() { }
+
+ /**
+ * Create a BubbleClockController instance.
+ *
+ * @param layoutInflater Inflater used to inflate custom clock views.
+ */
+ public static BubbleClockController build(LayoutInflater layoutInflater) {
+ BubbleClockController controller = new BubbleClockController();
+ controller.createViews(layoutInflater);
+ return controller;
+ }
+
+ private void createViews(LayoutInflater layoutInflater) {
+ mView = layoutInflater.inflate(R.layout.bubble_clock, null);
+ mDigitalClock = (TextClock) mView.findViewById(R.id.digital_clock);
+ mAnalogClock = (ImageClock) mView.findViewById(R.id.analog_clock);
+
+ mLockClockContainer = layoutInflater.inflate(R.layout.digital_clock, null);
+ mLockClock = (TextClock) mLockClockContainer.findViewById(R.id.lock_screen_clock);
+ mLockClock.setVisibility(View.GONE);
+
+ mDarkController = new CrossFadeDarkController(mDigitalClock, mLockClock);
+ }
+
+ @Override
+ public View getView() {
+ return mLockClockContainer;
+ }
+
+ @Override
+ public View getBigClockView() {
+ return mView;
+ }
+
+ @Override
+ public void setStyle(Style style) {}
+
+ @Override
+ public void setTextColor(int color) {
+ mLockClock.setTextColor(color);
+ mDigitalClock.setTextColor(color);
+ }
+
+ @Override
+ public void dozeTimeTick() {
+ mAnalogClock.onTimeChanged();
+ }
+
+ @Override
+ public void setDarkAmount(float darkAmount) {
+ mDarkController.setDarkAmount(darkAmount);
+ }
+
+ @Override
+ public void onTimeZoneChanged(TimeZone timeZone) {
+ mAnalogClock.onTimeZoneChanged(timeZone);
+ }
+
+ @Override
+ public boolean shouldShowStatusArea() {
+ return false;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/clock/ClockLayout.java b/packages/SystemUI/src/com/android/keyguard/clock/ClockLayout.java
new file mode 100644
index 000000000000..3591dc82c8ec
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/clock/ClockLayout.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.keyguard.clock;
+
+import static com.android.systemui.doze.util.BurnInHelperKt.getBurnInOffset;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.FrameLayout;
+
+import com.android.keyguard.R;
+
+/**
+ * Positions clock faces (analog, digital, typographic) and handles pixel shifting
+ * to prevent screen burn-in.
+ */
+public class ClockLayout extends FrameLayout {
+
+ /**
+ * Clock face views.
+ */
+ private View mDigitalClock;
+ private View mAnalogClock;
+ private View mTypeClock;
+
+ /**
+ * Pixel shifting amplitidues used to prevent screen burn-in.
+ */
+ private int mBurnInPreventionOffsetX;
+ private int mBurnInPreventionOffsetY;
+
+ public ClockLayout(Context context) {
+ this(context, null);
+ }
+
+ public ClockLayout(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public ClockLayout(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ mDigitalClock = findViewById(R.id.digital_clock);
+ mAnalogClock = findViewById(R.id.analog_clock);
+ mTypeClock = findViewById(R.id.type_clock);
+
+ // Get pixel shifting X, Y amplitudes from resources.
+ Resources resources = getResources();
+ mBurnInPreventionOffsetX = resources.getDimensionPixelSize(
+ R.dimen.burn_in_prevention_offset_x);
+ mBurnInPreventionOffsetY = resources.getDimensionPixelSize(
+ R.dimen.burn_in_prevention_offset_y);
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ super.onLayout(changed, left, top, right, bottom);
+
+ final float offsetX = getBurnInOffset(mBurnInPreventionOffsetX, true);
+ final float offsetY = getBurnInOffset(mBurnInPreventionOffsetY, false);
+
+ // Put digital clock in two left corner of the screen.
+ if (mDigitalClock != null) {
+ mDigitalClock.setX(0.1f * getWidth() + offsetX);
+ mDigitalClock.setY(0.1f * getHeight() + offsetY);
+ }
+
+ // Put the analog clock in the middle of the screen.
+ if (mAnalogClock != null) {
+ mAnalogClock.setX(Math.max(0f, 0.5f * (getWidth() - mAnalogClock.getWidth()))
+ + offsetX);
+ mAnalogClock.setY(Math.max(0f, 0.5f * (getHeight() - mAnalogClock.getHeight()))
+ + offsetY);
+ }
+
+ // Put the typographic clock part way down the screen.
+ if (mTypeClock != null) {
+ mTypeClock.setX(offsetX);
+ mTypeClock.setY(0.2f * getHeight() + offsetY);
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/clock/CrossFadeDarkController.java b/packages/SystemUI/src/com/android/keyguard/clock/CrossFadeDarkController.java
new file mode 100644
index 000000000000..3c3f4759614b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/clock/CrossFadeDarkController.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.keyguard.clock;
+
+import android.view.View;
+
+/**
+ * Controls transition to dark state by cross fading between views.
+ */
+final class CrossFadeDarkController {
+
+ private final View mFadeInView;
+ private final View mFadeOutView;
+
+ /**
+ * Creates a new controller that fades between views.
+ *
+ * @param fadeInView View to fade in when transitioning to AOD.
+ * @param fadeOutView View to fade out when transitioning to AOD.
+ */
+ CrossFadeDarkController(View fadeInView, View fadeOutView) {
+ mFadeInView = fadeInView;
+ mFadeOutView = fadeOutView;
+ }
+
+ /**
+ * Sets the amount the system has transitioned to the dark state.
+ *
+ * @param darkAmount Amount of transition to dark state: 1f for AOD and 0f for lock screen.
+ */
+ void setDarkAmount(float darkAmount) {
+ mFadeInView.setAlpha(Math.max(0f, 2f * darkAmount - 1f));
+ if (darkAmount == 0f) {
+ mFadeInView.setVisibility(View.GONE);
+ } else {
+ if (mFadeInView.getVisibility() == View.GONE) {
+ mFadeInView.setVisibility(View.VISIBLE);
+ }
+ }
+ mFadeOutView.setAlpha(Math.max(0f, 1f - 2f * darkAmount));
+ if (darkAmount == 1f) {
+ mFadeOutView.setVisibility(View.GONE);
+ } else {
+ if (mFadeOutView.getVisibility() == View.GONE) {
+ mFadeOutView.setVisibility(View.VISIBLE);
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/clock/ImageClock.java b/packages/SystemUI/src/com/android/keyguard/clock/ImageClock.java
new file mode 100644
index 000000000000..2c709e0393bd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/clock/ImageClock.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.keyguard.clock;
+
+import android.content.Context;
+import android.text.format.DateFormat;
+import android.util.AttributeSet;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+
+import com.android.keyguard.R;
+
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.TimeZone;
+
+/**
+ * Clock composed of two images that rotate with the time.
+ *
+ * The images are the clock hands. ImageClock expects two child ImageViews
+ * with ids hour_hand and minute_hand.
+ */
+public class ImageClock extends FrameLayout {
+
+ private ImageView mHourHand;
+ private ImageView mMinuteHand;
+ private Calendar mTime;
+ private String mDescFormat;
+ private TimeZone mTimeZone;
+
+ public ImageClock(Context context) {
+ this(context, null);
+ }
+
+ public ImageClock(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public ImageClock(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ mTime = Calendar.getInstance();
+ mDescFormat = ((SimpleDateFormat) DateFormat.getTimeFormat(context)).toLocalizedPattern();
+ }
+
+ /**
+ * Call when the time changes to update the rotation of the clock hands.
+ */
+ public void onTimeChanged() {
+ mTime.setTimeInMillis(System.currentTimeMillis());
+ final float hourAngle = mTime.get(Calendar.HOUR) * 30f;
+ mHourHand.setRotation(hourAngle);
+ final float minuteAngle = mTime.get(Calendar.MINUTE) * 6f;
+ mMinuteHand.setRotation(minuteAngle);
+ setContentDescription(DateFormat.format(mDescFormat, mTime));
+ invalidate();
+ }
+
+ /**
+ * Call when the time zone has changed to update clock hands.
+ *
+ * @param timeZone The updated time zone that will be used.
+ */
+ public void onTimeZoneChanged(TimeZone timeZone) {
+ mTimeZone = timeZone;
+ mTime.setTimeZone(timeZone);
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ mHourHand = findViewById(R.id.hour_hand);
+ mMinuteHand = findViewById(R.id.minute_hand);
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ mTime = Calendar.getInstance(mTimeZone != null ? mTimeZone : TimeZone.getDefault());
+ onTimeChanged();
+ }
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/clock/StretchAnalogClock.java b/packages/SystemUI/src/com/android/keyguard/clock/StretchAnalogClock.java
new file mode 100644
index 000000000000..8734754541a6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/clock/StretchAnalogClock.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.keyguard.clock;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.util.AttributeSet;
+import android.view.View;
+
+import java.util.Calendar;
+import java.util.TimeZone;
+
+/**
+ * Analog clock where the minute hand extends off of the screen.
+ */
+public class StretchAnalogClock extends View {
+
+ private static final int DEFAULT_COLOR = Color.parseColor("#F5C983");
+ private static final float HOUR_STROKE_WIDTH = 60f;
+ private static final float MINUTE_STROKE_WIDTH = 20f;
+ private static final float CENTER_GAP_AND_CIRCLE_RADIUS = 80f;
+
+ private final Paint mHourPaint = new Paint();
+ private final Paint mMinutePaint = new Paint();
+ private Calendar mTime;
+ private TimeZone mTimeZone;
+
+ public StretchAnalogClock(Context context) {
+ this(context, null);
+ }
+
+ public StretchAnalogClock(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public StretchAnalogClock(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public StretchAnalogClock(Context context, AttributeSet attrs, int defStyleAttr,
+ int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ init();
+ }
+
+ /**
+ * Call when the time changes to update the clock hands.
+ */
+ public void onTimeChanged() {
+ mTime.setTimeInMillis(System.currentTimeMillis());
+ invalidate();
+ }
+
+ /**
+ * Call when the time zone has changed to update clock hands.
+ *
+ * @param timeZone The updated time zone that will be used.
+ */
+ public void onTimeZoneChanged(TimeZone timeZone) {
+ mTime.setTimeZone(timeZone);
+ }
+
+ /**
+ * Set the color of the minute hand.
+ */
+ public void setMinuteHandColor(int color) {
+ mMinutePaint.setColor(color);
+ invalidate();
+ }
+
+ private void init() {
+ mHourPaint.setColor(DEFAULT_COLOR);
+ mHourPaint.setStrokeWidth(HOUR_STROKE_WIDTH);
+ mHourPaint.setAntiAlias(true);
+ mHourPaint.setStrokeCap(Paint.Cap.ROUND);
+
+ mMinutePaint.setColor(Color.WHITE);
+ mMinutePaint.setStrokeWidth(MINUTE_STROKE_WIDTH);
+ mMinutePaint.setAntiAlias(true);
+ mMinutePaint.setStrokeCap(Paint.Cap.ROUND);
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ final float centerX = getWidth() / 2f;
+ final float centerY = getHeight() / 2f;
+
+ final float minutesRotation = mTime.get(Calendar.MINUTE) * 6f;
+ final float hoursRotation = (mTime.get(Calendar.HOUR) * 30);
+
+ // Compute length of clock hands. Hour hand is 60% the length from center to edge
+ // and minute hand is twice the length to make sure it extends past screen edge.
+ double sMinuteHandLengthFactor = Math.sin(2d * Math.PI * minutesRotation / 360d);
+ float sMinuteHandLength = (float) (2d * (centerY + (centerX - centerY)
+ * sMinuteHandLengthFactor * sMinuteHandLengthFactor));
+ double sHourHandLengthFactor = Math.sin(2d * Math.PI * hoursRotation / 360d);
+ float sHourHandLength = (float) (0.6d * (centerY + (centerX - centerY)
+ * sHourHandLengthFactor * sHourHandLengthFactor));
+
+ canvas.save();
+
+ canvas.rotate(minutesRotation, centerX, centerY);
+ canvas.drawLine(
+ centerX,
+ centerY + CENTER_GAP_AND_CIRCLE_RADIUS,
+ centerX,
+ centerY - sMinuteHandLength,
+ mMinutePaint);
+
+ canvas.rotate(hoursRotation - minutesRotation, centerX, centerY);
+ canvas.drawLine(
+ centerX,
+ centerY + CENTER_GAP_AND_CIRCLE_RADIUS,
+ centerX,
+ centerY - sHourHandLength,
+ mHourPaint);
+
+ canvas.restore();
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ mTime = Calendar.getInstance(mTimeZone != null ? mTimeZone : TimeZone.getDefault());
+ onTimeChanged();
+ }
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/clock/StretchAnalogClockController.java b/packages/SystemUI/src/com/android/keyguard/clock/StretchAnalogClockController.java
new file mode 100644
index 000000000000..0a39158cd4be
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/clock/StretchAnalogClockController.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.keyguard.clock;
+
+import android.graphics.Paint.Style;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.TextClock;
+
+import com.android.keyguard.R;
+import com.android.systemui.plugins.ClockPlugin;
+
+import java.util.TimeZone;
+
+/**
+ * Controller for Stretch clock that can appear on lock screen and AOD.
+ */
+public class StretchAnalogClockController implements ClockPlugin {
+
+ /**
+ * Custom clock shown on AOD screen and behind stack scroller on lock.
+ */
+ private View mBigClockView;
+ private TextClock mDigitalClock;
+ private StretchAnalogClock mAnalogClock;
+
+ /**
+ * Small clock shown on lock screen above stack scroller.
+ */
+ private View mView;
+ private TextClock mLockClock;
+
+ /**
+ * Controller for transition to dark state.
+ */
+ private CrossFadeDarkController mDarkController;
+
+ private StretchAnalogClockController() { }
+
+ /**
+ * Create a BubbleClockController instance.
+ *
+ * @param layoutInflater Inflater used to inflate custom clock views.
+ */
+ public static StretchAnalogClockController build(LayoutInflater layoutInflater) {
+ StretchAnalogClockController controller = new StretchAnalogClockController();
+ controller.createViews(layoutInflater);
+ return controller;
+ }
+
+ private void createViews(LayoutInflater layoutInflater) {
+ mBigClockView = layoutInflater.inflate(R.layout.stretchanalog_clock, null);
+ mAnalogClock = mBigClockView.findViewById(R.id.analog_clock);
+ mDigitalClock = mBigClockView.findViewById(R.id.digital_clock);
+
+ mView = layoutInflater.inflate(R.layout.digital_clock, null);
+ mLockClock = mView.findViewById(R.id.lock_screen_clock);
+ mLockClock.setVisibility(View.GONE);
+
+ mDarkController = new CrossFadeDarkController(mDigitalClock, mLockClock);
+ }
+
+ @Override
+ public View getView() {
+ return mView;
+ }
+
+ @Override
+ public View getBigClockView() {
+ return mBigClockView;
+ }
+
+ @Override
+ public void setStyle(Style style) {}
+
+ @Override
+ public void setTextColor(int color) {
+ mLockClock.setTextColor(color);
+ mDigitalClock.setTextColor(color);
+ mAnalogClock.setMinuteHandColor(color);
+ }
+
+ @Override
+ public void dozeTimeTick() {
+ mAnalogClock.onTimeChanged();
+ }
+
+ @Override
+ public void setDarkAmount(float darkAmount) {
+ mDarkController.setDarkAmount(darkAmount);
+ }
+
+ @Override
+ public void onTimeZoneChanged(TimeZone timeZone) {
+ mAnalogClock.onTimeZoneChanged(timeZone);
+ }
+
+ @Override
+ public boolean shouldShowStatusArea() {
+ return false;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/clock/TypeClockController.java b/packages/SystemUI/src/com/android/keyguard/clock/TypeClockController.java
new file mode 100644
index 000000000000..17d929dc8a3b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/clock/TypeClockController.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.keyguard.clock;
+
+import android.graphics.Paint.Style;
+import android.view.LayoutInflater;
+import android.view.View;
+
+import com.android.keyguard.R;
+import com.android.systemui.plugins.ClockPlugin;
+
+import java.util.TimeZone;
+
+/**
+ * Plugin for a custom Typographic clock face that displays the time in words.
+ */
+public class TypeClockController implements ClockPlugin {
+
+ /**
+ * Custom clock shown on AOD screen and behind stack scroller on lock.
+ */
+ private View mView;
+ private TypographicClock mTypeClock;
+
+ /**
+ * Small clock shown on lock screen above stack scroller.
+ */
+ private View mLockClockContainer;
+
+ /**
+ * Controller for transition into dark state.
+ */
+ private CrossFadeDarkController mDarkController;
+
+ private TypeClockController() {}
+
+ /**
+ * Create a TypeClockController instance.
+ *
+ * @param inflater Inflater used to inflate custom clock views.
+ */
+ public static TypeClockController build(LayoutInflater inflater) {
+ TypeClockController controller = new TypeClockController();
+ controller.createViews(inflater);
+ return controller;
+ }
+
+ private void createViews(LayoutInflater inflater) {
+ mView = inflater.inflate(R.layout.type_clock, null);
+ mTypeClock = mView.findViewById(R.id.type_clock);
+
+ // For now, this view is used to hide the default digital clock.
+ // Need better transition to lock screen.
+ mLockClockContainer = inflater.inflate(R.layout.digital_clock, null);
+ mLockClockContainer.setVisibility(View.GONE);
+ }
+
+ @Override
+ public View getView() {
+ return mLockClockContainer;
+ }
+
+ @Override
+ public View getBigClockView() {
+ return mView;
+ }
+
+ @Override
+ public void setStyle(Style style) {}
+
+ @Override
+ public void setTextColor(int color) {
+ mTypeClock.setTextColor(color);
+ }
+
+ @Override
+ public void dozeTimeTick() {
+ mTypeClock.onTimeChanged();
+ }
+
+ @Override
+ public void setDarkAmount(float darkAmount) {}
+
+ @Override
+ public void onTimeZoneChanged(TimeZone timeZone) {
+ mTypeClock.onTimeZoneChanged(timeZone);
+ }
+
+ @Override
+ public boolean shouldShowStatusArea() {
+ return false;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/clock/TypographicClock.java b/packages/SystemUI/src/com/android/keyguard/clock/TypographicClock.java
new file mode 100644
index 000000000000..5f9da3ee33bb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/clock/TypographicClock.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.keyguard.clock;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.text.format.DateFormat;
+import android.util.AttributeSet;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.android.keyguard.R;
+
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.TimeZone;
+
+/**
+ * Clock that presents the time in words.
+ */
+public class TypographicClock extends LinearLayout {
+
+ private final String[] mHours;
+ private final String[] mMinutes;
+ private TextView mHeaderText;
+ private TextView mHourText;
+ private TextView mMinuteText;
+ private Calendar mTime;
+ private String mDescFormat;
+ private TimeZone mTimeZone;
+
+ public TypographicClock(Context context) {
+ this(context, null);
+ }
+
+ public TypographicClock(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public TypographicClock(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ mTime = Calendar.getInstance();
+ mDescFormat = ((SimpleDateFormat) DateFormat.getTimeFormat(context)).toLocalizedPattern();
+ Resources res = context.getResources();
+ mHours = res.getStringArray(R.array.type_clock_hours);
+ mMinutes = res.getStringArray(R.array.type_clock_minutes);
+ }
+
+ /**
+ * Call when the time changes to update the text of the time.
+ */
+ public void onTimeChanged() {
+ mTime.setTimeInMillis(System.currentTimeMillis());
+ setContentDescription(DateFormat.format(mDescFormat, mTime));
+ final int hour = mTime.get(Calendar.HOUR);
+ mHourText.setText(mHours[hour % 12]);
+ final int minute = mTime.get(Calendar.MINUTE);
+ mMinuteText.setText(mMinutes[minute % 60]);
+ invalidate();
+ }
+
+ /**
+ * Call when the time zone has changed to update clock time.
+ *
+ * @param timeZone The updated time zone that will be used.
+ */
+ public void onTimeZoneChanged(TimeZone timeZone) {
+ mTimeZone = timeZone;
+ mTime.setTimeZone(timeZone);
+ }
+
+ /**
+ * Set the color of the text used to display the time.
+ *
+ * This is necessary when the wallpaper shown behind the clock on the
+ * lock screen changes.
+ */
+ public void setTextColor(int color) {
+ mHourText.setTextColor(color);
+ mMinuteText.setTextColor(color);
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ mHeaderText = findViewById(R.id.header);
+ mHourText = findViewById(R.id.hour);
+ mMinuteText = findViewById(R.id.minute);
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ mTime = Calendar.getInstance(mTimeZone != null ? mTimeZone : TimeZone.getDefault());
+ onTimeChanged();
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java b/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java
index 6864ea185834..200679432200 100644
--- a/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java
+++ b/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java
@@ -40,7 +40,6 @@ import android.util.TypedValue;
import android.view.ContextThemeWrapper;
import android.view.Gravity;
import android.view.LayoutInflater;
-import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.LinearLayout;
@@ -56,7 +55,6 @@ import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
-import com.android.systemui.statusbar.policy.IconLogger;
import com.android.systemui.tuner.TunerService;
import com.android.systemui.tuner.TunerService.Tunable;
import com.android.systemui.util.Utils.DisableStateTracker;
@@ -71,11 +69,12 @@ public class BatteryMeterView extends LinearLayout implements
@Retention(SOURCE)
- @IntDef({MODE_DEFAULT, MODE_ON, MODE_OFF})
+ @IntDef({MODE_DEFAULT, MODE_ON, MODE_OFF, MODE_ESTIMATE})
public @interface BatteryPercentMode {}
public static final int MODE_DEFAULT = 0;
public static final int MODE_ON = 1;
public static final int MODE_OFF = 2;
+ public static final int MODE_ESTIMATE = 3;
private final BatteryMeterDrawableBase mDrawable;
private final String mSlotBattery;
@@ -93,6 +92,7 @@ public class BatteryMeterView extends LinearLayout implements
// Some places may need to show the battery conditionally, and not obey the tuner
private boolean mIgnoreTunerUpdates;
private boolean mIsSubscribedForTunerUpdates;
+ private boolean mCharging;
private int mDarkModeBackgroundColor;
private int mDarkModeFillColor;
@@ -276,9 +276,6 @@ public class BatteryMeterView extends LinearLayout implements
public void onTuningChanged(String key, String newValue) {
if (StatusBarIconController.ICON_BLACKLIST.equals(key)) {
ArraySet<String> icons = StatusBarIconController.getIconBlacklist(newValue);
- boolean hidden = icons.contains(mSlotBattery);
- Dependency.get(IconLogger.class).onIconVisibility(mSlotBattery, !hidden);
- setVisibility(hidden ? View.GONE : View.VISIBLE);
}
}
@@ -308,6 +305,7 @@ public class BatteryMeterView extends LinearLayout implements
public void onBatteryLevelChanged(int level, boolean pluggedIn, boolean charging) {
mDrawable.setBatteryLevel(level);
mDrawable.setCharging(pluggedIn);
+ mCharging = pluggedIn;
mLevel = level;
updatePercentText();
setContentDescription(
@@ -337,9 +335,19 @@ public class BatteryMeterView extends LinearLayout implements
}
private void updatePercentText() {
+ if (mBatteryController == null) {
+ return;
+ }
+
if (mBatteryPercentView != null) {
- mBatteryPercentView.setText(
- NumberFormat.getPercentInstance().format(mLevel / 100f));
+ if (mShowPercentMode == MODE_ESTIMATE && !mCharging) {
+ mBatteryController.getEstimatedTimeRemainingString((String estimate) -> {
+ mBatteryPercentView.setText(estimate);
+ });
+ } else {
+ mBatteryPercentView.setText(
+ NumberFormat.getPercentInstance().format(mLevel / 100f));
+ }
}
}
@@ -350,7 +358,7 @@ public class BatteryMeterView extends LinearLayout implements
SHOW_BATTERY_PERCENT, 0, mUser);
if ((mShowPercentAvailable && systemSetting && mShowPercentMode != MODE_OFF)
- || mShowPercentMode == MODE_ON) {
+ || mShowPercentMode == MODE_ON || mShowPercentMode == MODE_ESTIMATE) {
if (!showing) {
mBatteryPercentView = loadPercentView();
if (mTextColor != 0) mBatteryPercentView.setTextColor(mTextColor);
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index 3cc9bb6f668f..ec6ecc64d07e 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -56,12 +56,12 @@ import com.android.systemui.statusbar.SmartReplyController;
import com.android.systemui.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.VibratorHelper;
import com.android.systemui.statusbar.notification.NotificationAlertingManager;
-import com.android.systemui.statusbar.notification.NotificationData.KeyguardEnvironment;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.NotificationFilter;
import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider;
import com.android.systemui.statusbar.notification.NotificationRowBinder;
import com.android.systemui.statusbar.notification.VisualStabilityManager;
+import com.android.systemui.statusbar.notification.collection.NotificationData.KeyguardEnvironment;
import com.android.systemui.statusbar.notification.logging.NotificationLogger;
import com.android.systemui.statusbar.notification.row.NotificationBlockingHelperManager;
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
@@ -86,7 +86,6 @@ import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.statusbar.policy.ExtensionController;
import com.android.systemui.statusbar.policy.FlashlightController;
import com.android.systemui.statusbar.policy.HotspotController;
-import com.android.systemui.statusbar.policy.IconLogger;
import com.android.systemui.statusbar.policy.KeyguardMonitor;
import com.android.systemui.statusbar.policy.LocationController;
import com.android.systemui.statusbar.policy.NetworkController;
@@ -217,7 +216,6 @@ public class Dependency extends SystemUI {
@Inject Lazy<LeakDetector> mLeakDetector;
@Inject Lazy<LeakReporter> mLeakReporter;
@Inject Lazy<GarbageMonitor> mGarbageMonitor;
- @Inject Lazy<IconLogger> mIconLogger;
@Inject Lazy<TunerService> mTunerService;
@Inject Lazy<StatusBarWindowController> mStatusBarWindowController;
@Inject Lazy<DarkIconDispatcher> mDarkIconDispatcher;
@@ -392,8 +390,6 @@ public class Dependency extends SystemUI {
mProviders.put(PowerUI.WarningsUI.class, mWarningsUI::get);
- mProviders.put(IconLogger.class, mIconLogger::get);
-
mProviders.put(LightBarController.class, mLightBarController::get);
mProviders.put(IWindowManager.class, mIWindowManager::get);
diff --git a/packages/SystemUI/src/com/android/systemui/DependencyBinder.java b/packages/SystemUI/src/com/android/systemui/DependencyBinder.java
index f324a05bafff..ce9c637cebf6 100644
--- a/packages/SystemUI/src/com/android/systemui/DependencyBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/DependencyBinder.java
@@ -46,8 +46,6 @@ import com.android.systemui.statusbar.policy.FlashlightController;
import com.android.systemui.statusbar.policy.FlashlightControllerImpl;
import com.android.systemui.statusbar.policy.HotspotController;
import com.android.systemui.statusbar.policy.HotspotControllerImpl;
-import com.android.systemui.statusbar.policy.IconLogger;
-import com.android.systemui.statusbar.policy.IconLoggerImpl;
import com.android.systemui.statusbar.policy.KeyguardMonitor;
import com.android.systemui.statusbar.policy.KeyguardMonitorImpl;
import com.android.systemui.statusbar.policy.LocationController;
@@ -133,11 +131,6 @@ public abstract class DependencyBinder {
/**
*/
@Binds
- public abstract IconLogger provideIconLogger(IconLoggerImpl loggerImpl);
-
- /**
- */
- @Binds
public abstract CastController provideCastController(CastControllerImpl controllerImpl);
/**
diff --git a/packages/SystemUI/src/com/android/systemui/ForegroundServiceNotificationListener.java b/packages/SystemUI/src/com/android/systemui/ForegroundServiceNotificationListener.java
index 151a6b013110..96b62ac918ab 100644
--- a/packages/SystemUI/src/com/android/systemui/ForegroundServiceNotificationListener.java
+++ b/packages/SystemUI/src/com/android/systemui/ForegroundServiceNotificationListener.java
@@ -24,9 +24,9 @@ import android.service.notification.StatusBarNotification;
import android.util.Log;
import com.android.internal.statusbar.NotificationVisibility;
-import com.android.systemui.statusbar.notification.NotificationData;
import com.android.systemui.statusbar.notification.NotificationEntryListener;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import javax.inject.Inject;
import javax.inject.Singleton;
@@ -49,26 +49,21 @@ public class ForegroundServiceNotificationListener {
mForegroundServiceController = foregroundServiceController;
notificationEntryManager.addNotificationEntryListener(new NotificationEntryListener() {
@Override
- public void onPendingEntryAdded(NotificationData.Entry entry) {
+ public void onPendingEntryAdded(NotificationEntry entry) {
addNotification(entry.notification, entry.importance);
}
@Override
- public void onEntryUpdated(NotificationData.Entry entry) {
+ public void onPostEntryUpdated(NotificationEntry entry) {
updateNotification(entry.notification, entry.importance);
}
@Override
public void onEntryRemoved(
- NotificationData.Entry entry,
- String key,
- StatusBarNotification old,
+ NotificationEntry entry,
NotificationVisibility visibility,
- boolean lifetimeExtended,
boolean removedByUser) {
- if (entry != null && !lifetimeExtended) {
- removeNotification(entry.notification);
- }
+ removeNotification(entry.notification);
}
});
}
diff --git a/packages/SystemUI/src/com/android/systemui/HardwareUiLayout.java b/packages/SystemUI/src/com/android/systemui/HardwareUiLayout.java
index 16e869e9d317..e28aa9d369cb 100644
--- a/packages/SystemUI/src/com/android/systemui/HardwareUiLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/HardwareUiLayout.java
@@ -37,23 +37,25 @@ import com.android.systemui.tuner.TunerService;
import com.android.systemui.tuner.TunerService.Tunable;
import com.android.systemui.util.leak.RotationUtils;
-public class HardwareUiLayout extends LinearLayout implements Tunable {
+/**
+ * Layout for placing two containers at a specific physical position on the device, relative to the
+ * device's hardware, regardless of screen rotation.
+ */
+public class HardwareUiLayout extends MultiListLayout implements Tunable {
private static final String EDGE_BLEED = "sysui_hwui_edge_bleed";
private static final String ROUNDED_DIVIDER = "sysui_hwui_rounded_divider";
private final int[] mTmp2 = new int[2];
- private View mList;
- private View mSeparatedView;
+ private ViewGroup mList;
+ private ViewGroup mSeparatedView;
private int mOldHeight;
private boolean mAnimating;
private AnimatorSet mAnimation;
private View mDivision;
- private boolean mHasOutsideTouch;
private HardwareBgDrawable mListBackground;
private HardwareBgDrawable mSeparatedViewBackground;
private Animator mAnimator;
private boolean mCollapse;
- private boolean mHasSeparatedButton;
private int mEndPoint;
private boolean mEdgeBleed;
private boolean mRoundedDivider;
@@ -67,6 +69,35 @@ public class HardwareUiLayout extends LinearLayout implements Tunable {
}
@Override
+ protected ViewGroup getSeparatedView() {
+ return findViewById(com.android.systemui.R.id.separated_button);
+ }
+
+ @Override
+ protected ViewGroup getListView() {
+ return findViewById(android.R.id.list);
+ }
+
+ @Override
+ public void removeAllItems() {
+ if (mList != null) {
+ mList.removeAllViews();
+ }
+ if (mSeparatedView != null) {
+ mSeparatedView.removeAllViews();
+ }
+ }
+
+ @Override
+ public ViewGroup getParentView(boolean separated, int index) {
+ if (separated) {
+ return getSeparatedView();
+ } else {
+ return getListView();
+ }
+ }
+
+ @Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
updateSettings();
@@ -137,9 +168,9 @@ public class HardwareUiLayout extends LinearLayout implements Tunable {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (mList == null) {
if (getChildCount() != 0) {
- mList = getChildAt(0);
+ mList = getListView();
mList.setBackground(mListBackground);
- mSeparatedView = getChildAt(1);
+ mSeparatedView = getSeparatedView();
mSeparatedView.setBackground(mSeparatedViewBackground);
updateEdgeMargin(mEdgeBleed ? 0 : getEdgePadding());
mOldHeight = mList.getMeasuredHeight();
@@ -187,7 +218,7 @@ public class HardwareUiLayout extends LinearLayout implements Tunable {
} else {
rotateLeft();
}
- if (mHasSeparatedButton) {
+ if (mHasSeparatedView) {
if (from == ROTATION_SEASCAPE || to == ROTATION_SEASCAPE) {
// Separated view has top margin, so seascape separated view need special rotation,
// not a full left or right rotation.
@@ -408,8 +439,8 @@ public class HardwareUiLayout extends LinearLayout implements Tunable {
if (mList == null) return;
// If got separated button, setRotatedBackground to false,
// all items won't get white background.
- mListBackground.setRotatedBackground(mHasSeparatedButton);
- mSeparatedViewBackground.setRotatedBackground(mHasSeparatedButton);
+ mListBackground.setRotatedBackground(mHasSeparatedView);
+ mSeparatedViewBackground.setRotatedBackground(mHasSeparatedView);
if (mDivision != null && mDivision.getVisibility() == VISIBLE) {
int index = mRotatedBackground ? 0 : 1;
mDivision.getLocationOnScreen(mTmp2);
@@ -460,21 +491,21 @@ public class HardwareUiLayout extends LinearLayout implements Tunable {
case RotationUtils.ROTATION_LANDSCAPE:
defaultTopPadding = getPaddingLeft();
viewsTotalHeight = mList.getMeasuredWidth() + mSeparatedView.getMeasuredWidth();
- separatedViewTopMargin = mHasSeparatedButton ? params.leftMargin : 0;
+ separatedViewTopMargin = mHasSeparatedView ? params.leftMargin : 0;
screenHeight = getMeasuredWidth();
targetGravity = Gravity.CENTER_HORIZONTAL|Gravity.TOP;
break;
case RotationUtils.ROTATION_SEASCAPE:
defaultTopPadding = getPaddingRight();
viewsTotalHeight = mList.getMeasuredWidth() + mSeparatedView.getMeasuredWidth();
- separatedViewTopMargin = mHasSeparatedButton ? params.leftMargin : 0;
+ separatedViewTopMargin = mHasSeparatedView ? params.leftMargin : 0;
screenHeight = getMeasuredWidth();
targetGravity = Gravity.CENTER_HORIZONTAL|Gravity.BOTTOM;
break;
default: // Portrait
defaultTopPadding = getPaddingTop();
viewsTotalHeight = mList.getMeasuredHeight() + mSeparatedView.getMeasuredHeight();
- separatedViewTopMargin = mHasSeparatedButton ? params.topMargin : 0;
+ separatedViewTopMargin = mHasSeparatedView ? params.topMargin : 0;
screenHeight = getMeasuredHeight();
targetGravity = Gravity.CENTER_VERTICAL|Gravity.RIGHT;
break;
@@ -491,30 +522,10 @@ public class HardwareUiLayout extends LinearLayout implements Tunable {
return super.getOutlineProvider();
}
- public void setOutsideTouchListener(OnClickListener onClickListener) {
- mHasOutsideTouch = true;
- requestLayout();
- setOnClickListener(onClickListener);
- setClickable(true);
- setFocusable(true);
- }
-
public void setCollapse() {
mCollapse = true;
}
- public void setHasSeparatedButton(boolean hasSeparatedButton) {
- mHasSeparatedButton = hasSeparatedButton;
- }
-
- public static HardwareUiLayout get(View v) {
- if (v instanceof HardwareUiLayout) return (HardwareUiLayout) v;
- if (v.getParent() instanceof View) {
- return get((View) v.getParent());
- }
- return null;
- }
-
private final ViewTreeObserver.OnComputeInternalInsetsListener mInsetsListener = inoutInfo -> {
if (mHasOutsideTouch || (mList == null)) {
inoutInfo.setTouchableInsets(
diff --git a/packages/SystemUI/src/com/android/systemui/MultiListLayout.java b/packages/SystemUI/src/com/android/systemui/MultiListLayout.java
new file mode 100644
index 000000000000..0c7a9a9fffd2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/MultiListLayout.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.LinearLayout;
+
+/**
+ * Layout class representing the Global Actions menu which appears when the power button is held.
+ */
+public abstract class MultiListLayout extends LinearLayout {
+ boolean mHasOutsideTouch;
+ boolean mHasSeparatedView;
+
+ int mExpectedSeparatedItemCount;
+ int mExpectedListItemCount;
+
+ public MultiListLayout(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ protected abstract ViewGroup getSeparatedView();
+
+ protected abstract ViewGroup getListView();
+
+ /**
+ * Removes all child items from the separated and list views, if they exist.
+ */
+ public abstract void removeAllItems();
+
+ /**
+ * Get the parent view which will be used to contain the item at the specified index.
+ * @param separated Whether or not this index refers to a position in the separated or list
+ * container.
+ * @param index The index of the item within the container.
+ * @return The parent ViewGroup which will be used to contain the specified item
+ * after it has been added to the layout.
+ */
+ public abstract ViewGroup getParentView(boolean separated, int index);
+
+ /**
+ * Sets the divided view, which may have a differently-colored background.
+ */
+ public abstract void setDivisionView(View v);
+
+ /**
+ * Set the view accessibility delegate for the list view container.
+ */
+ public void setListViewAccessibilityDelegate(View.AccessibilityDelegate delegate) {
+ getListView().setAccessibilityDelegate(delegate);
+ }
+
+ protected void setSeparatedViewVisibility(boolean visible) {
+ getSeparatedView().setVisibility(visible ? View.VISIBLE : View.GONE);
+ }
+
+ /**
+ * Sets the number of items expected to be rendered in the separated container. This allows the
+ * layout to correctly determine which parent containers will be used for items before they have
+ * beenadded to the layout.
+ * @param count The number of items expected.
+ */
+ public void setExpectedSeparatedItemCount(int count) {
+ mExpectedSeparatedItemCount = count;
+ }
+
+ /**
+ * Sets the number of items expected to be rendered in the list container. This allows the
+ * layout to correctly determine which parent containers will be used for items before they have
+ * beenadded to the layout.
+ * @param count The number of items expected.
+ */
+ public void setExpectedListItemCount(int count) {
+ mExpectedListItemCount = count;
+ }
+
+ /**
+ * Sets whether the separated view should be shown, and handles updating visibility on
+ * that view.
+ */
+ public void setHasSeparatedView(boolean hasSeparatedView) {
+ mHasSeparatedView = hasSeparatedView;
+ setSeparatedViewVisibility(hasSeparatedView);
+ }
+
+ /**
+ * Sets this layout to respond to an outside touch listener.
+ */
+ public void setOutsideTouchListener(OnClickListener onClickListener) {
+ mHasOutsideTouch = true;
+ requestLayout();
+ setOnClickListener(onClickListener);
+ setClickable(true);
+ setFocusable(true);
+ }
+
+ /**
+ * Retrieve the MultiListLayout associated with the given view.
+ */
+ public static MultiListLayout get(View v) {
+ if (v instanceof MultiListLayout) return (MultiListLayout) v;
+ if (v.getParent() instanceof View) {
+ return get((View) v.getParent());
+ }
+ return null;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
index 5347a5cb16b6..f66a57b6133c 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
@@ -40,9 +40,10 @@ import com.android.systemui.statusbar.NotificationListener;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationLockscreenUserManagerImpl;
import com.android.systemui.statusbar.ScrimView;
-import com.android.systemui.statusbar.notification.NotificationData;
+import com.android.systemui.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider;
+import com.android.systemui.statusbar.notification.collection.NotificationData;
import com.android.systemui.statusbar.phone.DozeParameters;
import com.android.systemui.statusbar.phone.KeyguardBouncer;
import com.android.systemui.statusbar.phone.KeyguardEnvironmentImpl;
@@ -136,8 +137,8 @@ public class SystemUIFactory {
}
public NotificationIconAreaController createNotificationIconAreaController(Context context,
- StatusBar statusBar) {
- return new NotificationIconAreaController(context, statusBar);
+ StatusBar statusBar, StatusBarStateController statusBarStateController) {
+ return new NotificationIconAreaController(context, statusBar, statusBarStateController);
}
public KeyguardIndicationController createKeyguardIndicationController(Context context,
diff --git a/packages/SystemUI/src/com/android/systemui/appops/AppOpItem.java b/packages/SystemUI/src/com/android/systemui/appops/AppOpItem.java
index 9f363f681b04..7e5b42653210 100644
--- a/packages/SystemUI/src/com/android/systemui/appops/AppOpItem.java
+++ b/packages/SystemUI/src/com/android/systemui/appops/AppOpItem.java
@@ -25,12 +25,20 @@ public class AppOpItem {
private int mUid;
private String mPackageName;
private long mTimeStarted;
+ private String mState;
public AppOpItem(int code, int uid, String packageName, long timeStarted) {
this.mCode = code;
this.mUid = uid;
this.mPackageName = packageName;
this.mTimeStarted = timeStarted;
+ mState = new StringBuilder()
+ .append("AppOpItem(")
+ .append("Op code=").append(code).append(", ")
+ .append("UID=").append(uid).append(", ")
+ .append("Package name=").append(packageName)
+ .append(")")
+ .toString();
}
public int getCode() {
@@ -48,4 +56,9 @@ public class AppOpItem {
public long getTimeStarted() {
return mTimeStarted;
}
+
+ @Override
+ public String toString() {
+ return mState;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java b/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java
index e3bcb37474db..c013df385987 100644
--- a/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java
@@ -29,7 +29,10 @@ import android.util.Log;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.Dumpable;
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
@@ -47,7 +50,7 @@ import javax.inject.Singleton;
@Singleton
public class AppOpsControllerImpl implements AppOpsController,
AppOpsManager.OnOpActiveChangedListener,
- AppOpsManager.OnOpNotedListener {
+ AppOpsManager.OnOpNotedListener, Dumpable {
private static final long NOTED_OP_TIME_DELAY_MS = 5000;
private static final String TAG = "AppOpsControllerImpl";
@@ -271,6 +274,22 @@ public class AppOpsControllerImpl implements AppOpsController,
}
}
+ @Override
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ pw.println("AppOpsController state:");
+ pw.println(" Active Items:");
+ for (int i = 0; i < mActiveItems.size(); i++) {
+ final AppOpItem item = mActiveItems.get(i);
+ pw.print(" "); pw.println(item.toString());
+ }
+ pw.println(" Noted Items:");
+ for (int i = 0; i < mNotedItems.size(); i++) {
+ final AppOpItem item = mNotedItems.get(i);
+ pw.print(" "); pw.println(item.toString());
+ }
+
+ }
+
protected final class H extends Handler {
H(Looper looper) {
super(looper);
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogImpl.java b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogImpl.java
index 3167b9e0a458..016b8fe93093 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogImpl.java
@@ -33,9 +33,6 @@ import com.android.internal.os.SomeArgs;
import com.android.systemui.SystemUI;
import com.android.systemui.statusbar.CommandQueue;
-import java.util.HashMap;
-import java.util.Map;
-
/**
* Receives messages sent from AuthenticationClient and shows the appropriate biometric UI (e.g.
* BiometricDialogView).
@@ -52,10 +49,8 @@ public class BiometricDialogImpl extends SystemUI implements CommandQueue.Callba
private static final int MSG_BUTTON_NEGATIVE = 6;
private static final int MSG_USER_CANCELED = 7;
private static final int MSG_BUTTON_POSITIVE = 8;
- private static final int MSG_BIOMETRIC_SHOW_TRY_AGAIN = 9;
- private static final int MSG_TRY_AGAIN_PRESSED = 10;
+ private static final int MSG_TRY_AGAIN_PRESSED = 9;
- private Map<Integer, BiometricDialogView> mDialogs; // BiometricAuthenticator type, view
private SomeArgs mCurrentDialogArgs;
private BiometricDialogView mCurrentDialog;
private WindowManager mWindowManager;
@@ -63,21 +58,22 @@ public class BiometricDialogImpl extends SystemUI implements CommandQueue.Callba
private boolean mDialogShowing;
private Callback mCallback = new Callback();
- private boolean mTryAgainShowing; // No good place to save state before config change :/
- private boolean mConfirmShowing; // No good place to save state before config change :/
-
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch(msg.what) {
case MSG_SHOW_DIALOG:
- handleShowDialog((SomeArgs) msg.obj, false /* skipAnimation */);
+ handleShowDialog((SomeArgs) msg.obj, false /* skipAnimation */,
+ null /* savedState */);
break;
case MSG_BIOMETRIC_AUTHENTICATED:
- handleBiometricAuthenticated();
+ handleBiometricAuthenticated((boolean) msg.obj);
break;
case MSG_BIOMETRIC_HELP:
- handleBiometricHelp((String) msg.obj);
+ SomeArgs args = (SomeArgs) msg.obj;
+ handleBiometricHelp((String) args.arg1 /* message */,
+ (boolean) args.arg2 /* requireTryAgain */);
+ args.recycle();
break;
case MSG_BIOMETRIC_ERROR:
handleBiometricError((String) msg.obj);
@@ -94,9 +90,6 @@ public class BiometricDialogImpl extends SystemUI implements CommandQueue.Callba
case MSG_BUTTON_POSITIVE:
handleButtonPositive();
break;
- case MSG_BIOMETRIC_SHOW_TRY_AGAIN:
- handleShowTryAgain();
- break;
case MSG_TRY_AGAIN_PRESSED:
handleTryAgainPressed();
break;
@@ -137,30 +130,22 @@ public class BiometricDialogImpl extends SystemUI implements CommandQueue.Callba
@Override
public void start() {
- createDialogs();
-
- if (!mDialogs.isEmpty()) {
+ final PackageManager pm = mContext.getPackageManager();
+ if (pm.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)
+ || pm.hasSystemFeature(PackageManager.FEATURE_FACE)
+ || pm.hasSystemFeature(PackageManager.FEATURE_IRIS)) {
getComponent(CommandQueue.class).addCallback(this);
mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
}
}
- private void createDialogs() {
- final PackageManager pm = mContext.getPackageManager();
- mDialogs = new HashMap<>();
- if (pm.hasSystemFeature(PackageManager.FEATURE_FACE)) {
- mDialogs.put(BiometricAuthenticator.TYPE_FACE, new FaceDialogView(mContext, mCallback));
- }
- if (pm.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)) {
- mDialogs.put(BiometricAuthenticator.TYPE_FINGERPRINT,
- new FingerprintDialogView(mContext, mCallback));
- }
- }
-
@Override
public void showBiometricDialog(Bundle bundle, IBiometricServiceReceiverInternal receiver,
int type, boolean requireConfirmation, int userId) {
- if (DEBUG) Log.d(TAG, "showBiometricDialog, type: " + type);
+ if (DEBUG) {
+ Log.d(TAG, "showBiometricDialog, type: " + type
+ + ", requireConfirmation: " + requireConfirmation);
+ }
// Remove these messages as they are part of the previous client
mHandler.removeMessages(MSG_BIOMETRIC_ERROR);
mHandler.removeMessages(MSG_BIOMETRIC_HELP);
@@ -176,15 +161,18 @@ public class BiometricDialogImpl extends SystemUI implements CommandQueue.Callba
}
@Override
- public void onBiometricAuthenticated() {
- if (DEBUG) Log.d(TAG, "onBiometricAuthenticated");
- mHandler.obtainMessage(MSG_BIOMETRIC_AUTHENTICATED).sendToTarget();
+ public void onBiometricAuthenticated(boolean authenticated) {
+ if (DEBUG) Log.d(TAG, "onBiometricAuthenticated: " + authenticated);
+ mHandler.obtainMessage(MSG_BIOMETRIC_AUTHENTICATED, authenticated).sendToTarget();
}
@Override
public void onBiometricHelp(String message) {
if (DEBUG) Log.d(TAG, "onBiometricHelp: " + message);
- mHandler.obtainMessage(MSG_BIOMETRIC_HELP, message).sendToTarget();
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = message;
+ args.arg2 = false; // requireTryAgain
+ mHandler.obtainMessage(MSG_BIOMETRIC_HELP, args).sendToTarget();
}
@Override
@@ -199,16 +187,21 @@ public class BiometricDialogImpl extends SystemUI implements CommandQueue.Callba
mHandler.obtainMessage(MSG_HIDE_DIALOG, false /* userCanceled */).sendToTarget();
}
- @Override
- public void showBiometricTryAgain() {
- if (DEBUG) Log.d(TAG, "showBiometricTryAgain");
- mHandler.obtainMessage(MSG_BIOMETRIC_SHOW_TRY_AGAIN).sendToTarget();
- }
-
- private void handleShowDialog(SomeArgs args, boolean skipAnimation) {
+ private void handleShowDialog(SomeArgs args, boolean skipAnimation, Bundle savedState) {
mCurrentDialogArgs = args;
final int type = args.argi1;
- mCurrentDialog = mDialogs.get(type);
+
+ if (type == BiometricAuthenticator.TYPE_FINGERPRINT) {
+ mCurrentDialog = new FingerprintDialogView(mContext, mCallback);
+ } else if (type == BiometricAuthenticator.TYPE_FACE) {
+ mCurrentDialog = new FaceDialogView(mContext, mCallback);
+ } else {
+ Log.e(TAG, "Unsupported type: " + type);
+ }
+
+ if (savedState != null) {
+ mCurrentDialog.restoreState(savedState);
+ }
if (DEBUG) Log.d(TAG, "handleShowDialog, isAnimatingAway: "
+ mCurrentDialog.isAnimatingAway() + " type: " + type);
@@ -224,32 +217,36 @@ public class BiometricDialogImpl extends SystemUI implements CommandQueue.Callba
mCurrentDialog.setRequireConfirmation((boolean) args.arg3);
mCurrentDialog.setUserId(args.argi2);
mCurrentDialog.setSkipIntro(skipAnimation);
- mCurrentDialog.setPendingTryAgain(mTryAgainShowing);
- mCurrentDialog.setPendingConfirm(mConfirmShowing);
mWindowManager.addView(mCurrentDialog, mCurrentDialog.getLayoutParams());
mDialogShowing = true;
}
- private void handleBiometricAuthenticated() {
- if (DEBUG) Log.d(TAG, "handleBiometricAuthenticated");
-
- mCurrentDialog.announceForAccessibility(
- mContext.getResources()
- .getText(mCurrentDialog.getAuthenticatedAccessibilityResourceId()));
- if (mCurrentDialog.requiresConfirmation()) {
- mConfirmShowing = true;
- mCurrentDialog.showConfirmationButton(true /* show */);
+ private void handleBiometricAuthenticated(boolean authenticated) {
+ if (DEBUG) Log.d(TAG, "handleBiometricAuthenticated: " + authenticated);
+
+ if (authenticated) {
+ mCurrentDialog.announceForAccessibility(
+ mContext.getResources()
+ .getText(mCurrentDialog.getAuthenticatedAccessibilityResourceId()));
+ if (mCurrentDialog.requiresConfirmation()) {
+ mCurrentDialog.showConfirmationButton(true /* show */);
+ } else {
+ mCurrentDialog.updateState(BiometricDialogView.STATE_AUTHENTICATED);
+ mHandler.postDelayed(() -> {
+ handleHideDialog(false /* userCanceled */);
+ }, mCurrentDialog.getDelayAfterAuthenticatedDurationMs());
+ }
} else {
- mCurrentDialog.updateState(BiometricDialogView.STATE_AUTHENTICATED);
- mHandler.postDelayed(() -> {
- handleHideDialog(false /* userCanceled */);
- }, mCurrentDialog.getDelayAfterAuthenticatedDurationMs());
+ handleBiometricHelp(mContext.getResources()
+ .getString(com.android.internal.R.string.biometric_not_recognized),
+ true /* requireTryAgain */);
+ mCurrentDialog.showTryAgainButton(true /* show */);
}
}
- private void handleBiometricHelp(String message) {
+ private void handleBiometricHelp(String message, boolean requireTryAgain) {
if (DEBUG) Log.d(TAG, "handleBiometricHelp: " + message);
- mCurrentDialog.showHelpMessage(message);
+ mCurrentDialog.showHelpMessage(message, requireTryAgain);
}
private void handleBiometricError(String error) {
@@ -258,7 +255,6 @@ public class BiometricDialogImpl extends SystemUI implements CommandQueue.Callba
if (DEBUG) Log.d(TAG, "Dialog already dismissed");
return;
}
- mTryAgainShowing = false;
mCurrentDialog.showErrorMessage(error);
}
@@ -279,8 +275,6 @@ public class BiometricDialogImpl extends SystemUI implements CommandQueue.Callba
}
mReceiver = null;
mDialogShowing = false;
- mConfirmShowing = false;
- mTryAgainShowing = false;
mCurrentDialog.startDismiss();
}
@@ -294,7 +288,6 @@ public class BiometricDialogImpl extends SystemUI implements CommandQueue.Callba
} catch (RemoteException e) {
Log.e(TAG, "Remote exception when handling negative button", e);
}
- mTryAgainShowing = false;
handleHideDialog(false /* userCanceled */);
}
@@ -308,25 +301,16 @@ public class BiometricDialogImpl extends SystemUI implements CommandQueue.Callba
} catch (RemoteException e) {
Log.e(TAG, "Remote exception when handling positive button", e);
}
- mConfirmShowing = false;
handleHideDialog(false /* userCanceled */);
}
private void handleUserCanceled() {
- mTryAgainShowing = false;
- mConfirmShowing = false;
handleHideDialog(true /* userCanceled */);
}
- private void handleShowTryAgain() {
- mCurrentDialog.showTryAgainButton(true /* show */);
- mTryAgainShowing = true;
- }
-
private void handleTryAgainPressed() {
try {
mCurrentDialog.clearTemporaryMessage();
- mTryAgainShowing = false;
mReceiver.onTryAgainPressed();
} catch (RemoteException e) {
Log.e(TAG, "RemoteException when handling try again", e);
@@ -337,13 +321,20 @@ public class BiometricDialogImpl extends SystemUI implements CommandQueue.Callba
protected void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
final boolean wasShowing = mDialogShowing;
+
+ // Save the state of the current dialog (buttons showing, etc)
+ final Bundle savedState = new Bundle();
+ if (mCurrentDialog != null) {
+ mCurrentDialog.onSaveState(savedState);
+ }
+
if (mDialogShowing) {
mCurrentDialog.forceRemove();
mDialogShowing = false;
}
- createDialogs();
+
if (wasShowing) {
- handleShowDialog(mCurrentDialogArgs, true /* skipAnimation */);
+ handleShowDialog(mCurrentDialogArgs, true /* skipAnimation */, savedState);
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogView.java b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogView.java
index 9934bfd11f12..c92767763cb4 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogView.java
@@ -56,12 +56,15 @@ public abstract class BiometricDialogView extends LinearLayout {
private static final String TAG = "BiometricDialogView";
+ private static final String KEY_TRY_AGAIN_VISIBILITY = "key_try_again_visibility";
+ private static final String KEY_CONFIRM_VISIBILITY = "key_confirm_visibility";
+
private static final int ANIMATION_DURATION_SHOW = 250; // ms
private static final int ANIMATION_DURATION_AWAY = 350; // ms
private static final int MSG_CLEAR_MESSAGE = 1;
- protected static final int STATE_NONE = 0;
+ protected static final int STATE_IDLE = 0;
protected static final int STATE_AUTHENTICATING = 1;
protected static final int STATE_ERROR = 2;
protected static final int STATE_PENDING_CONFIRMATION = 3;
@@ -74,16 +77,24 @@ public abstract class BiometricDialogView extends LinearLayout {
private final DevicePolicyManager mDevicePolicyManager;
private final float mAnimationTranslationOffset;
private final int mErrorColor;
- private final int mTextColor;
private final float mDialogWidth;
private final DialogViewCallback mCallback;
- private ViewGroup mLayout;
- private final Button mPositiveButton;
- private final Button mNegativeButton;
- private final TextView mErrorText;
+ protected final ViewGroup mLayout;
+ protected final LinearLayout mDialog;
+ protected final TextView mTitleText;
+ protected final TextView mSubtitleText;
+ protected final TextView mDescriptionText;
+ protected final ImageView mBiometricIcon;
+ protected final TextView mErrorText;
+ protected final Button mPositiveButton;
+ protected final Button mNegativeButton;
+ protected final Button mTryAgainButton;
+
+ protected final int mTextColor;
+
private Bundle mBundle;
- private final LinearLayout mDialog;
+
private int mLastState;
private boolean mAnimatingAway;
private boolean mWasForceRemoved;
@@ -91,15 +102,14 @@ public abstract class BiometricDialogView extends LinearLayout {
protected boolean mRequireConfirmation;
private int mUserId; // used to determine if we should show work background
- private boolean mPendingShowTryAgain;
- private boolean mPendingShowConfirm;
-
protected abstract int getHintStringResourceId();
protected abstract int getAuthenticatedAccessibilityResourceId();
protected abstract int getIconDescriptionResourceId();
protected abstract Drawable getAnimationForTransition(int oldState, int newState);
protected abstract boolean shouldAnimateForTransition(int oldState, int newState);
protected abstract int getDelayAfterAuthenticatedDurationMs();
+ protected abstract boolean shouldGrayAreaDismissDialog();
+ protected abstract void handleClearMessage(boolean requireTryAgain);
private final Runnable mShowAnimationRunnable = new Runnable() {
@Override
@@ -124,7 +134,7 @@ public abstract class BiometricDialogView extends LinearLayout {
public void handleMessage(Message msg) {
switch(msg.what) {
case MSG_CLEAR_MESSAGE:
- handleClearMessage();
+ handleClearMessage((boolean) msg.obj /* requireTryAgain */);
break;
default:
Log.e(TAG, "Unhandled message: " + msg.what);
@@ -158,10 +168,6 @@ public abstract class BiometricDialogView extends LinearLayout {
mLayout = (ViewGroup) factory.inflate(R.layout.biometric_dialog, this, false);
addView(mLayout);
- mDialog = mLayout.findViewById(R.id.dialog);
-
- mErrorText = mLayout.findViewById(R.id.error);
-
mLayout.setOnKeyListener(new View.OnKeyListener() {
boolean downPressed = false;
@Override
@@ -184,12 +190,19 @@ public abstract class BiometricDialogView extends LinearLayout {
final View space = mLayout.findViewById(R.id.space);
final View leftSpace = mLayout.findViewById(R.id.left_space);
final View rightSpace = mLayout.findViewById(R.id.right_space);
- final ImageView icon = mLayout.findViewById(R.id.biometric_icon);
- final Button tryAgain = mLayout.findViewById(R.id.button_try_again);
+
+ mDialog = mLayout.findViewById(R.id.dialog);
+ mTitleText = mLayout.findViewById(R.id.title);
+ mSubtitleText = mLayout.findViewById(R.id.subtitle);
+ mDescriptionText = mLayout.findViewById(R.id.description);
+ mBiometricIcon = mLayout.findViewById(R.id.biometric_icon);
+ mErrorText = mLayout.findViewById(R.id.error);
mNegativeButton = mLayout.findViewById(R.id.button2);
mPositiveButton = mLayout.findViewById(R.id.button1);
+ mTryAgainButton = mLayout.findViewById(R.id.button_try_again);
- icon.setContentDescription(getResources().getString(getIconDescriptionResourceId()));
+ mBiometricIcon.setContentDescription(
+ getResources().getString(getIconDescriptionResourceId()));
setDismissesDialog(space);
setDismissesDialog(leftSpace);
@@ -206,8 +219,9 @@ public abstract class BiometricDialogView extends LinearLayout {
}, getDelayAfterAuthenticatedDurationMs());
});
- tryAgain.setOnClickListener((View v) -> {
+ mTryAgainButton.setOnClickListener((View v) -> {
showTryAgainButton(false /* show */);
+ handleClearMessage(false /* requireTryAgain */);
mCallback.onTryAgainPressed();
});
@@ -215,15 +229,17 @@ public abstract class BiometricDialogView extends LinearLayout {
mLayout.requestFocus();
}
+ public void onSaveState(Bundle bundle) {
+ bundle.putInt(KEY_TRY_AGAIN_VISIBILITY, mTryAgainButton.getVisibility());
+ bundle.putInt(KEY_CONFIRM_VISIBILITY, mPositiveButton.getVisibility());
+ }
+
@Override
public void onAttachedToWindow() {
super.onAttachedToWindow();
mErrorText.setText(getHintStringResourceId());
- final TextView title = mLayout.findViewById(R.id.title);
- final TextView subtitle = mLayout.findViewById(R.id.subtitle);
- final TextView description = mLayout.findViewById(R.id.description);
final ImageView backgroundView = mLayout.findViewById(R.id.background);
if (mUserManager.isManagedProfile(mUserId)) {
@@ -244,36 +260,34 @@ public abstract class BiometricDialogView extends LinearLayout {
mDialog.getLayoutParams().width = (int) mDialogWidth;
}
- mLastState = STATE_NONE;
+ mLastState = STATE_IDLE;
updateState(STATE_AUTHENTICATING);
CharSequence titleText = mBundle.getCharSequence(BiometricPrompt.KEY_TITLE);
- title.setText(titleText);
- title.setSelected(true);
+ mTitleText.setVisibility(View.VISIBLE);
+ mTitleText.setText(titleText);
+ mTitleText.setSelected(true);
final CharSequence subtitleText = mBundle.getCharSequence(BiometricPrompt.KEY_SUBTITLE);
if (TextUtils.isEmpty(subtitleText)) {
- subtitle.setVisibility(View.GONE);
+ mSubtitleText.setVisibility(View.GONE);
} else {
- subtitle.setVisibility(View.VISIBLE);
- subtitle.setText(subtitleText);
+ mSubtitleText.setVisibility(View.VISIBLE);
+ mSubtitleText.setText(subtitleText);
}
final CharSequence descriptionText =
mBundle.getCharSequence(BiometricPrompt.KEY_DESCRIPTION);
if (TextUtils.isEmpty(descriptionText)) {
- description.setVisibility(View.GONE);
+ mDescriptionText.setVisibility(View.GONE);
} else {
- description.setVisibility(View.VISIBLE);
- description.setText(descriptionText);
+ mDescriptionText.setVisibility(View.VISIBLE);
+ mDescriptionText.setText(descriptionText);
}
mNegativeButton.setText(mBundle.getCharSequence(BiometricPrompt.KEY_NEGATIVE_TEXT));
- showTryAgainButton(mPendingShowTryAgain);
- showConfirmationButton(mPendingShowConfirm);
-
if (mWasForceRemoved || mSkipIntro) {
// Show the dialog immediately
mLayout.animate().cancel();
@@ -302,8 +316,7 @@ public abstract class BiometricDialogView extends LinearLayout {
? (AnimatedVectorDrawable) icon
: null;
- final ImageView imageView = getLayout().findViewById(R.id.biometric_icon);
- imageView.setImageDrawable(icon);
+ mBiometricIcon.setImageDrawable(icon);
if (animation != null && shouldAnimateForTransition(lastState, newState)) {
animation.forceAnimationOnUI();
@@ -314,7 +327,7 @@ public abstract class BiometricDialogView extends LinearLayout {
private void setDismissesDialog(View v) {
v.setClickable(true);
v.setOnTouchListener((View view, MotionEvent event) -> {
- if (mLastState != STATE_AUTHENTICATED) {
+ if (mLastState != STATE_AUTHENTICATED && shouldGrayAreaDismissDialog()) {
mCallback.onUserCanceled();
}
return true;
@@ -331,11 +344,9 @@ public abstract class BiometricDialogView extends LinearLayout {
mWindowManager.removeView(BiometricDialogView.this);
mAnimatingAway = false;
// Set the icons / text back to normal state
- handleClearMessage();
+ handleClearMessage(false /* requireTryAgain */);
showTryAgainButton(false /* show */);
- mPendingShowTryAgain = false;
- mPendingShowConfirm = false;
- updateState(STATE_NONE);
+ updateState(STATE_IDLE);
}
};
@@ -412,35 +423,28 @@ public abstract class BiometricDialogView extends LinearLayout {
return mLayout;
}
- // Clears the temporary message and shows the help message.
- private void handleClearMessage() {
- updateState(STATE_AUTHENTICATING);
- mErrorText.setText(getHintStringResourceId());
- mErrorText.setTextColor(mTextColor);
- }
-
// Shows an error/help message
- private void showTemporaryMessage(String message) {
+ private void showTemporaryMessage(String message, boolean requireTryAgain) {
mHandler.removeMessages(MSG_CLEAR_MESSAGE);
updateState(STATE_ERROR);
mErrorText.setText(message);
mErrorText.setTextColor(mErrorColor);
mErrorText.setContentDescription(message);
- mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_CLEAR_MESSAGE),
+ mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_CLEAR_MESSAGE, requireTryAgain),
BiometricPrompt.HIDE_DIALOG_DELAY);
}
public void clearTemporaryMessage() {
mHandler.removeMessages(MSG_CLEAR_MESSAGE);
- mHandler.obtainMessage(MSG_CLEAR_MESSAGE).sendToTarget();
+ mHandler.obtainMessage(MSG_CLEAR_MESSAGE, false /* requireTryAgain */).sendToTarget();
}
- public void showHelpMessage(String message) {
- showTemporaryMessage(message);
+ public void showHelpMessage(String message, boolean requireTryAgain) {
+ showTemporaryMessage(message, requireTryAgain);
}
public void showErrorMessage(String error) {
- showTemporaryMessage(error);
+ showTemporaryMessage(error, false /* requireTryAgain */);
showTryAgainButton(false /* show */);
mCallback.onErrorShown();
}
@@ -459,22 +463,11 @@ public abstract class BiometricDialogView extends LinearLayout {
}
public void showTryAgainButton(boolean show) {
- final Button tryAgain = mLayout.findViewById(R.id.button_try_again);
- if (show) {
- tryAgain.setVisibility(View.VISIBLE);
- } else {
- tryAgain.setVisibility(View.GONE);
- }
- }
-
- // Set the state before the window is attached, so we know if the dialog should be started
- // with or without the button. This is because there's no good onPause signal
- public void setPendingTryAgain(boolean show) {
- mPendingShowTryAgain = show;
}
- public void setPendingConfirm(boolean show) {
- mPendingShowConfirm = show;
+ public void restoreState(Bundle bundle) {
+ mTryAgainButton.setVisibility(bundle.getInt(KEY_TRY_AGAIN_VISIBILITY));
+ mPositiveButton.setVisibility(bundle.getInt(KEY_CONFIRM_VISIBILITY));
}
public WindowManager.LayoutParams getLayoutParams() {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/FaceDialogView.java b/packages/SystemUI/src/com/android/systemui/biometrics/FaceDialogView.java
index de3f9471a6ba..9fba44b76863 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/FaceDialogView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/FaceDialogView.java
@@ -16,8 +16,18 @@
package com.android.systemui.biometrics;
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ValueAnimator;
import android.content.Context;
+import android.graphics.Outline;
import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.text.TextUtils;
+import android.util.DisplayMetrics;
+import android.view.View;
+import android.view.ViewOutlineProvider;
import com.android.systemui.R;
@@ -28,13 +38,263 @@ import com.android.systemui.R;
*/
public class FaceDialogView extends BiometricDialogView {
+ private static final String TAG = "FaceDialogView";
+ private static final String KEY_DIALOG_SIZE = "key_dialog_size";
+
private static final int HIDE_DIALOG_DELAY = 500; // ms
+ private static final int IMPLICIT_Y_PADDING = 16; // dp
+ private static final int GROW_DURATION = 150; // ms
+ private static final int TEXT_ANIMATE_DISTANCE = 32; // dp
+
+ private static final int SIZE_UNKNOWN = 0;
+ private static final int SIZE_SMALL = 1;
+ private static final int SIZE_GROWING = 2;
+ private static final int SIZE_BIG = 3;
+
+ private int mSize;
+ private float mIconOriginalY;
+ private DialogOutlineProvider mOutlineProvider = new DialogOutlineProvider();
+
+ private final class DialogOutlineProvider extends ViewOutlineProvider {
+
+ float mY;
+
+ @Override
+ public void getOutline(View view, Outline outline) {
+ outline.setRoundRect(
+ 0 /* left */,
+ (int) mY, /* top */
+ mDialog.getWidth() /* right */,
+ mDialog.getBottom(), /* bottom */
+ getResources().getDimension(R.dimen.biometric_dialog_corner_size));
+ }
+
+ int calculateSmall() {
+ final float padding = dpToPixels(IMPLICIT_Y_PADDING);
+ return mDialog.getHeight() - mBiometricIcon.getHeight() - 2 * (int) padding;
+ }
+
+ void setOutlineY(float y) {
+ mY = y;
+ }
+ }
public FaceDialogView(Context context,
DialogViewCallback callback) {
super(context, callback);
}
+ private void updateSize(int newSize) {
+ final float padding = dpToPixels(IMPLICIT_Y_PADDING);
+ final float iconSmallPositionY = mDialog.getHeight() - mBiometricIcon.getHeight() - padding;
+
+ if (newSize == SIZE_SMALL) {
+ // These fields are required and/or always hold a spot on the UI, so should be set to
+ // INVISIBLE so they keep their position
+ mTitleText.setVisibility(View.INVISIBLE);
+ mErrorText.setVisibility(View.INVISIBLE);
+ mNegativeButton.setVisibility(View.INVISIBLE);
+
+ // These fields are optional, so set them to gone or invisible depending on their
+ // usage. If they're empty, they're already set to GONE in BiometricDialogView.
+ if (!TextUtils.isEmpty(mSubtitleText.getText())) {
+ mSubtitleText.setVisibility(View.INVISIBLE);
+ }
+ if (!TextUtils.isEmpty(mDescriptionText.getText())) {
+ mDescriptionText.setVisibility(View.INVISIBLE);
+ }
+
+ // Move the biometric icon to the small spot
+ mBiometricIcon.setY(iconSmallPositionY);
+
+ // Clip the dialog to the small size
+ mDialog.setOutlineProvider(mOutlineProvider);
+ mOutlineProvider.setOutlineY(mOutlineProvider.calculateSmall());
+
+ mDialog.setClipToOutline(true);
+ mDialog.invalidateOutline();
+
+ mSize = newSize;
+ } else if (mSize == SIZE_SMALL && newSize == SIZE_BIG) {
+ mSize = SIZE_GROWING;
+
+ // Animate the outline
+ final ValueAnimator outlineAnimator =
+ ValueAnimator.ofFloat(mOutlineProvider.calculateSmall(), 0);
+ outlineAnimator.addUpdateListener((animation) -> {
+ final float y = (float) animation.getAnimatedValue();
+ mOutlineProvider.setOutlineY(y);
+ mDialog.invalidateOutline();
+ });
+
+ // Animate the icon back to original big position
+ final ValueAnimator iconAnimator =
+ ValueAnimator.ofFloat(iconSmallPositionY, mIconOriginalY);
+ iconAnimator.addUpdateListener((animation) -> {
+ final float y = (float) animation.getAnimatedValue();
+ mBiometricIcon.setY(y);
+ });
+
+ // Animate the error text so it slides up with the icon
+ final ValueAnimator textSlideAnimator =
+ ValueAnimator.ofFloat(dpToPixels(TEXT_ANIMATE_DISTANCE), 0);
+ textSlideAnimator.addUpdateListener((animation) -> {
+ final float y = (float) animation.getAnimatedValue();
+ mErrorText.setTranslationY(y);
+ });
+
+ // Opacity animator for things that should fade in (title, subtitle, details, negative
+ // button)
+ final ValueAnimator opacityAnimator = ValueAnimator.ofFloat(0, 1);
+ opacityAnimator.addUpdateListener((animation) -> {
+ final float opacity = (float) animation.getAnimatedValue();
+
+ // These fields are required and/or always hold a spot on the UI
+ mTitleText.setAlpha(opacity);
+ mErrorText.setAlpha(opacity);
+ mNegativeButton.setAlpha(opacity);
+ mTryAgainButton.setAlpha(opacity);
+
+ // These fields are optional, so only animate them if they're supposed to be showing
+ if (!TextUtils.isEmpty(mSubtitleText.getText())) {
+ mSubtitleText.setAlpha(opacity);
+ }
+ if (!TextUtils.isEmpty(mDescriptionText.getText())) {
+ mDescriptionText.setAlpha(opacity);
+ }
+ });
+
+ // Choreograph together
+ final AnimatorSet as = new AnimatorSet();
+ as.setDuration(GROW_DURATION);
+ as.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ super.onAnimationStart(animation);
+ // Set the visibility of opacity-animating views back to VISIBLE
+ mTitleText.setVisibility(View.VISIBLE);
+ mErrorText.setVisibility(View.VISIBLE);
+ mNegativeButton.setVisibility(View.VISIBLE);
+ mTryAgainButton.setVisibility(View.VISIBLE);
+
+ if (!TextUtils.isEmpty(mSubtitleText.getText())) {
+ mSubtitleText.setVisibility(View.VISIBLE);
+ }
+ if (!TextUtils.isEmpty(mDescriptionText.getText())) {
+ mDescriptionText.setVisibility(View.VISIBLE);
+ }
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ super.onAnimationEnd(animation);
+ mSize = SIZE_BIG;
+ }
+ });
+ as.play(outlineAnimator).with(iconAnimator).with(opacityAnimator)
+ .with(textSlideAnimator);
+ as.start();
+ } else if (mSize == SIZE_BIG) {
+ mDialog.setClipToOutline(false);
+ mDialog.invalidateOutline();
+
+ mBiometricIcon.setY(mIconOriginalY);
+
+ mSize = newSize;
+ }
+ }
+
+ @Override
+ public void onSaveState(Bundle bundle) {
+ super.onSaveState(bundle);
+ bundle.putInt(KEY_DIALOG_SIZE, mSize);
+ }
+
+
+ @Override
+ protected void handleClearMessage(boolean requireTryAgain) {
+ // Clears the temporary message and shows the help message. If requireTryAgain is true,
+ // we will start the authenticating state again.
+ if (!requireTryAgain) {
+ updateState(STATE_AUTHENTICATING);
+ mErrorText.setText(getHintStringResourceId());
+ mErrorText.setTextColor(mTextColor);
+ mErrorText.setVisibility(View.VISIBLE);
+ } else {
+ updateState(STATE_IDLE);
+ mErrorText.setVisibility(View.INVISIBLE);
+ }
+ }
+
+ @Override
+ public void restoreState(Bundle bundle) {
+ super.restoreState(bundle);
+ // Keep in mind that this happens before onAttachedToWindow()
+ mSize = bundle.getInt(KEY_DIALOG_SIZE);
+ }
+
+ /**
+ * Do small/big layout here instead of onAttachedToWindow, since:
+ * 1) We need the big layout to be measured, etc for small -> big animation
+ * 2) We need the dialog measurements to know where to move the biometric icon to
+ *
+ * BiometricDialogView already sets the views to their default big state, so here we only
+ * need to hide the ones that are unnecessary.
+ */
+ @Override
+ public void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ super.onLayout(changed, left, top, right, bottom);
+
+ if (mIconOriginalY == 0) {
+ mIconOriginalY = mBiometricIcon.getY();
+ }
+
+ // UNKNOWN means size hasn't been set yet. First time we create the dialog.
+ // onLayout can happen when visibility of views change (during animation, etc).
+ if (mSize != SIZE_UNKNOWN) {
+ // Probably not the cleanest way to do this, but since dialog is big by default,
+ // and small dialogs can persist across orientation changes, we need to set it to
+ // small size here again.
+ if (mSize == SIZE_SMALL) {
+ updateSize(SIZE_SMALL);
+ }
+ return;
+ }
+
+ // If we don't require confirmation, show the small dialog first (until errors occur).
+ if (!requiresConfirmation()) {
+ updateSize(SIZE_SMALL);
+ } else {
+ updateSize(SIZE_BIG);
+ }
+ }
+
+ @Override
+ public void showErrorMessage(String error) {
+ super.showErrorMessage(error);
+
+ // All error messages will cause the dialog to go from small -> big. Error messages
+ // are messages such as lockout, auth failed, etc.
+ if (mSize == SIZE_SMALL) {
+ updateSize(SIZE_BIG);
+ }
+ }
+
+ @Override
+ public void showTryAgainButton(boolean show) {
+ if (show && mSize == SIZE_SMALL) {
+ // Do not call super, we will nicely animate the alpha together with the rest
+ // of the elements in here.
+ updateSize(SIZE_BIG);
+ } else {
+ if (show) {
+ mTryAgainButton.setVisibility(View.VISIBLE);
+ } else {
+ mTryAgainButton.setVisibility(View.GONE);
+ }
+ }
+ }
+
@Override
protected int getHintStringResourceId() {
return R.string.face_dialog_looking_for_face;
@@ -56,7 +316,9 @@ public class FaceDialogView extends BiometricDialogView {
@Override
protected boolean shouldAnimateForTransition(int oldState, int newState) {
- if (oldState == STATE_NONE && newState == STATE_AUTHENTICATING) {
+ if (oldState == STATE_ERROR && newState == STATE_IDLE) {
+ return true;
+ } else if (oldState == STATE_IDLE && newState == STATE_AUTHENTICATING) {
return false;
} else if (oldState == STATE_AUTHENTICATING && newState == STATE_ERROR) {
return true;
@@ -78,9 +340,19 @@ public class FaceDialogView extends BiometricDialogView {
}
@Override
+ protected boolean shouldGrayAreaDismissDialog() {
+ if (mSize == SIZE_SMALL) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
protected Drawable getAnimationForTransition(int oldState, int newState) {
int iconRes;
- if (oldState == STATE_NONE && newState == STATE_AUTHENTICATING) {
+ if (oldState == STATE_ERROR && newState == STATE_IDLE) {
+ iconRes = R.drawable.face_dialog_error_to_face;
+ } else if (oldState == STATE_IDLE && newState == STATE_AUTHENTICATING) {
iconRes = R.drawable.face_dialog_face_to_error;
} else if (oldState == STATE_AUTHENTICATING && newState == STATE_ERROR) {
iconRes = R.drawable.face_dialog_face_to_error;
@@ -97,4 +369,14 @@ public class FaceDialogView extends BiometricDialogView {
}
return mContext.getDrawable(iconRes);
}
+
+ private float dpToPixels(float dp) {
+ return dp * ((float) mContext.getResources().getDisplayMetrics().densityDpi
+ / DisplayMetrics.DENSITY_DEFAULT);
+ }
+
+ private float pixelsToDp(float pixels) {
+ return pixels / ((float) mContext.getResources().getDisplayMetrics().densityDpi
+ / DisplayMetrics.DENSITY_DEFAULT);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/FingerprintDialogView.java b/packages/SystemUI/src/com/android/systemui/biometrics/FingerprintDialogView.java
index 1a6cee281c84..c9b30ba3ce8d 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/FingerprintDialogView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/FingerprintDialogView.java
@@ -32,6 +32,14 @@ public class FingerprintDialogView extends BiometricDialogView {
DialogViewCallback callback) {
super(context, callback);
}
+
+ @Override
+ protected void handleClearMessage(boolean requireTryAgain) {
+ updateState(STATE_AUTHENTICATING);
+ mErrorText.setText(getHintStringResourceId());
+ mErrorText.setTextColor(mTextColor);
+ }
+
@Override
protected int getHintStringResourceId() {
return R.string.fingerprint_dialog_touch_sensor;
@@ -49,7 +57,7 @@ public class FingerprintDialogView extends BiometricDialogView {
@Override
protected boolean shouldAnimateForTransition(int oldState, int newState) {
- if (oldState == STATE_NONE && newState == STATE_AUTHENTICATING) {
+ if (oldState == STATE_IDLE && newState == STATE_AUTHENTICATING) {
return false;
} else if (oldState == STATE_AUTHENTICATING && newState == STATE_ERROR) {
return true;
@@ -68,9 +76,15 @@ public class FingerprintDialogView extends BiometricDialogView {
}
@Override
+ protected boolean shouldGrayAreaDismissDialog() {
+ // Fingerprint dialog always dismisses when region outside the dialog is tapped
+ return true;
+ }
+
+ @Override
protected Drawable getAnimationForTransition(int oldState, int newState) {
int iconRes;
- if (oldState == STATE_NONE && newState == STATE_AUTHENTICATING) {
+ if (oldState == STATE_IDLE && newState == STATE_AUTHENTICATING) {
iconRes = R.drawable.fingerprint_dialog_fp_to_error;
} else if (oldState == STATE_AUTHENTICATING && newState == STATE_ERROR) {
iconRes = R.drawable.fingerprint_dialog_fp_to_error;
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BadgeRenderer.java b/packages/SystemUI/src/com/android/systemui/bubbles/BadgeRenderer.java
new file mode 100644
index 000000000000..845b08483064
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BadgeRenderer.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2018 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.bubbles;
+
+import static android.graphics.Paint.ANTI_ALIAS_FLAG;
+import static android.graphics.Paint.FILTER_BITMAP_FLAG;
+
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.util.Log;
+
+// XXX: Mostly opied from launcher code / can we share?
+/**
+ * Contains parameters necessary to draw a badge for an icon (e.g. the size of the badge).
+ */
+public class BadgeRenderer {
+
+ private static final String TAG = "BadgeRenderer";
+
+ // The badge sizes are defined as percentages of the app icon size.
+ private static final float SIZE_PERCENTAGE = 0.38f;
+
+ // Extra scale down of the dot
+ private static final float DOT_SCALE = 0.6f;
+
+ private final float mDotCenterOffset;
+ private final float mCircleRadius;
+ private final Paint mCirclePaint = new Paint(ANTI_ALIAS_FLAG | FILTER_BITMAP_FLAG);
+
+ public BadgeRenderer(int iconSizePx) {
+ mDotCenterOffset = SIZE_PERCENTAGE * iconSizePx;
+ int size = (int) (DOT_SCALE * mDotCenterOffset);
+ mCircleRadius = size / 2f;
+ }
+
+ /**
+ * Draw a circle in the top right corner of the given bounds.
+ *
+ * @param color The color (based on the icon) to use for the badge.
+ * @param iconBounds The bounds of the icon being badged.
+ * @param badgeScale The progress of the animation, from 0 to 1.
+ * @param spaceForOffset How much space to offset the badge up and to the left or right.
+ * @param onLeft Whether the badge should be draw on left or right side.
+ */
+ public void draw(Canvas canvas, int color, Rect iconBounds, float badgeScale,
+ Point spaceForOffset, boolean onLeft) {
+ if (iconBounds == null) {
+ Log.e(TAG, "Invalid null argument(s) passed in call to draw.");
+ return;
+ }
+ canvas.save();
+ // We draw the badge relative to its center.
+ int x = onLeft ? iconBounds.left : iconBounds.right;
+ float offset = onLeft ? (mDotCenterOffset / 2) : -(mDotCenterOffset / 2);
+ float badgeCenterX = x + offset;
+ float badgeCenterY = iconBounds.top + mDotCenterOffset / 2;
+
+ canvas.translate(badgeCenterX + spaceForOffset.x, badgeCenterY - spaceForOffset.y);
+
+ canvas.scale(badgeScale, badgeScale);
+ mCirclePaint.setColor(color);
+ canvas.drawCircle(0, 0, mCircleRadius, mCirclePaint);
+ canvas.restore();
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BadgedImageView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BadgedImageView.java
new file mode 100644
index 000000000000..92d3cc1ae34f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BadgedImageView.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2018 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.bubbles;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Path;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.widget.ImageView;
+
+import com.android.systemui.R;
+
+/**
+ * View that circle crops its contents and supports displaying a coloured dot on a top corner.
+ */
+public class BadgedImageView extends ImageView {
+
+ private BadgeRenderer mDotRenderer;
+ private int mIconSize;
+ private Rect mTempBounds = new Rect();
+ private Point mTempPoint = new Point();
+ private Path mClipPath = new Path();
+
+ private float mDotScale = 0f;
+ private int mUpdateDotColor;
+ private boolean mShowUpdateDot;
+ private boolean mOnLeft;
+
+ public BadgedImageView(Context context) {
+ this(context, null);
+ }
+
+ public BadgedImageView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public BadgedImageView(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public BadgedImageView(Context context, AttributeSet attrs, int defStyleAttr,
+ int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ setScaleType(ScaleType.CENTER_CROP);
+ mIconSize = getResources().getDimensionPixelSize(R.dimen.bubble_size);
+ mDotRenderer = new BadgeRenderer(mIconSize);
+ }
+
+ // TODO: Clipping oval path isn't great: rerender image into a separate, rounded bitmap and
+ // then draw would be better
+ @Override
+ public void onDraw(Canvas canvas) {
+ canvas.save();
+ // Circle crop
+ mClipPath.addOval(getPaddingStart(), getPaddingTop(),
+ getWidth() - getPaddingEnd(), getHeight() - getPaddingBottom(), Path.Direction.CW);
+ canvas.clipPath(mClipPath);
+ super.onDraw(canvas);
+
+ // After we've circle cropped what we're showing, restore so we don't clip the badge
+ canvas.restore();
+
+ // Draw the badge
+ if (mShowUpdateDot) {
+ getDrawingRect(mTempBounds);
+ mTempPoint.set((getWidth() - mIconSize) / 2, getPaddingTop());
+ mDotRenderer.draw(canvas, mUpdateDotColor, mTempBounds, mDotScale, mTempPoint,
+ mOnLeft);
+ }
+ }
+
+ /**
+ * Set whether the dot should appear on left or right side of the view.
+ */
+ public void setDotPosition(boolean onLeft) {
+ mOnLeft = onLeft;
+ invalidate();
+ }
+
+ /**
+ * Set whether the dot should show or not.
+ */
+ public void setShowDot(boolean showBadge) {
+ mShowUpdateDot = showBadge;
+ invalidate();
+ }
+
+ /**
+ * @return whether the dot is being displayed.
+ */
+ public boolean isShowingDot() {
+ return mShowUpdateDot;
+ }
+
+ /**
+ * The colour to use for the dot.
+ */
+ public void setDotColor(int color) {
+ mUpdateDotColor = color;
+ invalidate();
+ }
+
+ /**
+ * How big the dot should be, fraction from 0 to 1.
+ */
+ public void setDotScale(float fraction) {
+ mDotScale = fraction;
+ invalidate();
+ }
+
+ public float getDotScale() {
+ return mDotScale;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
index 881aa18285ff..957d772be730 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
@@ -21,25 +21,42 @@ import static android.view.View.VISIBLE;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
import static com.android.systemui.bubbles.BubbleMovementHelper.EDGE_OVERLAP;
+import static com.android.systemui.statusbar.StatusBarState.SHADE;
+import static com.android.systemui.statusbar.notification.NotificationAlertingManager.alertAgain;
+import android.annotation.Nullable;
+import android.app.INotificationManager;
import android.app.Notification;
+import android.app.PendingIntent;
import android.content.Context;
+import android.content.pm.ActivityInfo;
import android.graphics.Point;
import android.graphics.Rect;
+import android.os.RemoteException;
+import android.os.ServiceManager;
import android.provider.Settings;
import android.service.notification.StatusBarNotification;
+import android.util.Log;
+import android.view.LayoutInflater;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.FrameLayout;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.statusbar.NotificationVisibility;
+import com.android.systemui.Dependency;
import com.android.systemui.R;
-import com.android.systemui.statusbar.notification.NotificationData;
+import com.android.systemui.statusbar.StatusBarStateController;
+import com.android.systemui.statusbar.notification.NotificationEntryListener;
+import com.android.systemui.statusbar.notification.NotificationEntryManager;
+import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.row.NotificationInflater;
import com.android.systemui.statusbar.phone.StatusBarWindowController;
-import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
+import java.util.Set;
import javax.inject.Inject;
import javax.inject.Singleton;
@@ -57,45 +74,36 @@ public class BubbleController {
private static final String TAG = "BubbleController";
// Enables some subset of notifs to automatically become bubbles
- public static final boolean DEBUG_ENABLE_AUTO_BUBBLE = false;
- // When a bubble is dismissed, recreate it as a notification
- public static final boolean DEBUG_DEMOTE_TO_NOTIF = false;
+ private static final boolean DEBUG_ENABLE_AUTO_BUBBLE = false;
// Secure settings
private static final String ENABLE_AUTO_BUBBLE_MESSAGES = "experiment_autobubble_messaging";
private static final String ENABLE_AUTO_BUBBLE_ONGOING = "experiment_autobubble_ongoing";
private static final String ENABLE_AUTO_BUBBLE_ALL = "experiment_autobubble_all";
+ private static final String ENABLE_BUBBLE_ACTIVITY_VIEW = "experiment_bubble_activity_view";
+ private static final String ENABLE_BUBBLE_CONTENT_INTENT = "experiment_bubble_content_intent";
- private Context mContext;
- private BubbleDismissListener mDismissListener;
+ private final Context mContext;
+ private final NotificationEntryManager mNotificationEntryManager;
private BubbleStateChangeListener mStateChangeListener;
private BubbleExpandListener mExpandListener;
+ private LayoutInflater mInflater;
- private Map<String, BubbleView> mBubbles = new HashMap<>();
+ private final Map<String, BubbleView> mBubbles = new HashMap<>();
private BubbleStackView mStackView;
- private Point mDisplaySize;
+ private final Point mDisplaySize;
// Bubbles get added to the status bar view
- @VisibleForTesting
- protected StatusBarWindowController mStatusBarWindowController;
+ private final StatusBarWindowController mStatusBarWindowController;
+ private StatusBarStateListener mStatusBarStateListener;
- // Used for determining view rect for touch interaction
- private Rect mTempRect = new Rect();
+ private final NotificationInterruptionStateProvider mNotificationInterruptionStateProvider =
+ Dependency.get(NotificationInterruptionStateProvider.class);
- /**
- * Listener to find out about bubble / bubble stack dismissal events.
- */
- public interface BubbleDismissListener {
- /**
- * Called when the entire stack of bubbles is dismissed by the user.
- */
- void onStackDismissed();
+ private INotificationManager mNotificationManagerService;
- /**
- * Called when a specific bubble is dismissed by the user.
- */
- void onBubbleDismissed(String key);
- }
+ // Used for determining view rect for touch interaction
+ private Rect mTempRect = new Rect();
/**
* Listener to be notified when some states of the bubbles change.
@@ -113,11 +121,30 @@ public class BubbleController {
public interface BubbleExpandListener {
/**
* Called when the expansion state of the bubble stack changes.
- *
* @param isExpanding whether it's expanding or collapsing
- * @param amount fraction of how expanded or collapsed it is, 1 being fully, 0 at the start
+ * @param key the notification key associated with bubble being expanded
*/
- void onBubbleExpandChanged(boolean isExpanding, float amount);
+ void onBubbleExpandChanged(boolean isExpanding, String key);
+ }
+
+ /**
+ * Listens for the current state of the status bar and updates the visibility state
+ * of bubbles as needed.
+ */
+ private class StatusBarStateListener implements StatusBarStateController.StateListener {
+ private int mState;
+ /**
+ * Returns the current status bar state.
+ */
+ public int getCurrentState() {
+ return mState;
+ }
+
+ @Override
+ public void onStateChanged(int newState) {
+ mState = newState;
+ updateVisibility();
+ }
}
@Inject
@@ -126,14 +153,21 @@ public class BubbleController {
WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
mDisplaySize = new Point();
wm.getDefaultDisplay().getSize(mDisplaySize);
- mStatusBarWindowController = statusBarWindowController;
- }
+ mInflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
- /**
- * Set a listener to be notified of bubble dismissal events.
- */
- public void setDismissListener(BubbleDismissListener listener) {
- mDismissListener = listener;
+ mNotificationEntryManager = Dependency.get(NotificationEntryManager.class);
+ mNotificationEntryManager.addNotificationEntryListener(mEntryListener);
+
+ try {
+ mNotificationManagerService = INotificationManager.Stub.asInterface(
+ ServiceManager.getServiceOrThrow(Context.NOTIFICATION_SERVICE));
+ } catch (ServiceManager.ServiceNotFoundException e) {
+ e.printStackTrace();
+ }
+
+ mStatusBarWindowController = statusBarWindowController;
+ mStatusBarStateListener = new StatusBarStateListener();
+ Dependency.get(StatusBarStateController.class).addCallback(mStatusBarStateListener);
}
/**
@@ -158,7 +192,12 @@ public class BubbleController {
* screen (e.g. if on AOD).
*/
public boolean hasBubbles() {
- return mBubbles.size() > 0;
+ for (BubbleView bv : mBubbles.values()) {
+ if (!bv.getEntry().isBubbleDismissed()) {
+ return true;
+ }
+ }
+ return false;
}
/**
@@ -173,43 +212,43 @@ public class BubbleController {
*/
public void collapseStack() {
if (mStackView != null) {
- mStackView.animateExpansion(false);
+ mStackView.collapseStack();
}
}
/**
* Tell the stack of bubbles to be dismissed, this will remove all of the bubbles in the stack.
*/
- public void dismissStack() {
+ void dismissStack() {
if (mStackView == null) {
return;
}
- Point startPoint = getStartPoint(mStackView.getStackWidth(), mDisplaySize);
+ Set<String> keys = mBubbles.keySet();
+ for (String key: keys) {
+ mBubbles.get(key).getEntry().setBubbleDismissed(true);
+ }
+ mStackView.stackDismissed();
+
// Reset the position of the stack (TODO - or should we save / respect last user position?)
+ Point startPoint = getStartPoint(mStackView.getStackWidth(), mDisplaySize);
mStackView.setPosition(startPoint.x, startPoint.y);
- for (String key: mBubbles.keySet()) {
- removeBubble(key);
- }
- if (mDismissListener != null) {
- mDismissListener.onStackDismissed();
- }
- updateBubblesShowing();
+
+ updateVisibility();
+ mNotificationEntryManager.updateNotifications();
}
/**
- * Adds a bubble associated with the provided notification entry or updates it if it exists.
+ * Adds or updates a bubble associated with the provided notification entry.
+ *
+ * @param notif the notification associated with this bubble.
+ * @param updatePosition whether this update should promote the bubble to the top of the stack.
*/
- public void addBubble(NotificationData.Entry notif) {
+ public void updateBubble(NotificationEntry notif, boolean updatePosition) {
if (mBubbles.containsKey(notif.key)) {
// It's an update
BubbleView bubble = mBubbles.get(notif.key);
- mStackView.updateBubble(bubble, notif);
+ mStackView.updateBubble(bubble, notif, updatePosition);
} else {
- // It's new
- BubbleView bubble = new BubbleView(mContext);
- bubble.setNotif(notif);
- mBubbles.put(bubble.getKey(), bubble);
-
boolean setPosition = mStackView != null && mStackView.getVisibility() != VISIBLE;
if (mStackView == null) {
setPosition = true;
@@ -224,75 +263,134 @@ public class BubbleController {
mStackView.setExpandListener(mExpandListener);
}
}
+ // It's new
+ BubbleView bubble = (BubbleView) mInflater.inflate(
+ R.layout.bubble_view, mStackView, false /* attachToRoot */);
+ bubble.setNotif(notif);
+ if (shouldUseActivityView(mContext)) {
+ bubble.setAppOverlayIntent(getAppOverlayIntent(notif));
+ }
+ mBubbles.put(bubble.getKey(), bubble);
mStackView.addBubble(bubble);
if (setPosition) {
// Need to add the bubble to the stack before we can know the width
Point startPoint = getStartPoint(mStackView.getStackWidth(), mDisplaySize);
mStackView.setPosition(startPoint.x, startPoint.y);
- mStackView.setVisibility(VISIBLE);
}
- updateBubblesShowing();
}
+ updateVisibility();
+ }
+
+ @Nullable
+ private PendingIntent getAppOverlayIntent(NotificationEntry notif) {
+ Notification notification = notif.notification.getNotification();
+ if (canLaunchInActivityView(notification.getBubbleMetadata() != null
+ ? notification.getBubbleMetadata().getIntent() : null)) {
+ return notification.getBubbleMetadata().getIntent();
+ } else if (shouldUseContentIntent(mContext)
+ && canLaunchInActivityView(notification.contentIntent)) {
+ Log.d(TAG, "[addBubble " + notif.key
+ + "]: No appOverlayIntent, using contentIntent.");
+ return notification.contentIntent;
+ }
+ Log.d(TAG, "[addBubble " + notif.key + "]: No supported intent for ActivityView.");
+ return null;
}
/**
* Removes the bubble associated with the {@param uri}.
*/
- public void removeBubble(String key) {
- BubbleView bv = mBubbles.get(key);
+ void removeBubble(String key) {
+ BubbleView bv = mBubbles.remove(key);
if (mStackView != null && bv != null) {
mStackView.removeBubble(bv);
- bv.getEntry().setBubbleDismissed(true);
+ bv.destroyActivityView(mStackView);
}
- if (mDismissListener != null) {
- mDismissListener.onBubbleDismissed(key);
+
+ NotificationEntry entry = bv != null ? bv.getEntry() : null;
+ if (entry != null) {
+ entry.setBubbleDismissed(true);
+ mNotificationEntryManager.updateNotifications();
}
- updateBubblesShowing();
+ updateVisibility();
}
- private void updateBubblesShowing() {
- boolean hasBubblesShowing = false;
- for (BubbleView bv : mBubbles.values()) {
- if (!bv.getEntry().isBubbleDismissed()) {
- hasBubblesShowing = true;
- break;
+ @SuppressWarnings("FieldCanBeLocal")
+ private final NotificationEntryListener mEntryListener = new NotificationEntryListener() {
+ @Override
+ public void onPendingEntryAdded(NotificationEntry entry) {
+ if (shouldAutoBubble(mContext, entry) || shouldBubble(entry)) {
+ // TODO: handle group summaries
+ // It's a new notif, it shows in the shade and as a bubble
+ entry.setIsBubble(true);
+ entry.setShowInShadeWhenBubble(true);
}
}
- boolean hadBubbles = mStatusBarWindowController.getBubblesShowing();
- mStatusBarWindowController.setBubblesShowing(hasBubblesShowing);
- if (mStackView != null && !hasBubblesShowing) {
- mStackView.setVisibility(INVISIBLE);
+
+ @Override
+ public void onEntryInflated(NotificationEntry entry,
+ @NotificationInflater.InflationFlag int inflatedFlags) {
+ if (entry.isBubble() && mNotificationInterruptionStateProvider.shouldBubbleUp(entry)) {
+ updateBubble(entry, true /* updatePosition */);
+ }
}
- if (mStateChangeListener != null && hadBubbles != hasBubblesShowing) {
- mStateChangeListener.onHasBubblesChanged(hasBubblesShowing);
+
+ @Override
+ public void onPreEntryUpdated(NotificationEntry entry) {
+ if (mNotificationInterruptionStateProvider.shouldBubbleUp(entry)
+ && alertAgain(entry, entry.notification.getNotification())) {
+ entry.setShowInShadeWhenBubble(true);
+ entry.setBubbleDismissed(false); // updates come back as bubbles even if dismissed
+ if (mBubbles.containsKey(entry.key)) {
+ mBubbles.get(entry.key).updateDotVisibility();
+ }
+ updateBubble(entry, true /* updatePosition */);
+ }
}
- }
+
+ @Override
+ public void onEntryRemoved(NotificationEntry entry,
+ @Nullable NotificationVisibility visibility,
+ boolean removedByUser) {
+ entry.setShowInShadeWhenBubble(false);
+ if (mBubbles.containsKey(entry.key)) {
+ mBubbles.get(entry.key).updateDotVisibility();
+ }
+ if (!removedByUser) {
+ // This was a cancel so we should remove the bubble
+ removeBubble(entry.key);
+ }
+ }
+ };
/**
- * Sets the visibility of the bubbles, doesn't un-bubble them, just changes visibility.
+ * Lets any listeners know if bubble state has changed.
*/
- public void updateVisibility(boolean visible) {
+ private void updateBubblesShowing() {
if (mStackView == null) {
return;
}
- ArrayList<BubbleView> viewsToRemove = new ArrayList<>();
- for (BubbleView bv : mBubbles.values()) {
- NotificationData.Entry entry = bv.getEntry();
- if (entry != null) {
- if (entry.isRowRemoved() || entry.isBubbleDismissed() || entry.isRowDismissed()) {
- viewsToRemove.add(bv);
- }
- }
- }
- for (BubbleView view : viewsToRemove) {
- mBubbles.remove(view.getKey());
- mStackView.removeBubble(view);
+
+ boolean hadBubbles = mStatusBarWindowController.getBubblesShowing();
+ boolean hasBubblesShowing = hasBubbles() && mStackView.getVisibility() == VISIBLE;
+ mStatusBarWindowController.setBubblesShowing(hasBubblesShowing);
+ if (mStateChangeListener != null && hadBubbles != hasBubblesShowing) {
+ mStateChangeListener.onHasBubblesChanged(hasBubblesShowing);
}
- if (mStackView != null) {
- mStackView.setVisibility(visible ? VISIBLE : INVISIBLE);
- if (!visible) {
- collapseStack();
- }
+ }
+
+ /**
+ * Updates the visibility of the bubbles based on current state.
+ * Does not un-bubble, just hides or un-hides. Will notify any
+ * {@link BubbleStateChangeListener}s if visibility changes.
+ */
+ public void updateVisibility() {
+ if (mStatusBarStateListener.getCurrentState() == SHADE && hasBubbles()) {
+ // Bubbles only appear in unlocked shade
+ mStackView.setVisibility(hasBubbles() ? VISIBLE : INVISIBLE);
+ } else if (mStackView != null) {
+ mStackView.setVisibility(INVISIBLE);
+ collapseStack();
}
updateBubblesShowing();
}
@@ -308,8 +406,19 @@ public class BubbleController {
return mTempRect;
}
+ private boolean canLaunchInActivityView(PendingIntent intent) {
+ if (intent == null) {
+ return false;
+ }
+ ActivityInfo info =
+ intent.getIntent().resolveActivityInfo(mContext.getPackageManager(), 0);
+ return info != null
+ && ActivityInfo.isResizeableMode(info.resizeMode)
+ && (info.flags & ActivityInfo.FLAG_ALLOW_EMBEDDED) != 0;
+ }
+
@VisibleForTesting
- public BubbleStackView getStackView() {
+ BubbleStackView getStackView() {
return mStackView;
}
@@ -317,7 +426,7 @@ public class BubbleController {
/**
* Gets an appropriate starting point to position the bubble stack.
*/
- public static Point getStartPoint(int size, Point displaySize) {
+ private static Point getStartPoint(int size, Point displaySize) {
final int x = displaySize.x - size + EDGE_OVERLAP;
final int y = displaySize.y / 4;
return new Point(x, y);
@@ -326,24 +435,48 @@ public class BubbleController {
/**
* Gets an appropriate position for the bubble when the stack is expanded.
*/
- public static Point getExpandPoint(BubbleStackView view, int size, Point displaySize) {
+ static Point getExpandPoint(BubbleStackView view, int size, Point displaySize) {
// Same place for now..
return new Point(EDGE_OVERLAP, size);
}
/**
- * Whether the notification should bubble or not.
+ * Whether the notification has been developer configured to bubble and is allowed by the user.
*/
- public static boolean shouldAutoBubble(Context context, NotificationData.Entry entry) {
+ private boolean shouldBubble(NotificationEntry entry) {
+ StatusBarNotification n = entry.notification;
+ boolean canAppOverlay = false;
+ try {
+ canAppOverlay = mNotificationManagerService.areBubblesAllowedForPackage(
+ n.getPackageName(), n.getUid());
+ } catch (RemoteException e) {
+ Log.w(TAG, "Error calling NoMan to determine if app can overlay", e);
+ }
+
+ boolean canChannelOverlay = mNotificationEntryManager.getNotificationData().getChannel(
+ entry.key).canBubble();
+ boolean hasOverlayIntent = n.getNotification().getBubbleMetadata() != null
+ && n.getNotification().getBubbleMetadata().getIntent() != null;
+ return hasOverlayIntent && canChannelOverlay && canAppOverlay;
+ }
+
+ /**
+ * Whether the notification should bubble or not. Gated by debug flag.
+ * <p>
+ * If a notification has been set to bubble via proper bubble APIs or if it is an important
+ * message-like notification.
+ * </p>
+ */
+ private boolean shouldAutoBubble(Context context, NotificationEntry entry) {
if (entry.isBubbleDismissed()) {
return false;
}
+ StatusBarNotification n = entry.notification;
boolean autoBubbleMessages = shouldAutoBubbleMessages(context) || DEBUG_ENABLE_AUTO_BUBBLE;
boolean autoBubbleOngoing = shouldAutoBubbleOngoing(context) || DEBUG_ENABLE_AUTO_BUBBLE;
boolean autoBubbleAll = shouldAutoBubbleAll(context) || DEBUG_ENABLE_AUTO_BUBBLE;
- StatusBarNotification n = entry.notification;
boolean hasRemoteInput = false;
if (n.getNotification().actions != null) {
for (Notification.Action action : n.getNotification().actions) {
@@ -380,4 +513,14 @@ public class BubbleController {
return Settings.Secure.getInt(context.getContentResolver(),
ENABLE_AUTO_BUBBLE_ALL, 0) != 0;
}
+
+ private static boolean shouldUseActivityView(Context context) {
+ return Settings.Secure.getInt(context.getContentResolver(),
+ ENABLE_BUBBLE_ACTIVITY_VIEW, 0) != 0;
+ }
+
+ private static boolean shouldUseContentIntent(Context context) {
+ return Settings.Secure.getInt(context.getContentResolver(),
+ ENABLE_BUBBLE_CONTENT_INTENT, 0) != 0;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedViewContainer.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedViewContainer.java
index e28d96b2def9..71ae1f8620f6 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedViewContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedViewContainer.java
@@ -21,9 +21,11 @@ import android.content.Context;
import android.content.res.Resources;
import android.graphics.Color;
import android.graphics.drawable.ShapeDrawable;
+import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.View;
import android.widget.LinearLayout;
+import android.widget.TextView;
import com.android.systemui.R;
import com.android.systemui.recents.TriangleShape;
@@ -35,6 +37,8 @@ public class BubbleExpandedViewContainer extends LinearLayout {
// The triangle pointing to the expanded view
private View mPointerView;
+ // The view displayed between the pointer and the expanded view
+ private TextView mHeaderView;
// The view that is being displayed for the expanded state
private View mExpandedView;
@@ -68,6 +72,7 @@ public class BubbleExpandedViewContainer extends LinearLayout {
TriangleShape.create(width, height, true /* pointUp */));
triangleDrawable.setTint(Color.WHITE); // TODO: dark mode
mPointerView.setBackground(triangleDrawable);
+ mHeaderView = findViewById(R.id.bubble_content_header);
}
/**
@@ -80,9 +85,20 @@ public class BubbleExpandedViewContainer extends LinearLayout {
}
/**
+ * Set the text displayed within the header.
+ */
+ public void setHeaderText(CharSequence text) {
+ mHeaderView.setText(text);
+ mHeaderView.setVisibility(TextUtils.isEmpty(text) ? GONE : VISIBLE);
+ }
+
+ /**
* Set the view to display for the expanded state. Passing null will clear the view.
*/
public void setExpandedView(View view) {
+ if (mExpandedView == view) {
+ return;
+ }
if (mExpandedView != null) {
removeView(mExpandedView);
}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
index dfd18b23a5e3..9a11b965b319 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
@@ -21,10 +21,13 @@ import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
+import android.app.ActivityView;
+import android.app.PendingIntent;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Point;
import android.graphics.RectF;
+import android.util.Log;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
@@ -40,7 +43,7 @@ import androidx.annotation.Nullable;
import com.android.internal.widget.ViewClippingUtil;
import com.android.systemui.R;
-import com.android.systemui.statusbar.notification.NotificationData;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.stack.ExpandableViewState;
import com.android.systemui.statusbar.notification.stack.ViewState;
@@ -50,6 +53,7 @@ import com.android.systemui.statusbar.notification.stack.ViewState;
*/
public class BubbleStackView extends FrameLayout implements BubbleTouchHandler.FloatingView {
+ private static final String TAG = "BubbleStackView";
private Point mDisplaySize;
private FrameLayout mBubbleContainer;
@@ -59,9 +63,10 @@ public class BubbleStackView extends FrameLayout implements BubbleTouchHandler.F
private int mBubblePadding;
private boolean mIsExpanded;
+ private int mExpandedBubbleHeight;
+ private BubbleTouchHandler mTouchHandler;
private BubbleView mExpandedBubble;
private Point mCollapsedPosition;
- private BubbleTouchHandler mTouchHandler;
private BubbleController.BubbleExpandListener mExpandListener;
private boolean mViewUpdatedRequested = false;
@@ -106,6 +111,7 @@ public class BubbleStackView extends FrameLayout implements BubbleTouchHandler.F
mBubbleSize = res.getDimensionPixelSize(R.dimen.bubble_size);
mBubblePadding = res.getDimensionPixelSize(R.dimen.bubble_padding);
+ mExpandedBubbleHeight = res.getDimensionPixelSize(R.dimen.bubble_expanded_default_height);
mDisplaySize = new Point();
WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
wm.getDefaultDisplay().getSize(mDisplaySize);
@@ -205,13 +211,24 @@ public class BubbleStackView extends FrameLayout implements BubbleTouchHandler.F
*/
public void setExpandedBubble(BubbleView bubbleToExpand) {
mExpandedBubble = bubbleToExpand;
+ boolean prevExpanded = mIsExpanded;
mIsExpanded = true;
- updateExpandedBubble();
- requestUpdate();
+ if (!prevExpanded) {
+ // If we weren't previously expanded we should animate open.
+ animateExpansion(true /* expand */);
+ } else {
+ // If we were expanded just update the views
+ updateExpandedBubble();
+ requestUpdate();
+ }
+ mExpandedBubble.getEntry().setShowInShadeWhenBubble(false);
+ notifyExpansionChanged(mExpandedBubble, true /* expanded */);
}
/**
- * Adds a bubble to the stack.
+ * Adds a bubble to the top of the stack.
+ *
+ * @param bubbleView the view to add to the stack.
*/
public void addBubble(BubbleView bubbleView) {
mBubbleContainer.addView(bubbleView, 0,
@@ -228,17 +245,26 @@ public class BubbleStackView extends FrameLayout implements BubbleTouchHandler.F
mBubbleContainer.removeView(bubbleView);
boolean wasExpanded = mIsExpanded;
int bubbleCount = mBubbleContainer.getChildCount();
- if (bubbleView.equals(mExpandedBubble) && bubbleCount > 0) {
+ if (mIsExpanded && bubbleView.equals(mExpandedBubble) && bubbleCount > 0) {
// If we have other bubbles and are expanded go to the next one or previous
// if the bubble removed was last
int nextIndex = bubbleCount > removedIndex ? removedIndex : bubbleCount - 1;
- mExpandedBubble = (BubbleView) mBubbleContainer.getChildAt(nextIndex);
+ BubbleView expandedBubble = (BubbleView) mBubbleContainer.getChildAt(nextIndex);
+ setExpandedBubble(expandedBubble);
}
mIsExpanded = wasExpanded && mBubbleContainer.getChildCount() > 0;
- requestUpdate();
- if (wasExpanded && !mIsExpanded && mExpandListener != null) {
- mExpandListener.onBubbleExpandChanged(mIsExpanded, 1 /* amount */);
+ if (wasExpanded != mIsExpanded) {
+ notifyExpansionChanged(mExpandedBubble, mIsExpanded);
}
+ requestUpdate();
+ }
+
+ /**
+ * Dismiss the stack of bubbles.
+ */
+ public void stackDismissed() {
+ collapseStack();
+ mBubbleContainer.removeAllViews();
}
/**
@@ -246,11 +272,19 @@ public class BubbleStackView extends FrameLayout implements BubbleTouchHandler.F
*
* @param bubbleView the view to update in the stack.
* @param entry the entry to update it with.
+ * @param updatePosition whether this bubble should be moved to top of the stack.
*/
- public void updateBubble(BubbleView bubbleView, NotificationData.Entry entry) {
- // TODO - move to top of bubble stack, make it show its update if it makes sense
+ public void updateBubble(BubbleView bubbleView, NotificationEntry entry,
+ boolean updatePosition) {
bubbleView.update(entry);
- if (bubbleView.equals(mExpandedBubble)) {
+ if (updatePosition && !mIsExpanded) {
+ // If alerting it gets promoted to top of the stack
+ mBubbleContainer.removeView(bubbleView);
+ mBubbleContainer.addView(bubbleView, 0);
+ requestUpdate();
+ }
+ if (mIsExpanded && bubbleView.equals(mExpandedBubble)) {
+ entry.setShowInShadeWhenBubble(false);
requestUpdate();
}
}
@@ -281,17 +315,36 @@ public class BubbleStackView extends FrameLayout implements BubbleTouchHandler.F
}
/**
+ * Collapses the stack of bubbles.
+ */
+ public void collapseStack() {
+ if (mIsExpanded) {
+ // TODO: Save opened bubble & move it to top of stack
+ animateExpansion(false /* shouldExpand */);
+ notifyExpansionChanged(mExpandedBubble, mIsExpanded);
+ }
+ }
+
+ /**
+ * Expands the stack fo bubbles.
+ */
+ public void expandStack() {
+ if (!mIsExpanded) {
+ mExpandedBubble = getTopBubble();
+ mExpandedBubble.getEntry().setShowInShadeWhenBubble(false);
+ animateExpansion(true /* shouldExpand */);
+ notifyExpansionChanged(mExpandedBubble, true /* expanded */);
+ }
+ }
+
+ /**
* Tell the stack to animate to collapsed or expanded state.
*/
- public void animateExpansion(boolean shouldExpand) {
+ private void animateExpansion(boolean shouldExpand) {
if (mIsExpanded != shouldExpand) {
mIsExpanded = shouldExpand;
- mExpandedBubble = shouldExpand ? getTopBubble() : null;
updateExpandedBubble();
- if (mExpandListener != null) {
- mExpandListener.onBubbleExpandChanged(mIsExpanded, 1 /* amount */);
- }
if (shouldExpand) {
// Save current position so that we might return there
savePosition();
@@ -341,6 +394,13 @@ public class BubbleStackView extends FrameLayout implements BubbleTouchHandler.F
mCollapsedPosition = getPosition();
}
+ private void notifyExpansionChanged(BubbleView bubbleView, boolean expanded) {
+ if (mExpandListener != null) {
+ NotificationEntry entry = bubbleView != null ? bubbleView.getEntry() : null;
+ mExpandListener.onBubbleExpandChanged(expanded, entry != null ? entry.key : null);
+ }
+ }
+
private BubbleView getTopBubble() {
return getBubbleAt(0);
}
@@ -389,32 +449,78 @@ public class BubbleStackView extends FrameLayout implements BubbleTouchHandler.F
}
private void updateExpandedBubble() {
- if (mExpandedBubble != null) {
+ if (mExpandedBubble == null) {
+ return;
+ }
+
+ if (mExpandedBubble.hasAppOverlayIntent()) {
+ // Bubble with activity view expanded state
+ ActivityView expandedView = mExpandedBubble.getActivityView();
+ expandedView.setLayoutParams(new ViewGroup.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT, mExpandedBubbleHeight));
+
+ final PendingIntent intent = mExpandedBubble.getAppOverlayIntent();
+ mExpandedViewContainer.setHeaderText(intent.getIntent().getComponent().toShortString());
+ mExpandedViewContainer.setExpandedView(expandedView);
+ expandedView.setCallback(new ActivityView.StateCallback() {
+ @Override
+ public void onActivityViewReady(ActivityView view) {
+ Log.d(TAG, "onActivityViewReady("
+ + mExpandedBubble.getEntry().key + "): " + view);
+ view.startActivity(intent);
+ }
+
+ @Override
+ public void onActivityViewDestroyed(ActivityView view) {
+ NotificationEntry entry = mExpandedBubble != null
+ ? mExpandedBubble.getEntry() : null;
+ Log.d(TAG, "onActivityViewDestroyed(key="
+ + ((entry != null) ? entry.key : "(none)") + "): " + view);
+ }
+ });
+ } else {
+ // Bubble with notification view expanded state
ExpandableNotificationRow row = mExpandedBubble.getRowView();
- if (!row.equals(mExpandedViewContainer.getChildAt(0))) {
- // Different expanded view than what we have
+ if (row.getParent() != null) {
+ // Row might still be in the shade when we expand
+ ((ViewGroup) row.getParent()).removeView(row);
+ }
+ if (mIsExpanded) {
+ mExpandedViewContainer.setExpandedView(row);
+ } else {
mExpandedViewContainer.setExpandedView(null);
}
- int pointerPosition = mExpandedBubble.getPosition().x
- + (mExpandedBubble.getWidth() / 2);
- mExpandedViewContainer.setPointerPosition(pointerPosition);
- mExpandedViewContainer.setExpandedView(row);
+ // Bubble with notification as expanded state doesn't need a header / title
+ mExpandedViewContainer.setHeaderText(null);
+
}
+ int pointerPosition = mExpandedBubble.getPosition().x
+ + (mExpandedBubble.getWidth() / 2);
+ mExpandedViewContainer.setPointerPosition(pointerPosition);
}
private void applyCurrentState() {
+ Log.d(TAG, "applyCurrentState: mIsExpanded=" + mIsExpanded);
+
mExpandedViewContainer.setVisibility(mIsExpanded ? VISIBLE : GONE);
if (!mIsExpanded) {
mExpandedViewContainer.setExpandedView(null);
} else {
mExpandedViewContainer.setTranslationY(mBubbleContainer.getHeight());
- ExpandableNotificationRow row = mExpandedBubble.getRowView();
- applyRowState(row);
+ View expandedView = mExpandedViewContainer.getExpandedView();
+ if (expandedView instanceof ActivityView) {
+ if (expandedView.isAttachedToWindow()) {
+ ((ActivityView) expandedView).onLocationChanged();
+ }
+ } else {
+ applyRowState(mExpandedBubble.getRowView());
+ }
}
int bubbsCount = mBubbleContainer.getChildCount();
for (int i = 0; i < bubbsCount; i++) {
BubbleView bv = (BubbleView) mBubbleContainer.getChildAt(i);
- bv.setZ(bubbsCount - 1);
+ bv.updateDotVisibility();
+ bv.setZ(bubbsCount - i);
int transX = mIsExpanded ? (bv.getWidth() + mBubblePadding) * i : mBubblePadding * i;
ViewState viewState = new ViewState();
@@ -468,6 +574,7 @@ public class BubbleStackView extends FrameLayout implements BubbleTouchHandler.F
private void applyRowState(ExpandableNotificationRow view) {
view.reset();
view.setHeadsUp(false);
+ view.resetTranslation();
view.setOnKeyguard(false);
view.setOnAmbient(false);
view.setClipBottomAmount(0);
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java
index 88030eefdf54..97784b0f4f93 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java
@@ -33,7 +33,7 @@ import com.android.systemui.pip.phone.PipDismissViewController;
* Handles interpreting touches on a {@link BubbleStackView}. This includes expanding, collapsing,
* dismissing, and flings.
*/
-public class BubbleTouchHandler implements View.OnTouchListener {
+class BubbleTouchHandler implements View.OnTouchListener {
private BubbleController mController = Dependency.get(BubbleController.class);
private PipDismissViewController mDismissViewController;
@@ -110,7 +110,7 @@ public class BubbleTouchHandler implements View.OnTouchListener {
: stack.getTargetView(event);
boolean isFloating = targetView instanceof FloatingView;
if (!isFloating || targetView == null || action == MotionEvent.ACTION_OUTSIDE) {
- stack.animateExpansion(false /* shouldExpand */);
+ stack.collapseStack();
cleanUpDismissTarget();
resetTouches();
return false;
@@ -196,9 +196,13 @@ public class BubbleTouchHandler implements View.OnTouchListener {
mMovementHelper.getTranslateAnim(floatingView, toGoTo, 100, 0).start();
}
} else if (floatingView.equals(stack.getExpandedBubble())) {
- stack.animateExpansion(false /* shouldExpand */);
+ stack.collapseStack();
} else if (isBubbleStack) {
- stack.animateExpansion(!stack.isExpanded() /* shouldExpand */);
+ if (stack.isExpanded()) {
+ stack.collapseStack();
+ } else {
+ stack.expandStack();
+ }
} else {
stack.setExpandedBubble((BubbleView) floatingView);
}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java
index 6c47aac712f6..91893ef3db00 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012 The Android Open Source Project
+ * Copyright (C) 2018 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.
@@ -16,36 +16,49 @@
package com.android.systemui.bubbles;
+import android.annotation.Nullable;
+import android.app.ActivityView;
import android.app.Notification;
+import android.app.PendingIntent;
import android.content.Context;
import android.graphics.Color;
import android.graphics.Point;
+import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
-import android.graphics.drawable.ShapeDrawable;
-import android.graphics.drawable.shapes.OvalShape;
+import android.graphics.drawable.InsetDrawable;
+import android.graphics.drawable.LayerDrawable;
import android.util.AttributeSet;
+import android.util.Log;
import android.view.View;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+import android.widget.TextView;
-import com.android.internal.util.ContrastColorUtil;
+import com.android.internal.graphics.ColorUtils;
+import com.android.systemui.Interpolators;
import com.android.systemui.R;
-import com.android.systemui.statusbar.notification.NotificationData;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
/**
- * A floating object on the screen that has a collapsed and expanded state.
+ * A floating object on the screen that can post message updates.
*/
-public class BubbleView extends LinearLayout implements BubbleTouchHandler.FloatingView {
+public class BubbleView extends FrameLayout implements BubbleTouchHandler.FloatingView {
private static final String TAG = "BubbleView";
+ // Same value as Launcher3 badge code
+ private static final float WHITE_SCRIM_ALPHA = 0.54f;
private Context mContext;
- private View mIconView;
- private NotificationData.Entry mEntry;
- private int mBubbleSize;
- private int mIconSize;
+ private BadgedImageView mBadgedImageView;
+ private TextView mMessageView;
+ private int mPadding;
+ private int mIconInset;
+
+ private NotificationEntry mEntry;
+ private PendingIntent mAppOverlayIntent;
+ private ActivityView mActivityView;
public BubbleView(Context context) {
this(context, null);
@@ -61,72 +74,201 @@ public class BubbleView extends LinearLayout implements BubbleTouchHandler.Float
public BubbleView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
- setOrientation(LinearLayout.VERTICAL);
mContext = context;
- mBubbleSize = getResources().getDimensionPixelSize(R.dimen.bubble_size);
- mIconSize = getResources().getDimensionPixelSize(R.dimen.bubble_icon_size);
+ // XXX: can this padding just be on the view and we look it up?
+ mPadding = getResources().getDimensionPixelSize(R.dimen.bubble_view_padding);
+ mIconInset = getResources().getDimensionPixelSize(R.dimen.bubble_icon_inset);
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ mBadgedImageView = (BadgedImageView) findViewById(R.id.bubble_image);
+ mMessageView = (TextView) findViewById(R.id.message_view);
+ mMessageView.setVisibility(GONE);
+ mMessageView.setPivotX(0);
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ updateViews();
+ }
+
+ @Override
+ protected void onMeasure(int widthSpec, int heightSpec) {
+ measureChild(mBadgedImageView, widthSpec, heightSpec);
+ measureChild(mMessageView, widthSpec, heightSpec);
+ boolean messageGone = mMessageView.getVisibility() == GONE;
+ int imageHeight = mBadgedImageView.getMeasuredHeight();
+ int imageWidth = mBadgedImageView.getMeasuredWidth();
+ int messageHeight = messageGone ? 0 : mMessageView.getMeasuredHeight();
+ int messageWidth = messageGone ? 0 : mMessageView.getMeasuredWidth();
+ setMeasuredDimension(
+ getPaddingStart() + imageWidth + mPadding + messageWidth + getPaddingEnd(),
+ getPaddingTop() + Math.max(imageHeight, messageHeight) + getPaddingBottom());
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ left = getPaddingStart();
+ top = getPaddingTop();
+ int imageWidth = mBadgedImageView.getMeasuredWidth();
+ int imageHeight = mBadgedImageView.getMeasuredHeight();
+ int messageWidth = mMessageView.getMeasuredWidth();
+ int messageHeight = mMessageView.getMeasuredHeight();
+ mBadgedImageView.layout(left, top, left + imageWidth, top + imageHeight);
+ mMessageView.layout(left + imageWidth + mPadding, top,
+ left + imageWidth + mPadding + messageWidth, top + messageHeight);
}
/**
* Populates this view with a notification.
+ * <p>
+ * This should only be called when a new notification is being set on the view, updates to the
+ * current notification should use {@link #update(NotificationEntry)}.
*
* @param entry the notification to display as a bubble.
*/
- public void setNotif(NotificationData.Entry entry) {
- removeAllViews();
- // TODO: migrate to inflater
- mIconView = new ImageView(mContext);
- addView(mIconView);
+ public void setNotif(NotificationEntry entry) {
+ mEntry = entry;
+ updateViews();
+ }
- LinearLayout.LayoutParams iconLp = (LinearLayout.LayoutParams) mIconView.getLayoutParams();
- iconLp.width = mBubbleSize;
- iconLp.height = mBubbleSize;
- mIconView.setLayoutParams(iconLp);
+ /**
+ * The {@link NotificationEntry} associated with this view, if one exists.
+ */
+ @Nullable
+ public NotificationEntry getEntry() {
+ return mEntry;
+ }
- update(entry);
+ /**
+ * The key for the {@link NotificationEntry} associated with this view, if one exists.
+ */
+ @Nullable
+ public String getKey() {
+ return (mEntry != null) ? mEntry.key : null;
}
/**
- * Updates the UI based on the entry.
+ * Updates the UI based on the entry, updates badge and animates messages as needed.
*/
- public void update(NotificationData.Entry entry) {
+ public void update(NotificationEntry entry) {
mEntry = entry;
- Notification n = entry.notification.getNotification();
- Icon ic = n.getLargeIcon() != null ? n.getLargeIcon() : n.getSmallIcon();
+ updateViews();
+ }
- if (n.getLargeIcon() == null) {
- createCircledIcon(n.color, ic, ((ImageView) mIconView));
- } else {
- ((ImageView) mIconView).setImageIcon(ic);
- }
+
+ /**
+ * @return the {@link ExpandableNotificationRow} view to display notification content when the
+ * bubble is expanded.
+ */
+ @Nullable
+ public ExpandableNotificationRow getRowView() {
+ return (mEntry != null) ? mEntry.getRow() : null;
}
/**
- * @return the key identifying this bubble / notification entry associated with this
- * bubble, if it exists.
+ * Marks this bubble as "read", i.e. no badge should show.
*/
- public String getKey() {
- return mEntry == null ? null : mEntry.key;
+ public void updateDotVisibility() {
+ boolean showDot = getEntry().showInShadeWhenBubble();
+ animateDot(showDot);
}
/**
- * @return the notification entry associated with this bubble.
+ * Animates the badge to show or hide.
*/
- public NotificationData.Entry getEntry() {
- return mEntry;
+ private void animateDot(boolean showDot) {
+ if (mBadgedImageView.isShowingDot() != showDot) {
+ mBadgedImageView.setShowDot(showDot);
+ mBadgedImageView.clearAnimation();
+ mBadgedImageView.animate().setDuration(200)
+ .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
+ .setUpdateListener((valueAnimator) -> {
+ float fraction = valueAnimator.getAnimatedFraction();
+ fraction = showDot ? fraction : 1 - fraction;
+ mBadgedImageView.setDotScale(fraction);
+ }).withEndAction(() -> {
+ if (!showDot) {
+ mBadgedImageView.setShowDot(false);
+ }
+ }).start();
+ }
+ }
+
+ private void updateViews() {
+ if (mEntry == null) {
+ return;
+ }
+ Notification n = mEntry.notification.getNotification();
+ boolean isLarge = n.getLargeIcon() != null;
+ Icon ic = isLarge ? n.getLargeIcon() : n.getSmallIcon();
+ Drawable iconDrawable = ic.loadDrawable(mContext);
+ if (!isLarge) {
+ // Center icon on coloured background
+ iconDrawable.setTint(Color.WHITE); // TODO: dark mode
+ Drawable bg = new ColorDrawable(n.color);
+ InsetDrawable d = new InsetDrawable(iconDrawable, mIconInset);
+ Drawable[] layers = {bg, d};
+ mBadgedImageView.setImageDrawable(new LayerDrawable(layers));
+ } else {
+ mBadgedImageView.setImageDrawable(iconDrawable);
+ }
+ int badgeColor = determineDominateColor(iconDrawable, n.color);
+ mBadgedImageView.setDotColor(badgeColor);
+ animateDot(mEntry.showInShadeWhenBubble() /* showDot */);
+ }
+
+ private int determineDominateColor(Drawable d, int defaultTint) {
+ // XXX: should we pull from the drawable, app icon, notif tint?
+ return ColorUtils.blendARGB(defaultTint, Color.WHITE, WHITE_SCRIM_ALPHA);
}
/**
- * @return the view to display when the bubble is expanded.
+ * @return a view used to display app overlay content when expanded.
*/
- public ExpandableNotificationRow getRowView() {
- return mEntry.getRow();
+ public ActivityView getActivityView() {
+ if (mActivityView == null) {
+ mActivityView = new ActivityView(mContext);
+ Log.d(TAG, "[getActivityView] created: " + mActivityView);
+ }
+ return mActivityView;
+ }
+
+ /**
+ * Removes and releases an ActivityView if one was previously created for this bubble.
+ */
+ public void destroyActivityView(ViewGroup tmpParent) {
+ if (mActivityView == null) {
+ return;
+ }
+ // HACK: Only release if initialized. There's no way to know if the ActivityView has
+ // been initialized. Calling release() if it hasn't been initialized will crash.
+
+ if (!mActivityView.isAttachedToWindow()) {
+ // HACK: release() will crash if the view is not attached.
+
+ mActivityView.setVisibility(View.GONE);
+ tmpParent.addView(mActivityView, new ViewGroup.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT));
+ }
+ try {
+ mActivityView.release();
+ } catch (IllegalStateException ex) {
+ Log.e(TAG, "ActivityView either already released, or not yet initialized.", ex);
+ }
+
+ ((ViewGroup) mActivityView.getParent()).removeView(mActivityView);
+ mActivityView = null;
}
@Override
public void setPosition(int x, int y) {
- setTranslationX(x);
- setTranslationY(y);
+ setPositionX(x);
+ setPositionY(y);
}
@Override
@@ -144,22 +286,19 @@ public class BubbleView extends LinearLayout implements BubbleTouchHandler.Float
return new Point((int) getTranslationX(), (int) getTranslationY());
}
- // Seems sub optimal
- private void createCircledIcon(int tint, Icon icon, ImageView v) {
- // TODO: dark mode
- icon.setTint(Color.WHITE);
- icon.scaleDownIfNecessary(mIconSize, mIconSize);
- v.setImageDrawable(icon.loadDrawable(mContext));
- v.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
- LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) v.getLayoutParams();
- int color = ContrastColorUtil.ensureContrast(tint, Color.WHITE,
- false /* isBgDarker */, 3);
- Drawable d = new ShapeDrawable(new OvalShape());
- d.setTint(color);
- v.setBackgroundDrawable(d);
-
- lp.width = mBubbleSize;
- lp.height = mBubbleSize;
- v.setLayoutParams(lp);
+ /**
+ * @return whether an ActivityView should be used to display the content of this Bubble
+ */
+ public boolean hasAppOverlayIntent() {
+ return mAppOverlayIntent != null;
+ }
+
+ public PendingIntent getAppOverlayIntent() {
+ return mAppOverlayIntent;
+
+ }
+
+ public void setAppOverlayIntent(PendingIntent intent) {
+ mAppOverlayIntent = intent;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManager.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManager.java
index 1718cff4e62b..4ff27b1b64f0 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManager.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManager.java
@@ -48,7 +48,7 @@ import java.io.PrintWriter;
*
* It does not collect touch events when the bouncer shows up.
*/
-public class FalsingManager implements SensorEventListener {
+public class FalsingManager implements SensorEventListener, StateListener {
private static final String ENFORCE_BOUNCER = "falsing_manager_enforce_bouncer";
private static final int[] CLASSIFIER_SENSORS = new int[] {
@@ -84,8 +84,6 @@ public class FalsingManager implements SensorEventListener {
private boolean mShowingAod;
private Runnable mPendingWtf;
- private final StateListener mStateListener = this::setStatusBarState;
-
protected final ContentObserver mSettingsObserver = new ContentObserver(mHandler) {
@Override
public void onChange(boolean selfChange) {
@@ -108,7 +106,7 @@ public class FalsingManager implements SensorEventListener {
UserHandle.USER_ALL);
updateConfiguration();
- Dependency.get(StatusBarStateController.class).addCallback(mStateListener);
+ Dependency.get(StatusBarStateController.class).addCallback(this);
}
public static FalsingManager getInstance(Context context) {
@@ -282,14 +280,15 @@ public class FalsingManager implements SensorEventListener {
updateSessionActive();
}
- private void setStatusBarState(int state) {
+ @Override
+ public void onStateChanged(int newState) {
if (FalsingLog.ENABLED) {
FalsingLog.i("setStatusBarState", new StringBuilder()
.append("from=").append(StatusBarState.toShortString(mState))
- .append(" to=").append(StatusBarState.toShortString(state))
+ .append(" to=").append(StatusBarState.toShortString(newState))
.toString());
}
- mState = state;
+ mState = newState;
updateSessionActive();
}
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeHost.java b/packages/SystemUI/src/com/android/systemui/doze/DozeHost.java
index 4cb1feebcce4..bd7a421e9762 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeHost.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeHost.java
@@ -38,7 +38,13 @@ public interface DozeHost {
void setAnimateWakeup(boolean animateWakeup);
void setAnimateScreenOff(boolean animateScreenOff);
- void onDoubleTap(float x, float y);
+ /**
+ * Reports that a tap event happend on the Sensors Low Power Island.
+ *
+ * @param x Touch X, or -1 if sensor doesn't support touch location.
+ * @param y Touch Y, or -1 if sensor doesn't support touch location.
+ */
+ void onSlpiTap(float x, float y);
default void setAodDimmingScrim(float scrimOpacity) {}
void setDozeScreenBrightness(int value);
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java b/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
index 50003e3a6a38..a7847739cd00 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
@@ -35,7 +35,7 @@ public class DozeLog {
private static final int SIZE = Build.IS_DEBUGGABLE ? 400 : 50;
static final SimpleDateFormat FORMAT = new SimpleDateFormat("MM-dd HH:mm:ss.SSS");
- private static final int REASONS = 9;
+ private static final int REASONS = 10;
public static final int PULSE_REASON_NONE = -1;
public static final int PULSE_REASON_INTENT = 0;
@@ -47,6 +47,7 @@ public class DozeLog {
public static final int PULSE_REASON_DOCKING = 6;
public static final int REASON_SENSOR_WAKE_UP = 7;
public static final int PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN = 8;
+ public static final int PULSE_REASON_SENSOR_TAP = 9;
private static boolean sRegisterKeyguardCallback = true;
@@ -207,6 +208,7 @@ public class DozeLog {
case PULSE_REASON_DOCKING: return "docking";
case PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN: return "wakelockscreen";
case REASON_SENSOR_WAKE_UP: return "wakeup";
+ case PULSE_REASON_SENSOR_TAP: return "tap";
default: throw new IllegalArgumentException("bad reason: " + pulseReason);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
index b6fc35553760..78374a0b1621 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
@@ -107,6 +107,13 @@ public class DozeSensors {
dozeParameters.doubleTapReportsTouchCoordinates(),
true /* touchscreen */),
new TriggerSensor(
+ findSensorWithType(config.tapSensorType()),
+ Settings.Secure.DOZE_TAP_SCREEN_GESTURE,
+ true /* configured */,
+ DozeLog.PULSE_REASON_SENSOR_TAP,
+ false /* reports touch coordinates */,
+ true /* touchscreen */),
+ new TriggerSensor(
findSensorWithType(config.longPressSensorType()),
Settings.Secure.DOZE_PULSE_ON_LONG_PRESS,
false /* settingDef */,
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
index 6a9b6899d509..e2e448bb0d0c 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
@@ -132,6 +132,7 @@ public class DozeTriggers implements DozeMachine.Part {
private void onSensor(int pulseReason, boolean sensorPerformedProxCheck,
float screenX, float screenY, float[] rawValues) {
boolean isDoubleTap = pulseReason == DozeLog.PULSE_REASON_SENSOR_DOUBLE_TAP;
+ boolean isTap = pulseReason == DozeLog.PULSE_REASON_SENSOR_TAP;
boolean isPickup = pulseReason == DozeLog.PULSE_REASON_SENSOR_PICKUP;
boolean isLongPress = pulseReason == DozeLog.PULSE_REASON_SENSOR_LONG_PRESS;
boolean isWakeDisplay = pulseReason == DozeLog.REASON_SENSOR_WAKE_UP;
@@ -148,8 +149,10 @@ public class DozeTriggers implements DozeMachine.Part {
// In pocket, drop event.
return;
}
- if (isDoubleTap) {
- mDozeHost.onDoubleTap(screenX, screenY);
+ if (isDoubleTap || isTap) {
+ if (screenX != -1 && screenY != -1) {
+ mDozeHost.onSlpiTap(screenX, screenY);
+ }
mMachine.wakeUp();
} else if (isPickup) {
mMachine.wakeUp();
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
index 268245bd4acd..7b18fad0e105 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
@@ -67,10 +67,8 @@ import android.view.WindowManagerGlobal;
import android.view.accessibility.AccessibilityEvent;
import android.widget.AdapterView.OnItemLongClickListener;
import android.widget.BaseAdapter;
-import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.ImageView.ScaleType;
-import android.widget.LinearLayout;
import android.widget.TextView;
import com.android.internal.R;
@@ -86,8 +84,8 @@ import com.android.internal.util.ScreenRecordHelper;
import com.android.internal.util.ScreenshotHelper;
import com.android.internal.widget.LockPatternUtils;
import com.android.systemui.Dependency;
-import com.android.systemui.HardwareUiLayout;
import com.android.systemui.Interpolators;
+import com.android.systemui.MultiListLayout;
import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.plugins.GlobalActions.GlobalActionsManager;
import com.android.systemui.statusbar.phone.ScrimController;
@@ -490,6 +488,11 @@ class GlobalActionsDialog implements DialogInterface.OnDismissListener,
public boolean showBeforeProvisioning() {
return true;
}
+
+ @Override
+ public boolean shouldBeSeparated() {
+ return true;
+ }
}
private final class RestartAction extends SinglePressAction implements LongPressAction {
@@ -926,6 +929,34 @@ class GlobalActionsDialog implements DialogInterface.OnDismissListener,
return getItem(position).isEnabled();
}
+ public ArrayList<Action> getSeparatedActions(boolean shouldUseSeparatedView) {
+ ArrayList<Action> separatedActions = new ArrayList<Action>();
+ if (!shouldUseSeparatedView) {
+ return separatedActions;
+ }
+ for (int i = 0; i < mItems.size(); i++) {
+ final Action action = mItems.get(i);
+ if (action.shouldBeSeparated()) {
+ separatedActions.add(action);
+ }
+ }
+ return separatedActions;
+ }
+
+ public ArrayList<Action> getListActions(boolean shouldUseSeparatedView) {
+ if (!shouldUseSeparatedView) {
+ return new ArrayList<Action>(mItems);
+ }
+ ArrayList<Action> listActions = new ArrayList<Action>();
+ for (int i = 0; i < mItems.size(); i++) {
+ final Action action = mItems.get(i);
+ if (!action.shouldBeSeparated()) {
+ listActions.add(action);
+ }
+ }
+ return listActions;
+ }
+
@Override
public boolean areAllItemsEnabled() {
return false;
@@ -965,7 +996,7 @@ class GlobalActionsDialog implements DialogInterface.OnDismissListener,
View view = action.create(mContext, convertView, parent, LayoutInflater.from(mContext));
// Everything but screenshot, the last item, gets white background.
if (position == getCount() - 1) {
- HardwareUiLayout.get(parent).setDivisionView(view);
+ MultiListLayout.get(parent).setDivisionView(view);
}
return view;
}
@@ -1004,6 +1035,10 @@ class GlobalActionsDialog implements DialogInterface.OnDismissListener,
boolean showBeforeProvisioning();
boolean isEnabled();
+
+ default boolean shouldBeSeparated() {
+ return false;
+ }
}
/**
@@ -1423,9 +1458,7 @@ class GlobalActionsDialog implements DialogInterface.OnDismissListener,
private final Context mContext;
private final MyAdapter mAdapter;
- private final LinearLayout mListView;
- private final FrameLayout mSeparatedView;
- private final HardwareUiLayout mHardwareLayout;
+ private final MultiListLayout mGlobalActionsLayout;
private final OnClickListener mClickListener;
private final OnItemLongClickListener mLongClickListener;
private final GradientDrawable mGradientDrawable;
@@ -1466,16 +1499,11 @@ class GlobalActionsDialog implements DialogInterface.OnDismissListener,
window.setType(WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY);
setContentView(com.android.systemui.R.layout.global_actions_wrapped);
- mListView = findViewById(android.R.id.list);
- mSeparatedView = findViewById(com.android.systemui.R.id.separated_button);
- if (!mShouldDisplaySeparatedButton) {
- mSeparatedView.setVisibility(View.GONE);
- }
- mHardwareLayout = HardwareUiLayout.get(mListView);
- mHardwareLayout.setOutsideTouchListener(view -> dismiss());
- mHardwareLayout.setHasSeparatedButton(mShouldDisplaySeparatedButton);
- setTitle(R.string.global_actions);
- mListView.setAccessibilityDelegate(new View.AccessibilityDelegate() {
+ mGlobalActionsLayout = (MultiListLayout)
+ findViewById(com.android.systemui.R.id.global_actions_view);
+ mGlobalActionsLayout.setOutsideTouchListener(view -> dismiss());
+ mGlobalActionsLayout.setHasSeparatedView(mShouldDisplaySeparatedButton);
+ mGlobalActionsLayout.setListViewAccessibilityDelegate(new View.AccessibilityDelegate() {
@Override
public boolean dispatchPopulateAccessibilityEvent(
View host, AccessibilityEvent event) {
@@ -1484,20 +1512,33 @@ class GlobalActionsDialog implements DialogInterface.OnDismissListener,
return true;
}
});
+ setTitle(R.string.global_actions);
}
private void updateList() {
- mListView.removeAllViews();
- mSeparatedView.removeAllViews();
+ mGlobalActionsLayout.removeAllItems();
+ ArrayList<Action> separatedActions =
+ mAdapter.getSeparatedActions(mShouldDisplaySeparatedButton);
+ ArrayList<Action> listActions = mAdapter.getListActions(mShouldDisplaySeparatedButton);
+ mGlobalActionsLayout.setExpectedListItemCount(listActions.size());
+ mGlobalActionsLayout.setExpectedSeparatedItemCount(separatedActions.size());
+
for (int i = 0; i < mAdapter.getCount(); i++) {
- ViewGroup parentView = mShouldDisplaySeparatedButton && i == mAdapter.getCount() - 1
- ? mSeparatedView : mListView;
- View v = mAdapter.getView(i, null, parentView);
+ Action action = mAdapter.getItem(i);
+ int separatedIndex = separatedActions.indexOf(action);
+ ViewGroup parent;
+ if (separatedIndex != -1) {
+ parent = mGlobalActionsLayout.getParentView(true, separatedIndex);
+ } else {
+ int listIndex = listActions.indexOf(action);
+ parent = mGlobalActionsLayout.getParentView(false, listIndex);
+ }
+ View v = mAdapter.getView(i, null, parent);
final int pos = i;
v.setOnClickListener(view -> mClickListener.onClick(this, pos));
v.setOnLongClickListener(view ->
mLongClickListener.onItemLongClick(null, v, pos, 0));
- parentView.addView(v);
+ parent.addView(v);
}
}
@@ -1543,9 +1584,9 @@ class GlobalActionsDialog implements DialogInterface.OnDismissListener,
super.show();
mShowing = true;
mGradientDrawable.setAlpha(0);
- mHardwareLayout.setTranslationX(getAnimTranslation());
- mHardwareLayout.setAlpha(0);
- mHardwareLayout.animate()
+ mGlobalActionsLayout.setTranslationX(getAnimTranslation());
+ mGlobalActionsLayout.setAlpha(0);
+ mGlobalActionsLayout.animate()
.alpha(1)
.translationX(0)
.setDuration(300)
@@ -1564,9 +1605,9 @@ class GlobalActionsDialog implements DialogInterface.OnDismissListener,
return;
}
mShowing = false;
- mHardwareLayout.setTranslationX(0);
- mHardwareLayout.setAlpha(1);
- mHardwareLayout.animate()
+ mGlobalActionsLayout.setTranslationX(0);
+ mGlobalActionsLayout.setAlpha(1);
+ mGlobalActionsLayout.animate()
.alpha(0)
.translationX(getAnimTranslation())
.setDuration(300)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
index 6a0e8ad35736..c4c8bc703802 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
@@ -50,9 +50,9 @@ import androidx.slice.builders.SliceAction;
import com.android.internal.annotations.VisibleForTesting;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.KeyguardUpdateMonitorCallback;
-import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.statusbar.NotificationMediaManager;
+import com.android.systemui.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.policy.NextAlarmController;
import com.android.systemui.statusbar.policy.NextAlarmControllerImpl;
import com.android.systemui.statusbar.policy.ZenModeController;
@@ -68,7 +68,7 @@ import java.util.concurrent.TimeUnit;
*/
public class KeyguardSliceProvider extends SliceProvider implements
NextAlarmController.NextAlarmChangeCallback, ZenModeController.Callback,
- NotificationMediaManager.MediaListener {
+ NotificationMediaManager.MediaListener, StatusBarStateController.StateListener {
private static final StyleSpan BOLD_STYLE = new StyleSpan(Typeface.BOLD);
public static final String KEYGUARD_SLICE_URI = "content://com.android.systemui.keyguard/main";
@@ -109,7 +109,9 @@ public class KeyguardSliceProvider extends SliceProvider implements
private AlarmManager.AlarmClockInfo mNextAlarmInfo;
private PendingIntent mPendingIntent;
protected NotificationMediaManager mMediaManager;
+ private StatusBarStateController mStatusBarStateController;
protected MediaMetadata mMediaMetaData;
+ protected boolean mDozing;
/**
* Receiver responsible for time ticking and updating the date format.
@@ -167,9 +169,20 @@ public class KeyguardSliceProvider extends SliceProvider implements
mMediaUri = Uri.parse(KEYGUARD_MEDIA_URI);
}
- public void initDependencies() {
- mMediaManager = Dependency.get(NotificationMediaManager.class);
+ /**
+ * Initialize dependencies that don't exist during {@link android.content.ContentProvider}
+ * instantiation.
+ *
+ * @param mediaManager {@link NotificationMediaManager} singleton.
+ * @param statusBarStateController {@link StatusBarStateController} singleton.
+ */
+ public void initDependencies(
+ NotificationMediaManager mediaManager,
+ StatusBarStateController statusBarStateController) {
+ mMediaManager = mediaManager;
mMediaManager.addCallback(this);
+ mStatusBarStateController = statusBarStateController;
+ mStatusBarStateController.addCallback(this);
}
@AnyThread
@@ -179,7 +192,7 @@ public class KeyguardSliceProvider extends SliceProvider implements
Slice slice;
synchronized (this) {
ListBuilder builder = new ListBuilder(getContext(), mSliceUri, ListBuilder.INFINITY);
- if (mMediaMetaData != null) {
+ if (needsMediaLocked()) {
addMediaLocked(builder);
} else {
builder.addRow(new RowBuilder(mDateUri).setTitle(mLastText));
@@ -193,6 +206,10 @@ public class KeyguardSliceProvider extends SliceProvider implements
return slice;
}
+ protected boolean needsMediaLocked() {
+ return mMediaMetaData != null && mDozing;
+ }
+
protected void addMediaLocked(ListBuilder listBuilder) {
if (mMediaMetaData != null) {
SpannableStringBuilder builder = new SpannableStringBuilder();
@@ -209,7 +226,7 @@ public class KeyguardSliceProvider extends SliceProvider implements
}
RowBuilder mediaBuilder = new RowBuilder(mMediaUri).setTitle(builder);
- Icon notificationIcon = mMediaManager.getMediaIcon();
+ Icon notificationIcon = mMediaManager == null ? null : mMediaManager.getMediaIcon();
if (notificationIcon != null) {
IconCompat icon = IconCompat.createFromIcon(notificationIcon);
mediaBuilder.addEndItem(icon, ListBuilder.ICON_IMAGE);
@@ -389,13 +406,35 @@ public class KeyguardSliceProvider extends SliceProvider implements
@Override
public void onMetadataChanged(MediaMetadata metadata) {
+ final boolean notify;
synchronized (this) {
+ boolean neededMedia = needsMediaLocked();
mMediaMetaData = metadata;
+ notify = neededMedia != needsMediaLocked();
+ }
+ if (notify) {
+ notifyChange();
}
- notifyChange();
}
protected void notifyChange() {
mContentResolver.notifyChange(mSliceUri, null /* observer */);
}
+
+ @Override
+ public void onDozingChanged(boolean isDozing) {
+ final boolean notify;
+ synchronized (this) {
+ boolean neededMedia = needsMediaLocked();
+ mDozing = isDozing;
+ notify = neededMedia != needsMediaLocked();
+ }
+ if (notify) {
+ notifyChange();
+ }
+ }
+
+ @Override
+ public void onStateChanged(int newState) {
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 323cf1fe7022..f14495bb959d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -1843,6 +1843,13 @@ public class KeyguardViewMediator extends SystemUI {
*/
private void handleHide() {
Trace.beginSection("KeyguardViewMediator#handleHide");
+
+ // It's possible that the device was unlocked in a dream state. It's time to wake up.
+ if (mAodShowing) {
+ PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
+ pm.wakeUp(SystemClock.uptimeMillis(), "com.android.systemui:BOUNCER_DOZING");
+ }
+
synchronized (KeyguardViewMediator.this) {
if (DEBUG) Log.d(TAG, "handleHide");
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipAppOpsListener.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipAppOpsListener.java
index 1e0d4d01f167..b09d6e163b77 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipAppOpsListener.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipAppOpsListener.java
@@ -36,8 +36,7 @@ public class PipAppOpsListener {
private Handler mHandler;
private IActivityManager mActivityManager;
private AppOpsManager mAppOpsManager;
-
- private PipMotionHelper mMotionHelper;
+ private Callback mCallback;
private AppOpsManager.OnOpChangedListener mAppOpsChangedListener = new OnOpChangedListener() {
@Override
@@ -52,7 +51,7 @@ public class PipAppOpsListener {
if (appInfo.packageName.equals(topPipActivityInfo.first.getPackageName()) &&
mAppOpsManager.checkOpNoThrow(OP_PICTURE_IN_PICTURE, appInfo.uid,
packageName) != MODE_ALLOWED) {
- mHandler.post(() -> mMotionHelper.dismissPip());
+ mHandler.post(() -> mCallback.dismissPip());
}
}
} catch (NameNotFoundException e) {
@@ -63,12 +62,12 @@ public class PipAppOpsListener {
};
public PipAppOpsListener(Context context, IActivityManager activityManager,
- PipMotionHelper motionHelper) {
+ Callback callback) {
mContext = context;
mHandler = new Handler(mContext.getMainLooper());
mActivityManager = activityManager;
mAppOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
- mMotionHelper = motionHelper;
+ mCallback = callback;
}
public void onActivityPinned(String packageName) {
@@ -89,4 +88,10 @@ public class PipAppOpsListener {
private void unregisterAppOpsListener() {
mAppOpsManager.stopWatchingMode(mAppOpsChangedListener);
}
-} \ No newline at end of file
+
+ /** Callback for PipAppOpsListener to request changes to the PIP window. */
+ public interface Callback {
+ /** Dismisses the PIP window. */
+ void dismissPip();
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java
index 38583564f5b0..82aa4737af99 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java
@@ -55,7 +55,7 @@ import java.io.PrintWriter;
/**
* A helper to animate and manipulate the PiP.
*/
-public class PipMotionHelper implements Handler.Callback {
+public class PipMotionHelper implements Handler.Callback, PipAppOpsListener.Callback {
private static final String TAG = "PipMotionHelper";
private static final boolean DEBUG = false;
@@ -172,7 +172,8 @@ public class PipMotionHelper implements Handler.Callback {
/**
* Dismisses the pinned stack.
*/
- void dismissPip() {
+ @Override
+ public void dismissPip() {
if (DEBUG) {
Log.d(TAG, "dismissPip: callers=\n" + Debug.getCallers(5, " "));
}
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyDialog.kt b/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyDialog.kt
index 01ee5cabe5a0..77e25e324915 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyDialog.kt
@@ -20,6 +20,7 @@ import android.content.Context
import android.content.DialogInterface
import android.content.Intent
import android.content.res.ColorStateList
+import android.util.IconDrawableFactory
import android.view.Gravity
import android.view.LayoutInflater
import android.view.View
@@ -37,11 +38,22 @@ class OngoingPrivacyDialog constructor(
private val iconSize = context.resources.getDimensionPixelSize(
R.dimen.ongoing_appops_dialog_icon_size)
+ private val plusSize = context.resources.getDimensionPixelSize(
+ R.dimen.ongoing_appops_dialog_app_plus_size)
private val iconColor = context.resources.getColor(
com.android.internal.R.color.text_color_primary, context.theme)
+ private val plusColor: Int
private val iconMargin = context.resources.getDimensionPixelSize(
R.dimen.ongoing_appops_dialog_icon_margin)
private val MAX_ITEMS = context.resources.getInteger(R.integer.ongoing_appops_dialog_max_apps)
+ private val iconFactory = IconDrawableFactory.newInstance(context, true)
+
+ init {
+ val a = context.theme.obtainStyledAttributes(
+ intArrayOf(com.android.internal.R.attr.colorAccent))
+ plusColor = a.getColor(0, 0)
+ a.recycle()
+ }
fun createDialog(): Dialog {
val builder = AlertDialog.Builder(context).apply {
@@ -52,7 +64,8 @@ class OngoingPrivacyDialog constructor(
@Suppress("DEPRECATION")
override fun onClick(dialog: DialogInterface?, which: Int) {
- Dependency.get(ActivityStarter::class.java).startActivity(intent, false)
+ Dependency.get(ActivityStarter::class.java)
+ .postStartActivityDismissingKeyguard(intent, 0)
}
})
}
@@ -86,9 +99,15 @@ class OngoingPrivacyDialog constructor(
numItems - MAX_ITEMS
)
val overflowPlus = overflow.findViewById(R.id.app_icon) as ImageView
+ val lp = overflowPlus.layoutParams.apply {
+ height = plusSize
+ width = plusSize
+ }
+ overflowPlus.layoutParams = lp
overflowPlus.apply {
- imageTintList = ColorStateList.valueOf(iconColor)
- setImageDrawable(context.getDrawable(R.drawable.plus))
+ val plus = context.getDrawable(R.drawable.plus)
+ imageTintList = ColorStateList.valueOf(plusColor)
+ setImageDrawable(plus)
}
}
@@ -113,17 +132,18 @@ class OngoingPrivacyDialog constructor(
}
app.icon.let {
- appIcon.setImageDrawable(it)
+ appIcon.setImageDrawable(iconFactory.getShadowedIcon(it))
}
appName.text = app.applicationName
if (showIcons) {
- dialogBuilder.generateIconsForApp(types).forEach {
+ dialogBuilder.generateIconsForApp(types).forEachIndexed { index, it ->
it.setBounds(0, 0, iconSize, iconSize)
val image = ImageView(context).apply {
imageTintList = ColorStateList.valueOf(iconColor)
setImageDrawable(it)
}
+ image.contentDescription = types[index].getName(context)
icons.addView(image, lp)
}
icons.visibility = View.VISIBLE
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
index 72245999b084..75ab5dfd2977 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
@@ -141,14 +141,11 @@ public class QuickStatusBarHeader extends RelativeLayout implements
private View mStatusSeparator;
private ImageView mRingerModeIcon;
private TextView mRingerModeTextView;
- private BatteryMeterView mBatteryMeterView;
private Clock mClockView;
private DateView mDateView;
private OngoingPrivacyChip mPrivacyChip;
private Space mSpace;
private BatteryMeterView mBatteryRemainingIcon;
- private TextView mBatteryRemainingText;
- private boolean mShowBatteryPercentAndEstimate;
private PrivacyItemController mPrivacyItemController;
/** Counts how many times the long press tooltip has been shown to the user. */
@@ -229,13 +226,6 @@ public class QuickStatusBarHeader extends RelativeLayout implements
// Set the correct tint for the status icons so they contrast
mIconManager.setTint(fillColor);
- mShowBatteryPercentAndEstimate = mContext.getResources().getBoolean(
- com.android.internal.R.bool.config_battery_percentage_setting_available);
-
- mBatteryMeterView = findViewById(R.id.battery);
- mBatteryMeterView.setPercentShowMode(mShowBatteryPercentAndEstimate
- ? BatteryMeterView.MODE_ON : BatteryMeterView.MODE_OFF);
- mBatteryMeterView.setOnClickListener(this);
mClockView = findViewById(R.id.clock);
mClockView.setOnClickListener(this);
mDateView = findViewById(R.id.date);
@@ -245,13 +235,8 @@ public class QuickStatusBarHeader extends RelativeLayout implements
// Tint for the battery icons are handled in setupHost()
mBatteryRemainingIcon = findViewById(R.id.batteryRemainingIcon);
- mBatteryRemainingIcon.setPercentShowMode(BatteryMeterView.MODE_OFF);
// Don't need to worry about tuner settings for this icon
mBatteryRemainingIcon.setIgnoreTunerUpdates(true);
-
- mBatteryRemainingText = findViewById(R.id.batteryRemainingText);
- mBatteryRemainingText.setTextColor(fillColor);
-
updateShowPercent();
}
@@ -268,10 +253,8 @@ public class QuickStatusBarHeader extends RelativeLayout implements
}
private void setChipVisibility(boolean chipVisible) {
- mBatteryMeterView.setVisibility(View.VISIBLE);
if (chipVisible) {
mPrivacyChip.setVisibility(View.VISIBLE);
- if (mHasTopCutout) mBatteryMeterView.setVisibility(View.GONE);
} else {
mPrivacyChip.setVisibility(View.GONE);
}
@@ -339,7 +322,6 @@ public class QuickStatusBarHeader extends RelativeLayout implements
// Update color schemes in landscape to use wallpaperTextColor
boolean shouldUseWallpaperTextColor =
newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE;
- mBatteryMeterView.useWallpaperTextColor(shouldUseWallpaperTextColor);
mClockView.useWallpaperTextColor(shouldUseWallpaperTextColor);
}
@@ -415,17 +397,11 @@ public class QuickStatusBarHeader extends RelativeLayout implements
.build();
}
- private void updateBatteryRemainingText() {
- if (!mShowBatteryPercentAndEstimate) {
- return;
- }
- mBatteryRemainingText.setText(mBatteryController.getEstimatedTimeRemainingString());
- }
-
public void setExpanded(boolean expanded) {
if (mExpanded == expanded) return;
mExpanded = expanded;
mHeaderQsPanel.setExpanded(expanded);
+ updateEverything();
}
/**
@@ -518,7 +494,6 @@ public class QuickStatusBarHeader extends RelativeLayout implements
}
}
mSpace.setLayoutParams(lp);
- // Decide whether to show BatteryMeterView
setChipVisibility(mPrivacyChip.getVisibility() == View.VISIBLE);
return super.onApplyWindowInsets(insets);
}
@@ -545,7 +520,6 @@ public class QuickStatusBarHeader extends RelativeLayout implements
mAlarmController.addCallback(this);
mContext.registerReceiver(mRingerReceiver,
new IntentFilter(AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION));
- updateBatteryRemainingText();
} else {
mZenController.removeCallback(this);
mAlarmController.removeCallback(this);
@@ -558,9 +532,6 @@ public class QuickStatusBarHeader extends RelativeLayout implements
if (v == mClockView) {
mActivityStarter.postStartActivityDismissingKeyguard(new Intent(
AlarmClock.ACTION_SHOW_ALARMS),0);
- } else if (v == mBatteryMeterView) {
- mActivityStarter.postStartActivityDismissingKeyguard(new Intent(
- Intent.ACTION_POWER_USAGE_SUMMARY),0);
} else if (v == mPrivacyChip) {
Handler mUiHandler = new Handler(Looper.getMainLooper());
mUiHandler.post(() -> {
@@ -697,6 +668,10 @@ public class QuickStatusBarHeader extends RelativeLayout implements
.start();
}
+ public void updateEverything() {
+ post(() -> setClickable(!mExpanded));
+ }
+
public void setQSPanel(final QSPanel qsPanel) {
mQsPanel = qsPanel;
setupHost(qsPanel.getHost());
@@ -708,9 +683,6 @@ public class QuickStatusBarHeader extends RelativeLayout implements
mHeaderQsPanel.setQSPanelAndHeader(mQsPanel, this);
mHeaderQsPanel.setHost(host, null /* No customization in header */);
- // Use SystemUI context to get battery meter colors, and let it use the default tint (white)
- mBatteryMeterView.setColorsFromContext(mHost.getContext());
- mBatteryMeterView.onDarkChanged(new Rect(), 0, DarkIconDispatcher.DEFAULT_ICON_TINT);
Rect tintArea = new Rect(0, 0, 0, 0);
int colorForeground = Utils.getColorAttrDefaultColor(getContext(),
@@ -758,22 +730,8 @@ public class QuickStatusBarHeader extends RelativeLayout implements
.getIntForUser(getContext().getContentResolver(),
SHOW_BATTERY_PERCENT, 0, ActivityManager.getCurrentUser());
- mShowBatteryPercentAndEstimate = systemSetting;
-
- updateBatteryViews();
- }
-
- private void updateBatteryViews() {
- if (mShowBatteryPercentAndEstimate) {
- mBatteryMeterView.setPercentShowMode(BatteryMeterView.MODE_ON);
- mBatteryRemainingIcon.setVisibility(View.VISIBLE);
- mBatteryRemainingText.setVisibility(View.VISIBLE);
- updateBatteryRemainingText();
- } else {
- mBatteryMeterView.setPercentShowMode(BatteryMeterView.MODE_OFF);
- mBatteryRemainingIcon.setVisibility(View.GONE);
- mBatteryRemainingText.setVisibility(View.GONE);
- }
+ mBatteryRemainingIcon.setPercentShowMode(systemSetting
+ ? BatteryMeterView.MODE_ESTIMATE : BatteryMeterView.MODE_ON);
}
private final class PercentSettingObserver extends ContentObserver {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyTile.java
index 5230cea88e8e..7ee37d567a55 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyTile.java
@@ -84,7 +84,7 @@ public class SensorPrivacyTile extends QSTileImpl<BooleanState> implements
@Override
public Intent getLongClickIntent() {
- return null;
+ return new Intent();
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index 81757d0aadd4..c474faf6b1e0 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -101,6 +101,7 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis
private float mBackButtonAlpha;
private MotionEvent mStatusBarGestureDownEvent;
private float mWindowCornerRadius;
+ private boolean mSupportsRoundedCornersOnWindows;
private ISystemUiProxy mSysUiProxy = new ISystemUiProxy.Stub() {
@@ -244,6 +245,18 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis
}
}
+ public boolean supportsRoundedCornersOnWindows() {
+ if (!verifyCaller("supportsRoundedCornersOnWindows")) {
+ return false;
+ }
+ long token = Binder.clearCallingIdentity();
+ try {
+ return mSupportsRoundedCornersOnWindows;
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
private boolean verifyCaller(String reason) {
final int callerId = Binder.getCallingUserHandle().getIdentifier();
if (callerId != mCurrentBoundedUserId) {
@@ -353,6 +366,8 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis
mInteractionFlags = Prefs.getInt(mContext, Prefs.Key.QUICK_STEP_INTERACTION_FLAGS,
getDefaultInteractionFlags());
mWindowCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(mContext.getResources());
+ mSupportsRoundedCornersOnWindows = ScreenDecorationsUtils
+ .supportsRoundedCornersOnWindows(mContext.getResources());
// Listen for the package update changes.
if (mDeviceProvisionedController.getCurrentUser() == UserHandle.USER_SYSTEM) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/AlertingNotificationManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/AlertingNotificationManager.java
index bc381699494a..a776d0fbb45e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/AlertingNotificationManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/AlertingNotificationManager.java
@@ -16,8 +16,6 @@
package com.android.systemui.statusbar;
-import static com.android.systemui.statusbar.notification.NotificationData.Entry;
-
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.Handler;
@@ -29,6 +27,7 @@ import android.util.Log;
import android.view.accessibility.AccessibilityEvent;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.row.NotificationInflater.InflationFlag;
import java.util.stream.Stream;
@@ -48,7 +47,7 @@ public abstract class AlertingNotificationManager implements NotificationLifetim
* NotificationManagerService side, but we keep it to prevent the UI from looking weird and
* will remove when possible. See {@link NotificationLifetimeExtender}
*/
- protected final ArraySet<Entry> mExtendedLifetimeAlertEntries = new ArraySet<>();
+ protected final ArraySet<NotificationEntry> mExtendedLifetimeAlertEntries = new ArraySet<>();
protected NotificationSafeToRemoveCallback mNotificationLifetimeFinishedCallback;
protected int mMinimumDisplayTime;
@@ -61,7 +60,7 @@ public abstract class AlertingNotificationManager implements NotificationLifetim
* Adds the notification to be managed.
* @param entry entry to show
*/
- public void showNotification(@NonNull Entry entry) {
+ public void showNotification(@NonNull NotificationEntry entry) {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "showNotification");
}
@@ -139,7 +138,7 @@ public abstract class AlertingNotificationManager implements NotificationLifetim
* @return the entry
*/
@Nullable
- public Entry getEntry(@NonNull String key) {
+ public NotificationEntry getEntry(@NonNull String key) {
AlertEntry entry = mAlertEntries.get(key);
return entry != null ? entry.mEntry : null;
}
@@ -149,7 +148,7 @@ public abstract class AlertingNotificationManager implements NotificationLifetim
* @return all entries
*/
@NonNull
- public Stream<Entry> getAllEntries() {
+ public Stream<NotificationEntry> getAllEntries() {
return mAlertEntries.values().stream().map(headsUpEntry -> headsUpEntry.mEntry);
}
@@ -180,7 +179,7 @@ public abstract class AlertingNotificationManager implements NotificationLifetim
* Add a new entry and begin managing it.
* @param entry the entry to add
*/
- protected final void addAlertEntry(@NonNull Entry entry) {
+ protected final void addAlertEntry(@NonNull NotificationEntry entry) {
AlertEntry alertEntry = createAlertEntry();
alertEntry.setEntry(entry);
mAlertEntries.put(entry.key, alertEntry);
@@ -203,7 +202,7 @@ public abstract class AlertingNotificationManager implements NotificationLifetim
if (alertEntry == null) {
return;
}
- Entry entry = alertEntry.mEntry;
+ NotificationEntry entry = alertEntry.mEntry;
mAlertEntries.remove(key);
onAlertEntryRemoved(alertEntry);
entry.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
@@ -250,12 +249,12 @@ public abstract class AlertingNotificationManager implements NotificationLifetim
}
@Override
- public boolean shouldExtendLifetime(Entry entry) {
+ public boolean shouldExtendLifetime(NotificationEntry entry) {
return !canRemoveImmediately(entry.key);
}
@Override
- public void setShouldManageLifetime(Entry entry, boolean shouldExtend) {
+ public void setShouldManageLifetime(NotificationEntry entry, boolean shouldExtend) {
if (shouldExtend) {
mExtendedLifetimeAlertEntries.add(entry);
} else {
@@ -265,17 +264,17 @@ public abstract class AlertingNotificationManager implements NotificationLifetim
///////////////////////////////////////////////////////////////////////////////////////////////
protected class AlertEntry implements Comparable<AlertEntry> {
- @Nullable public Entry mEntry;
+ @Nullable public NotificationEntry mEntry;
public long mPostTime;
public long mEarliestRemovaltime;
@Nullable protected Runnable mRemoveAlertRunnable;
- public void setEntry(@NonNull final Entry entry) {
+ public void setEntry(@NonNull final NotificationEntry entry) {
setEntry(entry, () -> removeAlertEntry(entry.key));
}
- public void setEntry(@NonNull final Entry entry,
+ public void setEntry(@NonNull final NotificationEntry entry,
@Nullable Runnable removeAlertRunnable) {
mEntry = entry;
mRemoveAlertRunnable = removeAlertRunnable;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/AmbientPulseManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/AmbientPulseManager.java
index 9bfd4ee24ff0..a3beb96780d3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/AmbientPulseManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/AmbientPulseManager.java
@@ -25,7 +25,7 @@ import android.util.ArraySet;
import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.R;
-import com.android.systemui.statusbar.notification.NotificationData;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.row.NotificationInflater.InflationFlag;
import javax.inject.Inject;
@@ -83,7 +83,7 @@ public class AmbientPulseManager extends AlertingNotificationManager {
@Override
protected void onAlertEntryAdded(AlertEntry alertEntry) {
- NotificationData.Entry entry = alertEntry.mEntry;
+ NotificationEntry entry = alertEntry.mEntry;
entry.setAmbientPulsing(true);
for (OnAmbientChangedListener listener : mListeners) {
listener.onAmbientStateChanged(entry, true);
@@ -92,7 +92,7 @@ public class AmbientPulseManager extends AlertingNotificationManager {
@Override
protected void onAlertEntryRemoved(AlertEntry alertEntry) {
- NotificationData.Entry entry = alertEntry.mEntry;
+ NotificationEntry entry = alertEntry.mEntry;
entry.setAmbientPulsing(false);
for (OnAmbientChangedListener listener : mListeners) {
listener.onAmbientStateChanged(entry, false);
@@ -131,7 +131,7 @@ public class AmbientPulseManager extends AlertingNotificationManager {
* @param entry the entry that changed
* @param isPulsing true if the entry is now pulsing, false otherwise
*/
- void onAmbientStateChanged(NotificationData.Entry entry, boolean isPulsing);
+ void onAmbientStateChanged(NotificationEntry entry, boolean isPulsing);
}
private final class AmbientEntry extends AlertEntry {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index 6a015630a076..904478efb568 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -111,7 +111,6 @@ public class CommandQueue extends IStatusBar.Stub implements CallbackController<
private static final int MSG_SHOW_CHARGING_ANIMATION = 44 << MSG_SHIFT;
private static final int MSG_SHOW_PINNING_TOAST_ENTER_EXIT = 45 << MSG_SHIFT;
private static final int MSG_SHOW_PINNING_TOAST_ESCAPE = 46 << MSG_SHIFT;
- private static final int MSG_BIOMETRIC_TRY_AGAIN = 47 << MSG_SHIFT;
public static final int FLAG_EXCLUDE_NONE = 0;
public static final int FLAG_EXCLUDE_SEARCH_PANEL = 1 << 0;
@@ -271,11 +270,10 @@ public class CommandQueue extends IStatusBar.Stub implements CallbackController<
default void showBiometricDialog(Bundle bundle, IBiometricServiceReceiverInternal receiver,
int type, boolean requireConfirmation, int userId) { }
- default void onBiometricAuthenticated() { }
+ default void onBiometricAuthenticated(boolean authenticated) { }
default void onBiometricHelp(String message) { }
default void onBiometricError(String error) { }
default void hideBiometricDialog() { }
- default void showBiometricTryAgain() { }
}
@VisibleForTesting
@@ -736,9 +734,9 @@ public class CommandQueue extends IStatusBar.Stub implements CallbackController<
}
@Override
- public void onBiometricAuthenticated() {
+ public void onBiometricAuthenticated(boolean authenticated) {
synchronized (mLock) {
- mHandler.obtainMessage(MSG_BIOMETRIC_AUTHENTICATED).sendToTarget();
+ mHandler.obtainMessage(MSG_BIOMETRIC_AUTHENTICATED, authenticated).sendToTarget();
}
}
@@ -763,13 +761,6 @@ public class CommandQueue extends IStatusBar.Stub implements CallbackController<
}
}
- @Override
- public void showBiometricTryAgain() {
- synchronized (mLock) {
- mHandler.obtainMessage(MSG_BIOMETRIC_TRY_AGAIN).sendToTarget();
- }
- }
-
private final class H extends Handler {
private H(Looper l) {
super(l);
@@ -991,7 +982,7 @@ public class CommandQueue extends IStatusBar.Stub implements CallbackController<
break;
case MSG_BIOMETRIC_AUTHENTICATED:
for (int i = 0; i < mCallbacks.size(); i++) {
- mCallbacks.get(i).onBiometricAuthenticated();
+ mCallbacks.get(i).onBiometricAuthenticated((boolean) msg.obj);
}
break;
case MSG_BIOMETRIC_HELP:
@@ -1024,11 +1015,6 @@ public class CommandQueue extends IStatusBar.Stub implements CallbackController<
mCallbacks.get(i).showPinningEscapeToast();
}
break;
- case MSG_BIOMETRIC_TRY_AGAIN:
- for (int i = 0; i < mCallbacks.size(); i++) {
- mCallbacks.get(i).showBiometricTryAgain();
- }
- break;
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/FlingAnimationUtils.java b/packages/SystemUI/src/com/android/systemui/statusbar/FlingAnimationUtils.java
index 758fb7a9971e..22d1d5b233ce 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/FlingAnimationUtils.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/FlingAnimationUtils.java
@@ -18,12 +18,14 @@ package com.android.systemui.statusbar;
import android.animation.Animator;
import android.content.Context;
+import android.util.Log;
import android.view.ViewPropertyAnimator;
import android.view.animation.Interpolator;
import android.view.animation.PathInterpolator;
import com.android.systemui.Interpolators;
import com.android.systemui.statusbar.notification.NotificationUtils;
+import com.android.systemui.statusbar.phone.StatusBar;
/**
* Utility class to calculate general fling animation when the finger is released.
@@ -196,9 +198,16 @@ public class FlingAnimationUtils {
if (startGradient != mCachedStartGradient
|| velocityFactor != mCachedVelocityFactor) {
float speedup = mSpeedUpFactor * (1.0f - velocityFactor);
- mInterpolator = new PathInterpolator(speedup,
- speedup * startGradient,
- mLinearOutSlowInX2, mY2);
+ float x1 = speedup;
+ float y1 = speedup * startGradient;
+ float x2 = mLinearOutSlowInX2;
+ float y2 = mY2;
+ try {
+ mInterpolator = new PathInterpolator(x1, y1, x2, y2);
+ } catch (IllegalArgumentException e) {
+ throw new IllegalArgumentException("Illegal path with "
+ + "x1=" + x1 + " y1=" + y1 + " x2=" + x2 + " y2=" + y2, e);
+ }
mCachedStartGradient = startGradient;
mCachedVelocityFactor = velocityFactor;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/HeadsUpStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/HeadsUpStatusBarView.java
index e2177774a75d..3f1ff33437b9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/HeadsUpStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/HeadsUpStatusBarView.java
@@ -32,7 +32,7 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.keyguard.AlphaOptimizedLinearLayout;
import com.android.systemui.R;
import com.android.systemui.plugins.DarkIconDispatcher;
-import com.android.systemui.statusbar.notification.NotificationData;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import java.util.List;
@@ -50,7 +50,7 @@ public class HeadsUpStatusBarView extends AlphaOptimizedLinearLayout {
private int mEndMargin;
private View mIconPlaceholder;
private TextView mTextView;
- private NotificationData.Entry mShowingEntry;
+ private NotificationEntry mShowingEntry;
private Rect mLayoutedIconRect = new Rect();
private int[] mTmpPosition = new int[2];
private boolean mFirstLayout = true;
@@ -162,7 +162,7 @@ public class HeadsUpStatusBarView extends AlphaOptimizedLinearLayout {
mTextView = findViewById(R.id.text);
}
- public void setEntry(NotificationData.Entry entry) {
+ public void setEntry(NotificationEntry entry) {
if (entry != null) {
mShowingEntry = entry;
CharSequence text = entry.headsUpStatusBarText;
@@ -261,7 +261,7 @@ public class HeadsUpStatusBarView extends AlphaOptimizedLinearLayout {
return super.fitSystemWindows(insets);
}
- public NotificationData.Entry getShowingEntry() {
+ public NotificationEntry getShowingEntry() {
return mShowingEntry;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLifetimeExtender.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLifetimeExtender.java
index ecd9814c3073..0f295ba75fe4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLifetimeExtender.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLifetimeExtender.java
@@ -2,7 +2,7 @@ package com.android.systemui.statusbar;
import androidx.annotation.NonNull;
-import com.android.systemui.statusbar.notification.NotificationData;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
/**
* Interface for anything that may need to keep notifications managed even after
@@ -24,7 +24,7 @@ public interface NotificationLifetimeExtender {
* @param entry the entry containing the notification to check
* @return true if the notification lifetime should be extended
*/
- boolean shouldExtendLifetime(@NonNull NotificationData.Entry entry);
+ boolean shouldExtendLifetime(@NonNull NotificationEntry entry);
/**
* Sets whether or not the lifetime should be managed by the extender. In practice, if
@@ -37,7 +37,7 @@ public interface NotificationLifetimeExtender {
* @param entry the entry that needs an extended lifetime
* @param shouldManage true if the extender should manage the entry now, false otherwise
*/
- void setShouldManageLifetime(@NonNull NotificationData.Entry entry, boolean shouldManage);
+ void setShouldManageLifetime(@NonNull NotificationEntry entry, boolean shouldManage);
/**
* The callback for when the notification is now safe to remove (i.e. its lifetime has ended).
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java
index bc662e3d8855..f46ded4d61d8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java
@@ -18,7 +18,7 @@ import android.content.pm.UserInfo;
import android.service.notification.StatusBarNotification;
import android.util.SparseArray;
-import com.android.systemui.statusbar.notification.NotificationData.Entry;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
public interface NotificationLockscreenUserManager {
String PERMISSION_SELF = "com.android.systemui.permission.SELF";
@@ -55,7 +55,7 @@ public interface NotificationLockscreenUserManager {
void updatePublicMode();
- boolean needsRedaction(Entry entry);
+ boolean needsRedaction(NotificationEntry entry);
boolean userAllowsPrivateNotificationsInPublic(int currentUserId);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
index bba4369b5e01..d5f4d0461ba4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
@@ -46,9 +46,9 @@ import com.android.systemui.Dependency;
import com.android.systemui.Dumpable;
import com.android.systemui.recents.OverviewProxyService;
import com.android.systemui.statusbar.StatusBarStateController.StateListener;
-import com.android.systemui.statusbar.notification.NotificationData;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.NotificationUtils;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.statusbar.policy.KeyguardMonitor;
@@ -407,7 +407,7 @@ public class NotificationLockscreenUserManagerImpl implements
}
/** @return true if the entry needs redaction when on the lockscreen. */
- public boolean needsRedaction(NotificationData.Entry ent) {
+ public boolean needsRedaction(NotificationEntry ent) {
int userId = ent.notification.getUserId();
boolean currentUserWantsRedaction = !userAllowsPrivateNotificationsInPublic(mCurrentUserId);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
index 1bf101c00711..7412702abfea 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
@@ -37,7 +37,6 @@ import android.media.session.PlaybackState;
import android.os.Handler;
import android.os.Trace;
import android.os.UserHandle;
-import android.service.notification.StatusBarNotification;
import android.util.Log;
import android.view.View;
import android.widget.ImageView;
@@ -47,9 +46,9 @@ import com.android.systemui.Dependency;
import com.android.systemui.Dumpable;
import com.android.systemui.Interpolators;
import com.android.systemui.colorextraction.SysuiColorExtractor;
-import com.android.systemui.statusbar.notification.NotificationData.Entry;
import com.android.systemui.statusbar.notification.NotificationEntryListener;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.phone.BiometricUnlockController;
import com.android.systemui.statusbar.phone.LockscreenWallpaper;
import com.android.systemui.statusbar.phone.ScrimController;
@@ -157,15 +156,10 @@ public class NotificationMediaManager implements Dumpable {
notificationEntryManager.addNotificationEntryListener(new NotificationEntryListener() {
@Override
public void onEntryRemoved(
- @Nullable Entry entry,
- String key,
- StatusBarNotification old,
+ NotificationEntry entry,
NotificationVisibility visibility,
- boolean lifetimeExtended,
boolean removedByUser) {
- if (!lifetimeExtended) {
- onNotificationRemoved(key);
- }
+ onNotificationRemoved(entry.key);
}
});
}
@@ -194,7 +188,7 @@ public class NotificationMediaManager implements Dumpable {
return null;
}
synchronized (mEntryManager.getNotificationData()) {
- Entry entry = mEntryManager.getNotificationData().get(mMediaNotificationKey);
+ NotificationEntry entry = mEntryManager.getNotificationData().get(mMediaNotificationKey);
if (entry == null || entry.expandedIcon == null) {
return null;
}
@@ -216,15 +210,15 @@ public class NotificationMediaManager implements Dumpable {
boolean metaDataChanged = false;
synchronized (mEntryManager.getNotificationData()) {
- ArrayList<Entry> activeNotifications =
+ ArrayList<NotificationEntry> activeNotifications =
mEntryManager.getNotificationData().getActiveNotifications();
final int N = activeNotifications.size();
// Promote the media notification with a controller in 'playing' state, if any.
- Entry mediaNotification = null;
+ NotificationEntry mediaNotification = null;
MediaController controller = null;
for (int i = 0; i < N; i++) {
- final Entry entry = activeNotifications.get(i);
+ final NotificationEntry entry = activeNotifications.get(i);
if (entry.isMediaNotification()) {
final MediaSession.Token token =
@@ -264,7 +258,7 @@ public class NotificationMediaManager implements Dumpable {
final String pkg = aController.getPackageName();
for (int i = 0; i < N; i++) {
- final Entry entry = activeNotifications.get(i);
+ final NotificationEntry entry = activeNotifications.get(i);
if (entry.notification.getPackageName().equals(pkg)) {
if (DEBUG_MEDIA) {
Log.v(TAG, "DEBUG_MEDIA: found controller matching "
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
index 886d99eeff17..7d6231f27885 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
@@ -51,9 +51,9 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.statusbar.NotificationVisibility;
import com.android.systemui.Dumpable;
-import com.android.systemui.statusbar.notification.NotificationData;
import com.android.systemui.statusbar.notification.NotificationEntryListener;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.phone.ShadeController;
import com.android.systemui.statusbar.policy.RemoteInputView;
@@ -103,7 +103,7 @@ public class NotificationRemoteInputManager implements Dumpable {
* Notifications that are already removed but are kept around because the remote input is
* actively being used (i.e. user is typing in it). See {@link RemoteInputActiveExtender}.
*/
- protected final ArraySet<NotificationData.Entry> mEntriesKeptForRemoteInputActive =
+ protected final ArraySet<NotificationEntry> mEntriesKeptForRemoteInputActive =
new ArraySet<>();
// Dependencies:
@@ -253,14 +253,11 @@ public class NotificationRemoteInputManager implements Dumpable {
notificationEntryManager.addNotificationEntryListener(new NotificationEntryListener() {
@Override
public void onEntryRemoved(
- @Nullable NotificationData.Entry entry,
- String key,
- StatusBarNotification old,
+ @Nullable NotificationEntry entry,
NotificationVisibility visibility,
- boolean lifetimeExtended,
boolean removedByUser) {
if (removedByUser && entry != null) {
- onPerformRemoveNotification(entry, key);
+ onPerformRemoveNotification(entry, entry.key);
}
}
});
@@ -272,7 +269,7 @@ public class NotificationRemoteInputManager implements Dumpable {
mRemoteInputController = new RemoteInputController(delegate);
mRemoteInputController.addCallback(new RemoteInputController.Callback() {
@Override
- public void onRemoteInputSent(NotificationData.Entry entry) {
+ public void onRemoteInputSent(NotificationEntry entry) {
if (FORCE_REMOTE_INPUT_HISTORY
&& isNotificationKeptForRemoteInputHistory(entry.key)) {
mNotificationLifetimeFinishedCallback.onSafeToRemove(entry.key);
@@ -416,7 +413,7 @@ public class NotificationRemoteInputManager implements Dumpable {
}
@VisibleForTesting
- void onPerformRemoveNotification(NotificationData.Entry entry, final String key) {
+ void onPerformRemoveNotification(NotificationEntry entry, final String key) {
if (mKeysKeptForRemoteInputHistory.contains(key)) {
mKeysKeptForRemoteInputHistory.remove(key);
}
@@ -427,7 +424,7 @@ public class NotificationRemoteInputManager implements Dumpable {
public void onPanelCollapsed() {
for (int i = 0; i < mEntriesKeptForRemoteInputActive.size(); i++) {
- NotificationData.Entry entry = mEntriesKeptForRemoteInputActive.valueAt(i);
+ NotificationEntry entry = mEntriesKeptForRemoteInputActive.valueAt(i);
mRemoteInputController.removeRemoteInput(entry, null);
if (mNotificationLifetimeFinishedCallback != null) {
mNotificationLifetimeFinishedCallback.onSafeToRemove(entry.key);
@@ -440,7 +437,7 @@ public class NotificationRemoteInputManager implements Dumpable {
return mKeysKeptForRemoteInputHistory.contains(key);
}
- public boolean shouldKeepForRemoteInputHistory(NotificationData.Entry entry) {
+ public boolean shouldKeepForRemoteInputHistory(NotificationEntry entry) {
if (entry.isDismissed()) {
return false;
}
@@ -450,7 +447,7 @@ public class NotificationRemoteInputManager implements Dumpable {
return (mRemoteInputController.isSpinning(entry.key) || entry.hasJustSentRemoteInput());
}
- public boolean shouldKeepForSmartReplyHistory(NotificationData.Entry entry) {
+ public boolean shouldKeepForSmartReplyHistory(NotificationEntry entry) {
if (entry.isDismissed()) {
return false;
}
@@ -470,13 +467,13 @@ public class NotificationRemoteInputManager implements Dumpable {
@VisibleForTesting
StatusBarNotification rebuildNotificationForCanceledSmartReplies(
- NotificationData.Entry entry) {
+ NotificationEntry entry) {
return rebuildNotificationWithRemoteInput(entry, null /* remoteInputTest */,
false /* showSpinner */);
}
@VisibleForTesting
- StatusBarNotification rebuildNotificationWithRemoteInput(NotificationData.Entry entry,
+ StatusBarNotification rebuildNotificationWithRemoteInput(NotificationEntry entry,
CharSequence remoteInputText, boolean showSpinner) {
StatusBarNotification sbn = entry.notification;
@@ -533,7 +530,7 @@ public class NotificationRemoteInputManager implements Dumpable {
}
@VisibleForTesting
- public Set<NotificationData.Entry> getEntriesKeptForRemoteInputActive() {
+ public Set<NotificationEntry> getEntriesKeptForRemoteInputActive() {
return mEntriesKeptForRemoteInputActive;
}
@@ -556,12 +553,12 @@ public class NotificationRemoteInputManager implements Dumpable {
*/
protected class RemoteInputHistoryExtender extends RemoteInputExtender {
@Override
- public boolean shouldExtendLifetime(@NonNull NotificationData.Entry entry) {
+ public boolean shouldExtendLifetime(@NonNull NotificationEntry entry) {
return shouldKeepForRemoteInputHistory(entry);
}
@Override
- public void setShouldManageLifetime(NotificationData.Entry entry,
+ public void setShouldManageLifetime(NotificationEntry entry,
boolean shouldExtend) {
if (shouldExtend) {
CharSequence remoteInputText = entry.remoteInputText;
@@ -602,12 +599,12 @@ public class NotificationRemoteInputManager implements Dumpable {
*/
protected class SmartReplyHistoryExtender extends RemoteInputExtender {
@Override
- public boolean shouldExtendLifetime(@NonNull NotificationData.Entry entry) {
+ public boolean shouldExtendLifetime(@NonNull NotificationEntry entry) {
return shouldKeepForSmartReplyHistory(entry);
}
@Override
- public void setShouldManageLifetime(NotificationData.Entry entry,
+ public void setShouldManageLifetime(NotificationEntry entry,
boolean shouldExtend) {
if (shouldExtend) {
StatusBarNotification newSbn = rebuildNotificationForCanceledSmartReplies(entry);
@@ -640,7 +637,7 @@ public class NotificationRemoteInputManager implements Dumpable {
*/
protected class RemoteInputActiveExtender extends RemoteInputExtender {
@Override
- public boolean shouldExtendLifetime(@NonNull NotificationData.Entry entry) {
+ public boolean shouldExtendLifetime(@NonNull NotificationEntry entry) {
if (entry.isDismissed()) {
return false;
}
@@ -648,7 +645,7 @@ public class NotificationRemoteInputManager implements Dumpable {
}
@Override
- public void setShouldManageLifetime(NotificationData.Entry entry,
+ public void setShouldManageLifetime(NotificationEntry entry,
boolean shouldExtend) {
if (shouldExtend) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
index 91b34fc875bf..9ef9c94d7cdc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
@@ -55,7 +55,7 @@ import com.android.systemui.statusbar.phone.NotificationIconContainer;
* overflow icons that don't fit into the regular list anymore.
*/
public class NotificationShelf extends ActivatableNotificationView implements
- View.OnLayoutChangeListener {
+ View.OnLayoutChangeListener, StateListener {
private static final boolean USE_ANIMATIONS_WHEN_OPENING =
SystemProperties.getBoolean("debug.icon_opening_animations", true);
@@ -65,12 +65,12 @@ public class NotificationShelf extends ActivatableNotificationView implements
private static final String TAG = "NotificationShelf";
private static final long SHELF_IN_TRANSLATION_DURATION = 200;
- private boolean mDark;
private NotificationIconContainer mShelfIcons;
private int[] mTmp = new int[2];
private boolean mHideBackground;
private int mIconAppearTopPadding;
private int mShelfAppearTranslation;
+ private float mDarkShelfPadding;
private int mStatusBarHeight;
private int mStatusBarPaddingStart;
private AmbientState mAmbientState;
@@ -95,8 +95,6 @@ public class NotificationShelf extends ActivatableNotificationView implements
private int mCutoutHeight;
private int mGapHeight;
- private final StateListener mStateListener = this::setStatusBarState;
-
public NotificationShelf(Context context, AttributeSet attrs) {
super(context, attrs);
}
@@ -121,13 +119,13 @@ public class NotificationShelf extends ActivatableNotificationView implements
protected void onAttachedToWindow() {
super.onAttachedToWindow();
Dependency.get(StatusBarStateController.class)
- .addCallback(mStateListener, StatusBarStateController.RANK_SHELF);
+ .addCallback(this, StatusBarStateController.RANK_SHELF);
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
- Dependency.get(StatusBarStateController.class).removeCallback(mStateListener);
+ Dependency.get(StatusBarStateController.class).removeCallback(this);
}
public void bind(AmbientState ambientState, NotificationStackScrollLayout hostLayout) {
@@ -142,6 +140,7 @@ public class NotificationShelf extends ActivatableNotificationView implements
mStatusBarPaddingStart = res.getDimensionPixelOffset(R.dimen.status_bar_padding_start);
mPaddingBetweenElements = res.getDimensionPixelSize(R.dimen.notification_divider_height);
mShelfAppearTranslation = res.getDimensionPixelSize(R.dimen.shelf_appear_translation);
+ mDarkShelfPadding = res.getDimensionPixelSize(R.dimen.widget_bottom_separator_padding);
ViewGroup.LayoutParams layoutParams = getLayoutParams();
layoutParams.height = res.getDimensionPixelOffset(R.dimen.notification_shelf_height);
@@ -167,11 +166,29 @@ public class NotificationShelf extends ActivatableNotificationView implements
@Override
public void setDark(boolean dark, boolean fade, long delay) {
- super.setDark(dark, fade, delay);
if (mDark == dark) return;
- mDark = dark;
+ super.setDark(dark, fade, delay);
mShelfIcons.setDark(dark, fade, delay);
updateInteractiveness();
+ updateOutline();
+ }
+
+ /**
+ * Alpha animation with translation played when this view is visible on AOD.
+ */
+ public void fadeInTranslating() {
+ mShelfIcons.setTranslationY(-mShelfAppearTranslation);
+ mShelfIcons.setAlpha(0);
+ mShelfIcons.animate()
+ .setInterpolator(Interpolators.DECELERATE_QUINT)
+ .translationY(0)
+ .setDuration(SHELF_IN_TRANSLATION_DURATION)
+ .start();
+ mShelfIcons.animate()
+ .alpha(1)
+ .setInterpolator(Interpolators.LINEAR)
+ .setDuration(SHELF_IN_TRANSLATION_DURATION)
+ .start();
}
@Override
@@ -202,10 +219,9 @@ public class NotificationShelf extends ActivatableNotificationView implements
float awakenTranslation = Math.max(Math.min(viewEnd, maxShelfEnd) - viewState.height,
getFullyClosedTranslation());
- float darkTranslation = mAmbientState.getDarkTopPadding();
float yRatio = mAmbientState.hasPulsingNotifications() ?
0 : mAmbientState.getDarkAmount();
- viewState.yTranslation = MathUtils.lerp(awakenTranslation, darkTranslation, yRatio);
+ viewState.yTranslation = awakenTranslation + mDarkShelfPadding * yRatio;
viewState.zTranslation = ambientState.getBaseZHeight();
// For the small display size, it's not enough to make the icon not covered by
// the top cutout so the denominator add the height of cutout.
@@ -225,7 +241,7 @@ public class NotificationShelf extends ActivatableNotificationView implements
}
viewState.hasItemsInStableShelf = lastViewState.inShelf;
viewState.hidden = !mAmbientState.isShadeExpanded()
- || mAmbientState.isQsCustomizerShowing() || mAmbientState.isFullyDark();
+ || mAmbientState.isQsCustomizerShowing();
viewState.maxShelfEnd = maxShelfEnd;
} else {
viewState.hidden = true;
@@ -420,7 +436,7 @@ public class NotificationShelf extends ActivatableNotificationView implements
float maxTop = row.getTranslationY();
StatusBarIconView icon = row.getEntry().expandedIcon;
float shelfIconPosition = getTranslationY() + icon.getTop() + icon.getTranslationY();
- if (shelfIconPosition < maxTop) {
+ if (shelfIconPosition < maxTop && !mAmbientState.isDark()) {
int top = (int) (maxTop - shelfIconPosition);
Rect clipRect = new Rect(0, top, icon.getWidth(), Math.max(top, icon.getHeight()));
icon.setClipBounds(clipRect);
@@ -431,7 +447,7 @@ public class NotificationShelf extends ActivatableNotificationView implements
private void updateContinuousClipping(final ExpandableNotificationRow row) {
StatusBarIconView icon = row.getEntry().expandedIcon;
- boolean needsContinuousClipping = ViewState.isAnimatingY(icon);
+ boolean needsContinuousClipping = ViewState.isAnimatingY(icon) && !mAmbientState.isDark();
boolean isContinuousClipping = icon.getTag(TAG_CONTINUOUS_CLIPPING) != null;
if (needsContinuousClipping && !isContinuousClipping) {
final ViewTreeObserver observer = icon.getViewTreeObserver();
@@ -622,7 +638,9 @@ public class NotificationShelf extends ActivatableNotificationView implements
iconState.translateContent = false;
}
float transitionAmount;
- if (isLastChild || !USE_ANIMATIONS_WHEN_OPENING || iconState.useFullTransitionAmount
+ if (mAmbientState.isDarkAtAll() && !row.isInShelf()) {
+ transitionAmount = mAmbientState.isFullyDark() ? 1 : 0;
+ } else if (isLastChild || !USE_ANIMATIONS_WHEN_OPENING || iconState.useFullTransitionAmount
|| iconState.useLinearTransitionAmount) {
transitionAmount = iconTransitionAmount;
} else {
@@ -745,18 +763,14 @@ public class NotificationShelf extends ActivatableNotificationView implements
}
}
- public boolean hidesBackground() {
- return mHideBackground;
- }
-
@Override
protected boolean needsOutline() {
- return !mHideBackground && super.needsOutline();
+ return !mHideBackground && !mDark && super.needsOutline();
}
@Override
protected boolean shouldHideBackground() {
- return super.shouldHideBackground() || mHideBackground;
+ return super.shouldHideBackground() || mHideBackground || mDark;
}
@Override
@@ -860,8 +874,9 @@ public class NotificationShelf extends ActivatableNotificationView implements
mCollapsedIcons.addOnLayoutChangeListener(this);
}
- private void setStatusBarState(int statusBarState) {
- mStatusBarState = statusBarState;
+ @Override
+ public void onStateChanged(int newState) {
+ mStatusBarState = newState;
updateInteractiveness();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationUiAdjustment.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationUiAdjustment.java
index f23ae3f6bfb7..f0d804dbc7a3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationUiAdjustment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationUiAdjustment.java
@@ -24,7 +24,7 @@ import android.text.TextUtils;
import androidx.annotation.VisibleForTesting;
-import com.android.systemui.statusbar.notification.NotificationData;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import java.util.ArrayList;
import java.util.Arrays;
@@ -52,7 +52,7 @@ public class NotificationUiAdjustment {
}
public static NotificationUiAdjustment extractFromNotificationEntry(
- NotificationData.Entry entry) {
+ NotificationEntry entry) {
return new NotificationUiAdjustment(
entry.key, entry.systemGeneratedSmartActions, entry.smartReplies);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
index 017a9c368fdf..f2ff85bb226c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
@@ -16,8 +16,6 @@
package com.android.systemui.statusbar;
-import static com.android.systemui.statusbar.StatusBarState.SHADE;
-
import android.content.Context;
import android.content.res.Resources;
import android.os.Trace;
@@ -26,10 +24,9 @@ import android.view.View;
import android.view.ViewGroup;
import com.android.systemui.R;
-import com.android.systemui.bubbles.BubbleController;
-import com.android.systemui.statusbar.notification.NotificationData;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.VisualStabilityManager;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
import com.android.systemui.statusbar.phone.NotificationGroupManager;
@@ -66,7 +63,6 @@ public class NotificationViewHierarchyManager {
protected final VisualStabilityManager mVisualStabilityManager;
private final StatusBarStateController mStatusBarStateController;
private final NotificationEntryManager mEntryManager;
- private final BubbleController mBubbleController;
// Lazy
private final Lazy<ShadeController> mShadeController;
@@ -80,41 +76,6 @@ public class NotificationViewHierarchyManager {
private NotificationPresenter mPresenter;
private NotificationListContainer mListContainer;
- private StatusBarStateListener mStatusBarStateListener;
-
- /**
- * Listens for the current state of the status bar and updates the visibility state
- * of bubbles as needed.
- */
- public class StatusBarStateListener implements StatusBarStateController.StateListener {
- private int mState;
- private BubbleController mController;
-
- public StatusBarStateListener(BubbleController controller) {
- mController = controller;
- }
-
- /**
- * Returns the current status bar state.
- */
- public int getCurrentState() {
- return mState;
- }
-
- @Override
- public void onStateChanged(int newState) {
- mState = newState;
- // Order here matters because we need to remove the expandable notification row
- // from it's current parent (NSSL or bubble) before it can be added to the new parent
- if (mState == SHADE) {
- updateNotificationViews();
- mController.updateVisibility(true);
- } else {
- mController.updateVisibility(false);
- updateNotificationViews();
- }
- }
- }
@Inject
public NotificationViewHierarchyManager(Context context,
@@ -123,20 +84,16 @@ public class NotificationViewHierarchyManager {
VisualStabilityManager visualStabilityManager,
StatusBarStateController statusBarStateController,
NotificationEntryManager notificationEntryManager,
- BubbleController bubbleController,
Lazy<ShadeController> shadeController) {
mLockscreenUserManager = notificationLockscreenUserManager;
mGroupManager = groupManager;
mVisualStabilityManager = visualStabilityManager;
mStatusBarStateController = statusBarStateController;
mEntryManager = notificationEntryManager;
- mBubbleController = bubbleController;
mShadeController = shadeController;
Resources res = context.getResources();
mAlwaysExpandNonGroupedNotification =
res.getBoolean(R.bool.config_alwaysExpandNonGroupedNotifications);
- mStatusBarStateListener = new StatusBarStateListener(mBubbleController);
- mStatusBarStateController.addCallback(mStatusBarStateListener);
}
public void setUpWithPresenter(NotificationPresenter presenter,
@@ -150,25 +107,17 @@ public class NotificationViewHierarchyManager {
*/
//TODO: Rewrite this to focus on Entries, or some other data object instead of views
public void updateNotificationViews() {
- ArrayList<NotificationData.Entry> activeNotifications = mEntryManager.getNotificationData()
+ ArrayList<NotificationEntry> activeNotifications = mEntryManager.getNotificationData()
.getActiveNotifications();
ArrayList<ExpandableNotificationRow> toShow = new ArrayList<>(activeNotifications.size());
- ArrayList<NotificationData.Entry> toBubble = new ArrayList<>();
final int N = activeNotifications.size();
for (int i = 0; i < N; i++) {
- NotificationData.Entry ent = activeNotifications.get(i);
+ NotificationEntry ent = activeNotifications.get(i);
if (ent.isRowDismissed() || ent.isRowRemoved()) {
// we don't want to update removed notifications because they could
// temporarily become children if they were isolated before.
continue;
}
- ent.getRow().setStatusBarState(mStatusBarStateListener.getCurrentState());
- boolean showAsBubble = ent.isBubble() && !ent.isBubbleDismissed()
- && mStatusBarStateListener.getCurrentState() == SHADE;
- if (showAsBubble) {
- toBubble.add(ent);
- continue;
- }
int userId = ent.notification.getUserId();
@@ -187,7 +136,7 @@ public class NotificationViewHierarchyManager {
ent.getRow().setSensitive(sensitive, deviceSensitive);
ent.getRow().setNeedsRedaction(needsRedaction);
if (mGroupManager.isChildInGroupWithSummary(ent.notification)) {
- NotificationData.Entry summary = mGroupManager.getGroupSummary(ent.notification);
+ NotificationEntry summary = mGroupManager.getGroupSummary(ent.notification);
List<ExpandableNotificationRow> orderedChildren =
mTmpChildOrderMap.get(summary.getRow());
if (orderedChildren == null) {
@@ -269,12 +218,6 @@ public class NotificationViewHierarchyManager {
}
- for (int i = 0; i < toBubble.size(); i++) {
- // TODO: might make sense to leave them in the shade and just reposition them
- NotificationData.Entry ent = toBubble.get(i);
- mBubbleController.addBubble(ent);
- }
-
mVisualStabilityManager.onReorderingFinished();
// clear the map again for the next usage
mTmpChildOrderMap.clear();
@@ -385,7 +328,7 @@ public class NotificationViewHierarchyManager {
}
while(!stack.isEmpty()) {
ExpandableNotificationRow row = stack.pop();
- NotificationData.Entry entry = row.getEntry();
+ NotificationEntry entry = row.getEntry();
boolean isChildNotification =
mGroupManager.isChildInGroupWithSummary(entry.notification);
@@ -408,7 +351,7 @@ public class NotificationViewHierarchyManager {
if (!showOnKeyguard) {
// min priority notifications should show if their summary is showing
if (mGroupManager.isChildInGroupWithSummary(entry.notification)) {
- NotificationData.Entry summary = mGroupManager.getLogicalGroupSummary(
+ NotificationEntry summary = mGroupManager.getLogicalGroupSummary(
entry.notification);
if (summary != null && mLockscreenUserManager.shouldShowOnKeyguard(
summary.notification)) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java b/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java
index e8abcc2bd80e..998cf523d3df 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java
@@ -24,7 +24,7 @@ import android.util.ArrayMap;
import android.util.Pair;
import com.android.internal.util.Preconditions;
-import com.android.systemui.statusbar.notification.NotificationData;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.policy.RemoteInputView;
import java.lang.ref.WeakReference;
@@ -38,7 +38,7 @@ public class RemoteInputController {
private static final boolean ENABLE_REMOTE_INPUT =
SystemProperties.getBoolean("debug.enable_remote_input", true);
- private final ArrayList<Pair<WeakReference<NotificationData.Entry>, Object>> mOpen
+ private final ArrayList<Pair<WeakReference<NotificationEntry>, Object>> mOpen
= new ArrayList<>();
private final ArrayMap<String, Object> mSpinning = new ArrayMap<>();
private final ArrayList<Callback> mCallbacks = new ArrayList<>(3);
@@ -101,7 +101,7 @@ public class RemoteInputController {
* @param entry the entry for which a remote input is now active.
* @param token a token identifying the view that is managing the remote input
*/
- public void addRemoteInput(NotificationData.Entry entry, Object token) {
+ public void addRemoteInput(NotificationEntry entry, Object token) {
Preconditions.checkNotNull(entry);
Preconditions.checkNotNull(token);
@@ -122,7 +122,7 @@ public class RemoteInputController {
* the entry is only removed if the token matches the last added token for this
* entry. If null, the entry is removed regardless.
*/
- public void removeRemoteInput(NotificationData.Entry entry, Object token) {
+ public void removeRemoteInput(NotificationEntry entry, Object token) {
Preconditions.checkNotNull(entry);
pruneWeakThenRemoveAndContains(null /* contains */, entry /* remove */, token);
@@ -173,7 +173,7 @@ public class RemoteInputController {
return mSpinning.get(key) == token;
}
- private void apply(NotificationData.Entry entry) {
+ private void apply(NotificationEntry entry) {
mDelegate.setRemoteInputActive(entry, isRemoteInputActive(entry));
boolean remoteInputActive = isRemoteInputActive();
int N = mCallbacks.size();
@@ -185,7 +185,7 @@ public class RemoteInputController {
/**
* @return true if {@param entry} has an active RemoteInput
*/
- public boolean isRemoteInputActive(NotificationData.Entry entry) {
+ public boolean isRemoteInputActive(NotificationEntry entry) {
return pruneWeakThenRemoveAndContains(entry /* contains */, null /* remove */,
null /* removeToken */);
}
@@ -208,10 +208,10 @@ public class RemoteInputController {
* @return true if {@param contains} is in the set of active remote inputs
*/
private boolean pruneWeakThenRemoveAndContains(
- NotificationData.Entry contains, NotificationData.Entry remove, Object removeToken) {
+ NotificationEntry contains, NotificationEntry remove, Object removeToken) {
boolean found = false;
for (int i = mOpen.size() - 1; i >= 0; i--) {
- NotificationData.Entry item = mOpen.get(i).first.get();
+ NotificationEntry item = mOpen.get(i).first.get();
Object itemToken = mOpen.get(i).second;
boolean removeTokenMatches = (removeToken == null || itemToken == removeToken);
@@ -235,7 +235,7 @@ public class RemoteInputController {
mCallbacks.add(callback);
}
- public void remoteInputSent(NotificationData.Entry entry) {
+ public void remoteInputSent(NotificationEntry entry) {
int N = mCallbacks.size();
for (int i = 0; i < N; i++) {
mCallbacks.get(i).onRemoteInputSent(entry);
@@ -248,16 +248,16 @@ public class RemoteInputController {
}
// Make a copy because closing the remote inputs will modify mOpen.
- ArrayList<NotificationData.Entry> list = new ArrayList<>(mOpen.size());
+ ArrayList<NotificationEntry> list = new ArrayList<>(mOpen.size());
for (int i = mOpen.size() - 1; i >= 0; i--) {
- NotificationData.Entry entry = mOpen.get(i).first.get();
+ NotificationEntry entry = mOpen.get(i).first.get();
if (entry != null && entry.rowExists()) {
list.add(entry);
}
}
for (int i = list.size() - 1; i >= 0; i--) {
- NotificationData.Entry entry = list.get(i);
+ NotificationEntry entry = list.get(i);
if (entry.rowExists()) {
entry.closeRemoteInput();
}
@@ -268,31 +268,31 @@ public class RemoteInputController {
mDelegate.requestDisallowLongPressAndDismiss();
}
- public void lockScrollTo(NotificationData.Entry entry) {
+ public void lockScrollTo(NotificationEntry entry) {
mDelegate.lockScrollTo(entry);
}
public interface Callback {
default void onRemoteInputActive(boolean active) {}
- default void onRemoteInputSent(NotificationData.Entry entry) {}
+ default void onRemoteInputSent(NotificationEntry entry) {}
}
public interface Delegate {
/**
* Activate remote input if necessary.
*/
- void setRemoteInputActive(NotificationData.Entry entry, boolean remoteInputActive);
-
- /**
- * Request that the view does not dismiss nor perform long press for the current touch.
- */
- void requestDisallowLongPressAndDismiss();
-
- /**
- * Request that the view is made visible by scrolling to it, and keep the scroll locked until
- * the user scrolls, or {@param v} loses focus or is detached.
- */
- void lockScrollTo(NotificationData.Entry entry);
+ void setRemoteInputActive(NotificationEntry entry, boolean remoteInputActive);
+
+ /**
+ * Request that the view does not dismiss nor perform long press for the current touch.
+ */
+ void requestDisallowLongPressAndDismiss();
+
+ /**
+ * Request that the view is made visible by scrolling to it, and keep the scroll locked until
+ * the user scrolls, or {@param entry} loses focus or is detached.
+ */
+ void lockScrollTo(NotificationEntry entry);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java
deleted file mode 100644
index a846059a7cce..000000000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java
+++ /dev/null
@@ -1,694 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar;
-
-import static android.app.StatusBarManager.DISABLE2_SYSTEM_ICONS;
-import static android.app.StatusBarManager.DISABLE_NONE;
-
-import android.annotation.DrawableRes;
-import android.content.Context;
-import android.content.res.ColorStateList;
-import android.content.res.Resources;
-import android.graphics.Color;
-import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
-import android.telephony.SubscriptionInfo;
-import android.util.ArraySet;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.util.TypedValue;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.accessibility.AccessibilityEvent;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
-
-import com.android.settingslib.graph.SignalDrawable;
-import com.android.systemui.Dependency;
-import com.android.systemui.R;
-import com.android.systemui.plugins.DarkIconDispatcher;
-import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
-import com.android.systemui.statusbar.phone.StatusBarIconController;
-import com.android.systemui.statusbar.policy.IconLogger;
-import com.android.systemui.statusbar.policy.NetworkController;
-import com.android.systemui.statusbar.policy.NetworkController.IconState;
-import com.android.systemui.statusbar.policy.NetworkControllerImpl;
-import com.android.systemui.statusbar.policy.SecurityController;
-import com.android.systemui.tuner.TunerService;
-import com.android.systemui.tuner.TunerService.Tunable;
-import com.android.systemui.util.Utils.DisableStateTracker;
-
-import java.util.ArrayList;
-import java.util.List;
-
-// Intimately tied to the design of res/layout/signal_cluster_view.xml
-public class SignalClusterView extends LinearLayout implements NetworkControllerImpl.SignalCallback,
- SecurityController.SecurityControllerCallback, Tunable, DarkReceiver {
-
- static final String TAG = "SignalClusterView";
- static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
-
- private static final String SLOT_AIRPLANE = "airplane";
- private static final String SLOT_MOBILE = "mobile";
- private static final String SLOT_WIFI = "wifi";
- private static final String SLOT_ETHERNET = "ethernet";
- private static final String SLOT_VPN = "vpn";
-
- private final NetworkController mNetworkController;
- private final SecurityController mSecurityController;
-
- private boolean mVpnVisible = false;
- private int mVpnIconId = 0;
- private int mLastVpnIconId = -1;
- private boolean mEthernetVisible = false;
- private int mEthernetIconId = 0;
- private int mLastEthernetIconId = -1;
- private boolean mWifiVisible = false;
- private int mWifiStrengthId = 0;
- private int mLastWifiStrengthId = -1;
- private boolean mWifiIn;
- private boolean mWifiOut;
- private boolean mIsAirplaneMode = false;
- private int mAirplaneIconId = 0;
- private int mLastAirplaneIconId = -1;
- private String mAirplaneContentDescription;
- private String mWifiDescription;
- private String mEthernetDescription;
- private ArrayList<PhoneState> mPhoneStates = new ArrayList<PhoneState>();
- private int mIconTint = Color.WHITE;
- private float mDarkIntensity;
- private final Rect mTintArea = new Rect();
- private int mNoSimsIcon;
-
- ViewGroup mEthernetGroup, mWifiGroup;
- ImageView mVpn, mEthernet, mWifi, mAirplane, mEthernetDark, mWifiDark;
- ImageView mWifiActivityIn;
- ImageView mWifiActivityOut;
- View mWifiAirplaneSpacer;
- View mWifiSignalSpacer;
- LinearLayout mMobileSignalGroup;
-
- private final int mMobileSignalGroupEndPadding;
- private final int mMobileDataIconStartPadding;
- private final int mSecondaryTelephonyPadding;
- private final int mEndPadding;
- private final int mEndPaddingNothingVisible;
- private final float mIconScaleFactor;
-
- private boolean mBlockAirplane;
- private boolean mBlockMobile;
- private boolean mBlockWifi;
- private boolean mBlockEthernet;
- private boolean mActivityEnabled;
- private boolean mForceBlockWifi;
-
- private final IconLogger mIconLogger = Dependency.get(IconLogger.class);
-
- public SignalClusterView(Context context) {
- this(context, null);
- }
-
- public SignalClusterView(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public SignalClusterView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
-
- Resources res = getResources();
- mMobileSignalGroupEndPadding =
- res.getDimensionPixelSize(R.dimen.mobile_signal_group_end_padding);
- mMobileDataIconStartPadding =
- res.getDimensionPixelSize(R.dimen.mobile_data_icon_start_padding);
- mSecondaryTelephonyPadding = res.getDimensionPixelSize(R.dimen.secondary_telephony_padding);
- mEndPadding = res.getDimensionPixelSize(R.dimen.signal_cluster_battery_padding);
- mEndPaddingNothingVisible = res.getDimensionPixelSize(
- R.dimen.no_signal_cluster_battery_padding);
-
- TypedValue typedValue = new TypedValue();
- res.getValue(R.dimen.status_bar_icon_scale_factor, typedValue, true);
- mIconScaleFactor = typedValue.getFloat();
- mNetworkController = Dependency.get(NetworkController.class);
- mSecurityController = Dependency.get(SecurityController.class);
- addOnAttachStateChangeListener(
- new DisableStateTracker(DISABLE_NONE, DISABLE2_SYSTEM_ICONS));
- updateActivityEnabled();
- }
-
- public void setForceBlockWifi() {
- mForceBlockWifi = true;
- mBlockWifi = true;
- if (isAttachedToWindow()) {
- // Re-register to get new callbacks.
- mNetworkController.removeCallback(this);
- mNetworkController.addCallback(this);
- }
- }
-
- @Override
- public void onTuningChanged(String key, String newValue) {
- if (!StatusBarIconController.ICON_BLACKLIST.equals(key)) {
- return;
- }
- ArraySet<String> blockList = StatusBarIconController.getIconBlacklist(newValue);
- boolean blockAirplane = blockList.contains(SLOT_AIRPLANE);
- boolean blockMobile = blockList.contains(SLOT_MOBILE);
- boolean blockWifi = blockList.contains(SLOT_WIFI);
- boolean blockEthernet = blockList.contains(SLOT_ETHERNET);
-
- if (blockAirplane != mBlockAirplane || blockMobile != mBlockMobile
- || blockEthernet != mBlockEthernet || blockWifi != mBlockWifi) {
- mBlockAirplane = blockAirplane;
- mBlockMobile = blockMobile;
- mBlockEthernet = blockEthernet;
- mBlockWifi = blockWifi || mForceBlockWifi;
- // Re-register to get new callbacks.
- mNetworkController.removeCallback(this);
- mNetworkController.addCallback(this);
- }
- }
-
- @Override
- protected void onFinishInflate() {
- super.onFinishInflate();
-
- mVpn = findViewById(R.id.vpn);
- mEthernetGroup = findViewById(R.id.ethernet_combo);
- mEthernet = findViewById(R.id.ethernet);
- mEthernetDark = findViewById(R.id.ethernet_dark);
- mWifiGroup = findViewById(R.id.wifi_combo);
- mWifi = findViewById(R.id.wifi_signal);
- mWifiDark = findViewById(R.id.wifi_signal_dark);
- mWifiActivityIn = findViewById(R.id.wifi_in);
- mWifiActivityOut= findViewById(R.id.wifi_out);
- mAirplane = findViewById(R.id.airplane);
- mWifiAirplaneSpacer = findViewById(R.id.wifi_airplane_spacer);
- mWifiSignalSpacer = findViewById(R.id.wifi_signal_spacer);
- mMobileSignalGroup = findViewById(R.id.mobile_signal_group);
-
- maybeScaleVpnAndNoSimsIcons();
- }
-
- /**
- * Extracts the icon off of the VPN and no sims views and maybe scale them by
- * {@link #mIconScaleFactor}. Note that the other icons are not scaled here because they are
- * dynamic. As such, they need to be scaled each time the icon changes in {@link #apply()}.
- */
- private void maybeScaleVpnAndNoSimsIcons() {
- if (mIconScaleFactor == 1.f) {
- return;
- }
-
- mVpn.setImageDrawable(new ScalingDrawableWrapper(mVpn.getDrawable(), mIconScaleFactor));
- }
-
- @Override
- protected void onAttachedToWindow() {
- super.onAttachedToWindow();
- mVpnVisible = mSecurityController.isVpnEnabled();
- mVpnIconId = currentVpnIconId(mSecurityController.isVpnBranded());
-
- for (PhoneState state : mPhoneStates) {
- if (state.mMobileGroup.getParent() == null) {
- mMobileSignalGroup.addView(state.mMobileGroup);
- }
- }
-
- int endPadding = mMobileSignalGroup.getChildCount() > 0 ? mMobileSignalGroupEndPadding : 0;
- mMobileSignalGroup.setPaddingRelative(0, 0, endPadding, 0);
-
- Dependency.get(TunerService.class).addTunable(this, StatusBarIconController.ICON_BLACKLIST);
-
- apply();
- applyIconTint();
- mNetworkController.addCallback(this);
- mSecurityController.addCallback(this);
- }
-
- @Override
- protected void onDetachedFromWindow() {
- mMobileSignalGroup.removeAllViews();
- Dependency.get(TunerService.class).removeTunable(this);
- mSecurityController.removeCallback(this);
- mNetworkController.removeCallback(this);
-
- super.onDetachedFromWindow();
- }
-
- @Override
- protected void onLayout(boolean changed, int l, int t, int r, int b) {
- super.onLayout(changed, l, t, r, b);
-
- // Re-run all checks against the tint area for all icons
- applyIconTint();
- }
-
- // From SecurityController.
- @Override
- public void onStateChanged() {
- post(new Runnable() {
- @Override
- public void run() {
- mVpnVisible = mSecurityController.isVpnEnabled();
- mVpnIconId = currentVpnIconId(mSecurityController.isVpnBranded());
- apply();
- }
- });
- }
-
- private void updateActivityEnabled() {
- mActivityEnabled = mContext.getResources().getBoolean(R.bool.config_showActivity);
- }
-
- @Override
- public void setWifiIndicators(boolean enabled, IconState statusIcon, IconState qsIcon,
- boolean activityIn, boolean activityOut, String description, boolean isTransient,
- String secondaryLabel) {
- mWifiVisible = statusIcon.visible && !mBlockWifi;
- mWifiStrengthId = statusIcon.icon;
- mWifiDescription = statusIcon.contentDescription;
- mWifiIn = activityIn && mActivityEnabled && mWifiVisible;
- mWifiOut = activityOut && mActivityEnabled && mWifiVisible;
-
- apply();
- }
-
- @Override
- public void setMobileDataIndicators(IconState statusIcon, IconState qsIcon, int statusType,
- int qsType, boolean activityIn, boolean activityOut, int volteIcon,
- String typeContentDescription, String description, boolean isWide,
- int subId, boolean roaming) {
- PhoneState state = getState(subId);
- if (state == null) {
- return;
- }
- state.mMobileVisible = statusIcon.visible && !mBlockMobile;
- state.mMobileStrengthId = statusIcon.icon;
- state.mMobileTypeId = statusType;
- state.mMobileDescription = statusIcon.contentDescription;
- state.mMobileTypeDescription = typeContentDescription;
- state.mRoaming = roaming;
- state.mActivityIn = activityIn && mActivityEnabled;
- state.mActivityOut = activityOut && mActivityEnabled;
-
- apply();
- }
-
- @Override
- public void setEthernetIndicators(IconState state) {
- mEthernetVisible = state.visible && !mBlockEthernet;
- mEthernetIconId = state.icon;
- mEthernetDescription = state.contentDescription;
-
- apply();
- }
-
- @Override
- public void setNoSims(boolean show, boolean simDetected) {
- // Noop. Status bar no longer shows no sim icon.
- }
-
- @Override
- public void setSubs(List<SubscriptionInfo> subs) {
- if (hasCorrectSubs(subs)) {
- return;
- }
- mPhoneStates.clear();
- if (mMobileSignalGroup != null) {
- mMobileSignalGroup.removeAllViews();
- }
- final int n = subs.size();
- for (int i = 0; i < n; i++) {
- inflatePhoneState(subs.get(i).getSubscriptionId());
- }
- if (isAttachedToWindow()) {
- applyIconTint();
- }
- }
-
- private boolean hasCorrectSubs(List<SubscriptionInfo> subs) {
- final int N = subs.size();
- if (N != mPhoneStates.size()) {
- return false;
- }
- for (int i = 0; i < N; i++) {
- if (mPhoneStates.get(i).mSubId != subs.get(i).getSubscriptionId()) {
- return false;
- }
- }
- return true;
- }
-
- private PhoneState getState(int subId) {
- for (PhoneState state : mPhoneStates) {
- if (state.mSubId == subId) {
- return state;
- }
- }
- Log.e(TAG, "Unexpected subscription " + subId);
- return null;
- }
-
- private PhoneState inflatePhoneState(int subId) {
- PhoneState state = new PhoneState(subId, mContext);
- if (mMobileSignalGroup != null) {
- mMobileSignalGroup.addView(state.mMobileGroup);
- }
- mPhoneStates.add(state);
- return state;
- }
-
- @Override
- public void setIsAirplaneMode(IconState icon) {
- mIsAirplaneMode = icon.visible && !mBlockAirplane;
- mAirplaneIconId = icon.icon;
- mAirplaneContentDescription = icon.contentDescription;
-
- apply();
- }
-
- @Override
- public void setMobileDataEnabled(boolean enabled) {
- // Don't care.
- }
-
- @Override
- public boolean dispatchPopulateAccessibilityEventInternal(AccessibilityEvent event) {
- // Standard group layout onPopulateAccessibilityEvent() implementations
- // ignore content description, so populate manually
- if (mEthernetVisible && mEthernetGroup != null &&
- mEthernetGroup.getContentDescription() != null)
- event.getText().add(mEthernetGroup.getContentDescription());
- if (mWifiVisible && mWifiGroup != null && mWifiGroup.getContentDescription() != null)
- event.getText().add(mWifiGroup.getContentDescription());
- for (PhoneState state : mPhoneStates) {
- state.populateAccessibilityEvent(event);
- }
- return super.dispatchPopulateAccessibilityEventInternal(event);
- }
-
- @Override
- public void onRtlPropertiesChanged(int layoutDirection) {
- super.onRtlPropertiesChanged(layoutDirection);
-
- if (mEthernet != null) {
- mEthernet.setImageDrawable(null);
- mEthernetDark.setImageDrawable(null);
- mLastEthernetIconId = -1;
- }
-
- if (mWifi != null) {
- mWifi.setImageDrawable(null);
- mWifiDark.setImageDrawable(null);
- mLastWifiStrengthId = -1;
- }
-
- for (PhoneState state : mPhoneStates) {
- if (state.mMobileType != null) {
- state.mMobileType.setImageDrawable(null);
- state.mLastMobileTypeId = -1;
- }
- }
-
- if (mAirplane != null) {
- mAirplane.setImageDrawable(null);
- mLastAirplaneIconId = -1;
- }
-
- apply();
- }
-
- @Override
- public boolean hasOverlappingRendering() {
- return false;
- }
-
- // Run after each indicator change.
- private void apply() {
- if (mWifiGroup == null) return;
-
- if (mVpnVisible) {
- if (mLastVpnIconId != mVpnIconId) {
- setIconForView(mVpn, mVpnIconId);
- mLastVpnIconId = mVpnIconId;
- }
- mIconLogger.onIconShown(SLOT_VPN);
- mVpn.setVisibility(View.VISIBLE);
- } else {
- mIconLogger.onIconHidden(SLOT_VPN);
- mVpn.setVisibility(View.GONE);
- }
- if (DEBUG) Log.d(TAG, String.format("vpn: %s", mVpnVisible ? "VISIBLE" : "GONE"));
-
- if (mEthernetVisible) {
- if (mLastEthernetIconId != mEthernetIconId) {
- setIconForView(mEthernet, mEthernetIconId);
- setIconForView(mEthernetDark, mEthernetIconId);
- mLastEthernetIconId = mEthernetIconId;
- }
- mEthernetGroup.setContentDescription(mEthernetDescription);
- mIconLogger.onIconShown(SLOT_ETHERNET);
- mEthernetGroup.setVisibility(View.VISIBLE);
- } else {
- mIconLogger.onIconHidden(SLOT_ETHERNET);
- mEthernetGroup.setVisibility(View.GONE);
- }
-
- if (DEBUG) Log.d(TAG,
- String.format("ethernet: %s",
- (mEthernetVisible ? "VISIBLE" : "GONE")));
-
- if (mWifiVisible) {
- if (mWifiStrengthId != mLastWifiStrengthId) {
- setIconForView(mWifi, mWifiStrengthId);
- setIconForView(mWifiDark, mWifiStrengthId);
- mLastWifiStrengthId = mWifiStrengthId;
- }
- mIconLogger.onIconShown(SLOT_WIFI);
- mWifiGroup.setContentDescription(mWifiDescription);
- mWifiGroup.setVisibility(View.VISIBLE);
- } else {
- mIconLogger.onIconHidden(SLOT_WIFI);
- mWifiGroup.setVisibility(View.GONE);
- }
-
- if (DEBUG) Log.d(TAG,
- String.format("wifi: %s sig=%d",
- (mWifiVisible ? "VISIBLE" : "GONE"),
- mWifiStrengthId));
-
- mWifiActivityIn.setVisibility(mWifiIn ? View.VISIBLE : View.GONE);
- mWifiActivityOut.setVisibility(mWifiOut ? View.VISIBLE : View.GONE);
-
- boolean anyMobileVisible = false;
- int firstMobileTypeId = 0;
- for (PhoneState state : mPhoneStates) {
- if (state.apply(anyMobileVisible)) {
- if (!anyMobileVisible) {
- firstMobileTypeId = state.mMobileTypeId;
- anyMobileVisible = true;
- }
- }
- }
- if (anyMobileVisible) {
- mIconLogger.onIconShown(SLOT_MOBILE);
- } else {
- mIconLogger.onIconHidden(SLOT_MOBILE);
- }
-
- if (mIsAirplaneMode) {
- if (mLastAirplaneIconId != mAirplaneIconId) {
- setIconForView(mAirplane, mAirplaneIconId);
- mLastAirplaneIconId = mAirplaneIconId;
- }
- mAirplane.setContentDescription(mAirplaneContentDescription);
- mIconLogger.onIconShown(SLOT_AIRPLANE);
- mAirplane.setVisibility(View.VISIBLE);
- } else {
- mIconLogger.onIconHidden(SLOT_AIRPLANE);
- mAirplane.setVisibility(View.GONE);
- }
-
- if (mIsAirplaneMode && mWifiVisible) {
- mWifiAirplaneSpacer.setVisibility(View.VISIBLE);
- } else {
- mWifiAirplaneSpacer.setVisibility(View.GONE);
- }
-
- if ((anyMobileVisible && firstMobileTypeId != 0) && mWifiVisible) {
- mWifiSignalSpacer.setVisibility(View.VISIBLE);
- } else {
- mWifiSignalSpacer.setVisibility(View.GONE);
- }
-
- boolean anythingVisible = mWifiVisible || mIsAirplaneMode
- || anyMobileVisible || mVpnVisible || mEthernetVisible;
- setPaddingRelative(0, 0, anythingVisible ? mEndPadding : mEndPaddingNothingVisible, 0);
- }
-
- /**
- * Sets the given drawable id on the view. This method will also scale the icon by
- * {@link #mIconScaleFactor} if appropriate.
- */
- private void setIconForView(ImageView imageView, @DrawableRes int iconId) {
- // Using the imageView's context to retrieve the Drawable so that theme is preserved.
- Drawable icon = imageView.getContext().getDrawable(iconId);
-
- if (mIconScaleFactor == 1.f) {
- imageView.setImageDrawable(icon);
- } else {
- imageView.setImageDrawable(new ScalingDrawableWrapper(icon, mIconScaleFactor));
- }
- }
-
-
- @Override
- public void onDarkChanged(Rect tintArea, float darkIntensity, int tint) {
- boolean changed = tint != mIconTint || darkIntensity != mDarkIntensity
- || !mTintArea.equals(tintArea);
- mIconTint = tint;
- mDarkIntensity = darkIntensity;
- mTintArea.set(tintArea);
- if (changed && isAttachedToWindow()) {
- applyIconTint();
- }
- }
-
- private void applyIconTint() {
- setTint(mVpn, DarkIconDispatcher.getTint(mTintArea, mVpn, mIconTint));
- setTint(mAirplane, DarkIconDispatcher.getTint(mTintArea, mAirplane, mIconTint));
- applyDarkIntensity(
- DarkIconDispatcher.getDarkIntensity(mTintArea, mWifi, mDarkIntensity),
- mWifi, mWifiDark);
- setTint(mWifiActivityIn,
- DarkIconDispatcher.getTint(mTintArea, mWifiActivityIn, mIconTint));
- setTint(mWifiActivityOut,
- DarkIconDispatcher.getTint(mTintArea, mWifiActivityOut, mIconTint));
- applyDarkIntensity(
- DarkIconDispatcher.getDarkIntensity(mTintArea, mEthernet, mDarkIntensity),
- mEthernet, mEthernetDark);
- for (int i = 0; i < mPhoneStates.size(); i++) {
- mPhoneStates.get(i).setIconTint(mIconTint, mDarkIntensity, mTintArea);
- }
- }
-
- private void applyDarkIntensity(float darkIntensity, View lightIcon, View darkIcon) {
- lightIcon.setAlpha(1 - darkIntensity);
- darkIcon.setAlpha(darkIntensity);
- }
-
- private void setTint(ImageView v, int tint) {
- v.setImageTintList(ColorStateList.valueOf(tint));
- }
-
- private int currentVpnIconId(boolean isBranded) {
- return isBranded ? R.drawable.stat_sys_branded_vpn : R.drawable.stat_sys_vpn_ic;
- }
-
- private class PhoneState {
- private final int mSubId;
- private boolean mMobileVisible = false;
- private int mMobileStrengthId = 0, mMobileTypeId = 0;
- private int mLastMobileStrengthId = -1;
- private int mLastMobileTypeId = -1;
- private String mMobileDescription, mMobileTypeDescription;
-
- private ViewGroup mMobileGroup;
- private ImageView mMobile, mMobileType, mMobileRoaming;
- private View mMobileRoamingSpace;
- public boolean mRoaming;
- private ImageView mMobileActivityIn;
- private ImageView mMobileActivityOut;
- public boolean mActivityIn;
- public boolean mActivityOut;
- private SignalDrawable mMobileSignalDrawable;
-
- public PhoneState(int subId, Context context) {
- ViewGroup root = (ViewGroup) LayoutInflater.from(context)
- .inflate(R.layout.mobile_signal_group, null);
- setViews(root);
- mSubId = subId;
- }
-
- public void setViews(ViewGroup root) {
- mMobileGroup = root;
- mMobile = root.findViewById(R.id.mobile_signal);
- mMobileType = root.findViewById(R.id.mobile_type);
- mMobileRoaming = root.findViewById(R.id.mobile_roaming);
- mMobileRoamingSpace = root.findViewById(R.id.mobile_roaming_space);
- mMobileActivityIn = root.findViewById(R.id.mobile_in);
- mMobileActivityOut = root.findViewById(R.id.mobile_out);
- mMobileSignalDrawable = new SignalDrawable(mMobile.getContext());
- mMobile.setImageDrawable(mMobileSignalDrawable);
- }
-
- public boolean apply(boolean isSecondaryIcon) {
- if (mMobileVisible && !mIsAirplaneMode) {
- if (mLastMobileStrengthId != mMobileStrengthId) {
- mMobile.getDrawable().setLevel(mMobileStrengthId);
- mLastMobileStrengthId = mMobileStrengthId;
- }
-
- if (mLastMobileTypeId != mMobileTypeId) {
- mMobileType.setImageResource(mMobileTypeId);
- mLastMobileTypeId = mMobileTypeId;
- }
-
- mMobileGroup.setContentDescription(mMobileTypeDescription
- + " " + mMobileDescription);
- mMobileGroup.setVisibility(View.VISIBLE);
- } else {
- mMobileGroup.setVisibility(View.GONE);
- }
-
- // When this isn't next to wifi, give it some extra padding between the signals.
- mMobileGroup.setPaddingRelative(isSecondaryIcon ? mSecondaryTelephonyPadding : 0,
- 0, 0, 0);
- mMobile.setPaddingRelative(mMobileDataIconStartPadding, 0, 0, 0);
-
- if (DEBUG) Log.d(TAG, String.format("mobile: %s sig=%d typ=%d",
- (mMobileVisible ? "VISIBLE" : "GONE"), mMobileStrengthId, mMobileTypeId));
-
- mMobileType.setVisibility(mMobileTypeId != 0 ? View.VISIBLE : View.GONE);
- mMobileRoaming.setVisibility((mRoaming)? View.VISIBLE : View.GONE);
- mMobileRoamingSpace.setVisibility((mRoaming) ? View.VISIBLE : View.GONE);
- mMobileActivityIn.setVisibility(mActivityIn ? View.VISIBLE : View.GONE);
- mMobileActivityOut.setVisibility(mActivityOut ? View.VISIBLE : View.GONE);
-
- return mMobileVisible;
- }
-
- public void populateAccessibilityEvent(AccessibilityEvent event) {
- if (mMobileVisible && mMobileGroup != null
- && mMobileGroup.getContentDescription() != null) {
- event.getText().add(mMobileGroup.getContentDescription());
- }
- }
-
- public void setIconTint(int tint, float darkIntensity, Rect tintArea) {
- mMobileSignalDrawable.setDarkIntensity(darkIntensity);
- setTint(mMobileType, DarkIconDispatcher.getTint(tintArea, mMobileType, tint));
- setTint(mMobileRoaming, DarkIconDispatcher.getTint(tintArea, mMobileRoaming,
- tint));
- setTint(mMobileActivityIn,
- DarkIconDispatcher.getTint(tintArea, mMobileActivityIn, tint));
- setTint(mMobileActivityOut,
- DarkIconDispatcher.getTint(tintArea, mMobileActivityOut, tint));
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/SmartReplyController.java b/packages/SystemUI/src/com/android/systemui/statusbar/SmartReplyController.java
index 9e91133aff09..573c1f8ee509 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/SmartReplyController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/SmartReplyController.java
@@ -21,8 +21,8 @@ import android.util.ArraySet;
import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.statusbar.NotificationVisibility;
-import com.android.systemui.statusbar.notification.NotificationData;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import java.util.Set;
@@ -54,7 +54,7 @@ public class SmartReplyController {
/**
* Notifies StatusBarService a smart reply is sent.
*/
- public void smartReplySent(NotificationData.Entry entry, int replyIndex, CharSequence reply,
+ public void smartReplySent(NotificationEntry entry, int replyIndex, CharSequence reply,
boolean generatedByAssistant) {
mCallback.onSmartReplySent(entry, reply);
mSendingKeys.add(entry.key);
@@ -70,7 +70,7 @@ public class SmartReplyController {
* Notifies StatusBarService a smart action is clicked.
*/
public void smartActionClicked(
- NotificationData.Entry entry, int actionIndex, Notification.Action action,
+ NotificationEntry entry, int actionIndex, Notification.Action action,
boolean generatedByAssistant) {
final int count = mEntryManager.getNotificationData().getActiveNotifications().size();
final int rank = mEntryManager.getNotificationData().getRank(entry.key);
@@ -95,7 +95,7 @@ public class SmartReplyController {
/**
* Smart Replies and Actions have been added to the UI.
*/
- public void smartSuggestionsAdded(final NotificationData.Entry entry, int replyCount,
+ public void smartSuggestionsAdded(final NotificationEntry entry, int replyCount,
int actionCount, boolean generatedByAssistant) {
try {
mBarService.onNotificationSmartSuggestionsAdded(
@@ -105,7 +105,7 @@ public class SmartReplyController {
}
}
- public void stopSending(final NotificationData.Entry entry) {
+ public void stopSending(final NotificationEntry entry) {
if (entry != null) {
mSendingKeys.remove(entry.notification.getKey());
}
@@ -121,6 +121,6 @@ public class SmartReplyController {
* @param entry the entry for the notification
* @param reply the reply that was sent
*/
- void onSmartReplySent(NotificationData.Entry entry, CharSequence reply);
+ void onSmartReplySent(NotificationEntry entry, CharSequence reply);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconContainer.java
deleted file mode 100644
index 065222762b33..000000000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconContainer.java
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar;
-
-import com.android.internal.statusbar.StatusBarIcon;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Holds an array of {@link com.android.internal.statusbar.StatusBarIcon}s and draws them
- * in a linear layout
- */
-public class StatusBarIconContainer {
- private final List<StatusBarIcon> mIcons = new ArrayList<>();
-
- public StatusBarIconContainer(List<StatusBarIcon> icons) {
- mIcons.addAll(icons);
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
index 3c1335456d04..19ed13e7a95e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
@@ -206,6 +206,10 @@ public class StatusBarIconView extends AnimatedImageView implements StatusIconDi
mIconScale = SYSTEM_ICON_SCALE;
}
+ public float getIconScaleFullyDark() {
+ return (float) mStatusBarIconDrawingSizeDark / mStatusBarIconDrawingSize;
+ }
+
public float getIconScale() {
return mIconScale;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateController.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateController.java
index 087b65592b7a..54bce1c00354 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateController.java
@@ -331,7 +331,8 @@ public class StatusBarStateController implements CallbackController<StateListene
*
* @param newState the new {@link StatusBarState}
*/
- public void onStateChanged(int newState);
+ default void onStateChanged(int newState) {
+ }
/**
* Callback to be notified when Dozing changes. Dozing is stored separately from state.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationAlertingManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationAlertingManager.java
index 7b42dd901d72..5605f3db90fb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationAlertingManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationAlertingManager.java
@@ -20,7 +20,6 @@ import static com.android.systemui.statusbar.NotificationRemoteInputManager.FORC
import static com.android.systemui.statusbar.notification.row.NotificationInflater.FLAG_CONTENT_VIEW_AMBIENT;
import static com.android.systemui.statusbar.notification.row.NotificationInflater.FLAG_CONTENT_VIEW_HEADS_UP;
-import android.annotation.Nullable;
import android.app.Notification;
import android.service.notification.StatusBarNotification;
import android.util.Log;
@@ -30,6 +29,7 @@ import com.android.systemui.statusbar.AlertingNotificationManager;
import com.android.systemui.statusbar.AmbientPulseManager;
import com.android.systemui.statusbar.NotificationListener;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.row.NotificationInflater;
import com.android.systemui.statusbar.phone.ShadeController;
import com.android.systemui.statusbar.policy.HeadsUpManager;
@@ -72,24 +72,21 @@ public class NotificationAlertingManager {
notificationEntryManager.addNotificationEntryListener(new NotificationEntryListener() {
@Override
- public void onEntryInflated(NotificationData.Entry entry, int inflatedFlags) {
+ public void onEntryInflated(NotificationEntry entry, int inflatedFlags) {
showAlertingView(entry, inflatedFlags);
}
@Override
- public void onEntryUpdated(NotificationData.Entry entry) {
+ public void onPostEntryUpdated(NotificationEntry entry) {
updateAlertState(entry);
}
@Override
public void onEntryRemoved(
- @Nullable NotificationData.Entry entry,
- String key,
- StatusBarNotification old,
+ NotificationEntry entry,
NotificationVisibility visibility,
- boolean lifetimeExtended,
boolean removedByUser) {
- stopAlerting(key);
+ stopAlerting(entry.key);
}
});
}
@@ -105,7 +102,7 @@ public class NotificationAlertingManager {
* @param entry entry to add
* @param inflatedFlags flags representing content views that were inflated
*/
- private void showAlertingView(NotificationData.Entry entry,
+ private void showAlertingView(NotificationEntry entry,
@NotificationInflater.InflationFlag int inflatedFlags) {
if ((inflatedFlags & FLAG_CONTENT_VIEW_HEADS_UP) != 0) {
// Possible for shouldHeadsUp to change between the inflation starting and ending.
@@ -127,7 +124,7 @@ public class NotificationAlertingManager {
}
}
- private void updateAlertState(NotificationData.Entry entry) {
+ private void updateAlertState(NotificationEntry entry) {
boolean alertAgain = alertAgain(entry, entry.notification.getNotification());
AlertingNotificationManager alertManager;
boolean shouldAlert;
@@ -153,8 +150,15 @@ public class NotificationAlertingManager {
}
}
- private static boolean alertAgain(
- NotificationData.Entry oldEntry, Notification newNotification) {
+ /**
+ * Checks whether an update for a notification warrants an alert for the user.
+ *
+ * @param oldEntry the entry for this notification.
+ * @param newNotification the new notification for this entry.
+ * @return whether this notification should alert the user.
+ */
+ public static boolean alertAgain(
+ NotificationEntry oldEntry, Notification newNotification) {
return oldEntry == null || !oldEntry.hasInterrupted()
|| (newNotification.flags & Notification.FLAG_ONLY_ALERT_ONCE) == 0;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationData.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationData.java
deleted file mode 100644
index a51896ee69fc..000000000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationData.java
+++ /dev/null
@@ -1,1072 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.statusbar.notification;
-
-import static android.app.Notification.CATEGORY_ALARM;
-import static android.app.Notification.CATEGORY_CALL;
-import static android.app.Notification.CATEGORY_EVENT;
-import static android.app.Notification.CATEGORY_MESSAGE;
-import static android.app.Notification.CATEGORY_REMINDER;
-import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_AMBIENT;
-import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_FULL_SCREEN_INTENT;
-import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST;
-import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK;
-import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_STATUS_BAR;
-
-import android.annotation.NonNull;
-import android.app.Notification;
-import android.app.NotificationChannel;
-import android.app.NotificationManager;
-import android.app.Person;
-import android.content.Context;
-import android.graphics.drawable.Icon;
-import android.os.Bundle;
-import android.os.Parcelable;
-import android.os.SystemClock;
-import android.service.notification.NotificationListenerService.Ranking;
-import android.service.notification.NotificationListenerService.RankingMap;
-import android.service.notification.SnoozeCriterion;
-import android.service.notification.StatusBarNotification;
-import android.util.ArrayMap;
-import android.util.ArraySet;
-import android.view.View;
-import android.widget.ImageView;
-
-import androidx.annotation.Nullable;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.statusbar.StatusBarIcon;
-import com.android.internal.util.ArrayUtils;
-import com.android.internal.util.ContrastColorUtil;
-import com.android.systemui.Dependency;
-import com.android.systemui.statusbar.InflationTask;
-import com.android.systemui.statusbar.NotificationMediaManager;
-import com.android.systemui.statusbar.StatusBarIconView;
-import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
-import com.android.systemui.statusbar.notification.row.NotificationGuts;
-import com.android.systemui.statusbar.notification.row.NotificationInflater.InflationFlag;
-import com.android.systemui.statusbar.phone.NotificationGroupManager;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
-
-import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.List;
-import java.util.Objects;
-
-/**
- * The list of currently displaying notifications.
- */
-public class NotificationData {
-
- private final NotificationFilter mNotificationFilter = Dependency.get(NotificationFilter.class);
-
- /**
- * These dependencies are late init-ed
- */
- private KeyguardEnvironment mEnvironment;
- private NotificationMediaManager mMediaManager;
-
- private HeadsUpManager mHeadsUpManager;
-
- public static final class Entry {
- private static final long LAUNCH_COOLDOWN = 2000;
- private static final long REMOTE_INPUT_COOLDOWN = 500;
- private static final long INITIALIZATION_DELAY = 400;
- private static final long NOT_LAUNCHED_YET = -LAUNCH_COOLDOWN;
- private static final int COLOR_INVALID = 1;
- public final String key;
- public StatusBarNotification notification;
- public NotificationChannel channel;
- public long lastAudiblyAlertedMs;
- public boolean noisy;
- public boolean ambient;
- public int importance;
- public StatusBarIconView icon;
- public StatusBarIconView expandedIcon;
- private boolean interruption;
- public boolean autoRedacted; // whether the redacted notification was generated by us
- public int targetSdk;
- private long lastFullScreenIntentLaunchTime = NOT_LAUNCHED_YET;
- public CharSequence remoteInputText;
- public List<SnoozeCriterion> snoozeCriteria;
- public int userSentiment = Ranking.USER_SENTIMENT_NEUTRAL;
- /** Smart Actions provided by the NotificationAssistantService. */
- @NonNull
- public List<Notification.Action> systemGeneratedSmartActions = Collections.emptyList();
- public CharSequence[] smartReplies = new CharSequence[0];
- @VisibleForTesting
- public int suppressedVisualEffects;
- public boolean suspended;
-
- private Entry parent; // our parent (if we're in a group)
- private ArrayList<Entry> children = new ArrayList<Entry>();
- private ExpandableNotificationRow row; // the outer expanded view
-
- private int mCachedContrastColor = COLOR_INVALID;
- private int mCachedContrastColorIsFor = COLOR_INVALID;
- private InflationTask mRunningTask = null;
- private Throwable mDebugThrowable;
- public CharSequence remoteInputTextWhenReset;
- public long lastRemoteInputSent = NOT_LAUNCHED_YET;
- public ArraySet<Integer> mActiveAppOps = new ArraySet<>(3);
- public CharSequence headsUpStatusBarText;
- public CharSequence headsUpStatusBarTextPublic;
-
- private long initializationTime = -1;
-
- /**
- * Whether or not this row represents a system notification. Note that if this is
- * {@code null}, that means we were either unable to retrieve the info or have yet to
- * retrieve the info.
- */
- public Boolean mIsSystemNotification;
-
- /**
- * Has the user sent a reply through this Notification.
- */
- private boolean hasSentReply;
-
- /**
- * Whether this notification should be displayed as a bubble.
- */
- private boolean mIsBubble;
-
- /**
- * Whether the user has dismissed this notification when it was in bubble form.
- */
- private boolean mUserDismissedBubble;
-
- public Entry(StatusBarNotification n) {
- this(n, null);
- }
-
- public Entry(StatusBarNotification n, @Nullable Ranking ranking) {
- this.key = n.getKey();
- this.notification = n;
- if (ranking != null) {
- populateFromRanking(ranking);
- }
- }
-
- public void populateFromRanking(@NonNull Ranking ranking) {
- channel = ranking.getChannel();
- lastAudiblyAlertedMs = ranking.getLastAudiblyAlertedMillis();
- importance = ranking.getImportance();
- ambient = ranking.isAmbient();
- snoozeCriteria = ranking.getSnoozeCriteria();
- userSentiment = ranking.getUserSentiment();
- systemGeneratedSmartActions = ranking.getSmartActions() == null
- ? Collections.emptyList() : ranking.getSmartActions();
- smartReplies = ranking.getSmartReplies() == null
- ? new CharSequence[0]
- : ranking.getSmartReplies().toArray(new CharSequence[0]);
- suppressedVisualEffects = ranking.getSuppressedVisualEffects();
- suspended = ranking.isSuspended();
- }
-
- public void setInterruption() {
- interruption = true;
- }
-
- public boolean hasInterrupted() {
- return interruption;
- }
-
- public void setIsBubble(boolean bubbleable) {
- mIsBubble = bubbleable;
- }
-
- public boolean isBubble() {
- return mIsBubble;
- }
-
- public void setBubbleDismissed(boolean userDismissed) {
- mUserDismissedBubble = userDismissed;
- }
-
- public boolean isBubbleDismissed() {
- return mUserDismissedBubble;
- }
-
- /**
- * Resets the notification entry to be re-used.
- */
- public void reset() {
- if (row != null) {
- row.reset();
- }
- }
-
- public ExpandableNotificationRow getRow() {
- return row;
- }
-
- //TODO: This will go away when we have a way to bind an entry to a row
- public void setRow(ExpandableNotificationRow row) {
- this.row = row;
- }
-
- @Nullable
- public List<Entry> getChildren() {
- if (children.size() <= 0) {
- return null;
- }
-
- return children;
- }
-
- public void notifyFullScreenIntentLaunched() {
- setInterruption();
- lastFullScreenIntentLaunchTime = SystemClock.elapsedRealtime();
- }
-
- public boolean hasJustLaunchedFullScreenIntent() {
- return SystemClock.elapsedRealtime() < lastFullScreenIntentLaunchTime + LAUNCH_COOLDOWN;
- }
-
- public boolean hasJustSentRemoteInput() {
- return SystemClock.elapsedRealtime() < lastRemoteInputSent + REMOTE_INPUT_COOLDOWN;
- }
-
- public boolean hasFinishedInitialization() {
- return initializationTime == -1 ||
- SystemClock.elapsedRealtime() > initializationTime + INITIALIZATION_DELAY;
- }
-
- /**
- * Create the icons for a notification
- * @param context the context to create the icons with
- * @param sbn the notification
- * @throws InflationException
- */
- public void createIcons(Context context, StatusBarNotification sbn)
- throws InflationException {
- Notification n = sbn.getNotification();
- final Icon smallIcon = n.getSmallIcon();
- if (smallIcon == null) {
- throw new InflationException("No small icon in notification from "
- + sbn.getPackageName());
- }
-
- // Construct the icon.
- icon = new StatusBarIconView(context,
- sbn.getPackageName() + "/0x" + Integer.toHexString(sbn.getId()), sbn);
- icon.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
-
- // Construct the expanded icon.
- expandedIcon = new StatusBarIconView(context,
- sbn.getPackageName() + "/0x" + Integer.toHexString(sbn.getId()), sbn);
- expandedIcon.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
- final StatusBarIcon ic = new StatusBarIcon(
- sbn.getUser(),
- sbn.getPackageName(),
- smallIcon,
- n.iconLevel,
- n.number,
- StatusBarIconView.contentDescForNotification(context, n));
- if (!icon.set(ic) || !expandedIcon.set(ic)) {
- icon = null;
- expandedIcon = null;
- throw new InflationException("Couldn't create icon: " + ic);
- }
- expandedIcon.setVisibility(View.INVISIBLE);
- expandedIcon.setOnVisibilityChangedListener(
- newVisibility -> {
- if (row != null) {
- row.setIconsVisible(newVisibility != View.VISIBLE);
- }
- });
- }
-
- public void setIconTag(int key, Object tag) {
- if (icon != null) {
- icon.setTag(key, tag);
- expandedIcon.setTag(key, tag);
- }
- }
-
- /**
- * Update the notification icons.
- *
- * @param context the context to create the icons with.
- * @param sbn the notification to read the icon from.
- * @throws InflationException
- */
- public void updateIcons(Context context, StatusBarNotification sbn)
- throws InflationException {
- if (icon != null) {
- // Update the icon
- Notification n = sbn.getNotification();
- final StatusBarIcon ic = new StatusBarIcon(
- notification.getUser(),
- notification.getPackageName(),
- n.getSmallIcon(),
- n.iconLevel,
- n.number,
- StatusBarIconView.contentDescForNotification(context, n));
- icon.setNotification(sbn);
- expandedIcon.setNotification(sbn);
- if (!icon.set(ic) || !expandedIcon.set(ic)) {
- throw new InflationException("Couldn't update icon: " + ic);
- }
- }
- }
-
- public int getContrastedColor(Context context, boolean isLowPriority,
- int backgroundColor) {
- int rawColor = isLowPriority ? Notification.COLOR_DEFAULT :
- notification.getNotification().color;
- if (mCachedContrastColorIsFor == rawColor && mCachedContrastColor != COLOR_INVALID) {
- return mCachedContrastColor;
- }
- final int contrasted = ContrastColorUtil.resolveContrastColor(context, rawColor,
- backgroundColor);
- mCachedContrastColorIsFor = rawColor;
- mCachedContrastColor = contrasted;
- return mCachedContrastColor;
- }
-
- /**
- * Abort all existing inflation tasks
- */
- public void abortTask() {
- if (mRunningTask != null) {
- mRunningTask.abort();
- mRunningTask = null;
- }
- }
-
- public void setInflationTask(InflationTask abortableTask) {
- // abort any existing inflation
- InflationTask existing = mRunningTask;
- abortTask();
- mRunningTask = abortableTask;
- if (existing != null && mRunningTask != null) {
- mRunningTask.supersedeTask(existing);
- }
- }
-
- public void onInflationTaskFinished() {
- mRunningTask = null;
- }
-
- @VisibleForTesting
- public InflationTask getRunningTask() {
- return mRunningTask;
- }
-
- /**
- * Set a throwable that is used for debugging
- *
- * @param debugThrowable the throwable to save
- */
- public void setDebugThrowable(Throwable debugThrowable) {
- mDebugThrowable = debugThrowable;
- }
-
- public Throwable getDebugThrowable() {
- return mDebugThrowable;
- }
-
- public void onRemoteInputInserted() {
- lastRemoteInputSent = NOT_LAUNCHED_YET;
- remoteInputTextWhenReset = null;
- }
-
- public void setHasSentReply() {
- hasSentReply = true;
- }
-
- public boolean isLastMessageFromReply() {
- if (!hasSentReply) {
- return false;
- }
- Bundle extras = notification.getNotification().extras;
- CharSequence[] replyTexts = extras.getCharSequenceArray(
- Notification.EXTRA_REMOTE_INPUT_HISTORY);
- if (!ArrayUtils.isEmpty(replyTexts)) {
- return true;
- }
- Parcelable[] messages = extras.getParcelableArray(Notification.EXTRA_MESSAGES);
- if (messages != null && messages.length > 0) {
- Parcelable message = messages[messages.length - 1];
- if (message instanceof Bundle) {
- Notification.MessagingStyle.Message lastMessage =
- Notification.MessagingStyle.Message.getMessageFromBundle(
- (Bundle) message);
- if (lastMessage != null) {
- Person senderPerson = lastMessage.getSenderPerson();
- if (senderPerson == null) {
- return true;
- }
- Person user = extras.getParcelable(Notification.EXTRA_MESSAGING_PERSON);
- return Objects.equals(user, senderPerson);
- }
- }
- }
- return false;
- }
-
- public void setInitializationTime(long time) {
- if (initializationTime == -1) {
- initializationTime = time;
- }
- }
-
- public void sendAccessibilityEvent(int eventType) {
- if (row != null) {
- row.sendAccessibilityEvent(eventType);
- }
- }
-
- /**
- * Used by NotificationMediaManager to determine... things
- * @return {@code true} if we are a media notification
- */
- public boolean isMediaNotification() {
- if (row == null) return false;
-
- return row.isMediaRow();
- }
-
- /**
- * We are a top level child if our parent is the list of notifications duh
- * @return {@code true} if we're a top level notification
- */
- public boolean isTopLevelChild() {
- return row != null && row.isTopLevelChild();
- }
-
- public void resetUserExpansion() {
- if (row != null) row.resetUserExpansion();
- }
-
- public void freeContentViewWhenSafe(@InflationFlag int inflationFlag) {
- if (row != null) row.freeContentViewWhenSafe(inflationFlag);
- }
-
- public void setAmbientPulsing(boolean pulsing) {
- if (row != null) row.setAmbientPulsing(pulsing);
- }
-
- public boolean rowExists() {
- return row != null;
- }
-
- public boolean isRowDismissed() {
- return row != null && row.isDismissed();
- }
-
- public boolean isRowRemoved() {
- return row != null && row.isRemoved();
- }
-
- /**
- * @return {@code true} if the row is null or removed
- */
- public boolean isRemoved() {
- //TODO: recycling invalidates this
- return row == null || row.isRemoved();
- }
-
- /**
- * @return {@code true} if the row is null or dismissed
- */
- public boolean isDismissed() {
- //TODO: recycling
- return row == null || row.isDismissed();
- }
-
- public boolean isRowPinned() {
- return row != null && row.isPinned();
- }
-
- public void setRowPinned(boolean pinned) {
- if (row != null) row.setPinned(pinned);
- }
-
- public boolean isRowAnimatingAway() {
- return row != null && row.isHeadsUpAnimatingAway();
- }
-
- public boolean isRowHeadsUp() {
- return row != null && row.isHeadsUp();
- }
-
- public void setHeadsUp(boolean shouldHeadsUp) {
- if (row != null) row.setHeadsUp(shouldHeadsUp);
- }
-
- public boolean mustStayOnScreen() {
- return row != null && row.mustStayOnScreen();
- }
-
- public void setHeadsUpIsVisible() {
- if (row != null) row.setHeadsUpIsVisible();
- }
-
- //TODO: i'm imagining a world where this isn't just the row, but I could be rwong
- public ExpandableNotificationRow getHeadsUpAnimationView() {
- return row;
- }
-
- public void setUserLocked(boolean userLocked) {
- if (row != null) row.setUserLocked(userLocked);
- }
-
- public void setUserExpanded(boolean userExpanded, boolean allowChildExpansion) {
- if (row != null) row.setUserExpanded(userExpanded, allowChildExpansion);
- }
-
- public void setGroupExpansionChanging(boolean changing) {
- if (row != null) row.setGroupExpansionChanging(changing);
- }
-
- public void notifyHeightChanged(boolean needsAnimation) {
- if (row != null) row.notifyHeightChanged(needsAnimation);
- }
-
- public void closeRemoteInput() {
- if (row != null) row.closeRemoteInput();
- }
-
- public boolean areChildrenExpanded() {
- return row != null && row.areChildrenExpanded();
- }
-
- public boolean keepInParent() {
- return row != null && row.keepInParent();
- }
-
- //TODO: probably less confusing to say "is group fully visible"
- public boolean isGroupNotFullyVisible() {
- return row == null || row.isGroupNotFullyVisible();
- }
-
- public NotificationGuts getGuts() {
- if (row != null) return row.getGuts();
- return null;
- }
-
- public boolean hasLowPriorityStateUpdated() {
- return row != null && row.hasLowPriorityStateUpdated();
- }
-
- public void removeRow() {
- if (row != null) row.setRemoved();
- }
-
- public boolean isSummaryWithChildren() {
- return row != null && row.isSummaryWithChildren();
- }
-
- public void setKeepInParent(boolean keep) {
- if (row != null) row.setKeepInParent(keep);
- }
-
- public void onDensityOrFontScaleChanged() {
- if (row != null) row.onDensityOrFontScaleChanged();
- }
-
- public boolean areGutsExposed() {
- return row != null && row.getGuts() != null && row.getGuts().isExposed();
- }
-
- public boolean isChildInGroup() {
- return parent == null;
- }
-
- public void setLowPriorityStateUpdated(boolean updated) {
- if (row != null) row.setLowPriorityStateUpdated(updated);
- }
-
- /**
- * @return Can the underlying notification be cleared? This can be different from whether the
- * notification can be dismissed in case notifications are sensitive on the lockscreen.
- * @see #canViewBeDismissed()
- */
- public boolean isClearable() {
- if (notification == null || !notification.isClearable()) {
- return false;
- }
- if (children.size() > 0) {
- for (int i = 0; i < children.size(); i++) {
- Entry child = children.get(i);
- if (!child.isClearable()) {
- return false;
- }
- }
- }
- return true;
- }
-
- public boolean canViewBeDismissed() {
- if (row == null) return true;
- return row.canViewBeDismissed();
- }
-
- boolean isExemptFromDndVisualSuppression() {
- if (isNotificationBlockedByPolicy(notification.getNotification())) {
- return false;
- }
-
- if ((notification.getNotification().flags
- & Notification.FLAG_FOREGROUND_SERVICE) != 0) {
- return true;
- }
- if (notification.getNotification().isMediaNotification()) {
- return true;
- }
- if (mIsSystemNotification != null && mIsSystemNotification) {
- return true;
- }
- return false;
- }
-
- private boolean shouldSuppressVisualEffect(int effect) {
- if (isExemptFromDndVisualSuppression()) {
- return false;
- }
- return (suppressedVisualEffects & effect) != 0;
- }
-
- /**
- * Returns whether {@link NotificationManager.Policy#SUPPRESSED_EFFECT_FULL_SCREEN_INTENT}
- * is set for this entry.
- */
- public boolean shouldSuppressFullScreenIntent() {
- return shouldSuppressVisualEffect(SUPPRESSED_EFFECT_FULL_SCREEN_INTENT);
- }
-
- /**
- * Returns whether {@link NotificationManager.Policy#SUPPRESSED_EFFECT_PEEK}
- * is set for this entry.
- */
- public boolean shouldSuppressPeek() {
- return shouldSuppressVisualEffect(SUPPRESSED_EFFECT_PEEK);
- }
-
- /**
- * Returns whether {@link NotificationManager.Policy#SUPPRESSED_EFFECT_STATUS_BAR}
- * is set for this entry.
- */
- public boolean shouldSuppressStatusBar() {
- return shouldSuppressVisualEffect(SUPPRESSED_EFFECT_STATUS_BAR);
- }
-
- /**
- * Returns whether {@link NotificationManager.Policy#SUPPRESSED_EFFECT_AMBIENT}
- * is set for this entry.
- */
- public boolean shouldSuppressAmbient() {
- return shouldSuppressVisualEffect(SUPPRESSED_EFFECT_AMBIENT);
- }
-
- /**
- * Returns whether {@link NotificationManager.Policy#SUPPRESSED_EFFECT_NOTIFICATION_LIST}
- * is set for this entry.
- */
- public boolean shouldSuppressNotificationList() {
- return shouldSuppressVisualEffect(SUPPRESSED_EFFECT_NOTIFICATION_LIST);
- }
- }
-
- private final ArrayMap<String, Entry> mEntries = new ArrayMap<>();
- private final ArrayList<Entry> mSortedAndFiltered = new ArrayList<>();
- private final ArrayList<Entry> mFilteredForUser = new ArrayList<>();
-
- private final NotificationGroupManager mGroupManager
- = Dependency.get(NotificationGroupManager.class);
-
- private RankingMap mRankingMap;
- private final Ranking mTmpRanking = new Ranking();
-
- public void setHeadsUpManager(HeadsUpManager headsUpManager) {
- mHeadsUpManager = headsUpManager;
- }
-
- private final Comparator<Entry> mRankingComparator = new Comparator<Entry>() {
- private final Ranking mRankingA = new Ranking();
- private final Ranking mRankingB = new Ranking();
-
- @Override
- public int compare(Entry a, Entry b) {
- final StatusBarNotification na = a.notification;
- final StatusBarNotification nb = b.notification;
- int aImportance = NotificationManager.IMPORTANCE_DEFAULT;
- int bImportance = NotificationManager.IMPORTANCE_DEFAULT;
- int aRank = 0;
- int bRank = 0;
-
- if (mRankingMap != null) {
- // RankingMap as received from NoMan
- getRanking(a.key, mRankingA);
- getRanking(b.key, mRankingB);
- aImportance = mRankingA.getImportance();
- bImportance = mRankingB.getImportance();
- aRank = mRankingA.getRank();
- bRank = mRankingB.getRank();
- }
-
- String mediaNotification = getMediaManager().getMediaNotificationKey();
-
- // IMPORTANCE_MIN media streams are allowed to drift to the bottom
- final boolean aMedia = a.key.equals(mediaNotification)
- && aImportance > NotificationManager.IMPORTANCE_MIN;
- final boolean bMedia = b.key.equals(mediaNotification)
- && bImportance > NotificationManager.IMPORTANCE_MIN;
-
- boolean aSystemMax = aImportance >= NotificationManager.IMPORTANCE_HIGH &&
- isSystemNotification(na);
- boolean bSystemMax = bImportance >= NotificationManager.IMPORTANCE_HIGH &&
- isSystemNotification(nb);
-
- boolean isHeadsUp = a.row.isHeadsUp();
- if (isHeadsUp != b.row.isHeadsUp()) {
- return isHeadsUp ? -1 : 1;
- } else if (isHeadsUp) {
- // Provide consistent ranking with headsUpManager
- return mHeadsUpManager.compare(a, b);
- } else if (a.row.isAmbientPulsing() != b.row.isAmbientPulsing()) {
- return a.row.isAmbientPulsing() ? -1 : 1;
- } else if (aMedia != bMedia) {
- // Upsort current media notification.
- return aMedia ? -1 : 1;
- } else if (aSystemMax != bSystemMax) {
- // Upsort PRIORITY_MAX system notifications
- return aSystemMax ? -1 : 1;
- } else if (aRank != bRank) {
- return aRank - bRank;
- } else {
- return Long.compare(nb.getNotification().when, na.getNotification().when);
- }
- }
- };
-
- private KeyguardEnvironment getEnvironment() {
- if (mEnvironment == null) {
- mEnvironment = Dependency.get(KeyguardEnvironment.class);
- }
- return mEnvironment;
- }
-
- private NotificationMediaManager getMediaManager() {
- if (mMediaManager == null) {
- mMediaManager = Dependency.get(NotificationMediaManager.class);
- }
- return mMediaManager;
- }
-
- /**
- * Returns the sorted list of active notifications (depending on {@link KeyguardEnvironment}
- *
- * <p>
- * This call doesn't update the list of active notifications. Call {@link #filterAndSort()}
- * when the environment changes.
- * <p>
- * Don't hold on to or modify the returned list.
- */
- public ArrayList<Entry> getActiveNotifications() {
- return mSortedAndFiltered;
- }
-
- public ArrayList<Entry> getNotificationsForCurrentUser() {
- mFilteredForUser.clear();
-
- synchronized (mEntries) {
- final int N = mEntries.size();
- for (int i = 0; i < N; i++) {
- Entry entry = mEntries.valueAt(i);
- final StatusBarNotification sbn = entry.notification;
- if (!getEnvironment().isNotificationForCurrentProfiles(sbn)) {
- continue;
- }
- mFilteredForUser.add(entry);
- }
- }
- return mFilteredForUser;
- }
-
- public Entry get(String key) {
- return mEntries.get(key);
- }
-
- public void add(Entry entry) {
- synchronized (mEntries) {
- mEntries.put(entry.notification.getKey(), entry);
- }
- mGroupManager.onEntryAdded(entry);
-
- updateRankingAndSort(mRankingMap);
- }
-
- public Entry remove(String key, RankingMap ranking) {
- Entry removed;
- synchronized (mEntries) {
- removed = mEntries.remove(key);
- }
- if (removed == null) return null;
- mGroupManager.onEntryRemoved(removed);
- updateRankingAndSort(ranking);
- return removed;
- }
-
- /** Updates the given notification entry with the provided ranking. */
- public void update(Entry entry, RankingMap ranking, StatusBarNotification notification) {
- updateRanking(ranking);
- final StatusBarNotification oldNotification = entry.notification;
- entry.notification = notification;
- mGroupManager.onEntryUpdated(entry, oldNotification);
- }
-
- public void updateRanking(RankingMap ranking) {
- updateRankingAndSort(ranking);
- }
-
- public void updateAppOp(int appOp, int uid, String pkg, String key, boolean showIcon) {
- synchronized (mEntries) {
- final int N = mEntries.size();
- for (int i = 0; i < N; i++) {
- Entry entry = mEntries.valueAt(i);
- if (uid == entry.notification.getUid()
- && pkg.equals(entry.notification.getPackageName())
- && key.equals(entry.key)) {
- if (showIcon) {
- entry.mActiveAppOps.add(appOp);
- } else {
- entry.mActiveAppOps.remove(appOp);
- }
- }
- }
- }
- }
-
- /**
- * Returns true if this notification should be displayed in the high-priority notifications
- * section (and on the lockscreen and status bar).
- */
- public boolean isHighPriority(StatusBarNotification statusBarNotification) {
- if (mRankingMap != null) {
- getRanking(statusBarNotification.getKey(), mTmpRanking);
- if (mTmpRanking.getImportance() >= NotificationManager.IMPORTANCE_DEFAULT
- || statusBarNotification.getNotification().isForegroundService()
- || statusBarNotification.getNotification().hasMediaSession()) {
- return true;
- }
- if (mGroupManager.isSummaryOfGroup(statusBarNotification)) {
- for (Entry child : mGroupManager.getLogicalChildren(statusBarNotification)) {
- if (isHighPriority(child.notification)) {
- return true;
- }
- }
- }
- }
- return false;
- }
-
- public boolean isAmbient(String key) {
- if (mRankingMap != null) {
- getRanking(key, mTmpRanking);
- return mTmpRanking.isAmbient();
- }
- return false;
- }
-
- public int getVisibilityOverride(String key) {
- if (mRankingMap != null) {
- getRanking(key, mTmpRanking);
- return mTmpRanking.getVisibilityOverride();
- }
- return Ranking.VISIBILITY_NO_OVERRIDE;
- }
-
- /**
- * Categories that are explicitly called out on DND settings screens are always blocked, if
- * DND has flagged them, even if they are foreground or system notifications that might
- * otherwise visually bypass DND.
- */
- private static boolean isNotificationBlockedByPolicy(Notification n) {
- if (isCategory(CATEGORY_CALL, n)
- || isCategory(CATEGORY_MESSAGE, n)
- || isCategory(CATEGORY_ALARM, n)
- || isCategory(CATEGORY_EVENT, n)
- || isCategory(CATEGORY_REMINDER, n)) {
- return true;
- }
- return false;
- }
-
- private static boolean isCategory(String category, Notification n) {
- return Objects.equals(n.category, category);
- }
-
- public int getImportance(String key) {
- if (mRankingMap != null) {
- getRanking(key, mTmpRanking);
- return mTmpRanking.getImportance();
- }
- return NotificationManager.IMPORTANCE_UNSPECIFIED;
- }
-
- public String getOverrideGroupKey(String key) {
- if (mRankingMap != null) {
- getRanking(key, mTmpRanking);
- return mTmpRanking.getOverrideGroupKey();
- }
- return null;
- }
-
- public List<SnoozeCriterion> getSnoozeCriteria(String key) {
- if (mRankingMap != null) {
- getRanking(key, mTmpRanking);
- return mTmpRanking.getSnoozeCriteria();
- }
- return null;
- }
-
- public NotificationChannel getChannel(String key) {
- if (mRankingMap != null) {
- getRanking(key, mTmpRanking);
- return mTmpRanking.getChannel();
- }
- return null;
- }
-
- public int getRank(String key) {
- if (mRankingMap != null) {
- getRanking(key, mTmpRanking);
- return mTmpRanking.getRank();
- }
- return 0;
- }
-
- public boolean shouldHide(String key) {
- if (mRankingMap != null) {
- getRanking(key, mTmpRanking);
- return mTmpRanking.isSuspended();
- }
- return false;
- }
-
- private void updateRankingAndSort(RankingMap ranking) {
- if (ranking != null) {
- mRankingMap = ranking;
- synchronized (mEntries) {
- final int N = mEntries.size();
- for (int i = 0; i < N; i++) {
- Entry entry = mEntries.valueAt(i);
- if (!getRanking(entry.key, mTmpRanking)) {
- continue;
- }
- final StatusBarNotification oldSbn = entry.notification.cloneLight();
- final String overrideGroupKey = getOverrideGroupKey(entry.key);
- if (!Objects.equals(oldSbn.getOverrideGroupKey(), overrideGroupKey)) {
- entry.notification.setOverrideGroupKey(overrideGroupKey);
- mGroupManager.onEntryUpdated(entry, oldSbn);
- }
- entry.populateFromRanking(mTmpRanking);
- }
- }
- }
- filterAndSort();
- }
-
- /**
- * Get the ranking from the current ranking map.
- *
- * @param key the key to look up
- * @param outRanking the ranking to populate
- *
- * @return {@code true} if the ranking was properly obtained.
- */
- @VisibleForTesting
- protected boolean getRanking(String key, Ranking outRanking) {
- return mRankingMap.getRanking(key, outRanking);
- }
-
- // TODO: This should not be public. Instead the Environment should notify this class when
- // anything changed, and this class should call back the UI so it updates itself.
- public void filterAndSort() {
- mSortedAndFiltered.clear();
-
- synchronized (mEntries) {
- final int N = mEntries.size();
- for (int i = 0; i < N; i++) {
- Entry entry = mEntries.valueAt(i);
-
- if (mNotificationFilter.shouldFilterOut(entry)) {
- continue;
- }
-
- mSortedAndFiltered.add(entry);
- }
- }
-
- Collections.sort(mSortedAndFiltered, mRankingComparator);
- }
-
- public void dump(PrintWriter pw, String indent) {
- int N = mSortedAndFiltered.size();
- pw.print(indent);
- pw.println("active notifications: " + N);
- int active;
- for (active = 0; active < N; active++) {
- NotificationData.Entry e = mSortedAndFiltered.get(active);
- dumpEntry(pw, indent, active, e);
- }
- synchronized (mEntries) {
- int M = mEntries.size();
- pw.print(indent);
- pw.println("inactive notifications: " + (M - active));
- int inactiveCount = 0;
- for (int i = 0; i < M; i++) {
- Entry entry = mEntries.valueAt(i);
- if (!mSortedAndFiltered.contains(entry)) {
- dumpEntry(pw, indent, inactiveCount, entry);
- inactiveCount++;
- }
- }
- }
- }
-
- private void dumpEntry(PrintWriter pw, String indent, int i, Entry e) {
- getRanking(e.key, mTmpRanking);
- pw.print(indent);
- pw.println(" [" + i + "] key=" + e.key + " icon=" + e.icon);
- StatusBarNotification n = e.notification;
- pw.print(indent);
- pw.println(" pkg=" + n.getPackageName() + " id=" + n.getId() + " importance=" +
- mTmpRanking.getImportance());
- pw.print(indent);
- pw.println(" notification=" + n.getNotification());
- }
-
- private static boolean isSystemNotification(StatusBarNotification sbn) {
- String sbnPackage = sbn.getPackageName();
- return "android".equals(sbnPackage) || "com.android.systemui".equals(sbnPackage);
- }
-
- /**
- * Provides access to keyguard state and user settings dependent data.
- */
- public interface KeyguardEnvironment {
- boolean isDeviceProvisioned();
- boolean isNotificationForCurrentProfiles(StatusBarNotification sbn);
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryListener.java
index 1d06ce031f2b..839b06cec496 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryListener.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryListener.java
@@ -19,6 +19,7 @@ import android.annotation.Nullable;
import android.service.notification.StatusBarNotification;
import com.android.internal.statusbar.NotificationVisibility;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.row.NotificationInflater;
/**
@@ -29,25 +30,38 @@ public interface NotificationEntryListener {
* Called when a new notification is posted. At this point, the notification is "pending": its
* views haven't been inflated yet and most of the system pretends like it doesn't exist yet.
*/
- default void onPendingEntryAdded(NotificationData.Entry entry) {
+ default void onPendingEntryAdded(NotificationEntry entry) {
+ }
+
+ // TODO: Combine this with onPreEntryUpdated into "onBeforeEntryFiltered" or similar
+ /**
+ * Called when a new entry is created but before it has been filtered or displayed to the user.
+ */
+ default void onBeforeNotificationAdded(NotificationEntry entry) {
}
/**
* Called when a new entry is created.
*/
- default void onNotificationAdded(NotificationData.Entry entry) {
+ default void onNotificationAdded(NotificationEntry entry) {
+ }
+
+ /**
+ * Called when a notification is updated, before any filtering of notifications have occurred.
+ */
+ default void onPreEntryUpdated(NotificationEntry entry) {
}
/**
- * Called when a notification was updated.
+ * Called when a notification was updated, after any filtering of notifications have occurred.
*/
- default void onEntryUpdated(NotificationData.Entry entry) {
+ default void onPostEntryUpdated(NotificationEntry entry) {
}
/**
* Called when a notification's views are inflated for the first time.
*/
- default void onEntryInflated(NotificationData.Entry entry,
+ default void onEntryInflated(NotificationEntry entry,
@NotificationInflater.InflationFlag int inflatedFlags) {
}
@@ -57,7 +71,7 @@ public interface NotificationEntryListener {
*
* @param entry notification data entry that was reinflated.
*/
- default void onEntryReinflated(NotificationData.Entry entry) {
+ default void onEntryReinflated(NotificationEntry entry) {
}
/**
@@ -71,20 +85,14 @@ public interface NotificationEntryListener {
* because the developer retracted it).
* @param entry notification data entry that was removed. Null if no entry existed for the
* removed key at the time of removal.
- * @param key key of notification that was removed
- * @param old StatusBarNotification of the notification before it was removed
* @param visibility logging data related to the visibility of the notification at the time of
* removal, if it was removed by a user action. Null if it was not removed by
* a user action.
- * @param lifetimeExtended true if something is artificially extending how long the notification
* @param removedByUser true if the notification was removed by a user action
*/
default void onEntryRemoved(
- @Nullable NotificationData.Entry entry,
- String key,
- StatusBarNotification old,
+ NotificationEntry entry,
@Nullable NotificationVisibility visibility,
- boolean lifetimeExtended,
boolean removedByUser) {
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
index 2d316508c8cf..989e781ab5ba 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
@@ -15,17 +15,12 @@
*/
package com.android.systemui.statusbar.notification;
-import static com.android.systemui.bubbles.BubbleController.DEBUG_DEMOTE_TO_NOTIF;
-
import android.annotation.Nullable;
import android.app.Notification;
import android.content.Context;
-import android.os.Handler;
-import android.os.UserHandle;
import android.service.notification.NotificationListenerService;
import android.service.notification.StatusBarNotification;
import android.util.ArrayMap;
-import android.util.ArraySet;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
@@ -33,18 +28,17 @@ import com.android.internal.statusbar.NotificationVisibility;
import com.android.systemui.Dependency;
import com.android.systemui.Dumpable;
import com.android.systemui.ForegroundServiceController;
-import com.android.systemui.bubbles.BubbleController;
import com.android.systemui.statusbar.NotificationLifetimeExtender;
import com.android.systemui.statusbar.NotificationPresenter;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.NotificationUiAdjustment;
import com.android.systemui.statusbar.NotificationUpdateHandler;
-import com.android.systemui.statusbar.notification.NotificationData.KeyguardEnvironment;
-import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
+import com.android.systemui.statusbar.notification.collection.NotificationData;
+import com.android.systemui.statusbar.notification.collection.NotificationData.KeyguardEnvironment;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.row.NotificationInflater;
import com.android.systemui.statusbar.notification.row.NotificationInflater.InflationFlag;
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
-import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.util.leak.LeakDetector;
@@ -53,7 +47,6 @@ import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
-import java.util.concurrent.TimeUnit;
/**
* NotificationEntryManager is responsible for the adding, removing, and updating of notifications.
@@ -64,53 +57,31 @@ public class NotificationEntryManager implements
Dumpable,
NotificationInflater.InflationCallback,
NotificationUpdateHandler,
- VisualStabilityManager.Callback,
- BubbleController.BubbleDismissListener {
+ VisualStabilityManager.Callback {
private static final String TAG = "NotificationEntryMgr";
- protected static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
-
- public static final long RECENTLY_ALERTED_THRESHOLD_MS = TimeUnit.SECONDS.toMillis(30);
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
- protected final Context mContext;
- protected final HashMap<String, NotificationData.Entry> mPendingNotifications = new HashMap<>();
+ private final Context mContext;
+ @VisibleForTesting
+ protected final HashMap<String, NotificationEntry> mPendingNotifications = new HashMap<>();
- private final NotificationGutsManager mGutsManager =
- Dependency.get(NotificationGutsManager.class);
- private final DeviceProvisionedController mDeviceProvisionedController =
- Dependency.get(DeviceProvisionedController.class);
private final ForegroundServiceController mForegroundServiceController =
Dependency.get(ForegroundServiceController.class);
- private final BubbleController mBubbleController = Dependency.get(BubbleController.class);
// Lazily retrieved dependencies
private NotificationRemoteInputManager mRemoteInputManager;
private NotificationRowBinder mNotificationRowBinder;
- private final Handler mDeferredNotificationViewUpdateHandler;
- private Runnable mUpdateNotificationViewsCallback;
-
private NotificationPresenter mPresenter;
private NotificationListenerService.RankingMap mLatestRankingMap;
+ @VisibleForTesting
protected NotificationData mNotificationData;
- protected NotificationListContainer mListContainer;
+
@VisibleForTesting
final ArrayList<NotificationLifetimeExtender> mNotificationLifetimeExtenders
= new ArrayList<>();
private final List<NotificationEntryListener> mNotificationEntryListeners = new ArrayList<>();
- private final DeviceProvisionedController.DeviceProvisionedListener
- mDeviceProvisionedListener =
- new DeviceProvisionedController.DeviceProvisionedListener() {
- @Override
- public void onDeviceProvisionedChanged() {
- updateNotifications();
- }
- };
-
- public void destroy() {
- mDeviceProvisionedController.removeCallback(mDeviceProvisionedListener);
- }
-
@Override
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
pw.println("NotificationEntryManager state:");
@@ -118,7 +89,7 @@ public class NotificationEntryManager implements
if (mPendingNotifications.size() == 0) {
pw.println("null");
} else {
- for (NotificationData.Entry entry : mPendingNotifications.values()) {
+ for (NotificationEntry entry : mPendingNotifications.values()) {
pw.println(entry.notification);
}
}
@@ -126,9 +97,7 @@ public class NotificationEntryManager implements
public NotificationEntryManager(Context context) {
mContext = context;
- mBubbleController.setDismissListener(this /* bubbleEventListener */);
mNotificationData = new NotificationData();
- mDeferredNotificationViewUpdateHandler = new Handler();
}
/** Adds a {@link NotificationEntryListener}. */
@@ -153,15 +122,17 @@ public class NotificationEntryManager implements
return mNotificationRowBinder;
}
+ // TODO: Remove this once we can always use a mocked row binder in our tests
+ @VisibleForTesting
+ void setRowBinder(NotificationRowBinder notificationRowBinder) {
+ mNotificationRowBinder = notificationRowBinder;
+ }
+
public void setUpWithPresenter(NotificationPresenter presenter,
NotificationListContainer listContainer,
HeadsUpManager headsUpManager) {
mPresenter = presenter;
- mUpdateNotificationViewsCallback = mPresenter::updateNotificationViews;
mNotificationData.setHeadsUpManager(headsUpManager);
- mListContainer = listContainer;
-
- mDeviceProvisionedController.addCallback(mDeviceProvisionedListener);
}
/** Adds multiple {@link NotificationLifetimeExtender}s. */
@@ -181,14 +152,6 @@ public class NotificationEntryManager implements
return mNotificationData;
}
- protected Context getContext() {
- return mContext;
- }
-
- protected NotificationPresenter getPresenter() {
- return mPresenter;
- }
-
@Override
public void onReorderingAllowed() {
updateNotifications();
@@ -203,30 +166,13 @@ public class NotificationEntryManager implements
n.getKey(), null, nv, false /* forceRemove */, true /* removedByUser */);
}
- @Override
- public void onStackDismissed() {
- updateNotifications();
- }
-
- @Override
- public void onBubbleDismissed(String key) {
- NotificationData.Entry entry = mNotificationData.get(key);
- if (entry != null) {
- entry.setBubbleDismissed(true);
- if (!DEBUG_DEMOTE_TO_NOTIF) {
- performRemoveNotification(entry.notification);
- }
- }
- updateNotifications();
- }
-
private void abortExistingInflation(String key) {
if (mPendingNotifications.containsKey(key)) {
- NotificationData.Entry entry = mPendingNotifications.get(key);
+ NotificationEntry entry = mPendingNotifications.get(key);
entry.abortTask();
mPendingNotifications.remove(key);
}
- NotificationData.Entry addedEntry = mNotificationData.get(key);
+ NotificationEntry addedEntry = mNotificationData.get(key);
if (addedEntry != null) {
addedEntry.abortTask();
}
@@ -247,32 +193,8 @@ public class NotificationEntryManager implements
}
}
- private void addEntry(NotificationData.Entry shadeEntry) {
- if (shadeEntry == null) {
- return;
- }
- // Add the expanded view and icon.
- mNotificationData.add(shadeEntry);
- tagForeground(shadeEntry.notification);
- updateNotifications();
- for (NotificationEntryListener listener : mNotificationEntryListeners) {
- listener.onNotificationAdded(shadeEntry);
- }
-
- maybeScheduleUpdateNotificationViews(shadeEntry);
- }
-
- private void maybeScheduleUpdateNotificationViews(NotificationData.Entry entry) {
- long audibleAlertTimeout = RECENTLY_ALERTED_THRESHOLD_MS
- - (System.currentTimeMillis() - entry.lastAudiblyAlertedMs);
- if (audibleAlertTimeout > 0) {
- mDeferredNotificationViewUpdateHandler.postDelayed(
- mUpdateNotificationViewsCallback, audibleAlertTimeout);
- }
- }
-
@Override
- public void onAsyncInflationFinished(NotificationData.Entry entry,
+ public void onAsyncInflationFinished(NotificationEntry entry,
@InflationFlag int inflatedFlags) {
mPendingNotifications.remove(entry.key);
// If there was an async task started after the removal, we don't want to add it back to
@@ -283,7 +205,14 @@ public class NotificationEntryManager implements
for (NotificationEntryListener listener : mNotificationEntryListeners) {
listener.onEntryInflated(entry, inflatedFlags);
}
- addEntry(entry);
+ mNotificationData.add(entry);
+ for (NotificationEntryListener listener : mNotificationEntryListeners) {
+ listener.onBeforeNotificationAdded(entry);
+ }
+ updateNotifications();
+ for (NotificationEntryListener listener : mNotificationEntryListeners) {
+ listener.onNotificationAdded(entry);
+ }
} else {
for (NotificationEntryListener listener : mNotificationEntryListeners) {
listener.onEntryReinflated(entry);
@@ -305,11 +234,10 @@ public class NotificationEntryManager implements
@Nullable NotificationVisibility visibility,
boolean forceRemove,
boolean removedByUser) {
- final NotificationData.Entry entry = mNotificationData.get(key);
+ final NotificationEntry entry = mNotificationData.get(key);
abortExistingInflation(key);
- StatusBarNotification old = null;
boolean lifetimeExtended = false;
if (entry != null) {
@@ -336,31 +264,20 @@ public class NotificationEntryManager implements
if (entry.rowExists()) {
entry.removeRow();
- mListContainer.cleanUpViewStateForEntry(entry);
}
// Let's remove the children if this was a summary
handleGroupSummaryRemoved(key);
- old = removeNotificationViews(key, ranking);
- }
- }
+ mNotificationData.remove(key, ranking);
+ updateNotifications();
+ Dependency.get(LeakDetector.class).trackGarbage(entry);
- for (NotificationEntryListener listener : mNotificationEntryListeners) {
- listener.onEntryRemoved(entry, key, old, visibility, lifetimeExtended, removedByUser);
- }
- }
-
- private StatusBarNotification removeNotificationViews(String key,
- NotificationListenerService.RankingMap ranking) {
- NotificationData.Entry entry = mNotificationData.remove(key, ranking);
- if (entry == null) {
- Log.w(TAG, "removeNotification for unknown key: " + key);
- return null;
+ for (NotificationEntryListener listener : mNotificationEntryListeners) {
+ listener.onEntryRemoved(entry, visibility, removedByUser);
+ }
+ }
}
- updateNotifications();
- Dependency.get(LeakDetector.class).trackGarbage(entry);
- return entry.notification;
}
/**
@@ -374,19 +291,19 @@ public class NotificationEntryManager implements
*
*/
private void handleGroupSummaryRemoved(String key) {
- NotificationData.Entry entry = mNotificationData.get(key);
+ NotificationEntry entry = mNotificationData.get(key);
if (entry != null && entry.rowExists() && entry.isSummaryWithChildren()) {
if (entry.notification.getOverrideGroupKey() != null && !entry.isRowDismissed()) {
// We don't want to remove children for autobundled notifications as they are not
// always cancelled. We only remove them if they were dismissed by the user.
return;
}
- List<NotificationData.Entry> childEntries = entry.getChildren();
+ List<NotificationEntry> childEntries = entry.getChildren();
if (childEntries == null) {
return;
}
for (int i = 0; i < childEntries.size(); i++) {
- NotificationData.Entry childEntry = childEntries.get(i);
+ NotificationEntry childEntry = childEntries.get(i);
boolean isForeground = (entry.notification.getNotification().flags
& Notification.FLAG_FOREGROUND_SERVICE) != 0;
boolean keepForReply =
@@ -405,38 +322,6 @@ public class NotificationEntryManager implements
}
}
- public void updateNotificationsOnDensityOrFontScaleChanged() {
- ArrayList<NotificationData.Entry> userNotifications =
- mNotificationData.getNotificationsForCurrentUser();
- for (int i = 0; i < userNotifications.size(); i++) {
- NotificationData.Entry entry = userNotifications.get(i);
- entry.onDensityOrFontScaleChanged();
- boolean exposedGuts = entry.areGutsExposed();
- if (exposedGuts) {
- mGutsManager.onDensityOrFontScaleChanged(entry);
- }
- }
- }
-
- private NotificationData.Entry createNotificationEntry(
- StatusBarNotification sbn, NotificationListenerService.Ranking ranking)
- throws InflationException {
- if (DEBUG) {
- Log.d(TAG, "createNotificationEntry(notification=" + sbn + " " + ranking);
- }
-
- NotificationData.Entry entry = new NotificationData.Entry(sbn, ranking);
- if (BubbleController.shouldAutoBubble(getContext(), entry)) {
- entry.setIsBubble(true);
- }
-
- Dependency.get(LeakDetector.class).trackInstance(entry);
- // Construct the expanded view.
- getRowBinder().inflateViews(entry, () -> performRemoveNotification(sbn),
- mNotificationData.get(entry.key) != null);
- return entry;
- }
-
private void addNotificationInternal(StatusBarNotification notification,
NotificationListenerService.RankingMap rankingMap) throws InflationException {
String key = notification.getKey();
@@ -447,7 +332,14 @@ public class NotificationEntryManager implements
mNotificationData.updateRanking(rankingMap);
NotificationListenerService.Ranking ranking = new NotificationListenerService.Ranking();
rankingMap.getRanking(key, ranking);
- NotificationData.Entry entry = createNotificationEntry(notification, ranking);
+
+ NotificationEntry entry = new NotificationEntry(notification, ranking);
+
+ Dependency.get(LeakDetector.class).trackInstance(entry);
+ // Construct the expanded view.
+ getRowBinder().inflateViews(entry, () -> performRemoveNotification(notification),
+ mNotificationData.get(entry.key) != null);
+
abortExistingInflation(key);
mPendingNotifications.put(key, entry);
@@ -456,19 +348,6 @@ public class NotificationEntryManager implements
}
}
- @VisibleForTesting
- void tagForeground(StatusBarNotification notification) {
- ArraySet<Integer> activeOps = mForegroundServiceController.getAppOps(
- notification.getUserId(), notification.getPackageName());
- if (activeOps != null) {
- int N = activeOps.size();
- for (int i = 0; i < N; i++) {
- updateNotificationsForAppOp(activeOps.valueAt(i), notification.getUid(),
- notification.getPackageName(), true);
- }
- }
- }
-
@Override
public void addNotification(StatusBarNotification notification,
NotificationListenerService.RankingMap ranking) {
@@ -479,22 +358,13 @@ public class NotificationEntryManager implements
}
}
- public void updateNotificationsForAppOp(int appOp, int uid, String pkg, boolean showIcon) {
- String foregroundKey = mForegroundServiceController.getStandardLayoutKey(
- UserHandle.getUserId(uid), pkg);
- if (foregroundKey != null) {
- mNotificationData.updateAppOp(appOp, uid, pkg, foregroundKey, showIcon);
- updateNotifications();
- }
- }
-
private void updateNotificationInternal(StatusBarNotification notification,
NotificationListenerService.RankingMap ranking) throws InflationException {
if (DEBUG) Log.d(TAG, "updateNotification(" + notification + ")");
final String key = notification.getKey();
abortExistingInflation(key);
- NotificationData.Entry entry = mNotificationData.get(key);
+ NotificationEntry entry = mNotificationData.get(key);
if (entry == null) {
return;
}
@@ -510,14 +380,12 @@ public class NotificationEntryManager implements
getRowBinder().inflateViews(entry, () -> performRemoveNotification(notification),
mNotificationData.get(entry.key) != null);
- updateNotifications();
-
- if (!notification.isClearable()) {
- // The user may have performed a dismiss action on the notification, since it's
- // not clearable we should snap it back.
- mListContainer.snapViewIfNeeded(entry);
+ for (NotificationEntryListener listener : mNotificationEntryListeners) {
+ listener.onPreEntryUpdated(entry);
}
+ updateNotifications();
+
if (DEBUG) {
// Is this for you?
boolean isForCurrentUser = Dependency.get(KeyguardEnvironment.class)
@@ -526,10 +394,8 @@ public class NotificationEntryManager implements
}
for (NotificationEntryListener listener : mNotificationEntryListeners) {
- listener.onEntryUpdated(entry);
+ listener.onPostEntryUpdated(entry);
}
-
- maybeScheduleUpdateNotificationViews(entry);
}
@Override
@@ -544,19 +410,20 @@ public class NotificationEntryManager implements
public void updateNotifications() {
mNotificationData.filterAndSort();
-
- mPresenter.updateNotificationViews();
+ if (mPresenter != null) {
+ mPresenter.updateNotificationViews();
+ }
}
public void updateNotificationRanking(NotificationListenerService.RankingMap rankingMap) {
- List<NotificationData.Entry> entries = new ArrayList<>();
+ List<NotificationEntry> entries = new ArrayList<>();
entries.addAll(mNotificationData.getActiveNotifications());
entries.addAll(mPendingNotifications.values());
// Has a copy of the current UI adjustments.
ArrayMap<String, NotificationUiAdjustment> oldAdjustments = new ArrayMap<>();
ArrayMap<String, Integer> oldImportances = new ArrayMap<>();
- for (NotificationData.Entry entry : entries) {
+ for (NotificationEntry entry : entries) {
NotificationUiAdjustment adjustment =
NotificationUiAdjustment.extractFromNotificationEntry(entry);
oldAdjustments.put(entry.key, adjustment);
@@ -568,7 +435,7 @@ public class NotificationEntryManager implements
updateRankingOfPendingNotifications(rankingMap);
// By comparing the old and new UI adjustments, reinflate the view accordingly.
- for (NotificationData.Entry entry : entries) {
+ for (NotificationEntry entry : entries) {
getRowBinder().onNotificationRankingUpdated(
entry,
oldImportances.get(entry.key),
@@ -586,7 +453,7 @@ public class NotificationEntryManager implements
return;
}
NotificationListenerService.Ranking tmpRanking = new NotificationListenerService.Ranking();
- for (NotificationData.Entry pendingNotification : mPendingNotifications.values()) {
+ for (NotificationEntry pendingNotification : mPendingNotifications.values()) {
rankingMap.getRanking(pendingNotification.key, tmpRanking);
pendingNotification.populateFromRanking(tmpRanking);
}
@@ -597,7 +464,7 @@ public class NotificationEntryManager implements
* notifications whose views have not yet been inflated. In general, the system pretends like
* these don't exist, although there are a couple exceptions.
*/
- public Iterable<NotificationData.Entry> getPendingNotificationsIterator() {
+ public Iterable<NotificationEntry> getPendingNotificationsIterator() {
return mPendingNotifications.values();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationFilter.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationFilter.java
index 700382a103b2..154d7b356cd1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationFilter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationFilter.java
@@ -28,6 +28,8 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.Dependency;
import com.android.systemui.ForegroundServiceController;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
+import com.android.systemui.statusbar.notification.collection.NotificationData;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.phone.NotificationGroupManager;
import com.android.systemui.statusbar.phone.ShadeController;
import com.android.systemui.statusbar.phone.StatusBar;
@@ -82,7 +84,7 @@ public class NotificationFilter {
/**
* @return true if the provided notification should NOT be shown right now.
*/
- public boolean shouldFilterOut(NotificationData.Entry entry) {
+ public boolean shouldFilterOut(NotificationEntry entry) {
final StatusBarNotification sbn = entry.notification;
if (!(getEnvironment().isDeviceProvisioned()
|| showNotificationEvenIfUnprovisioned(sbn))) {
@@ -132,6 +134,10 @@ public class NotificationFilter {
}
}
+ if (entry.isBubble() && !entry.showInShadeWhenBubble()) {
+ return true;
+ }
+
return false;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationInterruptionStateProvider.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationInterruptionStateProvider.java
index 8bd0e9ad3c8b..c50f10b55a71 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationInterruptionStateProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationInterruptionStateProvider.java
@@ -37,6 +37,7 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.Dependency;
import com.android.systemui.statusbar.NotificationPresenter;
import com.android.systemui.statusbar.StatusBarStateController;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.phone.ShadeController;
import com.android.systemui.statusbar.policy.HeadsUpManager;
@@ -134,12 +135,35 @@ public class NotificationInterruptionStateProvider {
}
/**
+ * Whether the notification should appear as a bubble with a fly-out on top of the screen.
+ *
+ * @param entry the entry to check
+ * @return true if the entry should bubble up, false otherwise
+ */
+ public boolean shouldBubbleUp(NotificationEntry entry) {
+ StatusBarNotification sbn = entry.notification;
+ if (!entry.isBubble()) {
+ if (DEBUG) {
+ Log.d(TAG, "No bubble up: notification " + sbn.getKey()
+ + " is bubble? " + entry.isBubble());
+ }
+ return false;
+ }
+
+ if (!canHeadsUpCommon(entry)) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
* Whether the notification should peek in from the top and alert the user.
*
* @param entry the entry to check
* @return true if the entry should heads up, false otherwise
*/
- public boolean shouldHeadsUp(NotificationData.Entry entry) {
+ public boolean shouldHeadsUp(NotificationEntry entry) {
StatusBarNotification sbn = entry.notification;
if (getShadeController().isDozing()) {
@@ -149,10 +173,12 @@ public class NotificationInterruptionStateProvider {
return false;
}
- // TODO: need to changes this, e.g. should still heads up in expanded shade, might want
- // message bubble from the bubble to go through heads up path
boolean inShade = mStatusBarStateController.getState() == SHADE;
- if (entry.isBubble() && !entry.isBubbleDismissed() && inShade) {
+ if (entry.isBubble() && inShade) {
+ if (DEBUG) {
+ Log.d(TAG, "No heads up: in unlocked shade where notification is shown as a "
+ + "bubble: " + sbn.getKey());
+ }
return false;
}
@@ -163,9 +189,13 @@ public class NotificationInterruptionStateProvider {
return false;
}
- if (!mUseHeadsUp || mPresenter.isDeviceInVrMode()) {
+ if (!canHeadsUpCommon(entry)) {
+ return false;
+ }
+
+ if (entry.importance < NotificationManager.IMPORTANCE_HIGH) {
if (DEBUG) {
- Log.d(TAG, "No heads up: no huns or vr mode");
+ Log.d(TAG, "No heads up: unimportant notification: " + sbn.getKey());
}
return false;
}
@@ -185,34 +215,6 @@ public class NotificationInterruptionStateProvider {
return false;
}
- if (entry.shouldSuppressPeek()) {
- if (DEBUG) {
- Log.d(TAG, "No heads up: suppressed by DND: " + sbn.getKey());
- }
- return false;
- }
-
- if (isSnoozedPackage(sbn)) {
- if (DEBUG) {
- Log.d(TAG, "No heads up: snoozed package: " + sbn.getKey());
- }
- return false;
- }
-
- if (entry.hasJustLaunchedFullScreenIntent()) {
- if (DEBUG) {
- Log.d(TAG, "No heads up: recent fullscreen: " + sbn.getKey());
- }
- return false;
- }
-
- if (entry.importance < NotificationManager.IMPORTANCE_HIGH) {
- if (DEBUG) {
- Log.d(TAG, "No heads up: unimportant notification: " + sbn.getKey());
- }
- return false;
- }
-
if (!mHeadsUpSuppressor.canHeadsUp(entry, sbn)) {
return false;
}
@@ -227,7 +229,7 @@ public class NotificationInterruptionStateProvider {
* @param entry the entry to check
* @return true if the entry should ambient pulse, false otherwise
*/
- public boolean shouldPulse(NotificationData.Entry entry) {
+ public boolean shouldPulse(NotificationEntry entry) {
StatusBarNotification sbn = entry.notification;
if (!getShadeController().isDozing()) {
@@ -273,14 +275,14 @@ public class NotificationInterruptionStateProvider {
/**
* Common checks between heads up alerting and ambient pulse alerting. See
- * {@link #shouldHeadsUp(NotificationData.Entry)} and
- * {@link #shouldPulse(NotificationData.Entry)}. Notifications that fail any of these checks
+ * {@link #shouldHeadsUp(NotificationEntry)} and
+ * {@link #shouldPulse(NotificationEntry)}. Notifications that fail any of these checks
* should not alert at all.
*
* @param entry the entry to check
* @return true if these checks pass, false if the notification should not alert
*/
- protected boolean canAlertCommon(NotificationData.Entry entry) {
+ protected boolean canAlertCommon(NotificationEntry entry) {
StatusBarNotification sbn = entry.notification;
if (mNotificationFilter.shouldFilterOut(entry)) {
@@ -301,6 +303,49 @@ public class NotificationInterruptionStateProvider {
return true;
}
+ /**
+ * Common checks between heads up alerting and bubble fly out alerting. See
+ * {@link #shouldHeadsUp(NotificationEntry)} and
+ * {@link #shouldBubbleUp(NotificationEntry)}. Notifications that fail any of these
+ * checks should not interrupt the user on screen.
+ *
+ * @param entry the entry to check
+ * @return true if these checks pass, false if the notification should not interrupt on screen
+ */
+ public boolean canHeadsUpCommon(NotificationEntry entry) {
+ StatusBarNotification sbn = entry.notification;
+
+ if (!mUseHeadsUp || mPresenter.isDeviceInVrMode()) {
+ if (DEBUG) {
+ Log.d(TAG, "No heads up: no huns or vr mode");
+ }
+ return false;
+ }
+
+ if (entry.shouldSuppressPeek()) {
+ if (DEBUG) {
+ Log.d(TAG, "No heads up: suppressed by DND: " + sbn.getKey());
+ }
+ return false;
+ }
+
+ if (isSnoozedPackage(sbn)) {
+ if (DEBUG) {
+ Log.d(TAG, "No heads up: snoozed package: " + sbn.getKey());
+ }
+ return false;
+ }
+
+ if (entry.hasJustLaunchedFullScreenIntent()) {
+ if (DEBUG) {
+ Log.d(TAG, "No heads up: recent fullscreen: " + sbn.getKey());
+ }
+ return false;
+ }
+
+ return true;
+ }
+
private boolean isSnoozedPackage(StatusBarNotification sbn) {
return mHeadsUpManager.isSnoozed(sbn.getPackageName());
}
@@ -325,7 +370,7 @@ public class NotificationInterruptionStateProvider {
* @param sbn notification that might be heads upped
* @return false if the notification can not be heads upped
*/
- boolean canHeadsUp(NotificationData.Entry entry, StatusBarNotification sbn);
+ boolean canHeadsUp(NotificationEntry entry, StatusBarNotification sbn);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationListController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationListController.java
new file mode 100644
index 000000000000..88f4ca239af4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationListController.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification;
+
+import static com.android.internal.util.Preconditions.checkNotNull;
+
+import android.os.UserHandle;
+import android.service.notification.StatusBarNotification;
+import android.util.ArraySet;
+
+import com.android.internal.statusbar.NotificationVisibility;
+import com.android.systemui.ForegroundServiceController;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
+import com.android.systemui.statusbar.policy.DeviceProvisionedController;
+import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener;
+
+/**
+ * Root controller for the list of notifications in the shade.
+ *
+ * TODO: Much of the code in NotificationPresenter should eventually move in here. It will proxy
+ * domain-specific behavior (ARC, etc) to subcontrollers.
+ */
+public class NotificationListController {
+ private final NotificationEntryManager mEntryManager;
+ private final NotificationListContainer mListContainer;
+ private final ForegroundServiceController mForegroundServiceController;
+ private final DeviceProvisionedController mDeviceProvisionedController;
+
+ public NotificationListController(
+ NotificationEntryManager entryManager,
+ NotificationListContainer listContainer,
+ ForegroundServiceController foregroundServiceController,
+ DeviceProvisionedController deviceProvisionedController) {
+ mEntryManager = checkNotNull(entryManager);
+ mListContainer = checkNotNull(listContainer);
+ mForegroundServiceController = checkNotNull(foregroundServiceController);
+ mDeviceProvisionedController = checkNotNull(deviceProvisionedController);
+ }
+
+ /**
+ * Causes the controller to register listeners on its dependencies. This method must be called
+ * before the controller is ready to perform its duties.
+ */
+ public void bind() {
+ mEntryManager.addNotificationEntryListener(mEntryListener);
+ mDeviceProvisionedController.addCallback(mDeviceProvisionedListener);
+ }
+
+ /** Should be called when the list controller is being destroyed. */
+ public void destroy() {
+ mDeviceProvisionedController.removeCallback(mDeviceProvisionedListener);
+ }
+
+ @SuppressWarnings("FieldCanBeLocal")
+ private final NotificationEntryListener mEntryListener = new NotificationEntryListener() {
+ @Override
+ public void onEntryRemoved(
+ NotificationEntry entry,
+ NotificationVisibility visibility,
+ boolean removedByUser) {
+ mListContainer.cleanUpViewStateForEntry(entry);
+ }
+
+ @Override
+ public void onBeforeNotificationAdded(NotificationEntry entry) {
+ tagForeground(entry.notification);
+ }
+ };
+
+ private final DeviceProvisionedListener mDeviceProvisionedListener =
+ new DeviceProvisionedListener() {
+ @Override
+ public void onDeviceProvisionedChanged() {
+ mEntryManager.updateNotifications();
+ }
+ };
+
+ // TODO: This method is horrifically inefficient
+ private void tagForeground(StatusBarNotification notification) {
+ ArraySet<Integer> activeOps =
+ mForegroundServiceController.getAppOps(
+ notification.getUserId(), notification.getPackageName());
+ if (activeOps != null) {
+ int len = activeOps.size();
+ for (int i = 0; i < len; i++) {
+ updateNotificationsForAppOp(activeOps.valueAt(i), notification.getUid(),
+ notification.getPackageName(), true);
+ }
+ }
+ }
+
+ /** When an app op changes, propagate that change to notifications. */
+ public void updateNotificationsForAppOp(int appOp, int uid, String pkg, boolean showIcon) {
+ String foregroundKey =
+ mForegroundServiceController.getStandardLayoutKey(UserHandle.getUserId(uid), pkg);
+ if (foregroundKey != null) {
+ mEntryManager
+ .getNotificationData().updateAppOp(appOp, uid, pkg, foregroundKey, showIcon);
+ mEntryManager.updateNotifications();
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationRowBinder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationRowBinder.java
index 058efca51b21..0b8596f9cba7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationRowBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationRowBinder.java
@@ -27,13 +27,10 @@ import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.os.Build;
-import android.os.RemoteException;
-import android.os.ServiceManager;
import android.service.notification.StatusBarNotification;
import android.util.Log;
import android.view.ViewGroup;
-import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.util.NotificationMessagingUtil;
import com.android.systemui.Dependency;
import com.android.systemui.R;
@@ -42,6 +39,8 @@ import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationPresenter;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.NotificationUiAdjustment;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.logging.NotificationLogger;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
import com.android.systemui.statusbar.notification.row.NotificationInflater;
@@ -70,7 +69,6 @@ public class NotificationRowBinder {
Dependency.get(NotificationInterruptionStateProvider.class);
private final Context mContext;
- private final IStatusBarService mBarService;
private final NotificationMessagingUtil mMessagingUtil;
private final ExpandableNotificationRow.ExpansionLogger mExpansionLogger =
this::logNotificationExpansion;
@@ -84,6 +82,7 @@ public class NotificationRowBinder {
private ExpandableNotificationRow.OnAppOpsClickListener mOnAppOpsClickListener;
private BindRowCallback mBindRowCallback;
private NotificationClicker mNotificationClicker;
+ private final NotificationLogger mNotificationLogger = Dependency.get(NotificationLogger.class);
@Inject
public NotificationRowBinder(Context context,
@@ -91,8 +90,6 @@ public class NotificationRowBinder {
mContext = context;
mMessagingUtil = new NotificationMessagingUtil(context);
mAllowLongPress = allowLongPress;
- mBarService = IStatusBarService.Stub.asInterface(
- ServiceManager.getService(Context.STATUS_BAR_SERVICE));
}
private NotificationRemoteInputManager getRemoteInputManager() {
@@ -126,7 +123,7 @@ public class NotificationRowBinder {
* Inflates the views for the given entry (possibly asynchronously).
*/
public void inflateViews(
- NotificationData.Entry entry,
+ NotificationEntry entry,
Runnable onDismissRunnable,
boolean isUpdate)
throws InflationException {
@@ -149,7 +146,7 @@ public class NotificationRowBinder {
}
}
- private void bindRow(NotificationData.Entry entry, PackageManager pmUser,
+ private void bindRow(NotificationEntry entry, PackageManager pmUser,
StatusBarNotification sbn, ExpandableNotificationRow row,
Runnable onDismissRunnable) {
row.setExpansionLogger(mExpansionLogger, entry.notification.getKey());
@@ -197,7 +194,7 @@ public class NotificationRowBinder {
* reinflating them.
*/
public void onNotificationRankingUpdated(
- NotificationData.Entry entry,
+ NotificationEntry entry,
@Nullable Integer oldImportance,
NotificationUiAdjustment oldAdjustment,
NotificationUiAdjustment newAdjustment,
@@ -224,7 +221,7 @@ public class NotificationRowBinder {
//TODO: This method associates a row with an entry, but eventually needs to not do that
private void updateNotification(
- NotificationData.Entry entry,
+ NotificationEntry entry,
PackageManager pmUser,
StatusBarNotification sbn,
ExpandableNotificationRow row,
@@ -273,13 +270,7 @@ public class NotificationRowBinder {
}
private void logNotificationExpansion(String key, boolean userAction, boolean expanded) {
- mUiOffloadThread.submit(() -> {
- try {
- mBarService.onNotificationExpansionChanged(key, userAction, expanded);
- } catch (RemoteException e) {
- // Ignore.
- }
- });
+ mNotificationLogger.onExpansionChanged(key, userAction, expanded);
}
/** Callback for when a row is bound to an entry. */
@@ -292,7 +283,7 @@ public class NotificationRowBinder {
* @param sbn notification
* @param row row for the notification
*/
- void onBindRow(NotificationData.Entry entry, PackageManager pmUser,
+ void onBindRow(NotificationEntry entry, PackageManager pmUser,
StatusBarNotification sbn, ExpandableNotificationRow row);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/VisibilityLocationProvider.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/VisibilityLocationProvider.java
index a194eef39b6d..f09c57d786ef 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/VisibilityLocationProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/VisibilityLocationProvider.java
@@ -16,6 +16,8 @@
package com.android.systemui.statusbar.notification;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+
/**
* An object that can determine the visibility of a Notification.
*/
@@ -27,5 +29,5 @@ public interface VisibilityLocationProvider {
* @param entry
* @return true if row is in a visible location
*/
- boolean isInVisibleLocation(NotificationData.Entry entry);
+ boolean isInVisibleLocation(NotificationEntry entry);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/VisualStabilityManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/VisualStabilityManager.java
index 8e6a93deec69..c886685424f7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/VisualStabilityManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/VisualStabilityManager.java
@@ -21,6 +21,7 @@ import android.view.View;
import androidx.collection.ArraySet;
import com.android.systemui.statusbar.NotificationPresenter;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
@@ -52,7 +53,7 @@ public class VisualStabilityManager implements OnHeadsUpChangedListener {
public VisualStabilityManager(NotificationEntryManager notificationEntryManager) {
notificationEntryManager.addNotificationEntryListener(new NotificationEntryListener() {
@Override
- public void onEntryReinflated(NotificationData.Entry entry) {
+ public void onEntryReinflated(NotificationEntry entry) {
if (entry.hasLowPriorityStateUpdated()) {
onLowPriorityUpdated(entry);
if (mPresenter != null) {
@@ -163,7 +164,7 @@ public class VisualStabilityManager implements OnHeadsUpChangedListener {
}
@Override
- public void onHeadsUpStateChanged(NotificationData.Entry entry, boolean isHeadsUp) {
+ public void onHeadsUpStateChanged(NotificationEntry entry, boolean isHeadsUp) {
if (isHeadsUp) {
// Heads up notifications should in general be allowed to reorder if they are out of
// view and stay at the current location if they aren't.
@@ -171,7 +172,7 @@ public class VisualStabilityManager implements OnHeadsUpChangedListener {
}
}
- private void onLowPriorityUpdated(NotificationData.Entry entry) {
+ private void onLowPriorityUpdated(NotificationEntry entry) {
mLowPriorityReorderingViews.add(entry.getRow());
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationData.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationData.java
new file mode 100644
index 000000000000..8c29fb50f00a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationData.java
@@ -0,0 +1,424 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.collection;
+
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.service.notification.NotificationListenerService.Ranking;
+import android.service.notification.NotificationListenerService.RankingMap;
+import android.service.notification.SnoozeCriterion;
+import android.service.notification.StatusBarNotification;
+import android.util.ArrayMap;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.Dependency;
+import com.android.systemui.statusbar.NotificationMediaManager;
+import com.android.systemui.statusbar.notification.NotificationFilter;
+import com.android.systemui.statusbar.phone.NotificationGroupManager;
+import com.android.systemui.statusbar.policy.HeadsUpManager;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * The list of currently displaying notifications.
+ */
+public class NotificationData {
+
+ private final NotificationFilter mNotificationFilter = Dependency.get(NotificationFilter.class);
+
+ /**
+ * These dependencies are late init-ed
+ */
+ private KeyguardEnvironment mEnvironment;
+ private NotificationMediaManager mMediaManager;
+
+ private HeadsUpManager mHeadsUpManager;
+
+ private final ArrayMap<String, NotificationEntry> mEntries = new ArrayMap<>();
+ private final ArrayList<NotificationEntry> mSortedAndFiltered = new ArrayList<>();
+ private final ArrayList<NotificationEntry> mFilteredForUser = new ArrayList<>();
+
+ private final NotificationGroupManager mGroupManager =
+ Dependency.get(NotificationGroupManager.class);
+
+ private RankingMap mRankingMap;
+ private final Ranking mTmpRanking = new Ranking();
+
+ public void setHeadsUpManager(HeadsUpManager headsUpManager) {
+ mHeadsUpManager = headsUpManager;
+ }
+
+ private final Comparator<NotificationEntry> mRankingComparator =
+ new Comparator<NotificationEntry>() {
+ private final Ranking mRankingA = new Ranking();
+ private final Ranking mRankingB = new Ranking();
+
+ @Override
+ public int compare(NotificationEntry a, NotificationEntry b) {
+ final StatusBarNotification na = a.notification;
+ final StatusBarNotification nb = b.notification;
+ int aImportance = NotificationManager.IMPORTANCE_DEFAULT;
+ int bImportance = NotificationManager.IMPORTANCE_DEFAULT;
+ int aRank = 0;
+ int bRank = 0;
+
+ if (mRankingMap != null) {
+ // RankingMap as received from NoMan
+ getRanking(a.key, mRankingA);
+ getRanking(b.key, mRankingB);
+ aImportance = mRankingA.getImportance();
+ bImportance = mRankingB.getImportance();
+ aRank = mRankingA.getRank();
+ bRank = mRankingB.getRank();
+ }
+
+ String mediaNotification = getMediaManager().getMediaNotificationKey();
+
+ // IMPORTANCE_MIN media streams are allowed to drift to the bottom
+ final boolean aMedia = a.key.equals(mediaNotification)
+ && aImportance > NotificationManager.IMPORTANCE_MIN;
+ final boolean bMedia = b.key.equals(mediaNotification)
+ && bImportance > NotificationManager.IMPORTANCE_MIN;
+
+ boolean aSystemMax = aImportance >= NotificationManager.IMPORTANCE_HIGH
+ && isSystemNotification(na);
+ boolean bSystemMax = bImportance >= NotificationManager.IMPORTANCE_HIGH
+ && isSystemNotification(nb);
+
+ boolean isHeadsUp = a.getRow().isHeadsUp();
+ if (isHeadsUp != b.getRow().isHeadsUp()) {
+ return isHeadsUp ? -1 : 1;
+ } else if (isHeadsUp) {
+ // Provide consistent ranking with headsUpManager
+ return mHeadsUpManager.compare(a, b);
+ } else if (a.getRow().isAmbientPulsing() != b.getRow().isAmbientPulsing()) {
+ return a.getRow().isAmbientPulsing() ? -1 : 1;
+ } else if (aMedia != bMedia) {
+ // Upsort current media notification.
+ return aMedia ? -1 : 1;
+ } else if (aSystemMax != bSystemMax) {
+ // Upsort PRIORITY_MAX system notifications
+ return aSystemMax ? -1 : 1;
+ } else if (aRank != bRank) {
+ return aRank - bRank;
+ } else {
+ return Long.compare(nb.getNotification().when, na.getNotification().when);
+ }
+ }
+ };
+
+ private KeyguardEnvironment getEnvironment() {
+ if (mEnvironment == null) {
+ mEnvironment = Dependency.get(KeyguardEnvironment.class);
+ }
+ return mEnvironment;
+ }
+
+ private NotificationMediaManager getMediaManager() {
+ if (mMediaManager == null) {
+ mMediaManager = Dependency.get(NotificationMediaManager.class);
+ }
+ return mMediaManager;
+ }
+
+ /**
+ * Returns the sorted list of active notifications (depending on {@link KeyguardEnvironment}
+ *
+ * <p>
+ * This call doesn't update the list of active notifications. Call {@link #filterAndSort()}
+ * when the environment changes.
+ * <p>
+ * Don't hold on to or modify the returned list.
+ */
+ public ArrayList<NotificationEntry> getActiveNotifications() {
+ return mSortedAndFiltered;
+ }
+
+ public ArrayList<NotificationEntry> getNotificationsForCurrentUser() {
+ mFilteredForUser.clear();
+
+ synchronized (mEntries) {
+ final int len = mEntries.size();
+ for (int i = 0; i < len; i++) {
+ NotificationEntry entry = mEntries.valueAt(i);
+ final StatusBarNotification sbn = entry.notification;
+ if (!getEnvironment().isNotificationForCurrentProfiles(sbn)) {
+ continue;
+ }
+ mFilteredForUser.add(entry);
+ }
+ }
+ return mFilteredForUser;
+ }
+
+ public NotificationEntry get(String key) {
+ return mEntries.get(key);
+ }
+
+ public void add(NotificationEntry entry) {
+ synchronized (mEntries) {
+ mEntries.put(entry.notification.getKey(), entry);
+ }
+ mGroupManager.onEntryAdded(entry);
+
+ updateRankingAndSort(mRankingMap);
+ }
+
+ public NotificationEntry remove(String key, RankingMap ranking) {
+ NotificationEntry removed;
+ synchronized (mEntries) {
+ removed = mEntries.remove(key);
+ }
+ if (removed == null) return null;
+ mGroupManager.onEntryRemoved(removed);
+ updateRankingAndSort(ranking);
+ return removed;
+ }
+
+ /** Updates the given notification entry with the provided ranking. */
+ public void update(
+ NotificationEntry entry,
+ RankingMap ranking,
+ StatusBarNotification notification) {
+ updateRanking(ranking);
+ final StatusBarNotification oldNotification = entry.notification;
+ entry.notification = notification;
+ mGroupManager.onEntryUpdated(entry, oldNotification);
+ }
+
+ public void updateRanking(RankingMap ranking) {
+ updateRankingAndSort(ranking);
+ }
+
+ public void updateAppOp(int appOp, int uid, String pkg, String key, boolean showIcon) {
+ synchronized (mEntries) {
+ final int len = mEntries.size();
+ for (int i = 0; i < len; i++) {
+ NotificationEntry entry = mEntries.valueAt(i);
+ if (uid == entry.notification.getUid()
+ && pkg.equals(entry.notification.getPackageName())
+ && key.equals(entry.key)) {
+ if (showIcon) {
+ entry.mActiveAppOps.add(appOp);
+ } else {
+ entry.mActiveAppOps.remove(appOp);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Returns true if this notification should be displayed in the high-priority notifications
+ * section (and on the lockscreen and status bar).
+ */
+ public boolean isHighPriority(StatusBarNotification statusBarNotification) {
+ if (mRankingMap != null) {
+ getRanking(statusBarNotification.getKey(), mTmpRanking);
+ if (mTmpRanking.getImportance() >= NotificationManager.IMPORTANCE_DEFAULT
+ || statusBarNotification.getNotification().isForegroundService()
+ || statusBarNotification.getNotification().hasMediaSession()) {
+ return true;
+ }
+ if (mGroupManager.isSummaryOfGroup(statusBarNotification)) {
+ final ArrayList<NotificationEntry> logicalChildren =
+ mGroupManager.getLogicalChildren(statusBarNotification);
+ for (NotificationEntry child : logicalChildren) {
+ if (isHighPriority(child.notification)) {
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+ public boolean isAmbient(String key) {
+ if (mRankingMap != null) {
+ getRanking(key, mTmpRanking);
+ return mTmpRanking.isAmbient();
+ }
+ return false;
+ }
+
+ public int getVisibilityOverride(String key) {
+ if (mRankingMap != null) {
+ getRanking(key, mTmpRanking);
+ return mTmpRanking.getVisibilityOverride();
+ }
+ return Ranking.VISIBILITY_NO_OVERRIDE;
+ }
+
+ public int getImportance(String key) {
+ if (mRankingMap != null) {
+ getRanking(key, mTmpRanking);
+ return mTmpRanking.getImportance();
+ }
+ return NotificationManager.IMPORTANCE_UNSPECIFIED;
+ }
+
+ public String getOverrideGroupKey(String key) {
+ if (mRankingMap != null) {
+ getRanking(key, mTmpRanking);
+ return mTmpRanking.getOverrideGroupKey();
+ }
+ return null;
+ }
+
+ public List<SnoozeCriterion> getSnoozeCriteria(String key) {
+ if (mRankingMap != null) {
+ getRanking(key, mTmpRanking);
+ return mTmpRanking.getSnoozeCriteria();
+ }
+ return null;
+ }
+
+ public NotificationChannel getChannel(String key) {
+ if (mRankingMap != null) {
+ getRanking(key, mTmpRanking);
+ return mTmpRanking.getChannel();
+ }
+ return null;
+ }
+
+ public int getRank(String key) {
+ if (mRankingMap != null) {
+ getRanking(key, mTmpRanking);
+ return mTmpRanking.getRank();
+ }
+ return 0;
+ }
+
+ public boolean shouldHide(String key) {
+ if (mRankingMap != null) {
+ getRanking(key, mTmpRanking);
+ return mTmpRanking.isSuspended();
+ }
+ return false;
+ }
+
+ private void updateRankingAndSort(RankingMap ranking) {
+ if (ranking != null) {
+ mRankingMap = ranking;
+ synchronized (mEntries) {
+ final int len = mEntries.size();
+ for (int i = 0; i < len; i++) {
+ NotificationEntry entry = mEntries.valueAt(i);
+ if (!getRanking(entry.key, mTmpRanking)) {
+ continue;
+ }
+ final StatusBarNotification oldSbn = entry.notification.cloneLight();
+ final String overrideGroupKey = getOverrideGroupKey(entry.key);
+ if (!Objects.equals(oldSbn.getOverrideGroupKey(), overrideGroupKey)) {
+ entry.notification.setOverrideGroupKey(overrideGroupKey);
+ mGroupManager.onEntryUpdated(entry, oldSbn);
+ }
+ entry.populateFromRanking(mTmpRanking);
+ }
+ }
+ }
+ filterAndSort();
+ }
+
+ /**
+ * Get the ranking from the current ranking map.
+ *
+ * @param key the key to look up
+ * @param outRanking the ranking to populate
+ *
+ * @return {@code true} if the ranking was properly obtained.
+ */
+ @VisibleForTesting
+ protected boolean getRanking(String key, Ranking outRanking) {
+ return mRankingMap.getRanking(key, outRanking);
+ }
+
+ // TODO: This should not be public. Instead the Environment should notify this class when
+ // anything changed, and this class should call back the UI so it updates itself.
+ public void filterAndSort() {
+ mSortedAndFiltered.clear();
+
+ synchronized (mEntries) {
+ final int len = mEntries.size();
+ for (int i = 0; i < len; i++) {
+ NotificationEntry entry = mEntries.valueAt(i);
+
+ if (mNotificationFilter.shouldFilterOut(entry)) {
+ continue;
+ }
+
+ mSortedAndFiltered.add(entry);
+ }
+ }
+
+ Collections.sort(mSortedAndFiltered, mRankingComparator);
+ }
+
+ public void dump(PrintWriter pw, String indent) {
+ int filteredLen = mSortedAndFiltered.size();
+ pw.print(indent);
+ pw.println("active notifications: " + filteredLen);
+ int active;
+ for (active = 0; active < filteredLen; active++) {
+ NotificationEntry e = mSortedAndFiltered.get(active);
+ dumpEntry(pw, indent, active, e);
+ }
+ synchronized (mEntries) {
+ int totalLen = mEntries.size();
+ pw.print(indent);
+ pw.println("inactive notifications: " + (totalLen - active));
+ int inactiveCount = 0;
+ for (int i = 0; i < totalLen; i++) {
+ NotificationEntry entry = mEntries.valueAt(i);
+ if (!mSortedAndFiltered.contains(entry)) {
+ dumpEntry(pw, indent, inactiveCount, entry);
+ inactiveCount++;
+ }
+ }
+ }
+ }
+
+ private void dumpEntry(PrintWriter pw, String indent, int i, NotificationEntry e) {
+ getRanking(e.key, mTmpRanking);
+ pw.print(indent);
+ pw.println(" [" + i + "] key=" + e.key + " icon=" + e.icon);
+ StatusBarNotification n = e.notification;
+ pw.print(indent);
+ pw.println(" pkg=" + n.getPackageName() + " id=" + n.getId() + " importance="
+ + mTmpRanking.getImportance());
+ pw.print(indent);
+ pw.println(" notification=" + n.getNotification());
+ }
+
+ private static boolean isSystemNotification(StatusBarNotification sbn) {
+ String sbnPackage = sbn.getPackageName();
+ return "android".equals(sbnPackage) || "com.android.systemui".equals(sbnPackage);
+ }
+
+ /**
+ * Provides access to keyguard state and user settings dependent data.
+ */
+ public interface KeyguardEnvironment {
+ boolean isDeviceProvisioned();
+ boolean isNotificationForCurrentProfiles(StatusBarNotification sbn);
+ }
+}
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
new file mode 100644
index 000000000000..ee551ee96e7b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
@@ -0,0 +1,726 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.collection;
+
+import static android.app.Notification.CATEGORY_ALARM;
+import static android.app.Notification.CATEGORY_CALL;
+import static android.app.Notification.CATEGORY_EVENT;
+import static android.app.Notification.CATEGORY_MESSAGE;
+import static android.app.Notification.CATEGORY_REMINDER;
+import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_AMBIENT;
+import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_FULL_SCREEN_INTENT;
+import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST;
+import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK;
+import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_STATUS_BAR;
+
+import android.annotation.NonNull;
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager.Policy;
+import android.app.Person;
+import android.content.Context;
+import android.graphics.drawable.Icon;
+import android.os.Bundle;
+import android.os.Parcelable;
+import android.os.SystemClock;
+import android.service.notification.NotificationListenerService;
+import android.service.notification.SnoozeCriterion;
+import android.service.notification.StatusBarNotification;
+import android.util.ArraySet;
+import android.view.View;
+import android.widget.ImageView;
+
+import androidx.annotation.Nullable;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.statusbar.StatusBarIcon;
+import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.ContrastColorUtil;
+import com.android.systemui.statusbar.InflationTask;
+import com.android.systemui.statusbar.StatusBarIconView;
+import com.android.systemui.statusbar.notification.InflationException;
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
+import com.android.systemui.statusbar.notification.row.NotificationGuts;
+import com.android.systemui.statusbar.notification.row.NotificationInflater;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Represents a notification that the system UI knows about
+ *
+ * Whenever the NotificationManager tells us about the existence of a new notification, we wrap it
+ * in a NotificationEntry. Thus, every notification has an associated NotificationEntry, even if
+ * that notification is never displayed to the user (for example, if it's filtered out for some
+ * reason).
+ *
+ * Entries store information about the current state of the notification. Essentially:
+ * anything that needs to persist or be modifiable even when the notification's views don't
+ * exist. Any other state should be stored on the views/view controllers themselves.
+ *
+ * At the moment, there are many things here that shouldn't be and vice-versa. Hopefully we can
+ * clean this up in the future.
+ */
+public final class NotificationEntry {
+ private static final long LAUNCH_COOLDOWN = 2000;
+ private static final long REMOTE_INPUT_COOLDOWN = 500;
+ private static final long INITIALIZATION_DELAY = 400;
+ private static final long NOT_LAUNCHED_YET = -LAUNCH_COOLDOWN;
+ private static final int COLOR_INVALID = 1;
+ public final String key;
+ public StatusBarNotification notification;
+ public NotificationChannel channel;
+ public long lastAudiblyAlertedMs;
+ public boolean noisy;
+ public boolean ambient;
+ public int importance;
+ public StatusBarIconView icon;
+ public StatusBarIconView expandedIcon;
+ private boolean interruption;
+ public boolean autoRedacted; // whether the redacted notification was generated by us
+ public int targetSdk;
+ private long lastFullScreenIntentLaunchTime = NOT_LAUNCHED_YET;
+ public CharSequence remoteInputText;
+ public List<SnoozeCriterion> snoozeCriteria;
+ public int userSentiment = NotificationListenerService.Ranking.USER_SENTIMENT_NEUTRAL;
+ /** Smart Actions provided by the NotificationAssistantService. */
+ @NonNull
+ public List<Notification.Action> systemGeneratedSmartActions = Collections.emptyList();
+ public CharSequence[] smartReplies = new CharSequence[0];
+ @VisibleForTesting
+ public int suppressedVisualEffects;
+ public boolean suspended;
+
+ private NotificationEntry parent; // our parent (if we're in a group)
+ private ArrayList<NotificationEntry> children = new ArrayList<NotificationEntry>();
+ private ExpandableNotificationRow row; // the outer expanded view
+
+ private int mCachedContrastColor = COLOR_INVALID;
+ private int mCachedContrastColorIsFor = COLOR_INVALID;
+ private InflationTask mRunningTask = null;
+ private Throwable mDebugThrowable;
+ public CharSequence remoteInputTextWhenReset;
+ public long lastRemoteInputSent = NOT_LAUNCHED_YET;
+ public ArraySet<Integer> mActiveAppOps = new ArraySet<>(3);
+ public CharSequence headsUpStatusBarText;
+ public CharSequence headsUpStatusBarTextPublic;
+
+ private long initializationTime = -1;
+
+ /**
+ * Whether or not this row represents a system notification. Note that if this is
+ * {@code null}, that means we were either unable to retrieve the info or have yet to
+ * retrieve the info.
+ */
+ public Boolean mIsSystemNotification;
+
+ /**
+ * Has the user sent a reply through this Notification.
+ */
+ private boolean hasSentReply;
+
+ /**
+ * Whether this notification should be displayed as a bubble.
+ */
+ private boolean mIsBubble;
+
+ /**
+ * Whether this notification should be shown in the shade when it is also displayed as a bubble.
+ *
+ * <p>When a notification is a bubble we don't show it in the shade once the bubble has been
+ * expanded</p>
+ */
+ private boolean mShowInShadeWhenBubble;
+
+ /**
+ * Whether the user has dismissed this notification when it was in bubble form.
+ */
+ private boolean mUserDismissedBubble;
+
+ public NotificationEntry(StatusBarNotification n) {
+ this(n, null);
+ }
+
+ public NotificationEntry(
+ StatusBarNotification n,
+ @Nullable NotificationListenerService.Ranking ranking) {
+ this.key = n.getKey();
+ this.notification = n;
+ if (ranking != null) {
+ populateFromRanking(ranking);
+ }
+ }
+
+ public void populateFromRanking(@NonNull NotificationListenerService.Ranking ranking) {
+ channel = ranking.getChannel();
+ lastAudiblyAlertedMs = ranking.getLastAudiblyAlertedMillis();
+ importance = ranking.getImportance();
+ ambient = ranking.isAmbient();
+ snoozeCriteria = ranking.getSnoozeCriteria();
+ userSentiment = ranking.getUserSentiment();
+ systemGeneratedSmartActions = ranking.getSmartActions() == null
+ ? Collections.emptyList() : ranking.getSmartActions();
+ smartReplies = ranking.getSmartReplies() == null
+ ? new CharSequence[0]
+ : ranking.getSmartReplies().toArray(new CharSequence[0]);
+ suppressedVisualEffects = ranking.getSuppressedVisualEffects();
+ suspended = ranking.isSuspended();
+ }
+
+ public void setInterruption() {
+ interruption = true;
+ }
+
+ public boolean hasInterrupted() {
+ return interruption;
+ }
+
+ public void setIsBubble(boolean bubbleable) {
+ mIsBubble = bubbleable;
+ }
+
+ public boolean isBubble() {
+ return mIsBubble;
+ }
+
+ public void setBubbleDismissed(boolean userDismissed) {
+ mUserDismissedBubble = userDismissed;
+ }
+
+ public boolean isBubbleDismissed() {
+ return mUserDismissedBubble;
+ }
+
+ /**
+ * Sets whether this notification should be shown in the shade when it is also displayed as a
+ * bubble.
+ */
+ public void setShowInShadeWhenBubble(boolean showInShade) {
+ mShowInShadeWhenBubble = showInShade;
+ }
+
+ /**
+ * Whether this notification should be shown in the shade when it is also displayed as a
+ * bubble.
+ */
+ public boolean showInShadeWhenBubble() {
+ // We always show it in the shade if non-clearable
+ return !isClearable() || mShowInShadeWhenBubble;
+ }
+
+ /**
+ * Resets the notification entry to be re-used.
+ */
+ public void reset() {
+ if (row != null) {
+ row.reset();
+ }
+ }
+
+ public ExpandableNotificationRow getRow() {
+ return row;
+ }
+
+ //TODO: This will go away when we have a way to bind an entry to a row
+ public void setRow(ExpandableNotificationRow row) {
+ this.row = row;
+ }
+
+ @Nullable
+ public List<NotificationEntry> getChildren() {
+ if (children.size() <= 0) {
+ return null;
+ }
+
+ return children;
+ }
+
+ public void notifyFullScreenIntentLaunched() {
+ setInterruption();
+ lastFullScreenIntentLaunchTime = SystemClock.elapsedRealtime();
+ }
+
+ public boolean hasJustLaunchedFullScreenIntent() {
+ return SystemClock.elapsedRealtime() < lastFullScreenIntentLaunchTime + LAUNCH_COOLDOWN;
+ }
+
+ public boolean hasJustSentRemoteInput() {
+ return SystemClock.elapsedRealtime() < lastRemoteInputSent + REMOTE_INPUT_COOLDOWN;
+ }
+
+ public boolean hasFinishedInitialization() {
+ return initializationTime == -1
+ || SystemClock.elapsedRealtime() > initializationTime + INITIALIZATION_DELAY;
+ }
+
+ /**
+ * Create the icons for a notification
+ * @param context the context to create the icons with
+ * @param sbn the notification
+ * @throws InflationException Exception if required icons are not valid or specified
+ */
+ public void createIcons(Context context, StatusBarNotification sbn)
+ throws InflationException {
+ Notification n = sbn.getNotification();
+ final Icon smallIcon = n.getSmallIcon();
+ if (smallIcon == null) {
+ throw new InflationException("No small icon in notification from "
+ + sbn.getPackageName());
+ }
+
+ // Construct the icon.
+ icon = new StatusBarIconView(context,
+ sbn.getPackageName() + "/0x" + Integer.toHexString(sbn.getId()), sbn);
+ icon.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
+
+ // Construct the expanded icon.
+ expandedIcon = new StatusBarIconView(context,
+ sbn.getPackageName() + "/0x" + Integer.toHexString(sbn.getId()), sbn);
+ expandedIcon.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
+ final StatusBarIcon ic = new StatusBarIcon(
+ sbn.getUser(),
+ sbn.getPackageName(),
+ smallIcon,
+ n.iconLevel,
+ n.number,
+ StatusBarIconView.contentDescForNotification(context, n));
+ if (!icon.set(ic) || !expandedIcon.set(ic)) {
+ icon = null;
+ expandedIcon = null;
+ throw new InflationException("Couldn't create icon: " + ic);
+ }
+ expandedIcon.setVisibility(View.INVISIBLE);
+ expandedIcon.setOnVisibilityChangedListener(
+ newVisibility -> {
+ if (row != null) {
+ row.setIconsVisible(newVisibility != View.VISIBLE);
+ }
+ });
+ }
+
+ public void setIconTag(int key, Object tag) {
+ if (icon != null) {
+ icon.setTag(key, tag);
+ expandedIcon.setTag(key, tag);
+ }
+ }
+
+ /**
+ * Update the notification icons.
+ *
+ * @param context the context to create the icons with.
+ * @param sbn the notification to read the icon from.
+ * @throws InflationException Exception if required icons are not valid or specified
+ */
+ public void updateIcons(Context context, StatusBarNotification sbn)
+ throws InflationException {
+ if (icon != null) {
+ // Update the icon
+ Notification n = sbn.getNotification();
+ final StatusBarIcon ic = new StatusBarIcon(
+ notification.getUser(),
+ notification.getPackageName(),
+ n.getSmallIcon(),
+ n.iconLevel,
+ n.number,
+ StatusBarIconView.contentDescForNotification(context, n));
+ icon.setNotification(sbn);
+ expandedIcon.setNotification(sbn);
+ if (!icon.set(ic) || !expandedIcon.set(ic)) {
+ throw new InflationException("Couldn't update icon: " + ic);
+ }
+ }
+ }
+
+ public int getContrastedColor(Context context, boolean isLowPriority,
+ int backgroundColor) {
+ int rawColor = isLowPriority ? Notification.COLOR_DEFAULT :
+ notification.getNotification().color;
+ if (mCachedContrastColorIsFor == rawColor && mCachedContrastColor != COLOR_INVALID) {
+ return mCachedContrastColor;
+ }
+ final int contrasted = ContrastColorUtil.resolveContrastColor(context, rawColor,
+ backgroundColor);
+ mCachedContrastColorIsFor = rawColor;
+ mCachedContrastColor = contrasted;
+ return mCachedContrastColor;
+ }
+
+ /**
+ * Abort all existing inflation tasks
+ */
+ public void abortTask() {
+ if (mRunningTask != null) {
+ mRunningTask.abort();
+ mRunningTask = null;
+ }
+ }
+
+ public void setInflationTask(InflationTask abortableTask) {
+ // abort any existing inflation
+ InflationTask existing = mRunningTask;
+ abortTask();
+ mRunningTask = abortableTask;
+ if (existing != null && mRunningTask != null) {
+ mRunningTask.supersedeTask(existing);
+ }
+ }
+
+ public void onInflationTaskFinished() {
+ mRunningTask = null;
+ }
+
+ @VisibleForTesting
+ public InflationTask getRunningTask() {
+ return mRunningTask;
+ }
+
+ /**
+ * Set a throwable that is used for debugging
+ *
+ * @param debugThrowable the throwable to save
+ */
+ public void setDebugThrowable(Throwable debugThrowable) {
+ mDebugThrowable = debugThrowable;
+ }
+
+ public Throwable getDebugThrowable() {
+ return mDebugThrowable;
+ }
+
+ public void onRemoteInputInserted() {
+ lastRemoteInputSent = NOT_LAUNCHED_YET;
+ remoteInputTextWhenReset = null;
+ }
+
+ public void setHasSentReply() {
+ hasSentReply = true;
+ }
+
+ public boolean isLastMessageFromReply() {
+ if (!hasSentReply) {
+ return false;
+ }
+ Bundle extras = notification.getNotification().extras;
+ CharSequence[] replyTexts = extras.getCharSequenceArray(
+ Notification.EXTRA_REMOTE_INPUT_HISTORY);
+ if (!ArrayUtils.isEmpty(replyTexts)) {
+ return true;
+ }
+ Parcelable[] messages = extras.getParcelableArray(Notification.EXTRA_MESSAGES);
+ if (messages != null && messages.length > 0) {
+ Parcelable message = messages[messages.length - 1];
+ if (message instanceof Bundle) {
+ Notification.MessagingStyle.Message lastMessage =
+ Notification.MessagingStyle.Message.getMessageFromBundle(
+ (Bundle) message);
+ if (lastMessage != null) {
+ Person senderPerson = lastMessage.getSenderPerson();
+ if (senderPerson == null) {
+ return true;
+ }
+ Person user = extras.getParcelable(Notification.EXTRA_MESSAGING_PERSON);
+ return Objects.equals(user, senderPerson);
+ }
+ }
+ }
+ return false;
+ }
+
+ public void setInitializationTime(long time) {
+ if (initializationTime == -1) {
+ initializationTime = time;
+ }
+ }
+
+ public void sendAccessibilityEvent(int eventType) {
+ if (row != null) {
+ row.sendAccessibilityEvent(eventType);
+ }
+ }
+
+ /**
+ * Used by NotificationMediaManager to determine... things
+ * @return {@code true} if we are a media notification
+ */
+ public boolean isMediaNotification() {
+ if (row == null) return false;
+
+ return row.isMediaRow();
+ }
+
+ /**
+ * We are a top level child if our parent is the list of notifications duh
+ * @return {@code true} if we're a top level notification
+ */
+ public boolean isTopLevelChild() {
+ return row != null && row.isTopLevelChild();
+ }
+
+ public void resetUserExpansion() {
+ if (row != null) row.resetUserExpansion();
+ }
+
+ public void freeContentViewWhenSafe(@NotificationInflater.InflationFlag int inflationFlag) {
+ if (row != null) row.freeContentViewWhenSafe(inflationFlag);
+ }
+
+ public void setAmbientPulsing(boolean pulsing) {
+ if (row != null) row.setAmbientPulsing(pulsing);
+ }
+
+ public boolean rowExists() {
+ return row != null;
+ }
+
+ public boolean isRowDismissed() {
+ return row != null && row.isDismissed();
+ }
+
+ public boolean isRowRemoved() {
+ return row != null && row.isRemoved();
+ }
+
+ /**
+ * @return {@code true} if the row is null or removed
+ */
+ public boolean isRemoved() {
+ //TODO: recycling invalidates this
+ return row == null || row.isRemoved();
+ }
+
+ /**
+ * @return {@code true} if the row is null or dismissed
+ */
+ public boolean isDismissed() {
+ //TODO: recycling
+ return row == null || row.isDismissed();
+ }
+
+ public boolean isRowPinned() {
+ return row != null && row.isPinned();
+ }
+
+ public void setRowPinned(boolean pinned) {
+ if (row != null) row.setPinned(pinned);
+ }
+
+ public boolean isRowAnimatingAway() {
+ return row != null && row.isHeadsUpAnimatingAway();
+ }
+
+ public boolean isRowHeadsUp() {
+ return row != null && row.isHeadsUp();
+ }
+
+ public void setHeadsUp(boolean shouldHeadsUp) {
+ if (row != null) row.setHeadsUp(shouldHeadsUp);
+ }
+
+ public boolean mustStayOnScreen() {
+ return row != null && row.mustStayOnScreen();
+ }
+
+ public void setHeadsUpIsVisible() {
+ if (row != null) row.setHeadsUpIsVisible();
+ }
+
+ //TODO: i'm imagining a world where this isn't just the row, but I could be rwong
+ public ExpandableNotificationRow getHeadsUpAnimationView() {
+ return row;
+ }
+
+ public void setUserLocked(boolean userLocked) {
+ if (row != null) row.setUserLocked(userLocked);
+ }
+
+ public void setUserExpanded(boolean userExpanded, boolean allowChildExpansion) {
+ if (row != null) row.setUserExpanded(userExpanded, allowChildExpansion);
+ }
+
+ public void setGroupExpansionChanging(boolean changing) {
+ if (row != null) row.setGroupExpansionChanging(changing);
+ }
+
+ public void notifyHeightChanged(boolean needsAnimation) {
+ if (row != null) row.notifyHeightChanged(needsAnimation);
+ }
+
+ public void closeRemoteInput() {
+ if (row != null) row.closeRemoteInput();
+ }
+
+ public boolean areChildrenExpanded() {
+ return row != null && row.areChildrenExpanded();
+ }
+
+ public boolean keepInParent() {
+ return row != null && row.keepInParent();
+ }
+
+ //TODO: probably less confusing to say "is group fully visible"
+ public boolean isGroupNotFullyVisible() {
+ return row == null || row.isGroupNotFullyVisible();
+ }
+
+ public NotificationGuts getGuts() {
+ if (row != null) return row.getGuts();
+ return null;
+ }
+
+ public boolean hasLowPriorityStateUpdated() {
+ return row != null && row.hasLowPriorityStateUpdated();
+ }
+
+ public void removeRow() {
+ if (row != null) row.setRemoved();
+ }
+
+ public boolean isSummaryWithChildren() {
+ return row != null && row.isSummaryWithChildren();
+ }
+
+ public void setKeepInParent(boolean keep) {
+ if (row != null) row.setKeepInParent(keep);
+ }
+
+ public void onDensityOrFontScaleChanged() {
+ if (row != null) row.onDensityOrFontScaleChanged();
+ }
+
+ public boolean areGutsExposed() {
+ return row != null && row.getGuts() != null && row.getGuts().isExposed();
+ }
+
+ public boolean isChildInGroup() {
+ return parent == null;
+ }
+
+ public void setLowPriorityStateUpdated(boolean updated) {
+ if (row != null) row.setLowPriorityStateUpdated(updated);
+ }
+
+ /**
+ * @return Can the underlying notification be cleared? This can be different from whether the
+ * notification can be dismissed in case notifications are sensitive on the lockscreen.
+ * @see #canViewBeDismissed()
+ */
+ public boolean isClearable() {
+ if (notification == null || !notification.isClearable()) {
+ return false;
+ }
+ if (children.size() > 0) {
+ for (int i = 0; i < children.size(); i++) {
+ NotificationEntry child = children.get(i);
+ if (!child.isClearable()) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ public boolean canViewBeDismissed() {
+ if (row == null) return true;
+ return row.canViewBeDismissed();
+ }
+
+ @VisibleForTesting
+ boolean isExemptFromDndVisualSuppression() {
+ if (isNotificationBlockedByPolicy(notification.getNotification())) {
+ return false;
+ }
+
+ if ((notification.getNotification().flags
+ & Notification.FLAG_FOREGROUND_SERVICE) != 0) {
+ return true;
+ }
+ if (notification.getNotification().isMediaNotification()) {
+ return true;
+ }
+ if (mIsSystemNotification != null && mIsSystemNotification) {
+ return true;
+ }
+ return false;
+ }
+
+ private boolean shouldSuppressVisualEffect(int effect) {
+ if (isExemptFromDndVisualSuppression()) {
+ return false;
+ }
+ return (suppressedVisualEffects & effect) != 0;
+ }
+
+ /**
+ * Returns whether {@link Policy#SUPPRESSED_EFFECT_FULL_SCREEN_INTENT}
+ * is set for this entry.
+ */
+ public boolean shouldSuppressFullScreenIntent() {
+ return shouldSuppressVisualEffect(SUPPRESSED_EFFECT_FULL_SCREEN_INTENT);
+ }
+
+ /**
+ * Returns whether {@link Policy#SUPPRESSED_EFFECT_PEEK}
+ * is set for this entry.
+ */
+ public boolean shouldSuppressPeek() {
+ return shouldSuppressVisualEffect(SUPPRESSED_EFFECT_PEEK);
+ }
+
+ /**
+ * Returns whether {@link Policy#SUPPRESSED_EFFECT_STATUS_BAR}
+ * is set for this entry.
+ */
+ public boolean shouldSuppressStatusBar() {
+ return shouldSuppressVisualEffect(SUPPRESSED_EFFECT_STATUS_BAR);
+ }
+
+ /**
+ * Returns whether {@link Policy#SUPPRESSED_EFFECT_AMBIENT}
+ * is set for this entry.
+ */
+ public boolean shouldSuppressAmbient() {
+ return shouldSuppressVisualEffect(SUPPRESSED_EFFECT_AMBIENT);
+ }
+
+ /**
+ * Returns whether {@link Policy#SUPPRESSED_EFFECT_NOTIFICATION_LIST}
+ * is set for this entry.
+ */
+ public boolean shouldSuppressNotificationList() {
+ return shouldSuppressVisualEffect(SUPPRESSED_EFFECT_NOTIFICATION_LIST);
+ }
+
+ /**
+ * Categories that are explicitly called out on DND settings screens are always blocked, if
+ * DND has flagged them, even if they are foreground or system notifications that might
+ * otherwise visually bypass DND.
+ */
+ private static boolean isNotificationBlockedByPolicy(Notification n) {
+ return isCategory(CATEGORY_CALL, n)
+ || isCategory(CATEGORY_MESSAGE, n)
+ || isCategory(CATEGORY_ALARM, n)
+ || isCategory(CATEGORY_EVENT, n)
+ || isCategory(CATEGORY_REMINDER, n);
+ }
+
+ private static boolean isCategory(String category, Notification n) {
+ return Objects.equals(n.category, category);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java
index 610d3003f0e9..5ba9b4b34042 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java
@@ -15,7 +15,6 @@
*/
package com.android.systemui.statusbar.notification.logging;
-import android.annotation.Nullable;
import android.content.Context;
import android.os.Handler;
import android.os.RemoteException;
@@ -24,9 +23,12 @@ import android.os.SystemClock;
import android.service.notification.NotificationListenerService;
import android.service.notification.NotificationStats;
import android.service.notification.StatusBarNotification;
+import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Log;
+import androidx.annotation.Nullable;
+
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.statusbar.NotificationVisibility;
@@ -34,15 +36,16 @@ import com.android.systemui.UiOffloadThread;
import com.android.systemui.statusbar.NotificationListener;
import com.android.systemui.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.StatusBarStateController.StateListener;
-import com.android.systemui.statusbar.notification.NotificationData;
import com.android.systemui.statusbar.notification.NotificationEntryListener;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
+import java.util.Map;
import javax.inject.Inject;
import javax.inject.Singleton;
@@ -67,6 +70,7 @@ public class NotificationLogger implements StateListener {
private final UiOffloadThread mUiOffloadThread;
private final NotificationEntryManager mEntryManager;
private HeadsUpManager mHeadsUpManager;
+ private final ExpansionStateLogger mExpansionStateLogger;
protected Handler mHandler = new Handler();
protected IStatusBarService mBarService;
@@ -116,11 +120,11 @@ public class NotificationLogger implements StateListener {
// notifications.
// 3. Report newly visible and no-longer visible notifications.
// 4. Keep currently visible notifications for next report.
- ArrayList<NotificationData.Entry> activeNotifications = mEntryManager
+ ArrayList<NotificationEntry> activeNotifications = mEntryManager
.getNotificationData().getActiveNotifications();
int N = activeNotifications.size();
for (int i = 0; i < N; i++) {
- NotificationData.Entry entry = activeNotifications.get(i);
+ NotificationEntry entry = activeNotifications.get(i);
String key = entry.notification.getKey();
boolean isVisible = mListContainer.isInVisibleLocation(entry);
NotificationVisibility visObj = NotificationVisibility.obtain(key, i, N, isVisible);
@@ -145,6 +149,9 @@ public class NotificationLogger implements StateListener {
recycleAllVisibilityObjects(mCurrentlyVisibleNotifications);
mCurrentlyVisibleNotifications.addAll(mTmpCurrentlyVisibleNotifications);
+ mExpansionStateLogger.onVisibilityChanged(
+ mTmpCurrentlyVisibleNotifications, mTmpCurrentlyVisibleNotifications);
+
recycleAllVisibilityObjects(mTmpNoLongerVisibleNotifications);
mTmpCurrentlyVisibleNotifications.clear();
mTmpNewlyVisibleNotifications.clear();
@@ -156,27 +163,27 @@ public class NotificationLogger implements StateListener {
public NotificationLogger(NotificationListener notificationListener,
UiOffloadThread uiOffloadThread,
NotificationEntryManager entryManager,
- StatusBarStateController statusBarStateController) {
+ StatusBarStateController statusBarStateController,
+ ExpansionStateLogger expansionStateLogger) {
mNotificationListener = notificationListener;
mUiOffloadThread = uiOffloadThread;
mEntryManager = entryManager;
mBarService = IStatusBarService.Stub.asInterface(
ServiceManager.getService(Context.STATUS_BAR_SERVICE));
+ mExpansionStateLogger = expansionStateLogger;
// Not expected to be destroyed, don't need to unsubscribe
statusBarStateController.addCallback(this);
entryManager.addNotificationEntryListener(new NotificationEntryListener() {
@Override
public void onEntryRemoved(
- @Nullable NotificationData.Entry entry,
- String key,
- StatusBarNotification old,
+ NotificationEntry entry,
NotificationVisibility visibility,
- boolean lifetimeExtended,
boolean removedByUser) {
- if (removedByUser && visibility != null && entry != null) {
- logNotificationClear(key, entry.notification, visibility);
+ if (removedByUser && visibility != null) {
+ logNotificationClear(entry.key, entry.notification, visibility);
}
+ mExpansionStateLogger.onEntryRemoved(entry.key);
}
@Override
@@ -323,8 +330,8 @@ public class NotificationLogger implements StateListener {
}
}
- private NotificationVisibility[] cloneVisibilitiesAsArr(Collection<NotificationVisibility> c) {
-
+ private static NotificationVisibility[] cloneVisibilitiesAsArr(
+ Collection<NotificationVisibility> c) {
final NotificationVisibility[] array = new NotificationVisibility[c.size()];
int i = 0;
for(NotificationVisibility nv: c) {
@@ -352,9 +359,133 @@ public class NotificationLogger implements StateListener {
}
/**
+ * Called when the notification is expanded / collapsed.
+ */
+ public void onExpansionChanged(String key, boolean isUserAction, boolean isExpanded) {
+ mExpansionStateLogger.onExpansionChanged(key, isUserAction, isExpanded);
+ }
+
+ /**
* A listener that is notified when some child locations might have changed.
*/
public interface OnChildLocationsChangedListener {
void onChildLocationsChanged();
}
+
+ /**
+ * Logs the expansion state change when the notification is visible.
+ */
+ public static class ExpansionStateLogger {
+ /** Notification key -> state, should be accessed in UI offload thread only. */
+ private final Map<String, State> mExpansionStates = new ArrayMap<>();
+
+ /**
+ * Notification key -> last logged expansion state, should be accessed in UI thread only.
+ */
+ private final Map<String, Boolean> mLoggedExpansionState = new ArrayMap<>();
+ private final UiOffloadThread mUiOffloadThread;
+ @VisibleForTesting
+ IStatusBarService mBarService;
+
+ @Inject
+ public ExpansionStateLogger(UiOffloadThread uiOffloadThread) {
+ mUiOffloadThread = uiOffloadThread;
+ mBarService =
+ IStatusBarService.Stub.asInterface(
+ ServiceManager.getService(Context.STATUS_BAR_SERVICE));
+ }
+
+ @VisibleForTesting
+ void onExpansionChanged(String key, boolean isUserAction, boolean isExpanded) {
+ State state = getState(key);
+ state.mIsUserAction = isUserAction;
+ state.mIsExpanded = isExpanded;
+ maybeNotifyOnNotificationExpansionChanged(key, state);
+ }
+
+ @VisibleForTesting
+ void onVisibilityChanged(
+ Collection<NotificationVisibility> newlyVisible,
+ Collection<NotificationVisibility> noLongerVisible) {
+ final NotificationVisibility[] newlyVisibleAr =
+ cloneVisibilitiesAsArr(newlyVisible);
+ final NotificationVisibility[] noLongerVisibleAr =
+ cloneVisibilitiesAsArr(noLongerVisible);
+
+ for (NotificationVisibility nv : newlyVisibleAr) {
+ State state = getState(nv.key);
+ state.mIsVisible = true;
+ maybeNotifyOnNotificationExpansionChanged(nv.key, state);
+ }
+ for (NotificationVisibility nv : noLongerVisibleAr) {
+ State state = getState(nv.key);
+ state.mIsVisible = false;
+ }
+ }
+
+ @VisibleForTesting
+ void onEntryRemoved(String key) {
+ mExpansionStates.remove(key);
+ mLoggedExpansionState.remove(key);
+ }
+
+ private State getState(String key) {
+ State state = mExpansionStates.get(key);
+ if (state == null) {
+ state = new State();
+ mExpansionStates.put(key, state);
+ }
+ return state;
+ }
+
+ private void maybeNotifyOnNotificationExpansionChanged(final String key, State state) {
+ if (!state.isFullySet()) {
+ return;
+ }
+ if (!state.mIsVisible) {
+ return;
+ }
+ Boolean loggedExpansionState = mLoggedExpansionState.get(key);
+ // Consider notification is initially collapsed, so only expanded is logged in the
+ // first time.
+ if (loggedExpansionState == null && !state.mIsExpanded) {
+ return;
+ }
+ if (loggedExpansionState != null
+ && state.mIsExpanded == loggedExpansionState) {
+ return;
+ }
+ mLoggedExpansionState.put(key, state.mIsExpanded);
+ final State stateToBeLogged = new State(state);
+ mUiOffloadThread.submit(() -> {
+ try {
+ mBarService.onNotificationExpansionChanged(
+ key, stateToBeLogged.mIsUserAction, stateToBeLogged.mIsExpanded);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to call onNotificationExpansionChanged: ", e);
+ }
+ });
+ }
+
+ private static class State {
+ @Nullable
+ Boolean mIsUserAction;
+ @Nullable
+ Boolean mIsExpanded;
+ @Nullable
+ Boolean mIsVisible;
+
+ private State() {}
+
+ private State(State state) {
+ this.mIsUserAction = state.mIsUserAction;
+ this.mIsExpanded = state.mIsExpanded;
+ this.mIsVisible = state.mIsVisible;
+ }
+
+ private boolean isFullySet() {
+ return mIsUserAction != null && mIsExpanded != null && mIsVisible != null;
+ }
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
index 8b0a6828205d..c34d567da0be 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
@@ -105,7 +105,7 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
private final DoubleTapHelper mDoubleTapHelper;
private boolean mDimmed;
- private boolean mDark;
+ protected boolean mDark;
protected int mBgTint = NO_COLOR;
private float mBgAlpha = 1f;
@@ -140,7 +140,6 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
private FalsingManager mFalsingManager;
private float mNormalBackgroundVisibilityAmount;
- private ValueAnimator mFadeInFromDarkAnimator;
private float mDimmedBackgroundFadeInAmount = -1;
private ValueAnimator.AnimatorUpdateListener mBackgroundVisibilityUpdater
= new ValueAnimator.AnimatorUpdateListener() {
@@ -150,22 +149,6 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
mDimmedBackgroundFadeInAmount = mBackgroundDimmed.getAlpha();
}
};
- private AnimatorListenerAdapter mFadeInEndListener = new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- super.onAnimationEnd(animation);
- mFadeInFromDarkAnimator = null;
- mDimmedBackgroundFadeInAmount = -1;
- updateBackground();
- }
- };
- private ValueAnimator.AnimatorUpdateListener mUpdateOutlineListener
- = new ValueAnimator.AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator animation) {
- updateOutlineAlpha();
- }
- };
private FakeShadowView mFakeShadow;
private int mCurrentBackgroundTint;
private int mTargetTint;
@@ -465,22 +448,11 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
mDark = dark;
updateBackground();
updateBackgroundTint(false);
- if (!dark && fade && !shouldHideBackground()) {
- fadeInFromDark(delay);
- }
- updateOutlineAlpha();
}
private void updateOutlineAlpha() {
- if (mDark) {
- setOutlineAlpha(0f);
- return;
- }
float alpha = NotificationStackScrollLayout.BACKGROUND_ALPHA_DIMMED;
alpha = (alpha + (1.0f - alpha) * mNormalBackgroundVisibilityAmount);
- if (mFadeInFromDarkAnimator != null) {
- alpha *= mFadeInFromDarkAnimator.getAnimatedFraction();
- }
setOutlineAlpha(alpha);
}
@@ -638,36 +610,6 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
}
/**
- * Fades in the background when exiting dark mode.
- */
- private void fadeInFromDark(long delay) {
- final View background = mDimmed ? mBackgroundDimmed : mBackgroundNormal;
- background.setAlpha(0f);
- mBackgroundVisibilityUpdater.onAnimationUpdate(null);
- background.animate()
- .alpha(1f)
- .setDuration(DARK_ANIMATION_LENGTH)
- .setStartDelay(delay)
- .setInterpolator(Interpolators.ALPHA_IN)
- .setListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationCancel(Animator animation) {
- // Jump state if we are cancelled
- background.setAlpha(1f);
- }
- })
- .setUpdateListener(mBackgroundVisibilityUpdater)
- .start();
- mFadeInFromDarkAnimator = TimeAnimator.ofFloat(0.0f, 1.0f);
- mFadeInFromDarkAnimator.setDuration(DARK_ANIMATION_LENGTH);
- mFadeInFromDarkAnimator.setStartDelay(delay);
- mFadeInFromDarkAnimator.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
- mFadeInFromDarkAnimator.addListener(mFadeInEndListener);
- mFadeInFromDarkAnimator.addUpdateListener(mUpdateOutlineListener);
- mFadeInFromDarkAnimator.start();
- }
-
- /**
* Fades the background when the dimmed state changes.
*/
private void fadeDimmedBackground() {
@@ -708,9 +650,7 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
public void onAnimationEnd(Animator animation) {
updateBackground();
mBackgroundAnimator = null;
- if (mFadeInFromDarkAnimator == null) {
- mDimmedBackgroundFadeInAmount = -1;
- }
+ mDimmedBackgroundFadeInAmount = -1;
}
});
mBackgroundAnimator.addUpdateListener(mBackgroundVisibilityUpdater);
@@ -736,7 +676,7 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
mBackgroundNormal.setVisibility(mActivated ? VISIBLE : INVISIBLE);
} else if (mDimmed) {
// When groups are animating to the expanded state from the lockscreen, show the
- // normal background instead of the dimmed background
+ // normal background instead of the dimmed background.
final boolean dontShowDimmed = isGroupExpansionChanging() && isChildInGroup();
mBackgroundDimmed.setVisibility(dontShowDimmed ? View.INVISIBLE : View.VISIBLE);
mBackgroundNormal.setVisibility((mActivated || dontShowDimmed)
@@ -760,7 +700,7 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
}
protected boolean shouldHideBackground() {
- return mDark;
+ return false;
}
private void cancelFadeAnimations() {
@@ -1023,14 +963,11 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
/**
* @param withTint should a possible tint be factored in?
- * @param withOverRide should the value be interpolated with {@link #mOverrideTint}
+ * @param withOverride should the value be interpolated with {@link #mOverrideTint}
* @return the calculated background color
*/
- private int calculateBgColor(boolean withTint, boolean withOverRide) {
- if (withTint && mDark) {
- return getContext().getColor(R.color.notification_material_background_dark_color);
- }
- if (withOverRide && mOverrideTint != NO_COLOR) {
+ private int calculateBgColor(boolean withTint, boolean withOverride) {
+ if (withOverride && mOverrideTint != NO_COLOR) {
int defaultTint = calculateBgColor(withTint, false);
return NotificationUtils.interpolateColors(defaultTint, mOverrideTint, mOverrideAmount);
}
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 a58c7cde32a7..296c061459bc 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
@@ -16,7 +16,6 @@
package com.android.systemui.statusbar.notification.row;
-import static com.android.systemui.statusbar.StatusBarState.SHADE;
import static com.android.systemui.statusbar.notification.ActivityLaunchAnimator.ExpandAnimationParameters;
import static com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_AMBIENT;
import static com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_CONTRACTED;
@@ -86,10 +85,9 @@ import com.android.systemui.statusbar.RemoteInputController;
import com.android.systemui.statusbar.StatusBarIconView;
import com.android.systemui.statusbar.notification.AboveShelfChangedListener;
import com.android.systemui.statusbar.notification.ActivityLaunchAnimator;
-import com.android.systemui.statusbar.notification.NotificationData;
-import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.NotificationUtils;
import com.android.systemui.statusbar.notification.VisualStabilityManager;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.logging.NotificationCounters;
import com.android.systemui.statusbar.notification.row.NotificationInflater.InflationFlag;
import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper;
@@ -106,6 +104,7 @@ import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
+import java.util.concurrent.TimeUnit;
import java.util.function.BooleanSupplier;
import java.util.function.Consumer;
@@ -122,6 +121,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
private static final int MENU_VIEW_INDEX = 0;
private static final String TAG = "ExpandableNotifRow";
public static final float DEFAULT_HEADER_VISIBLE_AMOUNT = 1.0f;
+ private static final long RECENTLY_ALERTED_THRESHOLD_MS = TimeUnit.SECONDS.toMillis(30);
private boolean mUpdateBackgroundOnUpdate;
/**
@@ -132,7 +132,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
}
private LayoutListener mLayoutListener;
- private boolean mDark;
private boolean mLowPriorityStateUpdated;
private final NotificationInflater mNotificationInflater;
private int mIconTransformContentShift;
@@ -146,7 +145,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
private int mNotificationMinHeight;
private int mNotificationMinHeightLarge;
private int mNotificationMaxHeight;
- private int mNotificationAmbientHeight;
private int mIncreasedPaddingBetweenElements;
private int mNotificationLaunchHeight;
private boolean mMustStayOnScreen;
@@ -199,7 +197,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
private ExpansionLogger mLogger;
private String mLoggingKey;
private NotificationGuts mGuts;
- private NotificationData.Entry mEntry;
+ private NotificationEntry mEntry;
private StatusBarNotification mStatusBarNotification;
private String mAppName;
@@ -451,7 +449,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
*
* @param entry the entry this row is tied to
*/
- public void setEntry(@NonNull NotificationData.Entry entry) {
+ public void setEntry(@NonNull NotificationEntry entry) {
mEntry = entry;
mStatusBarNotification = entry.notification;
cacheIsSystemNotification();
@@ -677,15 +675,14 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
if (headsUpWrapper != null) {
headsUpHeight = Math.max(headsUpHeight, headsUpWrapper.getMinLayoutHeight());
}
- layout.setHeights(minHeight, headsUpHeight, mNotificationMaxHeight,
- mNotificationAmbientHeight);
+ layout.setHeights(minHeight, headsUpHeight, mNotificationMaxHeight, headsUpHeight);
}
public StatusBarNotification getStatusBarNotification() {
return mStatusBarNotification;
}
- public NotificationData.Entry getEntry() {
+ public NotificationEntry getEntry() {
return mEntry;
}
@@ -1422,7 +1419,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
public void performDismiss(boolean fromAccessibility) {
if (isOnlyChildInGroup()) {
- NotificationData.Entry groupSummary =
+ NotificationEntry groupSummary =
mGroupManager.getLogicalGroupSummary(getStatusBarNotification());
if (groupSummary.isClearable()) {
// If this is the only child in the group, dismiss the group, but don't try to show
@@ -1647,8 +1644,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
R.dimen.notification_min_height_increased);
mNotificationMaxHeight = NotificationUtils.getFontScaledHeight(mContext,
R.dimen.notification_max_height);
- mNotificationAmbientHeight = NotificationUtils.getFontScaledHeight(mContext,
- R.dimen.notification_ambient_height);
mMaxHeadsUpHeightBeforeN = NotificationUtils.getFontScaledHeight(mContext,
R.dimen.notification_max_heads_up_height_legacy);
mMaxHeadsUpHeightBeforeP = NotificationUtils.getFontScaledHeight(mContext,
@@ -1693,17 +1688,31 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
/** Sets the last time the notification being displayed audibly alerted the user. */
public void setLastAudiblyAlertedMs(long lastAudiblyAlertedMs) {
if (NotificationUtils.useNewInterruptionModel(mContext)) {
- boolean recentlyAudiblyAlerted = System.currentTimeMillis() - lastAudiblyAlertedMs
- < NotificationEntryManager.RECENTLY_ALERTED_THRESHOLD_MS;
- if (mIsSummaryWithChildren && mChildrenContainer.getHeaderView() != null) {
- mChildrenContainer.getHeaderView().setRecentlyAudiblyAlerted(
- recentlyAudiblyAlerted);
+ long timeSinceAlertedAudibly = System.currentTimeMillis() - lastAudiblyAlertedMs;
+ boolean alertedRecently =
+ timeSinceAlertedAudibly < RECENTLY_ALERTED_THRESHOLD_MS;
+
+ applyAudiblyAlertedRecently(alertedRecently);
+
+ removeCallbacks(mExpireRecentlyAlertedFlag);
+ if (alertedRecently) {
+ long timeUntilNoLongerRecent =
+ RECENTLY_ALERTED_THRESHOLD_MS - timeSinceAlertedAudibly;
+ postDelayed(mExpireRecentlyAlertedFlag, timeUntilNoLongerRecent);
}
- mPrivateLayout.setRecentlyAudiblyAlerted(recentlyAudiblyAlerted);
- mPublicLayout.setRecentlyAudiblyAlerted(recentlyAudiblyAlerted);
}
}
+ private final Runnable mExpireRecentlyAlertedFlag = () -> applyAudiblyAlertedRecently(false);
+
+ private void applyAudiblyAlertedRecently(boolean audiblyAlertedRecently) {
+ if (mIsSummaryWithChildren && mChildrenContainer.getHeaderView() != null) {
+ mChildrenContainer.getHeaderView().setRecentlyAudiblyAlerted(audiblyAlertedRecently);
+ }
+ mPrivateLayout.setRecentlyAudiblyAlerted(audiblyAlertedRecently);
+ mPublicLayout.setRecentlyAudiblyAlerted(audiblyAlertedRecently);
+ }
+
public View.OnClickListener getAppOpsOnClickListener() {
return mOnAppOpsClickListener;
}
@@ -1994,8 +2003,10 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
@Override
public void setDark(boolean dark, boolean fade, long delay) {
+ if (mDark == dark) {
+ return;
+ }
super.setDark(dark, fade, delay);
- mDark = dark;
if (!mIsAmbientPulsing) {
// Only fade the showing view of the pulsing notification.
fade = false;
@@ -2004,9 +2015,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
if (showing != null) {
showing.setDark(dark, fade, delay);
}
- if (mIsSummaryWithChildren) {
- mChildrenContainer.setDark(dark, fade, delay);
- }
updateShelfIconColor();
}
@@ -2307,7 +2315,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
}
private boolean isShownAsBubble() {
- return mEntry.isBubble() && (mStatusBarState == SHADE || mStatusBarState == -1);
+ return mEntry.isBubble() && !mEntry.showInShadeWhenBubble() && !mEntry.isBubbleDismissed();
}
/**
@@ -2538,7 +2546,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
/**
* @return Whether this view is allowed to be dismissed. Only valid for visible notifications as
* otherwise some state might not be updated. To request about the general clearability
- * see {@link NotificationData.Entry#isClearable()}.
+ * see {@link NotificationEntry#isClearable()}.
*/
public boolean canViewBeDismissed() {
return mEntry.isClearable() && (!shouldShowPublic() || !mSensitiveHiddenInGeneral);
@@ -2969,7 +2977,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
}
public interface OnExpandClickListener {
- void onExpandClicked(NotificationData.Entry clickedEntry, boolean nowExpanded);
+ void onExpandClicked(NotificationEntry clickedEntry, boolean nowExpanded);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridGroupManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridGroupManager.java
index 33badafd5488..90ff4a728915 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridGroupManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridGroupManager.java
@@ -39,14 +39,10 @@ public class HybridGroupManager {
private final NotificationDozeHelper mDozer;
private final ViewGroup mParent;
- private float mOverflowNumberSizeDark;
- private int mOverflowNumberPaddingDark;
private float mOverflowNumberSize;
private int mOverflowNumberPadding;
private int mOverflowNumberColor;
- private int mOverflowNumberColorDark;
- private float mDarkAmount = 0f;
public HybridGroupManager(Context ctx, ViewGroup parent) {
mContext = ctx;
@@ -59,12 +55,8 @@ public class HybridGroupManager {
Resources res = mContext.getResources();
mOverflowNumberSize = res.getDimensionPixelSize(
R.dimen.group_overflow_number_size);
- mOverflowNumberSizeDark = res.getDimensionPixelSize(
- R.dimen.group_overflow_number_size_dark);
mOverflowNumberPadding = res.getDimensionPixelSize(
R.dimen.group_overflow_number_padding);
- mOverflowNumberPaddingDark = mOverflowNumberPadding + res.getDimensionPixelSize(
- R.dimen.group_overflow_number_extra_padding_dark);
}
private HybridNotificationView inflateHybridViewWithStyle(int style) {
@@ -86,13 +78,11 @@ public class HybridGroupManager {
}
private void updateOverFlowNumberColor(TextView numberView) {
- numberView.setTextColor(NotificationUtils.interpolateColors(
- mOverflowNumberColor, mOverflowNumberColorDark, mDarkAmount));
+ numberView.setTextColor(mOverflowNumberColor);
}
- public void setOverflowNumberColor(TextView numberView, int colorRegular, int colorDark) {
+ public void setOverflowNumberColor(TextView numberView, int colorRegular) {
mOverflowNumberColor = colorRegular;
- mOverflowNumberColorDark = colorDark;
if (numberView != null) {
updateOverFlowNumberColor(numberView);
}
@@ -107,7 +97,7 @@ public class HybridGroupManager {
public HybridNotificationView bindAmbientFromNotification(HybridNotificationView reusableView,
Notification notification) {
return bindFromNotificationWithStyle(reusableView, notification,
- R.style.HybridNotification_Ambient);
+ R.style.HybridNotification);
}
private HybridNotificationView bindFromNotificationWithStyle(
@@ -150,6 +140,11 @@ public class HybridGroupManager {
R.plurals.notification_group_overflow_description, number), number);
reusableView.setContentDescription(contentDescription);
+ reusableView.setTextSize(TypedValue.COMPLEX_UNIT_PX, mOverflowNumberSize);
+ reusableView.setPaddingRelative(reusableView.getPaddingStart(),
+ reusableView.getPaddingTop(), mOverflowNumberPadding,
+ reusableView.getPaddingBottom());
+ updateOverFlowNumberColor(reusableView);
return reusableView;
}
@@ -163,16 +158,4 @@ public class HybridGroupManager {
}
return titleView;
}
-
- public void setOverflowNumberDark(TextView view, boolean dark, boolean fade, long delay) {
- mDozer.setIntensityDark((f)->{
- mDarkAmount = f;
- updateOverFlowNumberColor(view);
- }, dark, fade, delay, view);
- view.setTextSize(TypedValue.COMPLEX_UNIT_PX,
- dark ? mOverflowNumberSizeDark : mOverflowNumberSize);
- int paddingEnd = dark ? mOverflowNumberPaddingDark : mOverflowNumberPadding;
- view.setPaddingRelative(view.getPaddingStart(), view.getPaddingTop(), paddingEnd,
- view.getPaddingBottom());
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManager.java
index 607d96dcbd3f..6df72febc8db 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManager.java
@@ -20,11 +20,13 @@ import static android.service.notification.NotificationListenerService.Ranking
.USER_SENTIMENT_NEGATIVE;
import android.content.Context;
+import android.metrics.LogMaker;
import android.util.Log;
import androidx.annotation.VisibleForTesting;
import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.systemui.Dependency;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
@@ -58,6 +60,8 @@ public class NotificationBlockingHelperManager {
*/
private boolean mIsShadeExpanded;
+ private MetricsLogger mMetricsLogger = new MetricsLogger();
+
@Inject
public NotificationBlockingHelperManager(Context context) {
mContext = context;
@@ -100,6 +104,11 @@ public class NotificationBlockingHelperManager {
mBlockingHelperRow = row;
mBlockingHelperRow.setBlockingHelperShowing(true);
+ // Log triggering of blocking helper by the system. This log line
+ // should be emitted before the "display" log line.
+ mMetricsLogger.write(
+ getLogMaker().setSubtype(MetricsEvent.BLOCKING_HELPER_TRIGGERED_BY_SYSTEM));
+
// We don't care about the touch origin (x, y) since we're opening guts without any
// explicit user interaction.
manager.openGuts(mBlockingHelperRow, 0, 0, menuRow.getLongpressMenuItem(mContext));
@@ -153,6 +162,13 @@ public class NotificationBlockingHelperManager {
|| mNonBlockablePkgs.contains(makeChannelKey(packageName, channelName));
}
+ private LogMaker getLogMaker() {
+ return mBlockingHelperRow.getStatusBarNotification()
+ .getLogMaker()
+ .setCategory(MetricsEvent.NOTIFICATION_ITEM)
+ .setType(MetricsEvent.NOTIFICATION_BLOCKING_HELPER);
+ }
+
// Format must stay in sync with frameworks/base/core/res/res/values/config.xml
// config_nonBlockableNotificationPackages
private String makeChannelKey(String pkg, String channel) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
index 02a310c29379..c161da348c28 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
@@ -46,8 +46,8 @@ import com.android.systemui.R;
import com.android.systemui.statusbar.RemoteInputController;
import com.android.systemui.statusbar.SmartReplyController;
import com.android.systemui.statusbar.TransformableView;
-import com.android.systemui.statusbar.notification.NotificationData;
import com.android.systemui.statusbar.notification.NotificationUtils;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.row.wrapper.NotificationCustomViewWrapper;
import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper;
import com.android.systemui.statusbar.phone.NotificationGroupManager;
@@ -1231,7 +1231,7 @@ public class NotificationContentView extends FrameLayout {
updateAllSingleLineViews();
}
- public void onNotificationUpdated(NotificationData.Entry entry) {
+ public void onNotificationUpdated(NotificationEntry entry) {
mStatusBarNotification = entry.notification;
mOnContentViewInactiveListeners.clear();
mBeforeN = entry.targetSdk < Build.VERSION_CODES.N;
@@ -1292,7 +1292,7 @@ public class NotificationContentView extends FrameLayout {
}
}
- private void applyRemoteInputAndSmartReply(final NotificationData.Entry entry) {
+ private void applyRemoteInputAndSmartReply(final NotificationEntry entry) {
if (mRemoteInputController == null) {
return;
}
@@ -1313,7 +1313,7 @@ public class NotificationContentView extends FrameLayout {
@VisibleForTesting
static SmartRepliesAndActions chooseSmartRepliesAndActions(
SmartReplyConstants smartReplyConstants,
- final NotificationData.Entry entry) {
+ final NotificationEntry entry) {
boolean enableAppGeneratedSmartReplies = (smartReplyConstants.isEnabled()
&& (!smartReplyConstants.requiresTargetingP()
|| entry.targetSdk >= Build.VERSION_CODES.P));
@@ -1370,7 +1370,7 @@ public class NotificationContentView extends FrameLayout {
smartReplies, smartActions, freeformRemoteInputActionPair != null);
}
- private void applyRemoteInput(NotificationData.Entry entry, boolean hasFreeformRemoteInput) {
+ private void applyRemoteInput(NotificationEntry entry, boolean hasFreeformRemoteInput) {
View bigContentView = mExpandedChild;
if (bigContentView != null) {
mExpandedRemoteInput = applyRemoteInput(bigContentView, entry, hasFreeformRemoteInput,
@@ -1402,7 +1402,7 @@ public class NotificationContentView extends FrameLayout {
mCachedHeadsUpRemoteInput = null;
}
- private RemoteInputView applyRemoteInput(View view, NotificationData.Entry entry,
+ private RemoteInputView applyRemoteInput(View view, NotificationEntry entry,
boolean hasRemoteInput, PendingIntent existingPendingIntent,
RemoteInputView cachedView, NotificationViewWrapper wrapper) {
View actionContainerCandidate = view.findViewById(
@@ -1470,7 +1470,7 @@ public class NotificationContentView extends FrameLayout {
}
private void applySmartReplyView(SmartRepliesAndActions smartRepliesAndActions,
- NotificationData.Entry entry) {
+ NotificationEntry entry) {
if (mExpandedChild != null) {
mExpandedSmartReplyView =
applySmartReplyView(mExpandedChild, smartRepliesAndActions, entry);
@@ -1489,14 +1489,14 @@ public class NotificationContentView extends FrameLayout {
}
}
}
- if (mHeadsUpChild != null) {
+ if (mHeadsUpChild != null && mSmartReplyConstants.getShowInHeadsUp()) {
mHeadsUpSmartReplyView =
applySmartReplyView(mHeadsUpChild, smartRepliesAndActions, entry);
}
}
private SmartReplyView applySmartReplyView(View view,
- SmartRepliesAndActions smartRepliesAndActions, NotificationData.Entry entry) {
+ SmartRepliesAndActions smartRepliesAndActions, NotificationEntry entry) {
View smartReplyContainerCandidate = view.findViewById(
com.android.internal.R.id.smart_reply_container);
if (!(smartReplyContainerCandidate instanceof LinearLayout)) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
index ac4e5830d76b..bd1dfb181afe 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
@@ -48,7 +48,7 @@ import com.android.systemui.statusbar.NotificationPresenter;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.notification.NotificationActivityStarter;
-import com.android.systemui.statusbar.notification.NotificationData;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.row.NotificationInfo.CheckSaveListener;
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
import com.android.systemui.statusbar.phone.StatusBar;
@@ -116,7 +116,7 @@ public class NotificationGutsManager implements Dumpable, NotificationLifetimeEx
mNotificationActivityStarter = notificationActivityStarter;
}
- public void onDensityOrFontScaleChanged(NotificationData.Entry entry) {
+ public void onDensityOrFontScaleChanged(NotificationEntry entry) {
setExposedGuts(entry.getGuts());
bindGuts(entry.getRow());
}
@@ -429,7 +429,7 @@ public class NotificationGutsManager implements Dumpable, NotificationLifetimeEx
}
@Override
- public boolean shouldExtendLifetime(NotificationData.Entry entry) {
+ public boolean shouldExtendLifetime(NotificationEntry entry) {
return entry != null
&&(mNotificationGutsExposed != null
&& entry.getGuts() != null
@@ -438,7 +438,7 @@ public class NotificationGutsManager implements Dumpable, NotificationLifetimeEx
}
@Override
- public void setShouldManageLifetime(NotificationData.Entry entry, boolean shouldExtend) {
+ public void setShouldManageLifetime(NotificationEntry entry, boolean shouldExtend) {
if (shouldExtend) {
mKeyToRemoveOnGutsClosed = entry.key;
if (Log.isLoggable(TAG, Log.DEBUG)) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInflater.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInflater.java
index 9908049984d1..42ebfceca334 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInflater.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInflater.java
@@ -37,7 +37,7 @@ import com.android.internal.widget.ImageMessageConsumer;
import com.android.systemui.statusbar.InflationTask;
import com.android.systemui.statusbar.notification.InflationException;
import com.android.systemui.statusbar.notification.MediaNotificationProcessor;
-import com.android.systemui.statusbar.notification.NotificationData;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper;
import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.util.Assert;
@@ -614,7 +614,7 @@ public class NotificationInflater {
@Nullable InflationCallback endListener, ExpandableNotificationRow row,
boolean redactAmbient) {
Assert.isMainThread();
- NotificationData.Entry entry = row.getEntry();
+ NotificationEntry entry = row.getEntry();
NotificationContentView privateLayout = row.getPrivateLayout();
NotificationContentView publicLayout = row.getPublicLayout();
if (runningInflations.isEmpty()) {
@@ -724,7 +724,7 @@ public class NotificationInflater {
* @param entry the entry with the content views set
* @param inflatedFlags the flags associated with the content views that were inflated
*/
- void onAsyncInflationFinished(NotificationData.Entry entry,
+ void onAsyncInflationFinished(NotificationEntry entry,
@InflationFlag int inflatedFlags);
/**
@@ -782,7 +782,7 @@ public class NotificationInflater {
mRedactAmbient = redactAmbient;
mRemoteViewClickHandler = remoteViewClickHandler;
mCallback = callback;
- NotificationData.Entry entry = row.getEntry();
+ NotificationEntry entry = row.getEntry();
entry.setInflationTask(this);
}
@@ -857,7 +857,7 @@ public class NotificationInflater {
}
@Override
- public void onAsyncInflationFinished(NotificationData.Entry entry,
+ public void onAsyncInflationFinished(NotificationEntry entry,
@InflationFlag int inflatedFlags) {
mRow.getEntry().onInflationTaskFinished();
mRow.onNotificationUpdated();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java
index b1eab8069307..5253e38fd675 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java
@@ -123,11 +123,15 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G
private OnClickListener mOnKeepShowing = v -> {
mExitReason = NotificationCounters.BLOCKING_HELPER_KEEP_SHOWING;
closeControls(v);
+ mMetricsLogger.write(getLogMaker().setType(MetricsEvent.NOTIFICATION_BLOCKING_HELPER)
+ .setSubtype(MetricsEvent.BLOCKING_HELPER_CLICK_STAY_SILENT));
};
private OnClickListener mOnToggleSilent = v -> {
Runnable saveImportance = () -> {
swapContent(ACTION_TOGGLE_SILENT, true /* animate */);
+ mMetricsLogger.write(getLogMaker().setType(MetricsEvent.NOTIFICATION_BLOCKING_HELPER)
+ .setSubtype(MetricsEvent.BLOCKING_HELPER_CLICK_ALERT_ME));
};
if (mCheckSaveListener != null) {
mCheckSaveListener.checkSave(saveImportance, mSbn);
@@ -139,6 +143,8 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G
private OnClickListener mOnStopOrMinimizeNotifications = v -> {
Runnable saveImportance = () -> {
swapContent(ACTION_BLOCK, true /* animate */);
+ mMetricsLogger.write(getLogMaker().setType(MetricsEvent.NOTIFICATION_BLOCKING_HELPER)
+ .setSubtype(MetricsEvent.BLOCKING_HELPER_CLICK_BLOCKED));
};
if (mCheckSaveListener != null) {
mCheckSaveListener.checkSave(saveImportance, mSbn);
@@ -153,6 +159,8 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G
logBlockingHelperCounter(NotificationCounters.BLOCKING_HELPER_UNDO);
mMetricsLogger.write(importanceChangeLogMaker().setType(MetricsEvent.TYPE_DISMISS));
swapContent(ACTION_UNDO, true /* animate */);
+ mMetricsLogger.write(getLogMaker().setType(MetricsEvent.NOTIFICATION_BLOCKING_HELPER)
+ .setSubtype(MetricsEvent.BLOCKING_HELPER_CLICK_UNDO));
};
public NotificationInfo(Context context, AttributeSet attrs) {
@@ -251,6 +259,9 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G
bindHeader();
bindPrompt();
bindButtons();
+
+ mMetricsLogger.write(getLogMaker().setType(MetricsEvent.NOTIFICATION_BLOCKING_HELPER)
+ .setSubtype(MetricsEvent.BLOCKING_HELPER_DISPLAY));
}
private void bindHeader() throws RemoteException {
@@ -588,6 +599,8 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G
confirmation.setAlpha(1f);
header.setVisibility(VISIBLE);
header.setAlpha(1f);
+ mMetricsLogger.write(getLogMaker().setType(MetricsEvent.NOTIFICATION_BLOCKING_HELPER)
+ .setSubtype(MetricsEvent.BLOCKING_HELPER_DISMISS));
}
@Override
@@ -733,4 +746,8 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G
}
}
}
+
+ private LogMaker getLogMaker() {
+ return mSbn.getLogMaker().setCategory(MetricsEvent.NOTIFICATION_ITEM);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowInflaterTask.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowInflaterTask.java
index 1741a0b6b37c..0160c547b336 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowInflaterTask.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowInflaterTask.java
@@ -25,7 +25,7 @@ import androidx.asynclayoutinflater.view.AsyncLayoutInflater;
import com.android.systemui.R;
import com.android.systemui.statusbar.InflationTask;
-import com.android.systemui.statusbar.notification.NotificationData;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
/**
* An inflater task that asynchronously inflates a ExpandableNotificationRow
@@ -36,14 +36,14 @@ public class RowInflaterTask implements InflationTask, AsyncLayoutInflater.OnInf
private static final boolean TRACE_ORIGIN = true;
private RowInflationFinishedListener mListener;
- private NotificationData.Entry mEntry;
+ private NotificationEntry mEntry;
private boolean mCancelled;
private Throwable mInflateOrigin;
/**
* Inflates a new notificationView. This should not be called twice on this object
*/
- public void inflate(Context context, ViewGroup parent, NotificationData.Entry entry,
+ public void inflate(Context context, ViewGroup parent, NotificationEntry entry,
RowInflationFinishedListener listener) {
if (TRACE_ORIGIN) {
mInflateOrigin = new Throwable("inflate requested here");
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
index 670908fe175d..1b5013dedb0e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
@@ -25,7 +25,7 @@ import com.android.systemui.R;
import com.android.systemui.statusbar.AmbientPulseManager;
import com.android.systemui.statusbar.NotificationShelf;
import com.android.systemui.statusbar.StatusBarState;
-import com.android.systemui.statusbar.notification.NotificationData;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.ExpandableView;
@@ -75,7 +75,6 @@ public class AmbientState {
private int mIntrinsicPadding;
private int mExpandAnimationTopChange;
private ExpandableNotificationRow mExpandingNotification;
- private int mDarkTopPadding;
private float mDarkAmount;
private boolean mAppearing;
@@ -351,14 +350,15 @@ public class AmbientState {
}
public boolean hasPulsingNotifications() {
- return mPulsing;
+ return mPulsing && mAmbientPulseManager != null
+ && mAmbientPulseManager.hasNotifications();
}
public void setPulsing(boolean hasPulsing) {
mPulsing = hasPulsing;
}
- public boolean isPulsing(NotificationData.Entry entry) {
+ public boolean isPulsing(NotificationEntry entry) {
if (!mPulsing || mAmbientPulseManager == null) {
return false;
}
@@ -458,14 +458,6 @@ public class AmbientState {
return mDarkAmount != 0;
}
- public void setDarkTopPadding(int darkTopPadding) {
- mDarkTopPadding = darkTopPadding;
- }
-
- public int getDarkTopPadding() {
- return mDarkTopPadding;
- }
-
public void setAppearing(boolean appearing) {
mAppearing = appearing;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
index 51180361806b..8ffada43c53f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
@@ -316,7 +316,7 @@ public class NotificationChildrenContainer extends ViewGroup {
StatusBarNotification notification = mContainingNotification.getStatusBarNotification();
final Notification.Builder builder = Notification.Builder.recoverBuilder(getContext(),
notification.getNotification());
- RemoteViews header = builder.makeNotificationHeader(false /* ambient */);
+ RemoteViews header = builder.makeNotificationHeader();
if (mNotificationHeader == null) {
mNotificationHeader = (NotificationHeaderView) header.apply(getContext(), this);
final View expandButton = mNotificationHeader.findViewById(
@@ -344,7 +344,7 @@ public class NotificationChildrenContainer extends ViewGroup {
builder = Notification.Builder.recoverBuilder(getContext(),
notification.getNotification());
}
- header = builder.makeNotificationHeader(true /* ambient */);
+ header = builder.makeNotificationHeader();
if (mNotificationHeaderAmbient == null) {
mNotificationHeaderAmbient = (ViewGroup) header.apply(getContext(), this);
mNotificationHeaderWrapperAmbient = NotificationViewWrapper.wrap(getContext(),
@@ -1171,12 +1171,6 @@ public class NotificationChildrenContainer extends ViewGroup {
return mIsLowPriority && !mContainingNotification.isExpanded();
}
- public void setDark(boolean dark, boolean fade, long delay) {
- if (mOverflowNumber != null) {
- mHybridGroupManager.setOverflowNumberDark(mOverflowNumber, dark, fade, delay);
- }
- }
-
public void reInflateViews(OnClickListener listener, StatusBarNotification notification) {
if (mNotificationHeader != null) {
removeView(mNotificationHeader);
@@ -1227,8 +1221,7 @@ public class NotificationChildrenContainer extends ViewGroup {
public void onNotificationUpdated() {
mHybridGroupManager.setOverflowNumberColor(mOverflowNumber,
- mContainingNotification.getNotificationColor(),
- mContainingNotification.getNotificationColorAmbient());
+ mContainingNotification.getNotificationColor());
}
public int getPositionInLinearLayout(View childInGroup) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationListContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationListContainer.java
index f0a26536d7b8..f771be043c07 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationListContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationListContainer.java
@@ -16,15 +16,14 @@
package com.android.systemui.statusbar.notification.stack;
-import static com.android.systemui.statusbar.notification.ActivityLaunchAnimator
- .ExpandAnimationParameters;
+import static com.android.systemui.statusbar.notification.ActivityLaunchAnimator.ExpandAnimationParameters;
import android.view.View;
import android.view.ViewGroup;
import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
-import com.android.systemui.statusbar.notification.NotificationData;
import com.android.systemui.statusbar.notification.VisibilityLocationProvider;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.logging.NotificationLogger;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.ExpandableView;
@@ -113,19 +112,12 @@ public interface NotificationListContainer extends ExpandableView.OnHeightChange
void setMaxDisplayedNotifications(int maxNotifications);
/**
- * Handle snapping a non-dismissable row back if the user tried to dismiss it.
- *
- * @param entry the entry whose row needs to snap back
- */
- void snapViewIfNeeded(NotificationData.Entry entry);
-
- /**
* Get the view parent for a notification entry. For example, NotificationStackScrollLayout.
*
* @param entry entry to get the view parent for
* @return the view parent for entry
*/
- ViewGroup getViewParentForNotification(NotificationData.Entry entry);
+ ViewGroup getViewParentForNotification(NotificationEntry entry);
/**
* Resets the currently exposed menu view.
@@ -148,7 +140,7 @@ public interface NotificationListContainer extends ExpandableView.OnHeightChange
*
* @param entry the entry whose view's view state needs to be cleaned up (say that 5 times fast)
*/
- void cleanUpViewStateForEntry(NotificationData.Entry entry);
+ void cleanUpViewStateForEntry(NotificationEntry entry);
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java
index 4f0831f1043c..c5ab9f6049c6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java
@@ -18,7 +18,8 @@ package com.android.systemui.statusbar.notification.stack;
import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.NUM_SECTIONS;
-import com.android.systemui.statusbar.notification.NotificationData;
+import com.android.systemui.statusbar.AmbientPulseManager;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.ExpandableView;
@@ -26,35 +27,43 @@ import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
import java.util.HashSet;
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
/**
* A class that manages the roundness for notification views
*/
-class NotificationRoundnessManager implements OnHeadsUpChangedListener {
-
+@Singleton
+class NotificationRoundnessManager implements OnHeadsUpChangedListener,
+ AmbientPulseManager.OnAmbientChangedListener {
+
+ private final ActivatableNotificationView[] mFirstInSectionViews;
+ private final ActivatableNotificationView[] mLastInSectionViews;
+ private final ActivatableNotificationView[] mTmpFirstInSectionViews;
+ private final ActivatableNotificationView[] mTmpLastInSectionViews;
private boolean mExpanded;
- private ActivatableNotificationView[] mFirstInSectionViews;
- private ActivatableNotificationView[] mLastInSectionViews;
- private ActivatableNotificationView[] mTmpFirstInSectionViews;
- private ActivatableNotificationView[] mTmpLastInSectionViews;
private HashSet<ExpandableView> mAnimatedChildren;
private Runnable mRoundingChangedCallback;
private ExpandableNotificationRow mTrackedHeadsUp;
+ private ActivatableNotificationView mTrackedAmbient;
private float mAppearFraction;
- NotificationRoundnessManager() {
+ @Inject
+ NotificationRoundnessManager(AmbientPulseManager ambientPulseManager) {
mFirstInSectionViews = new ActivatableNotificationView[NUM_SECTIONS];
mLastInSectionViews = new ActivatableNotificationView[NUM_SECTIONS];
mTmpFirstInSectionViews = new ActivatableNotificationView[NUM_SECTIONS];
mTmpLastInSectionViews = new ActivatableNotificationView[NUM_SECTIONS];
+ ambientPulseManager.addListener(this);
}
@Override
- public void onHeadsUpPinned(NotificationData.Entry headsUp) {
+ public void onHeadsUpPinned(NotificationEntry headsUp) {
updateView(headsUp.getRow(), false /* animate */);
}
@Override
- public void onHeadsUpUnPinned(NotificationData.Entry headsUp) {
+ public void onHeadsUpUnPinned(NotificationEntry headsUp) {
updateView(headsUp.getRow(), true /* animate */);
}
@@ -63,6 +72,17 @@ class NotificationRoundnessManager implements OnHeadsUpChangedListener {
updateView(row, false /* animate */);
}
+ @Override
+ public void onAmbientStateChanged(NotificationEntry entry, boolean isPulsing) {
+ ActivatableNotificationView row = entry.getRow();
+ if (isPulsing) {
+ mTrackedAmbient = row;
+ } else if (mTrackedAmbient == row) {
+ mTrackedAmbient = null;
+ }
+ updateView(row, false /* animate */);
+ }
+
private void updateView(ActivatableNotificationView view, boolean animate) {
boolean changed = updateViewWithoutCallback(view, animate);
if (changed) {
@@ -125,6 +145,9 @@ class NotificationRoundnessManager implements OnHeadsUpChangedListener {
// rounded.
return 1.0f;
}
+ if (view == mTrackedAmbient) {
+ return 1.0f;
+ }
return 0.0f;
}
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 8deb7d5e8456..d0665670bf8f 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
@@ -96,12 +96,13 @@ import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.StatusBarStateController.StateListener;
import com.android.systemui.statusbar.notification.FakeShadowView;
-import com.android.systemui.statusbar.notification.NotificationData;
+import com.android.systemui.statusbar.notification.NotificationEntryListener;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.NotificationUtils;
import com.android.systemui.statusbar.notification.ShadeViewRefactor;
import com.android.systemui.statusbar.notification.ShadeViewRefactor.RefactorComponent;
import com.android.systemui.statusbar.notification.VisualStabilityManager;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.logging.NotificationLogger;
import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
@@ -112,6 +113,7 @@ import com.android.systemui.statusbar.notification.row.NotificationGuts;
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
import com.android.systemui.statusbar.notification.row.NotificationSnooze;
import com.android.systemui.statusbar.notification.row.StackScrollerDecorView;
+import com.android.systemui.statusbar.phone.DozeParameters;
import com.android.systemui.statusbar.phone.HeadsUpAppearanceController;
import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
import com.android.systemui.statusbar.phone.HeadsUpTouchHelper;
@@ -199,12 +201,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd
private int mPaddingBetweenElements;
private int mIncreasedPaddingBetweenElements;
private int mMaxTopPadding;
- private int mRegularTopPadding;
- private int mDarkTopPadding;
- // Current padding, will be either mRegularTopPadding or mDarkTopPadding
private int mTopPadding;
- // Distance between AOD separator and shelf
- private int mDarkShelfPadding;
private int mBottomMargin;
private int mBottomInset = 0;
private float mQsExpansionFraction;
@@ -316,7 +313,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd
private HashSet<Pair<ExpandableNotificationRow, Boolean>> mHeadsUpChangeAnimations
= new HashSet<>();
private HeadsUpManagerPhone mHeadsUpManager;
- private NotificationRoundnessManager mRoundnessManager = new NotificationRoundnessManager();
+ private final NotificationRoundnessManager mRoundnessManager;
private boolean mTrackingHeadsUp;
private ScrimController mScrimController;
private boolean mForceNoOverlappingRendering;
@@ -429,7 +426,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd
private int mHeadsUpInset;
private HeadsUpAppearanceController mHeadsUpAppearanceController;
private NotificationIconAreaController mIconAreaController;
- private float mVerticalPanelTranslation;
+ private float mHorizontalPanelTranslation;
private final NotificationLockscreenUserManager mLockscreenUserManager =
Dependency.get(NotificationLockscreenUserManager.class);
protected final NotificationGutsManager mGutsManager =
@@ -439,7 +436,8 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd
Dependency.get(NotificationEntryManager.class);
private final IStatusBarService mBarService = IStatusBarService.Stub.asInterface(
ServiceManager.getService(Context.STATUS_BAR_SERVICE));
- private final MetricsLogger mMetricsLogger = Dependency.get(MetricsLogger.class);
+ @VisibleForTesting
+ protected final MetricsLogger mMetricsLogger = Dependency.get(MetricsLogger.class);
private final NotificationRemoteInputManager mRemoteInputManager =
Dependency.get(NotificationRemoteInputManager.class);
private final SysuiColorExtractor mColorExtractor = Dependency.get(SysuiColorExtractor.class);
@@ -457,12 +455,17 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd
private final NotificationGutsManager
mNotificationGutsManager = Dependency.get(NotificationGutsManager.class);
+ /**
+ * If the {@link NotificationShelf} should be visible when dark.
+ */
+ private boolean mShowDarkShelf;
@Inject
public NotificationStackScrollLayout(
@Named(VIEW_CONTEXT) Context context,
AttributeSet attrs,
- @Named(ALLOW_NOTIFICATION_LONG_PRESS_NAME) boolean allowLongPress) {
+ @Named(ALLOW_NOTIFICATION_LONG_PRESS_NAME) boolean allowLongPress,
+ NotificationRoundnessManager notificationRoundnessManager) {
super(context, attrs, 0, 0);
Resources res = getResources();
@@ -473,6 +476,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd
}
mAmbientState = new AmbientState(context);
+ mRoundnessManager = notificationRoundnessManager;
mBgColor = context.getColor(R.color.notification_shade_background_color);
int minHeight = res.getDimensionPixelSize(R.dimen.notification_min_height);
int maxHeight = res.getDimensionPixelSize(R.dimen.notification_max_height);
@@ -489,7 +493,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd
res.getBoolean(R.bool.config_drawNotificationBackground);
mFadeNotificationsOnDismiss =
res.getBoolean(R.bool.config_fadeNotificationsOnDismiss);
- mDarkShelfPadding = res.getDimensionPixelSize(R.dimen.widget_bottom_separator_padding);
mRoundnessManager.setAnimatedChildren(mChildrenToAddAnimated);
mRoundnessManager.setOnRoundingChangedCallback(this::invalidate);
addOnExpandedHeightListener(mRoundnessManager::setExpanded);
@@ -517,6 +520,17 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd
mLowPriorityBeforeSpeedBump = "1".equals(newValue);
}
}, LOW_PRIORITY);
+
+ mEntryManager.addNotificationEntryListener(new NotificationEntryListener() {
+ @Override
+ public void onPostEntryUpdated(NotificationEntry entry) {
+ if (!entry.notification.isClearable()) {
+ // The user may have performed a dismiss action on the notification, since it's
+ // not clearable we should snap it back.
+ snapViewIfNeeded(entry);
+ }
+ }
+ });
}
@Override
@@ -590,14 +604,14 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd
@ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
public RemoteInputController.Delegate createDelegate() {
return new RemoteInputController.Delegate() {
- public void setRemoteInputActive(NotificationData.Entry entry,
+ public void setRemoteInputActive(NotificationEntry entry,
boolean remoteInputActive) {
mHeadsUpManager.setRemoteInputActive(entry, remoteInputActive);
entry.notifyHeightChanged(true /* needsAnimation */);
updateFooter();
}
- public void lockScrollTo(NotificationData.Entry entry) {
+ public void lockScrollTo(NotificationEntry entry) {
NotificationStackScrollLayout.this.lockScrollTo(entry.getRow());
}
@@ -667,7 +681,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd
int lockScreenTop = mSections[0].getCurrentBounds().top;
int lockScreenBottom = mSections[NUM_SECTIONS - 1].getCurrentBounds().bottom;
int darkLeft = getWidth() / 2;
- int darkTop = mRegularTopPadding;
+ int darkTop = mTopPadding;
float yProgress = 1 - mInterpolatedDarkAmount;
float xProgress = mDarkXInterpolator.getInterpolation(
@@ -910,7 +924,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd
@Override
@ShadeViewRefactor(RefactorComponent.LAYOUT_ALGORITHM)
- public boolean isInVisibleLocation(NotificationData.Entry entry) {
+ public boolean isInVisibleLocation(NotificationEntry entry) {
ExpandableNotificationRow row = entry.getRow();
ExpandableViewState childViewState = row.getViewState();
@@ -935,8 +949,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd
@ShadeViewRefactor(RefactorComponent.LAYOUT_ALGORITHM)
private void updateAlgorithmHeightAndPadding() {
- mTopPadding = (int) MathUtils.lerp(mRegularTopPadding, mDarkTopPadding,
- mInterpolatedDarkAmount);
mAmbientState.setLayoutHeight(getLayoutHeight());
updateAlgorithmLayoutMinHeight();
mAmbientState.setTopPadding(mTopPadding);
@@ -1074,10 +1086,8 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd
@ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
private void setTopPadding(int topPadding, boolean animate) {
- if (mRegularTopPadding != topPadding) {
- mRegularTopPadding = topPadding;
- mDarkTopPadding = topPadding + mDarkShelfPadding;
- mAmbientState.setDarkTopPadding(mDarkTopPadding);
+ if (mTopPadding != topPadding) {
+ mTopPadding = topPadding;
updateAlgorithmHeightAndPadding();
updateContentHeight();
if (animate && mAnimationsEnabled && mIsExpanded) {
@@ -1184,7 +1194,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd
mIsClipped = clipped;
}
- if (mPulsing) {
+ if (mPulsing || mAmbientState.isFullyDark() && mShowDarkShelf) {
setClipBounds(null);
} else if (mAmbientState.isDarkAtAll()) {
setClipBounds(mBackgroundAnimationRect);
@@ -1224,13 +1234,13 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd
*/
@ShadeViewRefactor(RefactorComponent.COORDINATOR)
private int getTopHeadsUpPinnedHeight() {
- NotificationData.Entry topEntry = mHeadsUpManager.getTopEntry();
+ NotificationEntry topEntry = mHeadsUpManager.getTopEntry();
if (topEntry == null) {
return 0;
}
ExpandableNotificationRow row = topEntry.getRow();
if (row.isChildInGroup()) {
- final NotificationData.Entry groupSummary
+ final NotificationEntry groupSummary
= mGroupManager.getGroupSummary(row.getStatusBarNotification());
if (groupSummary != null) {
row = groupSummary.getRow();
@@ -1405,7 +1415,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd
&& touchY >= top && touchY <= bottom && touchX >= left && touchX <= right) {
if (slidingChild instanceof ExpandableNotificationRow) {
ExpandableNotificationRow row = (ExpandableNotificationRow) slidingChild;
- NotificationData.Entry entry = row.getEntry();
+ NotificationEntry entry = row.getEntry();
if (!mIsExpanded && row.isHeadsUp() && row.isPinned()
&& mHeadsUpManager.getTopEntry().getRow() != row
&& mGroupManager.getGroupSummary(
@@ -1538,9 +1548,8 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd
true /* isDismissAll */);
}
- @Override
@ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
- public void snapViewIfNeeded(NotificationData.Entry entry) {
+ private void snapViewIfNeeded(NotificationEntry entry) {
ExpandableNotificationRow child = entry.getRow();
boolean animate = mIsExpanded || isPinnedHeadsUp(child);
// If the child is showing the notification menu snap to that
@@ -1550,7 +1559,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd
@Override
@ShadeViewRefactor(RefactorComponent.ADAPTER)
- public ViewGroup getViewParentForNotification(NotificationData.Entry entry) {
+ public ViewGroup getViewParentForNotification(NotificationEntry entry) {
return this;
}
@@ -2043,17 +2052,14 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd
}
mIntrinsicContentHeight = height;
- // We don't want to use the toppadding since that might be interpolated and we want
- // to take the final value of the animation.
- int topPadding = mAmbientState.isFullyDark() ? mDarkTopPadding : mRegularTopPadding;
- mContentHeight = height + topPadding + mBottomMargin;
+ mContentHeight = height + mTopPadding + mBottomMargin;
updateScrollability();
clampScrollPosition();
mAmbientState.setLayoutMaxHeight(mContentHeight);
}
@ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
- private boolean isPulsing(NotificationData.Entry entry) {
+ private boolean isPulsing(NotificationEntry entry) {
return mAmbientState.isPulsing(entry);
}
@@ -2210,13 +2216,16 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd
int top = 0;
if (section != null) {
ActivatableNotificationView firstView = section.getFirstVisibleChild();
- // Round Y up to avoid seeing the background during animation
- int finalTranslationY = (int) Math.ceil(ViewState.getFinalTranslationY(firstView));
- if (alreadyAnimating || section.isTargetTop(finalTranslationY)) {
- // we're ending up at the same location as we are now, let's just skip the animation
- top = finalTranslationY;
- } else {
- top = (int) Math.ceil(firstView.getTranslationY());
+ if (firstView != null) {
+ // Round Y up to avoid seeing the background during animation
+ int finalTranslationY = (int) Math.ceil(ViewState.getFinalTranslationY(firstView));
+ if (alreadyAnimating || section.isTargetTop(finalTranslationY)) {
+ // we're ending up at the same location as we are now, let's just skip the
+ // animation
+ top = finalTranslationY;
+ } else {
+ top = (int) Math.ceil(firstView.getTranslationY());
+ }
}
}
return top;
@@ -2558,7 +2567,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd
@ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
@Override
- public void cleanUpViewStateForEntry(NotificationData.Entry entry) {
+ public void cleanUpViewStateForEntry(NotificationEntry entry) {
View child = entry.getRow();
if (child == mSwipeHelper.getTranslatingParentView()) {
mSwipeHelper.clearTranslatingParentView();
@@ -2686,7 +2695,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd
private boolean isChildInInvisibleGroup(View child) {
if (child instanceof ExpandableNotificationRow) {
ExpandableNotificationRow row = (ExpandableNotificationRow) child;
- NotificationData.Entry groupSummary =
+ NotificationEntry groupSummary =
mGroupManager.getGroupSummary(row.getStatusBarNotification());
if (groupSummary != null && groupSummary.getRow() != row) {
return row.getVisibility() == View.INVISIBLE;
@@ -4350,6 +4359,9 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd
if (mAmbientState.isDark() == dark) {
return;
}
+ if (!dark) {
+ mShowDarkShelf = false;
+ }
mAmbientState.setDark(dark);
if (animate && mAnimationsEnabled) {
mDarkNeedsAnimation = true;
@@ -4366,12 +4378,12 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd
@ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
private void updatePanelTranslation() {
- setTranslationX(mVerticalPanelTranslation + mAntiBurnInOffsetX * mInterpolatedDarkAmount);
+ setTranslationX(mHorizontalPanelTranslation + mAntiBurnInOffsetX * mInterpolatedDarkAmount);
}
@ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
- public void setVerticalPanelTranslation(float verticalPanelTranslation) {
- mVerticalPanelTranslation = verticalPanelTranslation;
+ public void setHorizontalPanelTranslation(float verticalPanelTranslation) {
+ mHorizontalPanelTranslation = verticalPanelTranslation;
updatePanelTranslation();
}
@@ -4411,9 +4423,9 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd
boolean nowDarkAtAll = mAmbientState.isDarkAtAll();
if (nowFullyDark != wasFullyDark) {
updateContentHeight();
- }
- if (mIconAreaController != null) {
- mIconAreaController.setDarkAmount(interpolatedDarkAmount);
+ if (nowFullyDark && mShowDarkShelf) {
+ updateDarkShelfVisibility();
+ }
}
if (!wasDarkAtAll && nowDarkAtAll) {
resetExposedMenuView(true /* animate */, true /* animate */);
@@ -4424,6 +4436,22 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd
requestChildrenUpdate();
}
+ /**
+ * If the shelf should be visible when the device is in ambient mode (dozing.)
+ */
+ @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
+ public void setShowDarkShelf(boolean showDarkShelf) {
+ mShowDarkShelf = showDarkShelf;
+ }
+
+ private void updateDarkShelfVisibility() {
+ DozeParameters dozeParameters = DozeParameters.getInstance(mContext);
+ if (dozeParameters.shouldControlScreenOff()) {
+ mShelf.fadeInTranslating();
+ }
+ updateClipping();
+ }
+
@ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
public void notifyDarkAnimationStart(boolean dark) {
// We only swap the scaling factor if we're fully dark or fully awake to avoid
@@ -4705,7 +4733,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd
mHeadsUpManager.setAnimationStateHandler(this::setHeadsUpGoingAwayAnimationsAllowed);
}
- public void generateHeadsUpAnimation(NotificationData.Entry entry, boolean isHeadsUp) {
+ public void generateHeadsUpAnimation(NotificationEntry entry, boolean isHeadsUp) {
ExpandableNotificationRow row = entry.getHeadsUpAnimationView();
generateHeadsUpAnimation(row, isHeadsUp);
}
@@ -5183,9 +5211,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd
// another "changeViewPosition" call is ever added.
changeViewPosition(mShelf,
getChildCount() - offsetFromEnd);
-
- // Scrim opacity varies based on notification count
- mScrimController.setNotificationCount(getNotGoneChildCount());
}
@ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
@@ -5603,8 +5628,9 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd
}
};
+ @VisibleForTesting
@ShadeViewRefactor(RefactorComponent.INPUT)
- private final OnMenuEventListener mMenuEventListener = new OnMenuEventListener() {
+ protected final OnMenuEventListener mMenuEventListener = new OnMenuEventListener() {
@Override
public void onMenuClicked(View view, int x, int y, MenuItem item) {
if (mLongPressListener == null) {
@@ -5612,8 +5638,10 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd
}
if (view instanceof ExpandableNotificationRow) {
ExpandableNotificationRow row = (ExpandableNotificationRow) view;
- MetricsLogger.action(mContext, MetricsEvent.ACTION_TOUCH_GEAR,
- row.getStatusBarNotification().getPackageName());
+ mMetricsLogger.write(row.getStatusBarNotification().getLogMaker()
+ .setCategory(MetricsEvent.ACTION_TOUCH_GEAR)
+ .setType(MetricsEvent.TYPE_ACTION)
+ );
}
mLongPressListener.onLongPress(view, x, y, item);
}
@@ -5636,8 +5664,9 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd
public void onMenuShown(View row) {
if (row instanceof ExpandableNotificationRow) {
ExpandableNotificationRow notificationRow = (ExpandableNotificationRow) row;
- MetricsLogger.action(mContext, MetricsEvent.ACTION_REVEAL_GEAR,
- notificationRow.getStatusBarNotification().getPackageName());
+ mMetricsLogger.write(notificationRow.getStatusBarNotification().getLogMaker()
+ .setCategory(MetricsEvent.ACTION_REVEAL_GEAR)
+ .setType(MetricsEvent.TYPE_ACTION));
mHeadsUpManager.setMenuShown(notificationRow.getEntry(), true);
}
mSwipeHelper.onMenuShown(row);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
index 19fce485b0c7..b4c205ab980c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
@@ -426,6 +426,9 @@ public class StackStateAnimator {
mTmpState.yTranslation += mPulsingAppearingTranslation;
mTmpState.alpha = 0;
mTmpState.applyToView(changingView);
+
+ mTmpState.copyFrom(mShelf.getViewState());
+ mTmpState.applyToView(mShelf);
}
} else if (event.animationType == NotificationStackScrollLayout
.AnimationEvent.ANIMATION_TYPE_PULSE_DISAPPEAR) {
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 f907b6523bef..35763dce5529 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java
@@ -215,7 +215,10 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue
}
}
- if (mStatusBarStateController.isDozing()) {
+ // The shelf will be hidden when dozing with a custom clock, we must show notification
+ // icons in this occasion.
+ if (mStatusBarStateController.isDozing()
+ && mStatusBarComponent.getPanel().hasCustomClock()) {
state |= DISABLE_CLOCK | DISABLE_SYSTEM_INFO;
state &= ~DISABLE_NOTIFICATION_ICONS;
}
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 6f2b63d2e64d..10497734d0b1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
@@ -158,8 +158,7 @@ public class DozeParameters implements TunerService.Tunable {
* @return duration in millis.
*/
public long getWallpaperAodDuration() {
- if (mAmbientDisplayConfiguration.wakeLockScreenGestureEnabled(UserHandle.USER_CURRENT)
- || shouldControlScreenOff()) {
+ if (shouldControlScreenOff()) {
return DozeScreenState.ENTER_DOZE_HIDE_WALLPAPER_DELAY;
}
return mAlwaysOnPolicy.wallpaperVisibilityDuration;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
index d1e488ab2b4d..876b9028a9b6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
@@ -28,7 +28,7 @@ import com.android.systemui.R;
import com.android.systemui.plugins.DarkIconDispatcher;
import com.android.systemui.statusbar.CrossFadeHelper;
import com.android.systemui.statusbar.HeadsUpStatusBarView;
-import com.android.systemui.statusbar.notification.NotificationData;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
@@ -143,7 +143,7 @@ public class HeadsUpAppearanceController implements OnHeadsUpChangedListener,
}
@Override
- public void onHeadsUpPinned(NotificationData.Entry entry) {
+ public void onHeadsUpPinned(NotificationEntry entry) {
updateTopEntry();
updateHeader(entry);
}
@@ -206,11 +206,11 @@ public class HeadsUpAppearanceController implements OnHeadsUpChangedListener,
}
private void updateTopEntry() {
- NotificationData.Entry newEntry = null;
+ NotificationEntry newEntry = null;
if (!mIsExpanded && mHeadsUpManager.hasPinnedHeadsUp()) {
newEntry = mHeadsUpManager.getTopEntry();
}
- NotificationData.Entry previousEntry = mHeadsUpStatusBarView.getShowingEntry();
+ NotificationEntry previousEntry = mHeadsUpStatusBarView.getShowingEntry();
mHeadsUpStatusBarView.setEntry(newEntry);
if (newEntry != previousEntry) {
boolean animateIsolation = false;
@@ -298,7 +298,7 @@ public class HeadsUpAppearanceController implements OnHeadsUpChangedListener,
}
@Override
- public void onHeadsUpUnPinned(NotificationData.Entry entry) {
+ public void onHeadsUpUnPinned(NotificationEntry entry) {
updateTopEntry();
updateHeader(entry);
}
@@ -338,7 +338,7 @@ public class HeadsUpAppearanceController implements OnHeadsUpChangedListener,
});
}
- public void updateHeader(NotificationData.Entry entry) {
+ public void updateHeader(NotificationEntry entry) {
ExpandableNotificationRow row = entry.getRow();
float headerVisibleAmount = 1.0f;
if (row.isPinned() || row.isHeadsUpAnimatingAway() || row == mTrackedChild) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
index f4cfd4197637..7f75223407de 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
@@ -41,8 +41,8 @@ import com.android.systemui.bubbles.BubbleController;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.StatusBarStateController.StateListener;
-import com.android.systemui.statusbar.notification.NotificationData;
import com.android.systemui.statusbar.notification.VisualStabilityManager;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.HeadsUpManager;
@@ -58,7 +58,7 @@ import java.util.Stack;
*/
public class HeadsUpManagerPhone extends HeadsUpManager implements Dumpable,
ViewTreeObserver.OnComputeInternalInsetsListener, VisualStabilityManager.Callback,
- OnHeadsUpChangedListener, ConfigurationController.ConfigurationListener {
+ OnHeadsUpChangedListener, ConfigurationController.ConfigurationListener, StateListener {
private static final String TAG = "HeadsUpManagerPhone";
private final View mStatusBarWindowView;
@@ -72,8 +72,8 @@ public class HeadsUpManagerPhone extends HeadsUpManager implements Dumpable,
private int mDisplayCutoutTouchableRegionSize;
private boolean mTrackingHeadsUp;
private HashSet<String> mSwipedOutKeys = new HashSet<>();
- private HashSet<NotificationData.Entry> mEntriesToRemoveAfterExpand = new HashSet<>();
- private ArraySet<NotificationData.Entry> mEntriesToRemoveWhenReorderingAllowed
+ private HashSet<NotificationEntry> mEntriesToRemoveAfterExpand = new HashSet<>();
+ private ArraySet<NotificationEntry> mEntriesToRemoveWhenReorderingAllowed
= new ArraySet<>();
private boolean mIsExpanded;
private int[] mTmpTwoArray = new int[2];
@@ -83,7 +83,6 @@ public class HeadsUpManagerPhone extends HeadsUpManager implements Dumpable,
private boolean mIsObserving;
private int mStatusBarState;
- private final StateListener mStateListener = this::setStatusBarState;
private AnimationStateHandler mAnimationStateHandler;
private BubbleController mBubbleController = Dependency.get(BubbleController.class);
@@ -129,7 +128,7 @@ public class HeadsUpManagerPhone extends HeadsUpManager implements Dumpable,
updateTouchableRegionListener();
}
});
- Dependency.get(StatusBarStateController.class).addCallback(mStateListener);
+ Dependency.get(StatusBarStateController.class).addCallback(this);
mBubbleController.setBubbleStateChangeListener((hasBubbles) -> {
if (!hasBubbles) {
mBubbleGoingAway = true;
@@ -143,7 +142,7 @@ public class HeadsUpManagerPhone extends HeadsUpManager implements Dumpable,
}
public void destroy() {
- Dependency.get(StatusBarStateController.class).removeCallback(mStateListener);
+ Dependency.get(StatusBarStateController.class).removeCallback(this);
}
private void initResources() {
@@ -187,7 +186,7 @@ public class HeadsUpManagerPhone extends HeadsUpManager implements Dumpable,
releaseAllImmediately();
mReleaseOnExpandFinish = false;
} else {
- for (NotificationData.Entry entry : mEntriesToRemoveAfterExpand) {
+ for (NotificationEntry entry : mEntriesToRemoveAfterExpand) {
if (isAlerting(entry.key)) {
// Maybe the heads-up was removed already
removeAlertEntry(entry.key);
@@ -225,11 +224,9 @@ public class HeadsUpManagerPhone extends HeadsUpManager implements Dumpable,
}
}
- /**
- * Set the current state of the statusbar.
- */
- private void setStatusBarState(int statusBarState) {
- mStatusBarState = statusBarState;
+ @Override
+ public void onStateChanged(int newState) {
+ mStatusBarState = newState;
}
/**
@@ -252,7 +249,7 @@ public class HeadsUpManagerPhone extends HeadsUpManager implements Dumpable,
* @param remoteInputActive True to notify active, False to notify inactive.
*/
public void setRemoteInputActive(
- @NonNull NotificationData.Entry entry, boolean remoteInputActive) {
+ @NonNull NotificationEntry entry, boolean remoteInputActive) {
HeadsUpEntryPhone headsUpEntry = getHeadsUpEntryPhone(entry.key);
if (headsUpEntry != null && headsUpEntry.remoteInputActive != remoteInputActive) {
headsUpEntry.remoteInputActive = remoteInputActive;
@@ -268,7 +265,7 @@ public class HeadsUpManagerPhone extends HeadsUpManager implements Dumpable,
* Sets whether an entry's menu row is exposed and therefore it should stick in the heads up
* area if it's pinned until it's hidden again.
*/
- public void setMenuShown(@NonNull NotificationData.Entry entry, boolean menuShown) {
+ public void setMenuShown(@NonNull NotificationEntry entry, boolean menuShown) {
HeadsUpEntry headsUpEntry = getHeadsUpEntry(entry.key);
if (headsUpEntry instanceof HeadsUpEntryPhone && entry.isRowPinned()) {
((HeadsUpEntryPhone) headsUpEntry).setMenuShownPinned(menuShown);
@@ -315,9 +312,9 @@ public class HeadsUpManagerPhone extends HeadsUpManager implements Dumpable,
return;
}
if (hasPinnedHeadsUp()) {
- NotificationData.Entry topEntry = getTopEntry();
+ NotificationEntry topEntry = getTopEntry();
if (topEntry.isChildInGroup()) {
- final NotificationData.Entry groupSummary
+ final NotificationEntry groupSummary
= mGroupManager.getGroupSummary(topEntry.notification);
if (groupSummary != null) {
topEntry = groupSummary;
@@ -374,7 +371,7 @@ public class HeadsUpManagerPhone extends HeadsUpManager implements Dumpable,
@Override
public void onReorderingAllowed() {
mAnimationStateHandler.setHeadsUpGoingAwayAnimationsAllowed(false);
- for (NotificationData.Entry entry : mEntriesToRemoveWhenReorderingAllowed) {
+ for (NotificationEntry entry : mEntriesToRemoveWhenReorderingAllowed) {
if (isAlerting(entry.key)) {
// Maybe the heads-up was removed already
removeAlertEntry(entry.key);
@@ -399,7 +396,7 @@ public class HeadsUpManagerPhone extends HeadsUpManager implements Dumpable,
}
@Override
- protected boolean shouldHeadsUpBecomePinned(NotificationData.Entry entry) {
+ protected boolean shouldHeadsUpBecomePinned(NotificationEntry entry) {
return mStatusBarState != StatusBarState.KEYGUARD && !mIsExpanded
|| super.shouldHeadsUpBecomePinned(entry);
}
@@ -488,7 +485,7 @@ public class HeadsUpManagerPhone extends HeadsUpManager implements Dumpable,
return super.isSticky() || mMenuShownPinned;
}
- public void setEntry(@NonNull final NotificationData.Entry entry) {
+ public void setEntry(@NonNull final NotificationEntry entry) {
Runnable removeHeadsUpRunnable = () -> {
if (!mVisualStabilityManager.isReorderingAllowed()) {
mEntriesToRemoveWhenReorderingAllowed.add(entry);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java
index 9c1c71a2dec8..dd200da56d20 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java
@@ -21,7 +21,7 @@ import android.view.MotionEvent;
import android.view.ViewConfiguration;
import com.android.systemui.Gefingerpoken;
-import com.android.systemui.statusbar.notification.NotificationData;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.ExpandableView;
@@ -83,7 +83,7 @@ public class HeadsUpTouchHelper implements Gefingerpoken {
} else if (child == null && !mCallback.isExpanded()) {
// We might touch above the visible heads up child, but then we still would
// like to capture it.
- NotificationData.Entry topEntry = mHeadsUpManager.getTopEntry();
+ NotificationEntry topEntry = mHeadsUpManager.getTopEntry();
if (topEntry != null && topEntry.isRowPinned()) {
mPickedChild = topEntry.getRow();
mTouchingHeadsUpView = true;
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 a81b7e572a79..c68fdd4ec4a7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
@@ -91,11 +91,6 @@ public class KeyguardClockPositionAlgorithm {
private int mBurnInPreventionOffsetY;
/**
- * Clock vertical padding when pulsing.
- */
- private int mPulsingPadding;
-
- /**
* Doze/AOD transition amount.
*/
private float mDarkAmount;
@@ -105,10 +100,6 @@ public class KeyguardClockPositionAlgorithm {
*/
private boolean mCurrentlySecure;
- /**
- * Dozing and receiving a notification (AOD notification.)
- */
- private boolean mPulsing;
private float mEmptyDragAmount;
/**
@@ -123,13 +114,11 @@ public class KeyguardClockPositionAlgorithm {
R.dimen.burn_in_prevention_offset_x);
mBurnInPreventionOffsetY = res.getDimensionPixelSize(
R.dimen.burn_in_prevention_offset_y);
- mPulsingPadding = res.getDimensionPixelSize(
- R.dimen.widget_pulsing_bottom_padding);
}
public void setup(int minTopMargin, int maxShadeBottom, int notificationStackHeight,
float panelExpansion, int parentHeight, int keyguardStatusHeight, float dark,
- boolean secure, boolean pulsing, float emptyDragAmount) {
+ boolean secure, float emptyDragAmount) {
mMinTopMargin = minTopMargin + mContainerTopPadding;
mMaxShadeBottom = maxShadeBottom;
mNotificationStackHeight = notificationStackHeight;
@@ -138,7 +127,6 @@ public class KeyguardClockPositionAlgorithm {
mKeyguardStatusHeight = keyguardStatusHeight;
mDarkAmount = dark;
mCurrentlySecure = secure;
- mPulsing = pulsing;
mEmptyDragAmount = emptyDragAmount;
}
@@ -146,7 +134,7 @@ public class KeyguardClockPositionAlgorithm {
final int y = getClockY();
result.clockY = y;
result.clockAlpha = getClockAlpha(y);
- result.stackScrollerPadding = y + (mPulsing ? mPulsingPadding : mKeyguardStatusHeight);
+ result.stackScrollerPadding = y + mKeyguardStatusHeight;
result.clockX = (int) interpolate(0, burnInPreventionOffsetX(), mDarkAmount);
}
@@ -185,9 +173,6 @@ public class KeyguardClockPositionAlgorithm {
private int getClockY() {
// Dark: Align the bottom edge of the clock at about half of the screen:
float clockYDark = getMaxClockY() + burnInPreventionOffsetY();
- if (mPulsing) {
- clockYDark -= mPulsingPadding;
- }
clockYDark = MathUtils.max(0, clockYDark);
float clockYRegular = getExpandedClockPosition();
@@ -230,11 +215,6 @@ public class KeyguardClockPositionAlgorithm {
- mBurnInPreventionOffsetX;
}
- @VisibleForTesting
- void setPulsingPadding(int padding) {
- mPulsingPadding = padding;
- }
-
public static class Result {
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardEnvironmentImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardEnvironmentImpl.java
index 927228eae20e..925a19d6f5eb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardEnvironmentImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardEnvironmentImpl.java
@@ -22,7 +22,7 @@ import android.util.Log;
import com.android.systemui.Dependency;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
-import com.android.systemui.statusbar.notification.NotificationData.KeyguardEnvironment;
+import com.android.systemui.statusbar.notification.collection.NotificationData.KeyguardEnvironment;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
public class KeyguardEnvironmentImpl implements KeyguardEnvironment {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationAssistantAction.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationAssistantAction.java
new file mode 100644
index 000000000000..323e7761b475
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationAssistantAction.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.phone;
+
+import android.annotation.NonNull;
+import android.os.Bundle;
+import android.view.MotionEvent;
+
+import com.android.systemui.assist.AssistManager;
+import com.android.systemui.recents.OverviewProxyService;
+
+/**
+ * Assistant is triggered with this action
+ */
+public class NavigationAssistantAction extends NavigationGestureAction {
+ private static final String TAG = "NavigationAssistantActions";
+
+ private final AssistManager mAssistManager;
+
+ public NavigationAssistantAction(@NonNull NavigationBarView navigationBarView,
+ @NonNull OverviewProxyService service, AssistManager assistManager) {
+ super(navigationBarView, service);
+ mAssistManager = assistManager;
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return true;
+ }
+
+ @Override
+ public boolean disableProxyEvents() {
+ return true;
+ }
+
+ @Override
+ public void onGestureStart(MotionEvent event) {
+ mAssistManager.startAssist(new Bundle());
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBackAction.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBackAction.java
index 9c8b1b1e5227..7a42b03947ae 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBackAction.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBackAction.java
@@ -67,7 +67,7 @@ public class NavigationBackAction extends NavigationGestureAction {
@Override
public boolean isEnabled() {
- return !getGlobalBoolean(NavigationPrototypeController.NAVBAR_EXPERIMENTS_DISABLED);
+ return true;
}
@Override
@@ -95,6 +95,11 @@ public class NavigationBackAction extends NavigationGestureAction {
}
}
+ @Override
+ public boolean disableProxyEvents() {
+ return true;
+ }
+
private void performBack() {
sendEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK);
sendEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BACK);
@@ -102,8 +107,7 @@ public class NavigationBackAction extends NavigationGestureAction {
}
private boolean shouldExecuteBackOnUp() {
- return !getGlobalBoolean(NavigationPrototypeController.NAVBAR_EXPERIMENTS_DISABLED)
- && getGlobalBoolean(BACK_AFTER_END_PROP);
+ return getGlobalBoolean(BACK_AFTER_END_PROP);
}
private void sendEvent(int action, int code) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
index 6d97d6724d40..d3c6a1d8ee74 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
@@ -269,7 +269,7 @@ public class NavigationBarFragment extends LifecycleFragment implements Callback
mIsOnDefaultDisplay = mDisplayId == Display.DEFAULT_DISPLAY;
}
- mNavigationBarView.setComponents(mStatusBar.getPanel());
+ mNavigationBarView.setComponents(mStatusBar.getPanel(), mAssistManager);
mNavigationBarView.setDisabledFlags(mDisabledFlags1);
mNavigationBarView.setOnVerticalChangedListener(this::onVerticalChanged);
mNavigationBarView.setOnTouchListener(this::onNavigationTouch);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
index 2fc7b78c0ca3..8bf1c58df699 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
@@ -69,6 +69,7 @@ import com.android.systemui.DockedStackExistsListener;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
import com.android.systemui.SysUiServiceProvider;
+import com.android.systemui.assist.AssistManager;
import com.android.systemui.plugins.PluginListener;
import com.android.systemui.plugins.statusbar.phone.NavGesture;
import com.android.systemui.plugins.statusbar.phone.NavGesture.GestureHelper;
@@ -156,6 +157,7 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav
private QuickStepAction mQuickStepAction;
private NavigationBackAction mBackAction;
private QuickSwitchAction mQuickSwitchAction;
+ private NavigationAssistantAction mAssistantAction;
/**
* Helper that is responsible for showing the right toast when a disallowed activity operation
@@ -366,8 +368,12 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav
return mBarTransitions.getLightTransitionsController();
}
- public void setComponents(NotificationPanelView panel) {
+ public void setComponents(NotificationPanelView panel, AssistManager assistManager) {
mPanelView = panel;
+ if (mAssistantAction == null) {
+ mAssistantAction = new NavigationAssistantAction(this, mOverviewProxyService,
+ assistManager);
+ }
if (mGestureHelper instanceof QuickStepController) {
((QuickStepController) mGestureHelper).setComponents(this);
updateNavigationGestures();
@@ -398,6 +404,10 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav
return mBackAction;
case NavigationPrototypeController.ACTION_QUICKSWITCH:
return mQuickSwitchAction;
+ case NavigationPrototypeController.ACTION_ASSISTANT:
+ return mAssistantAction;
+ case NavigationPrototypeController.ACTION_NOTHING:
+ return null;
default:
return defaultAction;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationGestureAction.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationGestureAction.java
index 8c57fc31c3b4..a5d938216f5c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationGestureAction.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationGestureAction.java
@@ -24,6 +24,7 @@ import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_
import android.annotation.NonNull;
import android.content.Context;
import android.graphics.Canvas;
+import android.provider.Settings;
import android.view.MotionEvent;
import com.android.systemui.recents.OverviewProxyService;
@@ -32,6 +33,9 @@ import com.android.systemui.recents.OverviewProxyService;
* A gesture action that would be triggered and reassigned by {@link QuickStepController}
*/
public abstract class NavigationGestureAction {
+ private static final String ENABLE_TASK_STABILIZER_FLAG = "ENABLE_TASK_STABILIZER";
+
+ static private boolean sLastTaskStabilizationFlag;
protected final NavigationBarView mNavigationBarView;
protected final OverviewProxyService mProxySender;
@@ -45,6 +49,9 @@ public abstract class NavigationGestureAction {
@NonNull OverviewProxyService service) {
mNavigationBarView = navigationBarView;
mProxySender = service;
+ sLastTaskStabilizationFlag = Settings.Global.getInt(
+ mNavigationBarView.getContext().getContentResolver(),
+ ENABLE_TASK_STABILIZER_FLAG, 0) != 0;
}
/**
@@ -74,6 +81,15 @@ public abstract class NavigationGestureAction {
*/
public void startGesture(MotionEvent event) {
mIsActive = true;
+
+ // Tell launcher that this action requires a stable task list or not
+ boolean flag = requiresStableTaskList();
+ if (flag != sLastTaskStabilizationFlag) {
+ Settings.Global.putInt(mNavigationBarView.getContext().getContentResolver(),
+ ENABLE_TASK_STABILIZER_FLAG, flag ? 1 : 0);
+ sLastTaskStabilizationFlag = flag;
+ }
+
onGestureStart(event);
}
@@ -146,6 +162,13 @@ public abstract class NavigationGestureAction {
*/
public abstract boolean isEnabled();
+ /**
+ * @return action requires a stable task list from launcher
+ */
+ protected boolean requiresStableTaskList() {
+ return false;
+ }
+
protected void onDarkIntensityChange(float intensity) {
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationPrototypeController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationPrototypeController.java
index fb6254b0e4be..a09e5858d576 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationPrototypeController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationPrototypeController.java
@@ -22,7 +22,6 @@ import android.database.ContentObserver;
import android.net.Uri;
import android.os.Handler;
import android.provider.Settings;
-import android.provider.Settings.SettingNotFoundException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -36,18 +35,20 @@ public class NavigationPrototypeController extends ContentObserver {
private static final String HIDE_BACK_BUTTON_SETTING = "quickstepcontroller_hideback";
private static final String HIDE_HOME_BUTTON_SETTING = "quickstepcontroller_hidehome";
- static final String NAVBAR_EXPERIMENTS_DISABLED = "navbarexperiments_disabled";
private final String GESTURE_MATCH_SETTING = "quickstepcontroller_gesture_match_map";
public static final String NAV_COLOR_ADAPT_ENABLE_SETTING = "navbar_color_adapt_enable";
@Retention(RetentionPolicy.SOURCE)
- @IntDef({ACTION_DEFAULT, ACTION_QUICKSTEP, ACTION_QUICKSCRUB, ACTION_BACK})
+ @IntDef({ACTION_DEFAULT, ACTION_QUICKSTEP, ACTION_QUICKSCRUB, ACTION_BACK,
+ ACTION_QUICKSWITCH, ACTION_NOTHING, ACTION_ASSISTANT})
@interface GestureAction {}
static final int ACTION_DEFAULT = 0;
static final int ACTION_QUICKSTEP = 1;
static final int ACTION_QUICKSCRUB = 2;
static final int ACTION_BACK = 3;
static final int ACTION_QUICKSWITCH = 4;
+ static final int ACTION_NOTHING = 5;
+ static final int ACTION_ASSISTANT = 6;
private OnPrototypeChangedListener mListener;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java
index 3839ed5d5d88..d364356b19fe 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java
@@ -31,9 +31,9 @@ import com.android.systemui.statusbar.AmbientPulseManager.OnAmbientChangedListen
import com.android.systemui.statusbar.InflationTask;
import com.android.systemui.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.StatusBarStateController.StateListener;
-import com.android.systemui.statusbar.notification.NotificationData.Entry;
import com.android.systemui.statusbar.notification.NotificationEntryListener;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.row.NotificationInflater.AsyncInflationTask;
import com.android.systemui.statusbar.notification.row.NotificationInflater.InflationFlag;
import com.android.systemui.statusbar.phone.NotificationGroupManager.NotificationGroup;
@@ -108,7 +108,7 @@ public class NotificationGroupAlertTransferHelper implements OnHeadsUpChangedLis
* @param entry notification to check
* @return true if the entry was transferred to and should inflate + alert
*/
- public boolean isAlertTransferPending(@NonNull Entry entry) {
+ public boolean isAlertTransferPending(@NonNull NotificationEntry entry) {
PendingAlertInfo alertInfo = mPendingAlerts.get(entry.key);
return alertInfo != null && alertInfo.isStillValid();
}
@@ -172,16 +172,16 @@ public class NotificationGroupAlertTransferHelper implements OnHeadsUpChangedLis
};
@Override
- public void onAmbientStateChanged(Entry entry, boolean isAmbient) {
+ public void onAmbientStateChanged(NotificationEntry entry, boolean isAmbient) {
onAlertStateChanged(entry, isAmbient, mAmbientPulseManager);
}
@Override
- public void onHeadsUpStateChanged(Entry entry, boolean isHeadsUp) {
+ public void onHeadsUpStateChanged(NotificationEntry entry, boolean isHeadsUp) {
onAlertStateChanged(entry, isHeadsUp, mHeadsUpManager);
}
- private void onAlertStateChanged(Entry entry, boolean isAlerting,
+ private void onAlertStateChanged(NotificationEntry entry, boolean isAlerting,
AlertingNotificationManager alertManager) {
if (isAlerting && mGroupManager.isSummaryOfSuppressedGroup(entry.notification)) {
handleSuppressedSummaryAlerted(entry, alertManager);
@@ -193,7 +193,7 @@ public class NotificationGroupAlertTransferHelper implements OnHeadsUpChangedLis
// Called when a new notification has been posted but is not inflated yet. We use this to
// see as early as we can if we need to abort a transfer.
@Override
- public void onPendingEntryAdded(Entry entry) {
+ public void onPendingEntryAdded(NotificationEntry entry) {
String groupKey = mGroupManager.getGroupKey(entry.notification);
GroupAlertEntry groupAlertEntry = mGroupAlertEntries.get(groupKey);
if (groupAlertEntry != null) {
@@ -204,7 +204,7 @@ public class NotificationGroupAlertTransferHelper implements OnHeadsUpChangedLis
// Called when the entry's reinflation has finished. If there is an alert pending, we
// then show the alert.
@Override
- public void onEntryReinflated(Entry entry) {
+ public void onEntryReinflated(NotificationEntry entry) {
PendingAlertInfo alertInfo = mPendingAlerts.remove(entry.key);
if (alertInfo != null) {
if (alertInfo.isStillValid()) {
@@ -219,16 +219,13 @@ public class NotificationGroupAlertTransferHelper implements OnHeadsUpChangedLis
@Override
public void onEntryRemoved(
- @Nullable Entry entry,
- String key,
- StatusBarNotification old,
+ @Nullable NotificationEntry entry,
NotificationVisibility visibility,
- boolean lifetimeExtended,
boolean removedByUser) {
// Removes any alerts pending on this entry. Note that this will not stop any inflation
// tasks started by a transfer, so this should only be used as clean-up for when
// inflation is stopped and the pending alert no longer needs to happen.
- mPendingAlerts.remove(key);
+ mPendingAlerts.remove(entry.key);
}
};
@@ -244,8 +241,8 @@ public class NotificationGroupAlertTransferHelper implements OnHeadsUpChangedLis
return 0;
}
int number = 0;
- Iterable<Entry> values = mEntryManager.getPendingNotificationsIterator();
- for (Entry entry : values) {
+ Iterable<NotificationEntry> values = mEntryManager.getPendingNotificationsIterator();
+ for (NotificationEntry entry : values) {
if (isPendingNotificationInGroup(entry, group) && onlySummaryAlerts(entry)) {
number++;
}
@@ -263,8 +260,8 @@ public class NotificationGroupAlertTransferHelper implements OnHeadsUpChangedLis
if (mEntryManager == null) {
return false;
}
- Iterable<Entry> values = mEntryManager.getPendingNotificationsIterator();
- for (Entry entry : values) {
+ Iterable<NotificationEntry> values = mEntryManager.getPendingNotificationsIterator();
+ for (NotificationEntry entry : values) {
if (isPendingNotificationInGroup(entry, group)) {
return true;
}
@@ -279,7 +276,7 @@ public class NotificationGroupAlertTransferHelper implements OnHeadsUpChangedLis
* @param group group to check
* @return true if the notification will add to the group, false o/w
*/
- private boolean isPendingNotificationInGroup(@NonNull Entry entry,
+ private boolean isPendingNotificationInGroup(@NonNull NotificationEntry entry,
@NonNull NotificationGroup group) {
String groupKey = mGroupManager.getGroupKey(group.summary.notification);
return mGroupManager.isGroupChild(entry.notification)
@@ -296,7 +293,7 @@ public class NotificationGroupAlertTransferHelper implements OnHeadsUpChangedLis
* @param summary the summary that is suppressed and alerting
* @param alertManager the alert manager that manages the alerting summary
*/
- private void handleSuppressedSummaryAlerted(@NonNull Entry summary,
+ private void handleSuppressedSummaryAlerted(@NonNull NotificationEntry summary,
@NonNull AlertingNotificationManager alertManager) {
StatusBarNotification sbn = summary.notification;
GroupAlertEntry groupAlertEntry =
@@ -312,7 +309,7 @@ public class NotificationGroupAlertTransferHelper implements OnHeadsUpChangedLis
return;
}
- Entry child = mGroupManager.getLogicalChildren(summary.notification).iterator().next();
+ NotificationEntry child = mGroupManager.getLogicalChildren(summary.notification).iterator().next();
if (child != null) {
if (child.getRow().keepInParent()
|| child.isRowRemoved()
@@ -336,7 +333,7 @@ public class NotificationGroupAlertTransferHelper implements OnHeadsUpChangedLis
* @param toEntry entry to transfer to
* @param alertManager alert manager for the alert type
*/
- private void transferAlertState(@NonNull Entry fromEntry, @NonNull Entry toEntry,
+ private void transferAlertState(@NonNull NotificationEntry fromEntry, @NonNull NotificationEntry toEntry,
@NonNull AlertingNotificationManager alertManager) {
alertManager.removeNotification(fromEntry.key, true /* releaseImmediately */);
alertNotificationWhenPossible(toEntry, alertManager);
@@ -356,13 +353,13 @@ public class NotificationGroupAlertTransferHelper implements OnHeadsUpChangedLis
private void checkShouldTransferBack(@NonNull GroupAlertEntry groupAlertEntry) {
if (SystemClock.elapsedRealtime() - groupAlertEntry.mLastAlertTransferTime
< ALERT_TRANSFER_TIMEOUT) {
- Entry summary = groupAlertEntry.mGroup.summary;
+ NotificationEntry summary = groupAlertEntry.mGroup.summary;
AlertingNotificationManager alertManager = getActiveAlertManager();
if (!onlySummaryAlerts(summary)) {
return;
}
- ArrayList<Entry> children = mGroupManager.getLogicalChildren(summary.notification);
+ ArrayList<NotificationEntry> children = mGroupManager.getLogicalChildren(summary.notification);
int numChildren = children.size();
int numPendingChildren = getPendingChildrenNotAlerting(groupAlertEntry.mGroup);
numChildren += numPendingChildren;
@@ -371,7 +368,7 @@ public class NotificationGroupAlertTransferHelper implements OnHeadsUpChangedLis
}
boolean releasedChild = false;
for (int i = 0; i < children.size(); i++) {
- Entry entry = children.get(i);
+ NotificationEntry entry = children.get(i);
if (onlySummaryAlerts(entry) && alertManager.isAlerting(entry.key)) {
releasedChild = true;
alertManager.removeNotification(entry.key, true /* releaseImmediately */);
@@ -402,7 +399,7 @@ public class NotificationGroupAlertTransferHelper implements OnHeadsUpChangedLis
* @param entry entry to show
* @param alertManager alert manager for the alert type
*/
- private void alertNotificationWhenPossible(@NonNull Entry entry,
+ private void alertNotificationWhenPossible(@NonNull NotificationEntry entry,
@NonNull AlertingNotificationManager alertManager) {
@InflationFlag int contentFlag = alertManager.getContentFlag();
if (!entry.getRow().isInflationFlagSet(contentFlag)) {
@@ -422,7 +419,7 @@ public class NotificationGroupAlertTransferHelper implements OnHeadsUpChangedLis
return mIsDozing ? mAmbientPulseManager : mHeadsUpManager;
}
- private boolean onlySummaryAlerts(Entry entry) {
+ private boolean onlySummaryAlerts(NotificationEntry entry) {
return entry.notification.getNotification().getGroupAlertBehavior()
== Notification.GROUP_ALERT_SUMMARY;
}
@@ -442,7 +439,7 @@ public class NotificationGroupAlertTransferHelper implements OnHeadsUpChangedLis
* the transfer is still valid if the notification is updated.
*/
final StatusBarNotification mOriginalNotification;
- final Entry mEntry;
+ final NotificationEntry mEntry;
/**
* The notification is still pending inflation but we've decided that we no longer need
@@ -453,7 +450,7 @@ public class NotificationGroupAlertTransferHelper implements OnHeadsUpChangedLis
*/
boolean mAbortOnInflation;
- PendingAlertInfo(Entry entry, AlertingNotificationManager alertManager) {
+ PendingAlertInfo(NotificationEntry entry, AlertingNotificationManager alertManager) {
mOriginalNotification = entry.notification;
mEntry = entry;
mAlertManager = alertManager;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java
index 3c1c0765a3b8..bb9e4183ba76 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java
@@ -27,7 +27,7 @@ import com.android.systemui.statusbar.AmbientPulseManager.OnAmbientChangedListen
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.StatusBarStateController.StateListener;
-import com.android.systemui.statusbar.notification.NotificationData;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
@@ -97,7 +97,7 @@ public class NotificationGroupManager implements OnHeadsUpChangedListener,
}
}
- public void onEntryRemoved(NotificationData.Entry removed) {
+ public void onEntryRemoved(NotificationEntry removed) {
onEntryRemovedInternal(removed, removed.notification);
mIsolatedEntries.remove(removed.key);
}
@@ -109,7 +109,7 @@ public class NotificationGroupManager implements OnHeadsUpChangedListener,
* @param sbn the notification the entry has, which doesn't need to be the same as it's internal
* notification
*/
- private void onEntryRemovedInternal(NotificationData.Entry removed,
+ private void onEntryRemovedInternal(NotificationEntry removed,
final StatusBarNotification sbn) {
String groupKey = getGroupKey(sbn);
final NotificationGroup group = mGroupMap.get(groupKey);
@@ -136,7 +136,7 @@ public class NotificationGroupManager implements OnHeadsUpChangedListener,
}
}
- public void onEntryAdded(final NotificationData.Entry added) {
+ public void onEntryAdded(final NotificationEntry added) {
if (added.isRowRemoved()) {
added.setDebugThrowable(new Throwable());
}
@@ -152,7 +152,7 @@ public class NotificationGroupManager implements OnHeadsUpChangedListener,
}
}
if (isGroupChild) {
- NotificationData.Entry existing = group.children.get(added.key);
+ NotificationEntry existing = group.children.get(added.key);
if (existing != null && existing != added) {
Throwable existingThrowable = existing.getDebugThrowable();
Log.wtf(TAG, "Inconsistent entries found with the same key " + added.key
@@ -169,9 +169,9 @@ public class NotificationGroupManager implements OnHeadsUpChangedListener,
group.expanded = added.areChildrenExpanded();
updateSuppression(group);
if (!group.children.isEmpty()) {
- ArrayList<NotificationData.Entry> childrenCopy
+ ArrayList<NotificationEntry> childrenCopy
= new ArrayList<>(group.children.values());
- for (NotificationData.Entry child : childrenCopy) {
+ for (NotificationEntry child : childrenCopy) {
onEntryBecomingChild(child);
}
for (OnGroupChangeListener listener : mListeners) {
@@ -181,7 +181,7 @@ public class NotificationGroupManager implements OnHeadsUpChangedListener,
}
}
- private void onEntryBecomingChild(NotificationData.Entry entry) {
+ private void onEntryBecomingChild(NotificationEntry entry) {
if (shouldIsolate(entry)) {
isolateNotification(entry);
}
@@ -221,7 +221,7 @@ public class NotificationGroupManager implements OnHeadsUpChangedListener,
return count;
}
- private NotificationData.Entry getIsolatedChild(String groupKey) {
+ private NotificationEntry getIsolatedChild(String groupKey) {
for (StatusBarNotification sbn : mIsolatedEntries.values()) {
if (sbn.getGroupKey().equals(groupKey) && isIsolated(sbn)) {
return mGroupMap.get(sbn.getKey()).summary;
@@ -230,7 +230,7 @@ public class NotificationGroupManager implements OnHeadsUpChangedListener,
return null;
}
- public void onEntryUpdated(NotificationData.Entry entry,
+ public void onEntryUpdated(NotificationEntry entry,
StatusBarNotification oldNotification) {
String oldKey = oldNotification.getGroupKey();
String newKey = entry.notification.getGroupKey();
@@ -267,7 +267,7 @@ public class NotificationGroupManager implements OnHeadsUpChangedListener,
if (!isOnlyChild(sbn)) {
return false;
}
- NotificationData.Entry logicalGroupSummary = getLogicalGroupSummary(sbn);
+ NotificationEntry logicalGroupSummary = getLogicalGroupSummary(sbn);
return logicalGroupSummary != null
&& !logicalGroupSummary.notification.equals(sbn);
}
@@ -343,7 +343,7 @@ public class NotificationGroupManager implements OnHeadsUpChangedListener,
* Get the summary of a specified status bar notification. For isolated notification this return
* itself.
*/
- public NotificationData.Entry getGroupSummary(StatusBarNotification sbn) {
+ public NotificationEntry getGroupSummary(StatusBarNotification sbn) {
return getGroupSummary(getGroupKey(sbn));
}
@@ -352,12 +352,12 @@ public class NotificationGroupManager implements OnHeadsUpChangedListener,
* but the logical summary, i.e when a child is isolated, it still returns the summary as if
* it wasn't isolated.
*/
- public NotificationData.Entry getLogicalGroupSummary(StatusBarNotification sbn) {
+ public NotificationEntry getLogicalGroupSummary(StatusBarNotification sbn) {
return getGroupSummary(sbn.getGroupKey());
}
@Nullable
- private NotificationData.Entry getGroupSummary(String groupKey) {
+ private NotificationEntry getGroupSummary(String groupKey) {
NotificationGroup group = mGroupMap.get(groupKey);
//TODO: see if this can become an Entry
return group == null ? null
@@ -371,13 +371,13 @@ public class NotificationGroupManager implements OnHeadsUpChangedListener,
* @param summary summary of a group
* @return list of the children
*/
- public ArrayList<NotificationData.Entry> getLogicalChildren(StatusBarNotification summary) {
+ public ArrayList<NotificationEntry> getLogicalChildren(StatusBarNotification summary) {
NotificationGroup group = mGroupMap.get(summary.getGroupKey());
if (group == null) {
return null;
}
- ArrayList<NotificationData.Entry> children = new ArrayList<>(group.children.values());
- NotificationData.Entry isolatedChild = getIsolatedChild(summary.getGroupKey());
+ ArrayList<NotificationEntry> children = new ArrayList<>(group.children.values());
+ NotificationEntry isolatedChild = getIsolatedChild(summary.getGroupKey());
if (isolatedChild != null) {
children.add(isolatedChild);
}
@@ -443,24 +443,24 @@ public class NotificationGroupManager implements OnHeadsUpChangedListener,
}
@Override
- public void onHeadsUpPinned(NotificationData.Entry entry) {
+ public void onHeadsUpPinned(NotificationEntry entry) {
}
@Override
- public void onHeadsUpUnPinned(NotificationData.Entry entry) {
+ public void onHeadsUpUnPinned(NotificationEntry entry) {
}
@Override
- public void onAmbientStateChanged(NotificationData.Entry entry, boolean isAmbient) {
+ public void onAmbientStateChanged(NotificationEntry entry, boolean isAmbient) {
onAlertStateChanged(entry, isAmbient);
}
@Override
- public void onHeadsUpStateChanged(NotificationData.Entry entry, boolean isHeadsUp) {
+ public void onHeadsUpStateChanged(NotificationEntry entry, boolean isHeadsUp) {
onAlertStateChanged(entry, isHeadsUp);
}
- private void onAlertStateChanged(NotificationData.Entry entry, boolean isAlerting) {
+ private void onAlertStateChanged(NotificationEntry entry, boolean isAlerting) {
if (isAlerting) {
if (shouldIsolate(entry)) {
isolateNotification(entry);
@@ -479,7 +479,7 @@ public class NotificationGroupManager implements OnHeadsUpChangedListener,
* @return true if the entry should be isolated
*/
- private boolean shouldIsolate(NotificationData.Entry entry) {
+ private boolean shouldIsolate(NotificationEntry entry) {
StatusBarNotification sbn = entry.notification;
NotificationGroup notificationGroup = mGroupMap.get(sbn.getGroupKey());
if (!sbn.isGroup() || sbn.getNotification().isGroupSummary()) {
@@ -499,7 +499,7 @@ public class NotificationGroupManager implements OnHeadsUpChangedListener,
*
* @param entry the notification to isolate
*/
- private void isolateNotification(NotificationData.Entry entry) {
+ private void isolateNotification(NotificationEntry entry) {
StatusBarNotification sbn = entry.notification;
// We will be isolated now, so lets update the groups
@@ -523,7 +523,7 @@ public class NotificationGroupManager implements OnHeadsUpChangedListener,
*
* @param entry the notification to un-isolate
*/
- private void stopIsolatingNotification(NotificationData.Entry entry) {
+ private void stopIsolatingNotification(NotificationEntry entry) {
StatusBarNotification sbn = entry.notification;
if (mIsolatedEntries.containsKey(sbn.getKey())) {
// not isolated anymore, we need to update the groups
@@ -564,8 +564,8 @@ public class NotificationGroupManager implements OnHeadsUpChangedListener,
}
public static class NotificationGroup {
- public final HashMap<String, NotificationData.Entry> children = new HashMap<>();
- public NotificationData.Entry summary;
+ public final HashMap<String, NotificationEntry> children = new HashMap<>();
+ public NotificationEntry summary;
public boolean expanded;
/**
* Is this notification group suppressed, i.e its summary is hidden
@@ -580,7 +580,7 @@ public class NotificationGroupManager implements OnHeadsUpChangedListener,
? Log.getStackTraceString(summary.getDebugThrowable())
: "");
result += "\n children size: " + children.size();
- for (NotificationData.Entry child : children.values()) {
+ for (NotificationEntry child : children.values()) {
result += "\n " + child.notification
+ (child.getDebugThrowable() != null
? Log.getStackTraceString(child.getDebugThrowable())
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 056c8a7f5f5e..077fcda70f0c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
@@ -23,9 +23,10 @@ import com.android.systemui.plugins.DarkIconDispatcher;
import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
import com.android.systemui.statusbar.NotificationShelf;
import com.android.systemui.statusbar.StatusBarIconView;
-import com.android.systemui.statusbar.notification.NotificationData;
+import com.android.systemui.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.NotificationUtils;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.tuner.TunerService;
@@ -36,13 +37,15 @@ import java.util.function.Function;
* A controller for the space in the status bar to the left of the system icons. This area is
* normally reserved for notifications.
*/
-public class NotificationIconAreaController implements DarkReceiver {
+public class NotificationIconAreaController implements DarkReceiver,
+ StatusBarStateController.StateListener {
public static final String LOW_PRIORITY = "low_priority";
private final ContrastColorUtil mContrastColorUtil;
private final NotificationEntryManager mEntryManager;
private final Runnable mUpdateStatusBarIcons = this::updateStatusBarIcons;
+ private final StatusBarStateController mStatusBarStateController;
private final TunerService.Tunable mTunable = new TunerService.Tunable() {
@Override
public void onTuningChanged(String key, String newValue) {
@@ -86,11 +89,14 @@ public class NotificationIconAreaController implements DarkReceiver {
private final ViewClippingUtil.ClippingParameters mClippingParameters =
view -> view instanceof StatusBarWindowView;
- public NotificationIconAreaController(Context context, StatusBar statusBar) {
+ public NotificationIconAreaController(Context context, StatusBar statusBar,
+ StatusBarStateController statusBarStateController) {
mStatusBar = statusBar;
mContrastColorUtil = ContrastColorUtil.getInstance(context);
mContext = context;
mEntryManager = Dependency.get(NotificationEntryManager.class);
+ mStatusBarStateController = statusBarStateController;
+ mStatusBarStateController.addCallback(this);
Dependency.get(TunerService.class).addTunable(mTunable, LOW_PRIORITY);
@@ -182,7 +188,7 @@ public class NotificationIconAreaController implements DarkReceiver {
return mStatusBar.getStatusBarHeight();
}
- protected boolean shouldShowNotificationIcon(NotificationData.Entry entry,
+ protected boolean shouldShowNotificationIcon(NotificationEntry entry,
boolean showAmbient, boolean showLowPriority, boolean hideDismissed,
boolean hideRepliedMessages) {
if (mEntryManager.getNotificationData().isAmbient(entry.key) && !showAmbient) {
@@ -247,7 +253,7 @@ public class NotificationIconAreaController implements DarkReceiver {
* @param hideDismissed should dismissed icons be hidden
* @param hideRepliedMessages should messages that have been replied to be hidden
*/
- private void updateIconsForLayout(Function<NotificationData.Entry, StatusBarIconView> function,
+ private void updateIconsForLayout(Function<NotificationEntry, StatusBarIconView> function,
NotificationIconContainer hostLayout, boolean showAmbient, boolean showLowPriority,
boolean hideDismissed, boolean hideRepliedMessages) {
ArrayList<StatusBarIconView> toShow = new ArrayList<>(
@@ -257,7 +263,7 @@ public class NotificationIconAreaController implements DarkReceiver {
for (int i = 0; i < mNotificationScrollLayout.getChildCount(); i++) {
View view = mNotificationScrollLayout.getChildAt(i);
if (view instanceof ExpandableNotificationRow) {
- NotificationData.Entry ent = ((ExpandableNotificationRow) view).getEntry();
+ NotificationEntry ent = ((ExpandableNotificationRow) view).getEntry();
if (shouldShowNotificationIcon(ent, showAmbient, showLowPriority, hideDismissed,
hideRepliedMessages)) {
toShow.add(function.apply(ent));
@@ -373,24 +379,6 @@ public class NotificationIconAreaController implements DarkReceiver {
v.setDecorColor(mIconTint);
}
- /**
- * Dark amount, from 0 to 1, representing being awake or in AOD.
- */
- public void setDarkAmount(float darkAmount) {
- mDarkAmount = darkAmount;
- if (darkAmount == 0 || darkAmount == 1) {
- ViewClippingUtil.setClippingDeactivated(mNotificationIcons, darkAmount != 0,
- mClippingParameters);
- }
- dozeTimeTick();
-
- boolean fullyDark = darkAmount == 1f;
- if (mFullyDark != fullyDark) {
- mFullyDark = fullyDark;
- updateShelfIcons();
- }
- }
-
public void setDark(boolean dark) {
mNotificationIcons.setDark(dark, false, 0);
mShelfIcons.setDark(dark, false, 0);
@@ -408,10 +396,45 @@ public class NotificationIconAreaController implements DarkReceiver {
* Moves icons whenever the device wakes up in AOD, to avoid burn in.
*/
public void dozeTimeTick() {
+ if (mNotificationIcons.getVisibility() != View.VISIBLE) {
+ return;
+ }
+
+ if (mDarkAmount == 0 && !mStatusBarStateController.isDozing()) {
+ mNotificationIcons.setTranslationX(0);
+ mNotificationIcons.setTranslationY(0);
+ return;
+ }
+
int yOffset = (mKeyguardStatusBarHeight - getHeight()) / 2;
int translationX = getBurnInOffset(mBurnInOffset, true /* xAxis */);
int translationY = getBurnInOffset(mBurnInOffset, false /* xAxis */) + yOffset;
- mNotificationIcons.setTranslationX(translationX * mDarkAmount);
- mNotificationIcons.setTranslationY(translationY * mDarkAmount);
+ mNotificationIcons.setTranslationX(translationX);
+ mNotificationIcons.setTranslationY(translationY);
+ }
+
+ @Override
+ public void onDozingChanged(boolean isDozing) {
+ dozeTimeTick();
+ }
+
+ @Override
+ public void onDozeAmountChanged(float linear, float eased) {
+ boolean wasOrIsAwake = mDarkAmount == 0 || linear == 0;
+ boolean wasOrIsDozing = mDarkAmount == 1 || linear == 1;
+ mDarkAmount = linear;
+ if (wasOrIsAwake) {
+ ViewClippingUtil.setClippingDeactivated(mNotificationIcons, mDarkAmount != 0,
+ mClippingParameters);
+ }
+ if (wasOrIsAwake || wasOrIsDozing) {
+ dozeTimeTick();
+ }
+
+ boolean fullyDark = mDarkAmount == 1f;
+ if (mFullyDark != fullyDark) {
+ mFullyDark = fullyDark;
+ updateShelfIcons();
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
index 964b2210dd18..009afca5b034 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
@@ -128,6 +128,7 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout {
}
}.setDuration(CONTENT_FADE_DURATION);
+ private static final int MAX_VISIBLE_ICONS_WHEN_DARK = 5;
public static final int MAX_STATIC_ICONS = 4;
private static final int MAX_DOTS = 1;
@@ -371,7 +372,8 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout {
float translationX = getActualPaddingStart();
int firstOverflowIndex = -1;
int childCount = getChildCount();
- int maxVisibleIcons = mIsStaticLayout ? MAX_STATIC_ICONS : childCount;
+ int maxVisibleIcons = mDark ? MAX_VISIBLE_ICONS_WHEN_DARK :
+ mIsStaticLayout ? MAX_STATIC_ICONS : childCount;
float layoutEnd = getLayoutEnd();
float overflowStart = getMaxOverflowStart();
mVisualOverflowStart = 0;
@@ -387,6 +389,9 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout {
boolean forceOverflow = mSpeedBumpIndex != -1 && i >= mSpeedBumpIndex
&& iconState.iconAppearAmount > 0.0f || i >= maxVisibleIcons;
boolean noOverflowAfter = i == childCount - 1;
+ float drawingScale = mDark && view instanceof StatusBarIconView
+ ? ((StatusBarIconView) view).getIconScaleFullyDark()
+ : 1f;
if (mOpenedAmount != 0.0f) {
noOverflowAfter = noOverflowAfter && !hasAmbient && !forceOverflow;
}
@@ -402,7 +407,7 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout {
mVisualOverflowStart = Math.min(translationX, mVisualOverflowStart);
}
}
- translationX += iconState.iconAppearAmount * view.getWidth();
+ translationX += iconState.iconAppearAmount * view.getWidth() * drawingScale;
}
mNumDots = 0;
if (firstOverflowIndex != -1) {
@@ -432,6 +437,32 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout {
mFirstVisibleIconState = mIconStates.get(getChildAt(0));
}
+ boolean center = mDark;
+ if (center && translationX < getLayoutEnd()) {
+ float initialTranslation =
+ mFirstVisibleIconState == null ? 0 : mFirstVisibleIconState.xTranslation;
+
+ float contentWidth = 0;
+ if (mLastVisibleIconState != null) {
+ contentWidth = mLastVisibleIconState.xTranslation + mIconSize;
+ contentWidth = Math.min(getWidth(), contentWidth) - initialTranslation;
+ }
+ float availableSpace = getLayoutEnd() - getActualPaddingStart();
+ float delta = (availableSpace - contentWidth) / 2;
+
+ if (firstOverflowIndex != -1) {
+ // If we have an overflow, only count those half for centering because the dots
+ // don't have a lot of visual weight.
+ float deltaIgnoringOverflow = (getLayoutEnd() - mVisualOverflowStart) / 2;
+ delta = (deltaIgnoringOverflow + delta) / 2;
+ }
+ for (int i = 0; i < childCount; i++) {
+ View view = getChildAt(i);
+ IconState iconState = mIconStates.get(view);
+ iconState.xTranslation += delta;
+ }
+ }
+
if (isLayoutRtl()) {
for (int i = 0; i < childCount; i++) {
View view = getChildAt(i);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
index 16576ad6e3e1..0d5ebb9b6578 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -50,6 +50,7 @@ import android.view.WindowInsets;
import android.view.accessibility.AccessibilityManager;
import android.widget.FrameLayout;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.keyguard.KeyguardClockSwitch;
@@ -76,9 +77,9 @@ import com.android.systemui.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.StatusBarStateController.StateListener;
import com.android.systemui.statusbar.notification.ActivityLaunchAnimator;
import com.android.systemui.statusbar.notification.AnimatableProperty;
-import com.android.systemui.statusbar.notification.NotificationData;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.PropertyAnimator;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.ExpandableView;
@@ -140,9 +141,11 @@ public class NotificationPanelView extends PanelView implements
private KeyguardAffordanceHelper mAffordanceHelper;
private KeyguardUserSwitcher mKeyguardUserSwitcher;
private KeyguardStatusBarView mKeyguardStatusBar;
+ private ViewGroup mBigClockContainer;
private QS mQs;
private FrameLayout mQsFrame;
- private KeyguardStatusView mKeyguardStatusView;
+ @VisibleForTesting
+ protected KeyguardStatusView mKeyguardStatusView;
private View mQsNavbarScrim;
protected NotificationsQuickSettingsContainer mNotificationContainerParent;
protected NotificationStackScrollLayout mNotificationStackScroller;
@@ -327,6 +330,13 @@ public class NotificationPanelView extends PanelView implements
mDisplayId = context.getDisplayId();
}
+ /**
+ * Returns if there's a custom clock being presented.
+ */
+ public boolean hasCustomClock() {
+ return mKeyguardStatusView.hasCustomClock();
+ }
+
private void setStatusBar(StatusBar bar) {
mStatusBar = bar;
mKeyguardBottomArea.setStatusBar(mStatusBar);
@@ -339,8 +349,8 @@ public class NotificationPanelView extends PanelView implements
mKeyguardStatusView = findViewById(R.id.keyguard_status_view);
KeyguardClockSwitch keyguardClockSwitch = findViewById(R.id.keyguard_clock_container);
- ViewGroup bigClockContainer = findViewById(R.id.big_clock_container);
- keyguardClockSwitch.setBigClockContainer(bigClockContainer);
+ mBigClockContainer = findViewById(R.id.big_clock_container);
+ keyguardClockSwitch.setBigClockContainer(mBigClockContainer);
mNotificationContainerParent = findViewById(R.id.notification_container_parent);
mNotificationStackScroller = findViewById(R.id.notification_stack_scroller);
@@ -569,18 +579,23 @@ public class NotificationPanelView extends PanelView implements
mKeyguardStatusView.getHeight(),
mInterpolatedDarkAmount,
mStatusBar.isKeyguardCurrentlySecure(),
- mPulsing,
mEmptyDragAmount);
mClockPositionAlgorithm.run(mClockPositionResult);
PropertyAnimator.setProperty(mKeyguardStatusView, AnimatableProperty.X,
mClockPositionResult.clockX, CLOCK_ANIMATION_PROPERTIES, animateClock);
PropertyAnimator.setProperty(mKeyguardStatusView, AnimatableProperty.Y,
mClockPositionResult.clockY, CLOCK_ANIMATION_PROPERTIES, animateClock);
+ // Move big clock up while pulling up the bouncer
+ PropertyAnimator.setProperty(mBigClockContainer, AnimatableProperty.Y,
+ MathUtils.lerp(-mBigClockContainer.getHeight(), 0,
+ Interpolators.FAST_OUT_LINEAR_IN.getInterpolation(getExpandedFraction())),
+ CLOCK_ANIMATION_PROPERTIES, animateClock);
updateClock();
stackScrollerPadding = mClockPositionResult.stackScrollerPadding;
}
mNotificationStackScroller.setIntrinsicPadding(stackScrollerPadding);
- mNotificationStackScroller.setAntiBurnInOffsetX(mClockPositionResult.clockX);
+ int burnInXOffset = mPulsing ? 0 : mClockPositionResult.clockX;
+ mNotificationStackScroller.setAntiBurnInOffsetX(burnInXOffset);
mStackScrollerMeasuringPass++;
requestScrollerTopPaddingUpdate(animate);
@@ -956,7 +971,7 @@ public class NotificationPanelView extends PanelView implements
handled = true;
}
handled |= super.onTouchEvent(event);
- return mDozing ? handled : true;
+ return !mDozing || mPulsing || handled;
}
private boolean handleQsTouch(MotionEvent event) {
@@ -1233,7 +1248,7 @@ public class NotificationPanelView extends PanelView implements
updateDozingVisibilities(false /* animate */);
}
- resetVerticalPanelPosition();
+ resetHorizontalPanelPosition();
updateQsState();
}
@@ -2043,7 +2058,7 @@ public class NotificationPanelView extends PanelView implements
super.onConfigurationChanged(newConfig);
mAffordanceHelper.onConfigurationChanged();
if (newConfig.orientation != mLastOrientation) {
- resetVerticalPanelPosition();
+ resetHorizontalPanelPosition();
}
mLastOrientation = newConfig.orientation;
}
@@ -2489,12 +2504,12 @@ public class NotificationPanelView extends PanelView implements
}
@Override
- public void onHeadsUpPinned(NotificationData.Entry entry) {
+ public void onHeadsUpPinned(NotificationEntry entry) {
mNotificationStackScroller.generateHeadsUpAnimation(entry.getHeadsUpAnimationView(), true);
}
@Override
- public void onHeadsUpUnPinned(NotificationData.Entry entry) {
+ public void onHeadsUpUnPinned(NotificationEntry entry) {
// When we're unpinning the notification via active edge they remain heads-upped,
// we need to make sure that an animation happens in this case, otherwise the notification
@@ -2507,7 +2522,7 @@ public class NotificationPanelView extends PanelView implements
}
@Override
- public void onHeadsUpStateChanged(NotificationData.Entry entry, boolean isHeadsUp) {
+ public void onHeadsUpStateChanged(NotificationEntry entry, boolean isHeadsUp) {
mNotificationStackScroller.generateHeadsUpAnimation(entry, isHeadsUp);
}
@@ -2529,7 +2544,7 @@ public class NotificationPanelView extends PanelView implements
@Override
protected void onClosingFinished() {
super.onClosingFinished();
- resetVerticalPanelPosition();
+ resetHorizontalPanelPosition();
setClosingWithAlphaFadeout(false);
}
@@ -2546,7 +2561,7 @@ public class NotificationPanelView extends PanelView implements
*/
protected void updateVerticalPanelPosition(float x) {
if (mNotificationStackScroller.getWidth() * 1.75f > getWidth()) {
- resetVerticalPanelPosition();
+ resetHorizontalPanelPosition();
return;
}
float leftMost = mPositionMinSideMargin + mNotificationStackScroller.getWidth() / 2;
@@ -2556,16 +2571,17 @@ public class NotificationPanelView extends PanelView implements
x = getWidth() / 2;
}
x = Math.min(rightMost, Math.max(leftMost, x));
- setVerticalPanelTranslation(x -
- (mNotificationStackScroller.getLeft() + mNotificationStackScroller.getWidth() / 2));
+ float center =
+ mNotificationStackScroller.getLeft() + mNotificationStackScroller.getWidth() / 2;
+ setHorizontalPanelTranslation(x - center);
}
- private void resetVerticalPanelPosition() {
- setVerticalPanelTranslation(0f);
+ private void resetHorizontalPanelPosition() {
+ setHorizontalPanelTranslation(0f);
}
- protected void setVerticalPanelTranslation(float translation) {
- mNotificationStackScroller.setVerticalPanelTranslation(translation);
+ protected void setHorizontalPanelTranslation(float translation) {
+ mNotificationStackScroller.setHorizontalPanelTranslation(translation);
mQsFrame.setTranslationX(translation);
int size = mVerticalTranslationListener.size();
for (int i = 0; i < size; i++) {
@@ -2773,6 +2789,9 @@ public class NotificationPanelView extends PanelView implements
if (dozing == mDozing) return;
mDozing = dozing;
mNotificationStackScroller.setDark(mDozing, animate, wakeUpTouchLocation);
+ if (mDozing) {
+ mNotificationStackScroller.setShowDarkShelf(!hasCustomClock());
+ }
if (mBarState == StatusBarState.KEYGUARD
|| mBarState == StatusBarState.SHADE_LOCKED) {
@@ -2811,7 +2830,7 @@ public class NotificationPanelView extends PanelView implements
mAnimateNextPositionUpdate = false;
}
mNotificationStackScroller.setPulsing(pulsing, animatePulse);
- mKeyguardStatusView.setPulsing(pulsing, animatePulse);
+ mKeyguardStatusView.setPulsing(pulsing);
mKeyguardBottomArea.setPulsing(pulsing, animatePulse);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
index b3996f5e092c..a697603c689b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
@@ -149,8 +149,8 @@ public abstract class PanelView extends FrameLayout {
private boolean mIgnoreXTouchSlop;
private boolean mExpandLatencyTracking;
protected final KeyguardMonitor mKeyguardMonitor = Dependency.get(KeyguardMonitor.class);
- protected final StatusBarStateController
- mStatusBarStateController = Dependency.get(StatusBarStateController.class);
+ protected final StatusBarStateController mStatusBarStateController =
+ Dependency.get(StatusBarStateController.class);
protected void onExpandingFinished() {
mBar.onExpandingFinished();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStepAction.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStepAction.java
index b18b79e0e6d6..1999f9a8e04d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStepAction.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStepAction.java
@@ -47,6 +47,10 @@ public class QuickStepAction extends NavigationGestureAction {
return mNavigationBarView.isQuickStepSwipeUpEnabled();
}
+ protected boolean requiresStableTaskList() {
+ return true;
+ }
+
@Override
public void onGestureStart(MotionEvent event) {
try {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStepController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStepController.java
index 0cec6371d813..9e91ab70e3de 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStepController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStepController.java
@@ -34,13 +34,20 @@ import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Rect;
+import android.hardware.input.InputManager;
import android.os.RemoteException;
+import android.os.SystemClock;
import android.provider.Settings;
import android.util.Log;
+import android.view.InputDevice;
import android.view.MotionEvent;
import android.view.View;
+import android.view.ViewGroup;
import android.view.ViewPropertyAnimator;
+import android.view.WindowManager;
+import android.view.WindowManager.LayoutParams;
+
import com.android.systemui.Dependency;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
@@ -64,7 +71,10 @@ public class QuickStepController implements GestureHelper {
/** Experiment to swipe home button left to execute a back key press */
private static final String HIDE_BACK_BUTTON_PROP = "quickstepcontroller_hideback";
+ private static final String ENABLE_CLICK_THROUGH_NAV_PROP = "quickstepcontroller_clickthrough";
private static final long BACK_BUTTON_FADE_IN_ALPHA = 150;
+ private static final long CLICK_THROUGH_TAP_DELAY = 70;
+ private static final long CLICK_THROUGH_TAP_RESET_DELAY = 100;
/** When the home-swipe-back gesture is disallowed, make it harder to pull */
private static final float HORIZONTAL_GESTURE_DAMPING = 0.3f;
@@ -100,6 +110,9 @@ public class QuickStepController implements GestureHelper {
private float mMinDragLimit;
private float mDragDampeningFactor;
private float mEdgeSwipeThreshold;
+ private boolean mClickThroughPressed;
+ private float mClickThroughPressX;
+ private float mClickThroughPressY;
private NavigationGestureAction mCurrentAction;
private NavigationGestureAction[] mGestureActions = new NavigationGestureAction[MAX_GESTURES];
@@ -117,6 +130,19 @@ public class QuickStepController implements GestureHelper {
mOverviewEventSender = Dependency.get(OverviewProxyService.class);
}
+ private final Runnable mClickThroughSendTap = new Runnable() {
+ @Override
+ public void run() {
+ sendTap(mClickThroughPressX, mClickThroughPressY);
+ mNavigationBarView.postDelayed(mClickThroughResetTap, CLICK_THROUGH_TAP_RESET_DELAY);
+ }
+ };
+
+ private final Runnable mClickThroughResetTap = () -> {
+ setWindowTouchable(true);
+ mClickThroughPressed = false;
+ };
+
public void setComponents(NavigationBarView navigationBarView) {
mNavigationBarView = navigationBarView;
@@ -320,6 +346,25 @@ public class QuickStepController implements GestureHelper {
case MotionEvent.ACTION_UP:
if (mCurrentAction != null) {
mCurrentAction.endGesture();
+ } else if (action == MotionEvent.ACTION_UP
+ && getBoolGlobalSetting(mContext, ENABLE_CLICK_THROUGH_NAV_PROP)
+ && !mClickThroughPressed) {
+ // Enable click through functionality where no gesture has been detected and not
+ // passed the drag slop so inject a touch event at the same location
+ // after making the navigation bar window untouchable. After a some time, the
+ // navigation bar will be able to take input events again
+ float diffX = Math.abs(event.getX() - mTouchDownX);
+ float diffY = Math.abs(event.getY() - mTouchDownY);
+
+ if ((diffX <= NavigationBarCompat.getQuickStepDragSlopPx()
+ && diffY <= NavigationBarCompat.getQuickStepDragSlopPx())) {
+ setWindowTouchable(false);
+ mClickThroughPressX = event.getRawX();
+ mClickThroughPressY = event.getRawY();
+ mClickThroughPressed = true;
+ mNavigationBarView.postDelayed(mClickThroughSendTap,
+ CLICK_THROUGH_TAP_DELAY);
+ }
}
// Return the hit target back to its original position
@@ -350,6 +395,19 @@ public class QuickStepController implements GestureHelper {
return mCurrentAction != null || deadZoneConsumed;
}
+ private void setWindowTouchable(boolean flag) {
+ final WindowManager.LayoutParams lp = (WindowManager.LayoutParams)
+ ((ViewGroup) mNavigationBarView.getParent()).getLayoutParams();
+ if (flag) {
+ lp.flags &= ~LayoutParams.FLAG_NOT_TOUCHABLE;
+ } else {
+ lp.flags |= LayoutParams.FLAG_NOT_TOUCHABLE;
+ }
+ final WindowManager wm = (WindowManager) mNavigationBarView.getContext()
+ .getSystemService(Context.WINDOW_SERVICE);
+ wm.updateViewLayout((View) mNavigationBarView.getParent(), lp);
+ }
+
private boolean isEdgeSwipeAlongNavBar(int touchDown, boolean dragPositiveDirection) {
// Detect edge swipe from side of 0 -> threshold
if (dragPositiveDirection) {
@@ -562,6 +620,38 @@ public class QuickStepController implements GestureHelper {
return false;
}
+ private void sendTap(float x, float y) {
+ long now = SystemClock.uptimeMillis();
+ injectMotionEvent(InputDevice.SOURCE_TOUCHSCREEN, MotionEvent.ACTION_DOWN, now, x, y, 1.0f);
+ injectMotionEvent(InputDevice.SOURCE_TOUCHSCREEN, MotionEvent.ACTION_UP, now, x, y, 0.0f);
+ }
+
+ private int getInputDeviceId(int inputSource) {
+ int[] devIds = InputDevice.getDeviceIds();
+ for (int devId : devIds) {
+ InputDevice inputDev = InputDevice.getDevice(devId);
+ if (inputDev.supportsSource(inputSource)) {
+ return devId;
+ }
+ }
+ return 0;
+ }
+
+ private void injectMotionEvent(int inputSource, int action, long when, float x, float y,
+ float pressure) {
+ final float defaultSize = 1.0f;
+ final int defaultMetaState = 0;
+ final float defaultPrecisionX = 1.0f;
+ final float defaultPrecisionY = 1.0f;
+ final int defaultEdgeFlags = 0;
+ MotionEvent event = MotionEvent.obtain(when, when, action, x, y, pressure, defaultSize,
+ defaultMetaState, defaultPrecisionX, defaultPrecisionY,
+ getInputDeviceId(inputSource), defaultEdgeFlags);
+ event.setSource(inputSource);
+ InputManager.getInstance().injectInputEvent(event,
+ InputManager.INJECT_INPUT_EVENT_MODE_ASYNC);
+ }
+
private boolean proxyMotionEvents(MotionEvent event) {
final IOverviewProxy overviewProxy = mOverviewEventSender.getProxy();
event.transform(mTransformGlobalMatrix);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index e25c8292b637..bf143c8940e5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -88,16 +88,12 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, OnCo
/**
* Default alpha value for most scrims.
*/
- public static final float GRADIENT_SCRIM_ALPHA = 0.45f;
+ public static final float GRADIENT_SCRIM_ALPHA = 0.2f;
/**
* A scrim varies its opacity based on a busyness factor, for example
* how many notifications are currently visible.
*/
public static final float GRADIENT_SCRIM_ALPHA_BUSY = 0.7f;
- /**
- * Scrim opacity when a wallpaper doesn't support ambient mode.
- */
- public static final float PULSING_WALLPAPER_SCRIM_ALPHA = 0.6f;
/**
* The most common scrim, the one under the keyguard.
@@ -154,7 +150,6 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, OnCo
private Callback mCallback;
private boolean mWallpaperSupportsAmbientMode;
private boolean mScreenOn;
- private float mNotificationDensity;
// Scrim blanking callbacks
private Runnable mPendingFrameCallback;
@@ -245,7 +240,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, OnCo
mCurrentInFrontTint = state.getFrontTint();
mCurrentBehindTint = state.getBehindTint();
mCurrentInFrontAlpha = state.getFrontAlpha();
- mCurrentBehindAlpha = state.getBehindAlpha(mNotificationDensity);
+ mCurrentBehindAlpha = state.getBehindAlpha();
applyExpansionToAlpha();
// Scrim might acquire focus when user is navigating with a D-pad or a keyboard.
@@ -275,9 +270,11 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, OnCo
holdWakeLock();
}
- // AOD wallpapers should fade away after a while
- if (mWallpaperSupportsAmbientMode && mDozeParameters.getAlwaysOn()
- && mState == ScrimState.AOD) {
+ // AOD wallpapers should fade away after a while.
+ // Docking pulses may take a long time, wallpapers should also fade away after a while.
+ if (mWallpaperSupportsAmbientMode && (
+ mDozeParameters.getAlwaysOn() && mState == ScrimState.AOD
+ || mState == ScrimState.PULSING && mCallback != null)) {
if (!mWallpaperVisibilityTimedOut) {
mTimeTicker.schedule(mDozeParameters.getWallpaperAodDuration(),
AlarmTimeout.MODE_IGNORE_IF_SCHEDULED);
@@ -329,7 +326,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, OnCo
@VisibleForTesting
protected void onHideWallpaperTimeout() {
- if (mState != ScrimState.AOD) {
+ if (mState != ScrimState.AOD && mState != ScrimState.PULSING) {
return;
}
@@ -364,7 +361,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, OnCo
mExpansionFraction = fraction;
final boolean keyguardOrUnlocked = mState == ScrimState.UNLOCKED
- || mState == ScrimState.KEYGUARD;
+ || mState == ScrimState.KEYGUARD || mState == ScrimState.PULSING;
if (!keyguardOrUnlocked || !mExpansionAffectsAlpha) {
return;
}
@@ -409,11 +406,11 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, OnCo
behindFraction = (float) Math.pow(behindFraction, 0.8f);
mCurrentBehindAlpha = behindFraction * GRADIENT_SCRIM_ALPHA_BUSY;
mCurrentInFrontAlpha = 0;
- } else if (mState == ScrimState.KEYGUARD) {
+ } else if (mState == ScrimState.KEYGUARD || mState == ScrimState.PULSING) {
// Either darken of make the scrim transparent when you
// pull down the shade
float interpolatedFract = getInterpolatedFraction();
- float alphaBehind = mState.getBehindAlpha(mNotificationDensity);
+ float alphaBehind = mState.getBehindAlpha();
if (mDarkenWhileDragging) {
mCurrentBehindAlpha = MathUtils.lerp(GRADIENT_SCRIM_ALPHA_BUSY, alphaBehind,
interpolatedFract);
@@ -427,24 +424,6 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, OnCo
}
/**
- * Keyguard and shade scrim opacity varies according to how many notifications are visible.
- * @param notificationCount Number of visible notifications.
- */
- public void setNotificationCount(int notificationCount) {
- final float maxNotificationDensity = 3;
- float notificationDensity = Math.min(notificationCount / maxNotificationDensity, 1f);
- if (mNotificationDensity == notificationDensity) {
- return;
- }
- mNotificationDensity = notificationDensity;
-
- if (mState == ScrimState.KEYGUARD) {
- applyExpansionToAlpha();
- scheduleUpdate();
- }
- }
-
- /**
* Sets the given drawable as the background of the scrim that shows up behind the
* notifications.
*/
@@ -504,7 +483,8 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, OnCo
// We want to override the back scrim opacity for the AOD state
// when it's time to fade the wallpaper away.
- boolean aodWallpaperTimeout = mState == ScrimState.AOD && mWallpaperVisibilityTimedOut;
+ boolean aodWallpaperTimeout = (mState == ScrimState.AOD || mState == ScrimState.PULSING)
+ && mWallpaperVisibilityTimedOut;
// We also want to hide FLAG_SHOW_WHEN_LOCKED activities under the scrim.
boolean occludedKeyguard = (mState == ScrimState.PULSING || mState == ScrimState.AOD)
&& mKeyguardOccluded;
@@ -562,8 +542,8 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, OnCo
if (alpha == 0f) {
scrim.setClickable(false);
} else {
- // Eat touch events (unless dozing or pulsing).
- scrim.setClickable(mState != ScrimState.AOD && mState != ScrimState.PULSING);
+ // Eat touch events (unless dozing).
+ scrim.setClickable(mState != ScrimState.AOD);
}
updateScrim(scrim, alpha);
}
@@ -608,9 +588,11 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, OnCo
anim.setStartDelay(mAnimationDelay);
anim.setDuration(mAnimationDuration);
anim.addListener(new AnimatorListenerAdapter() {
+ private Callback lastCallback = mCallback;
+
@Override
public void onAnimationEnd(Animator animation) {
- onFinished();
+ onFinished(lastCallback);
scrim.setTag(TAG_KEY_ANIM, null);
dispatchScrimsVisible();
@@ -668,14 +650,23 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, OnCo
}
private void onFinished() {
+ onFinished(mCallback);
+ }
+
+ private void onFinished(Callback callback) {
if (mWakeLockHeld) {
mWakeLock.release();
mWakeLockHeld = false;
}
- if (mCallback != null) {
- mCallback.onFinished();
- mCallback = null;
+
+ if (callback != null) {
+ callback.onFinished();
+
+ if (callback == mCallback) {
+ mCallback = null;
+ }
}
+
// When unlocking with fingerprint, we'll fade the scrims from black to transparent.
// At the end of the animation we need to remove the tint.
if (mState == ScrimState.UNLOCKED) {
@@ -894,7 +885,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, OnCo
// Backdrop event may arrive after state was already applied,
// in this case, back-scrim needs to be re-evaluated
if (mState == ScrimState.AOD || mState == ScrimState.PULSING) {
- float newBehindAlpha = mState.getBehindAlpha(mNotificationDensity);
+ float newBehindAlpha = mState.getBehindAlpha();
if (mCurrentBehindAlpha != newBehindAlpha) {
mCurrentBehindAlpha = newBehindAlpha;
updateScrims();
@@ -908,6 +899,10 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, OnCo
}
}
+ public void setPulseReason(int pulseReason) {
+ ScrimState.PULSING.setPulseReason(pulseReason);
+ }
+
public interface Callback {
default void onStart() {
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
index fb3c4aa16ef5..11a2d32c9dd6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
@@ -18,8 +18,8 @@ package com.android.systemui.statusbar.phone;
import android.graphics.Color;
import android.os.Trace;
-import android.util.MathUtils;
+import com.android.systemui.doze.DozeLog;
import com.android.systemui.statusbar.ScrimView;
import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
@@ -57,13 +57,6 @@ public enum ScrimState {
mCurrentBehindAlpha = mScrimBehindAlphaKeyguard;
mCurrentInFrontAlpha = 0;
}
-
- @Override
- public float getBehindAlpha(float busynessFactor) {
- return MathUtils.map(0 /* start */, 1 /* stop */,
- mScrimBehindAlphaKeyguard, ScrimController.GRADIENT_SCRIM_ALPHA_BUSY,
- busynessFactor);
- }
},
/**
@@ -117,7 +110,7 @@ public enum ScrimState {
}
@Override
- public float getBehindAlpha(float busyness) {
+ public float getBehindAlpha() {
return mWallpaperSupportsAmbientMode && !mHasBackdrop ? 0f : 1f;
}
@@ -133,17 +126,17 @@ public enum ScrimState {
PULSING(5) {
@Override
public void prepare(ScrimState previousState) {
- mCurrentInFrontAlpha = 0;
- mCurrentInFrontTint = Color.BLACK;
- mCurrentBehindTint = Color.BLACK;
+ mCurrentInFrontAlpha = 0f;
+ if (mPulseReason == DozeLog.PULSE_REASON_NOTIFICATION
+ || mPulseReason == DozeLog.PULSE_REASON_DOCKING) {
+ mCurrentBehindAlpha = previousState.getBehindAlpha();
+ mCurrentBehindTint = Color.BLACK;
+ } else {
+ mCurrentBehindAlpha = mScrimBehindAlphaKeyguard;
+ mCurrentBehindTint = Color.TRANSPARENT;
+ }
mBlankScreen = mDisplayRequiresBlanking;
}
-
- @Override
- public float getBehindAlpha(float busyness) {
- return mWallpaperSupportsAmbientMode && !mHasBackdrop ? 0f
- : ScrimController.PULSING_WALLPAPER_SCRIM_ALPHA;
- }
},
/**
@@ -204,6 +197,7 @@ public enum ScrimState {
int mIndex;
boolean mHasBackdrop;
boolean mLaunchingAffordanceWithPreview;
+ int mPulseReason;
ScrimState(int index) {
mIndex = index;
@@ -219,6 +213,14 @@ public enum ScrimState {
public void prepare(ScrimState previousState) {
}
+ /**
+ * Check if lockscreen wallpaper or music album art exists.
+ * @return true if lockscreen wallpaper or music album art exists.
+ */
+ public boolean hasBackdrop() {
+ return mHasBackdrop;
+ }
+
public int getIndex() {
return mIndex;
}
@@ -227,7 +229,7 @@ public enum ScrimState {
return mCurrentInFrontAlpha;
}
- public float getBehindAlpha(float busyness) {
+ public float getBehindAlpha() {
return mCurrentBehindAlpha;
}
@@ -268,6 +270,10 @@ public enum ScrimState {
mAodFrontScrimAlpha = aodFrontScrimAlpha;
}
+ public void setPulseReason(int pulseReason) {
+ mPulseReason = pulseReason;
+ }
+
public void setScrimBehindAlphaKeyguard(float scrimBehindAlphaKeyguard) {
mScrimBehindAlphaKeyguard = scrimBehindAlphaKeyguard;
}
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 977e33688831..1470d0f44266 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -70,6 +70,7 @@ import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.Configuration;
import android.content.res.Resources;
+import android.database.ContentObserver;
import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.Rect;
@@ -98,6 +99,7 @@ import android.service.dreams.IDreamManager;
import android.service.notification.StatusBarNotification;
import android.util.DisplayMetrics;
import android.util.EventLog;
+import android.util.FeatureFlagUtils;
import android.util.Log;
import android.util.Slog;
import android.view.Display;
@@ -192,12 +194,12 @@ import com.android.systemui.statusbar.notification.ActivityLaunchAnimator;
import com.android.systemui.statusbar.notification.NotificationActivityStarter;
import com.android.systemui.statusbar.notification.NotificationAlertingManager;
import com.android.systemui.statusbar.notification.NotificationClicker;
-import com.android.systemui.statusbar.notification.NotificationData;
-import com.android.systemui.statusbar.notification.NotificationData.Entry;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider;
+import com.android.systemui.statusbar.notification.NotificationListController;
import com.android.systemui.statusbar.notification.NotificationRowBinder;
import com.android.systemui.statusbar.notification.VisualStabilityManager;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.logging.NotificationLogger;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
@@ -386,6 +388,7 @@ public class StatusBar extends SystemUI implements DemoMode,
private NotificationGutsManager mGutsManager;
protected NotificationLogger mNotificationLogger;
protected NotificationEntryManager mEntryManager;
+ private NotificationListController mNotificationListController;
private NotificationInterruptionStateProvider mNotificationInterruptionStateProvider;
private NotificationRowBinder mNotificationRowBinder;
protected NotificationViewHierarchyManager mViewHierarchyManager;
@@ -460,13 +463,6 @@ public class StatusBar extends SystemUI implements DemoMode,
private NotificationMediaManager mMediaManager;
protected NotificationLockscreenUserManager mLockscreenUserManager;
protected NotificationRemoteInputManager mRemoteInputManager;
- protected BubbleController mBubbleController;
- private final BubbleController.BubbleExpandListener mBubbleExpandListener =
- (isExpanding, amount) -> {
- if (amount == 1) {
- updateScrimController();
- }
- };
private final BroadcastReceiver mWallpaperChangedReceiver = new BroadcastReceiver() {
@Override
@@ -479,8 +475,13 @@ public class StatusBar extends SystemUI implements DemoMode,
WallpaperInfo info = wallpaperManager.getWallpaperInfo(UserHandle.USER_CURRENT);
final boolean deviceSupportsAodWallpaper = mContext.getResources().getBoolean(
com.android.internal.R.bool.config_dozeSupportsAodWallpaper);
+ final boolean aodImageWallpaperEnabled = FeatureFlagUtils.isEnabled(mContext,
+ FeatureFlagUtils.AOD_IMAGEWALLPAPER_ENABLED);
+ updateAodMaskVisibility(deviceSupportsAodWallpaper && aodImageWallpaperEnabled);
+ // If WallpaperInfo is null, it must be ImageWallpaper.
final boolean supportsAmbientMode = deviceSupportsAodWallpaper
- && info != null && info.supportsAmbientMode();
+ && (info == null && aodImageWallpaperEnabled
+ || info != null && info.supportsAmbientMode());
mStatusBarWindowController.setWallpaperSupportsAmbientMode(supportsAmbientMode);
mScrimController.setWallpaperSupportsAmbientMode(supportsAmbientMode);
@@ -489,7 +490,7 @@ public class StatusBar extends SystemUI implements DemoMode,
private Runnable mLaunchTransitionEndRunnable;
protected boolean mLaunchTransitionFadingAway;
- private NotificationData.Entry mDraggedDownEntry;
+ private NotificationEntry mDraggedDownEntry;
private boolean mLaunchCameraOnScreenTurningOn;
private boolean mLaunchCameraOnFinishedGoingToSleep;
private int mLastCameraLaunchSource;
@@ -582,12 +583,19 @@ public class StatusBar extends SystemUI implements DemoMode,
protected NotificationPresenter mPresenter;
private NotificationActivityStarter mNotificationActivityStarter;
private boolean mPulsing;
+ private ContentObserver mFeatureFlagObserver;
+ protected BubbleController mBubbleController;
+ private final BubbleController.BubbleExpandListener mBubbleExpandListener =
+ (isExpanding, key) -> {
+ mEntryManager.updateNotifications();
+ updateScrimController();
+ };
@Override
public void onActiveStateChanged(int code, int uid, String packageName, boolean active) {
mForegroundServiceController.onAppOpChanged(code, uid, packageName, active);
Dependency.get(Dependency.MAIN_HANDLER).post(() -> {
- mEntryManager.updateNotificationsForAppOp(code, uid, packageName, active);
+ mNotificationListController.updateNotificationsForAppOp(code, uid, packageName, active);
});
}
@@ -633,7 +641,7 @@ public class StatusBar extends SystemUI implements DemoMode,
mBubbleController.setExpandListener(mBubbleExpandListener);
KeyguardSliceProvider sliceProvider = KeyguardSliceProvider.getAttachedInstance();
if (sliceProvider != null) {
- sliceProvider.initDependencies();
+ sliceProvider.initDependencies(mMediaManager, mStatusBarStateController);
} else {
Log.w(TAG, "Cannot init KeyguardSliceProvider dependencies");
}
@@ -698,6 +706,9 @@ public class StatusBar extends SystemUI implements DemoMode,
mContext.registerReceiverAsUser(mWallpaperChangedReceiver, UserHandle.ALL,
wallpaperChangedFilter, null /* broadcastPermission */, null /* scheduler */);
mWallpaperChangedReceiver.onReceive(mContext, null);
+ mFeatureFlagObserver = new FeatureFlagObserver(
+ FeatureFlagUtils.AOD_IMAGEWALLPAPER_ENABLED /* feature */,
+ () -> mWallpaperChangedReceiver.onReceive(mContext, null) /* callback */);
// Set up the initial notification state. This needs to happen before CommandQueue.disable()
setUpPresenter();
@@ -790,7 +801,7 @@ public class StatusBar extends SystemUI implements DemoMode,
mNotificationLogger.setUpWithContainer(notifListContainer);
mNotificationIconAreaController = SystemUIFactory.getInstance()
- .createNotificationIconAreaController(context, this);
+ .createNotificationIconAreaController(context, this, mStatusBarStateController);
inflateShelf();
mNotificationIconAreaController.setupShelf(mNotificationShelf);
@@ -1035,6 +1046,13 @@ public class StatusBar extends SystemUI implements DemoMode,
mScrimController, mActivityLaunchAnimator, mStatusBarKeyguardViewManager,
mNotificationAlertingManager);
+ mNotificationListController =
+ new NotificationListController(
+ mEntryManager,
+ (NotificationListContainer) mStackScroller,
+ mForegroundServiceController,
+ mDeviceProvisionedController);
+
mAppOpsController.addCallback(APP_OPS, this);
mNotificationListener.setUpWithPresenter(mPresenter);
mNotificationShelf.setOnActivatedListener(mPresenter);
@@ -1047,6 +1065,7 @@ public class StatusBar extends SystemUI implements DemoMode,
this, Dependency.get(BubbleController.class), mNotificationActivityStarter));
mGroupAlertTransferHelper.bind(mEntryManager, mGroupManager);
+ mNotificationListController.bind();
}
/**
@@ -1510,21 +1529,21 @@ public class StatusBar extends SystemUI implements DemoMode,
}
@Override
- public void onHeadsUpPinned(NotificationData.Entry entry) {
+ public void onHeadsUpPinned(NotificationEntry entry) {
dismissVolumeDialog();
}
@Override
- public void onHeadsUpUnPinned(NotificationData.Entry entry) {
+ public void onHeadsUpUnPinned(NotificationEntry entry) {
}
@Override
- public void onHeadsUpStateChanged(Entry entry, boolean isHeadsUp) {
+ public void onHeadsUpStateChanged(NotificationEntry entry, boolean isHeadsUp) {
mEntryManager.updateNotificationRanking(null /* rankingMap */);
}
@Override
- public void onAmbientStateChanged(Entry entry, boolean isAmbient) {
+ public void onAmbientStateChanged(NotificationEntry entry, boolean isAmbient) {
mEntryManager.updateNotificationRanking(null);
if (isAmbient) {
mDozeServiceHost.fireNotificationPulse();
@@ -2551,11 +2570,11 @@ public class StatusBar extends SystemUI implements DemoMode,
};
public void resetUserExpandedStates() {
- ArrayList<Entry> activeNotifications = mEntryManager.getNotificationData()
+ ArrayList<NotificationEntry> activeNotifications = mEntryManager.getNotificationData()
.getActiveNotifications();
final int notificationCount = activeNotifications.size();
for (int i = 0; i < notificationCount; i++) {
- NotificationData.Entry entry = activeNotifications.get(i);
+ NotificationEntry entry = activeNotifications.get(i);
entry.resetUserExpansion();
}
}
@@ -2822,7 +2841,7 @@ public class StatusBar extends SystemUI implements DemoMode,
} catch (RemoteException e) {
// Ignore.
}
- mEntryManager.destroy();
+ mNotificationListController.destroy();
// End old BaseStatusBar.destroy().
if (mStatusBarWindow != null) {
mWindowManager.removeViewImmediate(mStatusBarWindow);
@@ -3506,7 +3525,7 @@ public class StatusBar extends SystemUI implements DemoMode,
int userId = mLockscreenUserManager.getCurrentUserId();
ExpandableNotificationRow row = null;
- NotificationData.Entry entry = null;
+ NotificationEntry entry = null;
if (expandView instanceof ExpandableNotificationRow) {
entry = ((ExpandableNotificationRow) expandView).getEntry();
entry.setUserExpanded(true /* userExpanded */, true /* allowChildExpansion */);
@@ -3574,10 +3593,7 @@ public class StatusBar extends SystemUI implements DemoMode,
mVisualStabilityManager.setScreenOn(false);
updateVisibleToUser();
- // We need to disable touch events because these might
- // collapse the panel after we expanded it, and thus we would end up with a blank
- // Keyguard.
- mNotificationPanel.setTouchAndAnimationDisabled(true);
+ updateNotificationPanelTouchState();
mStatusBarWindow.cancelCurrentTouch();
if (mLaunchCameraOnFinishedGoingToSleep) {
mLaunchCameraOnFinishedGoingToSleep = false;
@@ -3600,13 +3616,22 @@ public class StatusBar extends SystemUI implements DemoMode,
mDeviceInteractive = true;
mAmbientPulseManager.releaseAllImmediately();
mVisualStabilityManager.setScreenOn(true);
- mNotificationPanel.setTouchAndAnimationDisabled(false);
+ updateNotificationPanelTouchState();
updateVisibleToUser();
updateIsKeyguard();
mDozeServiceHost.stopDozing();
}
};
+ /**
+ * We need to disable touch events because these might
+ * collapse the panel after we expanded it, and thus we would end up with a blank
+ * Keyguard.
+ */
+ private void updateNotificationPanelTouchState() {
+ mNotificationPanel.setTouchAndAnimationDisabled(!mDeviceInteractive && !mPulsing);
+ }
+
final ScreenLifecycle.Observer mScreenObserver = new ScreenLifecycle.Observer() {
@Override
public void onScreenTurningOn() {
@@ -3858,6 +3883,7 @@ public class StatusBar extends SystemUI implements DemoMode,
@Override
public void pulseWhileDozing(@NonNull PulseCallback callback, int reason) {
+ mScrimController.setPulseReason(reason);
if (reason == DozeLog.PULSE_REASON_SENSOR_LONG_PRESS) {
mPowerManager.wakeUp(SystemClock.uptimeMillis(), "com.android.systemui:NODOZE");
startAssist(new Bundle());
@@ -3872,17 +3898,15 @@ public class StatusBar extends SystemUI implements DemoMode,
@Override
public void onPulseStarted() {
callback.onPulseStarted();
- if (mAmbientPulseManager.hasNotifications()) {
- // Only pulse the stack scroller if there's actually something to show.
- // Otherwise just show the always-on screen.
- setPulsing(true);
- }
+ updateNotificationPanelTouchState();
+ setPulsing(true);
}
@Override
public void onPulseFinished() {
mPulsing = false;
callback.onPulseFinished();
+ updateNotificationPanelTouchState();
setPulsing(false);
}
@@ -3986,7 +4010,7 @@ public class StatusBar extends SystemUI implements DemoMode,
}
@Override
- public void onDoubleTap(float screenX, float screenY) {
+ public void onSlpiTap(float screenX, float screenY) {
if (screenX > 0 && screenY > 0 && mAmbientIndicationContainer != null
&& mAmbientIndicationContainer.getVisibility() == View.VISIBLE) {
mAmbientIndicationContainer.getLocationOnScreen(mTmpInt2);
@@ -4412,4 +4436,33 @@ public class StatusBar extends SystemUI implements DemoMode,
public @TransitionMode int getStatusBarMode() {
return mStatusBarMode;
}
+
+ private void updateAodMaskVisibility(boolean supportsAodWallpaper) {
+ View mask = mStatusBarWindow.findViewById(R.id.aod_mask);
+ if (mask != null) {
+ mask.setVisibility(supportsAodWallpaper ? View.VISIBLE : View.INVISIBLE);
+ }
+ }
+
+ private final class FeatureFlagObserver extends ContentObserver {
+ private final Runnable mCallback;
+
+ FeatureFlagObserver(String feature, Runnable callback) {
+ this(null, feature, callback);
+ }
+
+ private FeatureFlagObserver(Handler handler, String feature, Runnable callback) {
+ super(handler);
+ mCallback = callback;
+ mContext.getContentResolver().registerContentObserver(
+ Settings.Global.getUriFor(feature), false, this);
+ }
+
+ @Override
+ public void onChange(boolean selfChange) {
+ if (mCallback != null) {
+ mStatusBarWindow.post(mCallback);
+ }
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
index db7589d0f333..f846036248d4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
@@ -36,7 +36,6 @@ import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.MobileIconStat
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.WifiIconState;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
-import com.android.systemui.statusbar.policy.IconLogger;
import com.android.systemui.tuner.TunerService;
import com.android.systemui.tuner.TunerService.Tunable;
@@ -61,7 +60,6 @@ public class StatusBarIconControllerImpl extends StatusBarIconList implements Tu
private final ArrayList<IconManager> mIconGroups = new ArrayList<>();
private final ArraySet<String> mIconBlacklist = new ArraySet<>();
- private final IconLogger mIconLogger = Dependency.get(IconLogger.class);
// Points to light or dark context depending on the... context?
private Context mContext;
@@ -147,7 +145,6 @@ public class StatusBarIconControllerImpl extends StatusBarIconList implements Tu
int viewIndex = getViewIndex(index, holder.getTag());
boolean blocked = mIconBlacklist.contains(slot);
- mIconLogger.onIconVisibility(getSlotName(index), holder.isVisible());
mIconGroups.forEach(l -> l.onIconAdded(viewIndex, slot, blocked, holder));
}
@@ -281,8 +278,6 @@ public class StatusBarIconControllerImpl extends StatusBarIconList implements Tu
return;
}
- mIconLogger.onIconHidden(slotName);
-
int slotIndex = getSlotIndex(slotName);
List<StatusBarIconHolder> iconsToRemove = slot.getHolderListInViewOrder();
for (StatusBarIconHolder holder : iconsToRemove) {
@@ -297,7 +292,6 @@ public class StatusBarIconControllerImpl extends StatusBarIconList implements Tu
if (getIcon(index, tag) == null) {
return;
}
- mIconLogger.onIconHidden(getSlotName(index));
super.removeIcon(index, tag);
int viewIndex = getViewIndex(index, 0);
mIconGroups.forEach(l -> l.onRemoveIcon(viewIndex));
@@ -305,7 +299,6 @@ public class StatusBarIconControllerImpl extends StatusBarIconList implements Tu
private void handleSet(int index, StatusBarIconHolder holder) {
int viewIndex = getViewIndex(index, holder.getTag());
- mIconLogger.onIconVisibility(getSlotName(index), holder.isVisible());
mIconGroups.forEach(l -> l.onSetIconHolder(viewIndex, holder));
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index 0f8970f1069f..bb23608799f0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -187,7 +187,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
mBouncer.setExpansion(KeyguardBouncer.EXPANSION_HIDDEN);
} else if (bouncerNeedsScrimming()) {
mBouncer.setExpansion(KeyguardBouncer.EXPANSION_VISIBLE);
- } else if (mShowing && !mDozing) {
+ } else if (mShowing) {
if (!isWakeAndUnlocking() && !mStatusBar.isInLaunchTransition()) {
mBouncer.setExpansion(expansion);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
index 8d1b911b101f..4f61009095c7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
@@ -60,10 +60,10 @@ import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.notification.ActivityLaunchAnimator;
import com.android.systemui.statusbar.notification.NotificationActivityStarter;
-import com.android.systemui.statusbar.notification.NotificationData;
import com.android.systemui.statusbar.notification.NotificationEntryListener;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.policy.HeadsUpUtil;
import com.android.systemui.statusbar.policy.KeyguardMonitor;
@@ -131,7 +131,7 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
mEntryManager.addNotificationEntryListener(new NotificationEntryListener() {
@Override
- public void onPendingEntryAdded(NotificationData.Entry entry) {
+ public void onPendingEntryAdded(NotificationEntry entry) {
handleFullScreenIntent(entry);
}
});
@@ -267,7 +267,7 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
}
}
Intent fillInIntent = null;
- NotificationData.Entry entry = row.getEntry();
+ NotificationEntry entry = row.getEntry();
CharSequence remoteInputText = null;
if (!TextUtils.isEmpty(entry.remoteInputText)) {
remoteInputText = entry.remoteInputText;
@@ -345,7 +345,7 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
}, null, false /* afterKeyguardGone */);
}
- private void handleFullScreenIntent(NotificationData.Entry entry) {
+ private void handleFullScreenIntent(NotificationEntry entry) {
boolean isHeadsUped = mNotificationInterruptionStateProvider.shouldHeadsUp(entry);
if (!isHeadsUped && entry.notification.getNotification().fullScreenIntent != null) {
if (shouldSuppressFullScreenIntent(entry)) {
@@ -413,7 +413,7 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
|| !mActivityLaunchAnimator.isAnimationPending();
}
- private boolean shouldSuppressFullScreenIntent(NotificationData.Entry entry) {
+ private boolean shouldSuppressFullScreenIntent(NotificationEntry entry) {
if (mPresenter.isDeviceInVrMode()) {
return true;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
index b9372e8935ad..df7f53b3b63a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
@@ -60,12 +60,12 @@ import com.android.systemui.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.notification.AboveShelfObserver;
import com.android.systemui.statusbar.notification.ActivityLaunchAnimator;
import com.android.systemui.statusbar.notification.NotificationAlertingManager;
-import com.android.systemui.statusbar.notification.NotificationData.Entry;
import com.android.systemui.statusbar.notification.NotificationEntryListener;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider;
import com.android.systemui.statusbar.notification.NotificationRowBinder;
import com.android.systemui.statusbar.notification.VisualStabilityManager;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
@@ -104,6 +104,8 @@ public class StatusBarNotificationPresenter implements NotificationPresenter,
Dependency.get(NotificationMediaManager.class);
private final VisualStabilityManager mVisualStabilityManager =
Dependency.get(VisualStabilityManager.class);
+ private final NotificationGutsManager mGutsManager =
+ Dependency.get(NotificationGutsManager.class);
protected AmbientPulseManager mAmbientPulseManager = Dependency.get(AmbientPulseManager.class);
private final NotificationPanelView mNotificationPanel;
@@ -183,41 +185,35 @@ public class StatusBarNotificationPresenter implements NotificationPresenter,
Dependency.get(InitController.class).addPostInitTask(() -> {
NotificationEntryListener notificationEntryListener = new NotificationEntryListener() {
@Override
- public void onNotificationAdded(Entry entry) {
+ public void onNotificationAdded(NotificationEntry entry) {
// Recalculate the position of the sliding windows and the titles.
mShadeController.updateAreThereNotifications();
}
@Override
- public void onEntryUpdated(Entry entry) {
+ public void onPostEntryUpdated(NotificationEntry entry) {
mShadeController.updateAreThereNotifications();
}
@Override
public void onEntryRemoved(
- @Nullable Entry entry,
- String key,
- StatusBarNotification old,
+ @Nullable NotificationEntry entry,
NotificationVisibility visibility,
- boolean lifetimeExtended,
boolean removedByUser) {
- if (!lifetimeExtended) {
- StatusBarNotificationPresenter.this.onNotificationRemoved(key, old);
- }
+ StatusBarNotificationPresenter.this.onNotificationRemoved(
+ entry.key, entry.notification);
if (removedByUser) {
maybeEndAmbientPulse();
}
}
};
- NotificationGutsManager gutsManager = Dependency.get(NotificationGutsManager.class);
-
mViewHierarchyManager.setUpWithPresenter(this, notifListContainer);
mEntryManager.setUpWithPresenter(this, notifListContainer, mHeadsUpManager);
mEntryManager.addNotificationEntryListener(notificationEntryListener);
mEntryManager.addNotificationLifetimeExtender(mHeadsUpManager);
mEntryManager.addNotificationLifetimeExtender(mAmbientPulseManager);
- mEntryManager.addNotificationLifetimeExtender(gutsManager);
+ mEntryManager.addNotificationLifetimeExtender(mGutsManager);
mEntryManager.addNotificationLifetimeExtenders(
remoteInputManager.getLifetimeExtenders());
mNotificationRowBinder.setUpWithPresenter(this, notifListContainer, mHeadsUpManager,
@@ -227,7 +223,7 @@ public class StatusBarNotificationPresenter implements NotificationPresenter,
mLockscreenUserManager.setUpWithPresenter(this);
mMediaManager.setUpWithPresenter(this);
mVisualStabilityManager.setUpWithPresenter(this);
- gutsManager.setUpWithPresenter(this,
+ mGutsManager.setUpWithPresenter(this,
notifListContainer, mCheckSaveListener, mOnSettingsClickListener);
// ForegroundServiceControllerListener adds its listener in its constructor
// but we need to request it here in order for it to be instantiated.
@@ -246,7 +242,7 @@ public class StatusBarNotificationPresenter implements NotificationPresenter,
MessagingMessage.dropCache();
MessagingGroup.dropCache();
if (!KeyguardUpdateMonitor.getInstance(mContext).isSwitchingUser()) {
- mEntryManager.updateNotificationsOnDensityOrFontScaleChanged();
+ updateNotificationsOnDensityOrFontScaleChanged();
} else {
mReinflateNotificationsOnUserSwitched = true;
}
@@ -262,10 +258,10 @@ public class StatusBarNotificationPresenter implements NotificationPresenter,
}
private void updateNotificationOnUiModeChanged() {
- ArrayList<Entry> userNotifications
+ ArrayList<NotificationEntry> userNotifications
= mEntryManager.getNotificationData().getNotificationsForCurrentUser();
for (int i = 0; i < userNotifications.size(); i++) {
- Entry entry = userNotifications.get(i);
+ NotificationEntry entry = userNotifications.get(i);
ExpandableNotificationRow row = entry.getRow();
if (row != null) {
row.onUiModeChanged();
@@ -273,6 +269,19 @@ public class StatusBarNotificationPresenter implements NotificationPresenter,
}
}
+ private void updateNotificationsOnDensityOrFontScaleChanged() {
+ ArrayList<NotificationEntry> userNotifications =
+ mEntryManager.getNotificationData().getNotificationsForCurrentUser();
+ for (int i = 0; i < userNotifications.size(); i++) {
+ NotificationEntry entry = userNotifications.get(i);
+ entry.onDensityOrFontScaleChanged();
+ boolean exposedGuts = entry.areGutsExposed();
+ if (exposedGuts) {
+ mGutsManager.onDensityOrFontScaleChanged(entry);
+ }
+ }
+ }
+
@Override
public boolean isCollapsing() {
return mNotificationPanel.isCollapsing()
@@ -327,7 +336,7 @@ public class StatusBarNotificationPresenter implements NotificationPresenter,
return !mEntryManager.getNotificationData().getActiveNotifications().isEmpty();
}
- public boolean canHeadsUp(Entry entry, StatusBarNotification sbn) {
+ public boolean canHeadsUp(NotificationEntry entry, StatusBarNotification sbn) {
if (mShadeController.isDozing()) {
return false;
}
@@ -371,7 +380,7 @@ public class StatusBarNotificationPresenter implements NotificationPresenter,
if (MULTIUSER_DEBUG) mNotificationPanelDebugText.setText("USER " + newUserId);
mCommandQueue.animateCollapsePanels();
if (mReinflateNotificationsOnUserSwitched) {
- mEntryManager.updateNotificationsOnDensityOrFontScaleChanged();
+ updateNotificationsOnDensityOrFontScaleChanged();
mReinflateNotificationsOnUserSwitched = false;
}
if (mDispatchUiModeChangeOnUserSwitched) {
@@ -385,7 +394,7 @@ public class StatusBarNotificationPresenter implements NotificationPresenter,
}
@Override
- public void onBindRow(Entry entry, PackageManager pmUser,
+ public void onBindRow(NotificationEntry entry, PackageManager pmUser,
StatusBarNotification sbn, ExpandableNotificationRow row) {
row.setAboveShelfChangedListener(mAboveShelfObserver);
row.setSecureStateProvider(mUnlockMethodCache::canSkipBouncer);
@@ -443,7 +452,7 @@ public class StatusBarNotificationPresenter implements NotificationPresenter,
}
@Override
- public void onExpandClicked(Entry clickedEntry, boolean nowExpanded) {
+ public void onExpandClicked(NotificationEntry clickedEntry, boolean nowExpanded) {
mHeadsUpManager.setExpanded(clickedEntry, nowExpanded);
if (mStatusBarStateController.getState() == StatusBarState.KEYGUARD && nowExpanded) {
mShadeController.goToLockedShade(clickedEntry.getRow());
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 3ddfc0c81c60..8d5841088591 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java
@@ -53,7 +53,8 @@ import javax.inject.Singleton;
/**
*/
@Singleton
-public class StatusBarRemoteInputCallback implements Callback, Callbacks {
+public class StatusBarRemoteInputCallback implements Callback, Callbacks,
+ StatusBarStateController.StateListener {
private final KeyguardMonitor mKeyguardMonitor = Dependency.get(KeyguardMonitor.class);
private final StatusBarStateController mStatusBarStateController
@@ -63,7 +64,6 @@ public class StatusBarRemoteInputCallback implements Callback, Callbacks {
private final ActivityStarter mActivityStarter = Dependency.get(ActivityStarter.class);
private final Context mContext;
private View mPendingWorkRemoteInputView;
- private final StatusBarStateController.StateListener mStateListener = this::setStatusBarState;
private View mPendingRemoteInputView;
private final ShadeController mShadeController = Dependency.get(ShadeController.class);
private KeyguardManager mKeyguardManager;
@@ -78,13 +78,14 @@ public class StatusBarRemoteInputCallback implements Callback, Callbacks {
mContext = context;
mContext.registerReceiverAsUser(mChallengeReceiver, UserHandle.ALL,
new IntentFilter(ACTION_DEVICE_LOCKED_CHANGED), null, null);
- mStatusBarStateController.addCallback(mStateListener);
+ mStatusBarStateController.addCallback(this);
mKeyguardManager = context.getSystemService(KeyguardManager.class);
mCommandQueue = getComponent(context, CommandQueue.class);
mCommandQueue.addCallback(this);
}
- private void setStatusBarState(int state) {
+ @Override
+ public void onStateChanged(int state) {
if (state == StatusBarState.SHADE && mStatusBarStateController.leaveOpenOnKeyguardHide()) {
if (!mStatusBarStateController.isKeyguardRequested()) {
if (mPendingRemoteInputView != null
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
index 53e461db3dd1..8b25c3469abe 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
@@ -339,7 +339,7 @@ public class StatusBarWindowView extends FrameLayout {
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
NotificationStackScrollLayout stackScrollLayout = getStackScrollLayout();
- if (mService.isDozing() && !stackScrollLayout.hasPulsingNotifications()) {
+ if (mService.isDozing() && !mService.isPulsing()) {
// Capture all touch events in always-on.
return true;
}
@@ -347,8 +347,7 @@ public class StatusBarWindowView extends FrameLayout {
if (mNotificationPanel.isFullyExpanded()
&& stackScrollLayout.getVisibility() == View.VISIBLE
&& mStatusBarStateController.getState() == StatusBarState.KEYGUARD
- && !mService.isBouncerShowing()
- && !mService.isDozing()) {
+ && !mService.isBouncerShowing()) {
intercept = mDragDownHelper.onInterceptTouchEvent(ev);
}
if (!intercept) {
@@ -369,7 +368,7 @@ public class StatusBarWindowView extends FrameLayout {
boolean handled = false;
if (mService.isDozing()) {
mDoubleTapHelper.onTouchEvent(ev);
- handled = true;
+ handled = !mService.isPulsing();
}
if ((mStatusBarStateController.getState() == StatusBarState.KEYGUARD && !handled)
|| mDragDownHelper.isDraggingDown()) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java
index f65f8261dcfb..5e94152e7eab 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java
@@ -48,18 +48,36 @@ public interface BatteryController extends DemoMode, Dumpable,
}
/**
- * A listener that will be notified whenever a change in battery level or power save mode
- * has occurred.
+ * A listener that will be notified whenever a change in battery level or power save mode has
+ * occurred.
*/
interface BatteryStateChangeCallback {
- default void onBatteryLevelChanged(int level, boolean pluggedIn, boolean charging) {}
- default void onPowerSaveChanged(boolean isPowerSave) {}
+
+ default void onBatteryLevelChanged(int level, boolean pluggedIn, boolean charging) {
+ }
+
+ default void onPowerSaveChanged(boolean isPowerSave) {
+ }
}
/**
- * If available, get the estimated battery time remaining as a string
+ * If available, get the estimated battery time remaining as a string.
+ *
+ * @param completion A lambda that will be called with the result of fetching the estimate. The
+ * first time this method is called may need to be dispatched to a background thread. The
+ * completion is called on the main thread
+ */
+ default void getEstimatedTimeRemainingString(EstimateFetchCompletion completion) {}
+
+ /**
+ * Callback called when the estimated time remaining text is fetched.
*/
- default String getEstimatedTimeRemainingString() {
- return null;
+ public interface EstimateFetchCompletion {
+
+ /**
+ * The callback
+ * @param estimate the estimate
+ */
+ void onBatteryRemainingEstimateRetrieved(String estimate);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
index 6190c8fff8cc..af3c96f73642 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
@@ -27,9 +27,12 @@ import android.os.PowerManager;
import android.os.PowerSaveState;
import android.util.Log;
+import androidx.annotation.Nullable;
+
import com.android.internal.annotations.VisibleForTesting;
import com.android.settingslib.fuelgauge.BatterySaverUtils;
import com.android.settingslib.utils.PowerUtil;
+import com.android.systemui.Dependency;
import com.android.systemui.power.EnhancedEstimates;
import com.android.systemui.power.Estimate;
@@ -56,6 +59,7 @@ public class BatteryControllerImpl extends BroadcastReceiver implements BatteryC
private final EnhancedEstimates mEstimates;
private final ArrayList<BatteryController.BatteryStateChangeCallback> mChangeCallbacks = new ArrayList<>();
+ private final ArrayList<EstimateFetchCompletion> mFetchCallbacks = new ArrayList<>();
private final PowerManager mPowerManager;
private final Handler mHandler;
private final Context mContext;
@@ -70,6 +74,7 @@ public class BatteryControllerImpl extends BroadcastReceiver implements BatteryC
private boolean mHasReceivedBattery = false;
private Estimate mEstimate;
private long mLastEstimateTimestamp = -1;
+ private boolean mFetchingEstimate = false;
@Inject
public BatteryControllerImpl(Context context, EnhancedEstimates enhancedEstimates) {
@@ -197,20 +202,61 @@ public class BatteryControllerImpl extends BroadcastReceiver implements BatteryC
}
@Override
- public String getEstimatedTimeRemainingString() {
- if (mEstimate == null
- || System.currentTimeMillis() > mLastEstimateTimestamp + UPDATE_GRANULARITY_MSEC) {
- updateEstimate();
+ public void getEstimatedTimeRemainingString(EstimateFetchCompletion completion) {
+ if (mEstimate != null
+ && mLastEstimateTimestamp > System.currentTimeMillis() - UPDATE_GRANULARITY_MSEC) {
+ String percentage = generateTimeRemainingString();
+ completion.onBatteryRemainingEstimateRetrieved(percentage);
+ return;
+ }
+
+ // Need to fetch or refresh the estimate, but it may involve binder calls so offload the
+ // work
+ synchronized (mFetchCallbacks) {
+ mFetchCallbacks.add(completion);
}
- // Estimates may not exist yet even if we've checked
+ updateEstimateInBackground();
+ }
+
+ @Nullable
+ private String generateTimeRemainingString() {
if (mEstimate == null) {
return null;
}
- final String percentage = NumberFormat.getPercentInstance().format((double) mLevel / 100.0);
+
+ String percentage = NumberFormat.getPercentInstance().format((double) mLevel / 100.0);
return PowerUtil.getBatteryRemainingShortStringFormatted(
mContext, mEstimate.estimateMillis);
}
+ private void updateEstimateInBackground() {
+ if (mFetchingEstimate) {
+ // Already dispatched a fetch. It will notify all listeners when finished
+ return;
+ }
+
+ mFetchingEstimate = true;
+ Dependency.get(Dependency.BG_HANDLER).post(() -> {
+ mEstimate = mEstimates.getEstimate();
+ mLastEstimateTimestamp = System.currentTimeMillis();
+ mFetchingEstimate = false;
+
+ Dependency.get(Dependency.MAIN_HANDLER).post(this::notifyEstimateFetchCallbacks);
+ });
+ }
+
+ private void notifyEstimateFetchCallbacks() {
+ String estimate = generateTimeRemainingString();
+
+ synchronized (mFetchCallbacks) {
+ for (EstimateFetchCompletion completion : mFetchCallbacks) {
+ completion.onBatteryRemainingEstimateRetrieved(estimate);
+ }
+
+ mFetchCallbacks.clear();
+ }
+ }
+
private void updateEstimate() {
mEstimate = mEstimates.getEstimate();
mLastEstimateTimestamp = System.currentTimeMillis();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java
index de7ef3ba645d..4299af7daf66 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java
@@ -274,7 +274,6 @@ public class Clock extends TextView implements DemoMode, Tunable, CommandQueue.C
private void updateClockVisibility() {
boolean visible = shouldBeVisible();
- Dependency.get(IconLogger.class).onIconVisibility("clock", visible);
int visibility = visible ? View.VISIBLE : View.GONE;
super.setVisibility(visibility);
}
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 a02c9d51fecf..fd3f680e5e77 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
@@ -30,7 +30,7 @@ import android.util.Log;
import com.android.internal.logging.MetricsLogger;
import com.android.systemui.R;
import com.android.systemui.statusbar.AlertingNotificationManager;
-import com.android.systemui.statusbar.notification.NotificationData;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.row.NotificationInflater.InflationFlag;
import java.io.FileDescriptor;
@@ -108,11 +108,11 @@ public abstract class HeadsUpManager extends AlertingNotificationManager {
}
}
- protected boolean shouldHeadsUpBecomePinned(@NonNull NotificationData.Entry entry) {
+ protected boolean shouldHeadsUpBecomePinned(@NonNull NotificationEntry entry) {
return hasFullScreenIntent(entry);
}
- protected boolean hasFullScreenIntent(@NonNull NotificationData.Entry entry) {
+ protected boolean hasFullScreenIntent(@NonNull NotificationEntry entry) {
return entry.notification.getNotification().fullScreenIntent != null;
}
@@ -121,7 +121,7 @@ public abstract class HeadsUpManager extends AlertingNotificationManager {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "setEntryPinned: " + isPinned);
}
- NotificationData.Entry entry = headsUpEntry.mEntry;
+ NotificationEntry entry = headsUpEntry.mEntry;
if (entry.isRowPinned() != isPinned) {
entry.setRowPinned(isPinned);
updatePinnedMode();
@@ -141,7 +141,7 @@ public abstract class HeadsUpManager extends AlertingNotificationManager {
@Override
protected void onAlertEntryAdded(AlertEntry alertEntry) {
- NotificationData.Entry entry = alertEntry.mEntry;
+ NotificationEntry entry = alertEntry.mEntry;
entry.setHeadsUp(true);
setEntryPinned((HeadsUpEntry) alertEntry, shouldHeadsUpBecomePinned(entry));
for (OnHeadsUpChangedListener listener : mListeners) {
@@ -151,7 +151,7 @@ public abstract class HeadsUpManager extends AlertingNotificationManager {
@Override
protected void onAlertEntryRemoved(AlertEntry alertEntry) {
- NotificationData.Entry entry = alertEntry.mEntry;
+ NotificationEntry entry = alertEntry.mEntry;
entry.setHeadsUp(false);
setEntryPinned((HeadsUpEntry) alertEntry, false /* isPinned */);
for (OnHeadsUpChangedListener listener : mListeners) {
@@ -222,7 +222,7 @@ public abstract class HeadsUpManager extends AlertingNotificationManager {
* Returns the top Heads Up Notification, which appears to show at first.
*/
@Nullable
- public NotificationData.Entry getTopEntry() {
+ public NotificationEntry getTopEntry() {
HeadsUpEntry topEntry = getTopHeadsUpEntry();
return (topEntry != null) ? topEntry.mEntry : null;
}
@@ -323,7 +323,7 @@ public abstract class HeadsUpManager extends AlertingNotificationManager {
* @return -1 if the first argument should be ranked higher than the second, 1 if the second
* one should be ranked higher and 0 if they are equal.
*/
- public int compare(@NonNull NotificationData.Entry a, @NonNull NotificationData.Entry b) {
+ public int compare(@NonNull NotificationEntry a, @NonNull NotificationEntry b) {
AlertEntry aEntry = getHeadsUpEntry(a.key);
AlertEntry bEntry = getHeadsUpEntry(b.key);
if (aEntry == null || bEntry == null) {
@@ -336,7 +336,7 @@ public abstract class HeadsUpManager extends AlertingNotificationManager {
* Set an entry to be expanded and therefore stick in the heads up area if it's pinned
* until it's collapsed again.
*/
- public void setExpanded(@NonNull NotificationData.Entry entry, boolean expanded) {
+ public void setExpanded(@NonNull NotificationEntry entry, boolean expanded) {
HeadsUpEntry headsUpEntry = getHeadsUpEntry(entry.key);
if (headsUpEntry != null && entry.isRowPinned()) {
headsUpEntry.setExpanded(expanded);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/IconLogger.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/IconLogger.java
deleted file mode 100644
index 710e1df61404..000000000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/IconLogger.java
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
- * except in compliance with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the
- * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the specific language governing
- * permissions and limitations under the License.
- */
-
-package com.android.systemui.statusbar.policy;
-
-public interface IconLogger {
-
- void onIconShown(String tag);
- void onIconHidden(String tag);
-
- default void onIconVisibility(String tag, boolean visible) {
- if (visible) {
- onIconShown(tag);
- } else {
- onIconHidden(tag);
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/IconLoggerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/IconLoggerImpl.java
deleted file mode 100644
index ba6369e2c90e..000000000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/IconLoggerImpl.java
+++ /dev/null
@@ -1,115 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
- * except in compliance with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the
- * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the specific language governing
- * permissions and limitations under the License.
- */
-
-package com.android.systemui.statusbar.policy;
-
-import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_NUM_STATUS_ICONS;
-import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_STATUS_ICONS;
-import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.STATUS_BAR_ICONS_CHANGED;
-import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_ACTION;
-import static com.android.systemui.Dependency.BG_LOOPER_NAME;
-
-import android.content.Context;
-import android.metrics.LogMaker;
-import android.os.Handler;
-import android.os.Looper;
-import android.util.ArraySet;
-
-import androidx.annotation.VisibleForTesting;
-
-import com.android.internal.logging.MetricsLogger;
-
-import java.util.Arrays;
-import java.util.List;
-
-import javax.inject.Inject;
-import javax.inject.Named;
-
-public class IconLoggerImpl implements IconLogger {
-
- // Minimum ms between log statements.
- // NonFinalForTesting
- @VisibleForTesting
- protected static long MIN_LOG_INTERVAL = 1000;
-
- private final Context mContext;
- private final Handler mHandler;
- private final MetricsLogger mLogger;
- private final ArraySet<String> mIcons = new ArraySet<>();
- private final List<String> mIconIndex;
- private long mLastLog = System.currentTimeMillis();
-
- @Inject
- public IconLoggerImpl(Context context, @Named(BG_LOOPER_NAME) Looper bgLooper,
- MetricsLogger logger) {
- mContext = context;
- mHandler = new Handler(bgLooper);
- mLogger = logger;
- String[] icons = mContext.getResources().getStringArray(
- com.android.internal.R.array.config_statusBarIcons);
- mIconIndex = Arrays.asList(icons);
- doLog();
- }
-
- @Override
- public void onIconShown(String tag) {
- synchronized (mIcons) {
- if (mIcons.contains(tag)) return;
- mIcons.add(tag);
- }
- if (!mHandler.hasCallbacks(mLog)) {
- mHandler.postDelayed(mLog, MIN_LOG_INTERVAL);
- }
- }
-
- @Override
- public void onIconHidden(String tag) {
- synchronized (mIcons) {
- if (!mIcons.contains(tag)) return;
- mIcons.remove(tag);
- }
- if (!mHandler.hasCallbacks(mLog)) {
- mHandler.postDelayed(mLog, MIN_LOG_INTERVAL);
- }
- }
-
- private void doLog() {
- long time = System.currentTimeMillis();
- long timeSinceLastLog = time - mLastLog;
- mLastLog = time;
-
- ArraySet<String> icons;
- synchronized (mIcons) {
- icons = new ArraySet<>(mIcons);
- }
- mLogger.write(new LogMaker(STATUS_BAR_ICONS_CHANGED)
- .setType(TYPE_ACTION)
- .setLatency(timeSinceLastLog)
- .addTaggedData(FIELD_NUM_STATUS_ICONS, icons.size())
- .addTaggedData(FIELD_STATUS_ICONS, getBitField(icons)));
- }
-
- private int getBitField(ArraySet<String> icons) {
- int iconsVisible = 0;
- for (String icon : icons) {
- int index = mIconIndex.indexOf(icon);
- if (index >= 0) {
- iconsVisible |= (1 << index);
- }
- }
- return iconsVisible;
- }
-
- private final Runnable mLog = this::doLog;
-}
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 2b0e708cf290..77c80dd3db57 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
@@ -545,7 +545,8 @@ public class NetworkControllerImpl extends BroadcastReceiver
@VisibleForTesting
void doUpdateMobileControllers() {
- List<SubscriptionInfo> subscriptions = mSubscriptionManager.getActiveSubscriptionInfoList();
+ List<SubscriptionInfo> subscriptions = mSubscriptionManager
+ .getActiveSubscriptionInfoList(true);
if (subscriptions == null) {
subscriptions = Collections.emptyList();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/OnHeadsUpChangedListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/OnHeadsUpChangedListener.java
index 7ad547afdb53..438226ae082d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/OnHeadsUpChangedListener.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/OnHeadsUpChangedListener.java
@@ -16,8 +16,7 @@
package com.android.systemui.statusbar.policy;
-import com.android.systemui.statusbar.notification.NotificationData;
-import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
/**
* A listener to heads up changes
@@ -33,12 +32,12 @@ public interface OnHeadsUpChangedListener {
/**
* A notification was just pinned to the top.
*/
- default void onHeadsUpPinned(NotificationData.Entry entry) {}
+ default void onHeadsUpPinned(NotificationEntry entry) {}
/**
* A notification was just unpinned from the top.
*/
- default void onHeadsUpUnPinned(NotificationData.Entry entry) {}
+ default void onHeadsUpUnPinned(NotificationEntry entry) {}
/**
* A notification just became a heads up or turned back to its normal state.
@@ -46,5 +45,5 @@ public interface OnHeadsUpChangedListener {
* @param entry the entry of the changed notification
* @param isHeadsUp whether the notification is now a headsUp notification
*/
- default void onHeadsUpStateChanged(NotificationData.Entry entry, boolean isHeadsUp) {}
+ default void onHeadsUpStateChanged(NotificationEntry entry, boolean isHeadsUp) {}
}
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 866015e84af0..9c4db34b4f76 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
@@ -18,6 +18,7 @@ package com.android.systemui.statusbar.policy;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
+import android.app.ActivityManager;
import android.app.Notification;
import android.app.PendingIntent;
import android.app.RemoteInput;
@@ -28,6 +29,7 @@ import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.SystemClock;
+import android.os.UserHandle;
import android.text.Editable;
import android.text.SpannedString;
import android.text.TextWatcher;
@@ -56,7 +58,7 @@ import com.android.systemui.Dependency;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
import com.android.systemui.statusbar.RemoteInputController;
-import com.android.systemui.statusbar.notification.NotificationData;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper;
import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
@@ -83,7 +85,7 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
private RemoteInputController mController;
private RemoteInputQuickSettingsDisabler mRemoteInputQuickSettingsDisabler;
- private NotificationData.Entry mEntry;
+ private NotificationEntry mEntry;
private boolean mRemoved;
@@ -180,7 +182,7 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
}
public static RemoteInputView inflate(Context context, ViewGroup root,
- NotificationData.Entry entry,
+ NotificationEntry entry,
RemoteInputController controller) {
RemoteInputView v = (RemoteInputView)
LayoutInflater.from(context).inflate(R.layout.remote_input, root, false);
@@ -283,6 +285,11 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
focus();
}
+ private static UserHandle computeTextOperationUser(UserHandle notificationUser) {
+ return UserHandle.ALL.equals(notificationUser)
+ ? UserHandle.of(ActivityManager.getCurrentUser()) : notificationUser;
+ }
+
public void focus() {
MetricsLogger.action(mContext, MetricsProto.MetricsEvent.ACTION_REMOTE_INPUT_OPEN,
mEntry.notification.getPackageName());
@@ -291,6 +298,7 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
if (mWrapper != null) {
mWrapper.setRemoteInputVisible(true);
}
+ mEditText.setTextOperationUser(computeTextOperationUser(mEntry.notification.getUser()));
mEditText.setInnerFocusable(true);
mEditText.mShowImeOnInputConnection = true;
mEditText.setText(mEntry.remoteInputText);
@@ -320,6 +328,7 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
mResetting = true;
mEntry.remoteInputTextWhenReset = SpannedString.valueOf(mEditText.getText());
+ mEditText.setTextOperationUser(null);
mEditText.getText().clear();
mEditText.setEnabled(true);
mSendButton.setVisibility(VISIBLE);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyConstants.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyConstants.java
index 6193159ec0a5..3bd0d456dbd3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyConstants.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyConstants.java
@@ -18,6 +18,7 @@ package com.android.systemui.statusbar.policy;
import static com.android.systemui.Dependency.MAIN_HANDLER_NAME;
+import android.app.RemoteInput;
import android.content.Context;
import android.content.res.Resources;
import android.database.ContentObserver;
@@ -42,14 +43,21 @@ public final class SmartReplyConstants extends ContentObserver {
private static final String KEY_REQUIRES_TARGETING_P = "requires_targeting_p";
private static final String KEY_MAX_SQUEEZE_REMEASURE_ATTEMPTS =
"max_squeeze_remeasure_attempts";
+ private static final String KEY_EDIT_CHOICES_BEFORE_SENDING =
+ "edit_choices_before_sending";
+ private static final String KEY_SHOW_IN_HEADS_UP = "show_in_heads_up";
private final boolean mDefaultEnabled;
private final boolean mDefaultRequiresP;
private final int mDefaultMaxSqueezeRemeasureAttempts;
+ private final boolean mDefaultEditChoicesBeforeSending;
+ private final boolean mDefaultShowInHeadsUp;
private boolean mEnabled;
private boolean mRequiresTargetingP;
private int mMaxSqueezeRemeasureAttempts;
+ private boolean mEditChoicesBeforeSending;
+ private boolean mShowInHeadsUp;
private final Context mContext;
private final KeyValueListParser mParser = new KeyValueListParser(',');
@@ -66,6 +74,10 @@ public final class SmartReplyConstants extends ContentObserver {
R.bool.config_smart_replies_in_notifications_requires_targeting_p);
mDefaultMaxSqueezeRemeasureAttempts = resources.getInteger(
R.integer.config_smart_replies_in_notifications_max_squeeze_remeasure_attempts);
+ mDefaultEditChoicesBeforeSending = resources.getBoolean(
+ R.bool.config_smart_replies_in_notifications_edit_choices_before_sending);
+ mDefaultShowInHeadsUp = resources.getBoolean(
+ R.bool.config_smart_replies_in_notifications_show_in_heads_up);
mContext.getContentResolver().registerContentObserver(
Settings.Global.getUriFor(Settings.Global.SMART_REPLIES_IN_NOTIFICATIONS_FLAGS),
@@ -90,6 +102,9 @@ public final class SmartReplyConstants extends ContentObserver {
mRequiresTargetingP = mParser.getBoolean(KEY_REQUIRES_TARGETING_P, mDefaultRequiresP);
mMaxSqueezeRemeasureAttempts = mParser.getInt(
KEY_MAX_SQUEEZE_REMEASURE_ATTEMPTS, mDefaultMaxSqueezeRemeasureAttempts);
+ mEditChoicesBeforeSending = mParser.getBoolean(
+ KEY_EDIT_CHOICES_BEFORE_SENDING, mDefaultEditChoicesBeforeSending);
+ mShowInHeadsUp = mParser.getBoolean(KEY_SHOW_IN_HEADS_UP, mDefaultShowInHeadsUp);
}
}
@@ -113,4 +128,31 @@ public final class SmartReplyConstants extends ContentObserver {
public int getMaxSqueezeRemeasureAttempts() {
return mMaxSqueezeRemeasureAttempts;
}
+
+ /**
+ * Returns whether by tapping on a choice should let the user edit the input before it
+ * is sent to the app.
+ *
+ * @param remoteInputEditChoicesBeforeSending The value from
+ * {@link RemoteInput#getEditChoicesBeforeSending()}
+ */
+ public boolean getEffectiveEditChoicesBeforeSending(
+ @RemoteInput.EditChoicesBeforeSending int remoteInputEditChoicesBeforeSending) {
+ switch (remoteInputEditChoicesBeforeSending) {
+ case RemoteInput.EDIT_CHOICES_BEFORE_SENDING_DISABLED:
+ return false;
+ case RemoteInput.EDIT_CHOICES_BEFORE_SENDING_ENABLED:
+ return true;
+ case RemoteInput.EDIT_CHOICES_BEFORE_SENDING_AUTO:
+ default:
+ return mEditChoicesBeforeSending;
+ }
+ }
+
+ /**
+ * Returns whether smart suggestions should be enabled in heads-up notifications.
+ */
+ public boolean getShowInHeadsUp() {
+ return mShowInHeadsUp;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java
index f85d8038ab3f..d6eff941ed70 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java
@@ -36,8 +36,8 @@ import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.SmartReplyController;
-import com.android.systemui.statusbar.notification.NotificationData;
import com.android.systemui.statusbar.notification.NotificationUtils;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.phone.KeyguardDismissUtil;
import java.text.BreakIterator;
@@ -194,7 +194,7 @@ public class SmartReplyView extends ViewGroup {
*/
public void addRepliesFromRemoteInput(
SmartReplies smartReplies,
- SmartReplyController smartReplyController, NotificationData.Entry entry) {
+ SmartReplyController smartReplyController, NotificationEntry entry) {
if (smartReplies.remoteInput != null && smartReplies.pendingIntent != null) {
if (smartReplies.choices != null) {
for (int i = 0; i < smartReplies.choices.length; ++i) {
@@ -212,7 +212,7 @@ public class SmartReplyView extends ViewGroup {
* notification are shown.
*/
public void addSmartActions(SmartActions smartActions,
- SmartReplyController smartReplyController, NotificationData.Entry entry,
+ SmartReplyController smartReplyController, NotificationEntry entry,
HeadsUpManager headsUpManager) {
int numSmartActions = smartActions.actions.size();
for (int n = 0; n < numSmartActions; n++) {
@@ -235,16 +235,15 @@ public class SmartReplyView extends ViewGroup {
@VisibleForTesting
Button inflateReplyButton(Context context, ViewGroup root, int replyIndex,
SmartReplies smartReplies, SmartReplyController smartReplyController,
- NotificationData.Entry entry) {
+ NotificationEntry entry) {
Button b = (Button) LayoutInflater.from(context).inflate(
R.layout.smart_reply_button, root, false);
CharSequence choice = smartReplies.choices[replyIndex];
b.setText(choice);
OnDismissAction action = () -> {
- // TODO(b/111437455): Also for EDIT_CHOICES_BEFORE_SENDING_AUTO, depending on flags.
- if (smartReplies.remoteInput.getEditChoicesBeforeSending()
- == RemoteInput.EDIT_CHOICES_BEFORE_SENDING_ENABLED) {
+ if (mConstants.getEffectiveEditChoicesBeforeSending(
+ smartReplies.remoteInput.getEditChoicesBeforeSending())) {
entry.remoteInputText = choice;
mRemoteInputManager.activateRemoteInput(b,
new RemoteInput[] { smartReplies.remoteInput }, smartReplies.remoteInput,
@@ -289,7 +288,7 @@ public class SmartReplyView extends ViewGroup {
@VisibleForTesting
Button inflateActionButton(Context context, ViewGroup root, int actionIndex,
SmartActions smartActions, SmartReplyController smartReplyController,
- NotificationData.Entry entry, HeadsUpManager headsUpManager) {
+ NotificationEntry entry, HeadsUpManager headsUpManager) {
Notification.Action action = smartActions.actions.get(actionIndex);
Button button = (Button) LayoutInflater.from(context).inflate(
R.layout.smart_action_button, root, false);
@@ -311,8 +310,6 @@ public class SmartReplyView extends ViewGroup {
headsUpManager.removeNotification(entry.key, true);
}));
- // TODO(b/119010281): handle accessibility
-
// Mark this as an Action button
final LayoutParams lp = (LayoutParams) button.getLayoutParams();
lp.buttonType = SmartButtonType.ACTION;
@@ -428,9 +425,9 @@ public class SmartReplyView extends ViewGroup {
markButtonsWithPendingSqueezeStatusAs(
LayoutParams.SQUEEZE_STATUS_FAILED, coveredSuggestions);
- // The current button doesn't fit, so there's no point in measuring further
- // buttons.
- break;
+ // The current button doesn't fit, keep on adding lower-priority buttons in case
+ // any of those fit.
+ continue;
}
// The current button fits, so mark all squeezed buttons as "successfully squeezed"
diff --git a/packages/SystemUI/src/com/android/systemui/wallpaper/AodMaskView.java b/packages/SystemUI/src/com/android/systemui/wallpaper/AodMaskView.java
new file mode 100644
index 000000000000..52cabe278e2d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/wallpaper/AodMaskView.java
@@ -0,0 +1,222 @@
+/*
+ * Copyright (C) 2018 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.wallpaper;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.app.WallpaperManager;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.RectF;
+import android.hardware.display.DisplayManager;
+import android.hardware.display.DisplayManager.DisplayListener;
+import android.util.AttributeSet;
+import android.util.FeatureFlagUtils;
+import android.util.Log;
+import android.view.Display;
+import android.view.DisplayInfo;
+import android.widget.ImageView;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.Dependency;
+import com.android.systemui.R;
+import com.android.systemui.statusbar.StatusBarStateController;
+import com.android.systemui.statusbar.notification.AnimatableProperty;
+import com.android.systemui.statusbar.notification.PropertyAnimator;
+import com.android.systemui.statusbar.notification.stack.AnimationProperties;
+import com.android.systemui.statusbar.phone.ScrimState;
+
+/**
+ * A view that draws mask upon either image wallpaper or music album art in AOD.
+ */
+public class AodMaskView extends ImageView implements StatusBarStateController.StateListener,
+ ImageWallpaperTransformer.TransformationListener {
+ private static final String TAG = AodMaskView.class.getSimpleName();
+ private static final int TRANSITION_DURATION = 1000;
+
+ private static final AnimatableProperty TRANSITION_PROGRESS = AnimatableProperty.from(
+ "transition_progress",
+ AodMaskView::setTransitionAmount,
+ AodMaskView::getTransitionAmount,
+ R.id.aod_mask_transition_progress_tag,
+ R.id.aod_mask_transition_progress_start_tag,
+ R.id.aod_mask_transition_progress_end_tag
+ );
+
+ private final AnimationProperties mTransitionProperties = new AnimationProperties();
+ private final ImageWallpaperTransformer mTransformer;
+ private final RectF mBounds = new RectF();
+ private boolean mChangingStates;
+ private boolean mNeedMask;
+ private float mTransitionAmount;
+ private final WallpaperManager mWallpaperManager;
+ private final DisplayManager mDisplayManager;
+ private DisplayListener mDisplayListener = new DisplayListener() {
+ @Override
+ public void onDisplayAdded(int displayId) {
+ }
+
+ @Override
+ public void onDisplayRemoved(int displayId) {
+ }
+
+ @Override
+ public void onDisplayChanged(int displayId) {
+ // We just support DEFAULT_DISPLAY currently.
+ if (displayId == Display.DEFAULT_DISPLAY) {
+ mTransformer.updateDisplayInfo(getDisplayInfo(displayId));
+ }
+ }
+ };
+
+ public AodMaskView(Context context) {
+ this(context, null);
+ }
+
+ public AodMaskView(Context context, AttributeSet attrs) {
+ this(context, attrs, null);
+ }
+
+ @VisibleForTesting
+ public AodMaskView(Context context, AttributeSet attrs, ImageWallpaperTransformer transformer) {
+ super(context, attrs);
+ setClickable(false);
+
+ StatusBarStateController controller = Dependency.get(StatusBarStateController.class);
+ if (controller != null) {
+ controller.addCallback(this);
+ } else {
+ Log.d(TAG, "Can not get StatusBarStateController!");
+ }
+
+ mDisplayManager = (DisplayManager) getContext().getSystemService(Context.DISPLAY_SERVICE);
+ mDisplayManager.registerDisplayListener(mDisplayListener, null);
+ mWallpaperManager =
+ (WallpaperManager) getContext().getSystemService(Context.WALLPAPER_SERVICE);
+
+ if (transformer == null) {
+ mTransformer = new ImageWallpaperTransformer(this);
+ mTransformer.addFilter(new ScrimFilter());
+ mTransformer.addFilter(new VignetteFilter());
+ mTransformer.updateOffsets();
+ mTransformer.updateDisplayInfo(getDisplayInfo(Display.DEFAULT_DISPLAY));
+
+ mTransitionProperties.setDuration(TRANSITION_DURATION);
+ mTransitionProperties.setAnimationFinishListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mTransformer.setIsTransiting(false);
+ }
+
+ @Override
+ public void onAnimationStart(Animator animation) {
+ mTransformer.setIsTransiting(true);
+ }
+ });
+ } else {
+ // This part should only be hit by test cases.
+ mTransformer = transformer;
+ }
+ }
+
+ private DisplayInfo getDisplayInfo(int displayId) {
+ DisplayInfo displayInfo = new DisplayInfo();
+ mDisplayManager.getDisplay(displayId).getDisplayInfo(displayInfo);
+ return displayInfo;
+ }
+
+ @Override
+ protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+ super.onSizeChanged(w, h, oldw, oldh);
+ mBounds.set(0, 0, w, h);
+ mTransformer.updateOffsets();
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+ if (mNeedMask) {
+ mTransformer.drawTransformedImage(canvas, null /* target */, null /* src */, mBounds);
+ }
+ }
+
+ private boolean checkIfNeedMask() {
+ // We need mask for ImageWallpaper / LockScreen Wallpaper (Music album art).
+ return mWallpaperManager.getWallpaperInfo() == null || ScrimState.AOD.hasBackdrop();
+ }
+
+ @Override
+ public void onStatePreChange(int oldState, int newState) {
+ mChangingStates = oldState != newState;
+ mNeedMask = checkIfNeedMask();
+ }
+
+ @Override
+ public void onStatePostChange() {
+ mChangingStates = false;
+ }
+
+ @Override
+ public void onStateChanged(int newState) {
+ }
+
+ @Override
+ public void onDozingChanged(boolean isDozing) {
+ if (!mNeedMask) {
+ return;
+ }
+
+ boolean enabled = checkFeatureIsEnabled();
+ mTransformer.updateAmbientModeState(enabled && isDozing);
+
+ if (enabled && !mChangingStates) {
+ setAnimatorProperty(isDozing);
+ } else {
+ invalidate();
+ }
+ }
+
+ private boolean checkFeatureIsEnabled() {
+ return FeatureFlagUtils.isEnabled(
+ getContext(), FeatureFlagUtils.AOD_IMAGEWALLPAPER_ENABLED);
+ }
+
+ @VisibleForTesting
+ void setAnimatorProperty(boolean isDozing) {
+ PropertyAnimator.setProperty(
+ this,
+ TRANSITION_PROGRESS,
+ isDozing ? 1f : 0f /* newEndValue */,
+ mTransitionProperties,
+ true /* animated */);
+ }
+
+ @Override
+ public void onTransformationUpdated() {
+ invalidate();
+ }
+
+ private void setTransitionAmount(float amount) {
+ mTransitionAmount = amount;
+ mTransformer.updateTransitionAmount(amount);
+ }
+
+ private float getTransitionAmount() {
+ return mTransitionAmount;
+ }
+
+}
diff --git a/packages/SystemUI/src/com/android/systemui/wallpaper/ImageWallpaperFilter.java b/packages/SystemUI/src/com/android/systemui/wallpaper/ImageWallpaperFilter.java
new file mode 100644
index 000000000000..d457dac3e14f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/wallpaper/ImageWallpaperFilter.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2018 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.wallpaper;
+
+import android.animation.ValueAnimator;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.graphics.RectF;
+
+/**
+ * Abstract filter used by static image wallpaper.
+ */
+abstract class ImageWallpaperFilter {
+ protected static final boolean DEBUG = false;
+
+ private ImageWallpaperTransformer mTransformer;
+
+ /**
+ * Apply this filter to the bitmap before drawing on canvas.
+ * @param c The canvas that will draw to.
+ * @param bitmap The bitmap to apply this filter.
+ * @param src The subset of the bitmap to be drawn.
+ * @param dest The rectangle that the bitmap will be scaled/translated to fit into.
+ */
+ public abstract void apply(@NonNull Canvas c, @Nullable Bitmap bitmap,
+ @Nullable Rect src, @NonNull RectF dest);
+
+ /**
+ * Notifies the occurrence of built-in transition of the animation.
+ * @param animator The animator which was animated.
+ */
+ public abstract void onAnimatorUpdate(ValueAnimator animator);
+
+ /**
+ * Notifies the occurrence of another transition of the animation.
+ * @param amount The transition amount.
+ */
+ public abstract void onTransitionAmountUpdate(float amount);
+
+ /**
+ * To set the associated transformer.
+ * @param transformer The transformer that is associated with this filter.
+ */
+ public void setTransformer(ImageWallpaperTransformer transformer) {
+ if (transformer != null) {
+ mTransformer = transformer;
+ }
+ }
+
+ protected ImageWallpaperTransformer getTransformer() {
+ return mTransformer;
+ }
+
+ /**
+ * Notifies the changing of the offset value of the ImageWallpaper.
+ * @param force True to force re-evaluate offsets.
+ * @param xOffset X offset of the ImageWallpaper in percentage.
+ * @param yOffset Y offset of the ImageWallpaper in percentage.
+ */
+ public void onOffsetsUpdate(boolean force, float xOffset, float yOffset) {
+ // No-op
+ }
+
+}
diff --git a/packages/SystemUI/src/com/android/systemui/wallpaper/ImageWallpaperTransformer.java b/packages/SystemUI/src/com/android/systemui/wallpaper/ImageWallpaperTransformer.java
new file mode 100644
index 000000000000..25b0b0aaf60b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/wallpaper/ImageWallpaperTransformer.java
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2018 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.wallpaper;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.view.DisplayInfo;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This class is used to manage the filters that will be applied.
+ */
+public class ImageWallpaperTransformer {
+ private static final String TAG = ImageWallpaperTransformer.class.getSimpleName();
+
+ private DisplayInfo mDisplayInfo;
+ private final List<ImageWallpaperFilter> mFilters;
+ private final TransformationListener mListener;
+ private boolean mIsInAmbientMode;
+ private boolean mIsTransiting;
+
+ /**
+ * Constructor.
+ * @param listener A listener to inform you the transformation has updated.
+ */
+ public ImageWallpaperTransformer(TransformationListener listener) {
+ mFilters = new ArrayList<>();
+ mListener = listener;
+ }
+
+ /**
+ * Claim that we want to use the specified filter.
+ * @param filter The filter will be used.
+ */
+ public void addFilter(ImageWallpaperFilter filter) {
+ if (filter != null) {
+ filter.setTransformer(this);
+ mFilters.add(filter);
+ }
+ }
+
+ /**
+ * Check if any transition is running.
+ * @return True if the transition is running, false otherwise.
+ */
+ boolean isTransiting() {
+ return mIsTransiting;
+ }
+
+ /**
+ * Indicate if any transition is running. <br/>
+ * @param isTransiting True if the transition is running.
+ */
+ void setIsTransiting(boolean isTransiting) {
+ mIsTransiting = isTransiting;
+ }
+
+ /**
+ * Check if the device is in ambient mode.
+ * @return True if the device is in ambient mode, false otherwise.
+ */
+ public boolean isInAmbientMode() {
+ return mIsInAmbientMode;
+ }
+
+ /**
+ * Update current state of ambient mode.
+ * @param isInAmbientMode Current ambient mode state.
+ */
+ public void updateAmbientModeState(boolean isInAmbientMode) {
+ mIsInAmbientMode = isInAmbientMode;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ int idx = 0;
+ for (ImageWallpaperFilter filter : mFilters) {
+ sb.append(idx++).append(": ").append(filter.getClass().getSimpleName()).append("\n");
+ }
+ if (sb.length() == 0) {
+ sb.append("No filters applied");
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Set a new display info.
+ * @param displayInfo New display info.
+ */
+ public void updateDisplayInfo(DisplayInfo displayInfo) {
+ mDisplayInfo = displayInfo;
+ }
+
+ /**
+ * To get current display info.
+ * @return Current display info.
+ */
+ public DisplayInfo getDisplayInfo() {
+ return mDisplayInfo;
+ }
+
+ /**
+ * Update the offsets with default value.
+ */
+ public void updateOffsets() {
+ this.updateOffsets(true, 0f, .5f);
+ }
+
+ /**
+ * To notify the filters that the offset of the ImageWallpaper changes.
+ * @param force True to force re-evaluate offsets.
+ * @param offsetX X offset of the ImageWallpaper in percentage.
+ * @param offsetY Y offset of the ImageWallpaper in percentage.
+ */
+ public void updateOffsets(boolean force, float offsetX, float offsetY) {
+ mFilters.forEach(filter -> filter.onOffsetsUpdate(force, offsetX, offsetY));
+ }
+
+ /**
+ * Apply all specified filters to the bitmap then draw to the canvas.
+ * @param c The canvas that will draw to.
+ * @param target The bitmap to apply filters.
+ * @param src The subset of the bitmap to be drawn
+ * @param dest The rectangle that the bitmap will be scaled/translated to fit into.
+ */
+ void drawTransformedImage(@NonNull Canvas c, @Nullable Bitmap target,
+ @Nullable Rect src, @NonNull RectF dest) {
+ mFilters.forEach(filter -> filter.apply(c, target, src, dest));
+ }
+
+ /**
+ * Update the transition amount. <br/>
+ * Must invoke this to update transition amount if not running built-in transition.
+ * @param amount The transition amount.
+ */
+ void updateTransitionAmount(float amount) {
+ mFilters.forEach(filter -> filter.onTransitionAmountUpdate(amount));
+ if (mListener != null) {
+ mListener.onTransformationUpdated();
+ }
+ }
+
+ /**
+ * An interface that informs the transformation status.
+ */
+ public interface TransformationListener {
+ /**
+ * Notifies the update of the transformation.
+ */
+ void onTransformationUpdated();
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/wallpaper/ScrimFilter.java b/packages/SystemUI/src/com/android/systemui/wallpaper/ScrimFilter.java
new file mode 100644
index 000000000000..637e48e67de6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/wallpaper/ScrimFilter.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2018 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.wallpaper;
+
+import android.animation.ValueAnimator;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.RectF;
+
+/**
+ * A filter that implements 70% black scrim effect.
+ */
+public class ScrimFilter extends ImageWallpaperFilter {
+ private static final int MAX_ALPHA = (int) (255 * .7f);
+ private static final int MIN_ALPHA = 0;
+
+ private final Paint mPaint;
+
+ public ScrimFilter() {
+ mPaint = new Paint();
+ mPaint.setColor(Color.BLACK);
+ mPaint.setAlpha(MAX_ALPHA);
+ }
+
+ @Override
+ public void apply(Canvas c, Bitmap bitmap, Rect src, RectF dest) {
+ ImageWallpaperTransformer transformer = getTransformer();
+
+ // If it is not in the transition, we need to set the property according to aod state.
+ if (!transformer.isTransiting()) {
+ mPaint.setAlpha(transformer.isInAmbientMode() ? MAX_ALPHA : MIN_ALPHA);
+ }
+
+ c.drawRect(dest, mPaint);
+ }
+
+ @Override
+ public void onAnimatorUpdate(ValueAnimator animator) {
+ ImageWallpaperTransformer transformer = getTransformer();
+ float fraction = animator.getAnimatedFraction();
+ float factor = transformer.isInAmbientMode() ? fraction : 1f - fraction;
+ mPaint.setAlpha((int) (factor * MAX_ALPHA));
+ }
+
+ @Override
+ public void onTransitionAmountUpdate(float amount) {
+ mPaint.setAlpha((int) (amount * MAX_ALPHA));
+ }
+
+}
diff --git a/packages/SystemUI/src/com/android/systemui/wallpaper/VignetteFilter.java b/packages/SystemUI/src/com/android/systemui/wallpaper/VignetteFilter.java
new file mode 100644
index 000000000000..ad0b98b67e68
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/wallpaper/VignetteFilter.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2018 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.wallpaper;
+
+import android.animation.ValueAnimator;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.PointF;
+import android.graphics.RadialGradient;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.Shader;
+import android.util.Log;
+import android.view.DisplayInfo;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+/**
+ * A filter that implements vignette effect.
+ */
+public class VignetteFilter extends ImageWallpaperFilter {
+ private static final String TAG = VignetteFilter.class.getSimpleName();
+ private static final int MAX_ALPHA = 255;
+ private static final int MIN_ALPHA = 0;
+
+ private final Paint mPaint;
+ private final Matrix mMatrix;
+ private final Shader mShader;
+
+ private float mXOffset;
+ private float mYOffset;
+ private float mCenterX;
+ private float mCenterY;
+ private float mStretchX;
+ private float mStretchY;
+ private boolean mCalculateOffsetNeeded;
+
+ public VignetteFilter() {
+ mPaint = new Paint();
+ mMatrix = new Matrix();
+ mShader = new RadialGradient(0, 0, 1,
+ Color.TRANSPARENT, Color.BLACK, Shader.TileMode.CLAMP);
+ }
+
+ @Override
+ public void apply(Canvas c, Bitmap bitmap, Rect src, RectF dest) {
+ DisplayInfo info = getTransformer().getDisplayInfo();
+
+ if (mCalculateOffsetNeeded) {
+ int lw = info.logicalWidth;
+ int lh = info.logicalHeight;
+ mCenterX = lw / 2 + (dest.width() - lw) * mXOffset;
+ mCenterY = lh / 2 + (dest.height() - lh) * mYOffset;
+ mStretchX = info.logicalWidth / 2;
+ mStretchY = info.logicalHeight / 2;
+ mCalculateOffsetNeeded = false;
+ }
+
+ if (DEBUG) {
+ Log.d(TAG, "apply: lw=" + info.logicalWidth + ", lh=" + info.logicalHeight
+ + ", center=(" + mCenterX + "," + mCenterY + ")"
+ + ", stretch=(" + mStretchX + "," + mStretchY + ")");
+ }
+
+ mMatrix.reset();
+ mMatrix.postTranslate(mCenterX, mCenterY);
+ mMatrix.postScale(mStretchX, mStretchY, mCenterX, mCenterY);
+ mShader.setLocalMatrix(mMatrix);
+ mPaint.setShader(mShader);
+
+ ImageWallpaperTransformer transformer = getTransformer();
+
+ // If it is not in the transition, we need to set the property according to aod state.
+ if (!transformer.isTransiting()) {
+ mPaint.setAlpha(transformer.isInAmbientMode() ? MAX_ALPHA : MIN_ALPHA);
+ }
+
+ c.drawRect(dest, mPaint);
+ }
+
+ @Override
+ public void onAnimatorUpdate(ValueAnimator animator) {
+ ImageWallpaperTransformer transformer = getTransformer();
+ float fraction = animator.getAnimatedFraction();
+ float factor = transformer.isInAmbientMode() ? fraction : 1f - fraction;
+ mPaint.setAlpha((int) (factor * MAX_ALPHA));
+ }
+
+ @Override
+ public void onTransitionAmountUpdate(float amount) {
+ mPaint.setAlpha((int) (amount * MAX_ALPHA));
+ }
+
+ @Override
+ public void onOffsetsUpdate(boolean force, float xOffset, float yOffset) {
+ if (force || mXOffset != xOffset || mYOffset != yOffset) {
+ mXOffset = xOffset;
+ mYOffset = yOffset;
+ mCalculateOffsetNeeded = true;
+ }
+ }
+
+ @VisibleForTesting
+ public PointF getCenterPoint() {
+ return new PointF(mCenterX, mCenterY);
+ }
+}