Merge changes from topic 'headsup'
* changes:
Finishing up heads up changes
Added the heads up scrim back
Fixed a bug where a notification could not be collapsed
Made ranking consistent with heads Up manager
Fixed a bug where the intrinsic height was not updated
Fixed more heads up bugs
More Heads Up bug fixes
Adapted the interpolator of the heads up appear motion
Treating headsUpViews now as real notification citizen
Handling a few more border cases with HUNs
Integrate Heads-up notifications into the shade
Fixed a bug with notification clipping
diff --git a/packages/SystemUI/res/anim/heads_up_enter.xml b/packages/SystemUI/res/anim/heads_up_enter.xml
deleted file mode 100644
index 59eef42..0000000
--- a/packages/SystemUI/res/anim/heads_up_enter.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<set xmlns:android="http://schemas.android.com/apk/res/android"
- >
- <translate
- android:interpolator="@android:interpolator/overshoot"
- android:fromYDelta="-50%" android:toYDelta="0"
- android:duration="@android:integer/config_shortAnimTime" />
- <alpha
- android:interpolator="@android:interpolator/decelerate_quad"
- android:fromAlpha="0.0" android:toAlpha="1.0"
- android:duration="@android:integer/config_shortAnimTime" />
-</set>
diff --git a/packages/SystemUI/res/anim/heads_up_exit.xml b/packages/SystemUI/res/anim/heads_up_exit.xml
deleted file mode 100644
index 2cad8f6..0000000
--- a/packages/SystemUI/res/anim/heads_up_exit.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<set xmlns:android="http://schemas.android.com/apk/res/android"
- >
- <translate
- android:interpolator="@android:interpolator/overshoot"
- android:fromYDelta="0" android:toYDelta="-50%"
- android:duration="@android:integer/config_shortAnimTime" />
- <alpha
- android:interpolator="@android:interpolator/accelerate_quad"
- android:fromAlpha="1.0" android:toAlpha="0.0"
- android:duration="@android:integer/config_shortAnimTime" />
-</set>
diff --git a/packages/SystemUI/res/layout/heads_up.xml b/packages/SystemUI/res/layout/heads_up.xml
deleted file mode 100644
index 650ee5d..0000000
--- a/packages/SystemUI/res/layout/heads_up.xml
+++ /dev/null
@@ -1,33 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2014 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<!-- extends FrameLayout -->
-<com.android.systemui.statusbar.policy.HeadsUpNotificationView
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_height="match_parent"
- android:layout_width="match_parent"
- android:background="@drawable/heads_up_scrim">
-
- <FrameLayout
- android:layout_width="@dimen/notification_panel_width"
- android:layout_height="wrap_content"
- android:layout_gravity="@integer/notification_panel_layout_gravity"
- android:paddingStart="@dimen/notification_side_padding"
- android:paddingEnd="@dimen/notification_side_padding"
- android:elevation="8dp"
- android:id="@+id/content_holder" />
-
-</com.android.systemui.statusbar.policy.HeadsUpNotificationView>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/super_status_bar.xml b/packages/SystemUI/res/layout/super_status_bar.xml
index 532e1b7..539aabf 100644
--- a/packages/SystemUI/res/layout/super_status_bar.xml
+++ b/packages/SystemUI/res/layout/super_status_bar.xml
@@ -46,6 +46,13 @@
android:layout_height="match_parent"
android:importantForAccessibility="no" />
+ <com.android.systemui.statusbar.AlphaOptimizedView
+ android:id="@+id/heads_up_scrim"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/heads_up_scrim_height"
+ android:background="@drawable/heads_up_scrim"
+ android:importantForAccessibility="no"/>
+
<include layout="@layout/status_bar"
android:layout_width="match_parent"
android:layout_height="@dimen/status_bar_height" />
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 2e9e9f7..051d233 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -147,7 +147,7 @@
<integer name="heads_up_default_snooze_length_ms">60000</integer>
<!-- Minimum display time for a heads up notification, in milliseconds. -->
- <integer name="heads_up_notification_minimum_time">3000</integer>
+ <integer name="heads_up_notification_minimum_time">2000</integer>
<!-- milliseconds before the heads up notification accepts touches. -->
<integer name="heads_up_sensitivity_delay">700</integer>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index b6ff1ce..9e084a0 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -330,7 +330,7 @@
keyguard_clock_height_fraction_* for the difference between min and max.-->
<dimen name="keyguard_clock_notifications_margin_min">24dp</dimen>
<dimen name="keyguard_clock_notifications_margin_max">36dp</dimen>
- <dimen name="heads_up_window_height">250dp</dimen>
+ <dimen name="heads_up_scrim_height">250dp</dimen>
<!-- The minimum amount the user needs to swipe to go to the camera / phone. -->
<dimen name="keyguard_min_swipe_amount">110dp</dimen>
diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml
index 3fc75d2..6d84727 100644
--- a/packages/SystemUI/res/values/ids.xml
+++ b/packages/SystemUI/res/values/ids.xml
@@ -37,6 +37,8 @@
<item type="id" name="doze_saved_filter_tag"/>
<item type="id" name="qs_icon_tag"/>
<item type="id" name="scrim"/>
+ <item type="id" name="hun_scrim_alpha_start"/>
+ <item type="id" name="hun_scrim_alpha_end"/>
<item type="id" name="notification_power"/>
<item type="id" name="notification_screenshot"/>
<item type="id" name="notification_hidden"/>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 974cc48..87f9ca2 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -205,11 +205,6 @@
<style name="Animation.StatusBar">
</style>
- <style name="Animation.StatusBar.HeadsUp">
- <item name="android:windowEnterAnimation">@anim/heads_up_enter</item>
- <item name="android:windowExitAnimation">@anim/heads_up_exit</item>
- </style>
-
<style name="systemui_theme" parent="@android:style/Theme.DeviceDefault">
<item name="android:colorPrimary">@color/system_primary_color</item>
<item name="android:colorControlActivated">@color/system_accent_color</item>
diff --git a/packages/SystemUI/src/com/android/systemui/ExpandHelper.java b/packages/SystemUI/src/com/android/systemui/ExpandHelper.java
index bc7f745..fece07f 100644
--- a/packages/SystemUI/src/com/android/systemui/ExpandHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/ExpandHelper.java
@@ -322,6 +322,10 @@
isInside(mScrollAdapter.getHostView(), x, y)
&& mScrollAdapter.isScrolledToTop();
mResizedView = findView(x, y);
+ if (mResizedView != null && !mCallback.canChildBeExpanded(mResizedView)) {
+ mResizedView = null;
+ mWatchingForPull = false;
+ }
mInitialTouchY = ev.getY();
break;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
index ee607a7..9f86475 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
@@ -96,7 +96,7 @@
import com.android.systemui.statusbar.phone.NavigationBarView;
import com.android.systemui.statusbar.phone.NotificationGroupManager;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
-import com.android.systemui.statusbar.policy.HeadsUpNotificationView;
+import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.statusbar.policy.PreviewInflater;
import com.android.systemui.statusbar.policy.RemoteInputView;
import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
@@ -130,9 +130,6 @@
protected static final int MSG_CANCEL_PRELOAD_RECENT_APPS = 1023;
protected static final int MSG_SHOW_NEXT_AFFILIATED_TASK = 1024;
protected static final int MSG_SHOW_PREV_AFFILIATED_TASK = 1025;
- protected static final int MSG_SHOW_HEADS_UP = 1028;
- protected static final int MSG_HIDE_HEADS_UP = 1029;
- protected static final int MSG_ESCALATE_HEADS_UP = 1030;
protected static final boolean ENABLE_HEADS_UP = true;
// scores above this threshold should be displayed in heads up mode.
@@ -158,8 +155,7 @@
protected NotificationGroupManager mGroupManager = new NotificationGroupManager();
// for heads up notifications
- protected HeadsUpNotificationView mHeadsUpNotificationView;
- protected int mHeadsUpNotificationDecay;
+ protected HeadsUpManager mHeadsUpManager;
protected int mCurrentUserId = 0;
final protected SparseArray<UserInfo> mCurrentProfiles = new SparseArray<UserInfo>();
@@ -446,9 +442,8 @@
@Override
public void run() {
processForRemoteInput(sbn.getNotification());
- Notification n = sbn.getNotification();
- boolean isUpdate = mNotificationData.get(sbn.getKey()) != null
- || isHeadsUp(sbn.getKey());
+ String key = sbn.getKey();
+ boolean isUpdate = mNotificationData.get(key) != null;
// In case we don't allow child notifications, we ignore children of
// notifications that have a summary, since we're not going to show them
@@ -462,7 +457,7 @@
// Remove existing notification to avoid stale data.
if (isUpdate) {
- removeNotification(sbn.getKey(), rankingMap);
+ removeNotification(key, rankingMap);
} else {
mNotificationData.updateRanking(rankingMap);
}
@@ -693,13 +688,11 @@
}
private void setHeadsUpUser(int newUserId) {
- if (mHeadsUpNotificationView != null) {
- mHeadsUpNotificationView.setUser(newUserId);
- }
+ mHeadsUpManager.setUser(newUserId);
}
public boolean isHeadsUp(String key) {
- return mHeadsUpNotificationView != null && mHeadsUpNotificationView.isShowing(key);
+ return mHeadsUpManager.isHeadsUp(key);
}
@Override // NotificationData.Environment
@@ -758,8 +751,7 @@
protected View updateNotificationVetoButton(View row, StatusBarNotification n) {
View vetoButton = row.findViewById(R.id.veto);
- if (n.isClearable() || (mHeadsUpNotificationView.getEntry() != null
- && mHeadsUpNotificationView.getEntry().row == row)) {
+ if (n.isClearable()) {
final String _pkg = n.getPackageName();
final String _tag = n.getTag();
final int _id = n.getId();
@@ -1002,9 +994,6 @@
}
}
- public void onHeadsUpDismissed() {
- }
-
@Override
public void showRecentApps(boolean triggeredFromAltTab) {
int msg = MSG_SHOW_RECENT_APPS;
@@ -1141,13 +1130,10 @@
// Do nothing
}
- public abstract void scheduleHeadsUpDecay(long delay);
-
- public abstract void scheduleHeadsUpOpen();
-
- public abstract void scheduleHeadsUpClose();
-
- public abstract void scheduleHeadsUpEscalation();
+ /**
+ * if the interrupting notification had a fullscreen intent, fire it now.
+ */
+ public abstract void escalateHeadsUp();
/**
* Save the current "public" (locked and secure) state of the lockscreen.
@@ -1238,15 +1224,7 @@
protected void workAroundBadLayerDrawableOpacity(View v) {
}
- protected boolean inflateViews(NotificationData.Entry entry, ViewGroup parent) {
- return inflateViews(entry, parent, false);
- }
-
- protected boolean inflateViewsForHeadsUp(NotificationData.Entry entry, ViewGroup parent) {
- return inflateViews(entry, parent, true);
- }
-
- private boolean inflateViews(NotificationData.Entry entry, ViewGroup parent, boolean isHeadsUp) {
+ protected boolean inflateViews(Entry entry, ViewGroup parent) {
PackageManager pmUser = getPackageManagerForUser(
entry.notification.getUser().getIdentifier());
@@ -1254,12 +1232,7 @@
final StatusBarNotification sbn = entry.notification;
RemoteViews contentView = sbn.getNotification().contentView;
RemoteViews bigContentView = sbn.getNotification().bigContentView;
-
- if (isHeadsUp) {
- maxHeight =
- mContext.getResources().getDimensionPixelSize(R.dimen.notification_mid_height);
- bigContentView = sbn.getNotification().headsUpContentView;
- }
+ RemoteViews headsUpContentView = sbn.getNotification().headsUpContentView;
if (contentView == null) {
return false;
@@ -1284,7 +1257,6 @@
hasUserChangedExpansion = row.hasUserChangedExpansion();
userExpanded = row.isUserExpanded();
userLocked = row.isUserLocked();
- entry.row.setHeadsUp(isHeadsUp);
entry.reset();
if (hasUserChangedExpansion) {
row.setUserExpanded(userExpanded);
@@ -1307,10 +1279,8 @@
// NB: the large icon is now handled entirely by the template
// bind the click event to the content area
- NotificationContentView expanded =
- (NotificationContentView) row.findViewById(R.id.expanded);
- NotificationContentView expandedPublic =
- (NotificationContentView) row.findViewById(R.id.expandedPublic);
+ NotificationContentView contentContainer = row.getPrivateLayout();
+ NotificationContentView contentContainerPublic = row.getPublicLayout();
row.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
if (ENABLE_REMOTE_INPUT) {
@@ -1328,11 +1298,16 @@
// set up the adaptive layout
View contentViewLocal = null;
View bigContentViewLocal = null;
+ View headsUpContentViewLocal = null;
try {
- contentViewLocal = contentView.apply(mContext, expanded,
+ contentViewLocal = contentView.apply(mContext, contentContainer,
mOnClickHandler);
if (bigContentView != null) {
- bigContentViewLocal = bigContentView.apply(mContext, expanded,
+ bigContentViewLocal = bigContentView.apply(mContext, contentContainer,
+ mOnClickHandler);
+ }
+ if (headsUpContentView != null) {
+ headsUpContentViewLocal = headsUpContentView.apply(mContext, contentContainer,
mOnClickHandler);
}
}
@@ -1344,23 +1319,27 @@
if (contentViewLocal != null) {
contentViewLocal.setIsRootNamespace(true);
- expanded.setContractedChild(contentViewLocal);
+ contentContainer.setContractedChild(contentViewLocal);
}
if (bigContentViewLocal != null) {
bigContentViewLocal.setIsRootNamespace(true);
- expanded.setExpandedChild(bigContentViewLocal);
+ contentContainer.setExpandedChild(bigContentViewLocal);
+ }
+ if (headsUpContentViewLocal != null) {
+ headsUpContentViewLocal.setIsRootNamespace(true);
+ contentContainer.setHeadsUpChild(headsUpContentViewLocal);
}
// now the public version
View publicViewLocal = null;
if (publicNotification != null) {
try {
- publicViewLocal = publicNotification.contentView.apply(mContext, expandedPublic,
+ publicViewLocal = publicNotification.contentView.apply(mContext, contentContainerPublic,
mOnClickHandler);
if (publicViewLocal != null) {
publicViewLocal.setIsRootNamespace(true);
- expandedPublic.setContractedChild(publicViewLocal);
+ contentContainerPublic.setContractedChild(publicViewLocal);
}
}
catch (RuntimeException e) {
@@ -1382,9 +1361,9 @@
// Add a basic notification template
publicViewLocal = LayoutInflater.from(mContext).inflate(
R.layout.notification_public_default,
- expandedPublic, false);
+ contentContainerPublic, false);
publicViewLocal.setIsRootNamespace(true);
- expandedPublic.setContractedChild(publicViewLocal);
+ contentContainerPublic.setContractedChild(publicViewLocal);
final TextView title = (TextView) publicViewLocal.findViewById(R.id.title);
try {
@@ -1520,13 +1499,8 @@
stripped.contentView = null;
stripped.extras.putBoolean("android.rebuild.bigView", true);
stripped.bigContentView = null;
-
- // Don't create the HUN input view for now because input doesn't work there yet.
- // TODO: Enable once HUNs can take remote input correctly.
- if (false) {
- stripped.extras.putBoolean("android.rebuild.hudView", true);
- stripped.headsUpContentView = null;
- }
+ stripped.extras.putBoolean("android.rebuild.hudView", true);
+ stripped.headsUpContentView = null;
Notification rebuilt = Notification.Builder.rebuild(mContext, stripped);
@@ -1558,16 +1532,23 @@
}
// See if we have somewhere to put that remote input
- ViewGroup actionContainer = null;
- if (remoteInput != null && entry.expandedBig != null) {
- View actionContainerCandidate = entry.expandedBig
- .findViewById(com.android.internal.R.id.actions);
- if (actionContainerCandidate instanceof ViewGroup) {
- actionContainer = (ViewGroup) actionContainerCandidate;
+ if (remoteInput != null) {
+ if (entry.expandedBig != null) {
+ inflateRemoteInput(entry.expandedBig, remoteInput, actions);
+ }
+ View headsUpChild = entry.row.getPrivateLayout().getHeadsUpChild();
+ if (headsUpChild != null) {
+ inflateRemoteInput(headsUpChild, remoteInput, actions);
}
}
- if (actionContainer != null) {
+ }
+
+ private void inflateRemoteInput(View view, RemoteInput remoteInput,
+ Notification.Action[] actions) {
+ View actionContainerCandidate = view.findViewById(com.android.internal.R.id.actions);
+ if (actionContainerCandidate instanceof ViewGroup) {
+ ViewGroup actionContainer = (ViewGroup) actionContainerCandidate;
actionContainer.removeAllViews();
actionContainer.addView(
RemoteInputView.inflate(mContext, actionContainer, actions[0], remoteInput));
@@ -1597,12 +1578,12 @@
mCurrentUserId);
dismissKeyguardThenExecute(new OnDismissAction() {
public boolean onDismiss() {
- if (mNotificationKey.equals(mHeadsUpNotificationView.getKey())) {
+ if (mHeadsUpManager.isHeadsUp(mNotificationKey)) {
// Release the HUN notification to the shade.
//
// In most cases, when FLAG_AUTO_CANCEL is set, the notification will
// become canceled shortly by NoMan, but we can't assume that.
- mHeadsUpNotificationView.releaseImmediately();
+ mHeadsUpManager.releaseImmediately(mNotificationKey);
}
new Thread() {
@Override
@@ -1889,26 +1870,120 @@
if (DEBUG) Log.d(TAG, "updateNotification(" + notification + ")");
final String key = notification.getKey();
- boolean wasHeadsUp = isHeadsUp(key);
- Entry oldEntry;
- if (wasHeadsUp) {
- oldEntry = mHeadsUpNotificationView.getEntry();
- } else {
- oldEntry = mNotificationData.get(key);
- }
- if (oldEntry == null) {
+ Entry entry = mNotificationData.get(key);
+ if (entry == null) {
return;
}
- final StatusBarNotification oldNotification = oldEntry.notification;
+ Notification n = notification.getNotification();
+ if (DEBUG) {
+ logUpdate(entry, n);
+ }
+ boolean applyInPlace = shouldApplyInPlace(entry, n);
+ final boolean shouldInterrupt = shouldInterrupt(notification);
+ final boolean alertAgain = alertAgain(entry, n);
+ entry.notification = notification;
+ mGroupManager.onEntryUpdated(entry, entry.notification);
+
+ boolean updateSuccessful = false;
+ if (applyInPlace) {
+ // We can just reapply the notifications in place
+ if (DEBUG) Log.d(TAG, "reusing notification for key: " + key);
+ try {
+ if (entry.icon != null) {
+ // Update the icon
+ final StatusBarIcon ic = new StatusBarIcon(notification.getPackageName(),
+ notification.getUser(),
+ n.icon,
+ n.iconLevel,
+ n.number,
+ n.tickerText);
+ entry.icon.setNotification(n);
+ if (!entry.icon.set(ic)) {
+ handleNotificationError(notification, "Couldn't update icon: " + ic);
+ return;
+ }
+ }
+ updateNotificationViews(entry, notification);
+ updateSuccessful = true;
+ }
+ catch (RuntimeException e) {
+ // It failed to add cleanly. Log, and remove the view from the panel.
+ Log.w(TAG, "Couldn't reapply views for package " + n.contentView.getPackage(), e);
+ }
+ }
+ if (!updateSuccessful) {
+ if (DEBUG) Log.d(TAG, "not reusing notification for key: " + key);
+ final StatusBarIcon ic = new StatusBarIcon(notification.getPackageName(),
+ notification.getUser(),
+ n.icon,
+ n.iconLevel,
+ n.number,
+ n.tickerText);
+ entry.icon.setNotification(n);
+ entry.icon.set(ic);
+ inflateViews(entry, mStackScroller);
+ }
+ updateHeadsUp(key, entry, shouldInterrupt, alertAgain);
+ mNotificationData.updateRanking(ranking);
+ updateNotifications();
+
+ // Update the veto button accordingly (and as a result, whether this row is
+ // swipe-dismissable)
+ updateNotificationVetoButton(entry.row, notification);
+
+ // Is this for you?
+ boolean isForCurrentUser = isNotificationForCurrentProfiles(notification);
+ if (DEBUG) Log.d(TAG, "notification is " + (isForCurrentUser ? "" : "not ") + "for you");
+
+ // Recalculate the position of the sliding windows and the titles.
+ setAreThereNotifications();
+ }
+
+ private void updateHeadsUp(String key, Entry entry, boolean shouldInterrupt,
+ boolean alertAgain) {
+ final boolean wasHeadsUp = isHeadsUp(key);
+ if (wasHeadsUp) {
+ mHeadsUpManager.updateNotification(entry, alertAgain);
+ if (!shouldInterrupt) {
+ // We don't want this to be interrupting anymore, lets remove it
+ mHeadsUpManager.removeNotification(key);
+ }
+ } else if (shouldInterrupt && alertAgain) {
+ // This notification was updated to be a heads-up, show it!
+ mHeadsUpManager.showNotification(entry);
+ }
+ }
+
+ private void logUpdate(Entry oldEntry, Notification n) {
+ StatusBarNotification oldNotification = oldEntry.notification;
+ Log.d(TAG, "old notification: when=" + oldNotification.getNotification().when
+ + " ongoing=" + oldNotification.isOngoing()
+ + " expanded=" + oldEntry.expanded
+ + " contentView=" + oldNotification.getNotification().contentView
+ + " bigContentView=" + oldNotification.getNotification().bigContentView
+ + " publicView=" + oldNotification.getNotification().publicVersion
+ + " rowParent=" + oldEntry.row.getParent());
+ Log.d(TAG, "new notification: when=" + n.when
+ + " ongoing=" + oldNotification.isOngoing()
+ + " contentView=" + n.contentView
+ + " bigContentView=" + n.bigContentView
+ + " publicView=" + n.publicVersion);
+ }
+
+ /**
+ * @return whether we can just reapply the RemoteViews in place when it is updated
+ */
+ private boolean shouldApplyInPlace(Entry entry, Notification n) {
+ StatusBarNotification oldNotification = entry.notification;
// XXX: modify when we do something more intelligent with the two content views
final RemoteViews oldContentView = oldNotification.getNotification().contentView;
- Notification n = notification.getNotification();
final RemoteViews contentView = n.contentView;
final RemoteViews oldBigContentView = oldNotification.getNotification().bigContentView;
final RemoteViews bigContentView = n.bigContentView;
- final RemoteViews oldHeadsUpContentView = oldNotification.getNotification().headsUpContentView;
+ final RemoteViews oldHeadsUpContentView
+ = oldNotification.getNotification().headsUpContentView;
final RemoteViews headsUpContentView = n.headsUpContentView;
final Notification oldPublicNotification = oldNotification.getNotification().publicVersion;
final RemoteViews oldPublicContentView = oldPublicNotification != null
@@ -1916,34 +1991,15 @@
final Notification publicNotification = n.publicVersion;
final RemoteViews publicContentView = publicNotification != null
? publicNotification.contentView : null;
-
- if (DEBUG) {
- Log.d(TAG, "old notification: when=" + oldNotification.getNotification().when
- + " ongoing=" + oldNotification.isOngoing()
- + " expanded=" + oldEntry.expanded
- + " contentView=" + oldContentView
- + " bigContentView=" + oldBigContentView
- + " publicView=" + oldPublicContentView
- + " rowParent=" + oldEntry.row.getParent());
- Log.d(TAG, "new notification: when=" + n.when
- + " ongoing=" + oldNotification.isOngoing()
- + " contentView=" + contentView
- + " bigContentView=" + bigContentView
- + " publicView=" + publicContentView);
- }
-
- // Can we just reapply the RemoteViews in place?
-
- // 1U is never null
- boolean contentsUnchanged = oldEntry.expanded != null
+ boolean contentsUnchanged = entry.expanded != null
&& contentView.getPackage() != null
&& oldContentView.getPackage() != null
&& oldContentView.getPackage().equals(contentView.getPackage())
&& oldContentView.getLayoutId() == contentView.getLayoutId();
// large view may be null
boolean bigContentsUnchanged =
- (oldEntry.getBigContentView() == null && bigContentView == null)
- || ((oldEntry.getBigContentView() != null && bigContentView != null)
+ (entry.getBigContentView() == null && bigContentView == null)
+ || ((entry.getBigContentView() != null && bigContentView != null)
&& bigContentView.getPackage() != null
&& oldBigContentView.getPackage() != null
&& oldBigContentView.getPackage().equals(bigContentView.getPackage())
@@ -1962,131 +2018,14 @@
&& oldPublicContentView.getPackage() != null
&& oldPublicContentView.getPackage().equals(publicContentView.getPackage())
&& oldPublicContentView.getLayoutId() == publicContentView.getLayoutId());
-
- final boolean shouldInterrupt = shouldInterrupt(notification);
- final boolean alertAgain = shouldInterrupt && alertAgain(oldEntry, n);
- boolean updateSuccessful = false;
- if (contentsUnchanged && bigContentsUnchanged && headsUpContentsUnchanged
- && publicUnchanged) {
- if (DEBUG) Log.d(TAG, "reusing notification for key: " + key);
- oldEntry.notification = notification;
- mGroupManager.onEntryUpdated(oldEntry, oldNotification);
- try {
- if (oldEntry.icon != null) {
- // Update the icon
- final StatusBarIcon ic = new StatusBarIcon(notification.getPackageName(),
- notification.getUser(),
- n.icon,
- n.iconLevel,
- n.number,
- n.tickerText);
- oldEntry.icon.setNotification(n);
- if (!oldEntry.icon.set(ic)) {
- handleNotificationError(notification, "Couldn't update icon: " + ic);
- return;
- }
- }
-
- if (wasHeadsUp) {
- // Release may hang on to the views for a bit, so we should always update them.
- updateHeadsUpViews(oldEntry, notification);
- mHeadsUpNotificationView.updateNotification(oldEntry, alertAgain);
- if (!shouldInterrupt) {
- // we updated the notification above, so release to build a new shade entry
- mHeadsUpNotificationView.release();
- return;
- }
- } else {
- if (shouldInterrupt && alertAgain) {
- mStackScroller.setRemoveAnimationEnabled(false);
- removeNotificationViews(key, ranking);
- mStackScroller.setRemoveAnimationEnabled(true);
- addNotification(notification, ranking, oldEntry); //this will pop the headsup
- } else {
- updateNotificationViews(oldEntry, notification);
- }
- }
- mNotificationData.updateRanking(ranking);
- updateNotifications();
- updateSuccessful = true;
- }
- catch (RuntimeException e) {
- // It failed to add cleanly. Log, and remove the view from the panel.
- Log.w(TAG, "Couldn't reapply views for package " + contentView.getPackage(), e);
- }
- }
- if (!updateSuccessful) {
- if (DEBUG) Log.d(TAG, "not reusing notification for key: " + key);
- if (wasHeadsUp) {
- if (DEBUG) Log.d(TAG, "rebuilding heads up for key: " + key);
- ViewGroup holder = mHeadsUpNotificationView.getHolder();
- if (inflateViewsForHeadsUp(oldEntry, holder)) {
- mHeadsUpNotificationView.updateNotification(oldEntry, alertAgain);
- } else {
- Log.w(TAG, "Couldn't create new updated headsup for package "
- + contentView.getPackage());
- }
- if (!shouldInterrupt) {
- if (DEBUG) Log.d(TAG, "releasing heads up for key: " + key);
- oldEntry.notification = notification;
- mGroupManager.onEntryUpdated(oldEntry, oldNotification);
- mHeadsUpNotificationView.release();
- return;
- }
- } else {
- if (shouldInterrupt && alertAgain) {
- if (DEBUG) Log.d(TAG, "reposting to invoke heads up for key: " + key);
- mStackScroller.setRemoveAnimationEnabled(false);
- removeNotificationViews(key, ranking);
- mStackScroller.setRemoveAnimationEnabled(true);
- addNotification(notification, ranking, oldEntry); //this will pop the headsup
- } else {
- if (DEBUG) Log.d(TAG, "rebuilding update in place for key: " + key);
- oldEntry.notification = notification;
- mGroupManager.onEntryUpdated(oldEntry, oldNotification);
- final StatusBarIcon ic = new StatusBarIcon(notification.getPackageName(),
- notification.getUser(),
- n.icon,
- n.iconLevel,
- n.number,
- n.tickerText);
- oldEntry.icon.setNotification(n);
- oldEntry.icon.set(ic);
- inflateViews(oldEntry, mStackScroller, wasHeadsUp);
- mNotificationData.updateRanking(ranking);
- updateNotifications();
- }
- }
- }
-
- // Update the veto button accordingly (and as a result, whether this row is
- // swipe-dismissable)
- updateNotificationVetoButton(oldEntry.row, notification);
-
- // Is this for you?
- boolean isForCurrentUser = isNotificationForCurrentProfiles(notification);
- if (DEBUG) Log.d(TAG, "notification is " + (isForCurrentUser ? "" : "not ") + "for you");
-
- // Recalculate the position of the sliding windows and the titles.
- setAreThereNotifications();
+ return contentsUnchanged && bigContentsUnchanged && headsUpContentsUnchanged
+ && publicUnchanged;
}
- private void updateNotificationViews(NotificationData.Entry entry,
- StatusBarNotification notification) {
- updateNotificationViews(entry, notification, false);
- }
-
- private void updateHeadsUpViews(NotificationData.Entry entry,
- StatusBarNotification notification) {
- updateNotificationViews(entry, notification, true);
- }
-
- private void updateNotificationViews(NotificationData.Entry entry,
- StatusBarNotification notification, boolean isHeadsUp) {
+ private void updateNotificationViews(Entry entry, StatusBarNotification notification) {
final RemoteViews contentView = notification.getNotification().contentView;
- final RemoteViews bigContentView = isHeadsUp
- ? notification.getNotification().headsUpContentView
- : notification.getNotification().bigContentView;
+ final RemoteViews bigContentView = notification.getNotification().bigContentView;
+ final RemoteViews headsUpContentView = notification.getNotification().headsUpContentView;
final Notification publicVersion = notification.getNotification().publicVersion;
final RemoteViews publicContentView = publicVersion != null ? publicVersion.contentView
: null;
@@ -2097,6 +2036,10 @@
bigContentView.reapply(mContext, entry.getBigContentView(),
mOnClickHandler);
}
+ View headsUpChild = entry.row.getPrivateLayout().getHeadsUpChild();
+ if (headsUpContentView != null && headsUpChild != null) {
+ headsUpContentView.reapply(mContext, headsUpChild, mOnClickHandler);
+ }
if (publicContentView != null && entry.getPublicContentView() != null) {
publicContentView.reapply(mContext, entry.getPublicContentView(), mOnClickHandler);
}
@@ -2115,10 +2058,8 @@
applyRemoteInput(entry);
}
- protected void notifyHeadsUpScreenOn(boolean screenOn) {
- if (!screenOn) {
- scheduleHeadsUpEscalation();
- }
+ protected void notifyHeadsUpScreenOff() {
+ escalateHeadsUp();
}
private boolean alertAgain(Entry oldEntry, Notification newNotification) {
@@ -2134,7 +2075,7 @@
return false;
}
- if (mHeadsUpNotificationView.isSnoozed(sbn.getPackageName())) {
+ if (mHeadsUpManager.isSnoozed(sbn.getPackageName())) {
return false;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
index 06a174e..33a07d9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
@@ -78,13 +78,14 @@
private NotificationContentView mPublicLayout;
private NotificationContentView mPrivateLayout;
private int mMaxExpandHeight;
+ private int mHeadsUpHeight;
private View mVetoButton;
private boolean mClearable;
private ExpansionLogger mLogger;
private String mLoggingKey;
private boolean mWasReset;
- private NotificationGuts mGuts;
+ private NotificationGuts mGuts;
private StatusBarNotification mStatusBarNotification;
private boolean mIsHeadsUp;
private View mExpandButton;
@@ -108,6 +109,15 @@
!mChildrenExpanded);
}
};
+ private boolean mInShade;
+
+ public NotificationContentView getPrivateLayout() {
+ return mPrivateLayout;
+ }
+
+ public NotificationContentView getPublicLayout() {
+ return mPublicLayout;
+ }
public void setIconAnimationRunning(boolean running) {
setIconAnimationRunning(running, mPublicLayout);
@@ -118,8 +128,10 @@
if (layout != null) {
View contractedChild = layout.getContractedChild();
View expandedChild = layout.getExpandedChild();
+ View headsUpChild = layout.getHeadsUpChild();
setIconAnimationRunningForChild(running, contractedChild);
setIconAnimationRunningForChild(running, expandedChild);
+ setIconAnimationRunningForChild(running, headsUpChild);
}
}
@@ -164,8 +176,17 @@
return mStatusBarNotification;
}
+ public boolean isHeadsUp() {
+ return mIsHeadsUp;
+ }
+
public void setHeadsUp(boolean isHeadsUp) {
+ int intrinsicBefore = getIntrinsicHeight();
mIsHeadsUp = isHeadsUp;
+ mPrivateLayout.setHeadsUp(isHeadsUp);
+ if (intrinsicBefore != getIntrinsicHeight()) {
+ notifyHeightChanged(false /* needsAnimation */);
+ }
}
public void setGroupManager(NotificationGroupManager groupManager) {
@@ -263,6 +284,18 @@
return realActualHeight;
}
+ public void setInShade(boolean inShade) {
+ mInShade = inShade;
+ }
+
+ public boolean isInShade() {
+ return mInShade;
+ }
+
+ public int getHeadsUpHeight() {
+ return mHeadsUpHeight;
+ }
+
public interface ExpansionLogger {
public void logNotificationExpansion(String key, boolean userAction, boolean expanded);
}
@@ -299,6 +332,7 @@
resetActualHeight();
}
mMaxExpandHeight = 0;
+ mHeadsUpHeight = 0;
mWasReset = true;
onHeightReset();
requestLayout();
@@ -536,7 +570,13 @@
}
boolean inExpansionState = isExpanded();
int maxContentHeight;
- if ((!inExpansionState && !mChildrenExpanded) || mShowingPublicForIntrinsicHeight) {
+ if (mIsHeadsUp) {
+ if (inExpansionState) {
+ maxContentHeight = Math.max(mMaxExpandHeight, mHeadsUpHeight);
+ } else {
+ maxContentHeight = Math.max(mRowMinHeight, mHeadsUpHeight);
+ }
+ } else if ((!inExpansionState && !mChildrenExpanded) || mShowingPublicForIntrinsicHeight) {
maxContentHeight = mRowMinHeight;
} else if (mChildrenExpanded) {
maxContentHeight = mChildrenContainer.getIntrinsicHeight();
@@ -583,7 +623,7 @@
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
boolean updateExpandHeight = mMaxExpandHeight == 0 && !mWasReset;
- updateMaxExpandHeight();
+ updateMaxHeights();
if (updateExpandHeight) {
applyExpansionToLayout();
}
@@ -599,9 +639,18 @@
return super.isChildInvisible(child) || isInvisibleChildContainer;
}
- private void updateMaxExpandHeight() {
+ private void updateMaxHeights() {
int intrinsicBefore = getIntrinsicHeight();
- mMaxExpandHeight = mPrivateLayout.getMaxHeight();
+ View expandedChild = mPrivateLayout.getExpandedChild();
+ if (expandedChild == null) {
+ expandedChild = mPrivateLayout.getContractedChild();
+ }
+ mMaxExpandHeight = expandedChild.getHeight();
+ View headsUpChild = mPrivateLayout.getHeadsUpChild();
+ if (headsUpChild == null) {
+ headsUpChild = mPrivateLayout.getContractedChild();
+ }
+ mHeadsUpHeight = headsUpChild.getHeight();
if (intrinsicBefore != getIntrinsicHeight()) {
notifyHeightChanged(false /* needsAnimation */);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java
index 7ae0d6d..e632cc8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java
@@ -118,6 +118,7 @@
setContentHeight(initialHeight);
}
}
+ updateClipping();
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java
index 745e75d..964d75f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java
@@ -23,10 +23,12 @@
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.View;
+import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.view.animation.Interpolator;
import android.view.animation.LinearInterpolator;
import android.widget.FrameLayout;
+
import com.android.systemui.R;
/**
@@ -37,23 +39,28 @@
public class NotificationContentView extends FrameLayout {
private static final long ANIMATION_DURATION_LENGTH = 170;
+ private static final int CONTRACTED = 1;
+ private static final int EXPANDED = 2;
+ private static final int HEADSUP = 3;
private final Rect mClipBounds = new Rect();
private View mContractedChild;
private View mExpandedChild;
+ private View mHeadsUpChild;
private NotificationViewWrapper mContractedWrapper;
- private int mSmallHeight;
+ private final int mSmallHeight;
+ private final int mHeadsUpHeight;
private int mClipTopAmount;
+
private int mContentHeight;
private final Interpolator mLinearInterpolator = new LinearInterpolator();
+ private int mVisibleView = CONTRACTED;
- private boolean mContractedVisible = true;
private boolean mDark;
-
private final Paint mFadePaint = new Paint();
private boolean mAnimate;
private ViewTreeObserver.OnPreDrawListener mEnableAnimationPredrawListener
@@ -65,14 +72,62 @@
return true;
}
};
+ private boolean mIsHeadsUp;
public NotificationContentView(Context context, AttributeSet attrs) {
super(context, attrs);
mFadePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.ADD));
+ mSmallHeight = getResources().getDimensionPixelSize(R.dimen.notification_min_height);
+ mHeadsUpHeight = getResources().getDimensionPixelSize(R.dimen.notification_mid_height);
reset(true);
}
@Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ int heightMode = MeasureSpec.getMode(heightMeasureSpec);
+ boolean hasFixedHeight = heightMode == MeasureSpec.EXACTLY;
+ boolean isHeightLimited = heightMode == MeasureSpec.AT_MOST;
+ int maxSize = Integer.MAX_VALUE;
+ if (hasFixedHeight || isHeightLimited) {
+ maxSize = MeasureSpec.getSize(heightMeasureSpec);
+ }
+ int maxChildHeight = 0;
+ if (mContractedChild != null) {
+ int size = Math.min(maxSize, mSmallHeight);
+ mContractedChild.measure(widthMeasureSpec,
+ MeasureSpec.makeMeasureSpec(size, MeasureSpec.AT_MOST));
+ maxChildHeight = Math.max(maxChildHeight, mContractedChild.getMeasuredHeight());
+ }
+ if (mExpandedChild != null) {
+ int size = maxSize;
+ ViewGroup.LayoutParams layoutParams = mExpandedChild.getLayoutParams();
+ if (layoutParams.height >= 0) {
+ // An actual height is set
+ size = Math.min(maxSize, layoutParams.height);
+ }
+ int spec = size == Integer.MAX_VALUE ?
+ MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED) :
+ MeasureSpec.makeMeasureSpec(size, MeasureSpec.AT_MOST);
+ mExpandedChild.measure(widthMeasureSpec, spec);
+ maxChildHeight = Math.max(maxChildHeight, mExpandedChild.getMeasuredHeight());
+ }
+ if (mHeadsUpChild != null) {
+ int size = Math.min(maxSize, mHeadsUpHeight);
+ ViewGroup.LayoutParams layoutParams = mHeadsUpChild.getLayoutParams();
+ if (layoutParams.height >= 0) {
+ // An actual height is set
+ size = Math.min(maxSize, layoutParams.height);
+ }
+ mHeadsUpChild.measure(widthMeasureSpec,
+ MeasureSpec.makeMeasureSpec(size, MeasureSpec.AT_MOST));
+ maxChildHeight = Math.max(maxChildHeight, mHeadsUpChild.getMeasuredHeight());
+ }
+ int ownHeight = Math.min(maxChildHeight, maxSize);
+ int width = MeasureSpec.getSize(widthMeasureSpec);
+ setMeasuredDimension(width, ownHeight);
+ }
+
+ @Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
updateClipping();
@@ -91,11 +146,14 @@
if (mExpandedChild != null) {
mExpandedChild.animate().cancel();
}
+ if (mHeadsUpChild != null) {
+ mHeadsUpChild.animate().cancel();
+ }
removeAllViews();
mContractedChild = null;
mExpandedChild = null;
- mSmallHeight = getResources().getDimensionPixelSize(R.dimen.notification_min_height);
- mContractedVisible = true;
+ mHeadsUpChild = null;
+ mVisibleView = CONTRACTED;
if (resetActualHeight) {
mContentHeight = mSmallHeight;
}
@@ -109,12 +167,15 @@
return mExpandedChild;
}
+ public View getHeadsUpChild() {
+ return mHeadsUpChild;
+ }
+
public void setContractedChild(View child) {
if (mContractedChild != null) {
mContractedChild.animate().cancel();
removeView(mContractedChild);
}
- sanitizeContractedLayoutParams(child);
addView(child);
mContractedChild = child;
mContractedWrapper = NotificationViewWrapper.wrap(getContext(), child);
@@ -132,6 +193,16 @@
selectLayout(false /* animate */, true /* force */);
}
+ public void setHeadsUpChild(View child) {
+ if (mHeadsUpChild != null) {
+ mHeadsUpChild.animate().cancel();
+ removeView(mHeadsUpChild);
+ }
+ addView(child);
+ mHeadsUpChild = child;
+ selectLayout(false /* animate */, true /* force */);
+ }
+
@Override
protected void onVisibilityChanged(View changedView, int visibility) {
super.onVisibilityChanged(changedView, visibility);
@@ -166,9 +237,12 @@
}
public int getMaxHeight() {
-
- // The maximum height is just the laid out height.
- return getHeight();
+ if (mIsHeadsUp && mHeadsUpChild != null) {
+ return mHeadsUpChild.getHeight();
+ } else if (mExpandedChild != null) {
+ return mExpandedChild.getHeight();
+ }
+ return mSmallHeight;
}
public int getMinHeight() {
@@ -185,62 +259,91 @@
setClipBounds(mClipBounds);
}
- private void sanitizeContractedLayoutParams(View contractedChild) {
- LayoutParams lp = (LayoutParams) contractedChild.getLayoutParams();
- lp.height = mSmallHeight;
- contractedChild.setLayoutParams(lp);
- }
-
private void selectLayout(boolean animate, boolean force) {
if (mContractedChild == null) {
return;
}
- boolean showContractedChild = showContractedChild();
- if (showContractedChild != mContractedVisible || force) {
+ int visibleView = calculateVisibleView();
+ if (visibleView != mVisibleView || force) {
if (animate && mExpandedChild != null) {
- runSwitchAnimation(showContractedChild);
- } else if (mExpandedChild != null) {
- mContractedChild.setVisibility(showContractedChild ? View.VISIBLE : View.INVISIBLE);
- mContractedChild.setAlpha(showContractedChild ? 1f : 0f);
- mExpandedChild.setVisibility(showContractedChild ? View.INVISIBLE : View.VISIBLE);
- mExpandedChild.setAlpha(showContractedChild ? 0f : 1f);
+ runSwitchAnimation(visibleView);
+ } else {
+ updateViewVisibilities(visibleView);
}
+ mVisibleView = visibleView;
}
- mContractedVisible = showContractedChild;
}
- private void runSwitchAnimation(final boolean showContractedChild) {
- mContractedChild.setVisibility(View.VISIBLE);
- mExpandedChild.setVisibility(View.VISIBLE);
- mContractedChild.setLayerType(LAYER_TYPE_HARDWARE, mFadePaint);
- mExpandedChild.setLayerType(LAYER_TYPE_HARDWARE, mFadePaint);
+ private void updateViewVisibilities(int visibleView) {
+ boolean contractedVisible = visibleView == CONTRACTED;
+ mContractedChild.setVisibility(contractedVisible ? View.VISIBLE : View.INVISIBLE);
+ mContractedChild.setAlpha(contractedVisible ? 1f : 0f);
+ mContractedChild.setLayerType(LAYER_TYPE_NONE, null);
+ if (mExpandedChild != null) {
+ boolean expandedVisible = visibleView == EXPANDED;
+ mExpandedChild.setVisibility(expandedVisible ? View.VISIBLE : View.INVISIBLE);
+ mExpandedChild.setAlpha(expandedVisible ? 1f : 0f);
+ mExpandedChild.setLayerType(LAYER_TYPE_NONE, null);
+ }
+ if (mHeadsUpChild != null) {
+ boolean headsUpVisible = visibleView == HEADSUP;
+ mHeadsUpChild.setVisibility(headsUpVisible ? View.VISIBLE : View.INVISIBLE);
+ mHeadsUpChild.setAlpha(headsUpVisible ? 1f : 0f);
+ mHeadsUpChild.setLayerType(LAYER_TYPE_NONE, null);
+ }
+ setLayerType(LAYER_TYPE_NONE, null);
+ }
+
+ private void runSwitchAnimation(int visibleView) {
+ View shownView = getViewFromFlag(visibleView);
+ View hiddenView = getViewFromFlag(mVisibleView);
+ shownView.setVisibility(View.VISIBLE);
+ hiddenView.setVisibility(View.VISIBLE);
+ shownView.setLayerType(LAYER_TYPE_HARDWARE, mFadePaint);
+ hiddenView.setLayerType(LAYER_TYPE_HARDWARE, mFadePaint);
setLayerType(LAYER_TYPE_HARDWARE, null);
- mContractedChild.animate()
- .alpha(showContractedChild ? 1f : 0f)
+ hiddenView.animate()
+ .alpha(0f)
.setDuration(ANIMATION_DURATION_LENGTH)
- .setInterpolator(mLinearInterpolator);
- mExpandedChild.animate()
- .alpha(showContractedChild ? 0f : 1f)
+ .setInterpolator(mLinearInterpolator)
+ .withEndAction(null); // In case we have multiple changes in one frame.
+ shownView.animate()
+ .alpha(1f)
.setDuration(ANIMATION_DURATION_LENGTH)
.setInterpolator(mLinearInterpolator)
.withEndAction(new Runnable() {
@Override
public void run() {
- mContractedChild.setLayerType(LAYER_TYPE_NONE, null);
- mExpandedChild.setLayerType(LAYER_TYPE_NONE, null);
- setLayerType(LAYER_TYPE_NONE, null);
- mContractedChild.setVisibility(showContractedChild
- ? View.VISIBLE
- : View.INVISIBLE);
- mExpandedChild.setVisibility(showContractedChild
- ? View.INVISIBLE
- : View.VISIBLE);
+ updateViewVisibilities(mVisibleView);
}
});
}
- private boolean showContractedChild() {
- return mContentHeight <= mSmallHeight || mExpandedChild == null;
+ private View getViewFromFlag(int visibleView) {
+ switch (visibleView) {
+ case EXPANDED:
+ return mExpandedChild;
+ case HEADSUP:
+ return mHeadsUpChild;
+ }
+ return mContractedChild;
+ }
+
+ private int calculateVisibleView() {
+ boolean noExpandedChild = mExpandedChild == null;
+ if (mIsHeadsUp && mHeadsUpChild != null) {
+ if (mContentHeight <= mHeadsUpChild.getHeight() || noExpandedChild) {
+ return HEADSUP;
+ } else {
+ return EXPANDED;
+ }
+ } else {
+ if (mContentHeight <= mSmallHeight || noExpandedChild) {
+ return CONTRACTED;
+ } else {
+ return EXPANDED;
+ }
+ }
}
public void notifyContentUpdated() {
@@ -261,6 +364,11 @@
mContractedWrapper.setDark(dark, fade, delay);
}
+ public void setHeadsUp(boolean headsUp) {
+ mIsHeadsUp = headsUp;
+ selectLayout(false /* animate */, true /* force */);
+ }
+
@Override
public boolean hasOverlappingRendering() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
index 912f414..429889d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
@@ -25,6 +25,7 @@
import android.view.View;
import com.android.systemui.statusbar.phone.NotificationGroupManager;
+import com.android.systemui.statusbar.policy.HeadsUpManager;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -37,6 +38,7 @@
public class NotificationData {
private final Environment mEnvironment;
+ private HeadsUpManager mHeadsUpManager;
public static final class Entry {
public String key;
@@ -98,43 +100,47 @@
private RankingMap mRankingMap;
private final Ranking mTmpRanking = new Ranking();
+ public void setHeadsUpManager(HeadsUpManager headsUpManager) {
+ mHeadsUpManager = headsUpManager;
+ }
+
private final Comparator<Entry> mRankingComparator = new Comparator<Entry>() {
private final Ranking mRankingA = new Ranking();
private final Ranking mRankingB = new Ranking();
@Override
public int compare(Entry a, Entry b) {
- // Upsort current media notification.
String mediaNotification = mEnvironment.getCurrentMediaNotificationKey();
boolean aMedia = a.key.equals(mediaNotification);
boolean bMedia = b.key.equals(mediaNotification);
- if (aMedia != bMedia) {
- return aMedia ? -1 : 1;
- }
final StatusBarNotification na = a.notification;
final StatusBarNotification nb = b.notification;
- // Upsort PRIORITY_MAX system notifications
boolean aSystemMax = na.getNotification().priority >= Notification.PRIORITY_MAX &&
isSystemNotification(na);
boolean bSystemMax = nb.getNotification().priority >= Notification.PRIORITY_MAX &&
isSystemNotification(nb);
- if (aSystemMax != bSystemMax) {
- return aSystemMax ? -1 : 1;
- }
+ int d = nb.getScore() - na.getScore();
- // RankingMap as received from NoMan.
- if (mRankingMap != null) {
+ boolean isHeadsUp = a.row.isHeadsUp();
+ if (isHeadsUp != b.row.isHeadsUp()) {
+ return isHeadsUp ? -1 : 1;
+ } else if (isHeadsUp) {
+ // Provide consistent ranking with headsUpManager
+ return mHeadsUpManager.compare(a, b);
+ } else if (aMedia != bMedia) {
+ // Upsort current media notification.
+ return aMedia ? -1 : 1;
+ } else if (aSystemMax != bSystemMax) {
+ // Upsort PRIORITY_MAX system notifications
+ return aSystemMax ? -1 : 1;
+ } else if (mRankingMap != null) {
+ // RankingMap as received from NoMan
mRankingMap.getRanking(a.key, mRankingA);
mRankingMap.getRanking(b.key, mRankingB);
return mRankingA.getRank() - mRankingB.getRank();
- }
-
- int d = nb.getScore() - na.getScore();
- if (a.interruption != b.interruption) {
- return a.interruption ? -1 : 1;
- } else if (d != 0) {
+ } if (d != 0) {
return d;
} else {
return (int) (nb.getNotification().when - na.getNotification().when);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java
new file mode 100644
index 0000000..3997807
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.statusbar.phone;
+
+import android.content.Context;
+import android.view.MotionEvent;
+import android.view.ViewConfiguration;
+
+import com.android.systemui.Gefingerpoken;
+import com.android.systemui.statusbar.ExpandableNotificationRow;
+import com.android.systemui.statusbar.ExpandableView;
+import com.android.systemui.statusbar.policy.HeadsUpManager;
+import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
+
+/**
+ * A Helper class to handle touches on the heads-up views
+ */
+public class HeadsUpTouchHelper implements Gefingerpoken {
+
+ private HeadsUpManager mHeadsUpManager;
+ private NotificationStackScrollLayout mStackScroller;
+ private int mTrackingPointer;
+ private float mTouchSlop;
+ private float mInitialTouchX;
+ private float mInitialTouchY;
+ private boolean mMotionOnHeadsUpView;
+ private boolean mTrackingHeadsUp;
+ private boolean mCollapseSnoozes;
+ private NotificationPanelView mPanel;
+ private ExpandableNotificationRow mPickedChild;
+
+ public boolean isTrackingHeadsUp() {
+ return mTrackingHeadsUp;
+ }
+
+ @Override
+ public boolean onInterceptTouchEvent(MotionEvent event) {
+ if (!mMotionOnHeadsUpView && event.getActionMasked() != MotionEvent.ACTION_DOWN) {
+ return false;
+ }
+ int pointerIndex = event.findPointerIndex(mTrackingPointer);
+ if (pointerIndex < 0) {
+ pointerIndex = 0;
+ mTrackingPointer = event.getPointerId(pointerIndex);
+ }
+ final float x = event.getX(pointerIndex);
+ final float y = event.getY(pointerIndex);
+ switch (event.getActionMasked()) {
+ case MotionEvent.ACTION_DOWN:
+ mInitialTouchY = y;
+ mInitialTouchX = x;
+ setTrackingHeadsUp(false);
+ ExpandableView child = mStackScroller.getChildAtPosition(x, y);
+ mMotionOnHeadsUpView = false;
+ if (child instanceof ExpandableNotificationRow) {
+ mPickedChild = (ExpandableNotificationRow) child;
+ mMotionOnHeadsUpView = mPickedChild.isHeadsUp() && !mPickedChild.isInShade();
+ }
+ break;
+ case MotionEvent.ACTION_POINTER_UP:
+ final int upPointer = event.getPointerId(event.getActionIndex());
+ if (mTrackingPointer == upPointer) {
+ // gesture is ongoing, find a new pointer to track
+ final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1;
+ mTrackingPointer = event.getPointerId(newIndex);
+ mInitialTouchX = event.getX(newIndex);
+ mInitialTouchY = event.getY(newIndex);
+ }
+ break;
+
+ case MotionEvent.ACTION_MOVE:
+ final float h = y - mInitialTouchY;
+ if (Math.abs(h) > mTouchSlop && Math.abs(h) > Math.abs(x - mInitialTouchX)) {
+ setTrackingHeadsUp(true);
+ mCollapseSnoozes = h < 0;
+ mInitialTouchX = x;
+ mInitialTouchY = y;
+ int expandedHeight = mPickedChild.getActualHeight();
+ mPanel.startExpandMotion(x, y, true /* startTracking */, expandedHeight);
+ return true;
+ }
+ break;
+
+ case MotionEvent.ACTION_CANCEL:
+ case MotionEvent.ACTION_UP:
+ if (mPickedChild != null && mMotionOnHeadsUpView) {
+ if (mHeadsUpManager.shouldSwallowClick(
+ mPickedChild.getStatusBarNotification().getKey())) {
+ endMotion();
+ return true;
+ }
+ }
+ endMotion();
+ break;
+ }
+ return false;
+ }
+
+ private void setTrackingHeadsUp(boolean tracking) {
+ mTrackingHeadsUp = tracking;
+ mHeadsUpManager.setTrackingHeadsUp(tracking);
+ mPanel.setTrackingHeadsUp(tracking);
+ }
+
+ public void notifyFling(boolean collapse) {
+ if (collapse && mCollapseSnoozes) {
+ mHeadsUpManager.snooze();
+ }
+ mCollapseSnoozes = false;
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ if (!mTrackingHeadsUp) {
+ return false;
+ }
+ switch (event.getActionMasked()) {
+ case MotionEvent.ACTION_UP:
+ case MotionEvent.ACTION_CANCEL:
+ endMotion();
+ setTrackingHeadsUp(false);
+ break;
+ }
+ return true;
+ }
+
+ private void endMotion() {
+ mTrackingPointer = -1;
+ mPickedChild = null;
+ mMotionOnHeadsUpView = false;
+ }
+
+ public ExpandableView getPickedChild() {
+ return mPickedChild;
+ }
+
+ public void bind(HeadsUpManager headsUpManager, NotificationStackScrollLayout stackScroller,
+ NotificationPanelView notificationPanelView) {
+ mHeadsUpManager = headsUpManager;
+ mStackScroller = stackScroller;
+ mPanel = notificationPanelView;
+ Context context = stackScroller.getContext();
+ final ViewConfiguration configuration = ViewConfiguration.get(context);
+ mTouchSlop = configuration.getScaledTouchSlop();
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
index 02b6c19..c266467 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -32,6 +32,7 @@
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewTreeObserver;
+import android.view.WindowInsets;
import android.view.accessibility.AccessibilityEvent;
import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator;
@@ -39,16 +40,19 @@
import android.widget.TextView;
import com.android.keyguard.KeyguardStatusView;
-import com.android.systemui.EventLogTags;
import com.android.systemui.EventLogConstants;
+import com.android.systemui.EventLogTags;
import com.android.systemui.R;
import com.android.systemui.qs.QSContainer;
import com.android.systemui.qs.QSPanel;
+import com.android.systemui.statusbar.ExpandableNotificationRow;
import com.android.systemui.statusbar.ExpandableView;
import com.android.systemui.statusbar.FlingAnimationUtils;
import com.android.systemui.statusbar.GestureRecorder;
import com.android.systemui.statusbar.KeyguardAffordanceView;
+import com.android.systemui.statusbar.NotificationData;
import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.statusbar.policy.KeyguardUserSwitcher;
import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
import com.android.systemui.statusbar.stack.StackStateAnimator;
@@ -56,7 +60,8 @@
public class NotificationPanelView extends PanelView implements
ExpandableView.OnHeightChangedListener, ObservableScrollView.Listener,
View.OnClickListener, NotificationStackScrollLayout.OnOverscrollTopChangedListener,
- KeyguardAffordanceHelper.Callback, NotificationStackScrollLayout.OnEmptySpaceClickListener {
+ KeyguardAffordanceHelper.Callback, NotificationStackScrollLayout.OnEmptySpaceClickListener,
+ HeadsUpManager.OnHeadsUpChangedListener {
private static final boolean DEBUG = false;
@@ -81,7 +86,7 @@
private TextView mClockView;
private View mReserveNotificationSpace;
private View mQsNavbarScrim;
- private View mNotificationContainerParent;
+ private NotificationsQuickSettingsContainer mNotificationContainerParent;
private NotificationStackScrollLayout mNotificationStackScroller;
private int mNotificationTopPadding;
private boolean mAnimateNextTopPaddingChange;
@@ -177,6 +182,17 @@
private float mKeyguardStatusBarAnimateAlpha = 1f;
private int mOldLayoutDirection;
+ private HeadsUpTouchHelper mHeadsUpTouchHelper = new HeadsUpTouchHelper();
+ private boolean mPinnedHeadsUpExist;
+ private boolean mExpansionIsFromHeadsUp;
+ private int mBottomBarHeight;
+ private boolean mExpandingFromHeadsUp;
+ private Runnable mHeadsUpExistenceChangedRunnable = new Runnable() {
+ @Override
+ public void run() {
+ notifyBarPanelExpansionChanged();
+ }
+ };
public NotificationPanelView(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -201,7 +217,8 @@
mScrollView.setListener(this);
mScrollView.setFocusable(false);
mReserveNotificationSpace = findViewById(R.id.reserve_notification_space);
- mNotificationContainerParent = findViewById(R.id.notification_container_parent);
+ mNotificationContainerParent = (NotificationsQuickSettingsContainer)
+ findViewById(R.id.notification_container_parent);
mNotificationStackScroller = (NotificationStackScrollLayout)
findViewById(R.id.notification_stack_scroller);
mNotificationStackScroller.setOnHeightChangedListener(this);
@@ -317,6 +334,7 @@
if (mQsSizeChangeAnimator == null) {
mQsContainer.setHeightOverride(mQsContainer.getDesiredHeight());
}
+ updateMaxHeadsUpTranslation();
}
@Override
@@ -493,22 +511,39 @@
}
@Override
+ protected void flingToHeight(float vel, boolean expand, float target) {
+ mHeadsUpTouchHelper.notifyFling(!expand);
+ super.flingToHeight(vel, expand, target);
+ }
+
+ @Override
public boolean dispatchPopulateAccessibilityEventInternal(AccessibilityEvent event) {
if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
event.getText().add(getKeyguardOrLockScreenString());
mLastAnnouncementWasQuickSettings = false;
return true;
}
-
return super.dispatchPopulateAccessibilityEventInternal(event);
}
@Override
- public boolean onInterceptTouchEvent(MotionEvent event) {
+ public boolean
+ onInterceptTouchEvent(MotionEvent event) {
if (mBlockTouches) {
return false;
}
initDownStates(event);
+ if (mHeadsUpTouchHelper.onInterceptTouchEvent(event)) {
+ mExpansionIsFromHeadsUp = true;
+ return true;
+ }
+ if (!isShadeCollapsed() && onQsIntercept(event)) {
+ return true;
+ }
+ return super.onInterceptTouchEvent(event);
+ }
+
+ private boolean onQsIntercept(MotionEvent event) {
int pointerIndex = event.findPointerIndex(mTrackingPointer);
if (pointerIndex < 0) {
pointerIndex = 0;
@@ -583,7 +618,7 @@
mIntercepting = false;
break;
}
- return super.onInterceptTouchEvent(event);
+ return false;
}
@Override
@@ -652,6 +687,15 @@
if (mOnlyAffordanceInThisMotion) {
return true;
}
+ mHeadsUpTouchHelper.onTouchEvent(event);
+ if (!mHeadsUpTouchHelper.isTrackingHeadsUp() && handleQSTouch(event)) {
+ return true;
+ }
+ super.onTouchEvent(event);
+ return true;
+ }
+
+ private boolean handleQSTouch(MotionEvent event) {
if (event.getActionMasked() == MotionEvent.ACTION_DOWN && getExpandedFraction() == 1f
&& mStatusBar.getBarState() != StatusBarState.KEYGUARD && !mQsExpanded
&& mQsExpansionEnabled) {
@@ -664,7 +708,7 @@
mInitialTouchY = event.getX();
mInitialTouchX = event.getY();
}
- if (mExpandedHeight != 0) {
+ if (!isShadeCollapsed()) {
handleQsDown(event);
}
if (!mQsExpandImmediate && mQsTracking) {
@@ -677,7 +721,7 @@
|| event.getActionMasked() == MotionEvent.ACTION_UP) {
mConflictingQsExpansionGesture = false;
}
- if (event.getActionMasked() == MotionEvent.ACTION_DOWN && mExpandedHeight == 0
+ if (event.getActionMasked() == MotionEvent.ACTION_DOWN && isShadeCollapsed()
&& mQsExpansionEnabled) {
mTwoFingerQsExpandPossible = true;
}
@@ -691,8 +735,7 @@
// earlier so the state is already up to date when dragging down.
setListening(true);
}
- super.onTouchEvent(event);
- return true;
+ return false;
}
private boolean isInQsArea(float x, float y) {
@@ -834,13 +877,13 @@
setQsExpansion(mQsExpansionHeight);
flingSettings(!mQsExpansionEnabled && open ? 0f : velocity, open && mQsExpansionEnabled,
new Runnable() {
- @Override
- public void run() {
- mStackScrollerOverscrolling = false;
- mQsExpansionFromOverscroll = false;
- updateQsState();
- }
- });
+ @Override
+ public void run() {
+ mStackScrollerOverscrolling = false;
+ mQsExpansionFromOverscroll = false;
+ updateQsState();
+ }
+ });
}
private void onQsExpansionStarted() {
@@ -869,6 +912,7 @@
mNotificationStackScroller.setInterceptDelegateEnabled(expanded);
mStatusBar.setQsExpanded(expanded);
mQsPanel.setExpanded(expanded);
+ mNotificationContainerParent.setQsExpanded(expanded);
}
}
@@ -1056,7 +1100,7 @@
mKeyguardBottomArea.animate()
.alpha(0f)
.setStartDelay(mStatusBar.getKeyguardFadingAwayDelay())
- .setDuration(mStatusBar.getKeyguardFadingAwayDuration()/2)
+ .setDuration(mStatusBar.getKeyguardFadingAwayDuration() / 2)
.setInterpolator(PhoneStatusBar.ALPHA_OUT)
.withEndAction(mAnimateKeyguardBottomAreaInvisibleEndRunnable)
.start();
@@ -1132,8 +1176,8 @@
updateEmptyShadeView();
mQsNavbarScrim.setVisibility(mStatusBarState == StatusBarState.SHADE && mQsExpanded
&& !mStackScrollerOverscrolling && mQsScrimEnabled
- ? View.VISIBLE
- : View.INVISIBLE);
+ ? View.VISIBLE
+ : View.INVISIBLE);
if (mKeyguardUserSwitcher != null && mQsExpanded && !mStackScrollerOverscrolling) {
mKeyguardUserSwitcher.hideIfNotSimple(true /* animate */);
}
@@ -1402,6 +1446,8 @@
updateHeader();
updateUnlockIcon();
updateNotificationTranslucency();
+ mHeadsUpManager.setIsExpanded(!isShadeCollapsed());
+ mNotificationStackScroller.setShadeExpanded(!isShadeCollapsed());
if (DEBUG) {
invalidate();
}
@@ -1471,16 +1517,24 @@
}
}
private void updateNotificationTranslucency() {
- float alpha = (getNotificationsTopY() + mNotificationStackScroller.getItemHeight())
- / (mQsMinExpansionHeight + mNotificationStackScroller.getBottomStackPeekSize()
- - mNotificationStackScroller.getCollapseSecondCardPadding());
- alpha = Math.max(0, Math.min(alpha, 1));
- alpha = (float) Math.pow(alpha, 0.75);
- if (alpha != 1f && mNotificationStackScroller.getLayerType() != LAYER_TYPE_HARDWARE) {
- mNotificationStackScroller.setLayerType(LAYER_TYPE_HARDWARE, null);
- } else if (alpha == 1f
- && mNotificationStackScroller.getLayerType() == LAYER_TYPE_HARDWARE) {
- mNotificationStackScroller.setLayerType(LAYER_TYPE_NONE, null);
+ float alpha;
+ if (mExpandingFromHeadsUp || mHeadsUpManager.hasPinnedHeadsUp()) {
+ alpha = 1f;
+ if (mNotificationStackScroller.getLayerType() == LAYER_TYPE_HARDWARE) {
+ mNotificationStackScroller.setLayerType(LAYER_TYPE_NONE, null);
+ }
+ } else {
+ alpha = (getNotificationsTopY() + mNotificationStackScroller.getItemHeight())
+ / (mQsMinExpansionHeight + mNotificationStackScroller.getBottomStackPeekSize()
+ - mNotificationStackScroller.getCollapseSecondCardPadding());
+ alpha = Math.max(0, Math.min(alpha, 1));
+ alpha = (float) Math.pow(alpha, 0.75);
+ if (alpha != 1f && mNotificationStackScroller.getLayerType() != LAYER_TYPE_HARDWARE) {
+ mNotificationStackScroller.setLayerType(LAYER_TYPE_HARDWARE, null);
+ } else if (alpha == 1f
+ && mNotificationStackScroller.getLayerType() == LAYER_TYPE_HARDWARE) {
+ mNotificationStackScroller.setLayerType(LAYER_TYPE_NONE, null);
+ }
}
mNotificationStackScroller.setAlpha(alpha);
}
@@ -1544,7 +1598,13 @@
return mExpandedHeight / HEADER_RUBBERBAND_FACTOR - mQsMinExpansionHeight;
}
}
- return Math.min(0, mNotificationStackScroller.getTranslationY()) / HEADER_RUBBERBAND_FACTOR;
+ float stackTranslation = mNotificationStackScroller.getStackTranslation();
+ float translation = stackTranslation / HEADER_RUBBERBAND_FACTOR;
+ if (mHeadsUpManager.hasPinnedHeadsUp() || mExpansionIsFromHeadsUp) {
+ translation = mNotificationStackScroller.getTopPadding() + stackTranslation
+ - mNotificationTopPadding - mQsMinExpansionHeight;
+ }
+ return Math.min(0, translation);
}
/**
@@ -1605,15 +1665,19 @@
protected void onExpandingFinished() {
super.onExpandingFinished();
mNotificationStackScroller.onExpansionStopped();
+ mHeadsUpManager.onExpandingFinished();
mIsExpanding = false;
mScrollYOverride = -1;
- if (mExpandedHeight == 0f) {
+ if (isShadeCollapsed()) {
setListening(false);
} else {
setListening(true);
}
mQsExpandImmediate = false;
mTwoFingerQsExpandPossible = false;
+ mExpansionIsFromHeadsUp = false;
+ mNotificationStackScroller.setTrackingHeadsUp(mHeadsUpTouchHelper.isTrackingHeadsUp());
+ mExpandingFromHeadsUp = mHeadsUpTouchHelper.isTrackingHeadsUp();
}
private void setListening(boolean listening) {
@@ -1709,6 +1773,17 @@
}
@Override
+ public WindowInsets onApplyWindowInsets(WindowInsets insets) {
+ mBottomBarHeight = insets.getSystemWindowInsetBottom();
+ updateMaxHeadsUpTranslation();
+ return insets;
+ }
+
+ private void updateMaxHeadsUpTranslation() {
+ mNotificationStackScroller.setHeadsUpBoundaries(getHeight(), mBottomBarHeight);
+ }
+
+ @Override
public void onRtlPropertiesChanged(int layoutDirection) {
if (layoutDirection != mOldLayoutDirection) {
mAfforanceHelper.onRtlPropertiesChanged();
@@ -2066,4 +2141,49 @@
mNotificationStackScroller.getTopPadding(), p);
}
}
+
+ @Override
+ public void OnPinnedHeadsUpExistChanged(final boolean exist, boolean changeImmediatly) {
+ if (exist != mPinnedHeadsUpExist) {
+ mPinnedHeadsUpExist = exist;
+ if (exist) {
+ mHeadsUpExistenceChangedRunnable.run();
+ updateNotificationTranslucency();
+ } else {
+ mNotificationStackScroller.performOnAnimationFinished(
+ mHeadsUpExistenceChangedRunnable);
+ }
+ }
+ }
+
+ @Override
+ public void OnHeadsUpPinnedChanged(ExpandableNotificationRow headsUp, boolean isHeadsUp) {
+ if (isHeadsUp) {
+ mNotificationStackScroller.generateHeadsUpAnimation(headsUp, true);
+ }
+ }
+
+ @Override
+ public void OnHeadsUpStateChanged(NotificationData.Entry entry, boolean isHeadsUp) {
+ mNotificationStackScroller.generateHeadsUpAnimation(entry.row, isHeadsUp);
+ }
+
+ @Override
+ protected boolean isShadeCollapsed() {
+ return mExpandedHeight == 0;
+ }
+
+ @Override
+ public void setHeadsUpManager(HeadsUpManager headsUpManager) {
+ super.setHeadsUpManager(headsUpManager);
+ mHeadsUpTouchHelper.bind(headsUpManager, mNotificationStackScroller, this);
+ }
+
+ public void setTrackingHeadsUp(boolean tracking) {
+ if (tracking) {
+ // otherwise we update the state when the expansion is finished
+ mNotificationStackScroller.setTrackingHeadsUp(true);
+ mExpandingFromHeadsUp = true;
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java
index a03c297..cbb71c5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java
@@ -37,6 +37,7 @@
private View mStackScroller;
private View mKeyguardStatusBar;
private boolean mInflated;
+ private boolean mQsExpanded;
public NotificationsQuickSettingsContainer(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -64,26 +65,29 @@
boolean userSwitcherVisible = mInflated && mUserSwitcher.getVisibility() == View.VISIBLE;
boolean statusBarVisible = mKeyguardStatusBar.getVisibility() == View.VISIBLE;
+ View stackQsTop = mQsExpanded ? mStackScroller : mScrollView;
+ View stackQsBottom = !mQsExpanded ? mStackScroller : mScrollView;
// Invert the order of the scroll view and user switcher such that the notifications receive
// touches first but the panel gets drawn above.
if (child == mScrollView) {
- return super.drawChild(canvas, mStackScroller, drawingTime);
- } else if (child == mStackScroller) {
- return super.drawChild(canvas,
- userSwitcherVisible && statusBarVisible ? mUserSwitcher
+ return super.drawChild(canvas, userSwitcherVisible && statusBarVisible ? mUserSwitcher
: statusBarVisible ? mKeyguardStatusBar
: userSwitcherVisible ? mUserSwitcher
- : mScrollView,
+ : stackQsBottom, drawingTime);
+ } else if (child == mStackScroller) {
+ return super.drawChild(canvas,
+ userSwitcherVisible && statusBarVisible ? mKeyguardStatusBar
+ : statusBarVisible || userSwitcherVisible ? stackQsBottom
+ : stackQsTop,
drawingTime);
} else if (child == mUserSwitcher) {
return super.drawChild(canvas,
- userSwitcherVisible && statusBarVisible ? mKeyguardStatusBar
- : mScrollView,
+ userSwitcherVisible && statusBarVisible ? stackQsBottom
+ : stackQsTop,
drawingTime);
} else if (child == mKeyguardStatusBar) {
return super.drawChild(canvas,
- userSwitcherVisible && statusBarVisible ? mScrollView
- : mScrollView,
+ stackQsTop,
drawingTime);
}else {
return super.drawChild(canvas, child, drawingTime);
@@ -97,4 +101,11 @@
mInflated = true;
}
}
+
+ public void setQsExpanded(boolean expanded) {
+ if (mQsExpanded != expanded) {
+ mQsExpanded = expanded;
+ invalidate();
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java
index c6e1be9..240438a7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java
@@ -157,8 +157,7 @@
if (DEBUG) LOG("panelExpansionChanged: start state=%d panel=%s", mState, panel.getName());
mPanelExpandedFractionSum = 0f;
for (PanelView pv : mPanels) {
- boolean visible = pv.getExpandedHeight() > 0;
- pv.setVisibility(visible ? View.VISIBLE : View.GONE);
+ pv.setVisibility(expanded ? View.VISIBLE : View.INVISIBLE);
// adjust any other panels that may be partially visible
if (expanded) {
if (mState == STATE_CLOSED) {
@@ -167,7 +166,7 @@
}
fullyClosed = false;
final float thisFrac = pv.getExpandedFraction();
- mPanelExpandedFractionSum += (visible ? thisFrac : 0);
+ mPanelExpandedFractionSum += thisFrac;
if (DEBUG) LOG("panelExpansionChanged: -> %s: f=%.1f", pv.getName(), thisFrac);
if (panel == pv) {
if (thisFrac == 1f) fullyOpenedPanel = panel;
@@ -196,7 +195,6 @@
} else {
pv.resetViews();
pv.setExpandedFraction(0); // just in case
- pv.setVisibility(View.GONE);
pv.cancelPeek();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
index 4bbf690..ddce9d5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
@@ -38,6 +38,7 @@
import com.android.systemui.doze.DozeLog;
import com.android.systemui.statusbar.FlingAnimationUtils;
import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.policy.HeadsUpManager;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -45,6 +46,7 @@
public abstract class PanelView extends FrameLayout {
public static final boolean DEBUG = PanelBar.DEBUG;
public static final String TAG = PanelView.class.getSimpleName();
+ protected HeadsUpManager mHeadsUpManager;
private final void logf(String fmt, Object... args) {
Log.v(TAG, (mViewName != null ? (mViewName + ": ") : "") + String.format(fmt, args));
@@ -232,18 +234,15 @@
final float y = event.getY(pointerIndex);
if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
- mGestureWaitForTouchSlop = mExpandedHeight == 0f;
+ mGestureWaitForTouchSlop = isShadeCollapsed();
}
boolean waitForTouchSlop = hasConflictingGestures() || mGestureWaitForTouchSlop;
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
- mInitialTouchY = y;
- mInitialTouchX = x;
- mInitialOffsetOnTouch = mExpandedHeight;
- mTouchSlopExceeded = false;
+ startExpandMotion(x, y, false /* startTracking */, mExpandedHeight);
mJustPeeked = false;
- mPanelClosedOnDown = mExpandedHeight == 0.0f;
+ mPanelClosedOnDown = isShadeCollapsed();
mHasLayoutedSinceDown = false;
mUpdateFlingOnLayout = false;
mMotionAborted = false;
@@ -261,7 +260,7 @@
|| mPeekPending || mPeekAnimator != null;
onTrackingStarted();
}
- if (mExpandedHeight == 0) {
+ if (isShadeCollapsed()) {
schedulePeek();
}
break;
@@ -274,9 +273,7 @@
final float newY = event.getY(newIndex);
final float newX = event.getX(newIndex);
mTrackingPointer = event.getPointerId(newIndex);
- mInitialOffsetOnTouch = mExpandedHeight;
- mInitialTouchY = newY;
- mInitialTouchX = newX;
+ startExpandMotion(newX, newY, true /* startTracking */, mExpandedHeight);
}
break;
case MotionEvent.ACTION_POINTER_DOWN:
@@ -297,9 +294,7 @@
mTouchSlopExceeded = true;
if (waitForTouchSlop && !mTracking) {
if (!mJustPeeked && mInitialOffsetOnTouch != 0f) {
- mInitialOffsetOnTouch = mExpandedHeight;
- mInitialTouchX = x;
- mInitialTouchY = y;
+ startExpandMotion(x, y, false /* startTracking */, mExpandedHeight);
h = 0;
}
cancelHeightAnimator();
@@ -334,6 +329,17 @@
return !waitForTouchSlop || mTracking;
}
+ protected void startExpandMotion(float newX, float newY, boolean startTracking,
+ float expandedHeight) {
+ mInitialOffsetOnTouch = expandedHeight;
+ mInitialTouchY = newY;
+ mInitialTouchX = newX;
+ if (startTracking) {
+ mTouchSlopExceeded = true;
+ onTrackingStarted();
+ }
+ }
+
private void endMotionEvent(MotionEvent event, float x, float y, boolean forceCancel) {
mTrackingPointer = -1;
if ((mTracking && mTouchSlopExceeded)
@@ -442,7 +448,7 @@
mTouchSlopExceeded = false;
mJustPeeked = false;
mMotionAborted = false;
- mPanelClosedOnDown = mExpandedHeight == 0.0f;
+ mPanelClosedOnDown = isShadeCollapsed();
mHasLayoutedSinceDown = false;
mUpdateFlingOnLayout = false;
mTouchAboveFalsingThreshold = false;
@@ -474,12 +480,7 @@
if (scrolledToBottom || mTouchStartedInEmptyArea) {
if (h < -mTouchSlop && h < -Math.abs(x - mInitialTouchX)) {
cancelHeightAnimator();
- mInitialOffsetOnTouch = mExpandedHeight;
- mInitialTouchY = y;
- mInitialTouchX = x;
- mTracking = true;
- mTouchSlopExceeded = true;
- onTrackingStarted();
+ startExpandMotion(x, y, true /* startTracking */, mExpandedHeight);
return true;
}
}
@@ -564,7 +565,10 @@
protected void fling(float vel, boolean expand) {
cancelPeek();
float target = expand ? getMaxPanelHeight() : 0.0f;
+ flingToHeight(vel, expand, target);
+ }
+ protected void flingToHeight(float vel, boolean expand, float target) {
// Hack to make the expand transition look nice when clear all button is visible - we make
// the animation only to the last notification, and then jump to the maximum panel height so
// clear all just fades in and the decelerating motion is towards the last notification.
@@ -644,7 +648,7 @@
mHasLayoutedSinceDown = true;
if (mUpdateFlingOnLayout) {
abortAnimations();
- fling(mUpdateFlingVelocity, true);
+ fling(mUpdateFlingVelocity, true /* expands */);
mUpdateFlingOnLayout = false;
}
}
@@ -655,7 +659,7 @@
// If the user isn't actively poking us, let's update the height
if ((!mTracking || isTrackingBlocked())
&& mHeightAnimator == null
- && mExpandedHeight > 0
+ && !isShadeCollapsed()
&& currentMaxPanelHeight != mExpandedHeight
&& !mPeekPending
&& mPeekAnimator == null
@@ -805,7 +809,7 @@
if (mExpanding) {
notifyExpandingFinished();
}
- setVisibility(VISIBLE);
+ notifyBarPanelExpansionChanged();
// Wait for window manager to pickup the change, so we know the maximum height of the panel
// then.
@@ -941,9 +945,9 @@
return animator;
}
- private void notifyBarPanelExpansionChanged() {
+ protected void notifyBarPanelExpansionChanged() {
mBar.panelExpansionChanged(this, mExpandedFraction, mExpandedFraction > 0f || mPeekPending
- || mPeekAnimator != null);
+ || mPeekAnimator != null || mInstantExpanding || mHeadsUpManager.hasPinnedHeadsUp());
}
/**
@@ -1014,4 +1018,10 @@
* @return the height of the clear all button, in pixels
*/
protected abstract int getClearAllHeight();
+
+ protected abstract boolean isShadeCollapsed();
+
+ public void setHeadsUpManager(HeadsUpManager headsUpManager) {
+ mHeadsUpManager = headsUpManager;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
index b0d6178..61e679a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -17,19 +17,6 @@
package com.android.systemui.statusbar.phone;
-import static android.app.StatusBarManager.NAVIGATION_HINT_BACK_ALT;
-import static android.app.StatusBarManager.NAVIGATION_HINT_IME_SHOWN;
-import static android.app.StatusBarManager.WINDOW_STATE_HIDDEN;
-import static android.app.StatusBarManager.WINDOW_STATE_SHOWING;
-import static android.app.StatusBarManager.windowStateToString;
-import static com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT;
-import static com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT_TRANSPARENT;
-import static com.android.systemui.statusbar.phone.BarTransitions.MODE_OPAQUE;
-import static com.android.systemui.statusbar.phone.BarTransitions.MODE_SEMI_TRANSPARENT;
-import static com.android.systemui.statusbar.phone.BarTransitions.MODE_TRANSLUCENT;
-import static com.android.systemui.statusbar.phone.BarTransitions.MODE_TRANSPARENT;
-import static com.android.systemui.statusbar.phone.BarTransitions.MODE_WARNING;
-
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.annotation.NonNull;
@@ -86,13 +73,11 @@
import android.util.EventLog;
import android.util.Log;
import android.view.Display;
-import android.view.Gravity;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
-import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
import android.view.ViewStub;
import android.view.WindowManager;
@@ -115,13 +100,13 @@
import com.android.systemui.EventLogTags;
import com.android.systemui.Prefs;
import com.android.systemui.R;
+import com.android.systemui.assist.AssistGestureManager;
import com.android.systemui.doze.DozeHost;
import com.android.systemui.doze.DozeLog;
import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.qs.QSPanel;
import com.android.systemui.recents.ScreenPinningRequest;
import com.android.systemui.statusbar.ActivatableNotificationView;
-import com.android.systemui.assist.AssistGestureManager;
import com.android.systemui.statusbar.BackDropView;
import com.android.systemui.statusbar.BaseStatusBar;
import com.android.systemui.statusbar.CommandQueue;
@@ -137,7 +122,6 @@
import com.android.systemui.statusbar.ScrimView;
import com.android.systemui.statusbar.SignalClusterView;
import com.android.systemui.statusbar.SpeedBumpView;
-import com.android.systemui.statusbar.StatusBarIconView;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.phone.UnlockMethodCache.OnUnlockMethodChangedListener;
import com.android.systemui.statusbar.policy.AccessibilityController;
@@ -147,7 +131,7 @@
import com.android.systemui.statusbar.policy.BrightnessMirrorController;
import com.android.systemui.statusbar.policy.CastControllerImpl;
import com.android.systemui.statusbar.policy.FlashlightController;
-import com.android.systemui.statusbar.policy.HeadsUpNotificationView;
+import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.statusbar.policy.HotspotControllerImpl;
import com.android.systemui.statusbar.policy.KeyButtonView;
import com.android.systemui.statusbar.policy.KeyguardMonitor;
@@ -172,11 +156,27 @@
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
+import java.util.TreeSet;
+
+import static android.app.StatusBarManager.NAVIGATION_HINT_BACK_ALT;
+import static android.app.StatusBarManager.NAVIGATION_HINT_IME_SHOWN;
+import static android.app.StatusBarManager.WINDOW_STATE_HIDDEN;
+import static android.app.StatusBarManager.WINDOW_STATE_SHOWING;
+import static android.app.StatusBarManager.windowStateToString;
+import static com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT;
+import static com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT_TRANSPARENT;
+import static com.android.systemui.statusbar.phone.BarTransitions.MODE_OPAQUE;
+import static com.android.systemui.statusbar.phone.BarTransitions.MODE_SEMI_TRANSPARENT;
+import static com.android.systemui.statusbar.phone.BarTransitions.MODE_TRANSLUCENT;
+import static com.android.systemui.statusbar.phone.BarTransitions.MODE_TRANSPARENT;
+import static com.android.systemui.statusbar.phone.BarTransitions.MODE_WARNING;
public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
- DragDownHelper.DragDownCallback, ActivityStarter, OnUnlockMethodChangedListener {
+ DragDownHelper.DragDownCallback, ActivityStarter, OnUnlockMethodChangedListener,
+ HeadsUpManager.OnHeadsUpChangedListener {
static final String TAG = "PhoneStatusBar";
public static final boolean DEBUG = BaseStatusBar.DEBUG;
public static final boolean SPEW = false;
@@ -360,11 +360,7 @@
if (wasUsing != mUseHeadsUp) {
if (!mUseHeadsUp) {
Log.d(TAG, "dismissing any existing heads up notification on disable event");
- setHeadsUpVisibility(false);
- mHeadsUpNotificationView.releaseImmediately();
- removeHeadsUpView();
- } else {
- addHeadsUpView();
+ mHeadsUpManager.releaseAllImmediately();
}
}
}
@@ -528,6 +524,8 @@
};
private HashMap<ExpandableNotificationRow, List<ExpandableNotificationRow>> mTmpChildOrderMap
= new HashMap<>();
+ private HashSet<Entry> mHeadsUpEntriesToRemoveOnSwitch = new HashSet<>();
+ private RankingMap mLatestRankingMap;
@Override
public void start() {
@@ -598,7 +596,8 @@
}
}
return mStatusBarWindow.onTouchEvent(event);
- }});
+ }
+ });
mStatusBarView = (PhoneStatusBarView) mStatusBarWindow.findViewById(R.id.status_bar);
mStatusBarView.setBar(this);
@@ -615,12 +614,14 @@
mNotificationPanel.setBackground(new FastColorDrawable(context.getColor(
R.color.notification_panel_solid_background)));
}
- if (ENABLE_HEADS_UP) {
- mHeadsUpNotificationView =
- (HeadsUpNotificationView) View.inflate(context, R.layout.heads_up, null);
- mHeadsUpNotificationView.setVisibility(View.GONE);
- mHeadsUpNotificationView.setBar(this);
- }
+
+ mHeadsUpManager = new HeadsUpManager(context, mNotificationPanel.getViewTreeObserver());
+ mHeadsUpManager.setBar(this);
+ mHeadsUpManager.addListener(this);
+ mHeadsUpManager.addListener(mNotificationPanel);
+ mNotificationPanel.setHeadsUpManager(mHeadsUpManager);
+ mNotificationData.setHeadsUpManager(mHeadsUpManager);
+
if (MULTIUSER_DEBUG) {
mNotificationPanelDebugText = (TextView) mNotificationPanel.findViewById(
R.id.header_debug_info);
@@ -667,6 +668,7 @@
mStackScroller.setLongPressListener(getNotificationLongClicker());
mStackScroller.setPhoneStatusBar(this);
mStackScroller.setGroupManager(mGroupManager);
+ mStackScroller.setHeadsUpManager(mHeadsUpManager);
mGroupManager.setOnGroupChangeListener(mStackScroller);
mKeyguardIconOverflowContainer =
@@ -699,7 +701,11 @@
ScrimView scrimBehind = (ScrimView) mStatusBarWindow.findViewById(R.id.scrim_behind);
ScrimView scrimInFront = (ScrimView) mStatusBarWindow.findViewById(R.id.scrim_in_front);
- mScrimController = new ScrimController(scrimBehind, scrimInFront, mScrimSrcModeEnabled);
+ View headsUpScrim = mStatusBarWindow.findViewById(R.id.heads_up_scrim);
+ mScrimController = new ScrimController(scrimBehind, scrimInFront, headsUpScrim,
+ mScrimSrcModeEnabled);
+ mHeadsUpManager.addListener(mScrimController);
+ mStackScroller.setScrimController(mScrimController);
mScrimController.setBackDropView(mBackdrop);
mStatusBarView.setScrimController(mScrimController);
mDozeScrimController = new DozeScrimController(mScrimController, context);
@@ -1084,32 +1090,6 @@
return lp;
}
- private void addHeadsUpView() {
- int headsUpHeight = mContext.getResources()
- .getDimensionPixelSize(R.dimen.heads_up_window_height);
- WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
- LayoutParams.MATCH_PARENT, headsUpHeight,
- WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL, // above the status bar!
- WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
- | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS
- | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
- | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
- | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM
- | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH,
- PixelFormat.TRANSLUCENT);
- lp.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
- lp.gravity = Gravity.TOP;
- lp.setTitle("Heads Up");
- lp.packageName = mContext.getPackageName();
- lp.windowAnimations = R.style.Animation_StatusBar_HeadsUp;
-
- mWindowManager.addView(mHeadsUpNotificationView, lp);
- }
-
- private void removeHeadsUpView() {
- mWindowManager.removeView(mHeadsUpNotificationView);
- }
-
public void addIcon(String slot, int index, int viewIndex, StatusBarIcon icon) {
mIconController.addSystemIcon(slot, index, viewIndex, icon);
}
@@ -1131,30 +1111,14 @@
public void addNotification(StatusBarNotification notification, RankingMap ranking,
Entry oldEntry) {
if (DEBUG) Log.d(TAG, "addNotification key=" + notification.getKey());
- if (mUseHeadsUp && shouldInterrupt(notification)) {
- if (DEBUG) Log.d(TAG, "launching notification in heads up mode");
- Entry interruptionCandidate = oldEntry;
- if (interruptionCandidate == null) {
- final StatusBarIconView iconView = createIcon(notification);
- if (iconView == null) {
- return;
- }
- interruptionCandidate = new Entry(notification, iconView);
- }
- ViewGroup holder = mHeadsUpNotificationView.getHolder();
- if (inflateViewsForHeadsUp(interruptionCandidate, holder)) {
- // 1. Populate mHeadsUpNotificationView
- mHeadsUpNotificationView.showNotification(interruptionCandidate);
-
- // do not show the notification in the shade, yet.
- return;
- }
- }
Entry shadeEntry = createNotificationViews(notification);
if (shadeEntry == null) {
return;
}
+ if (mUseHeadsUp && shouldInterrupt(notification)) {
+ mHeadsUpManager.showNotification(shadeEntry);
+ }
if (notification.getNotification().fullScreenIntent != null) {
// Stop screensaver if the notification has a full-screen intent.
@@ -1175,18 +1139,6 @@
setAreThereNotifications();
}
- public void displayNotificationFromHeadsUp(Entry shadeEntry) {
-
- // The notification comes from the headsup, let's inflate the normal layout again
- inflateViews(shadeEntry, mStackScroller);
- shadeEntry.setInterruption();
- shadeEntry.row.setHeadsUp(false);
-
- addNotificationViews(shadeEntry, null);
- // Recalculate the position of the sliding windows and the titles.
- setAreThereNotifications();
- }
-
@Override
protected void updateNotificationRanking(RankingMap ranking) {
mNotificationData.updateRanking(ranking);
@@ -1195,10 +1147,15 @@
@Override
public void removeNotification(String key, RankingMap ranking) {
- if (ENABLE_HEADS_UP) {
- mHeadsUpNotificationView.removeNotification(key);
+ boolean defferRemoval = false;
+ if (mHeadsUpManager.isHeadsUp(key)) {
+ defferRemoval = !mHeadsUpManager.removeNotification(key);
}
-
+ if (defferRemoval) {
+ mLatestRankingMap = ranking;
+ mHeadsUpEntriesToRemoveOnSwitch.add(mHeadsUpManager.getEntry(key));
+ return;
+ }
StatusBarNotification old = removeNotificationViews(key, ranking);
if (SPEW) Log.d(TAG, "removeNotification key=" + key + " old=" + old);
@@ -1870,6 +1827,45 @@
logStateToEventlog();
}
+ @Override
+ public void OnPinnedHeadsUpExistChanged(boolean exist, boolean changeImmediatly) {
+ if (exist) {
+ mStatusBarWindowManager.setHeadsUpShowing(true);
+ } else {
+ Runnable endRunnable = new Runnable() {
+ @Override
+ public void run() {
+ if (!mHeadsUpManager.hasPinnedHeadsUp()) {
+ mStatusBarWindowManager.setHeadsUpShowing(false);
+ }
+ }
+ };
+ if (changeImmediatly) {
+ endRunnable.run();
+ } else {
+ mStackScroller.performOnAnimationFinished(endRunnable);
+ }
+ }
+ }
+
+ @Override
+ public void OnHeadsUpPinnedChanged(ExpandableNotificationRow headsUp, boolean isHeadsUp) {
+ }
+
+ @Override
+ public void OnHeadsUpStateChanged(Entry entry, boolean isHeadsUp) {
+ if (!isHeadsUp && mHeadsUpEntriesToRemoveOnSwitch.contains(entry)) {
+ removeNotification(entry.key, mLatestRankingMap);
+ mHeadsUpEntriesToRemoveOnSwitch.remove(entry);
+ if (mHeadsUpEntriesToRemoveOnSwitch.isEmpty()) {
+ mLatestRankingMap = null;
+ }
+ } else {
+ updateNotificationRanking(null);
+ }
+
+ }
+
/**
* All changes to the status bar and notifications funnel through here and are batched.
*/
@@ -1886,15 +1882,6 @@
case MSG_CLOSE_PANELS:
animateCollapsePanels();
break;
- case MSG_SHOW_HEADS_UP:
- setHeadsUpVisibility(true);
- break;
- case MSG_ESCALATE_HEADS_UP:
- escalateHeadsUp();
- case MSG_HIDE_HEADS_UP:
- mHeadsUpNotificationView.releaseImmediately();
- setHeadsUpVisibility(false);
- break;
case MSG_LAUNCH_TRANSITION_TIMEOUT:
onLaunchTransitionTimeout();
break;
@@ -1903,44 +1890,15 @@
}
@Override
- public void scheduleHeadsUpDecay(long delay) {
- mHandler.removeMessages(MSG_HIDE_HEADS_UP);
- if (mHeadsUpNotificationView.isClearable()) {
- mHandler.sendEmptyMessageDelayed(MSG_HIDE_HEADS_UP, delay);
- }
- }
-
- @Override
- public void scheduleHeadsUpOpen() {
- mHandler.removeMessages(MSG_HIDE_HEADS_UP);
- mHandler.removeMessages(MSG_SHOW_HEADS_UP);
- mHandler.sendEmptyMessage(MSG_SHOW_HEADS_UP);
- }
-
- @Override
- public void scheduleHeadsUpClose() {
- mHandler.removeMessages(MSG_HIDE_HEADS_UP);
- if (mHeadsUpNotificationView.getVisibility() != View.GONE) {
- mHandler.sendEmptyMessage(MSG_HIDE_HEADS_UP);
- }
- }
-
- @Override
- public void scheduleHeadsUpEscalation() {
- mHandler.removeMessages(MSG_HIDE_HEADS_UP);
- mHandler.removeMessages(MSG_ESCALATE_HEADS_UP);
- mHandler.sendEmptyMessage(MSG_ESCALATE_HEADS_UP);
- }
-
- /** if the interrupting notification had a fullscreen intent, fire it now. */
- private void escalateHeadsUp() {
- if (mHeadsUpNotificationView.getEntry() != null) {
- final StatusBarNotification sbn = mHeadsUpNotificationView.getEntry().notification;
- mHeadsUpNotificationView.releaseImmediately();
+ public void escalateHeadsUp() {
+ TreeSet<HeadsUpManager.HeadsUpEntry> entries = mHeadsUpManager.getSortedEntries();
+ for (HeadsUpManager.HeadsUpEntry entry : entries) {
+ final StatusBarNotification sbn = entry.entry.notification;
final Notification notification = sbn.getNotification();
if (notification.fullScreenIntent != null) {
- if (DEBUG)
+ if (DEBUG) {
Log.d(TAG, "converting a heads up to fullScreen");
+ }
try {
EventLog.writeEvent(EventLogTags.SYSUI_HEADS_UP_ESCALATION,
sbn.getKey());
@@ -1949,6 +1907,7 @@
}
}
}
+ mHeadsUpManager.releaseAllImmediately();
}
boolean panelsEnabled() {
@@ -2081,10 +2040,6 @@
// Ensure the panel is fully collapsed (just in case; bug 6765842, 7260868)
mStatusBarView.collapseAllPanels(/*animate=*/ false, false /* delayed*/);
- // reset things to their proper state
- mStackScroller.setVisibility(View.VISIBLE);
- mNotificationPanel.setVisibility(View.GONE);
-
mNotificationPanel.closeQs();
mExpandedVisible = false;
@@ -2478,8 +2433,6 @@
pw.println(Settings.Global.zenModeToString(mZenMode));
pw.print(" mUseHeadsUp=");
pw.println(mUseHeadsUp);
- pw.print(" interrupting package: ");
- pw.println(hunStateToString(mHeadsUpNotificationView.getEntry()));
dumpBarTransitions(pw, "mStatusBarView", mStatusBarView.getBarTransitions());
if (mNavigationBarView != null) {
pw.print(" mNavigationBarWindowState=");
@@ -2571,10 +2524,10 @@
if (mSecurityController != null) {
mSecurityController.dump(fd, pw, args);
}
- if (mHeadsUpNotificationView != null) {
- mHeadsUpNotificationView.dump(fd, pw, args);
+ if (mHeadsUpManager != null) {
+ mHeadsUpManager.dump(fd, pw, args);
} else {
- pw.println(" mHeadsUpNotificationView: null");
+ pw.println(" mHeadsUpManager: null");
}
pw.println("SharedPreferences:");
@@ -2672,7 +2625,7 @@
else if (Intent.ACTION_SCREEN_OFF.equals(action)) {
mScreenOn = false;
notifyNavigationBarScreenOn(false);
- notifyHeadsUpScreenOn(false);
+ notifyHeadsUpScreenOff();
finishBarAnimations();
resetUserExpandedStates();
}
@@ -2764,15 +2717,6 @@
mUserSetupObserver, mCurrentUserId);
}
- private void setHeadsUpVisibility(boolean vis) {
- if (!ENABLE_HEADS_UP) return;
- if (DEBUG) Log.v(TAG, (vis ? "showing" : "hiding") + " heads up window");
- EventLog.writeEvent(EventLogTags.SYSUI_HEADS_UP_STATUS,
- vis ? mHeadsUpNotificationView.getKey() : "",
- vis ? 1 : 0);
- mHeadsUpNotificationView.setVisibility(vis ? View.VISIBLE : View.GONE);
- }
-
/**
* Reload some of our resources when the configuration changes.
*
@@ -2791,9 +2735,6 @@
if (mNotificationPanel != null) {
mNotificationPanel.updateResources();
}
- if (mHeadsUpNotificationView != null) {
- mHeadsUpNotificationView.updateResources();
- }
if (mBrightnessMirrorController != null) {
mBrightnessMirrorController.updateResources();
}
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 0e8a794..e701783 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -18,6 +18,7 @@
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
+import android.animation.PropertyValuesHolder;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Color;
@@ -29,13 +30,18 @@
import com.android.systemui.R;
import com.android.systemui.statusbar.BackDropView;
+import com.android.systemui.statusbar.ExpandableNotificationRow;
+import com.android.systemui.statusbar.NotificationData;
import com.android.systemui.statusbar.ScrimView;
+import com.android.systemui.statusbar.policy.HeadsUpManager;
+import com.android.systemui.statusbar.stack.StackStateAnimator;
/**
* Controls both the scrim behind the notifications and in front of the notifications (when a
* security method gets shown).
*/
-public class ScrimController implements ViewTreeObserver.OnPreDrawListener {
+public class ScrimController implements ViewTreeObserver.OnPreDrawListener,
+ HeadsUpManager.OnHeadsUpChangedListener {
public static final long ANIMATION_DURATION = 220;
private static final float SCRIM_BEHIND_ALPHA = 0.62f;
@@ -43,10 +49,13 @@
private static final float SCRIM_BEHIND_ALPHA_UNLOCKING = 0.2f;
private static final float SCRIM_IN_FRONT_ALPHA = 0.75f;
private static final int TAG_KEY_ANIM = R.id.scrim;
+ private static final int TAG_HUN_START_ALPHA = R.id.hun_scrim_alpha_start;
+ private static final int TAG_HUN_END_ALPHA = R.id.hun_scrim_alpha_end;
private final ScrimView mScrimBehind;
private final ScrimView mScrimInFront;
private final UnlockMethodCache mUnlockMethodCache;
+ private final View mHeadsUpScrim;
private boolean mKeyguardShowing;
private float mFraction;
@@ -70,15 +79,22 @@
private float mDozeBehindAlpha;
private float mCurrentInFrontAlpha;
private float mCurrentBehindAlpha;
+ private float mCurrentHeadsUpAlpha = 1;
+ private int mAmountOfPinnedHeadsUps;
+ private float mTopHeadsUpDragAmount;
+ private View mDraggedHeadsUpView;
- public ScrimController(ScrimView scrimBehind, ScrimView scrimInFront, boolean scrimSrcEnabled) {
+ public ScrimController(ScrimView scrimBehind, ScrimView scrimInFront, View headsUpScrim,
+ boolean scrimSrcEnabled) {
mScrimBehind = scrimBehind;
mScrimInFront = scrimInFront;
+ mHeadsUpScrim = headsUpScrim;
final Context context = scrimBehind.getContext();
mUnlockMethodCache = UnlockMethodCache.getInstance(context);
mLinearOutSlowInInterpolator = AnimationUtils.loadInterpolator(context,
android.R.interpolator.linear_out_slow_in);
mScrimSrcEnabled = scrimSrcEnabled;
+ updateHeadsUpScrim(false);
}
public void setKeyguardShowing(boolean showing) {
@@ -217,7 +233,7 @@
}
}
- private void setScrimColor(ScrimView scrim, float alpha) {
+ private void setScrimColor(View scrim, float alpha) {
Object runningAnim = scrim.getTag(TAG_KEY_ANIM);
if (runningAnim instanceof ValueAnimator) {
((ValueAnimator) runningAnim).cancel();
@@ -236,25 +252,34 @@
}
private float getCurrentScrimAlpha(View scrim) {
- return scrim == mScrimBehind ? mCurrentBehindAlpha : mCurrentInFrontAlpha;
+ return scrim == mScrimBehind ? mCurrentBehindAlpha
+ : scrim == mScrimInFront ? mCurrentInFrontAlpha
+ : mCurrentHeadsUpAlpha;
}
private void setCurrentScrimAlpha(View scrim, float alpha) {
if (scrim == mScrimBehind) {
mCurrentBehindAlpha = alpha;
- } else {
+ } else if (scrim == mScrimInFront) {
mCurrentInFrontAlpha = alpha;
+ } else {
+ alpha = Math.max(0.0f, Math.min(1.0f, alpha));
+ mCurrentHeadsUpAlpha = alpha;
}
}
- private void updateScrimColor(ScrimView scrim) {
+ private void updateScrimColor(View scrim) {
float alpha1 = getCurrentScrimAlpha(scrim);
- float alpha2 = getDozeAlpha(scrim);
- float alpha = 1 - (1 - alpha1) * (1 - alpha2);
- scrim.setScrimColor(Color.argb((int) (alpha * 255), 0, 0, 0));
+ if (scrim instanceof ScrimView) {
+ float alpha2 = getDozeAlpha(scrim);
+ float alpha = 1 - (1 - alpha1) * (1 - alpha2);
+ ((ScrimView) scrim).setScrimColor(Color.argb((int) (alpha * 255), 0, 0, 0));
+ } else {
+ scrim.setAlpha(alpha1);
+ }
}
- private void startScrimAnimation(final ScrimView scrim, float target) {
+ private void startScrimAnimation(final View scrim, float target) {
float current = getCurrentScrimAlpha(scrim);
ValueAnimator anim = ValueAnimator.ofFloat(current, target);
anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@@ -320,4 +345,84 @@
boolean asSrc = mBackDropView.getVisibility() != View.VISIBLE && mScrimSrcEnabled;
mScrimBehind.setDrawAsSrc(asSrc);
}
+
+ @Override
+ public void OnPinnedHeadsUpExistChanged(boolean exist, boolean changeImmediatly) {
+ }
+
+ @Override
+ public void OnHeadsUpPinnedChanged(ExpandableNotificationRow headsUp, boolean isHeadsUp) {
+ if (isHeadsUp) {
+ mAmountOfPinnedHeadsUps++;
+ } else {
+ mAmountOfPinnedHeadsUps--;
+ if (headsUp == mDraggedHeadsUpView) {
+ mDraggedHeadsUpView = null;
+ mTopHeadsUpDragAmount = 0.0f;
+ }
+ }
+ updateHeadsUpScrim(true);
+ }
+
+ @Override
+ public void OnHeadsUpStateChanged(NotificationData.Entry entry, boolean isHeadsUp) {
+ }
+
+ private void updateHeadsUpScrim(boolean animate) {
+ float alpha = calculateHeadsUpAlpha();
+ ValueAnimator previousAnimator = StackStateAnimator.getChildTag(mHeadsUpScrim,
+ TAG_KEY_ANIM);
+ float animEndValue = -1;
+ if (previousAnimator != null) {
+ if ((animate || alpha == mCurrentHeadsUpAlpha)) {
+ // lets cancel any running animators
+ previousAnimator.cancel();
+ }
+ animEndValue = StackStateAnimator.getChildTag(mHeadsUpScrim,
+ TAG_HUN_START_ALPHA);
+ }
+ if (alpha != mCurrentHeadsUpAlpha && alpha != animEndValue) {
+ if (animate) {
+ startScrimAnimation(mHeadsUpScrim, alpha);
+ mHeadsUpScrim.setTag(TAG_HUN_START_ALPHA, mCurrentHeadsUpAlpha);
+ mHeadsUpScrim.setTag(TAG_HUN_END_ALPHA, alpha);
+ } else {
+ if (previousAnimator != null) {
+ float previousStartValue = StackStateAnimator.getChildTag(mHeadsUpScrim,
+ TAG_HUN_START_ALPHA);
+ float previousEndValue = StackStateAnimator.getChildTag(mHeadsUpScrim,
+ TAG_HUN_END_ALPHA);
+ // we need to increase all animation keyframes of the previous animator by the
+ // relative change to the end value
+ PropertyValuesHolder[] values = previousAnimator.getValues();
+ float relativeDiff = alpha - previousEndValue;
+ float newStartValue = previousStartValue + relativeDiff;
+ values[0].setFloatValues(newStartValue, alpha);
+ mHeadsUpScrim.setTag(TAG_HUN_START_ALPHA, newStartValue);
+ mHeadsUpScrim.setTag(TAG_HUN_END_ALPHA, alpha);
+ previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
+ } else {
+ // update the alpha directly
+ setCurrentScrimAlpha(mHeadsUpScrim, alpha);
+ updateScrimColor(mHeadsUpScrim);
+ }
+ }
+ }
+ }
+
+ public void setTopHeadsUpDragAmount(View draggedHeadsUpView, float topHeadsUpDragAmount) {
+ mTopHeadsUpDragAmount = topHeadsUpDragAmount;
+ mDraggedHeadsUpView = draggedHeadsUpView;
+ updateHeadsUpScrim(false);
+ }
+
+ private float calculateHeadsUpAlpha() {
+ if (mAmountOfPinnedHeadsUps >= 2) {
+ return 1.0f;
+ } else if (mAmountOfPinnedHeadsUps == 0) {
+ return 0.0f;
+ } else {
+ return 1.0f - mTopHeadsUpDragAmount;
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java
index 63bbf97..84a9f64 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java
@@ -130,7 +130,7 @@
private void applyHeight(State state) {
boolean expanded = state.isKeyguardShowingAndNotOccluded() || state.statusBarExpanded
- || state.keyguardFadingAway || state.bouncerShowing;
+ || state.keyguardFadingAway || state.bouncerShowing || state.headsUpShowing;
if (expanded) {
mLpChanged.height = ViewGroup.LayoutParams.MATCH_PARENT;
} else {
@@ -172,11 +172,20 @@
applyUserActivityTimeout(state);
applyInputFeatures(state);
applyFitsSystemWindows(state);
+ applyModalFlag(state);
if (mLp.copyFrom(mLpChanged) != 0) {
mWindowManager.updateViewLayout(mStatusBarView, mLp);
}
}
+ private void applyModalFlag(State state) {
+ if (state.headsUpShowing) {
+ mLpChanged.flags |= WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
+ } else {
+ mLpChanged.flags &= ~WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
+ }
+ }
+
public void setKeyguardShowing(boolean showing) {
mCurrentState.keyguardShowing = showing;
apply(mCurrentState);
@@ -218,6 +227,11 @@
apply(mCurrentState);
}
+ public void setHeadsUpShowing(boolean showing) {
+ mCurrentState.headsUpShowing = showing;
+ apply(mCurrentState);
+ }
+
/**
* @param state The {@link StatusBarState} of the status bar.
*/
@@ -235,6 +249,7 @@
boolean bouncerShowing;
boolean keyguardFadingAway;
boolean qsExpanded;
+ boolean headsUpShowing;
/**
* The {@link BaseStatusBar} state from the status bar.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
new file mode 100644
index 0000000..920a0a1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
@@ -0,0 +1,519 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.policy;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.database.ContentObserver;
+import android.os.Handler;
+import android.os.SystemClock;
+import android.provider.Settings;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.util.Pools;
+import android.view.ViewTreeObserver;
+import android.view.accessibility.AccessibilityEvent;
+
+import com.android.systemui.R;
+import com.android.systemui.statusbar.ExpandableNotificationRow;
+import com.android.systemui.statusbar.NotificationData;
+import com.android.systemui.statusbar.phone.PhoneStatusBar;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Stack;
+import java.util.TreeSet;
+
+public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsListener {
+ private static final String TAG = "HeadsUpManager";
+ private static final boolean DEBUG = false;
+ private static final String SETTING_HEADS_UP_SNOOZE_LENGTH_MS = "heads_up_snooze_length_ms";
+
+ private final int mHeadsUpNotificationDecay;
+ private final int mMinimumDisplayTime;
+
+ private final int mTouchSensitivityDelay;
+ private final ArrayMap<String, Long> mSnoozedPackages;
+ private final HashSet<OnHeadsUpChangedListener> mListeners = new HashSet<>();
+ private final int mDefaultSnoozeLengthMs;
+ private final Handler mHandler = new Handler();
+ private final Pools.Pool<HeadsUpEntry> mEntryPool = new Pools.Pool<HeadsUpEntry>() {
+
+ private Stack<HeadsUpEntry> mPoolObjects = new Stack<>();
+
+ @Override
+ public HeadsUpEntry acquire() {
+ if (!mPoolObjects.isEmpty()) {
+ return mPoolObjects.pop();
+ }
+ return new HeadsUpEntry();
+ }
+
+ @Override
+ public boolean release(HeadsUpEntry instance) {
+ instance.removeAutoCancelCallbacks();
+ mPoolObjects.push(instance);
+ return true;
+ }
+ };
+
+
+ private PhoneStatusBar mBar;
+ private int mSnoozeLengthMs;
+ private ContentObserver mSettingsObserver;
+ private HashMap<String, HeadsUpEntry> mHeadsUpEntries = new HashMap<>();
+ private TreeSet<HeadsUpEntry> mSortedEntries = new TreeSet<>();
+ private HashSet<String> mSwipedOutKeys = new HashSet<>();
+ private int mUser;
+ private Clock mClock;
+ private boolean mReleaseOnExpandFinish;
+ private boolean mTrackingHeadsUp;
+ private HashSet<NotificationData.Entry> mEntriesToRemoveAfterExpand = new HashSet<>();
+ private boolean mIsExpanded;
+ private boolean mHasPinnedHeadsUp;
+ private int[] mTmpTwoArray = new int[2];
+
+ public HeadsUpManager(final Context context, ViewTreeObserver observer) {
+ Resources resources = context.getResources();
+ mTouchSensitivityDelay = resources.getInteger(R.integer.heads_up_sensitivity_delay);
+ if (DEBUG) Log.v(TAG, "create() " + mTouchSensitivityDelay);
+ mSnoozedPackages = new ArrayMap<>();
+ mDefaultSnoozeLengthMs = resources.getInteger(R.integer.heads_up_default_snooze_length_ms);
+ mSnoozeLengthMs = mDefaultSnoozeLengthMs;
+ mMinimumDisplayTime = resources.getInteger(R.integer.heads_up_notification_minimum_time);
+ mHeadsUpNotificationDecay = resources.getInteger(R.integer.heads_up_notification_decay);
+ mClock = new Clock();
+
+ mSnoozeLengthMs = Settings.Global.getInt(context.getContentResolver(),
+ SETTING_HEADS_UP_SNOOZE_LENGTH_MS, mDefaultSnoozeLengthMs);
+ mSettingsObserver = new ContentObserver(mHandler) {
+ @Override
+ public void onChange(boolean selfChange) {
+ final int packageSnoozeLengthMs = Settings.Global.getInt(
+ context.getContentResolver(), SETTING_HEADS_UP_SNOOZE_LENGTH_MS, -1);
+ if (packageSnoozeLengthMs > -1 && packageSnoozeLengthMs != mSnoozeLengthMs) {
+ mSnoozeLengthMs = packageSnoozeLengthMs;
+ if (DEBUG) Log.v(TAG, "mSnoozeLengthMs = " + mSnoozeLengthMs);
+ }
+ }
+ };
+ context.getContentResolver().registerContentObserver(
+ Settings.Global.getUriFor(SETTING_HEADS_UP_SNOOZE_LENGTH_MS), false,
+ mSettingsObserver);
+ if (DEBUG) Log.v(TAG, "mSnoozeLengthMs = " + mSnoozeLengthMs);
+ observer.addOnComputeInternalInsetsListener(this);
+ }
+
+ public void setBar(PhoneStatusBar bar) {
+ mBar = bar;
+ }
+
+ public void addListener(OnHeadsUpChangedListener listener) {
+ mListeners.add(listener);
+ }
+
+ public PhoneStatusBar getBar() {
+ return mBar;
+ }
+
+ /**
+ * Called when posting a new notification to the heads up.
+ */
+ public void showNotification(NotificationData.Entry headsUp) {
+ if (DEBUG) Log.v(TAG, "showNotification");
+ addHeadsUpEntry(headsUp);
+ updateNotification(headsUp, true);
+ headsUp.setInterruption();
+ }
+
+ /**
+ * Called when updating or posting a notification to the heads up.
+ */
+ public void updateNotification(NotificationData.Entry headsUp, boolean alert) {
+ if (DEBUG) Log.v(TAG, "updateNotification");
+
+ headsUp.row.setChildrenExpanded(false /* expanded */, false /* animated */);
+ headsUp.row.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
+
+ if (alert) {
+ HeadsUpEntry headsUpEntry = mHeadsUpEntries.get(headsUp.key);
+ headsUpEntry.updateEntry();
+ setEntryToShade(headsUpEntry, mIsExpanded, false /* justAdded */, false);
+ }
+ }
+
+ private void addHeadsUpEntry(NotificationData.Entry entry) {
+ HeadsUpEntry headsUpEntry = mEntryPool.acquire();
+
+ // This will also add the entry to the sortedList
+ headsUpEntry.setEntry(entry);
+ mHeadsUpEntries.put(entry.key, headsUpEntry);
+ entry.row.setHeadsUp(true);
+ setEntryToShade(headsUpEntry, mIsExpanded /* inShade */, true /* justAdded */, false);
+ for (OnHeadsUpChangedListener listener : mListeners) {
+ listener.OnHeadsUpStateChanged(entry, true);
+ }
+ entry.row.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
+ }
+
+ private void setEntryToShade(HeadsUpEntry headsUpEntry, boolean inShade, boolean justAdded,
+ boolean forceImmediate) {
+ ExpandableNotificationRow row = headsUpEntry.entry.row;
+ if (row.isInShade() != inShade || justAdded) {
+ row.setInShade(inShade);
+ if (!justAdded || !inShade) {
+ updatePinnedHeadsUpState(forceImmediate);
+ for (OnHeadsUpChangedListener listener : mListeners) {
+ listener.OnHeadsUpPinnedChanged(row, !inShade);
+ }
+ }
+ }
+ }
+
+ private void removeHeadsUpEntry(NotificationData.Entry entry) {
+ HeadsUpEntry remove = mHeadsUpEntries.remove(entry.key);
+ mSortedEntries.remove(remove);
+ mEntryPool.release(remove);
+ entry.row.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
+ entry.row.setHeadsUp(false);
+ setEntryToShade(remove, true /* inShade */, false /* justAdded */,
+ false /* forceImmediate */);
+ for (OnHeadsUpChangedListener listener : mListeners) {
+ listener.OnHeadsUpStateChanged(entry, false);
+ }
+ }
+
+ private void updatePinnedHeadsUpState(boolean forceImmediate) {
+ boolean hasPinnedHeadsUp = hasPinnedHeadsUpInternal();
+ if (hasPinnedHeadsUp == mHasPinnedHeadsUp) {
+ return;
+ }
+ mHasPinnedHeadsUp = hasPinnedHeadsUp;
+ for (OnHeadsUpChangedListener listener :mListeners) {
+ listener.OnPinnedHeadsUpExistChanged(hasPinnedHeadsUp, forceImmediate);
+ }
+ }
+
+ /**
+ * React to the removal of the notification in the heads up.
+ *
+ * @return true if the notification was removed and false if it still needs to be kept around
+ * for a bit since it wasn't shown long enough
+ */
+ public boolean removeNotification(String key) {
+ if (DEBUG) Log.v(TAG, "remove");
+ if (wasShownLongEnough(key)) {
+ releaseImmediately(key);
+ return true;
+ } else {
+ getHeadsUpEntry(key).hideAsSoonAsPossible();
+ return false;
+ }
+ }
+
+ private boolean wasShownLongEnough(String key) {
+ HeadsUpEntry headsUpEntry = getHeadsUpEntry(key);
+ HeadsUpEntry topEntry = getTopEntry();
+ if (mSwipedOutKeys.contains(key)) {
+ // We always instantly dismiss views being manually swiped out.
+ mSwipedOutKeys.remove(key);
+ return true;
+ }
+ if (headsUpEntry != topEntry) {
+ return true;
+ }
+ return headsUpEntry.wasShownLongEnough();
+ }
+
+ public boolean isHeadsUp(String key) {
+ return mHeadsUpEntries.containsKey(key);
+ }
+
+
+ /**
+ * Push any current Heads Up notification down into the shade.
+ */
+ public void releaseAllImmediately() {
+ if (DEBUG) Log.v(TAG, "releaseAllImmediately");
+ HashSet<String> keys = new HashSet<>(mHeadsUpEntries.keySet());
+ for (String key: keys) {
+ releaseImmediately(key);
+ }
+ }
+
+ public void releaseImmediately(String key) {
+ HeadsUpEntry headsUpEntry = getHeadsUpEntry(key);
+ if (headsUpEntry == null) {
+ return;
+ }
+ NotificationData.Entry shadeEntry = headsUpEntry.entry;
+ removeHeadsUpEntry(shadeEntry);
+ }
+
+ public boolean isSnoozed(String packageName) {
+ final String key = snoozeKey(packageName, mUser);
+ Long snoozedUntil = mSnoozedPackages.get(key);
+ if (snoozedUntil != null) {
+ if (snoozedUntil > SystemClock.elapsedRealtime()) {
+ if (DEBUG) Log.v(TAG, key + " snoozed");
+ return true;
+ }
+ mSnoozedPackages.remove(packageName);
+ }
+ return false;
+ }
+
+ public void snooze() {
+ for (String key: mHeadsUpEntries.keySet()) {
+ HeadsUpEntry entry = mHeadsUpEntries.get(key);
+ String packageName = entry.entry.notification.getPackageName();
+ mSnoozedPackages.put(snoozeKey(packageName, mUser),
+ SystemClock.elapsedRealtime() + mSnoozeLengthMs);
+ }
+ mReleaseOnExpandFinish = true;
+ }
+
+ private static String snoozeKey(String packageName, int user) {
+ return user + "," + packageName;
+ }
+
+ private HeadsUpEntry getHeadsUpEntry(String key) {
+ return mHeadsUpEntries.get(key);
+ }
+
+ public NotificationData.Entry getEntry(String key) {
+ return mHeadsUpEntries.get(key).entry;
+ }
+
+ public TreeSet<HeadsUpEntry> getSortedEntries() {
+ return mSortedEntries;
+ }
+
+ public HeadsUpEntry getTopEntry() {
+ return mSortedEntries.isEmpty() ? null : mSortedEntries.first();
+ }
+
+ /**
+ * @param key the key of the touched notification
+ * @return whether the touch is valid and should not be discarded
+ */
+ public boolean shouldSwallowClick(String key) {
+ if (mClock.currentTimeMillis() < mHeadsUpEntries.get(key).postTime) {
+ return true;
+ }
+ return false;
+ }
+
+ public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo info) {
+ if (!mIsExpanded && mHasPinnedHeadsUp) {
+ int minX = Integer.MAX_VALUE;
+ int maxX = 0;
+ int minY = Integer.MAX_VALUE;
+ int maxY = 0;
+ for (HeadsUpEntry entry: mSortedEntries) {
+ ExpandableNotificationRow row = entry.entry.row;
+ if (!row.isInShade()) {
+ row.getLocationOnScreen(mTmpTwoArray);
+ minX = Math.min(minX, mTmpTwoArray[0]);
+ minY = Math.min(minY, 0);
+ maxX = Math.max(maxX, mTmpTwoArray[0] + row.getWidth());
+ maxY = Math.max(maxY, row.getHeadsUpHeight());
+ }
+ }
+
+ info.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
+ info.touchableRegion.set(minX, minY, maxX, maxY);
+ }
+ }
+
+ public void setUser(int user) {
+ mUser = user;
+ }
+
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ pw.println("HeadsUpManager state:");
+ pw.print(" mTouchSensitivityDelay="); pw.println(mTouchSensitivityDelay);
+ pw.print(" mSnoozeLengthMs="); pw.println(mSnoozeLengthMs);
+ pw.print(" now="); pw.println(SystemClock.elapsedRealtime());
+ pw.print(" mUser="); pw.println(mUser);
+ for (HeadsUpEntry entry: mSortedEntries) {
+ pw.print(" HeadsUpEntry="); pw.println(entry.entry);
+ }
+ int N = mSnoozedPackages.size();
+ pw.println(" snoozed packages: " + N);
+ for (int i = 0; i < N; i++) {
+ pw.print(" "); pw.print(mSnoozedPackages.valueAt(i));
+ pw.print(", "); pw.println(mSnoozedPackages.keyAt(i));
+ }
+ }
+
+ public boolean hasPinnedHeadsUp() {
+ return mHasPinnedHeadsUp;
+ }
+
+ private boolean hasPinnedHeadsUpInternal() {
+ for (String key: mHeadsUpEntries.keySet()) {
+ HeadsUpEntry entry = mHeadsUpEntries.get(key);
+ if (!entry.entry.row.isInShade()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public void addSwipedOutKey(String key) {
+ mSwipedOutKeys.add(key);
+ }
+
+ public float getHighestPinnedHeadsUp() {
+ float max = 0;
+ for (HeadsUpEntry entry: mSortedEntries) {
+ if (!entry.entry.row.isInShade()) {
+ max = Math.max(max, entry.entry.row.getActualHeight());
+ }
+ }
+ return max;
+ }
+
+ public void releaseAllToShade() {
+ for (String key: mHeadsUpEntries.keySet()) {
+ HeadsUpEntry entry = mHeadsUpEntries.get(key);
+ setEntryToShade(entry, true /* toShade */, false /* justAdded */,
+ true /* forceImmediate */);
+ }
+ }
+
+ public void onExpandingFinished() {
+ if (mReleaseOnExpandFinish) {
+ releaseAllImmediately();
+ mReleaseOnExpandFinish = false;
+ } else {
+ for (NotificationData.Entry entry : mEntriesToRemoveAfterExpand) {
+ removeHeadsUpEntry(entry);
+ }
+ mEntriesToRemoveAfterExpand.clear();
+ }
+ }
+
+ public void setTrackingHeadsUp(boolean trackingHeadsUp) {
+ mTrackingHeadsUp = trackingHeadsUp;
+ }
+
+ public void setIsExpanded(boolean isExpanded) {
+ if (isExpanded != mIsExpanded) {
+ mIsExpanded = isExpanded;
+ if (isExpanded) {
+ releaseAllToShade();
+ }
+ }
+ }
+
+ public int getTopHeadsUpHeight() {
+ HeadsUpEntry topEntry = getTopEntry();
+ return topEntry != null ? topEntry.entry.row.getHeadsUpHeight() : 0;
+ }
+
+ public int compare(NotificationData.Entry a, NotificationData.Entry b) {
+ HeadsUpEntry aEntry = getHeadsUpEntry(a.key);
+ HeadsUpEntry bEntry = getHeadsUpEntry(b.key);
+ if (aEntry == null || bEntry == null) {
+ return aEntry == null ? 1 : -1;
+ }
+ return aEntry.compareTo(bEntry);
+ }
+
+ public class HeadsUpEntry implements Comparable<HeadsUpEntry> {
+ public NotificationData.Entry entry;
+ public long postTime;
+ public long earliestRemovaltime;
+ private Runnable mRemoveHeadsUpRunnable;
+
+ public void setEntry(final NotificationData.Entry entry) {
+ this.entry = entry;
+
+ // The actual post time will be just after the heads-up really slided in
+ postTime = mClock.currentTimeMillis() + mTouchSensitivityDelay;
+ mRemoveHeadsUpRunnable = new Runnable() {
+ @Override
+ public void run() {
+ if (!mTrackingHeadsUp) {
+ removeHeadsUpEntry(entry);
+ } else {
+ mEntriesToRemoveAfterExpand.add(entry);
+ }
+ }
+ };
+ updateEntry();
+ }
+
+ public void updateEntry() {
+ long currentTime = mClock.currentTimeMillis();
+ postTime = Math.max(postTime, currentTime);
+ long finishTime = postTime + mHeadsUpNotificationDecay;
+ long removeDelay = Math.max(finishTime - currentTime, mMinimumDisplayTime);
+ earliestRemovaltime = currentTime + mMinimumDisplayTime;
+ removeAutoCancelCallbacks();
+ mHandler.postDelayed(mRemoveHeadsUpRunnable, removeDelay);
+ updateSortOrder(HeadsUpEntry.this);
+ }
+
+ @Override
+ public int compareTo(HeadsUpEntry o) {
+ return postTime < o.postTime ? 1
+ : postTime == o.postTime ? 0
+ : -1;
+ }
+
+ public void removeAutoCancelCallbacks() {
+ mHandler.removeCallbacks(mRemoveHeadsUpRunnable);
+ }
+
+ public boolean wasShownLongEnough() {
+ return earliestRemovaltime < mClock.currentTimeMillis();
+ }
+
+ public void hideAsSoonAsPossible() {
+ removeAutoCancelCallbacks();
+ mHandler.postDelayed(mRemoveHeadsUpRunnable,
+ earliestRemovaltime - mClock.currentTimeMillis());
+ }
+ }
+
+ /**
+ * Update the sorted heads up order.
+ *
+ * @param headsUpEntry the headsUp that changed
+ */
+ private void updateSortOrder(HeadsUpEntry headsUpEntry) {
+ mSortedEntries.remove(headsUpEntry);
+ mSortedEntries.add(headsUpEntry);
+ }
+
+ public static class Clock {
+ public long currentTimeMillis() {
+ return SystemClock.elapsedRealtime();
+ }
+ }
+
+ public interface OnHeadsUpChangedListener {
+ void OnPinnedHeadsUpExistChanged(boolean exist, boolean changeImmediatly);
+ void OnHeadsUpPinnedChanged(ExpandableNotificationRow headsUp, boolean isHeadsUp);
+ void OnHeadsUpStateChanged(NotificationData.Entry entry, boolean isHeadsUp);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpNotificationView.java
deleted file mode 100644
index 1e40bab..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpNotificationView.java
+++ /dev/null
@@ -1,622 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.policy;
-
-import android.content.Context;
-import android.content.res.Configuration;
-import android.content.res.Resources;
-import android.database.ContentObserver;
-import android.graphics.Outline;
-import android.graphics.Rect;
-import android.os.SystemClock;
-import android.provider.Settings;
-import android.util.ArrayMap;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.ViewConfiguration;
-import android.view.ViewGroup;
-import android.view.ViewOutlineProvider;
-import android.view.ViewTreeObserver;
-import android.view.accessibility.AccessibilityEvent;
-import android.widget.FrameLayout;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.systemui.ExpandHelper;
-import com.android.systemui.Gefingerpoken;
-import com.android.systemui.R;
-import com.android.systemui.SwipeHelper;
-import com.android.systemui.statusbar.ExpandableView;
-import com.android.systemui.statusbar.NotificationData;
-import com.android.systemui.statusbar.phone.PhoneStatusBar;
-
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-
-public class HeadsUpNotificationView extends FrameLayout implements SwipeHelper.Callback, ExpandHelper.Callback,
- ViewTreeObserver.OnComputeInternalInsetsListener {
- private static final String TAG = "HeadsUpNotificationView";
- private static final boolean DEBUG = false;
- private static final boolean SPEW = DEBUG;
- private static final String SETTING_HEADS_UP_SNOOZE_LENGTH_MS = "heads_up_snooze_length_ms";
-
- Rect mTmpRect = new Rect();
- int[] mTmpTwoArray = new int[2];
-
- private final int mHeadsUpNotificationDecay;
- private final int mMinimumDisplayTime;
-
- private final int mTouchSensitivityDelay;
- private final float mMaxAlpha = 1f;
- private final ArrayMap<String, Long> mSnoozedPackages;
- private final int mDefaultSnoozeLengthMs;
-
- private SwipeHelper mSwipeHelper;
- private EdgeSwipeHelper mEdgeSwipeHelper;
-
- private PhoneStatusBar mBar;
-
- private long mLingerUntilMs;
- private long mStartTouchTime;
- private ViewGroup mContentHolder;
- private int mSnoozeLengthMs;
- private ContentObserver mSettingsObserver;
-
- private NotificationData.Entry mHeadsUp;
- private int mUser;
- private String mMostRecentPackageName;
- private boolean mTouched;
- private Clock mClock;
-
- public static class Clock {
- public long currentTimeMillis() {
- return SystemClock.elapsedRealtime();
- }
- }
-
- public HeadsUpNotificationView(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public HeadsUpNotificationView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- Resources resources = context.getResources();
- mTouchSensitivityDelay = resources.getInteger(R.integer.heads_up_sensitivity_delay);
- if (DEBUG) Log.v(TAG, "create() " + mTouchSensitivityDelay);
- mSnoozedPackages = new ArrayMap<>();
- mDefaultSnoozeLengthMs = resources.getInteger(R.integer.heads_up_default_snooze_length_ms);
- mSnoozeLengthMs = mDefaultSnoozeLengthMs;
- mMinimumDisplayTime = resources.getInteger(R.integer.heads_up_notification_minimum_time);
- mHeadsUpNotificationDecay = resources.getInteger(R.integer.heads_up_notification_decay);
- mClock = new Clock();
- }
-
- @VisibleForTesting
- public HeadsUpNotificationView(Context context, Clock clock, SwipeHelper swipeHelper,
- EdgeSwipeHelper edgeSwipeHelper, int headsUpNotificationDecay, int minimumDisplayTime,
- int touchSensitivityDelay, int snoozeLength) {
- super(context, null);
- mClock = clock;
- mSwipeHelper = swipeHelper;
- mEdgeSwipeHelper = edgeSwipeHelper;
- mMinimumDisplayTime = minimumDisplayTime;
- mHeadsUpNotificationDecay = headsUpNotificationDecay;
- mTouchSensitivityDelay = touchSensitivityDelay;
- mSnoozedPackages = new ArrayMap<>();
- mDefaultSnoozeLengthMs = snoozeLength;
- }
-
- public void updateResources() {
- if (mContentHolder != null) {
- final LayoutParams lp = (LayoutParams) mContentHolder.getLayoutParams();
- lp.width = getResources().getDimensionPixelSize(R.dimen.notification_panel_width);
- lp.gravity = getResources().getInteger(R.integer.notification_panel_layout_gravity);
- mContentHolder.setLayoutParams(lp);
- }
- }
-
- public void setBar(PhoneStatusBar bar) {
- mBar = bar;
- }
-
- public PhoneStatusBar getBar() {
- return mBar;
- }
-
- public ViewGroup getHolder() {
- return mContentHolder;
- }
-
- /**
- * Called when posting a new notification to the heads up.
- */
- public void showNotification(NotificationData.Entry headsUp) {
- if (DEBUG) Log.v(TAG, "showNotification");
- if (mHeadsUp != null) {
- // bump any previous heads up back to the shade
- releaseImmediately();
- }
- mTouched = false;
- updateNotification(headsUp, true);
- mLingerUntilMs = mClock.currentTimeMillis() + mMinimumDisplayTime;
- }
-
- /**
- * Called when updating or posting a notification to the heads up.
- */
- public void updateNotification(NotificationData.Entry headsUp, boolean alert) {
- if (DEBUG) Log.v(TAG, "updateNotification");
-
- if (mHeadsUp == headsUp) {
- resetViewForHeadsup();
- // This is an in-place update. Noting more to do.
- return;
- }
-
- mHeadsUp = headsUp;
-
- if (mContentHolder != null) {
- mContentHolder.removeAllViews();
- }
-
- if (mHeadsUp != null) {
- mMostRecentPackageName = mHeadsUp.notification.getPackageName();
- if (mHeadsUp.row != null) {
- resetViewForHeadsup();
- }
-
- mStartTouchTime = SystemClock.elapsedRealtime() + mTouchSensitivityDelay;
- if (mContentHolder != null) { // only null in tests and before we are attached to a window
- mContentHolder.setX(0);
- mContentHolder.setVisibility(View.VISIBLE);
- mContentHolder.setAlpha(mMaxAlpha);
- mContentHolder.addView(mHeadsUp.row);
- sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
-
- mSwipeHelper.snapChild(mContentHolder, 1f);
- }
-
- mHeadsUp.setInterruption();
- }
- if (alert) {
- // Make sure the heads up window is open.
- mBar.scheduleHeadsUpOpen();
- mBar.scheduleHeadsUpDecay(mHeadsUpNotificationDecay);
- }
- }
-
- private void resetViewForHeadsup() {
- if (mHeadsUp.row.areChildrenExpanded()) {
- mHeadsUp.row.setChildrenExpanded(false /* expanded */, false /* animated */);
- }
- mHeadsUp.row.setSystemExpanded(true);
- mHeadsUp.row.setSensitive(false);
- mHeadsUp.row.setHeadsUp(true);
- mHeadsUp.row.setTranslationY(0);
- mHeadsUp.row.setTranslationZ(0);
- mHeadsUp.row.setHideSensitive(
- false, false /* animated */, 0 /* delay */, 0 /* duration */);
- }
-
- /**
- * Possibly enter the lingering state by delaying the closing of the window.
- *
- * @return true if the notification has entered the lingering state.
- */
- private boolean startLingering(boolean removed) {
- final long now = mClock.currentTimeMillis();
- if (!mTouched && mHeadsUp != null && now < mLingerUntilMs) {
- if (removed) {
- mHeadsUp = null;
- }
- mBar.scheduleHeadsUpDecay(mLingerUntilMs - now);
- return true;
- }
- return false;
- }
-
- /**
- * React to the removal of the notification in the heads up.
- */
- public void removeNotification(String key) {
- if (DEBUG) Log.v(TAG, "remove");
- if (mHeadsUp == null || !mHeadsUp.key.equals(key)) {
- return;
- }
- if (!startLingering(/* removed */ true)) {
- mHeadsUp = null;
- releaseImmediately();
- }
- }
-
- /**
- * Ask for any current Heads Up notification to be pushed down into the shade.
- */
- public void release() {
- if (DEBUG) Log.v(TAG, "release");
- if (!startLingering(/* removed */ false)) {
- releaseImmediately();
- }
- }
-
- /**
- * Push any current Heads Up notification down into the shade.
- */
- public void releaseImmediately() {
- if (DEBUG) Log.v(TAG, "releaseImmediately");
- if (mHeadsUp != null) {
- mContentHolder.removeView(mHeadsUp.row);
- mBar.displayNotificationFromHeadsUp(mHeadsUp);
- }
- mHeadsUp = null;
- mBar.scheduleHeadsUpClose();
- }
-
- @Override
- protected void onVisibilityChanged(View changedView, int visibility) {
- super.onVisibilityChanged(changedView, visibility);
- if (DEBUG) Log.v(TAG, "onVisibilityChanged: " + visibility);
- if (changedView.getVisibility() == VISIBLE) {
- mStartTouchTime = mClock.currentTimeMillis() + mTouchSensitivityDelay;
- sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
- }
- }
-
- public boolean isSnoozed(String packageName) {
- final String key = snoozeKey(packageName, mUser);
- Long snoozedUntil = mSnoozedPackages.get(key);
- if (snoozedUntil != null) {
- if (snoozedUntil > SystemClock.elapsedRealtime()) {
- if (DEBUG) Log.v(TAG, key + " snoozed");
- return true;
- }
- mSnoozedPackages.remove(packageName);
- }
- return false;
- }
-
- private void snooze() {
- if (mMostRecentPackageName != null) {
- mSnoozedPackages.put(snoozeKey(mMostRecentPackageName, mUser),
- SystemClock.elapsedRealtime() + mSnoozeLengthMs);
- }
- releaseImmediately();
- }
-
- private static String snoozeKey(String packageName, int user) {
- return user + "," + packageName;
- }
-
- public boolean isShowing(String key) {
- return mHeadsUp != null && mHeadsUp.key.equals(key);
- }
-
- public NotificationData.Entry getEntry() {
- return mHeadsUp;
- }
-
- public boolean isClearable() {
- return mHeadsUp == null || mHeadsUp.notification.isClearable();
- }
-
- // ViewGroup methods
-
-private static final ViewOutlineProvider CONTENT_HOLDER_OUTLINE_PROVIDER =
- new ViewOutlineProvider() {
- @Override
- public void getOutline(View view, Outline outline) {
- int outlineLeft = view.getPaddingLeft();
- int outlineTop = view.getPaddingTop();
-
- // Apply padding to shadow.
- outline.setRect(outlineLeft, outlineTop,
- view.getWidth() - outlineLeft - view.getPaddingRight(),
- view.getHeight() - outlineTop - view.getPaddingBottom());
- }
- };
-
- @Override
- public void onAttachedToWindow() {
- final ViewConfiguration viewConfiguration = ViewConfiguration.get(getContext());
- float touchSlop = viewConfiguration.getScaledTouchSlop();
- mSwipeHelper = new SwipeHelper(SwipeHelper.X, this, getContext());
- mSwipeHelper.setMaxSwipeProgress(mMaxAlpha);
- mEdgeSwipeHelper = new EdgeSwipeHelper(this, touchSlop);
-
- int minHeight = getResources().getDimensionPixelSize(R.dimen.notification_min_height);
- int maxHeight = getResources().getDimensionPixelSize(R.dimen.notification_max_height);
-
- mContentHolder = (ViewGroup) findViewById(R.id.content_holder);
- mContentHolder.setOutlineProvider(CONTENT_HOLDER_OUTLINE_PROVIDER);
-
- mSnoozeLengthMs = Settings.Global.getInt(mContext.getContentResolver(),
- SETTING_HEADS_UP_SNOOZE_LENGTH_MS, mDefaultSnoozeLengthMs);
- mSettingsObserver = new ContentObserver(getHandler()) {
- @Override
- public void onChange(boolean selfChange) {
- final int packageSnoozeLengthMs = Settings.Global.getInt(
- mContext.getContentResolver(), SETTING_HEADS_UP_SNOOZE_LENGTH_MS, -1);
- if (packageSnoozeLengthMs > -1 && packageSnoozeLengthMs != mSnoozeLengthMs) {
- mSnoozeLengthMs = packageSnoozeLengthMs;
- if (DEBUG) Log.v(TAG, "mSnoozeLengthMs = " + mSnoozeLengthMs);
- }
- }
- };
- mContext.getContentResolver().registerContentObserver(
- Settings.Global.getUriFor(SETTING_HEADS_UP_SNOOZE_LENGTH_MS), false,
- mSettingsObserver);
- if (DEBUG) Log.v(TAG, "mSnoozeLengthMs = " + mSnoozeLengthMs);
-
- if (mHeadsUp != null) {
- // whoops, we're on already!
- showNotification(mHeadsUp);
- }
-
- getViewTreeObserver().addOnComputeInternalInsetsListener(this);
- }
-
-
- @Override
- protected void onDetachedFromWindow() {
- mContext.getContentResolver().unregisterContentObserver(mSettingsObserver);
- }
-
- @Override
- public boolean onInterceptTouchEvent(MotionEvent ev) {
- if (DEBUG) Log.v(TAG, "onInterceptTouchEvent()");
- if (mClock.currentTimeMillis() < mStartTouchTime) {
- return true;
- }
- mTouched = true;
- return mEdgeSwipeHelper.onInterceptTouchEvent(ev)
- || mSwipeHelper.onInterceptTouchEvent(ev)
- || mHeadsUp == null // lingering
- || super.onInterceptTouchEvent(ev);
- }
-
- // View methods
-
- @Override
- public void onDraw(android.graphics.Canvas c) {
- super.onDraw(c);
- if (DEBUG) {
- //Log.d(TAG, "onDraw: canvas height: " + c.getHeight() + "px; measured height: "
- // + getMeasuredHeight() + "px");
- c.save();
- c.clipRect(6, 6, c.getWidth() - 6, getMeasuredHeight() - 6,
- android.graphics.Region.Op.DIFFERENCE);
- c.drawColor(0xFFcc00cc);
- c.restore();
- }
- }
-
- @Override
- public boolean onTouchEvent(MotionEvent ev) {
- if (mClock.currentTimeMillis() < mStartTouchTime) {
- return false;
- }
-
- final boolean wasRemoved = mHeadsUp == null;
- if (!wasRemoved) {
- mBar.scheduleHeadsUpDecay(mHeadsUpNotificationDecay);
- }
- return mEdgeSwipeHelper.onTouchEvent(ev)
- || mSwipeHelper.onTouchEvent(ev)
- || wasRemoved
- || super.onTouchEvent(ev);
- }
-
- @Override
- protected void onConfigurationChanged(Configuration newConfig) {
- super.onConfigurationChanged(newConfig);
- float densityScale = getResources().getDisplayMetrics().density;
- mSwipeHelper.setDensityScale(densityScale);
- float pagingTouchSlop = ViewConfiguration.get(getContext()).getScaledPagingTouchSlop();
- mSwipeHelper.setPagingTouchSlop(pagingTouchSlop);
- }
-
- // ExpandHelper.Callback methods
-
- @Override
- public ExpandableView getChildAtRawPosition(float x, float y) {
- return getChildAtPosition(x, y);
- }
-
- @Override
- public ExpandableView getChildAtPosition(float x, float y) {
- return mHeadsUp == null ? null : mHeadsUp.row;
- }
-
- @Override
- public boolean canChildBeExpanded(View v) {
- return mHeadsUp != null && mHeadsUp.row == v && mHeadsUp.row.isExpandable();
- }
-
- @Override
- public void setUserExpandedChild(View v, boolean userExpanded) {
- if (mHeadsUp != null && mHeadsUp.row == v) {
- mHeadsUp.row.setUserExpanded(userExpanded);
- }
- }
-
- @Override
- public void setUserLockedChild(View v, boolean userLocked) {
- if (mHeadsUp != null && mHeadsUp.row == v) {
- mHeadsUp.row.setUserLocked(userLocked);
- }
- }
-
- @Override
- public void expansionStateChanged(boolean isExpanding) {
-
- }
-
- // SwipeHelper.Callback methods
-
- @Override
- public boolean canChildBeDismissed(View v) {
- return true;
- }
-
- @Override
- public boolean isAntiFalsingNeeded() {
- return false;
- }
-
- @Override
- public float getFalsingThresholdFactor() {
- return 1.0f;
- }
-
- @Override
- public void onChildDismissed(View v) {
- Log.v(TAG, "User swiped heads up to dismiss");
- if (mHeadsUp != null && mHeadsUp.notification.isClearable()) {
- mBar.onNotificationClear(mHeadsUp.notification);
- mHeadsUp = null;
- }
- releaseImmediately();
- }
-
- @Override
- public void onBeginDrag(View v) {
- }
-
- @Override
- public void onDragCancelled(View v) {
- mContentHolder.setAlpha(mMaxAlpha); // sometimes this isn't quite reset
- }
-
- @Override
- public void onChildSnappedBack(View animView) {
- }
-
- @Override
- public boolean updateSwipeProgress(View animView, boolean dismissable, float swipeProgress) {
- getBackground().setAlpha((int) (255 * swipeProgress));
- return false;
- }
-
- @Override
- public View getChildAtPosition(MotionEvent ev) {
- return mContentHolder;
- }
-
- @Override
- public View getChildContentView(View v) {
- return mContentHolder;
- }
-
- @Override
- public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo info) {
- mContentHolder.getLocationOnScreen(mTmpTwoArray);
-
- info.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
- info.touchableRegion.set(mTmpTwoArray[0], mTmpTwoArray[1],
- mTmpTwoArray[0] + mContentHolder.getWidth(),
- mTmpTwoArray[1] + mContentHolder.getHeight());
- }
-
- public void escalate() {
- mBar.scheduleHeadsUpEscalation();
- }
-
- public String getKey() {
- return mHeadsUp == null ? null : mHeadsUp.notification.getKey();
- }
-
- public void setUser(int user) {
- mUser = user;
- }
-
- public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
- pw.println("HeadsUpNotificationView state:");
- pw.print(" mTouchSensitivityDelay="); pw.println(mTouchSensitivityDelay);
- pw.print(" mSnoozeLengthMs="); pw.println(mSnoozeLengthMs);
- pw.print(" mLingerUntilMs="); pw.println(mLingerUntilMs);
- pw.print(" mTouched="); pw.println(mTouched);
- pw.print(" mMostRecentPackageName="); pw.println(mMostRecentPackageName);
- pw.print(" mStartTouchTime="); pw.println(mStartTouchTime);
- pw.print(" now="); pw.println(SystemClock.elapsedRealtime());
- pw.print(" mUser="); pw.println(mUser);
- if (mHeadsUp == null) {
- pw.println(" mHeadsUp=null");
- } else {
- pw.print(" mHeadsUp="); pw.println(mHeadsUp.notification.getKey());
- }
- int N = mSnoozedPackages.size();
- pw.println(" snoozed packages: " + N);
- for (int i = 0; i < N; i++) {
- pw.print(" "); pw.print(mSnoozedPackages.valueAt(i));
- pw.print(", "); pw.println(mSnoozedPackages.keyAt(i));
- }
- }
-
- public static class EdgeSwipeHelper implements Gefingerpoken {
- private static final boolean DEBUG_EDGE_SWIPE = false;
- private final float mTouchSlop;
- private final HeadsUpNotificationView mHeadsUpView;
- private boolean mConsuming;
- private float mFirstY;
- private float mFirstX;
-
- public EdgeSwipeHelper(HeadsUpNotificationView headsUpView, float touchSlop) {
- mHeadsUpView = headsUpView;
- mTouchSlop = touchSlop;
- }
-
- @Override
- public boolean onInterceptTouchEvent(MotionEvent ev) {
- switch (ev.getActionMasked()) {
- case MotionEvent.ACTION_DOWN:
- if (DEBUG_EDGE_SWIPE) Log.d(TAG, "action down " + ev.getY());
- mFirstX = ev.getX();
- mFirstY = ev.getY();
- mConsuming = false;
- break;
-
- case MotionEvent.ACTION_MOVE:
- if (DEBUG_EDGE_SWIPE) Log.d(TAG, "action move " + ev.getY());
- final float dY = ev.getY() - mFirstY;
- final float daX = Math.abs(ev.getX() - mFirstX);
- final float daY = Math.abs(dY);
- if (!mConsuming && daX < daY && daY > mTouchSlop) {
- mHeadsUpView.snooze();
- if (dY > 0) {
- if (DEBUG_EDGE_SWIPE) Log.d(TAG, "found an open");
- mHeadsUpView.getBar().animateExpandNotificationsPanel();
- }
- mConsuming = true;
- }
- break;
-
- case MotionEvent.ACTION_UP:
- case MotionEvent.ACTION_CANCEL:
- if (DEBUG_EDGE_SWIPE) Log.d(TAG, "action done");
- mConsuming = false;
- break;
- }
- return mConsuming;
- }
-
- @Override
- public boolean onTouchEvent(MotionEvent ev) {
- return mConsuming;
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java
index 8e677f1..f2b971f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java
@@ -17,9 +17,13 @@
package com.android.systemui.statusbar.stack;
import android.view.View;
+
import com.android.systemui.statusbar.ActivatableNotificationView;
+import com.android.systemui.statusbar.ExpandableNotificationRow;
+import com.android.systemui.statusbar.policy.HeadsUpManager;
import java.util.ArrayList;
+import java.util.TreeSet;
/**
* A global state to track all input states for the algorithm.
@@ -34,6 +38,12 @@
private int mSpeedBumpIndex = -1;
private boolean mDark;
private boolean mHideSensitive;
+ private HeadsUpManager mHeadsUpManager;
+ private float mStackTranslation;
+ private int mLayoutHeight;
+ private int mTopPadding;
+ private boolean mShadeExpanded;
+ private float mMaxHeadsUpTranslation;
public int getScrollY() {
return mScrollY;
@@ -115,4 +125,67 @@
public void setSpeedBumpIndex(int speedBumpIndex) {
mSpeedBumpIndex = speedBumpIndex;
}
+
+ public void setHeadsUpManager(HeadsUpManager headsUpManager) {
+ mHeadsUpManager = headsUpManager;
+ }
+
+ public TreeSet<HeadsUpManager.HeadsUpEntry> getSortedHeadsUpEntries() {
+ return mHeadsUpManager.getSortedEntries();
+ }
+
+ public float getStackTranslation() {
+ return mStackTranslation;
+ }
+
+ public void setStackTranslation(float stackTranslation) {
+ mStackTranslation = stackTranslation;
+ }
+
+ public int getLayoutHeight() {
+ return mLayoutHeight;
+ }
+
+ public void setLayoutHeight(int layoutHeight) {
+ mLayoutHeight = layoutHeight;
+ }
+
+ public float getTopPadding() {
+ return mTopPadding;
+ }
+
+ public void setTopPadding(int topPadding) {
+ mTopPadding = topPadding;
+ }
+
+ public int getInnerHeight() {
+ return mLayoutHeight - mTopPadding - getTopHeadsUpPushIn();
+ }
+
+ private int getTopHeadsUpPushIn() {
+ ExpandableNotificationRow topHeadsUpEntry = getTopHeadsUpEntry();
+ return topHeadsUpEntry != null ? topHeadsUpEntry.getHeadsUpHeight()
+ - topHeadsUpEntry.getMinHeight(): 0;
+ }
+
+ public boolean isShadeExpanded() {
+ return mShadeExpanded;
+ }
+
+ public void setShadeExpanded(boolean shadeExpanded) {
+ mShadeExpanded = shadeExpanded;
+ }
+
+ public void setMaxHeadsUpTranslation(float maxHeadsUpTranslation) {
+ mMaxHeadsUpTranslation = maxHeadsUpTranslation;
+ }
+
+ public float getMaxHeadsUpTranslation() {
+ return mMaxHeadsUpTranslation;
+ }
+
+ public ExpandableNotificationRow getTopHeadsUpEntry() {
+ HeadsUpManager.HeadsUpEntry topEntry = mHeadsUpManager.getTopEntry();
+ return topEntry == null ? null : topEntry.entry.row;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
index 2eafd57..f247488 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
@@ -24,6 +24,7 @@
import android.graphics.PointF;
import android.util.AttributeSet;
import android.util.Log;
+import android.util.Pair;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
@@ -47,6 +48,8 @@
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.phone.NotificationGroupManager;
import com.android.systemui.statusbar.phone.PhoneStatusBar;
+import com.android.systemui.statusbar.phone.ScrimController;
+import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.statusbar.policy.ScrollAdapter;
import java.util.ArrayList;
@@ -121,15 +124,15 @@
private StackScrollState mCurrentStackScrollState = new StackScrollState(this);
private AmbientState mAmbientState = new AmbientState();
private NotificationGroupManager mGroupManager;
- private ArrayList<View> mChildrenToAddAnimated = new ArrayList<View>();
- private ArrayList<View> mChildrenToRemoveAnimated = new ArrayList<View>();
- private ArrayList<View> mSnappedBackChildren = new ArrayList<View>();
- private ArrayList<View> mDragAnimPendingChildren = new ArrayList<View>();
- private ArrayList<View> mChildrenChangingPositions = new ArrayList<View>();
+ private ArrayList<View> mChildrenToAddAnimated = new ArrayList<>();
+ private ArrayList<View> mAddedHeadsUpChildren = new ArrayList<>();
+ private ArrayList<View> mChildrenToRemoveAnimated = new ArrayList<>();
+ private ArrayList<View> mSnappedBackChildren = new ArrayList<>();
+ private ArrayList<View> mDragAnimPendingChildren = new ArrayList<>();
+ private ArrayList<View> mChildrenChangingPositions = new ArrayList<>();
private HashSet<View> mFromMoreCardAdditions = new HashSet<>();
- private ArrayList<AnimationEvent> mAnimationEvents
- = new ArrayList<AnimationEvent>();
- private ArrayList<View> mSwipedOutViews = new ArrayList<View>();
+ private ArrayList<AnimationEvent> mAnimationEvents = new ArrayList<>();
+ private ArrayList<View> mSwipedOutViews = new ArrayList<>();
private final StackStateAnimator mStateAnimator = new StackStateAnimator(this);
private boolean mAnimationsEnabled;
private boolean mChangePositionInProgress;
@@ -143,7 +146,6 @@
* The raw amount of the overScroll on the bottom, which is not rubber-banded.
*/
private float mOverScrolledBottomPixels;
-
private OnChildLocationsChangedListener mListener;
private OnOverscrollTopChangedListener mOverscrollTopChangedListener;
private ExpandableView.OnHeightChangedListener mOnHeightChangedListener;
@@ -171,7 +173,6 @@
* Was the scroller scrolled to the top when the down motion was observed?
*/
private boolean mScrolledToTopOnFirstDown;
-
/**
* The minimal amount of over scroll which is needed in order to switch to the quick settings
* when over scrolling on a expanded card.
@@ -179,6 +180,7 @@
private float mMinTopOverScrollToEscape;
private int mIntrinsicPadding;
private int mNotificationTopPadding;
+ private float mStackTranslation;
private float mTopPaddingOverflow;
private boolean mDontReportNextOverScroll;
private boolean mRequestViewResizeAnimationOnLayout;
@@ -202,9 +204,9 @@
private ViewGroup mScrollView;
private boolean mInterceptDelegateEnabled;
private boolean mDelegateToScrollView;
+
private boolean mDisallowScrollingInThisMotion;
private long mGoToFullShadeDelay;
-
private ViewTreeObserver.OnPreDrawListener mChildrenUpdater
= new ViewTreeObserver.OnPreDrawListener() {
@Override
@@ -219,6 +221,12 @@
private int[] mTempInt2 = new int[2];
private boolean mGenerateChildOrderChangedEvent;
private boolean mRemoveAnimationEnabled;
+ private HashSet<Runnable> mAnimationFinishedRunnables = new HashSet<>();
+ private HashSet<Pair<ExpandableNotificationRow, Boolean>> mHeadsUpChangeAnimations
+ = new HashSet<>();
+ private HeadsUpManager mHeadsUpManager;
+ private boolean mTrackingHeadsUp;
+ private ScrimController mScrimController;
public NotificationStackScrollLayout(Context context) {
this(context, null);
@@ -404,8 +412,8 @@
}
private void updateAlgorithmHeightAndPadding() {
- mStackScrollAlgorithm.setLayoutHeight(getLayoutHeight());
- mStackScrollAlgorithm.setTopPadding(mTopPadding);
+ mAmbientState.setLayoutHeight(getLayoutHeight());
+ mAmbientState.setTopPadding(mTopPadding);
}
/**
@@ -478,9 +486,13 @@
int newStackHeight = (int) height;
int minStackHeight = getMinStackHeight();
int stackHeight;
- if (newStackHeight - mTopPadding - mTopPaddingOverflow >= minStackHeight
+ float paddingOffset;
+ boolean trackingHeadsUp = mTrackingHeadsUp;
+ int normalExpandPositionStart = trackingHeadsUp ? mHeadsUpManager.getTopHeadsUpHeight()
+ : minStackHeight;
+ if (newStackHeight - mTopPadding - mTopPaddingOverflow >= normalExpandPositionStart
|| getNotGoneChildCount() == 0) {
- setTranslationY(mTopPaddingOverflow);
+ paddingOffset = mTopPaddingOverflow;
stackHeight = newStackHeight;
} else {
@@ -492,9 +504,13 @@
float partiallyThere = (newStackHeight - mTopPadding - mTopPaddingOverflow)
/ minStackHeight;
partiallyThere = Math.max(0, partiallyThere);
- translationY += (1 - partiallyThere) * (mBottomStackPeekSize +
- mCollapseSecondCardPadding);
- setTranslationY(translationY - mTopPadding);
+ if (!trackingHeadsUp) {
+ translationY += (1 - partiallyThere) * (mBottomStackPeekSize +
+ mCollapseSecondCardPadding);
+ } else {
+ translationY = (int) (height - mHeadsUpManager.getTopHeadsUpHeight());
+ }
+ paddingOffset = translationY - mTopPadding;
stackHeight = (int) (height - (translationY - mTopPadding));
}
if (stackHeight != mCurrentStackHeight) {
@@ -502,6 +518,19 @@
updateAlgorithmHeightAndPadding();
requestChildrenUpdate();
}
+ setStackTranslation(paddingOffset);
+ }
+
+ public float getStackTranslation() {
+ return mStackTranslation;
+ }
+
+ private void setStackTranslation(float stackTranslation) {
+ if (stackTranslation != mStackTranslation) {
+ mStackTranslation = stackTranslation;
+ mAmbientState.setStackTranslation(stackTranslation);
+ requestChildrenUpdate();
+ }
}
/**
@@ -543,11 +572,6 @@
if (mDismissAllInProgress) {
return;
}
- if (DEBUG) Log.v(TAG, "onChildDismissed: " + v);
- final View veto = v.findViewById(R.id.veto);
- if (veto != null && veto.getVisibility() != View.GONE) {
- veto.performClick();
- }
setSwipingInProgress(false);
if (mDragAnimPendingChildren.contains(v)) {
// We start the swipe and finish it in the same frame, we don't want any animation
@@ -556,6 +580,17 @@
}
mSwipedOutViews.add(v);
mAmbientState.onDragFinished(v);
+ if (v instanceof ExpandableNotificationRow) {
+ ExpandableNotificationRow row = (ExpandableNotificationRow) v;
+ if (row.isHeadsUp()) {
+ mHeadsUpManager.addSwipedOutKey(row.getStatusBarNotification().getKey());
+ }
+ }
+ final View veto = v.findViewById(R.id.veto);
+ if (veto != null && veto.getVisibility() != View.GONE) {
+ veto.performClick();
+ }
+ if (DEBUG) Log.v(TAG, "onChildDismissed: " + v);
}
@Override
@@ -575,28 +610,48 @@
@Override
public boolean updateSwipeProgress(View animView, boolean dismissable, float swipeProgress) {
+ if (isPinnedHeadsUp(animView) && canChildBeDismissed(animView)) {
+ mScrimController.setTopHeadsUpDragAmount(animView,
+ Math.min(Math.abs(swipeProgress - 1.0f), 1.0f));
+ }
return false;
}
- @Override
- public float getFalsingThresholdFactor() {
- return mPhoneStatusBar.isScreenOnComingFromTouch() ? 1.5f : 1.0f;
- }
-
public void onBeginDrag(View v) {
setSwipingInProgress(true);
mAmbientState.onBeginDrag(v);
- if (mAnimationsEnabled) {
+ if (mAnimationsEnabled && !isPinnedHeadsUp(v)) {
mDragAnimPendingChildren.add(v);
mNeedsAnimation = true;
}
requestChildrenUpdate();
}
+ public boolean isPinnedHeadsUp(View v) {
+ if (v instanceof ExpandableNotificationRow) {
+ ExpandableNotificationRow row = (ExpandableNotificationRow) v;
+ return row.isHeadsUp() && !row.isInShade();
+ }
+ return false;
+ }
+
+ private boolean isHeadsUp(View v) {
+ if (v instanceof ExpandableNotificationRow) {
+ ExpandableNotificationRow row = (ExpandableNotificationRow) v;
+ return row.isHeadsUp();
+ }
+ return false;
+ }
+
public void onDragCancelled(View v) {
setSwipingInProgress(false);
}
+ @Override
+ public float getFalsingThresholdFactor() {
+ return mPhoneStatusBar.isScreenOnComingFromTouch() ? 1.5f : 1.0f;
+ }
+
public View getChildAtPosition(MotionEvent ev) {
return getChildAtPosition(ev.getX(), ev.getY());
}
@@ -657,6 +712,10 @@
if (touchY >= top && touchY <= bottom && touchX >= left && touchX <= right) {
if (slidingChild instanceof ExpandableNotificationRow) {
ExpandableNotificationRow row = (ExpandableNotificationRow) slidingChild;
+ if (row.isHeadsUp() && !row.isInShade()
+ && mHeadsUpManager.getTopEntry().entry.row != row) {
+ continue;
+ }
return row.getViewAtPosition(touchY - childTop);
}
return slidingChild;
@@ -667,7 +726,8 @@
public boolean canChildBeExpanded(View v) {
return v instanceof ExpandableNotificationRow
- && ((ExpandableNotificationRow) v).isExpandable();
+ && ((ExpandableNotificationRow) v).isExpandable()
+ && !((ExpandableNotificationRow) v).isHeadsUp();
}
public void setUserExpandedChild(View v, boolean userExpanded) {
@@ -1343,12 +1403,9 @@
// add the padding before this element
height += mPaddingBetweenElements;
}
- if (child instanceof ExpandableNotificationRow) {
- ExpandableNotificationRow row = (ExpandableNotificationRow) child;
- height += row.getIntrinsicHeight();
- } else if (child instanceof ExpandableView) {
+ if (child instanceof ExpandableView) {
ExpandableView expandableView = (ExpandableView) child;
- height += expandableView.getActualHeight();
+ height += expandableView.getIntrinsicHeight();
}
}
}
@@ -1713,16 +1770,17 @@
}
private void updateNotificationAnimationStates() {
- boolean running = mIsExpanded && mAnimationsEnabled;
+ boolean running = mAnimationsEnabled;
int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
+ running &= mIsExpanded || isPinnedHeadsUp(child);
updateAnimationState(running, child);
}
}
private void updateAnimationState(View child) {
- updateAnimationState(mAnimationsEnabled && mIsExpanded, child);
+ updateAnimationState((mAnimationsEnabled || isPinnedHeadsUp(child)) && mIsExpanded, child);
}
@@ -1752,6 +1810,10 @@
}
mNeedsAnimation = true;
}
+ if (isHeadsUp(child)) {
+ mAddedHeadsUpChildren.add(child);
+ mChildrenToAddAnimated.remove(child);
+ }
}
/**
@@ -1790,6 +1852,7 @@
}
private void generateChildHierarchyEvents() {
+ generateHeadsUpAnimationEvents();
generateChildRemovalEvents();
generateChildAdditionEvents();
generatePositionChangeEvents();
@@ -1807,6 +1870,40 @@
mNeedsAnimation = false;
}
+ private void generateHeadsUpAnimationEvents() {
+ for (Pair<ExpandableNotificationRow, Boolean> eventPair : mHeadsUpChangeAnimations) {
+ ExpandableNotificationRow row = eventPair.first;
+ boolean isHeadsUp = eventPair.second;
+ int type = AnimationEvent.ANIMATION_TYPE_HEADS_UP_OTHER;
+ boolean onBottom = false;
+ if (!mIsExpanded && !isHeadsUp) {
+ type = AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR;
+ } else if (mAddedHeadsUpChildren.contains(row) || (!row.isInShade() && !mIsExpanded)) {
+ if (!row.isInShade() || shouldHunAppearFromBottom(row)) {
+ // Our custom add animation
+ type = AnimationEvent.ANIMATION_TYPE_HEADS_UP_APPEAR;
+ } else {
+ // Normal add animation
+ type = AnimationEvent.ANIMATION_TYPE_ADD;
+ }
+ onBottom = row.isInShade();
+ }
+ AnimationEvent event = new AnimationEvent(row, type);
+ event.headsUpFromBottom = onBottom;
+ mAnimationEvents.add(event);
+ }
+ mHeadsUpChangeAnimations.clear();
+ mAddedHeadsUpChildren.clear();
+ }
+
+ private boolean shouldHunAppearFromBottom(ExpandableNotificationRow row) {
+ StackViewState viewState = mCurrentStackScrollState.getViewStateForView(row);
+ if (viewState.yTranslation + viewState.height < mAmbientState.getMaxHeadsUpTranslation()) {
+ return false;
+ }
+ return true;
+ }
+
private void generateGroupExpansionEvent() {
// Generate a group expansion/collapsing event if there is such a group at all
if (mExpandedGroupView != null) {
@@ -2182,6 +2279,10 @@
public void onChildAnimationFinished() {
requestChildrenUpdate();
+ for (Runnable runnable : mAnimationFinishedRunnables) {
+ runnable.run();
+ }
+ mAnimationFinishedRunnables.clear();
}
/**
@@ -2283,7 +2384,7 @@
* @return the y position of the first notification
*/
public float getNotificationsTopY() {
- return mTopPadding + getTranslationY();
+ return mTopPadding + getStackTranslation();
}
@Override
@@ -2470,7 +2571,7 @@
max = bottom;
}
}
- return max + getTranslationY();
+ return max + getStackTranslation();
}
/**
@@ -2579,6 +2680,50 @@
}
}
+ public void performOnAnimationFinished(Runnable runnable) {
+ mAnimationFinishedRunnables.add(runnable);
+ }
+
+ public void setHeadsUpManager(HeadsUpManager headsUpManager) {
+ mHeadsUpManager = headsUpManager;
+ mAmbientState.setHeadsUpManager(headsUpManager);
+ mStackScrollAlgorithm.setHeadsUpManager(headsUpManager);
+ }
+
+ public void generateHeadsUpAnimation(ExpandableNotificationRow row, boolean isHeadsUp) {
+ if (mAnimationsEnabled) {
+ mHeadsUpChangeAnimations.add(new Pair<>(row, isHeadsUp));
+ mNeedsAnimation = true;
+ requestChildrenUpdate();
+ }
+ }
+
+ public void setShadeExpanded(boolean shadeExpanded) {
+ mAmbientState.setShadeExpanded(shadeExpanded);
+ mStateAnimator.setShadeExpanded(shadeExpanded);
+ }
+
+ /**
+ * Set the boundary for the bottom heads up position. The heads up will always be above this
+ * position.
+ *
+ * @param height the height of the screen
+ * @param bottomBarHeight the height of the bar on the bottom
+ */
+ public void setHeadsUpBoundaries(int height, int bottomBarHeight) {
+ mAmbientState.setMaxHeadsUpTranslation(height - bottomBarHeight);
+ mStateAnimator.setHeadsUpAppearHeightBottom(height);
+ requestChildrenUpdate();
+ }
+
+ public void setTrackingHeadsUp(boolean trackingHeadsUp) {
+ mTrackingHeadsUp = trackingHeadsUp;
+ }
+
+ public void setScrimController(ScrimController scrimController) {
+ mScrimController = scrimController;
+ }
+
/**
* A listener that is notified when some child locations might have changed.
*/
@@ -2723,6 +2868,30 @@
.animateY()
.animateZ(),
+ // ANIMATION_TYPE_HEADS_UP_APPEAR
+ new AnimationFilter()
+ .animateAlpha()
+ .animateHeight()
+ .animateTopInset()
+ .animateY()
+ .animateZ(),
+
+ // ANIMATION_TYPE_HEADS_UP_DISAPPEAR
+ new AnimationFilter()
+ .animateAlpha()
+ .animateHeight()
+ .animateTopInset()
+ .animateY()
+ .animateZ(),
+
+ // ANIMATION_TYPE_HEADS_UP_OTHER
+ new AnimationFilter()
+ .animateAlpha()
+ .animateHeight()
+ .animateTopInset()
+ .animateY()
+ .animateZ(),
+
// ANIMATION_TYPE_EVERYTHING
new AnimationFilter()
.animateAlpha()
@@ -2780,6 +2949,15 @@
// ANIMATION_TYPE_GROUP_EXPANSION_CHANGED
StackStateAnimator.ANIMATION_DURATION_EXPAND_CLICKED,
+ // ANIMATION_TYPE_HEADS_UP_APPEAR
+ StackStateAnimator.ANIMATION_DURATION_HEADS_UP_APPEAR,
+
+ // ANIMATION_TYPE_HEADS_UP_DISAPPEAR
+ StackStateAnimator.ANIMATION_DURATION_HEADS_UP_DISAPPEAR,
+
+ // ANIMATION_TYPE_HEADS_UP_OTHER
+ StackStateAnimator.ANIMATION_DURATION_STANDARD,
+
// ANIMATION_TYPE_EVERYTHING
StackStateAnimator.ANIMATION_DURATION_STANDARD,
};
@@ -2798,7 +2976,10 @@
static final int ANIMATION_TYPE_HIDE_SENSITIVE = 11;
static final int ANIMATION_TYPE_VIEW_RESIZE = 12;
static final int ANIMATION_TYPE_GROUP_EXPANSION_CHANGED = 13;
- static final int ANIMATION_TYPE_EVERYTHING = 14;
+ static final int ANIMATION_TYPE_HEADS_UP_APPEAR = 14;
+ static final int ANIMATION_TYPE_HEADS_UP_DISAPPEAR = 15;
+ static final int ANIMATION_TYPE_HEADS_UP_OTHER = 16;
+ static final int ANIMATION_TYPE_EVERYTHING = 17;
static final int DARK_ANIMATION_ORIGIN_INDEX_ABOVE = -1;
static final int DARK_ANIMATION_ORIGIN_INDEX_BELOW = -2;
@@ -2810,6 +2991,7 @@
final long length;
View viewAfterChangingView;
int darkAnimationOriginIndex;
+ boolean headsUpFromBottom;
AnimationEvent(View view, int type) {
this(view, type, LENGTHS[type]);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
index e7bf47b..d98bcfe 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
@@ -25,9 +25,11 @@
import com.android.systemui.R;
import com.android.systemui.statusbar.ExpandableNotificationRow;
import com.android.systemui.statusbar.ExpandableView;
+import com.android.systemui.statusbar.policy.HeadsUpManager;
import java.util.ArrayList;
import java.util.List;
+import java.util.TreeSet;
/**
* The Algorithm of the {@link com.android.systemui.statusbar.stack
@@ -54,11 +56,6 @@
private StackIndentationFunctor mTopStackIndentationFunctor;
private StackIndentationFunctor mBottomStackIndentationFunctor;
- private int mLayoutHeight;
-
- /** mLayoutHeight - mTopPadding */
- private int mInnerHeight;
- private int mTopPadding;
private StackScrollAlgorithmState mTempAlgorithmState = new StackScrollAlgorithmState();
private boolean mIsExpansionChanging;
private int mFirstChildMaxHeight;
@@ -74,6 +71,7 @@
private boolean mIsSmallScreen;
private int mMaxNotificationHeight;
private boolean mScaleDimmed;
+ private HeadsUpManager mHeadsUpManager;
public StackScrollAlgorithm(Context context) {
initConstants(context);
@@ -157,20 +155,20 @@
scrollY = Math.max(0, scrollY);
algorithmState.scrollY = (int) (scrollY + mCollapsedSize + bottomOverScroll);
- updateVisibleChildren(resultState, algorithmState);
+ updateVisibleChildren(resultState, algorithmState, ambientState);
// Phase 1:
- findNumberOfItemsInTopStackAndUpdateState(resultState, algorithmState);
+ findNumberOfItemsInTopStackAndUpdateState(resultState, algorithmState, ambientState);
// Phase 2:
- updatePositionsForState(resultState, algorithmState);
+ updatePositionsForState(resultState, algorithmState, ambientState);
// Phase 3:
updateZValuesForState(resultState, algorithmState);
handleDraggedViews(ambientState, resultState, algorithmState);
updateDimmedActivatedHideSensitive(ambientState, resultState, algorithmState);
- updateClipping(resultState, algorithmState);
+ updateClipping(resultState, algorithmState, ambientState);
updateSpeedBumpState(resultState, algorithmState, ambientState.getSpeedBumpIndex());
getNotificationChildrenStates(resultState, algorithmState);
}
@@ -201,7 +199,7 @@
}
private void updateClipping(StackScrollState resultState,
- StackScrollAlgorithmState algorithmState) {
+ StackScrollAlgorithmState algorithmState, AmbientState ambientState) {
float previousNotificationEnd = 0;
float previousNotificationStart = 0;
boolean previousNotificationIsSwiped = false;
@@ -242,7 +240,7 @@
// otherwise we would clip to a transparent view.
previousNotificationStart = newYTranslation + state.clipTopAmount * state.scale;
previousNotificationEnd = newNotificationEnd;
- previousNotificationIsSwiped = child.getTranslationX() != 0;
+ previousNotificationIsSwiped = ambientState.getDraggedViews().contains(child);
}
}
}
@@ -314,7 +312,9 @@
StackViewState viewState = resultState.getViewStateForView(
nextChild);
// The child below the dragged one must be fully visible
- viewState.alpha = 1;
+ if (!isPinnedHeadsUpView(draggedView) || isPinnedHeadsUpView(nextChild)) {
+ viewState.alpha = 1;
+ }
}
// Lets set the alpha to the one it currently has, as its currently being dragged
@@ -325,27 +325,41 @@
}
}
+ private boolean isPinnedHeadsUpView(View view) {
+ if (view instanceof ExpandableNotificationRow) {
+ ExpandableNotificationRow row = (ExpandableNotificationRow) view;
+ return row.isHeadsUp() && !row.isInShade();
+ }
+ return false;
+ }
+
/**
* Update the visible children on the state.
*/
private void updateVisibleChildren(StackScrollState resultState,
- StackScrollAlgorithmState state) {
+ StackScrollAlgorithmState state, AmbientState ambientState) {
ViewGroup hostView = resultState.getHostView();
int childCount = hostView.getChildCount();
state.visibleChildren.clear();
state.visibleChildren.ensureCapacity(childCount);
int notGoneIndex = 0;
+ TreeSet<HeadsUpManager.HeadsUpEntry> headsUpEntries
+ = ambientState.getSortedHeadsUpEntries();
+ for (HeadsUpManager.HeadsUpEntry entry: headsUpEntries) {
+ ExpandableView v = entry.entry.row;
+ notGoneIndex = updateNotGoneIndex(resultState, state, notGoneIndex, v);
+ }
for (int i = 0; i < childCount; i++) {
ExpandableView v = (ExpandableView) hostView.getChildAt(i);
if (v.getVisibility() != View.GONE) {
- StackViewState viewState = resultState.getViewStateForView(v);
- viewState.notGoneIndex = notGoneIndex;
- state.visibleChildren.add(v);
- notGoneIndex++;
-
- // handle the notgoneIndex for the children as well
if (v instanceof ExpandableNotificationRow) {
ExpandableNotificationRow row = (ExpandableNotificationRow) v;
+ if (row.isHeadsUp()) {
+ continue;
+ }
+ notGoneIndex = updateNotGoneIndex(resultState, state, notGoneIndex, v);
+
+ // handle the notgoneIndex for the children as well
List<ExpandableNotificationRow> children =
row.getNotificationChildren();
if (row.areChildrenExpanded() && children != null) {
@@ -358,22 +372,35 @@
}
}
}
+ } else {
+ notGoneIndex = updateNotGoneIndex(resultState, state, notGoneIndex, v);
}
}
}
}
+ private int updateNotGoneIndex(StackScrollState resultState,
+ StackScrollAlgorithmState state, int notGoneIndex,
+ ExpandableView v) {
+ StackViewState viewState = resultState.getViewStateForView(v);
+ viewState.notGoneIndex = notGoneIndex;
+ state.visibleChildren.add(v);
+ notGoneIndex++;
+ return notGoneIndex;
+ }
+
/**
* Determine the positions for the views. This is the main part of the algorithm.
*
- * @param resultState The result state to update if a change to the properties of a child occurs
+ * @param resultState The result state to update if a change to the properties of a child occurs
* @param algorithmState The state in which the current pass of the algorithm is currently in
+ * @param ambientState The current ambient state
*/
private void updatePositionsForState(StackScrollState resultState,
- StackScrollAlgorithmState algorithmState) {
+ StackScrollAlgorithmState algorithmState, AmbientState ambientState) {
// The starting position of the bottom stack peek
- float bottomPeekStart = mInnerHeight - mBottomStackPeekSize;
+ float bottomPeekStart = ambientState.getInnerHeight() - mBottomStackPeekSize;
// The position where the bottom stack starts.
float bottomStackStart = bottomPeekStart - mBottomStackSlowDownLength;
@@ -384,13 +411,17 @@
// How far in is the element currently transitioning into the bottom stack.
float yPositionInScrollView = 0.0f;
+ // If we have a heads-up higher than the collapsed height we need to add the difference to
+ // the padding of all other elements, i.e push in the top stack slightly.
+ ExpandableNotificationRow topHeadsUpEntry = ambientState.getTopHeadsUpEntry();
+
int childCount = algorithmState.visibleChildren.size();
int numberOfElementsCompletelyIn = (int) algorithmState.itemsInTopStack;
for (int i = 0; i < childCount; i++) {
ExpandableView child = algorithmState.visibleChildren.get(i);
StackViewState childViewState = resultState.getViewStateForView(child);
childViewState.location = StackViewState.LOCATION_UNKNOWN;
- int childHeight = getMaxAllowedChildHeight(child);
+ int childHeight = getMaxAllowedChildHeight(child, ambientState);
float yPositionInScrollViewAfterElement = yPositionInScrollView
+ childHeight
+ mPaddingBetweenElements;
@@ -427,7 +458,8 @@
bottomPeekStart, childViewState.yTranslation, childViewState,
childHeight);
}
- clampPositionToBottomStackStart(childViewState, childViewState.height);
+ clampPositionToBottomStackStart(childViewState, childViewState.height,
+ ambientState);
} else if (nextYPosition >= bottomStackStart) {
// Case 2:
// We are in the bottom stack.
@@ -435,7 +467,7 @@
// According to the regular scroll view we are fully translated out of the
// bottom of the screen so we are fully in the bottom stack
updateStateForChildFullyInBottomStack(algorithmState,
- bottomStackStart, childViewState, childHeight);
+ bottomStackStart, childViewState, childHeight, ambientState);
} else {
// According to the regular scroll view we are currently translating out of /
// into the bottom of the screen
@@ -447,7 +479,7 @@
// Case 3:
// We are in the regular scroll area.
childViewState.location = StackViewState.LOCATION_MAIN_AREA;
- clampYTranslation(childViewState, childHeight);
+ clampYTranslation(childViewState, childHeight, ambientState);
}
// The first card is always rendered.
@@ -468,7 +500,44 @@
currentYPosition = childViewState.yTranslation + childHeight + mPaddingBetweenElements;
yPositionInScrollView = yPositionInScrollViewAfterElement;
- childViewState.yTranslation += mTopPadding;
+ if (ambientState.isShadeExpanded() && topHeadsUpEntry != null
+ && child != topHeadsUpEntry) {
+ childViewState.yTranslation += topHeadsUpEntry.getHeadsUpHeight() - mCollapsedSize;
+ }
+ childViewState.yTranslation += ambientState.getTopPadding()
+ + ambientState.getStackTranslation();
+ }
+ updateHeadsUpStates(resultState, ambientState);
+ }
+
+ private void updateHeadsUpStates(StackScrollState resultState, AmbientState ambientState) {
+ TreeSet<HeadsUpManager.HeadsUpEntry> headsUpEntries = ambientState.getSortedHeadsUpEntries();
+ for (HeadsUpManager.HeadsUpEntry entry: headsUpEntries) {
+ ExpandableNotificationRow row = entry.entry.row;
+ StackViewState childState = resultState.getViewStateForView(row);
+ ExpandableNotificationRow topHeadsUpEntry = ambientState.getTopHeadsUpEntry();
+ boolean isTopEntry = topHeadsUpEntry == row;
+ if (!row.isInShade()) {
+ childState.yTranslation = 0;
+ childState.height = row.getHeadsUpHeight();
+ if (!isTopEntry) {
+ // Ensure that a headsUp is never below the topmost headsUp
+ StackViewState topState = resultState.getViewStateForView(topHeadsUpEntry);
+ childState.height = row.getHeadsUpHeight();
+ childState.yTranslation = topState.yTranslation + topState.height
+ - childState.height;
+ }
+ } else if (mIsExpanded) {
+ if (isTopEntry) {
+ childState.height += row.getHeadsUpHeight() - mCollapsedSize;
+ }
+ childState.height = Math.max(childState.height, row.getHeadsUpHeight());
+ // Ensure that the heads up is always visible even when scrolled of from the bottom
+ float bottomPosition = ambientState.getMaxHeadsUpTranslation() - childState.height;
+ childState.yTranslation = Math.min(childState.yTranslation,
+ bottomPosition);
+ }
+
}
}
@@ -478,8 +547,9 @@
* @param childViewState the view state of the child
* @param childHeight the height of this child
*/
- private void clampYTranslation(StackViewState childViewState, int childHeight) {
- clampPositionToBottomStackStart(childViewState, childHeight);
+ private void clampYTranslation(StackViewState childViewState, int childHeight,
+ AmbientState ambientState) {
+ clampPositionToBottomStackStart(childViewState, childHeight, ambientState);
clampPositionToTopStackEnd(childViewState, childHeight);
}
@@ -491,14 +561,15 @@
* @param childHeight the height of this child
*/
private void clampPositionToBottomStackStart(StackViewState childViewState,
- int childHeight) {
+ int childHeight, AmbientState ambientState) {
childViewState.yTranslation = Math.min(childViewState.yTranslation,
- mInnerHeight - mBottomStackPeekSize - mCollapseSecondCardPadding - childHeight);
+ ambientState.getInnerHeight() - mBottomStackPeekSize - mCollapseSecondCardPadding
+ - childHeight);
}
/**
* Clamp the yTranslation of the child up such that its end is at lest on the end of the top
- * stack.get
+ * stack.
*
* @param childViewState the view state of the child
* @param childHeight the height of this child
@@ -509,9 +580,14 @@
mCollapsedSize - childHeight);
}
- private int getMaxAllowedChildHeight(View child) {
+ private int getMaxAllowedChildHeight(View child, AmbientState ambientState) {
if (child instanceof ExpandableNotificationRow) {
ExpandableNotificationRow row = (ExpandableNotificationRow) child;
+ if (ambientState == null && row.isHeadsUp()
+ || ambientState != null && ambientState.getTopHeadsUpEntry() == child) {
+ int extraSize = row.getIntrinsicHeight() - row.getHeadsUpHeight();
+ return mCollapsedSize + extraSize;
+ }
return row.getIntrinsicHeight();
} else if (child instanceof ExpandableView) {
ExpandableView expandableView = (ExpandableView) child;
@@ -548,8 +624,7 @@
private void updateStateForChildFullyInBottomStack(StackScrollAlgorithmState algorithmState,
float transitioningPositionStart, StackViewState childViewState,
- int childHeight) {
-
+ int childHeight, AmbientState ambientState) {
float currentYPosition;
algorithmState.itemsInBottomStack += 1.0f;
if (algorithmState.itemsInBottomStack < MAX_ITEMS_IN_BOTTOM_STACK) {
@@ -567,7 +642,7 @@
childViewState.alpha = 1.0f - algorithmState.partialInBottom;
}
childViewState.location = StackViewState.LOCATION_BOTTOM_STACK_HIDDEN;
- currentYPosition = mInnerHeight;
+ currentYPosition = ambientState.getInnerHeight();
}
childViewState.yTranslation = currentYPosition - childHeight;
clampPositionToTopStackEnd(childViewState, childHeight);
@@ -629,7 +704,7 @@
* @param algorithmState The state in which the current pass of the algorithm is currently in
*/
private void findNumberOfItemsInTopStackAndUpdateState(StackScrollState resultState,
- StackScrollAlgorithmState algorithmState) {
+ StackScrollAlgorithmState algorithmState, AmbientState ambientState) {
// The y Position if the element would be in a regular scrollView
float yPositionInScrollView = 0.0f;
@@ -639,7 +714,7 @@
for (int i = 0; i < childCount; i++) {
ExpandableView child = algorithmState.visibleChildren.get(i);
StackViewState childViewState = resultState.getViewStateForView(child);
- int childHeight = getMaxAllowedChildHeight(child);
+ int childHeight = getMaxAllowedChildHeight(child, ambientState);
float yPositionInScrollViewAfterElement = yPositionInScrollView
+ childHeight
+ mPaddingBetweenElements;
@@ -647,7 +722,7 @@
if (i == 0 && algorithmState.scrollY <= mCollapsedSize) {
// The starting position of the bottom stack peek
- int bottomPeekStart = mInnerHeight - mBottomStackPeekSize -
+ int bottomPeekStart = ambientState.getInnerHeight() - mBottomStackPeekSize -
mCollapseSecondCardPadding;
// Collapse and expand the first child while the shade is being expanded
float maxHeight = mIsExpansionChanging && child == mFirstChildWhileExpanding
@@ -744,21 +819,6 @@
}
}
- public void setLayoutHeight(int layoutHeight) {
- this.mLayoutHeight = layoutHeight;
- updateInnerHeight();
- }
-
- public void setTopPadding(int topPadding) {
- mTopPadding = topPadding;
- updateInnerHeight();
- }
-
- private void updateInnerHeight() {
- mInnerHeight = mLayoutHeight - mTopPadding;
- }
-
-
/**
* Update whether the device is very small, i.e. Notifications can be in both the top and the
* bottom stack at the same time
@@ -788,6 +848,13 @@
// current height or the end value of the animation.
mFirstChildMaxHeight = StackStateAnimator.getFinalActualHeight(
mFirstChildWhileExpanding);
+ if (mFirstChildWhileExpanding instanceof ExpandableNotificationRow) {
+ ExpandableNotificationRow row =
+ (ExpandableNotificationRow) mFirstChildWhileExpanding;
+ if (row.isHeadsUp()) {
+ mFirstChildMaxHeight += mCollapsedSize - row.getHeadsUpHeight();
+ }
+ }
} else {
updateFirstChildMaxSizeToMaxHeight();
}
@@ -809,7 +876,7 @@
int oldBottom) {
if (mFirstChildWhileExpanding != null) {
mFirstChildMaxHeight = getMaxAllowedChildHeight(
- mFirstChildWhileExpanding);
+ mFirstChildWhileExpanding, null);
} else {
mFirstChildMaxHeight = 0;
}
@@ -817,7 +884,7 @@
}
});
} else {
- mFirstChildMaxHeight = getMaxAllowedChildHeight(mFirstChildWhileExpanding);
+ mFirstChildMaxHeight = getMaxAllowedChildHeight(mFirstChildWhileExpanding, null);
}
}
@@ -830,6 +897,9 @@
}
private View findFirstVisibleChild(ViewGroup container) {
+ if (mHeadsUpManager != null && mHeadsUpManager.getTopEntry() != null) {
+ return mHeadsUpManager.getTopEntry().entry.row;
+ }
int childCount = container.getChildCount();
for (int i = 0; i < childCount; i++) {
View child = container.getChildAt(i);
@@ -870,6 +940,10 @@
}
}
+ public void setHeadsUpManager(HeadsUpManager headsUpManager) {
+ mHeadsUpManager = headsUpManager;
+ }
+
class StackScrollAlgorithmState {
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java
index b249fbf..f5d94c8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java
@@ -21,9 +21,11 @@
import android.animation.ObjectAnimator;
import android.animation.PropertyValuesHolder;
import android.animation.ValueAnimator;
+import android.graphics.Path;
import android.view.View;
import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator;
+import android.view.animation.PathInterpolator;
import com.android.systemui.R;
import com.android.systemui.statusbar.ExpandableNotificationRow;
@@ -32,7 +34,6 @@
import java.util.ArrayList;
import java.util.HashSet;
-import java.util.Set;
import java.util.Stack;
/**
@@ -45,6 +46,8 @@
public static final int ANIMATION_DURATION_APPEAR_DISAPPEAR = 464;
public static final int ANIMATION_DURATION_EXPAND_CLICKED = 360;
public static final int ANIMATION_DURATION_DIMMED_ACTIVATED = 220;
+ public static final int ANIMATION_DURATION_HEADS_UP_APPEAR = 650;
+ public static final int ANIMATION_DURATION_HEADS_UP_DISAPPEAR = 230;
public static final int ANIMATION_DELAY_PER_ELEMENT_INTERRUPTING = 80;
public static final int ANIMATION_DELAY_PER_ELEMENT_EXPAND_CHILDREN = 54;
public static final int ANIMATION_DELAY_PER_ELEMENT_MANUAL = 32;
@@ -73,12 +76,15 @@
private static final int TAG_START_TOP_INSET = R.id.top_inset_animator_start_value_tag;
private final Interpolator mFastOutSlowInInterpolator;
+ private final Interpolator mHeadsUpAppearInterpolator;
private final int mGoToFullShadeAppearingTranslation;
public NotificationStackScrollLayout mHostLayout;
private ArrayList<NotificationStackScrollLayout.AnimationEvent> mNewEvents =
new ArrayList<>();
private ArrayList<View> mNewAddChildren = new ArrayList<>();
- private Set<Animator> mAnimatorSet = new HashSet<>();
+ private HashSet<View> mHeadsUpAppearChildren = new HashSet<>();
+ private HashSet<View> mHeadsUpDisappearChildren = new HashSet<>();
+ private HashSet<Animator> mAnimatorSet = new HashSet<>();
private Stack<AnimatorListenerAdapter> mAnimationListenerPool = new Stack<>();
private AnimationFilter mAnimationFilter = new AnimationFilter();
private long mCurrentLength;
@@ -86,10 +92,12 @@
/** The current index for the last child which was not added in this event set. */
private int mCurrentLastNotAddedIndex;
-
private ValueAnimator mTopOverScrollAnimator;
private ValueAnimator mBottomOverScrollAnimator;
private ExpandableNotificationRow mChildExpandingView;
+ private StackViewState mTmpState = new StackViewState();
+ private int mHeadsUpAppearHeightBottom;
+ private boolean mShadeExpanded;
public StackStateAnimator(NotificationStackScrollLayout hostLayout) {
mHostLayout = hostLayout;
@@ -98,6 +106,25 @@
mGoToFullShadeAppearingTranslation =
hostLayout.getContext().getResources().getDimensionPixelSize(
R.dimen.go_to_full_shade_appearing_translation);
+ Path path = new Path();
+ path.moveTo(0, 0);
+ float x1 = 250f;
+ float x2 = 150f;
+ float x3 = 100f;
+ float y1 = 90f;
+ float y2 = 78f;
+ float y3 = 80f;
+ float xTot = (x1 + x2 + x3);
+ path.cubicTo(x1 * 0.9f / xTot, 0f,
+ x1 * 0.8f / xTot, y1 / y3,
+ x1 / xTot , y1 / y3);
+ path.cubicTo((x1 + x2 * 0.4f) / xTot, y1 / y3,
+ (x1 + x2 * 0.2f) / xTot, y2 / y3,
+ (x1 + x2) / xTot, y2 / y3);
+ path.cubicTo((x1 + x2 + x3 * 0.4f) / xTot, y2 / y3,
+ (x1 + x2 + x3 * 0.2f) / xTot, 1f,
+ 1f, 1f);
+ mHeadsUpAppearInterpolator = new PathInterpolator(path);
}
public boolean isRunning() {
@@ -119,7 +146,8 @@
final ExpandableView child = (ExpandableView) mHostLayout.getChildAt(i);
StackViewState viewState = finalState.getViewStateForView(child);
- if (viewState == null || child.getVisibility() == View.GONE) {
+ if (viewState == null || child.getVisibility() == View.GONE
+ || applyWithoutAnimation(child, viewState, finalState)) {
continue;
}
@@ -130,11 +158,39 @@
// no child has preformed any animation, lets finish
onAnimationFinished();
}
+ mHeadsUpAppearChildren.clear();
+ mHeadsUpDisappearChildren.clear();
mNewEvents.clear();
mNewAddChildren.clear();
mChildExpandingView = null;
}
+ /**
+ * Determines if a view should not perform an animation and applies it directly.
+ *
+ * @return true if no animation should be performed
+ */
+ private boolean applyWithoutAnimation(ExpandableView child, StackViewState viewState,
+ StackScrollState finalState) {
+ if (mShadeExpanded) {
+ return false;
+ }
+ if (getChildTag(child, TAG_ANIMATOR_TRANSLATION_Y) != null) {
+ // A Y translation animation is running
+ return false;
+ }
+ if (mHeadsUpDisappearChildren.contains(child) || mHeadsUpAppearChildren.contains(child)) {
+ // This is a heads up animation
+ return false;
+ }
+ if (mHostLayout.isPinnedHeadsUp(child)) {
+ // This is another headsUp which might move. Let's animate!
+ return false;
+ }
+ finalState.applyState(child, viewState);
+ return true;
+ }
+
private int findLastNotAddedIndex(StackScrollState finalState) {
int childCount = mHostLayout.getChildCount();
for (int i = childCount - 1; i >= 0; i--) {
@@ -616,7 +672,9 @@
ObjectAnimator animator = ObjectAnimator.ofFloat(child, View.TRANSLATION_Y,
child.getTranslationY(), newEndValue);
- animator.setInterpolator(mFastOutSlowInInterpolator);
+ Interpolator interpolator = mHeadsUpAppearChildren.contains(child) ?
+ mHeadsUpAppearInterpolator :mFastOutSlowInInterpolator;
+ animator.setInterpolator(interpolator);
long newDuration = cancelAnimatorAndGetNewDuration(duration, previousAnimator);
animator.setDuration(newDuration);
if (delay > 0 && (previousAnimator == null || !previousAnimator.isRunning())) {
@@ -731,7 +789,7 @@
};
}
- private static <T> T getChildTag(View child, int tag) {
+ public static <T> T getChildTag(View child, int tag) {
return (T) child.getTag(tag);
}
@@ -828,6 +886,22 @@
ExpandableNotificationRow row = (ExpandableNotificationRow) event.changingView;
row.prepareExpansionChanged(finalState);
mChildExpandingView = row;
+ } else if (event.animationType == NotificationStackScrollLayout
+ .AnimationEvent.ANIMATION_TYPE_HEADS_UP_APPEAR) {
+ // This item is added, initialize it's properties.
+ StackViewState viewState = finalState.getViewStateForView(changingView);
+ mTmpState.copyFrom(viewState);
+ if (event.headsUpFromBottom) {
+ mTmpState.yTranslation = mHeadsUpAppearHeightBottom;
+ } else {
+ mTmpState.yTranslation = -mTmpState.height;
+ }
+ mHeadsUpAppearChildren.add(changingView);
+ finalState.applyState(changingView, mTmpState);
+ } else if (event.animationType == NotificationStackScrollLayout
+ .AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR) {
+ // This item is added, initialize it's properties.
+ mHeadsUpDisappearChildren.add(changingView);
}
mNewEvents.add(event);
}
@@ -893,4 +967,12 @@
return getChildTag(view, TAG_END_HEIGHT);
}
}
+
+ public void setHeadsUpAppearHeightBottom(int headsUpAppearHeightBottom) {
+ mHeadsUpAppearHeightBottom = headsUpAppearHeightBottom;
+ }
+
+ public void setShadeExpanded(boolean shadeExpanded) {
+ mShadeExpanded = shadeExpanded;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java
index c272e48..78122d6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java
@@ -123,19 +123,7 @@
}
@Override
- public void scheduleHeadsUpDecay(long delay) {
- }
-
- @Override
- public void scheduleHeadsUpOpen() {
- }
-
- @Override
- public void scheduleHeadsUpEscalation() {
- }
-
- @Override
- public void scheduleHeadsUpClose() {
+ public void escalateHeadsUp() {
}
@Override
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HeadsUpNotificationTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HeadsUpNotificationTest.java
deleted file mode 100644
index e8a80d9..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HeadsUpNotificationTest.java
+++ /dev/null
@@ -1,261 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.systemui.statusbar.policy;
-
-import static org.mockito.Matchers.anyInt;
-import static org.mockito.Mockito.inOrder;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.reset;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.when;
-
-import android.app.Notification;
-import android.os.*;
-import android.service.notification.StatusBarNotification;
-import com.android.systemui.SwipeHelper;
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.statusbar.ExpandableNotificationRow;
-import com.android.systemui.statusbar.NotificationData;
-import com.android.systemui.statusbar.phone.PhoneStatusBar;
-
-import org.mockito.ArgumentCaptor;
-import org.mockito.InOrder;
-import org.mockito.Mock;
-import org.mockito.Mockito;
-import org.mockito.MockitoAnnotations;
-
-/**
- * Test the Heads Up Notification.
- *
- * Specifically the policy that a notificaiton must remain visibile for a minimum period of time.
- */
-public class HeadsUpNotificationTest extends SysuiTestCase {
- private static final String TAG = "HeadsUpNotificationTest";
-
- private static int TOUCH_SENSITIVITY = 100;
- private static int NOTIFICATION_DECAY = 10000;
- private static int MINIMUM_DISPLAY_TIME = 3000;
- private static int SNOOZE_TIME = 60000;
- private static long TOO_SOON = 1000L; // less than MINIMUM_DISPLAY_TIME
- private static long LATER = 5000L; // more than MINIMUM_DISPLAY_TIME
- private static long REMAINING_VISIBILITY = MINIMUM_DISPLAY_TIME - TOO_SOON;
-
- protected HeadsUpNotificationView mHeadsUp;
-
- @Mock protected PhoneStatusBar mMockStatusBar;
- @Mock private HeadsUpNotificationView.Clock mClock;
- @Mock private SwipeHelper mMockSwipeHelper;
- @Mock private HeadsUpNotificationView.EdgeSwipeHelper mMockEdgeSwipeHelper;
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
-
- MockitoAnnotations.initMocks(this);
-
- mHeadsUp = new HeadsUpNotificationView(mContext,
- mClock, mMockSwipeHelper, mMockEdgeSwipeHelper,
- NOTIFICATION_DECAY, MINIMUM_DISPLAY_TIME, TOUCH_SENSITIVITY, SNOOZE_TIME);
- mHeadsUp.setBar(mMockStatusBar);
- }
-
- private NotificationData.Entry makeNotification(String key) {
- StatusBarNotification sbn = mock(StatusBarNotification.class);
- when(sbn.getKey()).thenReturn(key);
- return new NotificationData.Entry(sbn, null);
- }
-
- public void testPostAndDecay() {
- NotificationData.Entry a = makeNotification("a");
- mHeadsUp.showNotification(a);
- Mockito.verify(mMockStatusBar, never()).scheduleHeadsUpClose();
- Mockito.verify(mMockStatusBar, times(1)).scheduleHeadsUpOpen();
- ArgumentCaptor<Long> decayArg = ArgumentCaptor.forClass(Long.class);
- Mockito.verify(mMockStatusBar).scheduleHeadsUpDecay(decayArg.capture());
- // New notification gets a full decay time.
- assertEquals(NOTIFICATION_DECAY, (long) decayArg.getValue());
- }
-
- public void testPostAndDeleteTooSoon() {
- when(mClock.currentTimeMillis()).thenReturn(0L);
- NotificationData.Entry a = makeNotification("a");
- mHeadsUp.showNotification(a);
- reset(mMockStatusBar);
-
- when(mClock.currentTimeMillis()).thenReturn(TOO_SOON);
- mHeadsUp.removeNotification(a.key);
- ArgumentCaptor<Long> decayArg = ArgumentCaptor.forClass(Long.class);
- Mockito.verify(mMockStatusBar, never()).scheduleHeadsUpClose();
- Mockito.verify(mMockStatusBar).scheduleHeadsUpDecay(decayArg.capture());
- // Leave the window up for the balance of the minumum time.
- assertEquals(REMAINING_VISIBILITY, (long) decayArg.getValue());
- }
-
- public void testPostAndDeleteLater() {
- when(mClock.currentTimeMillis()).thenReturn(0L);
- NotificationData.Entry a = makeNotification("a");
- mHeadsUp.showNotification(a);
- reset(mMockStatusBar);
-
- when(mClock.currentTimeMillis()).thenReturn(LATER);
- mHeadsUp.removeNotification(a.key);
- // Delete closes immediately if the minimum time window is satisfied.
- Mockito.verify(mMockStatusBar, times(1)).scheduleHeadsUpClose();
- Mockito.verify(mMockStatusBar, never()).scheduleHeadsUpDecay(anyInt());
- }
-
- // This is a bad test. It should not care that there is a call to scheduleHeadsUpClose(),
- // but it happens that there will be one, so it is important that it happen before the
- // call to scheduleHeadsUpOpen(), so that the final state is open.
- // Maybe mMockStatusBar should instead be a fake that tracks the open/closed state.
- public void testPostAndReplaceTooSoon() {
- InOrder callOrder = inOrder(mMockStatusBar);
- when(mClock.currentTimeMillis()).thenReturn(0L);
- NotificationData.Entry a = makeNotification("a");
- mHeadsUp.showNotification(a);
- reset(mMockStatusBar);
-
- when(mClock.currentTimeMillis()).thenReturn(TOO_SOON);
- NotificationData.Entry b = makeNotification("b");
- mHeadsUp.showNotification(b);
- Mockito.verify(mMockStatusBar, times(1)).scheduleHeadsUpClose();
- ArgumentCaptor<Long> decayArg = ArgumentCaptor.forClass(Long.class);
- Mockito.verify(mMockStatusBar, times(1)).scheduleHeadsUpDecay(decayArg.capture());
- // New notification gets a full decay time.
- assertEquals(NOTIFICATION_DECAY, (long) decayArg.getValue());
-
- // Make sure close was called before open, so that the heads up stays open.
- callOrder.verify(mMockStatusBar).scheduleHeadsUpClose();
- callOrder.verify(mMockStatusBar).scheduleHeadsUpOpen();
- }
-
- public void testPostAndUpdateAlertAgain() {
- when(mClock.currentTimeMillis()).thenReturn(0L);
- NotificationData.Entry a = makeNotification("a");
- mHeadsUp.showNotification(a);
- reset(mMockStatusBar);
-
- when(mClock.currentTimeMillis()).thenReturn(TOO_SOON);
- mHeadsUp.updateNotification(a, true);
- Mockito.verify(mMockStatusBar, never()).scheduleHeadsUpClose();
- ArgumentCaptor<Long> decayArg = ArgumentCaptor.forClass(Long.class);
- Mockito.verify(mMockStatusBar, times(1)).scheduleHeadsUpDecay(decayArg.capture());
- // Alert again gets a full decay time.
- assertEquals(NOTIFICATION_DECAY, (long) decayArg.getValue());
- }
-
- public void testPostAndUpdateAlertAgainFastFail() {
- when(mClock.currentTimeMillis()).thenReturn(0L);
- NotificationData.Entry a = makeNotification("a");
- mHeadsUp.showNotification(a);
- reset(mMockStatusBar);
-
- when(mClock.currentTimeMillis()).thenReturn(TOO_SOON);
- NotificationData.Entry a_prime = makeNotification("a");
- mHeadsUp.updateNotification(a_prime, true);
- Mockito.verify(mMockStatusBar, never()).scheduleHeadsUpClose();
- ArgumentCaptor<Long> decayArg = ArgumentCaptor.forClass(Long.class);
- Mockito.verify(mMockStatusBar, times(1)).scheduleHeadsUpDecay(decayArg.capture());
- // Alert again gets a full decay time.
- assertEquals(NOTIFICATION_DECAY, (long) decayArg.getValue());
- }
-
- public void testPostAndUpdateNoAlertAgain() {
- when(mClock.currentTimeMillis()).thenReturn(0L);
- NotificationData.Entry a = makeNotification("a");
- mHeadsUp.showNotification(a);
- reset(mMockStatusBar);
-
- when(mClock.currentTimeMillis()).thenReturn(TOO_SOON);
- mHeadsUp.updateNotification(a, false);
- Mockito.verify(mMockStatusBar, never()).scheduleHeadsUpClose();
- Mockito.verify(mMockStatusBar, never()).scheduleHeadsUpDecay(anyInt());
- }
-
- public void testPostAndUpdateNoAlertAgainFastFail() {
- when(mClock.currentTimeMillis()).thenReturn(0L);
- NotificationData.Entry a = makeNotification("a");
- mHeadsUp.showNotification(a);
- reset(mMockStatusBar);
-
- when(mClock.currentTimeMillis()).thenReturn(TOO_SOON);
- NotificationData.Entry a_prime = makeNotification("a");
- mHeadsUp.updateNotification(a_prime, false);
- Mockito.verify(mMockStatusBar, never()).scheduleHeadsUpClose();
- Mockito.verify(mMockStatusBar, never()).scheduleHeadsUpDecay(anyInt());
- }
-
- public void testPostAndUpdateLowPriorityTooSoon() {
- when(mClock.currentTimeMillis()).thenReturn(0L);
- NotificationData.Entry a = makeNotification("a");
- mHeadsUp.showNotification(a);
- reset(mMockStatusBar);
-
- when(mClock.currentTimeMillis()).thenReturn(TOO_SOON);
- mHeadsUp.release();
- Mockito.verify(mMockStatusBar, never()).scheduleHeadsUpClose();
- ArgumentCaptor<Long> decayArg = ArgumentCaptor.forClass(Long.class);
- Mockito.verify(mMockStatusBar, times(1)).scheduleHeadsUpDecay(decayArg.capture());
- // Down grade on update leaves the window up for the balance of the minumum time.
- assertEquals(REMAINING_VISIBILITY, (long) decayArg.getValue());
- }
-
- public void testPostAndUpdateLowPriorityTooSoonFastFail() {
- when(mClock.currentTimeMillis()).thenReturn(0L);
- NotificationData.Entry a = makeNotification("a");
- mHeadsUp.showNotification(a);
- reset(mMockStatusBar);
-
- when(mClock.currentTimeMillis()).thenReturn(TOO_SOON);
- NotificationData.Entry a_prime = makeNotification("a");
- mHeadsUp.updateNotification(a_prime, false);
- mHeadsUp.release();
- Mockito.verify(mMockStatusBar, never()).scheduleHeadsUpClose();
- ArgumentCaptor<Long> decayArg = ArgumentCaptor.forClass(Long.class);
- Mockito.verify(mMockStatusBar, times(1)).scheduleHeadsUpDecay(decayArg.capture());
- // Down grade on update leaves the window up for the balance of the minumum time.
- assertEquals(REMAINING_VISIBILITY, (long) decayArg.getValue());
- }
-
- public void testPostAndUpdateLowPriorityLater() {
- when(mClock.currentTimeMillis()).thenReturn(0L);
- NotificationData.Entry a = makeNotification("a");
- mHeadsUp.showNotification(a);
- reset(mMockStatusBar);
-
- when(mClock.currentTimeMillis()).thenReturn(LATER);
- mHeadsUp.release();
- // Down grade on update closes immediately if the minimum time window is satisfied.
- Mockito.verify(mMockStatusBar, times(1)).scheduleHeadsUpClose();
- Mockito.verify(mMockStatusBar, never()).scheduleHeadsUpDecay(anyInt());
- }
-
- public void testPostAndUpdateLowPriorityLaterFastFail() {
- when(mClock.currentTimeMillis()).thenReturn(0L);
- NotificationData.Entry a = makeNotification("a");
- mHeadsUp.showNotification(a);
- reset(mMockStatusBar);
-
- when(mClock.currentTimeMillis()).thenReturn(LATER);
- NotificationData.Entry a_prime = makeNotification("a");
- mHeadsUp.updateNotification(a_prime, false);
- mHeadsUp.release();
- // Down grade on update closes immediately if the minimum time window is satisfied.
- Mockito.verify(mMockStatusBar, times(1)).scheduleHeadsUpClose();
- Mockito.verify(mMockStatusBar, never()).scheduleHeadsUpDecay(anyInt());
- }
-}