diff options
author | Selim Cinek <cinek@google.com> | 2015-03-16 17:09:28 -0700 |
---|---|---|
committer | Selim Cinek <cinek@google.com> | 2015-04-15 12:32:00 -0700 |
commit | b8f09cf5533d458868a335ce334e4880b2b0788d (patch) | |
tree | 2eaf9145bf9bc4d92e6de1f3169c10fc47d56c02 /packages/SystemUI/src/com/android/systemui/statusbar | |
parent | ba67acff25950a32476273b5d60192a3874c2130 (diff) | |
download | frameworks_base-b8f09cf5533d458868a335ce334e4880b2b0788d.zip frameworks_base-b8f09cf5533d458868a335ce334e4880b2b0788d.tar.gz frameworks_base-b8f09cf5533d458868a335ce334e4880b2b0788d.tar.bz2 |
Integrate Heads-up notifications into the shade
Change-Id: I4ca0fb4e76e7c974490538c168da0564fe97e0ae
Diffstat (limited to 'packages/SystemUI/src/com/android/systemui/statusbar')
16 files changed, 1333 insertions, 1100 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java index ee607a7..b9b3388 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.NotificationData.Entry; 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 @@ public abstract class BaseStatusBar extends SystemUI implements 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 @@ public abstract class BaseStatusBar extends SystemUI implements 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 @@ public abstract class BaseStatusBar extends SystemUI implements @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 @@ public abstract class BaseStatusBar extends SystemUI implements // Remove existing notification to avoid stale data. if (isUpdate) { - removeNotification(sbn.getKey(), rankingMap); + removeNotification(key, rankingMap); } else { mNotificationData.updateRanking(rankingMap); } @@ -693,13 +688,11 @@ public abstract class BaseStatusBar extends SystemUI implements } 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,8 @@ public abstract class BaseStatusBar extends SystemUI implements protected View updateNotificationVetoButton(View row, StatusBarNotification n) { View vetoButton = row.findViewById(R.id.veto); - if (n.isClearable() || (mHeadsUpNotificationView.getEntry() != null - && mHeadsUpNotificationView.getEntry().row == row)) { + // TODO: make headsup non-clearable if it is in the shade + if (n.isClearable() || (mHeadsUpManager.isHeadsUp(n.getKey()))) { final String _pkg = n.getPackageName(); final String _tag = n.getTag(); final int _id = n.getId(); @@ -1002,9 +995,6 @@ public abstract class BaseStatusBar extends SystemUI implements } } - public void onHeadsUpDismissed() { - } - @Override public void showRecentApps(boolean triggeredFromAltTab) { int msg = MSG_SHOW_RECENT_APPS; @@ -1141,13 +1131,10 @@ public abstract class BaseStatusBar extends SystemUI implements // 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 +1225,8 @@ public abstract class BaseStatusBar extends SystemUI implements 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(NotificationData.Entry entry, ViewGroup parent, + boolean isHeadsUp) { PackageManager pmUser = getPackageManagerForUser( entry.notification.getUser().getIdentifier()); @@ -1284,7 +1264,6 @@ public abstract class BaseStatusBar extends SystemUI implements hasUserChangedExpansion = row.hasUserChangedExpansion(); userExpanded = row.isUserExpanded(); userLocked = row.isUserLocked(); - entry.row.setHeadsUp(isHeadsUp); entry.reset(); if (hasUserChangedExpansion) { row.setUserExpanded(userExpanded); @@ -1597,12 +1576,12 @@ public abstract class BaseStatusBar extends SystemUI implements 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 @@ -1740,7 +1719,8 @@ public abstract class BaseStatusBar extends SystemUI implements return entry.notification; } - protected NotificationData.Entry createNotificationViews(StatusBarNotification sbn) { + protected NotificationData.Entry createNotificationViews(StatusBarNotification sbn, + boolean isHeadsUp) { if (DEBUG) { Log.d(TAG, "createNotificationViews(notification=" + sbn); } @@ -1751,7 +1731,7 @@ public abstract class BaseStatusBar extends SystemUI implements // Construct the expanded view. NotificationData.Entry entry = new NotificationData.Entry(sbn, iconView); - if (!inflateViews(entry, mStackScroller)) { + if (!inflateViews(entry, mStackScroller, isHeadsUp)) { handleNotificationError(sbn, "Couldn't expand RemoteViews for: " + sbn); return null; } @@ -1889,90 +1869,29 @@ public abstract class BaseStatusBar extends SystemUI implements 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; - - // 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 headsUpContentView = n.headsUpContentView; - final Notification oldPublicNotification = oldNotification.getNotification().publicVersion; - final RemoteViews oldPublicContentView = oldPublicNotification != null - ? oldPublicNotification.contentView : null; - 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 - && 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) - && bigContentView.getPackage() != null - && oldBigContentView.getPackage() != null - && oldBigContentView.getPackage().equals(bigContentView.getPackage()) - && oldBigContentView.getLayoutId() == bigContentView.getLayoutId()); - boolean headsUpContentsUnchanged = - (oldHeadsUpContentView == null && headsUpContentView == null) - || ((oldHeadsUpContentView != null && headsUpContentView != null) - && headsUpContentView.getPackage() != null - && oldHeadsUpContentView.getPackage() != null - && oldHeadsUpContentView.getPackage().equals(headsUpContentView.getPackage()) - && oldHeadsUpContentView.getLayoutId() == headsUpContentView.getLayoutId()); - boolean publicUnchanged = - (oldPublicContentView == null && publicContentView == null) - || ((oldPublicContentView != null && publicContentView != null) - && publicContentView.getPackage() != null - && oldPublicContentView.getPackage() != null - && oldPublicContentView.getPackage().equals(publicContentView.getPackage()) - && oldPublicContentView.getLayoutId() == publicContentView.getLayoutId()); - + logUpdate(entry, n); + } + boolean applyInPlace = shouldApplyInPlace(entry, n); final boolean shouldInterrupt = shouldInterrupt(notification); - final boolean alertAgain = shouldInterrupt && alertAgain(oldEntry, n); + final boolean alertAgain = shouldInterrupt && alertAgain(entry, n); + boolean isHeadsUp = shouldInterrupt && alertAgain; + + entry.notification = notification; + mGroupManager.onEntryUpdated(entry, entry.notification); + boolean updateSuccessful = false; - if (contentsUnchanged && bigContentsUnchanged && headsUpContentsUnchanged - && publicUnchanged) { + if (applyInPlace) { + // We can just reapply the notifications in place if (DEBUG) Log.d(TAG, "reusing notification for key: " + key); - oldEntry.notification = notification; - mGroupManager.onEntryUpdated(oldEntry, oldNotification); try { - if (oldEntry.icon != null) { + if (entry.icon != null) { // Update the icon final StatusBarIcon ic = new StatusBarIcon(notification.getPackageName(), notification.getUser(), @@ -1980,88 +1899,39 @@ public abstract class BaseStatusBar extends SystemUI implements n.iconLevel, n.number, n.tickerText); - oldEntry.icon.setNotification(n); - if (!oldEntry.icon.set(ic)) { + entry.icon.setNotification(n); + if (!entry.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(); + updateNotificationViews(entry, notification, isHeadsUp); 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); + 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); - 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(); - } - } + 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, isHeadsUp); } + mNotificationData.updateRanking(ranking); + updateNotifications(); + updateHeadsUp(key, entry, shouldInterrupt, alertAgain); // Update the veto button accordingly (and as a result, whether this row is // swipe-dismissable) - updateNotificationVetoButton(oldEntry.row, notification); + updateNotificationVetoButton(entry.row, notification); // Is this for you? boolean isForCurrentUser = isNotificationForCurrentProfiles(notification); @@ -2071,14 +1941,81 @@ public abstract class BaseStatusBar extends SystemUI implements setAreThereNotifications(); } - private void updateNotificationViews(NotificationData.Entry entry, - StatusBarNotification notification) { - updateNotificationViews(entry, notification, false); + private void updateHeadsUp(String key, Entry entry, boolean shouldInterrupt, + boolean alertAgain) { + final boolean wasHeadsUp = isHeadsUp(key); + if (wasHeadsUp) { + mHeadsUpManager.updateNotification(entry, alertAgain); + if (!shouldInterrupt) { + mHeadsUpManager.removeNotification(key); + } + } + } + + 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); } - private void updateHeadsUpViews(NotificationData.Entry entry, - StatusBarNotification notification) { - updateNotificationViews(entry, notification, true); + /** + * @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; + final RemoteViews contentView = n.contentView; + final RemoteViews oldBigContentView = oldNotification.getNotification().bigContentView; + final RemoteViews bigContentView = n.bigContentView; + final RemoteViews oldHeadsUpContentView + = oldNotification.getNotification().headsUpContentView; + final RemoteViews headsUpContentView = n.headsUpContentView; + final Notification oldPublicNotification = oldNotification.getNotification().publicVersion; + final RemoteViews oldPublicContentView = oldPublicNotification != null + ? oldPublicNotification.contentView : null; + final Notification publicNotification = n.publicVersion; + final RemoteViews publicContentView = publicNotification != null + ? publicNotification.contentView : 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 = + (entry.getBigContentView() == null && bigContentView == null) + || ((entry.getBigContentView() != null && bigContentView != null) + && bigContentView.getPackage() != null + && oldBigContentView.getPackage() != null + && oldBigContentView.getPackage().equals(bigContentView.getPackage()) + && oldBigContentView.getLayoutId() == bigContentView.getLayoutId()); + boolean headsUpContentsUnchanged = + (oldHeadsUpContentView == null && headsUpContentView == null) + || ((oldHeadsUpContentView != null && headsUpContentView != null) + && headsUpContentView.getPackage() != null + && oldHeadsUpContentView.getPackage() != null + && oldHeadsUpContentView.getPackage().equals(headsUpContentView.getPackage()) + && oldHeadsUpContentView.getLayoutId() == headsUpContentView.getLayoutId()); + boolean publicUnchanged = + (oldPublicContentView == null && publicContentView == null) + || ((oldPublicContentView != null && publicContentView != null) + && publicContentView.getPackage() != null + && oldPublicContentView.getPackage() != null + && oldPublicContentView.getPackage().equals(publicContentView.getPackage()) + && oldPublicContentView.getLayoutId() == publicContentView.getLayoutId()); + return contentsUnchanged && bigContentsUnchanged && headsUpContentsUnchanged + && publicUnchanged; } private void updateNotificationViews(NotificationData.Entry entry, @@ -2115,10 +2052,8 @@ public abstract class BaseStatusBar extends SystemUI implements applyRemoteInput(entry); } - protected void notifyHeadsUpScreenOn(boolean screenOn) { - if (!screenOn) { - scheduleHeadsUpEscalation(); - } + protected void notifyHeadsUpScreenOff() { + escalateHeadsUp(); } private boolean alertAgain(Entry oldEntry, Notification newNotification) { @@ -2134,7 +2069,7 @@ public abstract class BaseStatusBar extends SystemUI implements 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..60093df 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java @@ -108,6 +108,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { !mChildrenExpanded); } }; + private boolean mInShade; public void setIconAnimationRunning(boolean running) { setIconAnimationRunning(running, mPublicLayout); @@ -164,6 +165,10 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { return mStatusBarNotification; } + public boolean isHeadsUp() { + return mIsHeadsUp; + } + public void setHeadsUp(boolean isHeadsUp) { mIsHeadsUp = isHeadsUp; } @@ -263,6 +268,19 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { return realActualHeight; } + public void setInShade(boolean inShade) { + mInShade = inShade; + } + + public boolean isInShade() { + return mInShade; + } + + public int getHeadsUpHeight() { + // TODO: improve this logic + return getIntrinsicHeight(); + } + public interface ExpansionLogger { public void logNotificationExpansion(String key, boolean userAction, boolean expanded); } 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..386be7e --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java @@ -0,0 +1,161 @@ +/* + * 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; + mHeadsUpManager.releaseAllToShade(); + 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..6fe609e 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.MotionEvent; 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,8 +40,8 @@ import android.widget.FrameLayout; 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; @@ -48,7 +49,9 @@ 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 +59,8 @@ import com.android.systemui.statusbar.stack.StackStateAnimator; 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 +85,7 @@ public class NotificationPanelView extends PanelView implements 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 +181,16 @@ public class NotificationPanelView extends PanelView implements private float mKeyguardStatusBarAnimateAlpha = 1f; private int mOldLayoutDirection; + private HeadsUpTouchHelper mHeadsUpTouchHelper = new HeadsUpTouchHelper(); + private boolean mPinnedHeadsUpExist; + private boolean mExpansionIsFromHeadsUp; + private int mBottomBarHeight; + private Runnable mHeadsUpExistenceChangedRunnable = new Runnable() { + @Override + public void run() { + notifyBarPanelExpansionChanged(); + } + }; public NotificationPanelView(Context context, AttributeSet attrs) { super(context, attrs); @@ -201,7 +215,8 @@ public class NotificationPanelView extends PanelView implements 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 +332,7 @@ public class NotificationPanelView extends PanelView implements if (mQsSizeChangeAnimator == null) { mQsContainer.setHeightOverride(mQsContainer.getDesiredHeight()); } + updateMaxHeadsUpTranslation(); } @Override @@ -493,22 +509,42 @@ public class NotificationPanelView extends PanelView implements } @Override + protected void flingToHeight(float vel, boolean expand, float target) { + if (!expand && mHeadsUpManager.hasPinnedHeadsUp()) { + target = mHeadsUpManager.getHighestPinnedHeadsUp(); + } + 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 (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 +619,7 @@ public class NotificationPanelView extends PanelView implements mIntercepting = false; break; } - return super.onInterceptTouchEvent(event); + return false; } @Override @@ -652,6 +688,15 @@ public class NotificationPanelView extends PanelView implements 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) { @@ -691,8 +736,7 @@ public class NotificationPanelView extends PanelView implements // 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 +878,13 @@ public class NotificationPanelView extends PanelView implements 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 +913,7 @@ public class NotificationPanelView extends PanelView implements mNotificationStackScroller.setInterceptDelegateEnabled(expanded); mStatusBar.setQsExpanded(expanded); mQsPanel.setExpanded(expanded); + mNotificationContainerParent.setQsExpanded(expanded); } } @@ -1056,7 +1101,7 @@ public class NotificationPanelView extends PanelView implements mKeyguardBottomArea.animate() .alpha(0f) .setStartDelay(mStatusBar.getKeyguardFadingAwayDelay()) - .setDuration(mStatusBar.getKeyguardFadingAwayDuration()/2) + .setDuration(mStatusBar.getKeyguardFadingAwayDuration() / 2) .setInterpolator(PhoneStatusBar.ALPHA_OUT) .withEndAction(mAnimateKeyguardBottomAreaInvisibleEndRunnable) .start(); @@ -1402,6 +1447,8 @@ public class NotificationPanelView extends PanelView implements updateHeader(); updateUnlockIcon(); updateNotificationTranslucency(); + mHeadsUpManager.setIsExpanded(!isShadeCollapsed()); + mNotificationStackScroller.setShadeExpanded(!isShadeCollapsed()); if (DEBUG) { invalidate(); } @@ -1544,7 +1591,14 @@ public class NotificationPanelView extends PanelView implements return mExpandedHeight / HEADER_RUBBERBAND_FACTOR - mQsMinExpansionHeight; } } - return Math.min(0, mNotificationStackScroller.getTranslationY()) / HEADER_RUBBERBAND_FACTOR; + float paddingOffset = mNotificationStackScroller.getPaddingOffset(); + float translation = paddingOffset / HEADER_RUBBERBAND_FACTOR; + if (mHeadsUpManager.hasPinnedHeadsUp() || mExpansionIsFromHeadsUp) { + translation = mNotificationStackScroller.getTopPadding() + + mNotificationStackScroller.getPaddingOffset() + - mNotificationTopPadding - mQsMinExpansionHeight; + } + return Math.min(0, translation); } /** @@ -1605,8 +1659,10 @@ public class NotificationPanelView extends PanelView implements protected void onExpandingFinished() { super.onExpandingFinished(); mNotificationStackScroller.onExpansionStopped(); + mHeadsUpManager.onExpandingFinished(); mIsExpanding = false; mScrollYOverride = -1; + // TODO: look into whether this is still correct if (mExpandedHeight == 0f) { setListening(false); } else { @@ -1614,6 +1670,8 @@ public class NotificationPanelView extends PanelView implements } mQsExpandImmediate = false; mTwoFingerQsExpandPossible = false; + mExpansionIsFromHeadsUp = false; + mNotificationStackScroller.setTrackingHeadsUp(mHeadsUpTouchHelper.isTrackingHeadsUp()); } private void setListening(boolean listening) { @@ -1709,6 +1767,17 @@ public class NotificationPanelView extends PanelView implements } @Override + public WindowInsets onApplyWindowInsets(WindowInsets insets) { + mBottomBarHeight = insets.getSystemWindowInsetBottom(); + updateMaxHeadsUpTranslation(); + return insets; + } + + private void updateMaxHeadsUpTranslation() { + mNotificationStackScroller.setMaxHeadsUpTranslation(getHeight() - mBottomBarHeight); + } + + @Override public void onRtlPropertiesChanged(int layoutDirection) { if (layoutDirection != mOldLayoutDirection) { mAfforanceHelper.onRtlPropertiesChanged(); @@ -2066,4 +2135,44 @@ public class NotificationPanelView extends PanelView implements mNotificationStackScroller.getTopPadding(), p); } } + + @Override + public void OnPinnedHeadsUpExistChanged(final boolean exist, boolean changeImmediatly) { + if (exist != mPinnedHeadsUpExist) { + mPinnedHeadsUpExist = exist; + if (!exist) { + mNotificationStackScroller.performOnAnimationFinished( + mHeadsUpExistenceChangedRunnable); + } else { + mHeadsUpExistenceChangedRunnable.run(); + } + } + } + + @Override + public void OnHeadsUpStateChanged(NotificationData.Entry entry, boolean isHeadsUp) { + // TODO: figure out the conditions when not to generate an animation + mNotificationStackScroller.generateHeadsUpAnimation(entry.row, isHeadsUp); + if (isShadeCollapsed()) { + setExpandedHeight(mHeadsUpManager.getHighestPinnedHeadsUp()); + } + } + + private boolean isShadeCollapsed() { + // TODO: handle this cleaner + return mHeader.getTranslationY() + mHeader.getCollapsedHeight() <= 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); + } + } } 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 @@ public class NotificationsQuickSettingsContainer extends FrameLayout 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 @@ public class NotificationsQuickSettingsContainer extends FrameLayout 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 @@ public class NotificationsQuickSettingsContainer extends FrameLayout 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..240438a 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 @@ public class PanelBar extends FrameLayout { 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 @@ public class PanelBar extends FrameLayout { } 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 @@ public class PanelBar extends FrameLayout { } 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..1b89b3f 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.R; 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 @@ import java.io.PrintWriter; 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)); @@ -238,10 +240,7 @@ public abstract class PanelView extends FrameLayout { 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; mHasLayoutedSinceDown = false; @@ -274,9 +273,7 @@ public abstract class PanelView extends FrameLayout { 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 @@ public abstract class PanelView extends FrameLayout { 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 @@ public abstract class PanelView extends FrameLayout { 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) @@ -474,12 +480,7 @@ public abstract class PanelView extends FrameLayout { 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 @@ public abstract class PanelView extends FrameLayout { 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 @@ public abstract class PanelView extends FrameLayout { mHasLayoutedSinceDown = true; if (mUpdateFlingOnLayout) { abortAnimations(); - fling(mUpdateFlingVelocity, true); + fling(mUpdateFlingVelocity, true /* expands */); mUpdateFlingOnLayout = false; } } @@ -805,7 +809,7 @@ public abstract class PanelView extends FrameLayout { 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 @@ public abstract class PanelView extends FrameLayout { 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,8 @@ public abstract class PanelView extends FrameLayout { * @return the height of the clear all button, in pixels */ protected abstract int getClearAllHeight(); + + 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..74a26a8 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; @@ -92,7 +79,6 @@ 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; @@ -137,7 +123,6 @@ import com.android.systemui.statusbar.NotificationOverflowContainer; 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 +132,7 @@ import com.android.systemui.statusbar.policy.BluetoothControllerImpl; 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 +157,26 @@ import java.util.ArrayList; 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.TreeMap; + +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 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, 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 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, }; 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 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, } } return mStatusBarWindow.onTouchEvent(event); - }}); + } + }); mStatusBarView = (PhoneStatusBarView) mStatusBarWindow.findViewById(R.id.status_bar); mStatusBarView.setBar(this); @@ -615,12 +614,13 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, 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); + mHeadsUpManager.setBar(this); + mHeadsUpManager.addListener(this); + mHeadsUpManager.addListener(mNotificationPanel); + mNotificationPanel.setHeadsUpManager(mHeadsUpManager); + if (MULTIUSER_DEBUG) { mNotificationPanelDebugText = (TextView) mNotificationPanel.findViewById( R.id.header_debug_info); @@ -667,6 +667,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, mStackScroller.setLongPressListener(getNotificationLongClicker()); mStackScroller.setPhoneStatusBar(this); mStackScroller.setGroupManager(mGroupManager); + mStackScroller.setHeadsUpManager(mHeadsUpManager); mGroupManager.setOnGroupChangeListener(mStackScroller); mKeyguardIconOverflowContainer = @@ -1084,32 +1085,6 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, 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 +1106,15 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, 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; - } - } + boolean isHeadsUp = mUseHeadsUp && shouldInterrupt(notification); - Entry shadeEntry = createNotificationViews(notification); + Entry shadeEntry = createNotificationViews(notification, isHeadsUp); if (shadeEntry == null) { return; } + if (isHeadsUp) { + mHeadsUpManager.showNotification(shadeEntry); + } if (notification.getNotification().fullScreenIntent != null) { // Stop screensaver if the notification has a full-screen intent. @@ -1175,18 +1135,6 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, 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 +1143,15 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, @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 +1823,38 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, 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 OnHeadsUpStateChanged(Entry entry, boolean isHeadsUp) { + if (!isHeadsUp && mHeadsUpEntriesToRemoveOnSwitch.contains(entry)) { + removeNotification(entry.key, mLatestRankingMap); + mHeadsUpEntriesToRemoveOnSwitch.remove(entry); + if (mHeadsUpEntriesToRemoveOnSwitch.isEmpty()) { + mLatestRankingMap = null; + } + } + } + /** * All changes to the status bar and notifications funnel through here and are batched. */ @@ -1886,15 +1871,6 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, 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 +1879,16 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, } @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() { + TreeMap<String, HeadsUpManager.HeadsUpEntry> entries = mHeadsUpManager.getEntries(); + for (String key : entries.keySet()) { + Entry entry = entries.get(key).entry; + final StatusBarNotification sbn = 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 +1897,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, } } } + mHeadsUpManager.releaseAllImmediately(); } boolean panelsEnabled() { @@ -2081,10 +2030,6 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, // 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 +2423,6 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, 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 +2514,10 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, 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 +2615,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, else if (Intent.ACTION_SCREEN_OFF.equals(action)) { mScreenOn = false; notifyNavigationBarScreenOn(false); - notifyHeadsUpScreenOn(false); + notifyHeadsUpScreenOff(); finishBarAnimations(); resetUserExpandedStates(); } @@ -2764,15 +2707,6 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, 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 +2725,6 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, 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/StatusBarWindowManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java index 63bbf97..6e0bf8f 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 @@ public class StatusBarWindowManager { 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 { @@ -218,6 +218,11 @@ public class StatusBarWindowManager { apply(mCurrentState); } + public void setHeadsUpShowing(boolean showing) { + mCurrentState.headsUpShowing = showing; + apply(mCurrentState); + } + /** * @param state The {@link StatusBarState} of the status bar. */ @@ -235,6 +240,7 @@ public class StatusBarWindowManager { 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..d43af59 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java @@ -0,0 +1,474 @@ +/* + * 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.View; +import android.view.ViewTreeObserver; +import android.view.accessibility.AccessibilityEvent; + +import com.android.systemui.R; +import com.android.systemui.statusbar.NotificationData; +import com.android.systemui.statusbar.phone.PhoneStatusBar; + +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.util.HashSet; +import java.util.Stack; +import java.util.TreeMap; + +public class HeadsUpManager { + 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 TreeMap<String ,HeadsUpEntry> mHeadsUpEntries = new TreeMap<>(); + 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; + + public HeadsUpManager(final Context context) { + 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 = 2000000; + mClock = new Clock(); + // TODO: shadow mSwipeHelper.setMaxSwipeProgress(mMaxAlpha); + + 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); + + // TODO: investigate whether this is still needed +// if (!mHeadsUpEntries.isEmpty()) { +// whoops, we're on already! +// showNotification(mHeadsUpEntries); +// } + } + + 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(); + updatePinnedHeadsUpState(false); + } + + /** + * 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(); + headsUpEntry.entry.row.setInShade(mIsExpanded); + } + } + + private void addHeadsUpEntry(NotificationData.Entry entry) { + boolean wasEmpty = mHeadsUpEntries.isEmpty(); + HeadsUpEntry headsUpEntry = mEntryPool.acquire(); + headsUpEntry.setEntry(entry); + mHeadsUpEntries.put(entry.key, headsUpEntry); + for (OnHeadsUpChangedListener listener : mListeners) { + listener.OnHeadsUpStateChanged(entry, true); + } + entry.row.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED); + entry.row.setHeadsUp(true); + } + + private void removeHeadsUpEntry(NotificationData.Entry entry) { + HeadsUpEntry remove = mHeadsUpEntries.remove(entry.key); + mEntryPool.release(remove); + entry.row.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED); + entry.row.setHeadsUp(false); + for (OnHeadsUpChangedListener listener : mListeners) { + listener.OnHeadsUpStateChanged(entry, false); + } + updatePinnedHeadsUpState(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"); + for (String key: mHeadsUpEntries.keySet()) { + 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 TreeMap<String, HeadsUpEntry> getEntries() { + return mHeadsUpEntries; + } + + public HeadsUpEntry getTopEntry() { + return mHeadsUpEntries.isEmpty() ? null : mHeadsUpEntries.lastEntry().getValue(); + } + + /** + * @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 boolean updateSwipeProgress(View animView, boolean dismissable, float swipeProgress) { + // TODO: handle the shadow + //getBackground().setAlpha((int) (255 * swipeProgress)); + return false; + } + + public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo info) { + // TODO: Look into touchable region +// 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 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 (String key: mHeadsUpEntries.keySet()) { + pw.print(" HeadsUpEntry="); pw.println(mHeadsUpEntries.get(key)); + } + 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 (String key: mHeadsUpEntries.keySet()) { + HeadsUpEntry entry = mHeadsUpEntries.get(key); + 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); + entry.entry.row.setInShade(true); + } + updatePinnedHeadsUpState(true); + } + + 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) { + mIsExpanded = isExpanded; + } + + public int getTopHeadsUpHeight() { + HeadsUpEntry topEntry = getTopEntry(); + return topEntry != null ? topEntry.entry.row.getHeadsUpHeight() : 0; + } + + 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); + } + + @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()); + } + } + + public static class Clock { + public long currentTimeMillis() { + return SystemClock.elapsedRealtime(); + } + } + + public interface OnHeadsUpChangedListener { + void OnPinnedHeadsUpExistChanged(boolean exist, boolean changeImmediatly); + 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..824ba94 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java @@ -18,8 +18,10 @@ package com.android.systemui.statusbar.stack; import android.view.View; import com.android.systemui.statusbar.ActivatableNotificationView; +import com.android.systemui.statusbar.policy.HeadsUpManager; import java.util.ArrayList; +import java.util.TreeMap; /** * A global state to track all input states for the algorithm. @@ -34,6 +36,12 @@ public class AmbientState { private int mSpeedBumpIndex = -1; private boolean mDark; private boolean mHideSensitive; + private HeadsUpManager mHeadsUpManager; + private float mPaddingOffset; + private int mLayoutHeight; + private int mTopPadding; + private boolean mShadeExpanded; + private float mMaxHeadsUpTranslation; public int getScrollY() { return mScrollY; @@ -115,4 +123,56 @@ public class AmbientState { public void setSpeedBumpIndex(int speedBumpIndex) { mSpeedBumpIndex = speedBumpIndex; } + + public void setHeadsUpManager(HeadsUpManager headsUpManager) { + mHeadsUpManager = headsUpManager; + } + + public TreeMap<String, HeadsUpManager.HeadsUpEntry> getHeadsUpEntries() { + return mHeadsUpManager.getEntries(); + } + + public float getPaddingOffset() { + return mPaddingOffset; + } + + public void setPaddingOffset(float paddingOffset) { + mPaddingOffset = paddingOffset; + } + + public int getLayoutHeight() { + return mLayoutHeight; + } + + public void setLayoutHeight(int layoutHeight) { + mLayoutHeight = layoutHeight; + } + + public int getTopPadding() { + return mTopPadding; + } + + public void setTopPadding(int topPadding) { + mTopPadding = topPadding; + } + + public int getInnerHeight() { + return mLayoutHeight - mTopPadding; + } + + public boolean isShadeExpanded() { + return mShadeExpanded; + } + + public void setShadeExpanded(boolean shadeExpanded) { + mShadeExpanded = shadeExpanded; + } + + public void setMaxHeadsUpTranslation(float maxHeadsUpTranslation) { + mMaxHeadsUpTranslation = maxHeadsUpTranslation; + } + + public float getMaxHeadsUpTranslation() { + return mMaxHeadsUpTranslation; + } } 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..cac1b8a 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.Paint; 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,7 @@ import com.android.systemui.statusbar.StackScrollerDecorView; 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.policy.HeadsUpManager; import com.android.systemui.statusbar.policy.ScrollAdapter; import java.util.ArrayList; @@ -179,19 +181,21 @@ public class NotificationStackScrollLayout extends ViewGroup private float mMinTopOverScrollToEscape; private int mIntrinsicPadding; private int mNotificationTopPadding; + private float mPaddingOffset; private float mTopPaddingOverflow; private boolean mDontReportNextOverScroll; private boolean mRequestViewResizeAnimationOnLayout; private boolean mNeedViewResizeAnimation; private View mExpandedGroupView; - private boolean mEverythingNeedsAnimation; + private boolean mEverythingNeedsAnimation; /** * The maximum scrollPosition which we are allowed to reach when a notification was expanded. * This is needed to avoid scrolling too far after the notification was collapsed in the same * motion. */ private int mMaxScrollAfterExpand; + private SwipeHelper.LongPressListener mLongPressListener; /** @@ -203,8 +207,8 @@ public class NotificationStackScrollLayout extends ViewGroup private boolean mInterceptDelegateEnabled; private boolean mDelegateToScrollView; private boolean mDisallowScrollingInThisMotion; - private long mGoToFullShadeDelay; + private long mGoToFullShadeDelay; private ViewTreeObserver.OnPreDrawListener mChildrenUpdater = new ViewTreeObserver.OnPreDrawListener() { @Override @@ -219,6 +223,11 @@ public class NotificationStackScrollLayout extends ViewGroup 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; public NotificationStackScrollLayout(Context context) { this(context, null); @@ -404,8 +413,8 @@ public class NotificationStackScrollLayout extends ViewGroup } private void updateAlgorithmHeightAndPadding() { - mStackScrollAlgorithm.setLayoutHeight(getLayoutHeight()); - mStackScrollAlgorithm.setTopPadding(mTopPadding); + mAmbientState.setLayoutHeight(getLayoutHeight()); + mAmbientState.setTopPadding(mTopPadding); } /** @@ -478,9 +487,13 @@ public class NotificationStackScrollLayout extends ViewGroup 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 +505,13 @@ public class NotificationStackScrollLayout extends ViewGroup 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 +519,19 @@ public class NotificationStackScrollLayout extends ViewGroup updateAlgorithmHeightAndPadding(); requestChildrenUpdate(); } + setPaddingOffset(paddingOffset); + } + + public float getPaddingOffset() { + return mPaddingOffset; + } + + private void setPaddingOffset(float paddingOffset) { + if (paddingOffset != mPaddingOffset) { + mPaddingOffset = paddingOffset; + mAmbientState.setPaddingOffset(paddingOffset); + requestChildrenUpdate(); + } } /** @@ -543,11 +573,6 @@ public class NotificationStackScrollLayout extends ViewGroup 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 +581,17 @@ public class NotificationStackScrollLayout extends ViewGroup } 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 @@ -578,11 +614,6 @@ public class NotificationStackScrollLayout extends ViewGroup return false; } - @Override - public float getFalsingThresholdFactor() { - return mPhoneStatusBar.isScreenOnComingFromTouch() ? 1.5f : 1.0f; - } - public void onBeginDrag(View v) { setSwipingInProgress(true); mAmbientState.onBeginDrag(v); @@ -597,6 +628,11 @@ public class NotificationStackScrollLayout extends ViewGroup setSwipingInProgress(false); } + @Override + public float getFalsingThresholdFactor() { + return mPhoneStatusBar.isScreenOnComingFromTouch() ? 1.5f : 1.0f; + } + public View getChildAtPosition(MotionEvent ev) { return getChildAtPosition(ev.getX(), ev.getY()); } @@ -1803,10 +1839,21 @@ public class NotificationStackScrollLayout extends ViewGroup generateGoToFullShadeEvent(); generateViewResizeEvent(); generateGroupExpansionEvent(); + generateHeadsUpAnimationEvents(); generateAnimateEverythingEvent(); mNeedsAnimation = false; } + private void generateHeadsUpAnimationEvents() { + for (Pair<ExpandableNotificationRow, Boolean> eventPair : mHeadsUpChangeAnimations) { + int type = eventPair.second ? AnimationEvent.ANIMATION_TYPE_HEADS_UP_APPEAR + : AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR; + mAnimationEvents.add(new AnimationEvent(eventPair.first, + type)); + } + mHeadsUpChangeAnimations.clear(); + } + private void generateGroupExpansionEvent() { // Generate a group expansion/collapsing event if there is such a group at all if (mExpandedGroupView != null) { @@ -2182,6 +2229,10 @@ public class NotificationStackScrollLayout extends ViewGroup public void onChildAnimationFinished() { requestChildrenUpdate(); + for (Runnable runnable : mAnimationFinishedRunnables) { + runnable.run(); + } + mAnimationFinishedRunnables.clear(); } /** @@ -2579,6 +2630,36 @@ public class NotificationStackScrollLayout extends ViewGroup } } + public void performOnAnimationFinished(Runnable runnable) { + mAnimationFinishedRunnables.add(runnable); + } + + public void setHeadsUpManager(HeadsUpManager headsUpManager) { + mHeadsUpManager = headsUpManager; + mAmbientState.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); + } + + public void setMaxHeadsUpTranslation(int maxTranslation) { + mAmbientState.setMaxHeadsUpTranslation(maxTranslation); + requestChildrenUpdate(); + } + + public void setTrackingHeadsUp(boolean trackingHeadsUp) { + mTrackingHeadsUp = trackingHeadsUp; + } + /** * A listener that is notified when some child locations might have changed. */ @@ -2723,6 +2804,22 @@ public class NotificationStackScrollLayout extends ViewGroup .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_EVERYTHING new AnimationFilter() .animateAlpha() @@ -2780,6 +2877,12 @@ public class NotificationStackScrollLayout extends ViewGroup // 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_EVERYTHING StackStateAnimator.ANIMATION_DURATION_STANDARD, }; @@ -2798,7 +2901,9 @@ public class NotificationStackScrollLayout extends ViewGroup 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_EVERYTHING = 16; static final int DARK_ANIMATION_ORIGIN_INDEX_ABOVE = -1; static final int DARK_ANIMATION_ORIGIN_INDEX_BELOW = -2; 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..b0f287f 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 android.view.ViewGroup; 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.TreeMap; /** * The Algorithm of the {@link com.android.systemui.statusbar.stack @@ -54,11 +56,6 @@ public class StackScrollAlgorithm { 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; @@ -157,13 +154,13 @@ public class StackScrollAlgorithm { 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); @@ -329,23 +326,30 @@ public class StackScrollAlgorithm { * 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; + TreeMap<String, HeadsUpManager.HeadsUpEntry> headsUpEntries = + ambientState.getHeadsUpEntries(); + for (String key: headsUpEntries.keySet()) { + ExpandableView v = headsUpEntries.get(key).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 +362,35 @@ public class StackScrollAlgorithm { } } } + } 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; @@ -427,7 +444,8 @@ public class StackScrollAlgorithm { 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 +453,7 @@ public class StackScrollAlgorithm { // 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 +465,7 @@ public class StackScrollAlgorithm { // 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 +486,44 @@ public class StackScrollAlgorithm { currentYPosition = childViewState.yTranslation + childHeight + mPaddingBetweenElements; yPositionInScrollView = yPositionInScrollViewAfterElement; - childViewState.yTranslation += mTopPadding; + childViewState.yTranslation += ambientState.getTopPadding() + + ambientState.getPaddingOffset(); + + updateHeadsUpStates(resultState, algorithmState, ambientState); + } + } + + private void updateHeadsUpStates(StackScrollState resultState, + StackScrollAlgorithmState algorithmState, AmbientState ambientState) { + TreeMap<String, HeadsUpManager.HeadsUpEntry> headsUpEntries = + ambientState.getHeadsUpEntries(); + boolean hasPinnedHeadsUp = false; + for (String key: headsUpEntries.keySet()) { + ExpandableNotificationRow row = headsUpEntries.get(key).entry.row; + StackViewState childState = resultState.getViewStateForView(row); + if (!row.isInShade()) { + childState.yTranslation = Math.max(childState.yTranslation, 0); + hasPinnedHeadsUp = true; + } + childState.height = Math.max(childState.height, row.getHeadsUpHeight()); + childState.yTranslation = Math.min(childState.yTranslation, + ambientState.getMaxHeadsUpTranslation() - childState.height); + } + if (hasPinnedHeadsUp && !ambientState.isShadeExpanded()) { + // Let's hide all normal views + int childCount = algorithmState.visibleChildren.size(); + for (int i = 0; i < childCount; i++) { + ExpandableView child = algorithmState.visibleChildren.get(i); + StackViewState state = resultState.getViewStateForView(child); + boolean hideView = true; + if (child instanceof ExpandableNotificationRow) { + ExpandableNotificationRow row = (ExpandableNotificationRow) child; + hideView = !row.isHeadsUp(); + } + if (hideView) { + state.alpha = 0.0f; + } + } } } @@ -478,8 +533,9 @@ public class StackScrollAlgorithm { * @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,9 +547,10 @@ public class StackScrollAlgorithm { * @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); } /** @@ -548,8 +605,7 @@ public class StackScrollAlgorithm { 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 +623,7 @@ public class StackScrollAlgorithm { 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 +685,7 @@ public class StackScrollAlgorithm { * @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; @@ -647,7 +703,7 @@ public class StackScrollAlgorithm { 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 +800,6 @@ public class StackScrollAlgorithm { } } - 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 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..9640b84 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java @@ -45,6 +45,8 @@ public class StackStateAnimator { 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 = 280; + public static final int ANIMATION_DURATION_HEADS_UP_DISAPPEAR = 220; 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; @@ -90,6 +92,7 @@ public class StackStateAnimator { private ValueAnimator mTopOverScrollAnimator; private ValueAnimator mBottomOverScrollAnimator; private ExpandableNotificationRow mChildExpandingView; + private StackViewState mTmpState = new StackViewState(); public StackStateAnimator(NotificationStackScrollLayout hostLayout) { mHostLayout = hostLayout; @@ -828,6 +831,13 @@ public class StackStateAnimator { 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); + mTmpState.yTranslation = -mTmpState.height; + finalState.applyState(changingView, mTmpState); } mNewEvents.add(event); } 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 @@ public class TvStatusBar extends BaseStatusBar { } @Override - public void scheduleHeadsUpDecay(long delay) { - } - - @Override - public void scheduleHeadsUpOpen() { - } - - @Override - public void scheduleHeadsUpEscalation() { - } - - @Override - public void scheduleHeadsUpClose() { + public void escalateHeadsUp() { } @Override |