Merge "Add key layout for Nintendo Switch controller" into qt-dev am: 6eca92dd43
am: bfd67e9f0c
Change-Id: I4e23f824adacbd808bd40f5e6bf13e68816dea5c
diff --git a/core/java/android/app/ITaskStackListener.aidl b/core/java/android/app/ITaskStackListener.aidl
index 3f6880f..8c43ae5 100644
--- a/core/java/android/app/ITaskStackListener.aidl
+++ b/core/java/android/app/ITaskStackListener.aidl
@@ -177,4 +177,11 @@
* @param displayId the id of the display on which contents are drawn.
*/
void onSingleTaskDisplayDrawn(int displayId);
+
+ /*
+ * Called when the last task is removed from a display which can only contain one task.
+ *
+ * @param displayId the id of the display from which the window is removed.
+ */
+ void onSingleTaskDisplayEmpty(int displayId);
}
diff --git a/core/java/android/app/TaskStackListener.java b/core/java/android/app/TaskStackListener.java
index 36daf32..fcf17bf 100644
--- a/core/java/android/app/TaskStackListener.java
+++ b/core/java/android/app/TaskStackListener.java
@@ -177,4 +177,8 @@
@Override
public void onSingleTaskDisplayDrawn(int displayId) throws RemoteException {
}
+
+ @Override
+ public void onSingleTaskDisplayEmpty(int displayId) throws RemoteException {
+ }
}
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 91a8ab5..bfc646b 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -113,6 +113,7 @@
"androidx.lifecycle_lifecycle-extensions",
"androidx.dynamicanimation_dynamicanimation",
"androidx-constraintlayout_constraintlayout",
+ "iconloader_base",
"SystemUI-tags",
"SystemUI-proto",
"metrics-helper-lib",
diff --git a/packages/SystemUI/res/layout/bubble_view.xml b/packages/SystemUI/res/layout/bubble_view.xml
index a8eb2914..e2dea45 100644
--- a/packages/SystemUI/res/layout/bubble_view.xml
+++ b/packages/SystemUI/res/layout/bubble_view.xml
@@ -24,7 +24,6 @@
android:id="@+id/bubble_image"
android:layout_width="@dimen/individual_bubble_size"
android:layout_height="@dimen/individual_bubble_size"
- android:padding="@dimen/bubble_view_padding"
android:clipToPadding="false"/>
</com.android.systemui.bubbles.BubbleView>
diff --git a/packages/SystemUI/res/layout/super_status_bar.xml b/packages/SystemUI/res/layout/super_status_bar.xml
index a914930..9716a00 100644
--- a/packages/SystemUI/res/layout/super_status_bar.xml
+++ b/packages/SystemUI/res/layout/super_status_bar.xml
@@ -44,7 +44,7 @@
</com.android.systemui.statusbar.BackDropView>
<com.android.systemui.statusbar.ScrimView
- android:id="@+id/scrim_behind"
+ android:id="@+id/scrim_for_bubble"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:importantForAccessibility="no"
@@ -56,6 +56,14 @@
android:layout_width="match_parent"
android:layout_height="wrap_content" />
+ <com.android.systemui.statusbar.ScrimView
+ android:id="@+id/scrim_behind"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:importantForAccessibility="no"
+ sysui:ignoreRightInset="true"
+ />
+
<include layout="@layout/status_bar_expanded"
android:layout_width="match_parent"
android:layout_height="match_parent"
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index afe6d9c..919844e 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1107,12 +1107,12 @@
<dimen name="bubble_flyout_pointer_size">6dp</dimen>
<!-- How much space to leave between the flyout (tip of the arrow) and the bubble stack. -->
<dimen name="bubble_flyout_space_from_bubble">8dp</dimen>
- <!-- Padding around a collapsed bubble -->
- <dimen name="bubble_view_padding">0dp</dimen>
- <!-- Padding between bubbles when displayed in expanded state -->
- <dimen name="bubble_padding">8dp</dimen>
+ <!-- Padding between status bar and bubbles when displayed in expanded state -->
+ <dimen name="bubble_padding_top">16dp</dimen>
<!-- Size of individual bubbles. -->
- <dimen name="individual_bubble_size">52dp</dimen>
+ <dimen name="individual_bubble_size">60dp</dimen>
+ <!-- Size of bubble icon bitmap. -->
+ <dimen name="bubble_icon_bitmap_size">52dp</dimen>
<!-- Size of the circle around the bubbles when they're in the dismiss target. -->
<dimen name="bubble_dismiss_encircle_size">56dp</dimen>
<!-- How much to inset the icon in the circle -->
@@ -1144,9 +1144,9 @@
<!-- Offset between bubbles in their stacked position. -->
<dimen name="bubble_stack_offset">5dp</dimen>
<!-- How far offscreen the bubble stack rests. -->
- <dimen name="bubble_stack_offscreen">5dp</dimen>
+ <dimen name="bubble_stack_offscreen">9dp</dimen>
<!-- How far down the screen the stack starts. -->
- <dimen name="bubble_stack_starting_offset_y">100dp</dimen>
+ <dimen name="bubble_stack_starting_offset_y">96dp</dimen>
<!-- Size of image buttons in the bubble header -->
<dimen name="bubble_header_icon_size">48dp</dimen>
<!-- Space between the pointer triangle and the bubble expanded view -->
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListener.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListener.java
index bd2b19c..3ec5642 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListener.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListener.java
@@ -72,6 +72,13 @@
*/
public void onSingleTaskDisplayDrawn(int displayId) { }
+ /**
+ * Called when the last task is removed from a display which can only contain one task.
+ *
+ * @param displayId the id of the display from which the window is removed.
+ */
+ public void onSingleTaskDisplayEmpty(int displayId) {}
+
public void onTaskProfileLocked(int taskId, int userId) { }
public void onTaskCreated(int taskId, ComponentName componentName) { }
public void onTaskRemoved(int taskId) { }
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListeners.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListeners.java
index c89f2ab..d514a75 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListeners.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListeners.java
@@ -208,6 +208,12 @@
0 /* unused */).sendToTarget();
}
+ @Override
+ public void onSingleTaskDisplayEmpty(int displayId) throws RemoteException {
+ mHandler.obtainMessage(H.ON_SINGLE_TASK_DISPLAY_EMPTY, displayId,
+ 0 /* unused */).sendToTarget();
+ }
+
private final class H extends Handler {
private static final int ON_TASK_STACK_CHANGED = 1;
private static final int ON_TASK_SNAPSHOT_CHANGED = 2;
@@ -228,6 +234,7 @@
private static final int ON_SIZE_COMPAT_MODE_ACTIVITY_CHANGED = 17;
private static final int ON_BACK_PRESSED_ON_TASK_ROOT = 18;
private static final int ON_SINGLE_TASK_DISPLAY_DRAWN = 19;
+ private static final int ON_SINGLE_TASK_DISPLAY_EMPTY = 20;
public H(Looper looper) {
@@ -370,6 +377,13 @@
}
break;
}
+ case ON_SINGLE_TASK_DISPLAY_EMPTY: {
+ for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
+ mTaskStackListeners.get(i).onSingleTaskDisplayEmpty(
+ msg.arg1);
+ }
+ break;
+ }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
index 34cc70c..c32d48b 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
@@ -47,6 +47,7 @@
import com.android.systemui.statusbar.NotificationMediaManager;
import com.android.systemui.statusbar.ScrimView;
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.collection.NotificationData;
import com.android.systemui.statusbar.phone.DozeParameters;
@@ -136,11 +137,12 @@
}
public ScrimController createScrimController(ScrimView scrimBehind, ScrimView scrimInFront,
+ ScrimView scrimForBubble,
LockscreenWallpaper lockscreenWallpaper,
TriConsumer<ScrimState, Float, GradientColors> scrimStateListener,
Consumer<Integer> scrimVisibleListener, DozeParameters dozeParameters,
AlarmManager alarmManager) {
- return new ScrimController(scrimBehind, scrimInFront, scrimStateListener,
+ return new ScrimController(scrimBehind, scrimInFront, scrimForBubble, scrimStateListener,
scrimVisibleListener, dozeParameters, alarmManager);
}
@@ -216,8 +218,8 @@
@Singleton
@Provides
public NotificationInterruptionStateProvider provideNotificationInterruptionStateProvider(
- Context context) {
- return new NotificationInterruptionStateProvider(context);
+ Context context, NotificationFilter filter, StatusBarStateController controller) {
+ return new NotificationInterruptionStateProvider(context, filter, controller);
}
@Singleton
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BadgeRenderer.java b/packages/SystemUI/src/com/android/systemui/bubbles/BadgeRenderer.java
index 74ad0fa..f0f351f 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BadgeRenderer.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BadgeRenderer.java
@@ -18,6 +18,9 @@
import static android.graphics.Paint.ANTI_ALIAS_FLAG;
import static android.graphics.Paint.FILTER_BITMAP_FLAG;
+import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_BUBBLES;
+import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
+
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
@@ -33,7 +36,7 @@
*/
public class BadgeRenderer {
- private static final String TAG = "BadgeRenderer";
+ private static final String TAG = TAG_WITH_CLASS_NAME ? "BadgeRenderer" : TAG_BUBBLES;
/** The badge sizes are defined as percentages of the app icon size. */
private static final float SIZE_PERCENTAGE = 0.38f;
@@ -52,9 +55,9 @@
/** Space between the center of the dot and the top or left of the bubble stack. */
static float getDotCenterOffset(Context context) {
- final int iconSizePx =
- context.getResources().getDimensionPixelSize(R.dimen.individual_bubble_size);
- return SIZE_PERCENTAGE * iconSizePx;
+ final int iconBitmapSize =
+ context.getResources().getDimensionPixelSize(R.dimen.bubble_icon_bitmap_size);
+ return SIZE_PERCENTAGE * iconBitmapSize;
}
static float getDotRadius(float dotCenterOffset) {
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BadgedImageView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BadgedImageView.java
index 783780f..d2fd13e 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BadgedImageView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BadgedImageView.java
@@ -32,7 +32,8 @@
public class BadgedImageView extends ImageView {
private BadgeRenderer mDotRenderer;
- private int mIconSize;
+ private int mIconBitmapSize;
+
private Rect mTempBounds = new Rect();
private Point mTempPoint = new Point();
@@ -56,7 +57,7 @@
public BadgedImageView(Context context, AttributeSet attrs, int defStyleAttr,
int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
- mIconSize = getResources().getDimensionPixelSize(R.dimen.individual_bubble_size);
+ mIconBitmapSize = getResources().getDimensionPixelSize(R.dimen.bubble_icon_bitmap_size);
mDotRenderer = new BadgeRenderer(getContext());
TypedArray ta = context.obtainStyledAttributes(
@@ -69,7 +70,7 @@
super.onDraw(canvas);
if (mShowUpdateDot) {
getDrawingRect(mTempBounds);
- mTempPoint.set((getWidth() - mIconSize) / 2, getPaddingTop());
+ mTempPoint.set((getWidth() - mIconBitmapSize) / 2, getPaddingTop());
mDotRenderer.draw(canvas, mUpdateDotColor, mTempBounds, mDotScale, mTempPoint,
mOnLeft);
}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java b/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java
index 5c6c397..0b800e4 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java
@@ -36,14 +36,9 @@
* Encapsulates the data and UI elements of a bubble.
*/
class Bubble {
-
- private static final boolean DEBUG = false;
- private static final String TAG = "Bubble";
-
private final String mKey;
private final String mGroupId;
private String mAppName;
- private final BubbleExpandedView.OnBubbleBlockedListener mListener;
private boolean mInflated;
public NotificationEntry entry;
@@ -61,16 +56,10 @@
/** Used in tests when no UI is required. */
@VisibleForTesting(visibility = PRIVATE)
Bubble(Context context, NotificationEntry e) {
- this (context, e, null);
- }
-
- Bubble(Context context, NotificationEntry e,
- BubbleExpandedView.OnBubbleBlockedListener listener) {
entry = e;
mKey = e.key;
mLastUpdated = e.notification.getPostTime();
mGroupId = groupId(e);
- mListener = listener;
mPm = context.getPackageManager();
ApplicationInfo info;
@@ -126,7 +115,6 @@
expandedView = (BubbleExpandedView) inflater.inflate(
R.layout.bubble_expanded_view, stackView, false /* attachToRoot */);
expandedView.setEntry(entry, stackView, mAppName);
- expandedView.setOnBlockedListener(mListener);
mInflated = true;
}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
index a23c99e..5d61179 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
@@ -31,6 +31,9 @@
import static android.view.View.VISIBLE;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+import static com.android.systemui.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_CONTROLLER;
+import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_BUBBLES;
+import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.systemui.statusbar.StatusBarState.SHADE;
import static com.android.systemui.statusbar.notification.NotificationEntryManager.UNDEFINED_DISMISS_REASON;
@@ -41,7 +44,6 @@
import android.app.ActivityManager;
import android.app.ActivityManager.RunningTaskInfo;
-import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
@@ -53,7 +55,6 @@
import android.os.ServiceManager;
import android.provider.Settings;
import android.service.notification.NotificationListenerService.RankingMap;
-import android.service.notification.StatusBarNotification;
import android.service.notification.ZenModeConfig;
import android.util.Log;
import android.util.Pair;
@@ -101,8 +102,7 @@
@Singleton
public class BubbleController implements ConfigurationController.ConfigurationListener {
- private static final String TAG = "BubbleController";
- private static final boolean DEBUG = false;
+ private static final String TAG = TAG_WITH_CLASS_NAME ? "BubbleController" : TAG_BUBBLES;
@Retention(SOURCE)
@IntDef({DISMISS_USER_GESTURE, DISMISS_AGED, DISMISS_TASK_FINISHED, DISMISS_BLOCKED,
@@ -120,21 +120,8 @@
public static final int MAX_BUBBLES = 5; // TODO: actually enforce this
- // Enables some subset of notifs to automatically become bubbles
- public static final boolean DEBUG_ENABLE_AUTO_BUBBLE = false;
-
/** Flag to enable or disable the entire feature */
private static final String ENABLE_BUBBLES = "experiment_enable_bubbles";
- /** Auto bubble flags set whether different notif types should be presented as a bubble */
- 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";
-
- /** Use an activityView for an auto-bubbled notifs if it has an appropriate content intent */
- private static final String ENABLE_BUBBLE_CONTENT_INTENT = "experiment_bubble_content_intent";
-
- private static final String BUBBLE_STIFFNESS = "experiment_bubble_stiffness";
- private static final String BUBBLE_BOUNCINESS = "experiment_bubble_bounciness";
private final Context mContext;
private final NotificationEntryManager mNotificationEntryManager;
@@ -271,10 +258,9 @@
if (mStackView == null) {
mStackView = new BubbleStackView(mContext, mBubbleData, mSurfaceSynchronizer);
ViewGroup sbv = mStatusBarWindowController.getStatusBarView();
- // TODO(b/130237686): When you expand the shade on top of expanded bubble, there is no
- // scrim between bubble and the shade
- int bubblePosition = sbv.indexOfChild(sbv.findViewById(R.id.scrim_behind)) + 1;
- sbv.addView(mStackView, bubblePosition,
+ int bubbleScrimIndex = sbv.indexOfChild(sbv.findViewById(R.id.scrim_for_bubble));
+ int stackIndex = bubbleScrimIndex + 1; // Show stack above bubble scrim.
+ sbv.addView(mStackView, stackIndex,
new FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT));
if (mExpandListener != null) {
mStackView.setExpandListener(mExpandListener);
@@ -585,7 +571,7 @@
mNotificationEntryManager.updateNotifications();
updateStack();
- if (DEBUG) {
+ if (DEBUG_BUBBLE_CONTROLLER) {
Log.d(TAG, "[BubbleData]");
Log.d(TAG, formatBubblesString(mBubbleData.getBubbles(),
mBubbleData.getSelectedBubble()));
@@ -696,46 +682,15 @@
return mStackView;
}
- /**
- * Whether the notification should automatically bubble or not. Gated by secure settings flags.
- */
- @VisibleForTesting
- protected boolean shouldAutoBubbleForFlags(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;
-
- boolean hasRemoteInput = false;
- if (n.getNotification().actions != null) {
- for (Notification.Action action : n.getNotification().actions) {
- if (action.getRemoteInputs() != null) {
- hasRemoteInput = true;
- break;
- }
- }
- }
- boolean isCall = Notification.CATEGORY_CALL.equals(n.getNotification().category)
- && n.isOngoing();
- boolean isMusic = n.getNotification().hasMediaSession();
- boolean isImportantOngoing = isMusic || isCall;
-
- Class<? extends Notification.Style> style = n.getNotification().getNotificationStyle();
- boolean isMessageType = Notification.CATEGORY_MESSAGE.equals(n.getNotification().category);
- boolean isMessageStyle = Notification.MessagingStyle.class.equals(style);
- return (((isMessageType && hasRemoteInput) || isMessageStyle) && autoBubbleMessages)
- || (isImportantOngoing && autoBubbleOngoing)
- || autoBubbleAll;
- }
-
private void updateShowInShadeForSuppressNotification(NotificationEntry entry) {
boolean suppressNotification = entry.getBubbleMetadata() != null
&& entry.getBubbleMetadata().isNotificationSuppressed()
&& isForegroundApp(mContext, entry.notification.getPackageName());
+ Bubble b = mBubbleData.getBubbleWithKey(entry.key);
+ if (b != null && mBubbleData.getSelectedBubble() == b && mBubbleData.isExpanded()) {
+ // If we're expanded & selected don't show in shade
+ suppressNotification = true;
+ }
entry.setShowInShadeWhenBubble(!suppressNotification);
}
@@ -807,26 +762,20 @@
expandedBubble.setContentVisibility(true);
}
}
- }
- private static boolean shouldAutoBubbleMessages(Context context) {
- return Settings.Secure.getInt(context.getContentResolver(),
- ENABLE_AUTO_BUBBLE_MESSAGES, 0) != 0;
- }
-
- private static boolean shouldAutoBubbleOngoing(Context context) {
- return Settings.Secure.getInt(context.getContentResolver(),
- ENABLE_AUTO_BUBBLE_ONGOING, 0) != 0;
- }
-
- private static boolean shouldAutoBubbleAll(Context context) {
- return Settings.Secure.getInt(context.getContentResolver(),
- ENABLE_AUTO_BUBBLE_ALL, 0) != 0;
- }
-
- static boolean shouldUseContentIntent(Context context) {
- return Settings.Secure.getInt(context.getContentResolver(),
- ENABLE_BUBBLE_CONTENT_INTENT, 0) != 0;
+ @Override
+ public void onSingleTaskDisplayEmpty(int displayId) {
+ final Bubble expandedBubble = getExpandedBubble(mContext);
+ if (expandedBubble == null) {
+ return;
+ }
+ if (expandedBubble.getDisplayId() == displayId) {
+ mBubbleData.setExpanded(false);
+ }
+ if (expandedBubble.expandedView != null) {
+ expandedBubble.expandedView.notifyDisplayEmpty();
+ }
+ }
}
private static boolean areBubblesEnabled(Context context) {
@@ -834,20 +783,6 @@
ENABLE_BUBBLES, 1) != 0;
}
- /** Default stiffness to use for bubble physics animations. */
- public static int getBubbleStiffness(Context context, int defaultStiffness) {
- return Settings.Secure.getInt(
- context.getContentResolver(), BUBBLE_STIFFNESS, defaultStiffness);
- }
-
- /** Default bounciness/damping ratio to use for bubble physics animations. */
- public static float getBubbleBounciness(Context context, float defaultBounciness) {
- return Settings.Secure.getInt(
- context.getContentResolver(),
- BUBBLE_BOUNCINESS,
- (int) (defaultBounciness * 100)) / 100f;
- }
-
/**
* Whether an intent is properly configured to display in an {@link android.app.ActivityView}.
*
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
index 5575b35..8290c5a 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
@@ -16,6 +16,9 @@
package com.android.systemui.bubbles;
import static com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE;
+import static com.android.systemui.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_DATA;
+import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_BUBBLES;
+import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
import static java.util.stream.Collectors.toList;
@@ -37,7 +40,6 @@
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
-import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@@ -51,8 +53,7 @@
@Singleton
public class BubbleData {
- private static final String TAG = "BubbleData";
- private static final boolean DEBUG = false;
+ private static final String TAG = TAG_WITH_CLASS_NAME ? "BubbleData" : TAG_BUBBLES;
private static final int MAX_BUBBLES = 5;
@@ -148,7 +149,7 @@
}
public void setExpanded(boolean expanded) {
- if (DEBUG) {
+ if (DEBUG_BUBBLE_DATA) {
Log.d(TAG, "setExpanded: " + expanded);
}
setExpandedInternal(expanded);
@@ -156,7 +157,7 @@
}
public void setSelectedBubble(Bubble bubble) {
- if (DEBUG) {
+ if (DEBUG_BUBBLE_DATA) {
Log.d(TAG, "setSelectedBubble: " + bubble);
}
setSelectedBubbleInternal(bubble);
@@ -164,13 +165,13 @@
}
public void notificationEntryUpdated(NotificationEntry entry) {
- if (DEBUG) {
+ if (DEBUG_BUBBLE_DATA) {
Log.d(TAG, "notificationEntryUpdated: " + entry);
}
Bubble bubble = getBubbleWithKey(entry.key);
if (bubble == null) {
// Create a new bubble
- bubble = new Bubble(mContext, entry, this::onBubbleBlocked);
+ bubble = new Bubble(mContext, entry);
doAdd(bubble);
trim();
} else {
@@ -190,7 +191,7 @@
}
public void notificationEntryRemoved(NotificationEntry entry, @DismissReason int reason) {
- if (DEBUG) {
+ if (DEBUG_BUBBLE_DATA) {
Log.d(TAG, "notificationEntryRemoved: entry=" + entry + " reason=" + reason);
}
doRemove(entry.key, reason);
@@ -223,7 +224,7 @@
}
private void doAdd(Bubble bubble) {
- if (DEBUG) {
+ if (DEBUG_BUBBLE_DATA) {
Log.d(TAG, "doAdd: " + bubble);
}
int minInsertPoint = 0;
@@ -256,7 +257,7 @@
}
private void doUpdate(Bubble bubble) {
- if (DEBUG) {
+ if (DEBUG_BUBBLE_DATA) {
Log.d(TAG, "doUpdate: " + bubble);
}
mStateChange.updatedBubble = bubble;
@@ -306,7 +307,7 @@
}
public void dismissAll(@DismissReason int reason) {
- if (DEBUG) {
+ if (DEBUG_BUBBLE_DATA) {
Log.d(TAG, "dismissAll: reason=" + reason);
}
if (mBubbles.isEmpty()) {
@@ -336,7 +337,7 @@
* @param bubble the new selected bubble
*/
private void setSelectedBubbleInternal(@Nullable Bubble bubble) {
- if (DEBUG) {
+ if (DEBUG_BUBBLE_DATA) {
Log.d(TAG, "setSelectedBubbleInternal: " + bubble);
}
if (Objects.equals(bubble, mSelectedBubble)) {
@@ -361,7 +362,7 @@
* @param shouldExpand the new requested state
*/
private void setExpandedInternal(boolean shouldExpand) {
- if (DEBUG) {
+ if (DEBUG_BUBBLE_DATA) {
Log.d(TAG, "setExpandedInternal: shouldExpand=" + shouldExpand);
}
if (mExpanded == shouldExpand) {
@@ -466,7 +467,7 @@
* @return true if the position of any bubbles has changed as a result
*/
private boolean packGroup(int position) {
- if (DEBUG) {
+ if (DEBUG_BUBBLE_DATA) {
Log.d(TAG, "packGroup: position=" + position);
}
Bubble groupStart = mBubbles.get(position);
@@ -495,7 +496,7 @@
* @return true if the position of any bubbles changed as a result
*/
private boolean repackAll() {
- if (DEBUG) {
+ if (DEBUG_BUBBLE_DATA) {
Log.d(TAG, "repackAll()");
}
if (mBubbles.isEmpty()) {
@@ -550,28 +551,6 @@
}
}
- private void onBubbleBlocked(NotificationEntry entry) {
- final String blockedGroupId = Bubble.groupId(entry);
- int selectedIndex = mBubbles.indexOf(mSelectedBubble);
- for (Iterator<Bubble> i = mBubbles.iterator(); i.hasNext(); ) {
- Bubble bubble = i.next();
- if (bubble.getGroupId().equals(blockedGroupId)) {
- mStateChange.bubbleRemoved(bubble, BubbleController.DISMISS_BLOCKED);
- i.remove();
- }
- }
- if (mBubbles.isEmpty()) {
- setExpandedInternal(false);
- setSelectedBubbleInternal(null);
- } else if (!mBubbles.contains(mSelectedBubble)) {
- // choose a new one
- int newIndex = Math.min(selectedIndex, mBubbles.size() - 1);
- Bubble newSelected = mBubbles.get(newIndex);
- setSelectedBubbleInternal(newSelected);
- }
- dispatchPendingChanges();
- }
-
private int indexForKey(String key) {
for (int i = 0; i < mBubbles.size(); i++) {
Bubble bubble = mBubbles.get(i);
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleDebugConfig.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleDebugConfig.java
new file mode 100644
index 0000000..5357f772
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleDebugConfig.java
@@ -0,0 +1,39 @@
+/*
+ * 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.bubbles;
+
+/**
+ * Common class for the various debug {@link android.util.Log} output configuration in the Bubbles
+ * package.
+ */
+public class BubbleDebugConfig {
+
+ // All output logs in the Bubbles package use the {@link #TAG_BUBBLES} string for tagging their
+ // log output. This makes it easy to identify the origin of the log message when sifting
+ // through a large amount of log output from multiple sources. However, it also makes trying
+ // to figure-out the origin of a log message while debugging the Bubbles a little painful. By
+ // setting this constant to true, log messages from the Bubbles package will be tagged with
+ // their class names instead fot the generic tag.
+ static final boolean TAG_WITH_CLASS_NAME = false;
+
+ // Default log tag for the Bubbles package.
+ static final String TAG_BUBBLES = "Bubbles";
+
+ static final boolean DEBUG_BUBBLE_CONTROLLER = false;
+ static final boolean DEBUG_BUBBLE_DATA = false;
+ static final boolean DEBUG_BUBBLE_STACK_VIEW = false;
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
index 923ca20..af68ddb 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
@@ -18,14 +18,16 @@
import static android.view.Display.INVALID_DISPLAY;
-import static com.android.systemui.bubbles.BubbleController.DEBUG_ENABLE_AUTO_BUBBLE;
+import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_BUBBLES;
+import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
import android.annotation.Nullable;
import android.app.ActivityOptions;
+import android.app.ActivityTaskManager;
import android.app.ActivityView;
-import android.app.INotificationManager;
import android.app.Notification;
import android.app.PendingIntent;
+import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
@@ -37,7 +39,7 @@
import android.graphics.Point;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.ShapeDrawable;
-import android.os.ServiceManager;
+import android.os.RemoteException;
import android.os.UserHandle;
import android.provider.Settings;
import android.service.notification.StatusBarNotification;
@@ -45,8 +47,8 @@
import android.util.Log;
import android.util.StatsLog;
import android.view.View;
-import android.view.ViewGroup;
import android.view.WindowInsets;
+import android.view.WindowManager;
import android.widget.LinearLayout;
import com.android.internal.policy.ScreenDecorationsUtils;
@@ -55,14 +57,23 @@
import com.android.systemui.recents.TriangleShape;
import com.android.systemui.statusbar.AlphaOptimizedButton;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
-import com.android.systemui.statusbar.notification.stack.ExpandableViewState;
/**
* Container for the expanded bubble view, handles rendering the caret and settings icon.
*/
public class BubbleExpandedView extends LinearLayout implements View.OnClickListener {
- private static final String TAG = "BubbleExpandedView";
+ private static final String TAG = TAG_WITH_CLASS_NAME ? "BubbleExpandedView" : TAG_BUBBLES;
+
+ private enum ActivityViewStatus {
+ // ActivityView is being initialized, cannot start an activity yet.
+ INITIALIZING,
+ // ActivityView is initialized, and ready to start an activity.
+ INITIALIZED,
+ // Activity runs in the ActivityView.
+ ACTIVITY_STARTED,
+ // ActivityView is released, so activity launching will no longer be permitted.
+ RELEASED,
+ }
// The triangle pointing to the expanded view
private View mPointerView;
@@ -71,18 +82,19 @@
private AlphaOptimizedButton mSettingsIcon;
// Views for expanded state
- private ExpandableNotificationRow mNotifRow;
private ActivityView mActivityView;
- private boolean mActivityViewReady = false;
+ private ActivityViewStatus mActivityViewStatus = ActivityViewStatus.INITIALIZING;
+ private int mTaskId = -1;
+
private PendingIntent mBubbleIntent;
private boolean mKeyboardVisible;
private boolean mNeedsNewHeight;
+ private Point mDisplaySize;
private int mMinHeight;
private int mSettingsIconHeight;
- private int mBubbleHeight;
private int mPointerWidth;
private int mPointerHeight;
private ShapeDrawable mPointerDrawable;
@@ -92,29 +104,36 @@
private String mAppName;
private Drawable mAppIcon;
- private INotificationManager mNotificationManagerService;
private BubbleController mBubbleController = Dependency.get(BubbleController.class);
private BubbleStackView mStackView;
- private BubbleExpandedView.OnBubbleBlockedListener mOnBubbleBlockedListener;
-
private ActivityView.StateCallback mStateCallback = new ActivityView.StateCallback() {
@Override
public void onActivityViewReady(ActivityView view) {
- if (!mActivityViewReady) {
- mActivityViewReady = true;
- // Custom options so there is no activity transition animation
- ActivityOptions options = ActivityOptions.makeCustomAnimation(getContext(),
- 0 /* enterResId */, 0 /* exitResId */);
- // Post to keep the lifecycle normal
- post(() -> mActivityView.startActivity(mBubbleIntent, options));
+ switch (mActivityViewStatus) {
+ case INITIALIZING:
+ case INITIALIZED:
+ // Custom options so there is no activity transition animation
+ ActivityOptions options = ActivityOptions.makeCustomAnimation(getContext(),
+ 0 /* enterResId */, 0 /* exitResId */);
+ // Post to keep the lifecycle normal
+ post(() -> mActivityView.startActivity(mBubbleIntent, options));
+ mActivityViewStatus = ActivityViewStatus.ACTIVITY_STARTED;
}
}
@Override
public void onActivityViewDestroyed(ActivityView view) {
- mActivityViewReady = false;
+ mActivityViewStatus = ActivityViewStatus.RELEASED;
+ }
+
+ @Override
+ public void onTaskCreated(int taskId, ComponentName componentName) {
+ // Since Bubble ActivityView applies singleTaskDisplay this is
+ // guaranteed to only be called once per ActivityView. The taskId is
+ // saved to use for removeTask, preventing appearance in recent tasks.
+ mTaskId = taskId;
}
/**
@@ -149,15 +168,12 @@
int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
mPm = context.getPackageManager();
+ mDisplaySize = new Point();
+ WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
+ wm.getDefaultDisplay().getSize(mDisplaySize);
mMinHeight = getResources().getDimensionPixelSize(
R.dimen.bubble_expanded_default_height);
mPointerMargin = getResources().getDimensionPixelSize(R.dimen.bubble_pointer_margin);
- try {
- mNotificationManagerService = INotificationManager.Stub.asInterface(
- ServiceManager.getServiceOrThrow(Context.NOTIFICATION_SERVICE));
- } catch (ServiceManager.ServiceNotFoundException e) {
- Log.w(TAG, e);
- }
}
@Override
@@ -173,7 +189,7 @@
mPointerDrawable = new ShapeDrawable(TriangleShape.create(
mPointerWidth, mPointerHeight, true /* pointUp */));
mPointerView.setBackground(mPointerDrawable);
- mPointerView.setVisibility(GONE);
+ mPointerView.setVisibility(INVISIBLE);
mSettingsIconHeight = getContext().getResources().getDimensionPixelSize(
R.dimen.bubble_expanded_header_height);
@@ -259,26 +275,17 @@
*/
void updateInsets(WindowInsets insets) {
if (usingActivityView()) {
- Point displaySize = new Point();
- mActivityView.getContext().getDisplay().getSize(displaySize);
int[] windowLocation = mActivityView.getLocationOnScreen();
final int windowBottom = windowLocation[1] + mActivityView.getHeight();
final int keyboardHeight = insets.getSystemWindowInsetBottom()
- insets.getStableInsetBottom();
final int insetsBottom = Math.max(0,
- windowBottom + keyboardHeight - displaySize.y);
+ windowBottom + keyboardHeight - mDisplaySize.y);
mActivityView.setForwardedInsets(Insets.of(0, 0, 0, insetsBottom));
}
}
/**
- * Sets the listener to notify when a bubble has been blocked.
- */
- public void setOnBlockedListener(OnBubbleBlockedListener listener) {
- mOnBubbleBlockedListener = listener;
- }
-
- /**
* Sets the notification entry used to populate this view.
*/
public void setEntry(NotificationEntry entry, BubbleStackView stackView, String appName) {
@@ -286,17 +293,14 @@
mEntry = entry;
mAppName = appName;
- ApplicationInfo info;
try {
- info = mPm.getApplicationInfo(
+ ApplicationInfo info = mPm.getApplicationInfo(
entry.notification.getPackageName(),
PackageManager.MATCH_UNINSTALLED_PACKAGES
| PackageManager.MATCH_DISABLED_COMPONENTS
| PackageManager.MATCH_DIRECT_BOOT_UNAWARE
| PackageManager.MATCH_DIRECT_BOOT_AWARE);
- if (info != null) {
- mAppIcon = mPm.getApplicationIcon(info);
- }
+ mAppIcon = mPm.getApplicationIcon(info);
} catch (PackageManager.NameNotFoundException e) {
// Do nothing.
}
@@ -315,17 +319,7 @@
if (usingActivityView()) {
mActivityView.setCallback(mStateCallback);
} else {
- // We're using notification template
- ViewGroup parent = (ViewGroup) mNotifRow.getParent();
- if (parent == this) {
- // Already added
- return;
- } else if (parent != null) {
- // Still in the shade... remove it
- parent.removeView(mNotifRow);
- }
- addView(mNotifRow, 1 /* index */);
- mPointerView.setAlpha(1f);
+ Log.e(TAG, "Cannot populate expanded view.");
}
}
@@ -347,17 +341,8 @@
private void updateExpandedView() {
mBubbleIntent = getBubbleIntent(mEntry);
if (mBubbleIntent != null) {
- if (mNotifRow != null) {
- // Clear out the row if we had it previously
- removeView(mNotifRow);
- mNotifRow = null;
- }
setContentVisibility(false);
mActivityView.setVisibility(VISIBLE);
- } else if (DEBUG_ENABLE_AUTO_BUBBLE) {
- // Hide activity view if we had it previously
- mActivityView.setVisibility(GONE);
- mNotifRow = mEntry.getRow();
}
updateView();
}
@@ -377,7 +362,7 @@
if (data == null) {
// This is a contentIntent based bubble, lets allow it to be the max height
// as it was forced into this mode and not prepared to be small
- desiredHeight = mStackView.getMaxExpandedHeight();
+ desiredHeight = getMaxExpandedHeight();
} else {
boolean useRes = data.getDesiredHeightResId() != 0;
float desiredPx;
@@ -391,9 +376,7 @@
}
desiredHeight = desiredPx > 0 ? desiredPx : mMinHeight;
}
- int max = mStackView.getMaxExpandedHeight() - mSettingsIconHeight - mPointerHeight
- - mPointerMargin;
- float height = Math.min(desiredHeight, max);
+ float height = Math.min(desiredHeight, getMaxExpandedHeight());
height = Math.max(height, mMinHeight);
LayoutParams lp = (LayoutParams) mActivityView.getLayoutParams();
mNeedsNewHeight = lp.height != height;
@@ -401,15 +384,17 @@
// If the keyboard is visible... don't adjust the height because that will cause
// a configuration change and the keyboard will be lost.
lp.height = (int) height;
- mBubbleHeight = (int) height;
mActivityView.setLayoutParams(lp);
mNeedsNewHeight = false;
}
- } else {
- mBubbleHeight = mNotifRow != null ? mNotifRow.getIntrinsicHeight() : mMinHeight;
}
}
+ private int getMaxExpandedHeight() {
+ int[] windowLocation = mActivityView.getLocationOnScreen();
+ return mDisplaySize.y - windowLocation[1] - mSettingsIconHeight;
+ }
+
@Override
public void onClick(View view) {
if (mEntry == null) {
@@ -446,9 +431,6 @@
&& mActivityView.getVisibility() == VISIBLE
&& mActivityView.isAttachedToWindow()) {
mActivityView.onLocationChanged();
- } else if (mNotifRow != null) {
- applyRowState(mNotifRow);
- mPointerView.setAlpha(1f);
}
updateHeight();
}
@@ -467,17 +449,35 @@
* Removes and releases an ActivityView if one was previously created for this bubble.
*/
public void cleanUpExpandedState() {
- removeView(mNotifRow);
-
if (mActivityView == null) {
return;
}
- if (mActivityViewReady) {
- mActivityView.release();
+ switch (mActivityViewStatus) {
+ case INITIALIZED:
+ case ACTIVITY_STARTED:
+ mActivityView.release();
+ }
+ if (mTaskId != -1) {
+ try {
+ ActivityTaskManager.getService().removeTask(mTaskId);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to remove taskId " + mTaskId);
+ }
+ mTaskId = -1;
}
removeView(mActivityView);
+
mActivityView = null;
- mActivityViewReady = false;
+ }
+
+ /**
+ * Called when the last task is removed from a {@link android.hardware.display.VirtualDisplay}
+ * which {@link ActivityView} uses.
+ */
+ void notifyDisplayEmpty() {
+ if (mActivityViewStatus == ActivityViewStatus.ACTIVITY_STARTED) {
+ mActivityViewStatus = ActivityViewStatus.INITIALIZED;
+ }
}
private boolean usingActivityView() {
@@ -494,38 +494,6 @@
return INVALID_DISPLAY;
}
- private void applyRowState(ExpandableNotificationRow view) {
- view.reset();
- view.setHeadsUp(false);
- view.resetTranslation();
- view.setOnKeyguard(false);
- view.setClipBottomAmount(0);
- view.setClipTopAmount(0);
- view.setContentTransformationAmount(0, false);
- view.setIconsVisible(true);
-
- // TODO - Need to reset this (and others) when view goes back in shade, leave for now
- // view.setTopRoundness(1, false);
- // view.setBottomRoundness(1, false);
-
- ExpandableViewState viewState = view.getViewState();
- viewState = viewState == null ? new ExpandableViewState() : viewState;
- viewState.height = view.getIntrinsicHeight();
- viewState.gone = false;
- viewState.hidden = false;
- viewState.dimmed = false;
- viewState.alpha = 1f;
- viewState.notGoneIndex = -1;
- viewState.xTranslation = 0;
- viewState.yTranslation = 0;
- viewState.zTranslation = 0;
- viewState.scaleX = 1;
- viewState.scaleY = 1;
- viewState.inShelf = true;
- viewState.headsUpIsVisible = false;
- viewState.applyToView(view);
- }
-
private Intent getSettingsIntent(String packageName, final int appUid) {
final Intent intent = new Intent(Settings.ACTION_APP_NOTIFICATION_BUBBLE_SETTINGS);
intent.putExtra(Settings.EXTRA_APP_PACKAGE, packageName);
@@ -547,16 +515,6 @@
}
/**
- * Listener that is notified when a bubble is blocked.
- */
- public interface OnBubbleBlockedListener {
- /**
- * Called when a bubble is blocked for the provided entry.
- */
- void onBubbleBlocked(NotificationEntry entry);
- }
-
- /**
* Logs bubble UI click event.
*
* @param entry the bubble notification entry that user is interacting with.
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleFlyoutView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleFlyoutView.java
index 71f68c1..1cf5281 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleFlyoutView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleFlyoutView.java
@@ -57,6 +57,7 @@
private final int mFlyoutSpaceFromBubble;
private final int mPointerSize;
private final int mBubbleSize;
+ private final int mBubbleIconBitmapSize;
private final int mFlyoutElevation;
private final int mBubbleElevation;
private final int mFloatingBackgroundColor;
@@ -143,7 +144,9 @@
mFlyoutPadding = res.getDimensionPixelSize(R.dimen.bubble_flyout_padding_x);
mFlyoutSpaceFromBubble = res.getDimensionPixelSize(R.dimen.bubble_flyout_space_from_bubble);
mPointerSize = res.getDimensionPixelSize(R.dimen.bubble_flyout_pointer_size);
+
mBubbleSize = res.getDimensionPixelSize(R.dimen.individual_bubble_size);
+ mBubbleIconBitmapSize = res.getDimensionPixelSize(R.dimen.bubble_icon_bitmap_size);
mBubbleElevation = res.getDimensionPixelSize(R.dimen.bubble_elevation);
mFlyoutElevation = res.getDimensionPixelSize(R.dimen.bubble_flyout_elevation);
mNewDotOffsetFromBubbleBounds = BadgeRenderer.getDotCenterOffset(context);
@@ -216,7 +219,8 @@
post(() -> {
// Multi line flyouts get top-aligned to the bubble.
if (mFlyoutText.getLineCount() > 1) {
- setTranslationY(stackPos.y);
+ float bubbleIconTopPadding = (mBubbleSize - mBubbleIconBitmapSize) / 2f;
+ setTranslationY(stackPos.y + bubbleIconTopPadding);
} else {
// Single line flyouts are vertically centered with respect to the bubble.
setTranslationY(
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleIconFactory.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleIconFactory.java
new file mode 100644
index 0000000..dc38d59
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleIconFactory.java
@@ -0,0 +1,31 @@
+/*
+ * 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 com.android.launcher3.icons.BaseIconFactory;
+
+/**
+ * Factory for creating normalized bubble icons.
+ * We are not using Launcher's IconFactory because bubbles only runs on the UI thread,
+ * so there is no need to manage a pool across multiple threads.
+ */
+public class BubbleIconFactory extends BaseIconFactory {
+ protected BubbleIconFactory(Context context, int iconBitmapSize) {
+ super(context, context.getResources().getConfiguration().densityDpi, iconBitmapSize);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
index f87bcef..ec220e5 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
@@ -19,6 +19,10 @@
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
+import static com.android.systemui.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_STACK_VIEW;
+import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_BUBBLES;
+import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
+
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
@@ -28,7 +32,6 @@
import android.content.res.Resources;
import android.graphics.ColorMatrix;
import android.graphics.ColorMatrixColorFilter;
-import android.graphics.Outline;
import android.graphics.Paint;
import android.graphics.Point;
import android.graphics.PointF;
@@ -45,7 +48,6 @@
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
-import android.view.ViewOutlineProvider;
import android.view.ViewTreeObserver;
import android.view.WindowInsets;
import android.view.WindowManager;
@@ -79,8 +81,7 @@
* Renders bubbles in a stack and handles animating expanded and collapsed states.
*/
public class BubbleStackView extends FrameLayout {
- private static final String TAG = "BubbleStackView";
- private static final boolean DEBUG = false;
+ private static final String TAG = TAG_WITH_CLASS_NAME ? "BubbleStackView" : TAG_BUBBLES;
/** How far the flyout needs to be dragged before it's dismissed regardless of velocity. */
static final float FLYOUT_DRAG_PERCENT_DISMISS = 0.25f;
@@ -172,7 +173,7 @@
private float mVerticalPosPercentBeforeRotation = -1;
private int mBubbleSize;
- private int mBubblePadding;
+ private int mBubblePaddingTop;
private int mExpandedViewPadding;
private int mExpandedAnimateXDistance;
private int mExpandedAnimateYDistance;
@@ -180,7 +181,7 @@
private int mStatusBarHeight;
private int mPipDismissHeight;
private int mImeOffset;
-
+ private BubbleIconFactory mBubbleIconFactory;
private Bubble mExpandedBubble;
private boolean mIsExpanded;
private boolean mImeVisible;
@@ -193,7 +194,6 @@
private BubbleTouchHandler mTouchHandler;
private BubbleController.BubbleExpandListener mExpandListener;
- private BubbleExpandedView.OnBubbleBlockedListener mBlockedListener;
private boolean mViewUpdatedRequested = false;
private boolean mIsExpansionAnimating = false;
@@ -225,7 +225,7 @@
@Override
public boolean onPreDraw() {
getViewTreeObserver().removeOnPreDrawListener(mViewUpdater);
- applyCurrentState();
+ updateExpandedView();
mViewUpdatedRequested = false;
return true;
}
@@ -282,7 +282,8 @@
}
};
- @NonNull private final SurfaceSynchronizer mSurfaceSynchronizer;
+ @NonNull
+ private final SurfaceSynchronizer mSurfaceSynchronizer;
private BubbleDismissView mDismissContainer;
private Runnable mAfterMagnet;
@@ -302,7 +303,7 @@
Resources res = getResources();
mBubbleSize = res.getDimensionPixelSize(R.dimen.individual_bubble_size);
- mBubblePadding = res.getDimensionPixelSize(R.dimen.bubble_padding);
+ mBubblePaddingTop = res.getDimensionPixelSize(R.dimen.bubble_padding_top);
mExpandedAnimateXDistance =
res.getDimensionPixelSize(R.dimen.bubble_expanded_animate_x_distance);
mExpandedAnimateYDistance =
@@ -335,6 +336,9 @@
mBubbleContainer.setClipChildren(false);
addView(mBubbleContainer, new FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT));
+ int iconBitmapSize = getResources().getDimensionPixelSize(R.dimen.bubble_icon_bitmap_size);
+ mBubbleIconFactory = new BubbleIconFactory(context, iconBitmapSize);
+
mExpandedViewContainer = new FrameLayout(context);
mExpandedViewContainer.setElevation(elevation);
mExpandedViewContainer.setPadding(mExpandedViewPadding, mExpandedViewPadding,
@@ -399,7 +403,7 @@
}
mImeVisible = keyboardHeight != 0;
- float newY = getYPositionForExpandedView();
+ float newY = getExpandedViewY();
if (newY < 0) {
// TODO: This means our expanded content is too big to fit on screen. Right now
// we'll let it translate off but we should be clipping it & pushing the header
@@ -453,7 +457,7 @@
* Handle theme changes.
*/
public void onThemeChanged() {
- for (Bubble b: mBubbleData.getBubbles()) {
+ for (Bubble b : mBubbleData.getBubbles()) {
b.iconView.updateViews();
b.expandedView.applyThemeAttrs();
}
@@ -616,6 +620,7 @@
/**
* Updates the visibility of the 'dot' indicating an update on the bubble.
+ *
* @param key the {@link NotificationEntry#key} associated with the bubble.
*/
public void updateDotVisibility(String key) {
@@ -684,10 +689,13 @@
// via BubbleData.Listener
void addBubble(Bubble bubble) {
- if (DEBUG) {
+ if (DEBUG_BUBBLE_STACK_VIEW) {
Log.d(TAG, "addBubble: " + bubble);
}
bubble.inflate(mInflater, this);
+ bubble.iconView.setBubbleIconFactory(mBubbleIconFactory);
+ bubble.iconView.updateViews();
+
mBubbleContainer.addView(bubble.iconView, 0,
new FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT));
ViewClippingUtil.setClippingDeactivated(bubble.iconView, true, mClippingParameters);
@@ -702,7 +710,7 @@
// via BubbleData.Listener
void removeBubble(Bubble bubble) {
- if (DEBUG) {
+ if (DEBUG_BUBBLE_STACK_VIEW) {
Log.d(TAG, "removeBubble: " + bubble);
}
// Remove it from the views
@@ -737,7 +745,7 @@
*/
// via BubbleData.Listener
public void setSelectedBubble(@Nullable Bubble bubbleToSelect) {
- if (DEBUG) {
+ if (DEBUG_BUBBLE_STACK_VIEW) {
Log.d(TAG, "setSelectedBubble: " + bubbleToSelect);
}
if (mExpandedBubble != null && mExpandedBubble.equals(bubbleToSelect)) {
@@ -774,28 +782,17 @@
*/
// via BubbleData.Listener
public void setExpanded(boolean shouldExpand) {
- if (DEBUG) {
+ if (DEBUG_BUBBLE_STACK_VIEW) {
Log.d(TAG, "setExpanded: " + shouldExpand);
}
- boolean wasExpanded = mIsExpanded;
- if (shouldExpand == wasExpanded) {
+ if (shouldExpand == mIsExpanded) {
return;
}
- if (wasExpanded) {
- // Collapse the stack
- mExpandedViewContainer.setAlpha(0.0f);
- // TODO: In order to prevent flicker, code below should be executed after the alpha
- // value set on the mExpandedViewContainer is reflected on the screen. However, we
- // cannot just postpone the execution like #setSelectedBubble(), since some of member
- // variables referred by the code are overridden before the execution.
- if (mExpandedBubble != null) {
- mExpandedBubble.setContentVisibility(false);
- }
- animateExpansion(false /* expand */);
+ if (mIsExpanded) {
+ animateCollapse();
logBubbleEvent(mExpandedBubble, StatsLog.BUBBLE_UICHANGED__ACTION__COLLAPSED);
} else {
- // Expand the stack
- animateExpansion(true /* expand */);
+ animateExpansion();
// TODO: move next line to BubbleData
logBubbleEvent(mExpandedBubble, StatsLog.BUBBLE_UICHANGED__ACTION__EXPANDED);
logBubbleEvent(mExpandedBubble, StatsLog.BUBBLE_UICHANGED__ACTION__STACK_EXPANDED);
@@ -805,11 +802,12 @@
/**
* Dismiss the stack of bubbles.
+ *
* @deprecated
*/
@Deprecated
void stackDismissed(int reason) {
- if (DEBUG) {
+ if (DEBUG_BUBBLE_STACK_VIEW) {
Log.d(TAG, "stackDismissed: reason=" + reason);
}
mBubbleData.dismissAll(reason);
@@ -859,7 +857,7 @@
@Deprecated
@MainThread
void collapseStack() {
- if (DEBUG) {
+ if (DEBUG_BUBBLE_STACK_VIEW) {
Log.d(TAG, "collapseStack()");
}
mBubbleData.setExpanded(false);
@@ -871,7 +869,7 @@
@Deprecated
@MainThread
void collapseStack(Runnable endRunnable) {
- if (DEBUG) {
+ if (DEBUG_BUBBLE_STACK_VIEW) {
Log.d(TAG, "collapseStack(endRunnable)");
}
collapseStack();
@@ -889,68 +887,76 @@
@Deprecated
@MainThread
void expandStack() {
- if (DEBUG) {
+ if (DEBUG_BUBBLE_STACK_VIEW) {
Log.d(TAG, "expandStack()");
}
mBubbleData.setExpanded(true);
}
- /**
- * Tell the stack to animate to collapsed or expanded state.
- */
- private void animateExpansion(boolean shouldExpand) {
- if (DEBUG) {
- Log.d(TAG, "animateExpansion: shouldExpand=" + shouldExpand);
- }
- if (mIsExpanded != shouldExpand) {
- hideFlyoutImmediate();
+ private void beforeExpandedViewAnimation() {
+ hideFlyoutImmediate();
+ updateExpandedBubble();
+ updateExpandedView();
+ mIsExpansionAnimating = true;
+ }
- mIsExpanded = shouldExpand;
- updateExpandedBubble();
- applyCurrentState();
+ private void afterExpandedViewAnimation() {
+ updateExpandedView();
+ mIsExpansionAnimating = false;
+ requestUpdate();
+ }
- mIsExpansionAnimating = true;
+ private void animateCollapse() {
+ mIsExpanded = false;
+ beforeExpandedViewAnimation();
- Runnable updateAfter = () -> {
- applyCurrentState();
- mIsExpansionAnimating = false;
- requestUpdate();
- };
+ mBubbleContainer.cancelAllAnimations();
+ mExpandedAnimationController.collapseBackToStack(
+ mStackAnimationController.getStackPositionAlongNearestHorizontalEdge()
+ /* collapseTo */,
+ () -> {
+ mBubbleContainer.setActiveController(mStackAnimationController);
+ afterExpandedViewAnimation();
+ });
- if (shouldExpand) {
- mBubbleContainer.setActiveController(mExpandedAnimationController);
- mExpandedAnimationController.expandFromStack(() -> {
- updatePointerPosition();
- updateAfter.run();
- } /* after */);
- } else {
- mBubbleContainer.cancelAllAnimations();
- mExpandedAnimationController.collapseBackToStack(
- mStackAnimationController.getStackPositionAlongNearestHorizontalEdge(),
- () -> {
- mBubbleContainer.setActiveController(mStackAnimationController);
- updateAfter.run();
- });
- }
+ mExpandedViewXAnim.animateToFinalPosition(getCollapsedX());
+ mExpandedViewYAnim.animateToFinalPosition(getCollapsedY());
+ mExpandedViewContainer.animate()
+ .setDuration(100)
+ .alpha(0f);
+ }
- final float xStart =
- mStackAnimationController.getStackPosition().x < getWidth() / 2
- ? -mExpandedAnimateXDistance
- : mExpandedAnimateXDistance;
+ private void animateExpansion() {
+ mIsExpanded = true;
+ beforeExpandedViewAnimation();
- final float yStart = Math.min(
- mStackAnimationController.getStackPosition().y,
- mExpandedAnimateYDistance);
- final float yDest = getYPositionForExpandedView();
+ mBubbleContainer.setActiveController(mExpandedAnimationController);
+ mExpandedAnimationController.expandFromStack(() -> {
+ updatePointerPosition();
+ afterExpandedViewAnimation();
+ } /* after */);
- if (shouldExpand) {
- mExpandedViewContainer.setTranslationX(xStart);
- mExpandedViewContainer.setTranslationY(yStart);
- }
- mExpandedViewXAnim.animateToFinalPosition(shouldExpand ? 0f : xStart);
- mExpandedViewYAnim.animateToFinalPosition(shouldExpand ? yDest : yStart);
- }
+ mExpandedViewContainer.setTranslationX(getCollapsedX());
+ mExpandedViewContainer.setTranslationY(getCollapsedY());
+ mExpandedViewContainer.setAlpha(0f);
+
+ mExpandedViewXAnim.animateToFinalPosition(0f);
+ mExpandedViewYAnim.animateToFinalPosition(getExpandedViewY());
+ mExpandedViewContainer.animate()
+ .setDuration(100)
+ .alpha(1f);
+ }
+
+ private float getCollapsedX() {
+ return mStackAnimationController.getStackPosition().x < getWidth() / 2
+ ? -mExpandedAnimateXDistance
+ : mExpandedAnimateXDistance;
+ }
+
+ private float getCollapsedY() {
+ return Math.min(mStackAnimationController.getStackPosition().y,
+ mExpandedAnimateYDistance);
}
private void notifyExpansionChanged(NotificationEntry entry, boolean expanded) {
@@ -977,7 +983,7 @@
/** Called when a drag operation on an individual bubble has started. */
public void onBubbleDragStart(View bubble) {
- if (DEBUG) {
+ if (DEBUG_BUBBLE_STACK_VIEW) {
Log.d(TAG, "onBubbleDragStart: bubble=" + bubble);
}
mExpandedAnimationController.prepareForBubbleDrag(bubble);
@@ -996,7 +1002,7 @@
/** Called when a drag operation on an individual bubble has finished. */
public void onBubbleDragFinish(
View bubble, float x, float y, float velX, float velY) {
- if (DEBUG) {
+ if (DEBUG_BUBBLE_STACK_VIEW) {
Log.d(TAG, "onBubbleDragFinish: bubble=" + bubble);
}
@@ -1009,7 +1015,7 @@
}
void onDragStart() {
- if (DEBUG) {
+ if (DEBUG_BUBBLE_STACK_VIEW) {
Log.d(TAG, "onDragStart()");
}
if (mIsExpanded || mIsExpansionAnimating) {
@@ -1033,7 +1039,7 @@
}
void onDragFinish(float x, float y, float velX, float velY) {
- if (DEBUG) {
+ if (DEBUG_BUBBLE_STACK_VIEW) {
Log.d(TAG, "onDragFinish");
}
@@ -1296,21 +1302,10 @@
}
/**
- * Calculates how large the expanded view of the bubble can be. This takes into account the
- * y position when the bubbles are expanded as well as the bounds of the dismiss target.
- */
- int getMaxExpandedHeight() {
- int expandedY = (int) mExpandedAnimationController.getExpandedY();
- // PIP dismiss view uses FLAG_LAYOUT_IN_SCREEN so we need to subtract the bottom inset
- int pipDismissHeight = mPipDismissHeight - getBottomInset();
- return mDisplaySize.y - expandedY - mBubbleSize - pipDismissHeight;
- }
-
- /**
* Calculates the y position of the expanded view when it is expanded.
*/
- float getYPositionForExpandedView() {
- return getStatusBarHeight() + mBubbleSize + mBubblePadding + mPointerHeight;
+ float getExpandedViewY() {
+ return getStatusBarHeight() + mBubbleSize + mBubblePaddingTop + mPointerHeight;
}
/**
@@ -1479,7 +1474,7 @@
}
private void updateExpandedBubble() {
- if (DEBUG) {
+ if (DEBUG_BUBBLE_STACK_VIEW) {
Log.d(TAG, "updateExpandedBubble()");
}
mExpandedViewContainer.removeAllViews();
@@ -1491,9 +1486,9 @@
}
}
- private void applyCurrentState() {
- if (DEBUG) {
- Log.d(TAG, "applyCurrentState: mIsExpanded=" + mIsExpanded);
+ private void updateExpandedView() {
+ if (DEBUG_BUBBLE_STACK_VIEW) {
+ Log.d(TAG, "updateExpandedView: mIsExpanded=" + mIsExpanded);
}
mExpandedViewContainer.setVisibility(mIsExpanded ? VISIBLE : GONE);
@@ -1501,7 +1496,7 @@
// First update the view so that it calculates a new height (ensuring the y position
// calculation is correct)
mExpandedBubble.expandedView.updateView();
- final float y = getYPositionForExpandedView();
+ final float y = getExpandedViewY();
if (!mExpandedViewYAnim.isRunning()) {
// We're not animating so set the value
mExpandedViewContainer.setTranslationY(y);
@@ -1519,24 +1514,12 @@
/** Sets the appropriate Z-order and dot position for each bubble in the stack. */
private void updateBubbleShadowsAndDotPosition(boolean animate) {
- int bubbsCount = mBubbleContainer.getChildCount();
- for (int i = 0; i < bubbsCount; i++) {
+ int bubbleCount = mBubbleContainer.getChildCount();
+ for (int i = 0; i < bubbleCount; i++) {
BubbleView bv = (BubbleView) mBubbleContainer.getChildAt(i);
bv.updateDotVisibility(true /* animate */);
bv.setZ((BubbleController.MAX_BUBBLES
* getResources().getDimensionPixelSize(R.dimen.bubble_elevation)) - i);
-
- // Draw the shadow around the circle inscribed within the bubble's bounds. This
- // (intentionally) does not draw a shadow behind the update dot, which should be drawing
- // its own shadow since it's on a different (higher) plane.
- bv.setOutlineProvider(new ViewOutlineProvider() {
- @Override
- public void getOutline(View view, Outline outline) {
- outline.setOval(0, 0, mBubbleSize, mBubbleSize);
- }
- });
- bv.setClipToOutline(false);
-
// If the dot is on the left, and so is the stack, we need to change the dot position.
if (bv.getDotPositionOnLeft() == mStackOnLeftOrWillBe) {
bv.setDotPosition(!mStackOnLeftOrWillBe, animate);
@@ -1545,7 +1528,7 @@
}
private void updatePointerPosition() {
- if (DEBUG) {
+ if (DEBUG_BUBBLE_STACK_VIEW) {
Log.d(TAG, "updatePointerPosition()");
}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java
index 8fe8bd3..21bb35d 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java
@@ -45,7 +45,6 @@
*/
private static final float INDIVIDUAL_BUBBLE_DISMISS_MIN_VELOCITY = 6000f;
- private static final String TAG = "BubbleTouchHandler";
/**
* When the stack is flung towards the bottom of the screen, it'll be dismissed if it's flung
* towards the center of the screen (where the dismiss target is). This value is the width of
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java
index 6f1ed28..697d381 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java
@@ -29,6 +29,7 @@
import android.widget.FrameLayout;
import com.android.internal.graphics.ColorUtils;
+import com.android.launcher3.icons.BitmapInfo;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -38,20 +39,21 @@
* A floating object on the screen that can post message updates.
*/
public class BubbleView extends FrameLayout {
- private static final String TAG = "BubbleView";
private static final int DARK_ICON_ALPHA = 180;
private static final double ICON_MIN_CONTRAST = 4.1;
- private static final int DEFAULT_BACKGROUND_COLOR = Color.LTGRAY;
+ private static final int DEFAULT_BACKGROUND_COLOR = Color.LTGRAY;
// Same value as Launcher3 badge code
private static final float WHITE_SCRIM_ALPHA = 0.54f;
private Context mContext;
private BadgedImageView mBadgedImageView;
private int mBadgeColor;
- private int mPadding;
private int mIconInset;
+ // mBubbleIconFactory cannot be static because it depends on Context.
+ private BubbleIconFactory mBubbleIconFactory;
+
private boolean mSuppressDot = false;
private NotificationEntry mEntry;
@@ -71,8 +73,6 @@
public BubbleView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
mContext = context;
- // 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);
}
@@ -97,7 +97,6 @@
*/
public void setNotif(NotificationEntry entry) {
mEntry = entry;
- updateViews();
}
/**
@@ -125,6 +124,13 @@
}
/**
+ * @param factory Factory for creating normalized bubble icons.
+ */
+ public void setBubbleIconFactory(BubbleIconFactory factory) {
+ mBubbleIconFactory = factory;
+ }
+
+ /**
* @return the {@link ExpandableNotificationRow} view to display notification content when the
* bubble is expanded.
*/
@@ -207,7 +213,7 @@
}
void updateViews() {
- if (mEntry == null) {
+ if (mEntry == null || mBubbleIconFactory == null) {
return;
}
Notification.BubbleMetadata metadata = mEntry.getBubbleMetadata();
@@ -223,10 +229,13 @@
}
Drawable iconDrawable = ic.loadDrawable(mContext);
if (needsTint) {
- mBadgedImageView.setImageDrawable(buildIconWithTint(iconDrawable, n.color));
- } else {
- mBadgedImageView.setImageDrawable(iconDrawable);
+ iconDrawable = buildIconWithTint(iconDrawable, n.color);
}
+ BitmapInfo bitmapInfo = mBubbleIconFactory.createBadgedIconBitmap(iconDrawable,
+ null /* user */,
+ true /* shrinkNonAdaptiveIcons */);
+ mBadgedImageView.setImageBitmap(bitmapInfo.icon);
+
int badgeColor = determineDominateColor(iconDrawable, n.color);
mBadgeColor = badgeColor;
mBadgedImageView.setDotColor(badgeColor);
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java b/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java
index 1fa0e12..b69b94c 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java
@@ -54,16 +54,16 @@
/** Horizontal offset between bubbles, which we need to know to re-stack them. */
private float mStackOffsetPx;
- /** Spacing between bubbles in the expanded state. */
- private float mBubblePaddingPx;
+ /** Space between status bar and bubbles in the expanded state. */
+ private float mBubblePaddingTop;
/** Size of each bubble. */
private float mBubbleSizePx;
/** Height of the status bar. */
private float mStatusBarHeight;
/** Size of display. */
private Point mDisplaySize;
- /** Size of dismiss target at bottom of screen. */
- private float mPipDismissHeight;
+ /** Max number of bubbles shown in row above expanded view.*/
+ private int mBubblesMaxRendered;
/** Whether the dragged-out bubble is in the dismiss target. */
private boolean mIndividualBubbleWithinDismissTarget = false;
@@ -86,10 +86,12 @@
private boolean mSpringingBubbleToTouch = false;
private int mExpandedViewPadding;
+ private float mLauncherGridDiff;
public ExpandedAnimationController(Point displaySize, int expandedViewPadding) {
mDisplaySize = displaySize;
mExpandedViewPadding = expandedViewPadding;
+ mLauncherGridDiff = 30f;
}
/**
@@ -265,6 +267,7 @@
public void onGestureFinished() {
mBubbleDraggedOutEnough = false;
mBubbleDraggingOut = null;
+ updateBubblePositions();
}
/**
@@ -276,26 +279,13 @@
0, (i, anim) -> anim.translationY(getExpandedY())).startAll(after);
}
- /**
- * Animates the bubbles, starting at the given index, to the left or right by the given number
- * of bubble widths. Passing zero for numBubbleWidths will animate the bubbles to their normal
- * positions.
- */
- private void animateStackByBubbleWidthsStartingFrom(int numBubbleWidths, int startIndex) {
- animationsForChildrenFromIndex(
- startIndex,
- (index, animation) ->
- animation.translationX(getXForChildAtIndex(index + numBubbleWidths)))
- .startAll();
- }
-
/** The Y value of the row of expanded bubbles. */
public float getExpandedY() {
if (mLayout == null || mLayout.getRootWindowInsets() == null) {
return 0;
}
final WindowInsets insets = mLayout.getRootWindowInsets();
- return mBubblePaddingPx + Math.max(
+ return mBubblePaddingTop + Math.max(
mStatusBarHeight,
insets.getDisplayCutout() != null
? insets.getDisplayCutout().getSafeInsetTop()
@@ -306,11 +296,11 @@
void onActiveControllerForLayout(PhysicsAnimationLayout layout) {
final Resources res = layout.getResources();
mStackOffsetPx = res.getDimensionPixelSize(R.dimen.bubble_stack_offset);
- mBubblePaddingPx = res.getDimensionPixelSize(R.dimen.bubble_padding);
+ mBubblePaddingTop = res.getDimensionPixelSize(R.dimen.bubble_padding_top);
mBubbleSizePx = res.getDimensionPixelSize(R.dimen.individual_bubble_size);
mStatusBarHeight =
res.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_height);
- mPipDismissHeight = res.getDimensionPixelSize(R.dimen.pip_dismiss_gradient_height);
+ mBubblesMaxRendered = res.getInteger(R.integer.bubbles_max_rendered);
// Ensure that all child views are at 1x scale, and visible, in case they were animating
// in.
@@ -355,7 +345,7 @@
} else if (mAnimatingCollapse) {
startOrUpdateCollapseAnimation();
} else {
- child.setTranslationX(getXForChildAtIndex(index));
+ child.setTranslationX(getBubbleLeft(index));
animationForChild(child)
.translationY(
getExpandedY() - mBubbleSizePx * ANIMATE_TRANSLATION_FACTOR, /* from */
@@ -411,35 +401,53 @@
}
}
- /** Returns the appropriate X translation value for a bubble at the given index. */
- private float getXForChildAtIndex(int index) {
- return mBubblePaddingPx + (mBubbleSizePx + mBubblePaddingPx) * index;
- }
-
/**
* @param index Bubble index in row.
* @return Bubble left x from left edge of screen.
*/
public float getBubbleLeft(int index) {
- float bubbleLeftFromRowLeft = index * (mBubbleSizePx + mBubblePaddingPx);
- return getRowLeft() + bubbleLeftFromRowLeft;
+ final float bubbleFromRowLeft = index * (mBubbleSizePx + getSpaceBetweenBubbles());
+ return getRowLeft() + bubbleFromRowLeft;
}
private float getRowLeft() {
if (mLayout == null) {
return 0;
}
+
int bubbleCount = mLayout.getChildCount();
- // Width calculations.
- double bubble = bubbleCount * mBubbleSizePx;
- float gap = (bubbleCount - 1) * mBubblePaddingPx;
- float row = gap + (float) bubble;
+ final float totalBubbleWidth = bubbleCount * mBubbleSizePx;
+ final float totalGapWidth = (bubbleCount - 1) * getSpaceBetweenBubbles();
+ final float rowWidth = totalGapWidth + totalBubbleWidth;
- float halfRow = row / 2f;
- float centerScreen = mDisplaySize.x / 2;
- float rowLeftFromScreenLeft = centerScreen - halfRow;
+ final float centerScreen = mDisplaySize.x / 2f;
+ final float halfRow = rowWidth / 2f;
+ final float rowLeft = centerScreen - halfRow;
- return rowLeftFromScreenLeft;
+ return rowLeft;
+ }
+
+ /**
+ * @return Space between bubbles in row above expanded view.
+ */
+ private float getSpaceBetweenBubbles() {
+ /**
+ * Ordered left to right:
+ * Screen edge
+ * [mExpandedViewPadding]
+ * Expanded view edge
+ * [launcherGridDiff] --- arbitrary value until launcher exports widths
+ * Launcher's app icon grid edge that we must match
+ */
+ final float rowMargins = (mExpandedViewPadding + mLauncherGridDiff) * 2;
+ final float maxRowWidth = mDisplaySize.x - rowMargins;
+
+ final float totalBubbleWidth = mBubblesMaxRendered * mBubbleSizePx;
+ final float totalGapWidth = maxRowWidth - totalBubbleWidth;
+
+ final int gapCount = mBubblesMaxRendered - 1;
+ final float gapWidth = totalGapWidth / gapCount;
+ return gapWidth;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java b/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java
index ab8752e4..0d9e3b6 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java
@@ -140,13 +140,13 @@
/** Horizontal offset of bubbles in the stack. */
private float mStackOffset;
/** Diameter of the bubbles themselves. */
- private int mIndividualBubbleSize;
+ private int mBubbleIconBitmapSize;
/**
* The amount of space to add between the bubbles and certain UI elements, such as the top of
* the screen or the IME. This does not apply to the left/right sides of the screen since the
* stack goes offscreen intentionally.
*/
- private int mBubblePadding;
+ private int mBubblePaddingTop;
/** How far offscreen the stack rests. */
private int mBubbleOffscreen;
/** How far down the screen the stack starts, when there is no pre-existing location. */
@@ -185,7 +185,7 @@
return false;
}
- float stackCenter = mStackPosition.x + mIndividualBubbleSize / 2;
+ float stackCenter = mStackPosition.x + mBubbleIconBitmapSize / 2;
float screenCenter = mLayout.getWidth() / 2;
return stackCenter < screenCenter;
}
@@ -218,7 +218,7 @@
* @return The X value that the stack will end up at after the fling/spring.
*/
public float flingStackThenSpringToEdge(float x, float velX, float velY) {
- final boolean stackOnLeftSide = x - mIndividualBubbleSize / 2 < mLayout.getWidth() / 2;
+ final boolean stackOnLeftSide = x - mBubbleIconBitmapSize / 2 < mLayout.getWidth() / 2;
final boolean stackShouldFlingLeft = stackOnLeftSide
? velX < ESCAPE_VELOCITY
@@ -427,7 +427,7 @@
: 0);
allowableRegion.right =
mLayout.getWidth()
- - mIndividualBubbleSize
+ - mBubbleIconBitmapSize
+ mBubbleOffscreen
- Math.max(
insets.getSystemWindowInsetRight(),
@@ -436,7 +436,7 @@
: 0);
allowableRegion.top =
- mBubblePadding
+ mBubblePaddingTop
+ Math.max(
mStatusBarHeight,
insets.getDisplayCutout() != null
@@ -444,9 +444,9 @@
: 0);
allowableRegion.bottom =
mLayout.getHeight()
- - mIndividualBubbleSize
- - mBubblePadding
- - (mImeHeight > Float.MIN_VALUE ? mImeHeight + mBubblePadding : 0f)
+ - mBubbleIconBitmapSize
+ - mBubblePaddingTop
+ - (mImeHeight > Float.MIN_VALUE ? mImeHeight + mBubblePaddingTop : 0f)
- Math.max(
insets.getSystemWindowInsetBottom(),
insets.getDisplayCutout() != null
@@ -517,7 +517,7 @@
mFirstBubbleSpringingToTouch = false;
animationForChildAtIndex(0)
- .translationX(mLayout.getWidth() / 2f - mIndividualBubbleSize / 2f)
+ .translationX(mLayout.getWidth() / 2f - mBubbleIconBitmapSize / 2f)
.translationY(destY, after)
.withPositionStartVelocities(velX, velY)
.withStiffness(SpringForce.STIFFNESS_MEDIUM)
@@ -657,8 +657,8 @@
void onActiveControllerForLayout(PhysicsAnimationLayout layout) {
Resources res = layout.getResources();
mStackOffset = res.getDimensionPixelSize(R.dimen.bubble_stack_offset);
- mIndividualBubbleSize = res.getDimensionPixelSize(R.dimen.individual_bubble_size);
- mBubblePadding = res.getDimensionPixelSize(R.dimen.bubble_padding);
+ mBubbleIconBitmapSize = res.getDimensionPixelSize(R.dimen.bubble_icon_bitmap_size);
+ mBubblePaddingTop = res.getDimensionPixelSize(R.dimen.bubble_padding_top);
mBubbleOffscreen = res.getDimensionPixelSize(R.dimen.bubble_stack_offscreen);
mStackStartingVerticalOffset =
res.getDimensionPixelSize(R.dimen.bubble_stack_starting_offset_y);
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 5bab0ef3..80b2f39 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationInterruptionStateProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationInterruptionStateProvider.java
@@ -23,7 +23,6 @@
import android.content.Context;
import android.database.ContentObserver;
import android.hardware.display.AmbientDisplayConfiguration;
-import android.os.Bundle;
import android.os.PowerManager;
import android.os.RemoteException;
import android.os.ServiceManager;
@@ -32,7 +31,6 @@
import android.service.dreams.DreamService;
import android.service.dreams.IDreamManager;
import android.service.notification.StatusBarNotification;
-import android.text.TextUtils;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
@@ -41,9 +39,10 @@
import com.android.systemui.statusbar.NotificationPresenter;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.phone.ShadeController;
import com.android.systemui.statusbar.policy.HeadsUpManager;
+import javax.inject.Inject;
+
/**
* Provides heads-up and pulsing state for notification entries.
*/
@@ -54,9 +53,8 @@
private static final boolean ENABLE_HEADS_UP = true;
private static final String SETTING_HEADS_UP_TICKER = "ticker_gets_heads_up";
- private final StatusBarStateController mStatusBarStateController =
- Dependency.get(StatusBarStateController.class);
- private final NotificationFilter mNotificationFilter = Dependency.get(NotificationFilter.class);
+ private final StatusBarStateController mStatusBarStateController;
+ private final NotificationFilter mNotificationFilter;
private final AmbientDisplayConfiguration mAmbientDisplayConfiguration;
private final Context mContext;
@@ -64,7 +62,6 @@
private final IDreamManager mDreamManager;
private NotificationPresenter mPresenter;
- private ShadeController mShadeController;
private HeadsUpManager mHeadsUpManager;
private HeadsUpSuppressor mHeadsUpSuppressor;
@@ -73,12 +70,16 @@
protected boolean mUseHeadsUp = false;
private boolean mDisableNotificationAlerts;
- public NotificationInterruptionStateProvider(Context context) {
+ @Inject
+ public NotificationInterruptionStateProvider(Context context, NotificationFilter filter,
+ StatusBarStateController stateController) {
this(context,
(PowerManager) context.getSystemService(Context.POWER_SERVICE),
IDreamManager.Stub.asInterface(
ServiceManager.checkService(DreamService.DREAM_SERVICE)),
- new AmbientDisplayConfiguration(context));
+ new AmbientDisplayConfiguration(context),
+ filter,
+ stateController);
}
@VisibleForTesting
@@ -86,11 +87,15 @@
Context context,
PowerManager powerManager,
IDreamManager dreamManager,
- AmbientDisplayConfiguration ambientDisplayConfiguration) {
+ AmbientDisplayConfiguration ambientDisplayConfiguration,
+ NotificationFilter notificationFilter,
+ StatusBarStateController statusBarStateController) {
mContext = context;
mPowerManager = powerManager;
mDreamManager = dreamManager;
mAmbientDisplayConfiguration = ambientDisplayConfiguration;
+ mNotificationFilter = notificationFilter;
+ mStatusBarStateController = statusBarStateController;
}
/** Sets up late-binding dependencies for this component. */
@@ -98,29 +103,39 @@
NotificationPresenter notificationPresenter,
HeadsUpManager headsUpManager,
HeadsUpSuppressor headsUpSuppressor) {
+ setUpWithPresenter(notificationPresenter, headsUpManager, headsUpSuppressor,
+ new ContentObserver(Dependency.get(Dependency.MAIN_HANDLER)) {
+ @Override
+ public void onChange(boolean selfChange) {
+ boolean wasUsing = mUseHeadsUp;
+ mUseHeadsUp = ENABLE_HEADS_UP && !mDisableNotificationAlerts
+ && Settings.Global.HEADS_UP_OFF != Settings.Global.getInt(
+ mContext.getContentResolver(),
+ Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED,
+ Settings.Global.HEADS_UP_OFF);
+ Log.d(TAG, "heads up is " + (mUseHeadsUp ? "enabled" : "disabled"));
+ if (wasUsing != mUseHeadsUp) {
+ if (!mUseHeadsUp) {
+ Log.d(TAG,
+ "dismissing any existing heads up notification on disable"
+ + " event");
+ mHeadsUpManager.releaseAllImmediately();
+ }
+ }
+ }
+ });
+ }
+
+ /** Sets up late-binding dependencies for this component. */
+ public void setUpWithPresenter(
+ NotificationPresenter notificationPresenter,
+ HeadsUpManager headsUpManager,
+ HeadsUpSuppressor headsUpSuppressor,
+ ContentObserver observer) {
mPresenter = notificationPresenter;
mHeadsUpManager = headsUpManager;
mHeadsUpSuppressor = headsUpSuppressor;
-
- mHeadsUpObserver = new ContentObserver(Dependency.get(Dependency.MAIN_HANDLER)) {
- @Override
- public void onChange(boolean selfChange) {
- boolean wasUsing = mUseHeadsUp;
- mUseHeadsUp = ENABLE_HEADS_UP && !mDisableNotificationAlerts
- && Settings.Global.HEADS_UP_OFF != Settings.Global.getInt(
- mContext.getContentResolver(),
- Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED,
- Settings.Global.HEADS_UP_OFF);
- Log.d(TAG, "heads up is " + (mUseHeadsUp ? "enabled" : "disabled"));
- if (wasUsing != mUseHeadsUp) {
- if (!mUseHeadsUp) {
- Log.d(TAG,
- "dismissing any existing heads up notification on disable event");
- mHeadsUpManager.releaseAllImmediately();
- }
- }
- }
- };
+ mHeadsUpObserver = observer;
if (ENABLE_HEADS_UP) {
mContext.getContentResolver().registerContentObserver(
@@ -134,13 +149,6 @@
mHeadsUpObserver.onChange(true); // set up
}
- private ShadeController getShadeController() {
- if (mShadeController == null) {
- mShadeController = Dependency.get(ShadeController.class);
- }
- return mShadeController;
- }
-
/**
* Whether the notification should appear as a bubble with a fly-out on top of the screen.
*
@@ -149,6 +157,15 @@
*/
public boolean shouldBubbleUp(NotificationEntry entry) {
final StatusBarNotification sbn = entry.notification;
+
+ if (!canAlertCommon(entry)) {
+ return false;
+ }
+
+ if (!canAlertAwakeCommon(entry)) {
+ return false;
+ }
+
if (!entry.canBubble) {
if (DEBUG) {
Log.d(TAG, "No bubble up: not allowed to bubble: " + sbn.getKey());
@@ -173,10 +190,6 @@
return false;
}
- if (!canHeadsUpCommon(entry)) {
- return false;
- }
-
return true;
}
@@ -197,6 +210,21 @@
private boolean shouldHeadsUpWhenAwake(NotificationEntry entry) {
StatusBarNotification sbn = entry.notification;
+ if (!mUseHeadsUp) {
+ if (DEBUG) {
+ Log.d(TAG, "No heads up: no huns");
+ }
+ return false;
+ }
+
+ if (!canAlertCommon(entry)) {
+ return false;
+ }
+
+ if (!canAlertAwakeCommon(entry)) {
+ return false;
+ }
+
boolean inShade = mStatusBarStateController.getState() == SHADE;
if (entry.isBubble() && inShade) {
if (DEBUG) {
@@ -206,17 +234,13 @@
return false;
}
- if (!canAlertCommon(entry)) {
+ if (entry.shouldSuppressPeek()) {
if (DEBUG) {
- Log.d(TAG, "No heads up: notification shouldn't alert: " + sbn.getKey());
+ Log.d(TAG, "No heads up: suppressed by DND: " + sbn.getKey());
}
return false;
}
- if (!canHeadsUpCommon(entry)) {
- return false;
- }
-
if (entry.importance < NotificationManager.IMPORTANCE_HIGH) {
if (DEBUG) {
Log.d(TAG, "No heads up: unimportant notification: " + sbn.getKey());
@@ -287,16 +311,13 @@
}
/**
- * Common checks between regular heads up and when pulsing. See
- * {@link #shouldHeadsUp(NotificationEntry)} and
- * {@link #shouldHeadsUpWhenDozing(NotificationEntry)}. Notifications that fail any of these
- * checks
- * should not alert at all.
+ * Common checks between regular & AOD heads up and bubbles.
*
* @param entry the entry to check
* @return true if these checks pass, false if the notification should not alert
*/
- protected boolean canAlertCommon(NotificationEntry entry) {
+ @VisibleForTesting
+ public boolean canAlertCommon(NotificationEntry entry) {
StatusBarNotification sbn = entry.notification;
if (mNotificationFilter.shouldFilterOut(entry)) {
@@ -313,46 +334,36 @@
}
return false;
}
-
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.
+ * Common checks between alerts that occur while the device is awake (heads up & bubbles).
*
* @param entry the entry to check
- * @return true if these checks pass, false if the notification should not interrupt on screen
+ * @return true if these checks pass, false if the notification should not alert
*/
- public boolean canHeadsUpCommon(NotificationEntry entry) {
+ @VisibleForTesting
+ public boolean canAlertAwakeCommon(NotificationEntry entry) {
StatusBarNotification sbn = entry.notification;
- if (!mUseHeadsUp || mPresenter.isDeviceInVrMode()) {
+ if (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());
+ Log.d(TAG, "No alerting: no huns or vr mode");
}
return false;
}
if (isSnoozedPackage(sbn)) {
if (DEBUG) {
- Log.d(TAG, "No heads up: snoozed package: " + sbn.getKey());
+ Log.d(TAG, "No alerting: snoozed package: " + sbn.getKey());
}
return false;
}
if (entry.hasJustLaunchedFullScreenIntent()) {
if (DEBUG) {
- Log.d(TAG, "No heads up: recent fullscreen: " + sbn.getKey());
+ Log.d(TAG, "No alerting: recent fullscreen: " + sbn.getKey());
}
return false;
}
@@ -370,6 +381,18 @@
mHeadsUpObserver.onChange(true);
}
+ /** Whether all alerts are disabled. */
+ @VisibleForTesting
+ public boolean areNotificationAlertsDisabled() {
+ return mDisableNotificationAlerts;
+ }
+
+ /** Whether HUNs should be used. */
+ @VisibleForTesting
+ public boolean getUseHeadsUp() {
+ return mUseHeadsUp;
+ }
+
protected NotificationPresenter getPresenter() {
return mPresenter;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
index abcdc7a..c56ba9b4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
@@ -161,7 +161,7 @@
* <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;
+ private boolean mShowInShadeWhenBubble = true;
/**
* Whether the user has dismissed this notification when it was in bubble form.
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 0ca8814..12eaaa3 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
@@ -2310,9 +2310,6 @@
@Override
public int getIntrinsicHeight() {
- if (isShownAsBubble()) {
- return getMaxExpandHeight();
- }
if (isUserLocked()) {
return getActualHeight();
}
@@ -2358,10 +2355,6 @@
return mStatusbarStateController != null && mStatusbarStateController.isDozing();
}
- private boolean isShownAsBubble() {
- return mEntry.isBubble() && !mEntry.showInShadeWhenBubble() && !mEntry.isBubbleDismissed();
- }
-
@Override
public boolean isGroupExpanded() {
return mGroupManager.isGroupExpanded(mStatusBarNotification);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java
index 07436f8..0d7cab0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java
@@ -513,7 +513,7 @@
*/
public void onScrimVisibilityChanged(@ScrimVisibility int scrimsVisible) {
if (mWakeAndUnlockRunning
- && scrimsVisible == ScrimController.VISIBILITY_FULLY_TRANSPARENT) {
+ && scrimsVisible == ScrimController.TRANSPARENT) {
mWakeAndUnlockRunning = false;
update();
}
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 5dcbea2..9971c90 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -78,23 +78,24 @@
/**
* When both scrims have 0 alpha.
*/
- public static final int VISIBILITY_FULLY_TRANSPARENT = 0;
+ public static final int TRANSPARENT = 0;
/**
* When scrims aren't transparent (alpha 0) but also not opaque (alpha 1.)
*/
- public static final int VISIBILITY_SEMI_TRANSPARENT = 1;
+ public static final int SEMI_TRANSPARENT = 1;
/**
* When at least 1 scrim is fully opaque (alpha set to 1.)
*/
- public static final int VISIBILITY_FULLY_OPAQUE = 2;
+ public static final int OPAQUE = 2;
- @IntDef(prefix = { "VISIBILITY_" }, value = {
- VISIBILITY_FULLY_TRANSPARENT,
- VISIBILITY_SEMI_TRANSPARENT,
- VISIBILITY_FULLY_OPAQUE
+ @IntDef(prefix = {"VISIBILITY_"}, value = {
+ TRANSPARENT,
+ SEMI_TRANSPARENT,
+ OPAQUE
})
@Retention(RetentionPolicy.SOURCE)
- public @interface ScrimVisibility {}
+ public @interface ScrimVisibility {
+ }
/**
* Default alpha value for most scrims.
@@ -122,8 +123,11 @@
private ScrimState mState = ScrimState.UNINITIALIZED;
private final Context mContext;
- protected final ScrimView mScrimBehind;
+
protected final ScrimView mScrimInFront;
+ protected final ScrimView mScrimBehind;
+ protected final ScrimView mScrimForBubble;
+
private final UnlockMethodCache mUnlockMethodCache;
private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
private final DozeParameters mDozeParameters;
@@ -152,10 +156,15 @@
private Runnable mOnAnimationFinished;
private boolean mDeferFinishedListener;
private final Interpolator mInterpolator = new DecelerateInterpolator();
- private float mCurrentInFrontAlpha = NOT_INITIALIZED;
- private float mCurrentBehindAlpha = NOT_INITIALIZED;
- private int mCurrentInFrontTint;
- private int mCurrentBehindTint;
+
+ private float mInFrontAlpha = NOT_INITIALIZED;
+ private float mBehindAlpha = NOT_INITIALIZED;
+ private float mBubbleAlpha = NOT_INITIALIZED;
+
+ private int mInFrontTint;
+ private int mBehindTint;
+ private int mBubbleTint;
+
private boolean mWallpaperVisibilityTimedOut;
private int mScrimsVisibility;
private final TriConsumer<ScrimState, Float, GradientColors> mScrimStateListener;
@@ -174,14 +183,18 @@
private boolean mWakeLockHeld;
private boolean mKeyguardOccluded;
- public ScrimController(ScrimView scrimBehind, ScrimView scrimInFront,
+ public ScrimController(ScrimView scrimBehind, ScrimView scrimInFront, ScrimView scrimForBubble,
TriConsumer<ScrimState, Float, GradientColors> scrimStateListener,
Consumer<Integer> scrimVisibleListener, DozeParameters dozeParameters,
AlarmManager alarmManager) {
+
mScrimBehind = scrimBehind;
mScrimInFront = scrimInFront;
+ mScrimForBubble = scrimForBubble;
+
mScrimStateListener = scrimStateListener;
mScrimVisibleListener = scrimVisibleListener;
+
mContext = scrimBehind.getContext();
mUnlockMethodCache = UnlockMethodCache.getInstance(mContext);
mDarkenWhileDragging = !mUnlockMethodCache.canSkipBouncer();
@@ -205,12 +218,13 @@
final ScrimState[] states = ScrimState.values();
for (int i = 0; i < states.length; i++) {
- states[i].init(mScrimInFront, mScrimBehind, mDozeParameters);
+ states[i].init(mScrimInFront, mScrimBehind, mScrimForBubble, mDozeParameters);
states[i].setScrimBehindAlphaKeyguard(mScrimBehindAlphaKeyguard);
}
mScrimBehind.setDefaultFocusHighlightEnabled(false);
mScrimInFront.setDefaultFocusHighlightEnabled(false);
+ mScrimForBubble.setDefaultFocusHighlightEnabled(false);
updateScrims();
}
@@ -249,10 +263,14 @@
mBlankScreen = state.getBlanksScreen();
mAnimateChange = state.getAnimateChange();
mAnimationDuration = state.getAnimationDuration();
- mCurrentInFrontTint = state.getFrontTint();
- mCurrentBehindTint = state.getBehindTint();
- mCurrentInFrontAlpha = state.getFrontAlpha();
- mCurrentBehindAlpha = state.getBehindAlpha();
+
+ mInFrontTint = state.getFrontTint();
+ mBehindTint = state.getBehindTint();
+ mBubbleTint = state.getBubbleTint();
+
+ mInFrontAlpha = state.getFrontAlpha();
+ mBehindAlpha = state.getBehindAlpha();
+ mBubbleAlpha = state.getBubbleAlpha();
applyExpansionToAlpha();
// Scrim might acquire focus when user is navigating with a D-pad or a keyboard.
@@ -371,31 +389,29 @@
if (mExpansionFraction != fraction) {
mExpansionFraction = fraction;
- final boolean keyguardOrUnlocked = mState == ScrimState.UNLOCKED
- || mState == ScrimState.KEYGUARD || mState == ScrimState.PULSING;
- if (!keyguardOrUnlocked || !mExpansionAffectsAlpha) {
+ boolean relevantState = (mState == ScrimState.UNLOCKED
+ || mState == ScrimState.KEYGUARD
+ || mState == ScrimState.PULSING
+ || mState == ScrimState.BUBBLE_EXPANDED);
+ if (!(relevantState && mExpansionAffectsAlpha)) {
return;
}
-
applyExpansionToAlpha();
-
if (mUpdatePending) {
return;
}
-
setOrAdaptCurrentAnimation(mScrimBehind);
setOrAdaptCurrentAnimation(mScrimInFront);
-
+ setOrAdaptCurrentAnimation(mScrimForBubble);
dispatchScrimState(mScrimBehind.getViewAlpha());
}
}
private void setOrAdaptCurrentAnimation(View scrim) {
- if (!isAnimating(scrim)) {
- updateScrimColor(scrim, getCurrentScrimAlpha(scrim), getCurrentScrimTint(scrim));
- } else {
+ float alpha = getCurrentScrimAlpha(scrim);
+ if (isAnimating(scrim)) {
+ // Adapt current animation.
ValueAnimator previousAnimator = (ValueAnimator) scrim.getTag(TAG_KEY_ANIM);
- float alpha = getCurrentScrimAlpha(scrim);
float previousEndValue = (Float) scrim.getTag(TAG_END_ALPHA);
float previousStartValue = (Float) scrim.getTag(TAG_START_ALPHA);
float relativeDiff = alpha - previousEndValue;
@@ -403,6 +419,9 @@
scrim.setTag(TAG_START_ALPHA, newStartValue);
scrim.setTag(TAG_END_ALPHA, alpha);
previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
+ } else {
+ // Set animation.
+ updateScrimColor(scrim, alpha, getCurrentScrimTint(scrim));
}
}
@@ -411,27 +430,27 @@
return;
}
- if (mState == ScrimState.UNLOCKED) {
+ if (mState == ScrimState.UNLOCKED || mState == ScrimState.BUBBLE_EXPANDED) {
// Darken scrim as you pull down the shade when unlocked
float behindFraction = getInterpolatedFraction();
behindFraction = (float) Math.pow(behindFraction, 0.8f);
- mCurrentBehindAlpha = behindFraction * GRADIENT_SCRIM_ALPHA_BUSY;
- mCurrentInFrontAlpha = 0;
+ mBehindAlpha = behindFraction * GRADIENT_SCRIM_ALPHA_BUSY;
+ mInFrontAlpha = 0;
} 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();
if (mDarkenWhileDragging) {
- mCurrentBehindAlpha = MathUtils.lerp(GRADIENT_SCRIM_ALPHA_BUSY, alphaBehind,
+ mBehindAlpha = MathUtils.lerp(GRADIENT_SCRIM_ALPHA_BUSY, alphaBehind,
interpolatedFract);
- mCurrentInFrontAlpha = 0;
+ mInFrontAlpha = 0;
} else {
- mCurrentBehindAlpha = MathUtils.lerp(0 /* start */, alphaBehind,
+ mBehindAlpha = MathUtils.lerp(0 /* start */, alphaBehind,
interpolatedFract);
- mCurrentInFrontAlpha = 0;
+ mInFrontAlpha = 0;
}
- mCurrentBehindTint = ColorUtils.blendARGB(ScrimState.BOUNCER.getBehindTint(),
+ mBehindTint = ColorUtils.blendARGB(ScrimState.BOUNCER.getBehindTint(),
mState.getBehindTint(), interpolatedFract);
}
}
@@ -456,8 +475,8 @@
*/
public void setAodFrontScrimAlpha(float alpha) {
if (mState == ScrimState.AOD && mDozeParameters.getAlwaysOn()
- && mCurrentInFrontAlpha != alpha) {
- mCurrentInFrontAlpha = alpha;
+ && mInFrontAlpha != alpha) {
+ mInFrontAlpha = alpha;
updateScrims();
}
@@ -474,8 +493,8 @@
if (mState == ScrimState.PULSING) {
float newBehindAlpha = mState.getBehindAlpha();
- if (mCurrentBehindAlpha != newBehindAlpha) {
- mCurrentBehindAlpha = newBehindAlpha;
+ if (mBehindAlpha != newBehindAlpha) {
+ mBehindAlpha = newBehindAlpha;
updateScrims();
}
}
@@ -497,8 +516,11 @@
// Only animate scrim color if the scrim view is actually visible
boolean animateScrimInFront = mScrimInFront.getViewAlpha() != 0 && !mBlankScreen;
boolean animateScrimBehind = mScrimBehind.getViewAlpha() != 0 && !mBlankScreen;
+ boolean animateScrimForBubble = mScrimForBubble.getViewAlpha() != 0 && !mBlankScreen;
+
mScrimInFront.setColors(mColors, animateScrimInFront);
mScrimBehind.setColors(mColors, animateScrimBehind);
+ mScrimForBubble.setColors(mColors, animateScrimForBubble);
// Calculate minimum scrim opacity for white or black text.
int textColor = mColors.supportsDarkText() ? Color.BLACK : Color.WHITE;
@@ -517,12 +539,11 @@
boolean occludedKeyguard = (mState == ScrimState.PULSING || mState == ScrimState.AOD)
&& mKeyguardOccluded;
if (aodWallpaperTimeout || occludedKeyguard) {
- mCurrentBehindAlpha = 1;
+ mBehindAlpha = 1;
}
-
- setScrimInFrontAlpha(mCurrentInFrontAlpha);
- setScrimBehindAlpha(mCurrentBehindAlpha);
-
+ setScrimAlpha(mScrimInFront, mInFrontAlpha);
+ setScrimAlpha(mScrimBehind, mBehindAlpha);
+ setScrimAlpha(mScrimForBubble, mBubbleAlpha);
dispatchScrimsVisible();
}
@@ -533,11 +554,11 @@
private void dispatchScrimsVisible() {
final int currentScrimVisibility;
if (mScrimInFront.getViewAlpha() == 1 || mScrimBehind.getViewAlpha() == 1) {
- currentScrimVisibility = VISIBILITY_FULLY_OPAQUE;
+ currentScrimVisibility = OPAQUE;
} else if (mScrimInFront.getViewAlpha() == 0 && mScrimBehind.getViewAlpha() == 0) {
- currentScrimVisibility = VISIBILITY_FULLY_TRANSPARENT;
+ currentScrimVisibility = TRANSPARENT;
} else {
- currentScrimVisibility = VISIBILITY_SEMI_TRANSPARENT;
+ currentScrimVisibility = SEMI_TRANSPARENT;
}
if (mScrimsVisibility != currentScrimVisibility) {
@@ -554,18 +575,10 @@
return 0;
} else {
// woo, special effects
- return (float)(1f-0.5f*(1f-Math.cos(3.14159f * Math.pow(1f-frac, 2f))));
+ return (float) (1f - 0.5f * (1f - Math.cos(3.14159f * Math.pow(1f - frac, 2f))));
}
}
- private void setScrimBehindAlpha(float alpha) {
- setScrimAlpha(mScrimBehind, alpha);
- }
-
- private void setScrimInFrontAlpha(float alpha) {
- setScrimAlpha(mScrimInFront, alpha);
- }
-
private void setScrimAlpha(ScrimView scrim, float alpha) {
if (alpha == 0f) {
scrim.setClickable(false);
@@ -576,17 +589,26 @@
updateScrim(scrim, alpha);
}
+ private String getScrimName(ScrimView scrim) {
+ if (scrim == mScrimInFront) {
+ return "front_scrim";
+ } else if (scrim == mScrimBehind) {
+ return "back_scrim";
+ } else if (scrim == mScrimForBubble) {
+ return "bubble_scrim";
+ }
+ return "unknown_scrim";
+ }
+
private void updateScrimColor(View scrim, float alpha, int tint) {
alpha = Math.max(0, Math.min(1.0f, alpha));
if (scrim instanceof ScrimView) {
ScrimView scrimView = (ScrimView) scrim;
- Trace.traceCounter(Trace.TRACE_TAG_APP,
- scrim == mScrimInFront ? "front_scrim_alpha" : "back_scrim_alpha",
+ Trace.traceCounter(Trace.TRACE_TAG_APP, getScrimName(scrimView) + "_alpha",
(int) (alpha * 255));
- Trace.traceCounter(Trace.TRACE_TAG_APP,
- scrim == mScrimInFront ? "front_scrim_tint" : "back_scrim_tint",
+ Trace.traceCounter(Trace.TRACE_TAG_APP, getScrimName(scrimView) + "_tint",
Color.alpha(tint));
scrimView.setTint(tint);
@@ -643,9 +665,11 @@
private float getCurrentScrimAlpha(View scrim) {
if (scrim == mScrimInFront) {
- return mCurrentInFrontAlpha;
+ return mInFrontAlpha;
} else if (scrim == mScrimBehind) {
- return mCurrentBehindAlpha;
+ return mBehindAlpha;
+ } else if (scrim == mScrimForBubble) {
+ return mBubbleAlpha;
} else {
throw new IllegalArgumentException("Unknown scrim view");
}
@@ -653,9 +677,11 @@
private int getCurrentScrimTint(View scrim) {
if (scrim == mScrimInFront) {
- return mCurrentInFrontTint;
+ return mInFrontTint;
} else if (scrim == mScrimBehind) {
- return mCurrentBehindTint;
+ return mBehindTint;
+ } else if (scrim == mScrimForBubble) {
+ return mBubbleTint;
} else {
throw new IllegalArgumentException("Unknown scrim view");
}
@@ -698,8 +724,9 @@
// 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) {
- mCurrentInFrontTint = Color.TRANSPARENT;
- mCurrentBehindTint = Color.TRANSPARENT;
+ mInFrontTint = Color.TRANSPARENT;
+ mBehindTint = Color.TRANSPARENT;
+ mBubbleTint = Color.TRANSPARENT;
}
}
@@ -804,6 +831,7 @@
/**
* Executes a callback after the frame has hit the display.
+ *
* @param callback What to run.
*/
@VisibleForTesting
@@ -847,16 +875,35 @@
@Override
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
pw.println(" ScrimController: ");
- pw.print(" state: "); pw.println(mState);
- pw.print(" frontScrim:"); pw.print(" viewAlpha="); pw.print(mScrimInFront.getViewAlpha());
- pw.print(" alpha="); pw.print(mCurrentInFrontAlpha);
- pw.print(" tint=0x"); pw.println(Integer.toHexString(mScrimInFront.getTint()));
+ pw.print(" state: ");
+ pw.println(mState);
- pw.print(" backScrim:"); pw.print(" viewAlpha="); pw.print(mScrimBehind.getViewAlpha());
- pw.print(" alpha="); pw.print(mCurrentBehindAlpha);
- pw.print(" tint=0x"); pw.println(Integer.toHexString(mScrimBehind.getTint()));
+ pw.print(" frontScrim:");
+ pw.print(" viewAlpha=");
+ pw.print(mScrimInFront.getViewAlpha());
+ pw.print(" alpha=");
+ pw.print(mInFrontAlpha);
+ pw.print(" tint=0x");
+ pw.println(Integer.toHexString(mScrimInFront.getTint()));
- pw.print(" mTracking="); pw.println(mTracking);
+ pw.print(" backScrim:");
+ pw.print(" viewAlpha=");
+ pw.print(mScrimBehind.getViewAlpha());
+ pw.print(" alpha=");
+ pw.print(mBehindAlpha);
+ pw.print(" tint=0x");
+ pw.println(Integer.toHexString(mScrimBehind.getTint()));
+
+ pw.print(" bubbleScrim:");
+ pw.print(" viewAlpha=");
+ pw.print(mScrimForBubble.getViewAlpha());
+ pw.print(" alpha=");
+ pw.print(mBubbleAlpha);
+ pw.print(" tint=0x");
+ pw.println(Integer.toHexString(mScrimForBubble.getTint()));
+
+ pw.print(" mTracking=");
+ pw.println(mTracking);
}
public void setWallpaperSupportsAmbientMode(boolean wallpaperSupportsAmbientMode) {
@@ -903,8 +950,8 @@
// in this case, back-scrim needs to be re-evaluated
if (mState == ScrimState.AOD || mState == ScrimState.PULSING) {
float newBehindAlpha = mState.getBehindAlpha();
- if (mCurrentBehindAlpha != newBehindAlpha) {
- mCurrentBehindAlpha = newBehindAlpha;
+ if (mBehindAlpha != newBehindAlpha) {
+ mBehindAlpha = newBehindAlpha;
updateScrims();
}
}
@@ -919,10 +966,13 @@
public interface Callback {
default void onStart() {
}
+
default void onDisplayBlanked() {
}
+
default void onFinished() {
}
+
default void onCancelled() {
}
}
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 d152ecd..4d374dc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
@@ -36,7 +36,6 @@
* On the lock screen.
*/
KEYGUARD(0) {
-
@Override
public void prepare(ScrimState previousState) {
mBlankScreen = false;
@@ -53,10 +52,13 @@
} else {
mAnimationDuration = ScrimController.ANIMATION_DURATION;
}
- mCurrentInFrontTint = Color.BLACK;
- mCurrentBehindTint = Color.BLACK;
- mCurrentBehindAlpha = mScrimBehindAlphaKeyguard;
- mCurrentInFrontAlpha = 0;
+ mFrontTint = Color.BLACK;
+ mBehindTint = Color.BLACK;
+ mBubbleTint = Color.TRANSPARENT;
+
+ mFrontAlpha = 0;
+ mBehindAlpha = mScrimBehindAlphaKeyguard;
+ mBubbleAlpha = 0;
}
},
@@ -66,8 +68,9 @@
BOUNCER(1) {
@Override
public void prepare(ScrimState previousState) {
- mCurrentBehindAlpha = ScrimController.GRADIENT_SCRIM_ALPHA_BUSY;
- mCurrentInFrontAlpha = 0f;
+ mBehindAlpha = ScrimController.GRADIENT_SCRIM_ALPHA_BUSY;
+ mFrontAlpha = 0f;
+ mBubbleAlpha = 0f;
}
},
@@ -77,8 +80,9 @@
BOUNCER_SCRIMMED(2) {
@Override
public void prepare(ScrimState previousState) {
- mCurrentBehindAlpha = 0;
- mCurrentInFrontAlpha = ScrimController.GRADIENT_SCRIM_ALPHA_BUSY;
+ mBehindAlpha = 0;
+ mBubbleAlpha = 0f;
+ mFrontAlpha = ScrimController.GRADIENT_SCRIM_ALPHA_BUSY;
}
},
@@ -88,8 +92,9 @@
BRIGHTNESS_MIRROR(3) {
@Override
public void prepare(ScrimState previousState) {
- mCurrentBehindAlpha = 0;
- mCurrentInFrontAlpha = 0;
+ mBehindAlpha = 0;
+ mFrontAlpha = 0;
+ mBubbleAlpha = 0;
}
},
@@ -101,9 +106,16 @@
public void prepare(ScrimState previousState) {
final boolean alwaysOnEnabled = mDozeParameters.getAlwaysOn();
mBlankScreen = mDisplayRequiresBlanking;
- mCurrentInFrontAlpha = alwaysOnEnabled ? mAodFrontScrimAlpha : 1f;
- mCurrentInFrontTint = Color.BLACK;
- mCurrentBehindTint = Color.BLACK;
+
+ mFrontTint = Color.BLACK;
+ mFrontAlpha = alwaysOnEnabled ? mAodFrontScrimAlpha : 1f;
+
+ mBehindTint = Color.BLACK;
+ mBehindAlpha = ScrimController.TRANSPARENT;
+
+ mBubbleTint = Color.TRANSPARENT;
+ mBubbleAlpha = ScrimController.TRANSPARENT;
+
mAnimationDuration = ScrimController.ANIMATION_DURATION_LONG;
// DisplayPowerManager may blank the screen for us,
// in this case we just need to set our state.
@@ -127,8 +139,9 @@
PULSING(5) {
@Override
public void prepare(ScrimState previousState) {
- mCurrentInFrontAlpha = 0f;
- mCurrentBehindTint = Color.BLACK;
+ mFrontAlpha = 0f;
+ mBubbleAlpha = 0f;
+ mBehindTint = Color.BLACK;
mBlankScreen = mDisplayRequiresBlanking;
}
@@ -145,23 +158,30 @@
UNLOCKED(6) {
@Override
public void prepare(ScrimState previousState) {
- mCurrentBehindAlpha = 0;
- mCurrentInFrontAlpha = 0;
+ // State that UI will sync to.
+ mBehindAlpha = 0;
+ mFrontAlpha = 0;
+ mBubbleAlpha = 0;
+
mAnimationDuration = StatusBar.FADE_KEYGUARD_DURATION;
mAnimateChange = !mLaunchingAffordanceWithPreview;
+ mFrontTint = Color.TRANSPARENT;
+ mBehindTint = Color.TRANSPARENT;
+ mBubbleTint = Color.TRANSPARENT;
+ mBlankScreen = false;
+
if (previousState == ScrimState.AOD) {
- // Fade from black to transparent when coming directly from AOD
- updateScrimColor(mScrimInFront, 1, Color.BLACK);
- updateScrimColor(mScrimBehind, 1, Color.BLACK);
+ // Set all scrims black, before they fade transparent.
+ updateScrimColor(mScrimInFront, 1f /* alpha */, Color.BLACK /* tint */);
+ updateScrimColor(mScrimBehind, 1f /* alpha */, Color.BLACK /* tint */);
+ updateScrimColor(mScrimForBubble, 1f /* alpha */, Color.BLACK /* tint */);
+
// Scrims should still be black at the end of the transition.
- mCurrentInFrontTint = Color.BLACK;
- mCurrentBehindTint = Color.BLACK;
+ mFrontTint = Color.BLACK;
+ mBehindTint = Color.BLACK;
+ mBubbleTint = Color.BLACK;
mBlankScreen = true;
- } else {
- mCurrentInFrontTint = Color.TRANSPARENT;
- mCurrentBehindTint = Color.TRANSPARENT;
- mBlankScreen = false;
}
}
},
@@ -172,25 +192,36 @@
BUBBLE_EXPANDED(7) {
@Override
public void prepare(ScrimState previousState) {
- mCurrentInFrontTint = Color.TRANSPARENT;
- mCurrentBehindTint = Color.TRANSPARENT;
+ mFrontTint = Color.TRANSPARENT;
+ mBehindTint = Color.TRANSPARENT;
+ mBubbleTint = Color.TRANSPARENT;
+
+ mFrontAlpha = ScrimController.TRANSPARENT;
+ mBehindAlpha = ScrimController.GRADIENT_SCRIM_ALPHA_BUSY;
+ mBubbleAlpha = ScrimController.GRADIENT_SCRIM_ALPHA;
+
mAnimationDuration = ScrimController.ANIMATION_DURATION;
- mCurrentBehindAlpha = ScrimController.GRADIENT_SCRIM_ALPHA_BUSY;
mBlankScreen = false;
}
};
boolean mBlankScreen = false;
long mAnimationDuration = ScrimController.ANIMATION_DURATION;
- int mCurrentInFrontTint = Color.TRANSPARENT;
- int mCurrentBehindTint = Color.TRANSPARENT;
+ int mFrontTint = Color.TRANSPARENT;
+ int mBehindTint = Color.TRANSPARENT;
+ int mBubbleTint = Color.TRANSPARENT;
+
boolean mAnimateChange = true;
- float mCurrentInFrontAlpha;
- float mCurrentBehindAlpha;
float mAodFrontScrimAlpha;
+ float mFrontAlpha;
+ float mBehindAlpha;
+ float mBubbleAlpha;
+
float mScrimBehindAlphaKeyguard;
ScrimView mScrimInFront;
ScrimView mScrimBehind;
+ ScrimView mScrimForBubble;
+
DozeParameters mDozeParameters;
boolean mDisplayRequiresBlanking;
boolean mWallpaperSupportsAmbientMode;
@@ -203,13 +234,17 @@
mIndex = index;
}
- public void init(ScrimView scrimInFront, ScrimView scrimBehind, DozeParameters dozeParameters) {
+ public void init(ScrimView scrimInFront, ScrimView scrimBehind, ScrimView scrimForBubble,
+ DozeParameters dozeParameters) {
mScrimInFront = scrimInFront;
mScrimBehind = scrimBehind;
+ mScrimForBubble = scrimForBubble;
+
mDozeParameters = dozeParameters;
mDisplayRequiresBlanking = dozeParameters.getDisplayNeedsBlanking();
}
+ /** Prepare state for transition. */
public void prepare(ScrimState previousState) {
}
@@ -218,19 +253,27 @@
}
public float getFrontAlpha() {
- return mCurrentInFrontAlpha;
+ return mFrontAlpha;
}
public float getBehindAlpha() {
- return mCurrentBehindAlpha;
+ return mBehindAlpha;
+ }
+
+ public float getBubbleAlpha() {
+ return mBubbleAlpha;
}
public int getFrontTint() {
- return mCurrentInFrontTint;
+ return mFrontTint;
}
public int getBehindTint() {
- return mCurrentBehindTint;
+ return mBehindTint;
+ }
+
+ public int getBubbleTint() {
+ return mBubbleTint;
}
public long getAnimationDuration() {
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 e19fe07..a27a916 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -791,6 +791,7 @@
// TODO: Deal with the ugliness that comes from having some of the statusbar broken out
// into fragments, but the rest here, it leaves some awkward lifecycle and whatnot.
mNotificationPanel = mStatusBarWindow.findViewById(R.id.notification_panel);
+
mStackScroller = mStatusBarWindow.findViewById(R.id.notification_stack_scroller);
mZenController.addCallback(this);
NotificationListContainer notifListContainer = (NotificationListContainer) mStackScroller;
@@ -907,8 +908,10 @@
ScrimView scrimBehind = mStatusBarWindow.findViewById(R.id.scrim_behind);
ScrimView scrimInFront = mStatusBarWindow.findViewById(R.id.scrim_in_front);
+ ScrimView scrimForBubble = mStatusBarWindow.findViewById(R.id.scrim_for_bubble);
+
mScrimController = SystemUIFactory.getInstance().createScrimController(
- scrimBehind, scrimInFront, mLockscreenWallpaper,
+ scrimBehind, scrimInFront, scrimForBubble, mLockscreenWallpaper,
(state, alpha, color) -> mLightBarController.setScrimState(state, alpha, color),
scrimsVisible -> {
if (mStatusBarWindowController != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowController.java
index a4f495a..ddebee9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowController.java
@@ -177,7 +177,7 @@
}
final boolean scrimsOccludingWallpaper =
- state.scrimsVisibility == ScrimController.VISIBILITY_FULLY_OPAQUE;
+ state.scrimsVisibility == ScrimController.OPAQUE;
final boolean keyguardOrAod = state.keyguardShowing
|| (state.dozing && mDozeParameters.getAlwaysOn());
if (keyguardOrAod && !state.backdropShowing && !scrimsOccludingWallpaper) {
@@ -251,7 +251,7 @@
return !state.forceCollapsed && (state.isKeyguardShowingAndNotOccluded()
|| state.panelVisible || state.keyguardFadingAway || state.bouncerShowing
|| state.headsUpShowing || state.bubblesShowing
- || state.scrimsVisibility != ScrimController.VISIBILITY_FULLY_TRANSPARENT);
+ || state.scrimsVisibility != ScrimController.TRANSPARENT);
}
private void applyFitsSystemWindows(State state) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
index b3f6f4e..fa7aa50 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
@@ -57,14 +57,15 @@
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.NotificationPresenter;
import com.android.systemui.statusbar.NotificationRemoveInterceptor;
import com.android.systemui.statusbar.NotificationTestHelper;
import com.android.systemui.statusbar.notification.NotificationEntryListener;
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.collection.NotificationData;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.phone.DozeParameters;
import com.android.systemui.statusbar.phone.StatusBarWindowController;
@@ -172,7 +173,9 @@
when(mZenModeController.getConfig()).thenReturn(mZenModeConfig);
TestableNotificationInterruptionStateProvider interruptionStateProvider =
- new TestableNotificationInterruptionStateProvider(mContext);
+ new TestableNotificationInterruptionStateProvider(mContext,
+ mock(NotificationFilter.class),
+ mock(StatusBarStateController.class));
interruptionStateProvider.setUpWithPresenter(
mock(NotificationPresenter.class),
mock(HeadsUpManager.class),
@@ -643,18 +646,14 @@
super(context, statusBarWindowController, data, Runnable::run,
configurationController, interruptionStateProvider, zenModeController);
}
-
- @Override
- public boolean shouldAutoBubbleForFlags(Context c, NotificationEntry entry) {
- return entry.notification.getNotification().getBubbleMetadata() != null;
- }
}
public static class TestableNotificationInterruptionStateProvider extends
NotificationInterruptionStateProvider {
- public TestableNotificationInterruptionStateProvider(Context context) {
- super(context);
+ public TestableNotificationInterruptionStateProvider(Context context,
+ NotificationFilter filter, StatusBarStateController controller) {
+ super(context, filter, controller);
mUseHeadsUp = true;
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/ExpandedAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/ExpandedAnimationControllerTest.java
index b324235..7a0bad2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/ExpandedAnimationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/ExpandedAnimationControllerTest.java
@@ -45,14 +45,17 @@
private int mDisplayWidth = 500;
private int mDisplayHeight = 1000;
+ private int mExpandedViewPadding = 10;
+ private float mLauncherGridDiff = 30f;
@Spy
private ExpandedAnimationController mExpandedController =
new ExpandedAnimationController(
new Point(mDisplayWidth, mDisplayHeight) /* displaySize */,
- 0 /* expandedViewPadding */);
+ mExpandedViewPadding);
+
private int mStackOffset;
- private float mBubblePadding;
+ private float mBubblePaddingTop;
private float mBubbleSize;
private PointF mExpansionPoint;
@@ -65,7 +68,7 @@
Resources res = mLayout.getResources();
mStackOffset = res.getDimensionPixelSize(R.dimen.bubble_stack_offset);
- mBubblePadding = res.getDimensionPixelSize(R.dimen.bubble_padding);
+ mBubblePaddingTop = res.getDimensionPixelSize(R.dimen.bubble_padding_top);
mBubbleSize = res.getDimensionPixelSize(R.dimen.individual_bubble_size);
mExpansionPoint = new PointF(100, 100);
}
@@ -138,7 +141,6 @@
assertEquals(500f, draggedBubble.getTranslationX(), 1f);
assertEquals(500f, draggedBubble.getTranslationY(), 1f);
- // Snap it back and make sure it made it back correctly.
mLayout.removeView(draggedBubble);
waitForLayoutMessageQueue();
waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y);
@@ -174,7 +176,7 @@
waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y);
- assertEquals(mBubblePadding, mViews.get(1).getTranslationX(), 1f);
+ assertEquals(mBubblePaddingTop, mViews.get(1).getTranslationX(), 1f);
}
@Test
@@ -256,8 +258,8 @@
* @return Bubble left x from left edge of screen.
*/
public float getBubbleLeft(int index) {
- float bubbleLeftFromRowLeft = index * (mBubbleSize + mBubblePadding);
- return getRowLeft() + bubbleLeftFromRowLeft;
+ final float bubbleLeft = index * (mBubbleSize + getSpaceBetweenBubbles());
+ return getRowLeft() + bubbleLeft;
}
private float getRowLeft() {
@@ -265,16 +267,29 @@
return 0;
}
int bubbleCount = mLayout.getChildCount();
+ final float totalBubbleWidth = bubbleCount * mBubbleSize;
+ final float totalGapWidth = (bubbleCount - 1) * getSpaceBetweenBubbles();
+ final float rowWidth = totalGapWidth + totalBubbleWidth;
- // Width calculations.
- double bubble = bubbleCount * mBubbleSize;
- float gap = (bubbleCount - 1) * mBubblePadding;
- float row = gap + (float) bubble;
+ final float centerScreen = mDisplayWidth / 2f;
+ final float halfRow = rowWidth / 2f;
+ final float rowLeft = centerScreen - halfRow;
- float halfRow = row / 2f;
- float centerScreen = mDisplayWidth / 2;
- float rowLeftFromScreenLeft = centerScreen - halfRow;
+ return rowLeft;
+ }
- return rowLeftFromScreenLeft;
+ /**
+ * @return Space between bubbles in row above expanded view.
+ */
+ private float getSpaceBetweenBubbles() {
+ final float rowMargins = (mExpandedViewPadding + mLauncherGridDiff) * 2;
+ final float maxRowWidth = mDisplayWidth - rowMargins;
+
+ final float totalBubbleWidth = mMaxBubbles * mBubbleSize;
+ final float totalGapWidth = maxRowWidth - totalBubbleWidth;
+
+ final int gapCount = mMaxBubbles - 1;
+ final float gapWidth = totalGapWidth / gapCount;
+ return gapWidth;
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayoutTestCase.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayoutTestCase.java
index f633f39..2ae759f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayoutTestCase.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayoutTestCase.java
@@ -68,7 +68,7 @@
@Mock
private DisplayCutout mCutout;
- private int mMaxBubbles;
+ protected int mMaxBubbles;
@Before
public void setUp() throws Exception {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationInterruptionStateProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationInterruptionStateProviderTest.java
new file mode 100644
index 0000000..a66cf84
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationInterruptionStateProviderTest.java
@@ -0,0 +1,589 @@
+/*
+ * 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;
+
+
+import static android.app.Notification.FLAG_BUBBLE;
+import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
+import static android.app.NotificationManager.IMPORTANCE_HIGH;
+import static android.app.NotificationManager.IMPORTANCE_LOW;
+import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_AMBIENT;
+import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK;
+
+import static com.android.systemui.statusbar.StatusBarState.SHADE;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.Notification;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.drawable.Icon;
+import android.hardware.display.AmbientDisplayConfiguration;
+import android.os.PowerManager;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.service.dreams.IDreamManager;
+import android.service.notification.StatusBarNotification;
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.R;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.statusbar.notification.NotificationFilter;
+import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.policy.HeadsUpManager;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Tests for the interruption state provider which understands whether the system & notification
+ * is in a state allowing a particular notification to hun, pulse, or bubble.
+ */
+@RunWith(AndroidTestingRunner.class)
+@SmallTest
+public class NotificationInterruptionStateProviderTest extends SysuiTestCase {
+
+ @Mock
+ PowerManager mPowerManager;
+ @Mock
+ IDreamManager mDreamManager;
+ @Mock
+ AmbientDisplayConfiguration mAmbientDisplayConfiguration;
+ @Mock
+ NotificationFilter mNotificationFilter;
+ @Mock
+ StatusBarStateController mStatusBarStateController;
+ @Mock
+ NotificationPresenter mPresenter;
+ @Mock
+ HeadsUpManager mHeadsUpManager;
+ @Mock
+ NotificationInterruptionStateProvider.HeadsUpSuppressor mHeadsUpSuppressor;
+
+ private NotificationInterruptionStateProvider mNotifInterruptionStateProvider;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+
+ mNotifInterruptionStateProvider =
+ new TestableNotificationInterruptionStateProvider(mContext,
+ mPowerManager,
+ mDreamManager,
+ mAmbientDisplayConfiguration,
+ mNotificationFilter,
+ mStatusBarStateController);
+
+ mNotifInterruptionStateProvider.setUpWithPresenter(
+ mPresenter,
+ mHeadsUpManager,
+ mHeadsUpSuppressor);
+ }
+
+ /**
+ * Sets up the state such that any requests to
+ * {@link NotificationInterruptionStateProvider#canAlertCommon(NotificationEntry)} will
+ * pass as long its provided NotificationEntry fulfills group suppression check.
+ */
+ private void ensureStateForAlertCommon() {
+ when(mNotificationFilter.shouldFilterOut(any())).thenReturn(false);
+ }
+
+ /**
+ * Sets up the state such that any requests to
+ * {@link NotificationInterruptionStateProvider#canAlertAwakeCommon(NotificationEntry)} will
+ * pass as long its provided NotificationEntry fulfills launch fullscreen check.
+ */
+ private void ensureStateForAlertAwakeCommon() {
+ when(mPresenter.isDeviceInVrMode()).thenReturn(false);
+ when(mHeadsUpManager.isSnoozed(any())).thenReturn(false);
+ }
+
+ /**
+ * Sets up the state such that any requests to
+ * {@link NotificationInterruptionStateProvider#shouldHeadsUp(NotificationEntry)} will
+ * pass as long its provided NotificationEntry fulfills importance & DND checks.
+ */
+ private void ensureStateForHeadsUpWhenAwake() throws RemoteException {
+ ensureStateForAlertCommon();
+ ensureStateForAlertAwakeCommon();
+
+ when(mStatusBarStateController.isDozing()).thenReturn(false);
+ when(mDreamManager.isDreaming()).thenReturn(false);
+ when(mPowerManager.isScreenOn()).thenReturn(true);
+ when(mHeadsUpSuppressor.canHeadsUp(any(), any())).thenReturn(true);
+ }
+
+ /**
+ * Sets up the state such that any requests to
+ * {@link NotificationInterruptionStateProvider#shouldHeadsUp(NotificationEntry)} will
+ * pass as long its provided NotificationEntry fulfills importance & DND checks.
+ */
+ private void ensureStateForHeadsUpWhenDozing() {
+ ensureStateForAlertCommon();
+
+ when(mStatusBarStateController.isDozing()).thenReturn(true);
+ when(mAmbientDisplayConfiguration.pulseOnNotificationEnabled(anyInt())).thenReturn(true);
+ }
+
+ /**
+ * Sets up the state such that any requests to
+ * {@link NotificationInterruptionStateProvider#shouldBubbleUp(NotificationEntry)} will
+ * pass as long its provided NotificationEntry fulfills importance & bubble checks.
+ */
+ private void ensureStateForBubbleUp() {
+ ensureStateForAlertCommon();
+ ensureStateForAlertAwakeCommon();
+ }
+
+ /**
+ * Ensure that the disabled state is set correctly.
+ */
+ @Test
+ public void testDisableNotificationAlerts() {
+ // Enabled by default
+ assertThat(mNotifInterruptionStateProvider.areNotificationAlertsDisabled()).isFalse();
+
+ // Disable alerts
+ mNotifInterruptionStateProvider.setDisableNotificationAlerts(true);
+ assertThat(mNotifInterruptionStateProvider.areNotificationAlertsDisabled()).isTrue();
+
+ // Enable alerts
+ mNotifInterruptionStateProvider.setDisableNotificationAlerts(false);
+ assertThat(mNotifInterruptionStateProvider.areNotificationAlertsDisabled()).isFalse();
+ }
+
+ /**
+ * Ensure that the disabled alert state effects whether HUNs are enabled.
+ */
+ @Test
+ public void testHunSettingsChange_enabled_butAlertsDisabled() {
+ // Set up but without a mock change observer
+ mNotifInterruptionStateProvider.setUpWithPresenter(
+ mPresenter,
+ mHeadsUpManager,
+ mHeadsUpSuppressor);
+
+ // HUNs enabled by default
+ assertThat(mNotifInterruptionStateProvider.getUseHeadsUp()).isTrue();
+
+ // Set alerts disabled
+ mNotifInterruptionStateProvider.setDisableNotificationAlerts(true);
+
+ // No more HUNs
+ assertThat(mNotifInterruptionStateProvider.getUseHeadsUp()).isFalse();
+ }
+
+ /**
+ * Alerts can happen.
+ */
+ @Test
+ public void testCanAlertCommon_true() {
+ ensureStateForAlertCommon();
+
+ NotificationEntry entry = createNotification(IMPORTANCE_DEFAULT);
+ assertThat(mNotifInterruptionStateProvider.canAlertCommon(entry)).isTrue();
+ }
+
+ /**
+ * Filtered out notifications don't alert.
+ */
+ @Test
+ public void testCanAlertCommon_false_filteredOut() {
+ ensureStateForAlertCommon();
+ when(mNotificationFilter.shouldFilterOut(any())).thenReturn(true);
+
+ NotificationEntry entry = createNotification(IMPORTANCE_DEFAULT);
+ assertThat(mNotifInterruptionStateProvider.canAlertCommon(entry)).isFalse();
+ }
+
+ /**
+ * Grouped notifications have different alerting behaviours, sometimes the alert for a
+ * grouped notification may be suppressed {@link android.app.Notification#GROUP_ALERT_CHILDREN}.
+ */
+ @Test
+ public void testCanAlertCommon_false_suppressedForGroups() {
+ ensureStateForAlertCommon();
+
+ Notification n = new Notification.Builder(getContext(), "a")
+ .setGroup("a")
+ .setGroupSummary(true)
+ .setGroupAlertBehavior(Notification.GROUP_ALERT_CHILDREN)
+ .build();
+ StatusBarNotification sbn = new StatusBarNotification("a", "a", 0, "a", 0, 0, n,
+ UserHandle.of(0), null, 0);
+ NotificationEntry entry = new NotificationEntry(sbn);
+ entry.importance = IMPORTANCE_DEFAULT;
+
+ assertThat(mNotifInterruptionStateProvider.canAlertCommon(entry)).isFalse();
+ }
+
+ /**
+ * HUNs while dozing can happen.
+ */
+ @Test
+ public void testShouldHeadsUpWhenDozing_true() {
+ ensureStateForHeadsUpWhenDozing();
+
+ NotificationEntry entry = createNotification(IMPORTANCE_DEFAULT);
+ assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isTrue();
+ }
+
+ /**
+ * Ambient display can show HUNs for new notifications, this may be disabled.
+ */
+ @Test
+ public void testShouldHeadsUpWhenDozing_false_pulseDisabled() {
+ ensureStateForHeadsUpWhenDozing();
+ when(mAmbientDisplayConfiguration.pulseOnNotificationEnabled(anyInt())).thenReturn(false);
+
+ NotificationEntry entry = createNotification(IMPORTANCE_DEFAULT);
+ assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isFalse();
+ }
+
+ /**
+ * If the device is not in ambient display or sleeping then we don't HUN.
+ */
+ @Test
+ public void testShouldHeadsUpWhenDozing_false_notDozing() {
+ ensureStateForHeadsUpWhenDozing();
+ when(mStatusBarStateController.isDozing()).thenReturn(false);
+
+ NotificationEntry entry = createNotification(IMPORTANCE_DEFAULT);
+ assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isFalse();
+ }
+
+ /**
+ * In DND ambient effects can be suppressed
+ * {@link android.app.NotificationManager.Policy#SUPPRESSED_EFFECT_AMBIENT}.
+ */
+ @Test
+ public void testShouldHeadsUpWhenDozing_false_suppressingAmbient() {
+ ensureStateForHeadsUpWhenDozing();
+
+ NotificationEntry entry = createNotification(IMPORTANCE_DEFAULT);
+ entry.suppressedVisualEffects = SUPPRESSED_EFFECT_AMBIENT;
+
+ assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isFalse();
+ }
+
+ /**
+ * Notifications that are < {@link android.app.NotificationManager#IMPORTANCE_DEFAULT} don't
+ * get to pulse.
+ */
+ @Test
+ public void testShouldHeadsUpWhenDozing_false_lessImportant() {
+ ensureStateForHeadsUpWhenDozing();
+
+ NotificationEntry entry = createNotification(IMPORTANCE_LOW);
+ assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isFalse();
+ }
+
+ /**
+ * Heads up can happen.
+ */
+ @Test
+ public void testShouldHeadsUp_true() throws RemoteException {
+ ensureStateForHeadsUpWhenAwake();
+
+ NotificationEntry entry = createNotification(IMPORTANCE_HIGH);
+ assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isTrue();
+ }
+
+ /**
+ * Heads up notifications can be disabled in general.
+ */
+ @Test
+ public void testShouldHeadsUp_false_noHunsAllowed() throws RemoteException {
+ ensureStateForHeadsUpWhenAwake();
+
+ // Set alerts disabled, this should cause heads up to be false
+ mNotifInterruptionStateProvider.setDisableNotificationAlerts(true);
+ assertThat(mNotifInterruptionStateProvider.getUseHeadsUp()).isFalse();
+
+ NotificationEntry entry = createNotification(IMPORTANCE_HIGH);
+ assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isFalse();
+ }
+
+ /**
+ * If the device is dozing, we don't show as heads up.
+ */
+ @Test
+ public void testShouldHeadsUp_false_dozing() throws RemoteException {
+ ensureStateForHeadsUpWhenAwake();
+ when(mStatusBarStateController.isDozing()).thenReturn(true);
+
+ NotificationEntry entry = createNotification(IMPORTANCE_HIGH);
+ assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isFalse();
+ }
+
+ /**
+ * If the notification is a bubble, and the user is not on AOD / lockscreen, then
+ * the bubble is shown rather than the heads up.
+ */
+ @Test
+ public void testShouldHeadsUp_false_bubble() throws RemoteException {
+ ensureStateForHeadsUpWhenAwake();
+
+ // Bubble bit only applies to interruption when we're in the shade
+ when(mStatusBarStateController.getState()).thenReturn(SHADE);
+
+ assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(createBubble())).isFalse();
+ }
+
+ /**
+ * If we're not allowed to alert in general, we shouldn't be shown as heads up.
+ */
+ @Test
+ public void testShouldHeadsUp_false_alertCommonFalse() throws RemoteException {
+ ensureStateForHeadsUpWhenAwake();
+ // Make canAlertCommon false by saying it's filtered out
+ when(mNotificationFilter.shouldFilterOut(any())).thenReturn(true);
+
+ NotificationEntry entry = createNotification(IMPORTANCE_HIGH);
+ assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isFalse();
+ }
+
+ /**
+ * In DND HUN peek effects can be suppressed
+ * {@link android.app.NotificationManager.Policy#SUPPRESSED_EFFECT_PEEK}.
+ */
+ @Test
+ public void testShouldHeadsUp_false_suppressPeek() throws RemoteException {
+ ensureStateForHeadsUpWhenAwake();
+
+ NotificationEntry entry = createNotification(IMPORTANCE_HIGH);
+ entry.suppressedVisualEffects = SUPPRESSED_EFFECT_PEEK;
+
+ assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isFalse();
+ }
+
+ /**
+ * Notifications that are < {@link android.app.NotificationManager#IMPORTANCE_HIGH} don't get
+ * to show as a heads up.
+ */
+ @Test
+ public void testShouldHeadsUp_false_lessImportant() throws RemoteException {
+ ensureStateForHeadsUpWhenAwake();
+
+ NotificationEntry entry = createNotification(IMPORTANCE_DEFAULT);
+ assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isFalse();
+ }
+
+ /**
+ * If the device is not in use then we shouldn't be shown as heads up.
+ */
+ @Test
+ public void testShouldHeadsUp_false_deviceNotInUse() throws RemoteException {
+ ensureStateForHeadsUpWhenAwake();
+ NotificationEntry entry = createNotification(IMPORTANCE_HIGH);
+
+ // Device is not in use if screen is not on
+ when(mPowerManager.isScreenOn()).thenReturn(false);
+ assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isFalse();
+
+ // Also not in use if screen is on but we're showing screen saver / "dreaming"
+ when(mPowerManager.isDeviceIdleMode()).thenReturn(true);
+ when(mDreamManager.isDreaming()).thenReturn(true);
+ assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isFalse();
+ }
+
+ /**
+ * If something wants to suppress this heads up, then it shouldn't be shown as a heads up.
+ */
+ @Test
+ public void testShouldHeadsUp_false_suppressed() throws RemoteException {
+ ensureStateForHeadsUpWhenAwake();
+ when(mHeadsUpSuppressor.canHeadsUp(any(), any())).thenReturn(false);
+
+ NotificationEntry entry = createNotification(IMPORTANCE_HIGH);
+ assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isFalse();
+ verify(mHeadsUpSuppressor).canHeadsUp(any(), any());
+ }
+
+ /**
+ * On screen alerts don't happen when the device is in VR Mode.
+ */
+ @Test
+ public void testCanAlertAwakeCommon__false_vrMode() {
+ ensureStateForAlertAwakeCommon();
+ when(mPresenter.isDeviceInVrMode()).thenReturn(true);
+
+ NotificationEntry entry = createNotification(IMPORTANCE_DEFAULT);
+ assertThat(mNotifInterruptionStateProvider.canAlertAwakeCommon(entry)).isFalse();
+ }
+
+ /**
+ * On screen alerts don't happen when the notification is snoozed.
+ */
+ @Test
+ public void testCanAlertAwakeCommon_false_snoozedPackage() {
+ ensureStateForAlertAwakeCommon();
+ when(mHeadsUpManager.isSnoozed(any())).thenReturn(true);
+
+ NotificationEntry entry = createNotification(IMPORTANCE_DEFAULT);
+ assertThat(mNotifInterruptionStateProvider.canAlertAwakeCommon(entry)).isFalse();
+ }
+
+ /**
+ * On screen alerts don't happen when that package has just launched fullscreen.
+ */
+ @Test
+ public void testCanAlertAwakeCommon_false_justLaunchedFullscreen() {
+ ensureStateForAlertAwakeCommon();
+
+ NotificationEntry entry = createNotification(IMPORTANCE_DEFAULT);
+ entry.notifyFullScreenIntentLaunched();
+
+ assertThat(mNotifInterruptionStateProvider.canAlertAwakeCommon(entry)).isFalse();
+ }
+
+ /**
+ * Bubbles can happen.
+ */
+ @Test
+ public void testShouldBubbleUp_true() {
+ ensureStateForBubbleUp();
+ assertThat(mNotifInterruptionStateProvider.shouldBubbleUp(createBubble())).isTrue();
+ }
+
+ /**
+ * If the notification doesn't have permission to bubble, it shouldn't bubble.
+ */
+ @Test
+ public void shouldBubbleUp_false_notAllowedToBubble() {
+ ensureStateForBubbleUp();
+
+ NotificationEntry entry = createBubble();
+ entry.canBubble = false;
+
+ assertThat(mNotifInterruptionStateProvider.shouldBubbleUp(entry)).isFalse();
+ }
+
+ /**
+ * If the notification isn't a bubble, it should definitely not show as a bubble.
+ */
+ @Test
+ public void shouldBubbleUp_false_notABubble() {
+ ensureStateForBubbleUp();
+
+ NotificationEntry entry = createNotification(IMPORTANCE_HIGH);
+ entry.canBubble = true;
+
+ assertThat(mNotifInterruptionStateProvider.shouldBubbleUp(entry)).isFalse();
+ }
+
+ /**
+ * If the notification doesn't have bubble metadata, it shouldn't bubble.
+ */
+ @Test
+ public void shouldBubbleUp_false_invalidMetadata() {
+ ensureStateForBubbleUp();
+
+ NotificationEntry entry = createNotification(IMPORTANCE_HIGH);
+ entry.canBubble = true;
+ entry.notification.getNotification().flags |= FLAG_BUBBLE;
+
+ assertThat(mNotifInterruptionStateProvider.shouldBubbleUp(entry)).isFalse();
+ }
+
+ /**
+ * If the notification can't heads up in general, it shouldn't bubble.
+ */
+ @Test
+ public void shouldBubbleUp_false_alertAwakeCommonFalse() {
+ ensureStateForBubbleUp();
+
+ // Make alert common return false by pretending we're in VR mode
+ when(mPresenter.isDeviceInVrMode()).thenReturn(true);
+
+ assertThat(mNotifInterruptionStateProvider.shouldBubbleUp(createBubble())).isFalse();
+ }
+
+ /**
+ * If the notification can't heads up in general, it shouldn't bubble.
+ */
+ @Test
+ public void shouldBubbleUp_false_alertCommonFalse() {
+ ensureStateForBubbleUp();
+
+ // Make canAlertCommon false by saying it's filtered out
+ when(mNotificationFilter.shouldFilterOut(any())).thenReturn(true);
+
+ assertThat(mNotifInterruptionStateProvider.shouldBubbleUp(createBubble())).isFalse();
+ }
+
+ private NotificationEntry createBubble() {
+ Notification.BubbleMetadata data = new Notification.BubbleMetadata.Builder()
+ .setIntent(PendingIntent.getActivity(mContext, 0, new Intent(), 0))
+ .setIcon(Icon.createWithResource(mContext.getResources(), R.drawable.android))
+ .build();
+ Notification n = new Notification.Builder(getContext(), "a")
+ .setContentTitle("title")
+ .setContentText("content text")
+ .setBubbleMetadata(data)
+ .build();
+ StatusBarNotification sbn = new StatusBarNotification("a", "a", 0, "a", 0, 0, n,
+ UserHandle.of(0), null, 0);
+ NotificationEntry entry = new NotificationEntry(sbn);
+ entry.notification.getNotification().flags |= FLAG_BUBBLE;
+ entry.importance = IMPORTANCE_HIGH;
+ entry.canBubble = true;
+ return entry;
+ }
+
+ private NotificationEntry createNotification(int importance) {
+ Notification n = new Notification.Builder(getContext(), "a")
+ .setContentTitle("title")
+ .setContentText("content text")
+ .build();
+ StatusBarNotification sbn = new StatusBarNotification("a", "a", 0, "a", 0, 0, n,
+ UserHandle.of(0), null, 0);
+ NotificationEntry entry = new NotificationEntry(sbn);
+ entry.importance = importance;
+ return entry;
+ }
+
+ /**
+ * Testable class overriding constructor.
+ */
+ public class TestableNotificationInterruptionStateProvider extends
+ NotificationInterruptionStateProvider {
+
+ TestableNotificationInterruptionStateProvider(Context context,
+ PowerManager powerManager, IDreamManager dreamManager,
+ AmbientDisplayConfiguration ambientDisplayConfiguration,
+ NotificationFilter notificationFilter,
+ StatusBarStateController statusBarStateController) {
+ super(context, powerManager, dreamManager, ambientDisplayConfiguration,
+ notificationFilter,
+ statusBarStateController);
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
index 191c983..cf75e05 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
@@ -16,9 +16,9 @@
package com.android.systemui.statusbar.phone;
-import static com.android.systemui.statusbar.phone.ScrimController.VISIBILITY_FULLY_OPAQUE;
-import static com.android.systemui.statusbar.phone.ScrimController.VISIBILITY_FULLY_TRANSPARENT;
-import static com.android.systemui.statusbar.phone.ScrimController.VISIBILITY_SEMI_TRANSPARENT;
+import static com.android.systemui.statusbar.phone.ScrimController.OPAQUE;
+import static com.android.systemui.statusbar.phone.ScrimController.SEMI_TRANSPARENT;
+import static com.android.systemui.statusbar.phone.ScrimController.TRANSPARENT;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -68,6 +68,7 @@
private SynchronousScrimController mScrimController;
private ScrimView mScrimBehind;
private ScrimView mScrimInFront;
+ private ScrimView mScrimForBubble;
private ScrimState mScrimState;
private float mScrimBehindAlpha;
private GradientColors mScrimInFrontColor;
@@ -83,6 +84,7 @@
public void setup() {
mScrimBehind = spy(new ScrimView(getContext()));
mScrimInFront = new ScrimView(getContext());
+ mScrimForBubble = new ScrimView(getContext());
mWakeLock = mock(WakeLock.class);
mAlarmManager = mock(AlarmManager.class);
mAlwaysOnEnabled = true;
@@ -91,6 +93,7 @@
when(mDozeParamenters.getAlwaysOn()).thenAnswer(invocation -> mAlwaysOnEnabled);
when(mDozeParamenters.getDisplayNeedsBlanking()).thenReturn(true);
mScrimController = new SynchronousScrimController(mScrimBehind, mScrimInFront,
+ mScrimForBubble,
(scrimState, scrimBehindAlpha, scrimInFrontColor) -> {
mScrimState = scrimState;
mScrimBehindAlpha = scrimBehindAlpha;
@@ -112,21 +115,28 @@
public void transitionToKeyguard() {
mScrimController.transitionTo(ScrimState.KEYGUARD);
mScrimController.finishAnimationsImmediately();
- // Front scrim should be transparent
- // Back scrim should be visible without tint
- assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_SEMI_TRANSPARENT);
- assertScrimTint(mScrimBehind, true /* tinted */);
+
+ assertScrimAlpha(TRANSPARENT /* front */,
+ SEMI_TRANSPARENT /* back */,
+ TRANSPARENT /* bubble */);
+
+ assertScrimTint(true /* front */,
+ true /* behind */,
+ false /* bubble */);
}
@Test
public void transitionToAod_withRegularWallpaper() {
mScrimController.transitionTo(ScrimState.AOD);
mScrimController.finishAnimationsImmediately();
- // Front scrim should be transparent
- // Back scrim should be visible with tint
- assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_FULLY_OPAQUE);
- assertScrimTint(mScrimBehind, true /* tinted */);
- assertScrimTint(mScrimInFront, true /* tinted */);
+
+ assertScrimAlpha(TRANSPARENT /* front */,
+ OPAQUE /* back */,
+ TRANSPARENT /* bubble */);
+
+ assertScrimTint(true /* front */,
+ true /* behind */,
+ false /* bubble */);
}
@Test
@@ -134,14 +144,18 @@
mScrimController.setWallpaperSupportsAmbientMode(true);
mScrimController.transitionTo(ScrimState.AOD);
mScrimController.finishAnimationsImmediately();
- // Front scrim should be transparent
- // Back scrim should be transparent
- assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_FULLY_TRANSPARENT);
+
+ assertScrimAlpha(TRANSPARENT /* front */,
+ TRANSPARENT /* back */,
+ TRANSPARENT /* bubble */);
// Pulsing notification should conserve AOD wallpaper.
mScrimController.transitionTo(ScrimState.PULSING);
mScrimController.finishAnimationsImmediately();
- assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_FULLY_TRANSPARENT);
+
+ assertScrimAlpha(TRANSPARENT /* front */,
+ TRANSPARENT /* back */,
+ TRANSPARENT /* bubble */);
}
@Test
@@ -150,11 +164,14 @@
mScrimController.setWallpaperSupportsAmbientMode(true);
mScrimController.transitionTo(ScrimState.AOD);
mScrimController.finishAnimationsImmediately();
- // Front scrim should be transparent
- // Back scrim should be visible with tint
- assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_FULLY_OPAQUE);
- assertScrimTint(mScrimBehind, true /* tinted */);
- assertScrimTint(mScrimInFront, true /* tinted */);
+
+ assertScrimAlpha(TRANSPARENT /* front */,
+ OPAQUE /* back */,
+ TRANSPARENT /* bubble */);
+
+ assertScrimTint(true /* front */,
+ true /* behind */,
+ false /* bubble */);
}
@Test
@@ -164,11 +181,14 @@
mScrimController.finishAnimationsImmediately();
mScrimController.setHasBackdrop(true);
mScrimController.finishAnimationsImmediately();
- // Front scrim should be transparent
- // Back scrim should be visible with tint
- assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_FULLY_OPAQUE);
- assertScrimTint(mScrimBehind, true /* tinted */);
- assertScrimTint(mScrimInFront, true /* tinted */);
+
+ assertScrimAlpha(TRANSPARENT /* front */,
+ OPAQUE /* back */,
+ TRANSPARENT /* bubble */);
+
+ assertScrimTint(true /* front */,
+ true /* behind */,
+ false /* bubble */);
}
@Test
@@ -177,27 +197,32 @@
mScrimController.transitionTo(ScrimState.KEYGUARD);
mScrimController.setAodFrontScrimAlpha(0.5f);
mScrimController.finishAnimationsImmediately();
- // Front scrim should be transparent
- // Back scrim should be visible without tint
- assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_SEMI_TRANSPARENT);
+
+ assertScrimAlpha(TRANSPARENT /* front */,
+ SEMI_TRANSPARENT /* back */,
+ TRANSPARENT /* bubble */);
// ... but that it does take effect once we enter the AOD state.
mScrimController.transitionTo(ScrimState.AOD);
mScrimController.finishAnimationsImmediately();
- // Front scrim should be semi-transparent
- // Back scrim should be visible
- assertScrimVisibility(VISIBILITY_SEMI_TRANSPARENT, VISIBILITY_FULLY_OPAQUE);
+ assertScrimAlpha(SEMI_TRANSPARENT /* front */,
+ OPAQUE /* back */,
+ TRANSPARENT /* bubble */);
// ... and that if we set it while we're in AOD, it does take immediate effect.
mScrimController.setAodFrontScrimAlpha(1f);
- assertScrimVisibility(VISIBILITY_FULLY_OPAQUE, VISIBILITY_FULLY_OPAQUE);
+ assertScrimAlpha(OPAQUE /* front */,
+ OPAQUE /* back */,
+ TRANSPARENT /* bubble */);
// ... and make sure we recall the previous front scrim alpha even if we transition away
// for a bit.
mScrimController.transitionTo(ScrimState.UNLOCKED);
mScrimController.transitionTo(ScrimState.AOD);
mScrimController.finishAnimationsImmediately();
- assertScrimVisibility(VISIBILITY_FULLY_OPAQUE, VISIBILITY_FULLY_OPAQUE);
+ assertScrimAlpha(OPAQUE /* front */,
+ OPAQUE /* back */,
+ TRANSPARENT /* bubble */);
// ... and alpha updates should be completely ignored if always_on is off.
// Passing it forward would mess up the wake-up transition.
@@ -221,19 +246,28 @@
mScrimController.setWallpaperSupportsAmbientMode(false);
mScrimController.transitionTo(ScrimState.AOD);
mScrimController.finishAnimationsImmediately();
- assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_FULLY_OPAQUE);
+ assertScrimAlpha(TRANSPARENT /* front */,
+ OPAQUE /* back */,
+ TRANSPARENT /* bubble */);
mScrimController.transitionTo(ScrimState.PULSING);
mScrimController.finishAnimationsImmediately();
// Front scrim should be transparent
// Back scrim should be semi-transparent so the user can see the wallpaper
// Pulse callback should have been invoked
- assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_FULLY_OPAQUE);
- assertScrimTint(mScrimBehind, true /* tinted */);
+ assertScrimAlpha(TRANSPARENT /* front */,
+ OPAQUE /* back */,
+ TRANSPARENT /* bubble */);
+
+ assertScrimTint(false /* front */,
+ true /* behind */,
+ false /* bubble */);
mScrimController.setWakeLockScreenSensorActive(true);
mScrimController.finishAnimationsImmediately();
- assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_SEMI_TRANSPARENT);
+ assertScrimAlpha(TRANSPARENT /* front */,
+ SEMI_TRANSPARENT /* back */,
+ TRANSPARENT /* bubble */);
}
@Test
@@ -242,8 +276,13 @@
mScrimController.finishAnimationsImmediately();
// Front scrim should be transparent
// Back scrim should be visible without tint
- assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_SEMI_TRANSPARENT);
- assertScrimTint(mScrimBehind, false /* tinted */);
+ assertScrimAlpha(TRANSPARENT /* front */,
+ SEMI_TRANSPARENT /* back */,
+ TRANSPARENT /* bubble */);
+
+ assertScrimTint(false /* front */,
+ false /* behind */,
+ false /* bubble */);
}
@Test
@@ -252,8 +291,12 @@
mScrimController.finishAnimationsImmediately();
// Front scrim should be transparent
// Back scrim should be visible without tint
- assertScrimVisibility(VISIBILITY_SEMI_TRANSPARENT, VISIBILITY_FULLY_TRANSPARENT);
- assertScrimTint(mScrimBehind, false /* tinted */);
+ assertScrimAlpha(SEMI_TRANSPARENT /* front */,
+ TRANSPARENT /* back */,
+ TRANSPARENT /* bubble */);
+ assertScrimTint(false /* front */,
+ false /* behind */,
+ false /* bubble */);
}
@Test
@@ -261,15 +304,19 @@
mScrimController.setPanelExpansion(0f);
mScrimController.transitionTo(ScrimState.UNLOCKED);
mScrimController.finishAnimationsImmediately();
- // Front scrim should be transparent
- // Back scrim should be transparent
- assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_FULLY_TRANSPARENT);
- assertScrimTint(mScrimBehind, false /* tinted */);
- assertScrimTint(mScrimInFront, false /* tinted */);
+ assertScrimAlpha(TRANSPARENT /* front */,
+ TRANSPARENT /* back */,
+ TRANSPARENT /* bubble */);
+
+ assertScrimTint(false /* front */,
+ false /* behind */,
+ false /* bubble */);
// Back scrim should be visible after start dragging
mScrimController.setPanelExpansion(0.5f);
- assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_SEMI_TRANSPARENT);
+ assertScrimAlpha(TRANSPARENT /* front */,
+ SEMI_TRANSPARENT /* back */,
+ TRANSPARENT /* bubble */);
}
@Test
@@ -277,12 +324,19 @@
mScrimController.transitionTo(ScrimState.BUBBLE_EXPANDED);
mScrimController.finishAnimationsImmediately();
+ assertScrimTint(false /* front */,
+ false /* behind */,
+ false /* bubble */);
+
// Front scrim should be transparent
- Assert.assertEquals(ScrimController.VISIBILITY_FULLY_TRANSPARENT,
+ Assert.assertEquals(ScrimController.TRANSPARENT,
mScrimInFront.getViewAlpha(), 0.0f);
// Back scrim should be visible
Assert.assertEquals(ScrimController.GRADIENT_SCRIM_ALPHA_BUSY,
mScrimBehind.getViewAlpha(), 0.0f);
+ // Bubble scrim should be visible
+ Assert.assertEquals(ScrimController.GRADIENT_SCRIM_ALPHA_BUSY,
+ mScrimBehind.getViewAlpha(), 0.0f);
}
@Test
@@ -351,16 +405,22 @@
mScrimController.setPanelExpansion(0f);
mScrimController.finishAnimationsImmediately();
mScrimController.transitionTo(ScrimState.UNLOCKED);
- // Immediately tinted after the transition starts
- assertScrimTint(mScrimInFront, true /* tinted */);
- assertScrimTint(mScrimBehind, true /* tinted */);
+
+ // Immediately tinted black after the transition starts
+ assertScrimTint(true /* front */,
+ true /* behind */,
+ true /* bubble */);
+
mScrimController.finishAnimationsImmediately();
- // Front scrim should be transparent
- // Back scrim should be transparent
- // Neither scrims should be tinted anymore after the animation.
- assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_FULLY_TRANSPARENT);
- assertScrimTint(mScrimInFront, false /* tinted */);
- assertScrimTint(mScrimBehind, false /* tinted */);
+
+ // All scrims should be transparent at the end of fade transition.
+ assertScrimAlpha(TRANSPARENT /* front */,
+ TRANSPARENT /* behind */,
+ TRANSPARENT /* bubble */);
+
+ assertScrimTint(false /* front */,
+ false /* behind */,
+ false /* bubble */);
}
@Test
@@ -375,9 +435,11 @@
// Front scrim should be black in the middle of the transition
Assert.assertTrue("Scrim should be visible during transition. Alpha: "
+ mScrimInFront.getViewAlpha(), mScrimInFront.getViewAlpha() > 0);
- assertScrimTint(mScrimInFront, true /* tinted */);
+ assertScrimTint(true /* front */,
+ true /* behind */,
+ true /* bubble */);
Assert.assertSame("Scrim should be visible during transition.",
- mScrimVisibility, VISIBILITY_FULLY_OPAQUE);
+ mScrimVisibility, OPAQUE);
}
});
mScrimController.finishAnimationsImmediately();
@@ -553,11 +615,15 @@
mScrimController.setKeyguardOccluded(true);
mScrimController.transitionTo(ScrimState.AOD);
mScrimController.finishAnimationsImmediately();
- assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_FULLY_OPAQUE);
+ assertScrimAlpha(TRANSPARENT /* front */,
+ OPAQUE /* behind */,
+ TRANSPARENT /* bubble */);
mScrimController.transitionTo(ScrimState.PULSING);
mScrimController.finishAnimationsImmediately();
- assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_FULLY_OPAQUE);
+ assertScrimAlpha(TRANSPARENT /* front */,
+ OPAQUE /* behind */,
+ TRANSPARENT /* bubble */);
}
@Test
@@ -565,11 +631,15 @@
mScrimController.setWallpaperSupportsAmbientMode(true);
mScrimController.transitionTo(ScrimState.AOD);
mScrimController.finishAnimationsImmediately();
- assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_FULLY_TRANSPARENT);
+ assertScrimAlpha(TRANSPARENT /* front */,
+ TRANSPARENT /* behind */,
+ TRANSPARENT /* bubble */);
mScrimController.setKeyguardOccluded(true);
mScrimController.finishAnimationsImmediately();
- assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_FULLY_OPAQUE);
+ assertScrimAlpha(TRANSPARENT /* front */,
+ OPAQUE /* behind */,
+ TRANSPARENT /* bubble */);
}
@Test
@@ -608,33 +678,68 @@
mScrimInFront.getDefaultFocusHighlightEnabled());
Assert.assertFalse("Scrim shouldn't have focus highlight",
mScrimBehind.getDefaultFocusHighlightEnabled());
+ Assert.assertFalse("Scrim shouldn't have focus highlight",
+ mScrimForBubble.getDefaultFocusHighlightEnabled());
}
- private void assertScrimTint(ScrimView scrimView, boolean tinted) {
- final boolean viewIsTinted = scrimView.getTint() != Color.TRANSPARENT;
- final String name = scrimView == mScrimInFront ? "front" : "back";
+ private void assertScrimTint(boolean front, boolean behind, boolean bubble) {
Assert.assertEquals("Tint test failed at state " + mScrimController.getState()
- +" with scrim: " + name + " and tint: " + Integer.toHexString(scrimView.getTint()),
- tinted, viewIsTinted);
+ + " with scrim: " + getScrimName(mScrimInFront) + " and tint: "
+ + Integer.toHexString(mScrimInFront.getTint()),
+ front, mScrimInFront.getTint() != Color.TRANSPARENT);
+
+ Assert.assertEquals("Tint test failed at state " + mScrimController.getState()
+ + " with scrim: " + getScrimName(mScrimBehind) + " and tint: "
+ + Integer.toHexString(mScrimBehind.getTint()),
+ behind, mScrimBehind.getTint() != Color.TRANSPARENT);
+
+ Assert.assertEquals("Tint test failed at state " + mScrimController.getState()
+ + " with scrim: " + getScrimName(mScrimForBubble) + " and tint: "
+ + Integer.toHexString(mScrimForBubble.getTint()),
+ bubble, mScrimForBubble.getTint() != Color.TRANSPARENT);
}
- private void assertScrimVisibility(int inFront, int behind) {
- boolean inFrontVisible = inFront != ScrimController.VISIBILITY_FULLY_TRANSPARENT;
- boolean behindVisible = behind != ScrimController.VISIBILITY_FULLY_TRANSPARENT;
- Assert.assertEquals("Unexpected front scrim visibility. Alpha is "
- + mScrimInFront.getViewAlpha(), inFrontVisible, mScrimInFront.getViewAlpha() > 0);
- Assert.assertEquals("Unexpected back scrim visibility. Alpha is "
- + mScrimBehind.getViewAlpha(), behindVisible, mScrimBehind.getViewAlpha() > 0);
-
- final int visibility;
- if (inFront == VISIBILITY_FULLY_OPAQUE || behind == VISIBILITY_FULLY_OPAQUE) {
- visibility = VISIBILITY_FULLY_OPAQUE;
- } else if (inFront > VISIBILITY_FULLY_TRANSPARENT || behind > VISIBILITY_FULLY_TRANSPARENT) {
- visibility = VISIBILITY_SEMI_TRANSPARENT;
- } else {
- visibility = VISIBILITY_FULLY_TRANSPARENT;
+ private String getScrimName(ScrimView scrim) {
+ if (scrim == mScrimInFront) {
+ return "front";
+ } else if (scrim == mScrimBehind) {
+ return "back";
+ } else if (scrim == mScrimForBubble) {
+ return "bubble";
}
- Assert.assertEquals("Invalid visibility.", visibility, mScrimVisibility);
+ return "unknown_scrim";
+ }
+
+ private void assertScrimAlpha(int front, int behind, int bubble) {
+ // Check single scrim visibility.
+ Assert.assertEquals("Unexpected front scrim alpha: "
+ + mScrimInFront.getViewAlpha(),
+ front != TRANSPARENT /* expected */,
+ mScrimInFront.getViewAlpha() > TRANSPARENT /* actual */);
+
+ Assert.assertEquals("Unexpected back scrim alpha: "
+ + mScrimBehind.getViewAlpha(),
+ behind != TRANSPARENT /* expected */,
+ mScrimBehind.getViewAlpha() > TRANSPARENT /* actual */);
+
+ Assert.assertEquals(
+ "Unexpected bubble scrim alpha: "
+ + mScrimForBubble.getViewAlpha(), /* message */
+ bubble != TRANSPARENT /* expected */,
+ mScrimForBubble.getViewAlpha() > TRANSPARENT /* actual */);
+
+ // Check combined scrim visibility.
+ final int visibility;
+ if (front == OPAQUE || behind == OPAQUE || bubble == OPAQUE) {
+ visibility = OPAQUE;
+ } else if (front > TRANSPARENT || behind > TRANSPARENT || bubble > TRANSPARENT) {
+ visibility = SEMI_TRANSPARENT;
+ } else {
+ visibility = TRANSPARENT;
+ }
+ Assert.assertEquals("Invalid visibility.",
+ visibility /* expected */,
+ mScrimVisibility);
}
/**
@@ -646,11 +751,12 @@
boolean mOnPreDrawCalled;
SynchronousScrimController(ScrimView scrimBehind, ScrimView scrimInFront,
+ ScrimView scrimForBubble,
TriConsumer<ScrimState, Float, GradientColors> scrimStateListener,
Consumer<Integer> scrimVisibleListener, DozeParameters dozeParameters,
AlarmManager alarmManager) {
- super(scrimBehind, scrimInFront, scrimStateListener, scrimVisibleListener,
- dozeParameters, alarmManager);
+ super(scrimBehind, scrimInFront, scrimForBubble, scrimStateListener,
+ scrimVisibleListener, dozeParameters, alarmManager);
}
@Override
@@ -661,13 +767,14 @@
void finishAnimationsImmediately() {
boolean[] animationFinished = {false};
- setOnAnimationFinished(()-> animationFinished[0] = true);
+ setOnAnimationFinished(() -> animationFinished[0] = true);
// Execute code that will trigger animations.
onPreDraw();
// Force finish all animations.
mLooper.processAllMessages();
endAnimation(mScrimBehind, TAG_KEY_ANIM);
endAnimation(mScrimInFront, TAG_KEY_ANIM);
+ endAnimation(mScrimForBubble, TAG_KEY_ANIM);
if (!animationFinished[0]) {
throw new IllegalStateException("Animation never finished");
@@ -705,6 +812,7 @@
/**
* Do not wait for a frame since we're in a test environment.
+ *
* @param callback What to execute.
*/
@Override
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
index cffd57b..ea3a742 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
@@ -208,7 +208,8 @@
mNotificationInterruptionStateProvider =
new TestableNotificationInterruptionStateProvider(mContext, mPowerManager,
- mDreamManager, mAmbientDisplayConfiguration);
+ mDreamManager, mAmbientDisplayConfiguration, mNotificationFilter,
+ mStatusBarStateController);
mDependency.injectTestDependency(NotificationInterruptionStateProvider.class,
mNotificationInterruptionStateProvider);
mDependency.injectMockDependency(NavigationBarController.class);
@@ -870,8 +871,11 @@
Context context,
PowerManager powerManager,
IDreamManager dreamManager,
- AmbientDisplayConfiguration ambientDisplayConfiguration) {
- super(context, powerManager, dreamManager, ambientDisplayConfiguration);
+ AmbientDisplayConfiguration ambientDisplayConfiguration,
+ NotificationFilter filter,
+ StatusBarStateController controller) {
+ super(context, powerManager, dreamManager, ambientDisplayConfiguration, filter,
+ controller);
mUseHeadsUp = true;
}
}
diff --git a/services/core/java/com/android/server/wm/ActivityStack.java b/services/core/java/com/android/server/wm/ActivityStack.java
index 74c3069..d902454 100644
--- a/services/core/java/com/android/server/wm/ActivityStack.java
+++ b/services/core/java/com/android/server/wm/ActivityStack.java
@@ -5441,6 +5441,7 @@
task.cleanUpResourcesForDestroy();
}
+ final ActivityDisplay display = getDisplay();
if (mTaskHistory.isEmpty()) {
if (DEBUG_STACK) Slog.i(TAG_STACK, "removeTask: removing stack=" + this);
// We only need to adjust focused stack if this stack is in focus and we are not in the
@@ -5449,11 +5450,11 @@
&& mRootActivityContainer.isTopDisplayFocusedStack(this)) {
String myReason = reason + " leftTaskHistoryEmpty";
if (!inMultiWindowMode() || adjustFocusToNextFocusableStack(myReason) == null) {
- getDisplay().moveHomeStackToFront(myReason);
+ display.moveHomeStackToFront(myReason);
}
}
if (isAttached()) {
- getDisplay().positionChildAtBottom(this);
+ display.positionChildAtBottom(this);
}
if (!isActivityTypeHome() || !isAttached()) {
remove();
@@ -5466,6 +5467,9 @@
if (inPinnedWindowingMode()) {
mService.getTaskChangeNotificationController().notifyActivityUnpinned();
}
+ if (display.isSingleTaskInstance()) {
+ mService.notifySingleTaskDisplayEmpty(display.mDisplayId);
+ }
}
TaskRecord createTaskRecord(int taskId, ActivityInfo info, Intent intent,
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 9fc278e..277da06 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -6040,6 +6040,10 @@
return allUids.contains(uid);
}
+ void notifySingleTaskDisplayEmpty(int displayId) {
+ mTaskChangeNotificationController.notifySingleTaskDisplayEmpty(displayId);
+ }
+
final class H extends Handler {
static final int REPORT_TIME_TRACKER_MSG = 1;
diff --git a/services/core/java/com/android/server/wm/TaskChangeNotificationController.java b/services/core/java/com/android/server/wm/TaskChangeNotificationController.java
index 27175c7..80470c6 100644
--- a/services/core/java/com/android/server/wm/TaskChangeNotificationController.java
+++ b/services/core/java/com/android/server/wm/TaskChangeNotificationController.java
@@ -55,6 +55,7 @@
private static final int NOTIFY_SIZE_COMPAT_MODE_ACTIVITY_CHANGED_MSG = 20;
private static final int NOTIFY_BACK_PRESSED_ON_TASK_ROOT = 21;
private static final int NOTIFY_SINGLE_TASK_DISPLAY_DRAWN = 22;
+ private static final int NOTIFY_SINGLE_TASK_DISPLAY_EMPTY = 23;
// Delay in notifying task stack change listeners (in millis)
private static final int NOTIFY_TASK_STACK_CHANGE_LISTENERS_DELAY = 100;
@@ -159,6 +160,10 @@
l.onSingleTaskDisplayDrawn(m.arg1);
};
+ private final TaskStackConsumer mNotifySingleTaskDisplayEmpty = (l, m) -> {
+ l.onSingleTaskDisplayEmpty(m.arg1);
+ };
+
@FunctionalInterface
public interface TaskStackConsumer {
void accept(ITaskStackListener t, Message m) throws RemoteException;
@@ -241,6 +246,9 @@
case NOTIFY_SINGLE_TASK_DISPLAY_DRAWN:
forAllRemoteListeners(mNotifySingleTaskDisplayDrawn, msg);
break;
+ case NOTIFY_SINGLE_TASK_DISPLAY_EMPTY:
+ forAllRemoteListeners(mNotifySingleTaskDisplayEmpty, msg);
+ break;
}
}
}
@@ -495,4 +503,15 @@
forAllLocalListeners(mNotifySingleTaskDisplayDrawn, msg);
msg.sendToTarget();
}
+
+ /**
+ * Notify listeners that the last task is removed from a single task display.
+ */
+ void notifySingleTaskDisplayEmpty(int displayId) {
+ final Message msg = mHandler.obtainMessage(
+ NOTIFY_SINGLE_TASK_DISPLAY_EMPTY,
+ displayId, 0 /* unused */);
+ forAllLocalListeners(mNotifySingleTaskDisplayEmpty, msg);
+ msg.sendToTarget();
+ }
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskStackChangedListenerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskStackChangedListenerTest.java
index 19fd93fe..6e41118 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskStackChangedListenerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskStackChangedListenerTest.java
@@ -271,6 +271,54 @@
waitForCallback(singleTaskDisplayDrawnLatch);
}
+ @Test
+ public void testSingleTaskDisplayEmpty() throws Exception {
+ final Instrumentation instrumentation = getInstrumentation();
+
+ final CountDownLatch activityViewReadyLatch = new CountDownLatch(1);
+ final CountDownLatch activityViewDestroyedLatch = new CountDownLatch(1);
+ final CountDownLatch singleTaskDisplayDrawnLatch = new CountDownLatch(1);
+ final CountDownLatch singleTaskDisplayEmptyLatch = new CountDownLatch(1);
+
+ registerTaskStackChangedListener(new TaskStackListener() {
+ @Override
+ public void onSingleTaskDisplayDrawn(int displayId) throws RemoteException {
+ singleTaskDisplayDrawnLatch.countDown();
+ }
+ @Override
+ public void onSingleTaskDisplayEmpty(int displayId)
+ throws RemoteException {
+ singleTaskDisplayEmptyLatch.countDown();
+ }
+ });
+ final ActivityViewTestActivity activity =
+ (ActivityViewTestActivity) startTestActivity(ActivityViewTestActivity.class);
+ final ActivityView activityView = activity.getActivityView();
+ activityView.setCallback(new ActivityView.StateCallback() {
+ @Override
+ public void onActivityViewReady(ActivityView view) {
+ activityViewReadyLatch.countDown();
+ }
+
+ @Override
+ public void onActivityViewDestroyed(ActivityView view) {
+ activityViewDestroyedLatch.countDown();
+ }
+ });
+ waitForCallback(activityViewReadyLatch);
+
+ final Context context = instrumentation.getContext();
+ Intent intent = new Intent(context, ActivityInActivityView.class);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
+ activityView.startActivity(intent);
+ waitForCallback(singleTaskDisplayDrawnLatch);
+ assertEquals(1, singleTaskDisplayEmptyLatch.getCount());
+
+ activityView.release();
+ waitForCallback(activityViewDestroyedLatch);
+ waitForCallback(singleTaskDisplayEmptyLatch);
+ }
+
/**
* Starts the provided activity and returns the started instance.
*/