diff options
Diffstat (limited to 'packages/SystemUI/src/com/android')
18 files changed, 1116 insertions, 529 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/NotificationsTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/NotificationsTile.java index 130f9ce..267786b 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/NotificationsTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/NotificationsTile.java @@ -85,7 +85,7 @@ public class NotificationsTile extends QSTile<NotificationsTile.NotificationsSta // noop } }); - vp.postVolumeChanged(AudioManager.STREAM_NOTIFICATION, AudioManager.FLAG_SHOW_UI); + vp.postVolumeChanged(AudioManager.STREAM_RING, AudioManager.FLAG_SHOW_UI); return v; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/AlphaImageView.java b/packages/SystemUI/src/com/android/systemui/statusbar/AlphaImageView.java new file mode 100644 index 0000000..06dc4e6 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/AlphaImageView.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.systemui.statusbar; + +import android.content.Context; +import android.util.AttributeSet; +import android.widget.ImageView; + +/** + * An ImageView which does not have overlapping renderings commands and therefore does not need a + * layer when alpha is changed. + */ +public class AlphaImageView extends ImageView { + public AlphaImageView(Context context) { + super(context); + } + + public AlphaImageView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public AlphaImageView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + public AlphaImageView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + } + + @Override + public boolean hasOverlappingRendering() { + return false; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java index f4db625..fbe76f9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java @@ -48,6 +48,7 @@ import android.provider.Settings; import android.service.dreams.DreamService; import android.service.dreams.IDreamManager; import android.service.notification.NotificationListenerService; +import android.service.notification.NotificationListenerService.Ranking; import android.service.notification.StatusBarNotification; import android.text.TextUtils; import android.util.Log; @@ -77,11 +78,12 @@ import com.android.systemui.R; import com.android.systemui.RecentsComponent; import com.android.systemui.SearchPanelView; import com.android.systemui.SystemUI; +import com.android.systemui.statusbar.NotificationData.Entry; import com.android.systemui.statusbar.phone.KeyguardTouchDelegate; import com.android.systemui.statusbar.stack.NotificationStackScrollLayout; import java.util.ArrayList; -import java.util.Arrays; +import java.util.Collections; import java.util.Locale; import static com.android.keyguard.KeyguardHostView.OnDismissAction; @@ -91,7 +93,7 @@ public abstract class BaseStatusBar extends SystemUI implements public static final String TAG = "StatusBar"; public static final boolean DEBUG = false; public static final boolean MULTIUSER_DEBUG = false; - private static final boolean USE_NOTIFICATION_LISTENER = false; + private static final boolean USE_NOTIFICATION_LISTENER = true; protected static final int MSG_SHOW_RECENT_APPS = 1019; protected static final int MSG_HIDE_RECENT_APPS = 1020; @@ -194,7 +196,7 @@ public abstract class BaseStatusBar extends SystemUI implements mContext.getContentResolver(), Settings.Global.DEVICE_PROVISIONED, 0); if (provisioned != mDeviceProvisioned) { mDeviceProvisioned = provisioned; - updateNotificationIcons(); + updateNotifications(); } final int mode = Settings.Global.getInt(mContext.getContentResolver(), Settings.Global.ZEN_MODE, Settings.Global.ZEN_MODE_OFF); @@ -209,7 +211,7 @@ public abstract class BaseStatusBar extends SystemUI implements // so we just dump our cache ... mUsersAllowingPrivateNotifications.clear(); // ... and refresh all the notifications - updateNotificationIcons(); + updateNotifications(); } }; @@ -280,11 +282,12 @@ public abstract class BaseStatusBar extends SystemUI implements public void onListenerConnected() { if (DEBUG) Log.d(TAG, "onListenerConnected"); final StatusBarNotification[] notifications = getActiveNotifications(); + final Ranking currentRanking = getCurrentRanking(); mHandler.post(new Runnable() { @Override public void run() { for (StatusBarNotification sbn : notifications) { - addNotificationInternal(sbn); + addNotificationInternal(sbn, currentRanking); } } }); @@ -293,13 +296,14 @@ public abstract class BaseStatusBar extends SystemUI implements @Override public void onNotificationPosted(final StatusBarNotification sbn) { if (DEBUG) Log.d(TAG, "onNotificationPosted: " + sbn); + final Ranking currentRanking = getCurrentRanking(); mHandler.post(new Runnable() { @Override public void run() { if (mNotificationData.findByKey(sbn.getKey()) != null) { - updateNotificationInternal(sbn); + updateNotificationInternal(sbn, currentRanking); } else { - addNotificationInternal(sbn); + addNotificationInternal(sbn, currentRanking); } } }); @@ -308,10 +312,24 @@ public abstract class BaseStatusBar extends SystemUI implements @Override public void onNotificationRemoved(final StatusBarNotification sbn) { if (DEBUG) Log.d(TAG, "onNotificationRemoved: " + sbn); + final Ranking currentRanking = getCurrentRanking(); mHandler.post(new Runnable() { @Override public void run() { - removeNotificationInternal(sbn.getKey()); + removeNotificationInternal(sbn.getKey(), currentRanking); + } + }); + } + + @Override + public void onNotificationRankingUpdate() { + if (DEBUG) Log.d(TAG, "onRankingUpdate"); + final Ranking currentRanking = getCurrentRanking(); + mHandler.post(new Runnable() { + @Override + public void run() { + mNotificationData.updateRanking(currentRanking); + updateNotifications(); } }); } @@ -1113,19 +1131,13 @@ public abstract class BaseStatusBar extends SystemUI implements } } - protected StatusBarNotification removeNotificationViews(String key) { - NotificationData.Entry entry = mNotificationData.remove(key); + protected StatusBarNotification removeNotificationViews(String key, Ranking ranking) { + NotificationData.Entry entry = mNotificationData.remove(key, ranking); if (entry == null) { Log.w(TAG, "removeNotification for unknown key: " + key); return null; } - // Remove the expanded view. - ViewGroup rowParent = (ViewGroup)entry.row.getParent(); - if (rowParent != null) rowParent.removeView(entry.row); - updateRowStates(); - updateNotificationIcons(); - updateSpeedBump(); - + updateNotifications(); return entry.notification; } @@ -1159,35 +1171,17 @@ public abstract class BaseStatusBar extends SystemUI implements return entry; } - protected void addNotificationViews(NotificationData.Entry entry) { + protected void addNotificationViews(Entry entry, Ranking ranking) { if (entry == null) { return; } // Add the expanded view and icon. - int pos = mNotificationData.add(entry); - if (DEBUG) { - Log.d(TAG, "addNotificationViews: added at " + pos); - } - updateRowStates(); - updateNotificationIcons(); - updateSpeedBump(); - } - - protected void updateSpeedBump() { - int n = mNotificationData.size(); - int speedBumpIndex = -1; - for (int i = n-1; i >= 0; i--) { - NotificationData.Entry entry = mNotificationData.get(i); - if (entry.row.getVisibility() != View.GONE && speedBumpIndex == -1 - && entry.row.isBelowSpeedBump() ) { - speedBumpIndex = n - 1 - i; - } - } - mStackScroller.updateSpeedBumpIndex(speedBumpIndex); + mNotificationData.add(entry, ranking); + updateNotifications(); } - private void addNotificationViews(StatusBarNotification notification) { - addNotificationViews(createNotificationViews(notification)); + private void addNotificationViews(StatusBarNotification notification, Ranking ranking) { + addNotificationViews(createNotificationViews(notification), ranking); } /** @@ -1201,17 +1195,17 @@ public abstract class BaseStatusBar extends SystemUI implements protected void updateRowStates() { int maxKeyguardNotifications = getMaxKeyguardNotifications(); mKeyguardIconOverflowContainer.getIconsView().removeAllViews(); - int n = mNotificationData.size(); + final int N = mNotificationData.size(); int visibleNotifications = 0; boolean onKeyguard = mState == StatusBarState.KEYGUARD; - for (int i = n-1; i >= 0; i--) { + for (int i = 0; i < N; i++) { NotificationData.Entry entry = mNotificationData.get(i); if (onKeyguard) { entry.row.setExpansionDisabled(true); } else { entry.row.setExpansionDisabled(false); if (!entry.row.isUserLocked()) { - boolean top = (i == n-1); + boolean top = (i == 0); entry.row.setSystemExpanded(top); } } @@ -1238,6 +1232,9 @@ public abstract class BaseStatusBar extends SystemUI implements } else { mKeyguardIconOverflowContainer.setVisibility(View.GONE); } + // Move overflow container to last position. + mStackScroller.changeViewPosition(mKeyguardIconOverflowContainer, + mStackScroller.getChildCount() - 1); } private boolean shouldShowOnKeyguard(StatusBarNotification sbn) { @@ -1247,46 +1244,42 @@ public abstract class BaseStatusBar extends SystemUI implements protected void setZenMode(int mode) { if (!isDeviceProvisioned()) return; mZenMode = mode; - updateNotificationIcons(); + updateNotifications(); } protected abstract void haltTicker(); protected abstract void setAreThereNotifications(); - protected abstract void updateNotificationIcons(); + protected abstract void updateNotifications(); protected abstract void tick(StatusBarNotification n, boolean firstTime); protected abstract void updateExpandedViewPos(int expandedPosition); protected abstract boolean shouldDisableNavbarGestures(); - protected boolean isTopNotification(ViewGroup parent, NotificationData.Entry entry) { - return parent != null && parent.indexOfChild(entry.row) == 0; - } - - @Override public void addNotification(StatusBarNotification notification) { if (!USE_NOTIFICATION_LISTENER) { - addNotificationInternal(notification); + addNotificationInternal(notification, null); } } - public abstract void addNotificationInternal(StatusBarNotification notification); + public abstract void addNotificationInternal(StatusBarNotification notification, + Ranking ranking); @Override public void removeNotification(String key) { if (!USE_NOTIFICATION_LISTENER) { - removeNotificationInternal(key); + removeNotificationInternal(key, null); } } - protected abstract void removeNotificationInternal(String key); + protected abstract void removeNotificationInternal(String key, Ranking ranking); public void updateNotification(StatusBarNotification notification) { if (!USE_NOTIFICATION_LISTENER) { - updateNotificationInternal(notification); + updateNotificationInternal(notification, null); } } - public void updateNotificationInternal(StatusBarNotification notification) { + public void updateNotificationInternal(StatusBarNotification notification, Ranking ranking) { if (DEBUG) Log.d(TAG, "updateNotification(" + notification + ")"); final NotificationData.Entry oldEntry = mNotificationData.findByKey(notification.getKey()); @@ -1358,18 +1351,12 @@ public abstract class BaseStatusBar extends SystemUI implements && oldPublicContentView.getPackage().equals(publicContentView.getPackage()) && oldPublicContentView.getLayoutId() == publicContentView.getLayoutId()); - ViewGroup rowParent = (ViewGroup) oldEntry.row.getParent(); - boolean orderUnchanged = - notification.getNotification().when == oldNotification.getNotification().when - && notification.getScore() == oldNotification.getScore(); - // score now encompasses/supersedes isOngoing() boolean updateTicker = notification.getNotification().tickerText != null && !TextUtils.equals(notification.getNotification().tickerText, oldEntry.notification.getNotification().tickerText); - boolean isTopAnyway = isTopNotification(rowParent, oldEntry); - if (contentsUnchanged && bigContentsUnchanged && headsUpContentsUnchanged && publicUnchanged - && (orderUnchanged || isTopAnyway)) { + if (contentsUnchanged && bigContentsUnchanged && headsUpContentsUnchanged + && publicUnchanged) { if (DEBUG) Log.d(TAG, "reusing notification for key: " + notification.getKey()); oldEntry.notification = notification; try { @@ -1397,22 +1384,20 @@ public abstract class BaseStatusBar extends SystemUI implements handleNotificationError(notification, "Couldn't update icon: " + ic); return; } - updateRowStates(); - updateSpeedBump(); + mNotificationData.updateRanking(ranking); + updateNotifications(); } 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); - removeNotificationViews(notification.getKey()); - addNotificationViews(notification); + removeNotificationViews(notification.getKey(), ranking); + addNotificationViews(notification, ranking); } } else { if (DEBUG) Log.d(TAG, "not reusing notification for key: " + notification.getKey()); if (DEBUG) Log.d(TAG, "contents was " + (contentsUnchanged ? "unchanged" : "changed")); - if (DEBUG) Log.d(TAG, "order was " + (orderUnchanged ? "unchanged" : "changed")); - if (DEBUG) Log.d(TAG, "notification is " + (isTopAnyway ? "top" : "not top")); - removeNotificationViews(notification.getKey()); - addNotificationViews(notification); // will also replace the heads up + removeNotificationViews(notification.getKey(), ranking); + addNotificationViews(notification, ranking); // will also replace the heads up final NotificationData.Entry newEntry = mNotificationData.findByKey( notification.getKey()); final boolean userChangedExpansion = oldEntry.row.hasUserChangedExpansion(); @@ -1559,5 +1544,12 @@ public abstract class BaseStatusBar extends SystemUI implements mWindowManager.removeViewImmediate(mSearchPanelView); } mContext.unregisterReceiver(mBroadcastReceiver); + if (USE_NOTIFICATION_LISTENER) { + try { + mNotificationListener.unregisterAsSystemService(); + } catch (RemoteException e) { + // Ignore. + } + } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/FlingAnimationUtils.java b/packages/SystemUI/src/com/android/systemui/statusbar/FlingAnimationUtils.java index 7d576cb..5f1325b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/FlingAnimationUtils.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/FlingAnimationUtils.java @@ -18,6 +18,7 @@ package com.android.systemui.statusbar; import android.animation.ValueAnimator; import android.content.Context; +import android.view.ViewPropertyAnimator; import android.view.animation.AnimationUtils; import android.view.animation.Interpolator; import android.view.animation.PathInterpolator; @@ -46,6 +47,8 @@ public class FlingAnimationUtils { private float mMaxLengthSeconds; private float mHighVelocityPxPerSecond; + private AnimatorProperties mAnimatorProperties = new AnimatorProperties(); + public FlingAnimationUtils(Context ctx, float maxLengthSeconds) { mMaxLengthSeconds = maxLengthSeconds; mLinearOutSlowIn = new PathInterpolator(0, 0, LINEAR_OUT_SLOW_IN_X2, 1); @@ -80,18 +83,59 @@ public class FlingAnimationUtils { * @param currValue the current value * @param endValue the end value of the animator * @param velocity the current velocity of the motion + */ + public void apply(ViewPropertyAnimator animator, float currValue, float endValue, + float velocity) { + apply(animator, currValue, endValue, velocity, Math.abs(endValue - currValue)); + } + + /** + * Applies the interpolator and length to the animator, such that the fling animation is + * consistent with the finger motion. + * + * @param animator the animator to apply + * @param currValue the current value + * @param endValue the end value of the animator + * @param velocity the current velocity of the motion * @param maxDistance the maximum distance for this interaction; the maximum animation length * gets multiplied by the ratio between the actual distance and this value */ public void apply(ValueAnimator animator, float currValue, float endValue, float velocity, float maxDistance) { + AnimatorProperties properties = getProperties(currValue, endValue, velocity, + maxDistance); + animator.setDuration(properties.duration); + animator.setInterpolator(properties.interpolator); + } + + /** + * Applies the interpolator and length to the animator, such that the fling animation is + * consistent with the finger motion. + * + * @param animator the animator to apply + * @param currValue the current value + * @param endValue the end value of the animator + * @param velocity the current velocity of the motion + * @param maxDistance the maximum distance for this interaction; the maximum animation length + * gets multiplied by the ratio between the actual distance and this value + */ + public void apply(ViewPropertyAnimator animator, float currValue, float endValue, + float velocity, float maxDistance) { + AnimatorProperties properties = getProperties(currValue, endValue, velocity, + maxDistance); + animator.setDuration(properties.duration); + animator.setInterpolator(properties.interpolator); + } + + private AnimatorProperties getProperties(float currValue, + float endValue, float velocity, float maxDistance) { float maxLengthSeconds = (float) (mMaxLengthSeconds * Math.sqrt(Math.abs(endValue - currValue) / maxDistance)); float diff = Math.abs(endValue - currValue); float velAbs = Math.abs(velocity); float durationSeconds = LINEAR_OUT_SLOW_IN_START_GRADIENT * diff / velAbs; if (durationSeconds <= maxLengthSeconds) { - animator.setInterpolator(mLinearOutSlowIn); + mAnimatorProperties.interpolator = mLinearOutSlowIn; } else if (velAbs >= mMinVelocityPxPerSecond) { // Cross fade between fast-out-slow-in and linear interpolator with current velocity. @@ -100,14 +144,15 @@ public class FlingAnimationUtils { = new VelocityInterpolator(durationSeconds, velAbs, diff); InterpolatorInterpolator superInterpolator = new InterpolatorInterpolator( velocityInterpolator, mLinearOutSlowIn, mLinearOutSlowIn); - animator.setInterpolator(superInterpolator); + mAnimatorProperties.interpolator = superInterpolator; } else { // Just use a normal interpolator which doesn't take the velocity into account. durationSeconds = maxLengthSeconds; - animator.setInterpolator(mFastOutSlowIn); + mAnimatorProperties.interpolator = mFastOutSlowIn; } - animator.setDuration((long) (durationSeconds * 1000)); + mAnimatorProperties.duration = (long) (durationSeconds * 1000); + return mAnimatorProperties; } /** @@ -124,6 +169,34 @@ public class FlingAnimationUtils { */ public void applyDismissing(ValueAnimator animator, float currValue, float endValue, float velocity, float maxDistance) { + AnimatorProperties properties = getDismissingProperties(currValue, endValue, velocity, + maxDistance); + animator.setDuration(properties.duration); + animator.setInterpolator(properties.interpolator); + } + + /** + * Applies the interpolator and length to the animator, such that the fling animation is + * consistent with the finger motion for the case when the animation is making something + * disappear. + * + * @param animator the animator to apply + * @param currValue the current value + * @param endValue the end value of the animator + * @param velocity the current velocity of the motion + * @param maxDistance the maximum distance for this interaction; the maximum animation length + * gets multiplied by the ratio between the actual distance and this value + */ + public void applyDismissing(ViewPropertyAnimator animator, float currValue, float endValue, + float velocity, float maxDistance) { + AnimatorProperties properties = getDismissingProperties(currValue, endValue, velocity, + maxDistance); + animator.setDuration(properties.duration); + animator.setInterpolator(properties.interpolator); + } + + private AnimatorProperties getDismissingProperties(float currValue, float endValue, + float velocity, float maxDistance) { float maxLengthSeconds = (float) (mMaxLengthSeconds * Math.pow(Math.abs(endValue - currValue) / maxDistance, 0.5f)); float diff = Math.abs(endValue - currValue); @@ -135,7 +208,7 @@ public class FlingAnimationUtils { Interpolator mLinearOutFasterIn = new PathInterpolator(0, 0, 1, y2); float durationSeconds = startGradient * diff / velAbs; if (durationSeconds <= maxLengthSeconds) { - animator.setInterpolator(mLinearOutFasterIn); + mAnimatorProperties.interpolator = mLinearOutFasterIn; } else if (velAbs >= mMinVelocityPxPerSecond) { // Cross fade between linear-out-faster-in and linear interpolator with current @@ -145,14 +218,15 @@ public class FlingAnimationUtils { = new VelocityInterpolator(durationSeconds, velAbs, diff); InterpolatorInterpolator superInterpolator = new InterpolatorInterpolator( velocityInterpolator, mLinearOutFasterIn, mLinearOutSlowIn); - animator.setInterpolator(superInterpolator); + mAnimatorProperties.interpolator = superInterpolator; } else { // Just use a normal interpolator which doesn't take the velocity into account. durationSeconds = maxLengthSeconds; - animator.setInterpolator(mFastOutLinearIn); + mAnimatorProperties.interpolator = mFastOutLinearIn; } - animator.setDuration((long) (durationSeconds * 1000)); + mAnimatorProperties.duration = (long) (durationSeconds * 1000); + return mAnimatorProperties; } /** @@ -221,4 +295,10 @@ public class FlingAnimationUtils { return time * mVelocity / mDiff; } } + + private static class AnimatorProperties { + Interpolator interpolator; + long duration; + } + } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/InterceptedNotifications.java b/packages/SystemUI/src/com/android/systemui/statusbar/InterceptedNotifications.java index 0555879..993bb92 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/InterceptedNotifications.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/InterceptedNotifications.java @@ -50,7 +50,7 @@ public class InterceptedNotifications { for (int i = 0; i < n; i++) { final StatusBarNotification sbn = mIntercepted.valueAt(i); sbn.getNotification().extras.putBoolean(EXTRA_INTERCEPT, false); - mBar.addNotificationInternal(sbn); + mBar.addNotificationInternal(sbn, null); } mIntercepted.clear(); updateSyntheticNotification(); @@ -88,7 +88,7 @@ public class InterceptedNotifications { private void updateSyntheticNotification() { if (mIntercepted.isEmpty()) { if (mSynKey != null) { - mBar.removeNotificationInternal(mSynKey); + mBar.removeNotificationInternal(mSynKey, null); mSynKey = null; } return; @@ -107,9 +107,9 @@ public class InterceptedNotifications { mBar.getCurrentUserHandle()); if (mSynKey == null) { mSynKey = sbn.getKey(); - mBar.addNotificationInternal(sbn); + mBar.addNotificationInternal(sbn, null); } else { - mBar.updateNotificationInternal(sbn); + mBar.updateNotificationInternal(sbn, null); } final NotificationData.Entry entry = mBar.mNotificationData.findByKey(mSynKey); entry.row.setOnClickListener(mSynClickListener); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java index 5696246..d829ac0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java @@ -16,15 +16,19 @@ package com.android.systemui.statusbar; +import android.app.Notification; +import android.service.notification.NotificationListenerService.Ranking; import android.service.notification.StatusBarNotification; import android.view.View; -import android.widget.ImageView; import java.util.ArrayList; +import java.util.Collections; import java.util.Comparator; /** * The list of currently displaying notifications. + * + * TODO: Rename to NotificationList. */ public class NotificationData { public static final class Entry { @@ -34,7 +38,6 @@ public class NotificationData { public ExpandableNotificationRow row; // the outer expanded view public View expanded; // the inflated RemoteViews public View expandedPublic; // for insecure lockscreens - public ImageView largeIcon; public View expandedBig; private boolean interruption; public Entry() {} @@ -64,18 +67,23 @@ public class NotificationData { } private final ArrayList<Entry> mEntries = new ArrayList<Entry>(); - private final Comparator<Entry> mEntryCmp = new Comparator<Entry>() { - // sort first by score, then by when + private Ranking mRanking; + private final Comparator<Entry> mRankingComparator = new Comparator<Entry>() { + @Override public int compare(Entry a, Entry b) { + if (mRanking != null) { + return mRanking.getRank(a.key) - mRanking.getRank(b.key); + } + final StatusBarNotification na = a.notification; final StatusBarNotification nb = b.notification; - int d = na.getScore() - nb.getScore(); + int d = nb.getScore() - na.getScore(); if (a.interruption != b.interruption) { - return a.interruption ? 1 : -1; + return a.interruption ? -1 : 1; } else if (d != 0) { return d; } else { - return (int) (na.getNotification().when - nb.getNotification().when); + return (int) (nb.getNotification().when - na.getNotification().when); } } }; @@ -97,26 +105,47 @@ public class NotificationData { return null; } - public int add(Entry entry) { - int i; - int N = mEntries.size(); - for (i = 0; i < N; i++) { - if (mEntryCmp.compare(mEntries.get(i), entry) > 0) { - break; - } - } - mEntries.add(i, entry); - return i; + public void add(Entry entry, Ranking ranking) { + mEntries.add(entry); + updateRankingAndSort(ranking); } - public Entry remove(String key) { + public Entry remove(String key, Ranking ranking) { Entry e = findByKey(key); - if (e != null) { - mEntries.remove(e); + if (e == null) { + return null; } + mEntries.remove(e); + updateRankingAndSort(ranking); return e; } + public void updateRanking(Ranking ranking) { + updateRankingAndSort(ranking); + } + + public boolean isAmbient(String key) { + // TODO: Remove when switching to NotificationListener. + if (mRanking == null) { + for (Entry entry : mEntries) { + if (key.equals(entry.key)) { + return entry.notification.getNotification().priority == + Notification.PRIORITY_MIN; + } + } + } else { + return mRanking.isAmbient(key); + } + return false; + } + + private void updateRankingAndSort(Ranking ranking) { + if (ranking != null) { + mRanking = ranking; + } + Collections.sort(mEntries, mRankingComparator); + } + /** * Return whether there are any visible items (i.e. items without an error). */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java index 714ad06..994b329 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java @@ -23,7 +23,6 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.PackageManager; -import android.os.PowerManager; import android.os.RemoteException; import android.os.UserHandle; import android.provider.MediaStore; @@ -33,26 +32,23 @@ import android.view.View; import android.view.accessibility.AccessibilityManager; import android.widget.FrameLayout; import android.widget.ImageView; - import com.android.systemui.R; /** * Implementation for the bottom area of the Keyguard, including camera/phone affordance and status * text. */ -public class KeyguardBottomAreaView extends FrameLayout - implements SwipeAffordanceView.AffordanceListener, +public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickListener, UnlockMethodCache.OnUnlockMethodChangedListener { final static String TAG = "PhoneStatusBar/KeyguardBottomAreaView"; private static final Intent PHONE_INTENT = new Intent(Intent.ACTION_DIAL); - private SwipeAffordanceView mCameraButton; - private SwipeAffordanceView mPhoneButton; + private ImageView mCameraImageView; + private ImageView mPhoneImageView; private ImageView mLockIcon; - private PowerManager mPowerManager; private ActivityStarter mActivityStarter; private UnlockMethodCache mUnlockMethodCache; @@ -76,11 +72,9 @@ public class KeyguardBottomAreaView extends FrameLayout @Override protected void onFinishInflate() { super.onFinishInflate(); - mCameraButton = (SwipeAffordanceView) findViewById(R.id.camera_button); - mPhoneButton = (SwipeAffordanceView) findViewById(R.id.phone_button); + mCameraImageView = (ImageView) findViewById(R.id.camera_button); + mPhoneImageView = (ImageView) findViewById(R.id.phone_button); mLockIcon = (ImageView) findViewById(R.id.lock_icon); - mCameraButton.setAffordanceListener(this); - mPhoneButton.setAffordanceListener(this); watchForDevicePolicyChanges(); watchForAccessibilityChanges(); updateCameraVisibility(); @@ -88,7 +82,6 @@ public class KeyguardBottomAreaView extends FrameLayout mUnlockMethodCache = UnlockMethodCache.getInstance(getContext()); mUnlockMethodCache.addListener(this); updateTrust(); - mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); } public void setActivityStarter(ActivityStarter activityStarter) { @@ -97,12 +90,12 @@ public class KeyguardBottomAreaView extends FrameLayout private void updateCameraVisibility() { boolean visible = !isCameraDisabledByDpm(); - mCameraButton.setVisibility(visible ? View.VISIBLE : View.GONE); + mCameraImageView.setVisibility(visible ? View.VISIBLE : View.GONE); } private void updatePhoneVisibility() { boolean visible = isPhoneVisible(); - mPhoneButton.setVisibility(visible ? View.VISIBLE : View.GONE); + mPhoneImageView.setVisibility(visible ? View.VISIBLE : View.GONE); } private boolean isPhoneVisible() { @@ -162,33 +155,31 @@ public class KeyguardBottomAreaView extends FrameLayout } private void enableAccessibility(boolean touchExplorationEnabled) { - mCameraButton.enableAccessibility(touchExplorationEnabled); - mPhoneButton.enableAccessibility(touchExplorationEnabled); + mCameraImageView.setOnClickListener(touchExplorationEnabled ? this : null); + mCameraImageView.setClickable(touchExplorationEnabled); + mPhoneImageView.setOnClickListener(touchExplorationEnabled ? this : null); + mPhoneImageView.setClickable(touchExplorationEnabled); } - private void launchCamera() { + @Override + public void onClick(View v) { + if (v == mCameraImageView) { + launchCamera(); + } else if (v == mPhoneImageView) { + launchPhone(); + } + } + + public void launchCamera() { mContext.startActivityAsUser( new Intent(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE), UserHandle.CURRENT); } - private void launchPhone() { + public void launchPhone() { mActivityStarter.startActivity(PHONE_INTENT); } - @Override - public void onUserActivity(long when) { - mPowerManager.userActivity(when, false); - } - - @Override - public void onActionPerformed(SwipeAffordanceView view) { - if (view == mCameraButton) { - launchCamera(); - } else if (view == mPhoneButton) { - launchPhone(); - } - } @Override protected void onVisibilityChanged(View changedView, int visibility) { @@ -208,6 +199,18 @@ public class KeyguardBottomAreaView extends FrameLayout mLockIcon.setImageResource(iconRes); } + public ImageView getPhoneImageView() { + return mPhoneImageView; + } + + public ImageView getCameraImageView() { + return mCameraImageView; + } + + public ImageView getLockIcon() { + return mLockIcon; + } + @Override public void onMethodSecureChanged(boolean methodSecure) { updateTrust(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardPageSwipeHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardPageSwipeHelper.java new file mode 100644 index 0000000..b4f4865 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardPageSwipeHelper.java @@ -0,0 +1,398 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.systemui.statusbar.phone; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ValueAnimator; +import android.content.Context; +import android.os.PowerManager; +import android.view.MotionEvent; +import android.view.VelocityTracker; +import android.view.View; +import android.view.ViewConfiguration; +import android.view.ViewPropertyAnimator; +import android.view.animation.AnimationUtils; +import android.view.animation.Interpolator; +import com.android.systemui.R; +import com.android.systemui.statusbar.FlingAnimationUtils; + +import java.util.ArrayList; + +/** + * A touch handler of the Keyguard which is responsible for swiping the content left or right. + */ +public class KeyguardPageSwipeHelper { + + private static final float SWIPE_MAX_ICON_SCALE_AMOUNT = 2.0f; + private static final float SWIPE_RESTING_ALPHA_AMOUNT = 0.7f; + private final Context mContext; + + private FlingAnimationUtils mFlingAnimationUtils; + private Callback mCallback; + private int mTrackingPointer; + private VelocityTracker mVelocityTracker; + private boolean mSwipingInProgress; + private float mInitialTouchX; + private float mInitialTouchY; + private float mTranslation; + private float mTranslationOnDown; + private int mTouchSlop; + private int mMinTranslationAmount; + private int mMinFlingVelocity; + private PowerManager mPowerManager; + private final View mLeftIcon; + private final View mCenterIcon; + private final View mRightIcon; + private Interpolator mFastOutSlowIn; + private Animator mSwipeAnimator; + private boolean mCallbackCalled; + + KeyguardPageSwipeHelper(Callback callback, Context context) { + mContext = context; + mCallback = callback; + mLeftIcon = mCallback.getLeftIcon(); + mCenterIcon = mCallback.getCenterIcon(); + mRightIcon = mCallback.getRightIcon(); + updateIcon(mLeftIcon, 1.0f, SWIPE_RESTING_ALPHA_AMOUNT, false); + updateIcon(mCenterIcon, 1.0f, SWIPE_RESTING_ALPHA_AMOUNT, false); + updateIcon(mRightIcon, 1.0f, SWIPE_RESTING_ALPHA_AMOUNT, false); + mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); + initDimens(); + } + + private void initDimens() { + final ViewConfiguration configuration = ViewConfiguration.get(mContext); + mTouchSlop = configuration.getScaledTouchSlop(); + mMinFlingVelocity = configuration.getScaledMinimumFlingVelocity(); + mMinTranslationAmount = mContext.getResources().getDimensionPixelSize( + R.dimen.keyguard_min_swipe_amount); + mFlingAnimationUtils = new FlingAnimationUtils(mContext, 0.4f); + mFastOutSlowIn = AnimationUtils.loadInterpolator(mContext, + android.R.interpolator.fast_out_slow_in); + } + + public boolean onTouchEvent(MotionEvent event) { + int pointerIndex = event.findPointerIndex(mTrackingPointer); + if (pointerIndex < 0) { + pointerIndex = 0; + mTrackingPointer = event.getPointerId(pointerIndex); + } + final float y = event.getY(pointerIndex); + final float x = event.getX(pointerIndex); + + switch (event.getActionMasked()) { + case MotionEvent.ACTION_DOWN: + if (mSwipingInProgress) { + cancelAnimations(); + } + mInitialTouchY = y; + mInitialTouchX = x; + mTranslationOnDown = mTranslation; + initVelocityTracker(); + trackMovement(event); + 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; + final float newY = event.getY(newIndex); + final float newX = event.getX(newIndex); + mTrackingPointer = event.getPointerId(newIndex); + mInitialTouchY = newY; + mInitialTouchX = newX; + mTranslationOnDown = mTranslation; + } + break; + + case MotionEvent.ACTION_MOVE: + final float w = x - mInitialTouchX; + trackMovement(event); + if (((leftSwipePossible() && w > mTouchSlop) + || (rightSwipePossible() && w < -mTouchSlop)) + && Math.abs(w) > Math.abs(y - mInitialTouchY) + && !mSwipingInProgress) { + cancelAnimations(); + mInitialTouchY = y; + mInitialTouchX = x; + mTranslationOnDown = mTranslation; + mSwipingInProgress = true; + } + if (mSwipingInProgress) { + setTranslation(mTranslationOnDown + x - mInitialTouchX, false); + onUserActivity(event.getEventTime()); + } + break; + + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_CANCEL: + mTrackingPointer = -1; + trackMovement(event); + if (mSwipingInProgress) { + flingWithCurrentVelocity(); + } + if (mVelocityTracker != null) { + mVelocityTracker.recycle(); + mVelocityTracker = null; + } + break; + } + return true; + } + + private boolean rightSwipePossible() { + return mRightIcon.getVisibility() == View.VISIBLE; + } + + private boolean leftSwipePossible() { + return mLeftIcon.getVisibility() == View.VISIBLE; + } + + public boolean onInterceptTouchEvent(MotionEvent ev) { + return false; + } + + private void onUserActivity(long when) { + mPowerManager.userActivity(when, false); + } + + private void cancelAnimations() { + ArrayList<View> targetViews = mCallback.getTranslationViews(); + for (View target : targetViews) { + target.animate().cancel(); + } + View targetView = mTranslation > 0 ? mLeftIcon : mRightIcon; + targetView.animate().cancel(); + if (mSwipeAnimator != null) { + mSwipeAnimator.removeAllListeners(); + mSwipeAnimator.cancel(); + hideInactiveIcons(true); + } + } + + private void flingWithCurrentVelocity() { + float vel = getCurrentVelocity(); + + // We snap back if the current translation is not far enough + boolean snapBack = Math.abs(mTranslation) < mMinTranslationAmount; + + // or if the velocity is in the opposite direction. + boolean velIsInWrongDirection = vel * mTranslation < 0; + snapBack |= Math.abs(vel) > mMinFlingVelocity && velIsInWrongDirection; + vel = snapBack ^ velIsInWrongDirection ? 0 : vel; + fling(vel, snapBack); + } + + private void fling(float vel, final boolean snapBack) { + float target = mTranslation < 0 ? -mCallback.getPageWidth() : mCallback.getPageWidth(); + target = snapBack ? 0 : target; + + // translation Animation + startTranslationAnimations(vel, target); + + // animate left / right icon + startIconAnimation(vel, snapBack, target); + + ValueAnimator animator = ValueAnimator.ofFloat(mTranslation, target); + mFlingAnimationUtils.apply(animator, mTranslation, target, vel); + animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + mTranslation = (float) animation.getAnimatedValue(); + } + }); + animator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mSwipeAnimator = null; + mSwipingInProgress = false; + if (!snapBack && !mCallbackCalled) { + + // ensure that the callback is called eventually + mCallback.onAnimationToSideStarted(mTranslation < 0); + mCallbackCalled = true; + } + } + }); + if (!snapBack) { + mCallbackCalled = false; + animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + int frameNumber; + + @Override + public void onAnimationUpdate(ValueAnimator animation) { + if (frameNumber == 2 && !mCallbackCalled) { + + // we have to wait for the second frame for this call, + // until the render thread has definitely kicked in, to avoid a lag. + mCallback.onAnimationToSideStarted(mTranslation < 0); + mCallbackCalled = true; + } + frameNumber++; + } + }); + } else { + showAllIcons(true); + } + animator.start(); + mSwipeAnimator = animator; + } + + private void startTranslationAnimations(float vel, float target) { + ArrayList<View> targetViews = mCallback.getTranslationViews(); + for (View targetView : targetViews) { + ViewPropertyAnimator animator = targetView.animate(); + mFlingAnimationUtils.apply(animator, mTranslation, target, vel); + animator.translationX(target); + } + } + + private void startIconAnimation(float vel, boolean snapBack, float target) { + float scale = snapBack ? 1.0f : SWIPE_MAX_ICON_SCALE_AMOUNT; + float alpha = snapBack ? SWIPE_RESTING_ALPHA_AMOUNT : 1.0f; + View targetView = mTranslation > 0 + ? mLeftIcon + : mRightIcon; + if (targetView.getVisibility() == View.VISIBLE) { + ViewPropertyAnimator iconAnimator = targetView.animate(); + mFlingAnimationUtils.apply(iconAnimator, mTranslation, target, vel); + iconAnimator.scaleX(scale); + iconAnimator.scaleY(scale); + iconAnimator.alpha(alpha); + } + } + + private void setTranslation(float translation, boolean isReset) { + translation = rightSwipePossible() ? translation : Math.max(0, translation); + translation = leftSwipePossible() ? translation : Math.min(0, translation); + if (translation != mTranslation) { + ArrayList<View> translatedViews = mCallback.getTranslationViews(); + for (View view : translatedViews) { + view.setTranslationX(translation); + } + if (translation == 0.0f) { + boolean animate = !isReset; + showAllIcons(animate); + } else { + View targetView = translation > 0 ? mLeftIcon : mRightIcon; + float progress = Math.abs(translation) / mCallback.getPageWidth(); + progress = Math.min(progress, 1.0f); + float alpha = SWIPE_RESTING_ALPHA_AMOUNT * (1.0f - progress) + progress; + float scale = (1.0f - progress) + progress * SWIPE_MAX_ICON_SCALE_AMOUNT; + updateIcon(targetView, scale, alpha, false); + View otherView = translation < 0 ? mLeftIcon : mRightIcon; + if (mTranslation * translation <= 0) { + // The sign of the translation has changed so we need to hide the other icons + updateIcon(otherView, 0, 0, true); + updateIcon(mCenterIcon, 0, 0, true); + } + } + mTranslation = translation; + } + } + + private void showAllIcons(boolean animate) { + float scale = 1.0f; + float alpha = SWIPE_RESTING_ALPHA_AMOUNT; + updateIcon(mRightIcon, scale, alpha, animate); + updateIcon(mCenterIcon, scale, alpha, animate); + updateIcon(mLeftIcon, scale, alpha, animate); + } + + private void hideInactiveIcons(boolean animate){ + View otherView = mTranslation < 0 ? mLeftIcon : mRightIcon; + updateIcon(otherView, 0, 0, animate); + updateIcon(mCenterIcon, 0, 0, animate); + } + + private void updateIcon(View view, float scale, float alpha, boolean animate) { + if (view.getVisibility() != View.VISIBLE) { + return; + } + if (!animate) { + view.setAlpha(alpha); + view.setScaleX(scale); + view.setScaleY(scale); + // TODO: remove this invalidate once the property setters invalidate it properly + view.invalidate(); + } else { + if (view.getAlpha() != alpha || view.getScaleX() != scale) { + view.animate() + .setInterpolator(mFastOutSlowIn) + .alpha(alpha) + .scaleX(scale) + .scaleY(scale); + } + } + } + + private void trackMovement(MotionEvent event) { + if (mVelocityTracker != null) { + mVelocityTracker.addMovement(event); + } + } + + private void initVelocityTracker() { + if (mVelocityTracker != null) { + mVelocityTracker.recycle(); + } + mVelocityTracker = VelocityTracker.obtain(); + } + + private float getCurrentVelocity() { + if (mVelocityTracker == null) { + return 0; + } + mVelocityTracker.computeCurrentVelocity(1000); + return mVelocityTracker.getXVelocity(); + } + + public void onConfigurationChanged() { + initDimens(); + } + + public void reset() { + setTranslation(0.0f, true); + mSwipingInProgress = false; + } + + public boolean isSwipingInProgress() { + return mSwipingInProgress; + } + + public interface Callback { + + /** + * Notifies the callback when an animation to a side page was started. + * + * @param rightPage Is the page animated to the right page? + */ + void onAnimationToSideStarted(boolean rightPage); + + float getPageWidth(); + + ArrayList<View> getTranslationViews(); + + View getLeftIcon(); + + View getCenterIcon(); + + View getRightIcon(); + } +} 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 52688df..838e5e3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java @@ -21,6 +21,7 @@ import android.animation.AnimatorListenerAdapter; import android.animation.ObjectAnimator; import android.animation.ValueAnimator; import android.content.Context; +import android.content.res.Configuration; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.VelocityTracker; @@ -40,10 +41,13 @@ import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.stack.NotificationStackScrollLayout; import com.android.systemui.statusbar.stack.StackStateAnimator; +import java.util.ArrayList; + public class NotificationPanelView extends PanelView implements ExpandableView.OnHeightChangedListener, ObservableScrollView.Listener, - View.OnClickListener { + View.OnClickListener, KeyguardPageSwipeHelper.Callback { + private KeyguardPageSwipeHelper mPageSwiper; PhoneStatusBar mStatusBar; private StatusBarHeaderView mHeader; private View mQsContainer; @@ -58,7 +62,7 @@ public class NotificationPanelView extends PanelView implements private int mTrackingPointer; private VelocityTracker mVelocityTracker; - private boolean mTracking; + private boolean mQsTracking; /** * Whether we are currently handling a motion gesture in #onInterceptTouchEvent, but haven't @@ -92,6 +96,11 @@ public class NotificationPanelView extends PanelView implements new KeyguardClockPositionAlgorithm(); private KeyguardClockPositionAlgorithm.Result mClockPositionResult = new KeyguardClockPositionAlgorithm.Result(); + private boolean mIsSwipedHorizontally; + private boolean mIsExpanding; + private KeyguardBottomAreaView mKeyguardBottomArea; + private boolean mBlockTouches; + private ArrayList<View> mSwipeTranslationViews = new ArrayList<>(); public NotificationPanelView(Context context, AttributeSet attrs) { super(context, attrs); @@ -129,6 +138,10 @@ public class NotificationPanelView extends PanelView implements mNotificationStackScroller.setOnHeightChangedListener(this); mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(getContext(), android.R.interpolator.fast_out_slow_in); + mKeyguardBottomArea = (KeyguardBottomAreaView) findViewById(R.id.keyguard_bottom_area); + mSwipeTranslationViews.add(mNotificationStackScroller); + mSwipeTranslationViews.add(mKeyguardStatusView); + mPageSwiper = new KeyguardPageSwipeHelper(this, getContext()); } @Override @@ -247,6 +260,12 @@ public class NotificationPanelView extends PanelView implements mQsExpansionEnabled = qsExpansionEnabled; } + public void resetViews() { + mBlockTouches = false; + mPageSwiper.reset(); + closeQs(); + } + public void closeQs() { cancelAnimation(); setQsExpansion(mQsMinExpansionHeight); @@ -263,9 +282,7 @@ public class NotificationPanelView extends PanelView implements public void fling(float vel, boolean always) { GestureRecorder gr = ((PhoneStatusBarView) mBar).mBar.getGestureRecorder(); if (gr != null) { - gr.tag( - "fling " + ((vel > 0) ? "open" : "closed"), - "notifications,v=" + vel); + gr.tag("fling " + ((vel > 0) ? "open" : "closed"), "notifications,v=" + vel); } super.fling(vel, always); } @@ -283,6 +300,9 @@ public class NotificationPanelView extends PanelView implements @Override public boolean onInterceptTouchEvent(MotionEvent event) { + if (mBlockTouches) { + return false; + } int pointerIndex = event.findPointerIndex(mTrackingPointer); if (pointerIndex < 0) { pointerIndex = 0; @@ -298,7 +318,7 @@ public class NotificationPanelView extends PanelView implements mInitialTouchX = x; initVelocityTracker(); trackMovement(event); - if (shouldIntercept(mInitialTouchX, mInitialTouchY, 0)) { + if (shouldQuickSettingsIntercept(mInitialTouchX, mInitialTouchY, 0)) { getParent().requestDisallowInterceptTouchEvent(true); } break; @@ -316,7 +336,7 @@ public class NotificationPanelView extends PanelView implements case MotionEvent.ACTION_MOVE: final float h = y - mInitialTouchY; trackMovement(event); - if (mTracking) { + if (mQsTracking) { // Already tracking because onOverscrolled was called. We need to update here // so we don't stop for a frame until the next touch event gets handled in @@ -327,12 +347,12 @@ public class NotificationPanelView extends PanelView implements return true; } if (Math.abs(h) > mTouchSlop && Math.abs(h) > Math.abs(x - mInitialTouchX) - && shouldIntercept(mInitialTouchX, mInitialTouchY, h)) { + && shouldQuickSettingsIntercept(mInitialTouchX, mInitialTouchY, h)) { onQsExpansionStarted(); mInitialHeightOnTouch = mQsExpansionHeight; mInitialTouchY = y; mInitialTouchX = x; - mTracking = true; + mQsTracking = true; mIntercepting = false; return true; } @@ -341,9 +361,9 @@ public class NotificationPanelView extends PanelView implements case MotionEvent.ACTION_CANCEL: case MotionEvent.ACTION_UP: trackMovement(event); - if (mTracking) { - flingWithCurrentVelocity(); - mTracking = false; + if (mQsTracking) { + flingQsWithCurrentVelocity(); + mQsTracking = false; } mIntercepting = false; break; @@ -362,7 +382,7 @@ public class NotificationPanelView extends PanelView implements super.requestDisallowInterceptTouchEvent(disallowIntercept); } - private void flingWithCurrentVelocity() { + private void flingQsWithCurrentVelocity() { float vel = getCurrentVelocity(); // TODO: Better logic whether we should expand or not. @@ -371,65 +391,83 @@ public class NotificationPanelView extends PanelView implements @Override public boolean onTouchEvent(MotionEvent event) { + if (mBlockTouches) { + return false; + } // TODO: Handle doublefinger swipe to notifications again. Look at history for a reference // implementation. - if (mTracking) { - int pointerIndex = event.findPointerIndex(mTrackingPointer); - if (pointerIndex < 0) { - pointerIndex = 0; - mTrackingPointer = event.getPointerId(pointerIndex); + if (!mIsExpanding && !mQsExpanded && mStatusBar.getBarState() != StatusBarState.SHADE) { + mPageSwiper.onTouchEvent(event); + if (mPageSwiper.isSwipingInProgress()) { + return true; } - final float y = event.getY(pointerIndex); - final float x = event.getX(pointerIndex); + } + if (mQsTracking || mQsExpanded) { + return onQsTouch(event); + } - switch (event.getActionMasked()) { - case MotionEvent.ACTION_DOWN: - mTracking = true; - mInitialTouchY = y; - mInitialTouchX = x; - onQsExpansionStarted(); - mInitialHeightOnTouch = mQsExpansionHeight; - initVelocityTracker(); - trackMovement(event); - 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; - final float newY = event.getY(newIndex); - final float newX = event.getX(newIndex); - mTrackingPointer = event.getPointerId(newIndex); - mInitialHeightOnTouch = mQsExpansionHeight; - mInitialTouchY = newY; - mInitialTouchX = newX; - } - break; + super.onTouchEvent(event); + return true; + } - case MotionEvent.ACTION_MOVE: - final float h = y - mInitialTouchY; - setQsExpansion(h + mInitialHeightOnTouch); - trackMovement(event); - break; + @Override + protected boolean hasConflictingGestures() { + return mStatusBar.getBarState() != StatusBarState.SHADE; + } - case MotionEvent.ACTION_UP: - case MotionEvent.ACTION_CANCEL: - mTracking = false; - mTrackingPointer = -1; - trackMovement(event); - flingWithCurrentVelocity(); - if (mVelocityTracker != null) { - mVelocityTracker.recycle(); - mVelocityTracker = null; - } - break; - } - return true; + private boolean onQsTouch(MotionEvent event) { + int pointerIndex = event.findPointerIndex(mTrackingPointer); + if (pointerIndex < 0) { + pointerIndex = 0; + mTrackingPointer = event.getPointerId(pointerIndex); } + final float y = event.getY(pointerIndex); + final float x = event.getX(pointerIndex); + + switch (event.getActionMasked()) { + case MotionEvent.ACTION_DOWN: + mQsTracking = true; + mInitialTouchY = y; + mInitialTouchX = x; + onQsExpansionStarted(); + mInitialHeightOnTouch = mQsExpansionHeight; + initVelocityTracker(); + trackMovement(event); + 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; + final float newY = event.getY(newIndex); + final float newX = event.getX(newIndex); + mTrackingPointer = event.getPointerId(newIndex); + mInitialHeightOnTouch = mQsExpansionHeight; + mInitialTouchY = newY; + mInitialTouchX = newX; + } + break; - // Consume touch events when QS are expanded. - return mQsExpanded || super.onTouchEvent(event); + case MotionEvent.ACTION_MOVE: + final float h = y - mInitialTouchY; + setQsExpansion(h + mInitialHeightOnTouch); + trackMovement(event); + break; + + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_CANCEL: + mQsTracking = false; + mTrackingPointer = -1; + trackMovement(event); + flingQsWithCurrentVelocity(); + if (mVelocityTracker != null) { + mVelocityTracker.recycle(); + mVelocityTracker = null; + } + break; + } + return true; } @Override @@ -439,7 +477,7 @@ public class NotificationPanelView extends PanelView implements mInitialHeightOnTouch = mQsExpansionHeight; mInitialTouchY = mLastTouchY; mInitialTouchX = mLastTouchX; - mTracking = true; + mQsTracking = true; } } @@ -569,7 +607,7 @@ public class NotificationPanelView extends PanelView implements /** * @return Whether we should intercept a gesture to open Quick Settings. */ - private boolean shouldIntercept(float x, float y, float yDiff) { + private boolean shouldQuickSettingsIntercept(float x, float y, float yDiff) { if (!mQsExpansionEnabled) { return false; } @@ -647,12 +685,14 @@ public class NotificationPanelView extends PanelView implements protected void onExpandingStarted() { super.onExpandingStarted(); mNotificationStackScroller.onExpansionStarted(); + mIsExpanding = true; } @Override protected void onExpandingFinished() { super.onExpandingFinished(); mNotificationStackScroller.onExpansionStopped(); + mIsExpanding = false; } @Override @@ -686,6 +726,12 @@ public class NotificationPanelView extends PanelView implements } @Override + protected void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + mPageSwiper.onConfigurationChanged(); + } + + @Override public void onClick(View v) { if (v == mHeader.getBackgroundView()) { onQsExpansionStarted(); @@ -696,4 +742,40 @@ public class NotificationPanelView extends PanelView implements } } } + + @Override + public void onAnimationToSideStarted(boolean rightPage) { + if (rightPage) { + mKeyguardBottomArea.launchCamera(); + } else { + mKeyguardBottomArea.launchPhone(); + } + mBlockTouches = true; + } + + + @Override + public float getPageWidth() { + return getWidth(); + } + + @Override + public ArrayList<View> getTranslationViews() { + return mSwipeTranslationViews; + } + + @Override + public View getLeftIcon() { + return mKeyguardBottomArea.getPhoneImageView(); + } + + @Override + public View getCenterIcon() { + return mKeyguardBottomArea.getLockIcon(); + } + + @Override + public View getRightIcon() { + return mKeyguardBottomArea.getCameraImageView(); + } } 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 7500c10..f6db8a8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java @@ -35,7 +35,7 @@ import com.android.systemui.statusbar.FlingAnimationUtils; import java.io.FileDescriptor; import java.io.PrintWriter; -public class PanelView extends FrameLayout { +public abstract class PanelView extends FrameLayout { public static final boolean DEBUG = PanelBar.DEBUG; public static final String TAG = PanelView.class.getSimpleName(); protected float mOverExpansion; @@ -130,21 +130,24 @@ public class PanelView extends FrameLayout { final float y = event.getY(pointerIndex); final float x = event.getX(pointerIndex); + boolean waitForTouchSlop = hasConflictingGestures(); + switch (event.getActionMasked()) { case MotionEvent.ACTION_DOWN: - mTracking = true; mInitialTouchY = y; mInitialTouchX = x; + mInitialOffsetOnTouch = mExpandedHeight; if (mVelocityTracker == null) { initVelocityTracker(); } trackMovement(event); - if (mHeightAnimator != null) { - mHeightAnimator.cancel(); // end any outstanding animations + if (!waitForTouchSlop || mHeightAnimator != null) { + if (mHeightAnimator != null) { + mHeightAnimator.cancel(); // end any outstanding animations + } + onTrackingStarted(); } - onTrackingStarted(); - mInitialOffsetOnTouch = mExpandedHeight; if (mExpandedHeight == 0) { mJustPeeked = true; runPeekAnimation(); @@ -166,15 +169,27 @@ public class PanelView extends FrameLayout { break; case MotionEvent.ACTION_MOVE: - final float h = y - mInitialTouchY + mInitialOffsetOnTouch; - if (h > mPeekHeight) { + float h = y - mInitialTouchY; + if (waitForTouchSlop && !mTracking && Math.abs(h) > mTouchSlop + && Math.abs(h) > Math.abs(x - mInitialTouchX)) { + mInitialOffsetOnTouch = mExpandedHeight; + mInitialTouchX = x; + mInitialTouchY = y; + if (mHeightAnimator != null) { + mHeightAnimator.cancel(); // end any outstanding animations + } + onTrackingStarted(); + h = 0; + } + final float newHeight = h + mInitialOffsetOnTouch; + if (newHeight > mPeekHeight) { if (mPeekAnimator != null && mPeekAnimator.isStarted()) { mPeekAnimator.cancel(); } mJustPeeked = false; } - if (!mJustPeeked) { - setExpandedHeightInternal(h); + if (!mJustPeeked && (!waitForTouchSlop || mTracking)) { + setExpandedHeightInternal(newHeight); mBar.panelExpansionChanged(PanelView.this, mExpandedFraction); } @@ -183,7 +198,6 @@ public class PanelView extends FrameLayout { case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: - mTracking = false; mTrackingPointer = -1; trackMovement(event); boolean expand = flingWithCurrentVelocity(); @@ -194,14 +208,18 @@ public class PanelView extends FrameLayout { } break; } - return true; + return !waitForTouchSlop || mTracking; } + protected abstract boolean hasConflictingGestures(); + protected void onTrackingStopped(boolean expand) { + mTracking = false; mBar.onTrackingStopped(PanelView.this, expand); } protected void onTrackingStarted() { + mTracking = true; mBar.onTrackingStarted(PanelView.this); onExpandingStarted(); } 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 15ad709..5dcd61c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java @@ -64,6 +64,7 @@ import android.os.SystemClock; import android.os.UserHandle; import android.provider.Settings; import android.provider.Settings.Global; +import android.service.notification.NotificationListenerService.Ranking; import android.service.notification.StatusBarNotification; import android.util.ArraySet; import android.util.DisplayMetrics; @@ -1037,13 +1038,15 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, } @Override - public void addNotificationInternal(StatusBarNotification notification) { + public void addNotificationInternal(StatusBarNotification notification, Ranking ranking) { if (DEBUG) Log.d(TAG, "addNotification score=" + notification.getScore()); Entry shadeEntry = createNotificationViews(notification); if (shadeEntry == null) { return; } if (mZenMode != Global.ZEN_MODE_OFF && mIntercepted.tryIntercept(notification)) { + // Forward the ranking so we can sort the new notification. + mNotificationData.updateRanking(ranking); return; } if (mUseHeadsUp && shouldInterrupt(notification)) { @@ -1083,7 +1086,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, tick(notification, true); } } - addNotificationViews(shadeEntry); + addNotificationViews(shadeEntry, ranking); // Recalculate the position of the sliding windows and the titles. setAreThereNotifications(); updateExpandedViewPos(EXPANDED_LEAVE_ALONE); @@ -1099,14 +1102,14 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, } @Override - public void updateNotification(StatusBarNotification notification) { - super.updateNotification(notification); + public void updateNotificationInternal(StatusBarNotification notification, Ranking ranking) { + super.updateNotificationInternal(notification, ranking); mIntercepted.update(notification); } @Override - public void removeNotificationInternal(String key) { - StatusBarNotification old = removeNotificationViews(key); + public void removeNotificationInternal(String key, Ranking ranking) { + StatusBarNotification old = removeNotificationViews(key, ranking); if (SPEW) Log.d(TAG, "removeNotification key=" + key + " old=" + old); if (old != null) { @@ -1143,7 +1146,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, R.integer.config_show_search_delay); } - private void loadNotificationShade() { + private void updateNotificationShade() { if (mStackScroller == null) return; int N = mNotificationData.size(); @@ -1153,7 +1156,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, final boolean provisioned = isDeviceProvisioned(); // If the device hasn't been through Setup, we only show system notifications for (int i=0; i<N; i++) { - Entry ent = mNotificationData.get(N-i-1); + Entry ent = mNotificationData.get(i); if (!(provisioned || showNotificationEvenIfUnprovisioned(ent.notification))) continue; // TODO How do we want to badge notifcations from profiles. @@ -1184,26 +1187,75 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, for (int i=0; i<toShow.size(); i++) { View v = toShow.get(i); if (v.getParent() == null) { - mStackScroller.addView(v, i); + mStackScroller.addView(v); } } + // So after all this work notifications still aren't sorted correctly. + // Let's do that now by advancing through toShow and mStackScroller in + // lock-step, making sure mStackScroller matches what we see in toShow. + int j = 0; + for (int i = 0; i < mStackScroller.getChildCount(); i++) { + View child = mStackScroller.getChildAt(i); + if (!(child instanceof ExpandableNotificationRow)) { + // We don't care about non-notification views. + continue; + } + + if (child == toShow.get(j)) { + // Everything is well, advance both lists. + j++; + continue; + } + + // Oops, wrong notification at this position. Put the right one + // here and advance both lists. + mStackScroller.changeViewPosition(toShow.get(j), i); + j++; + } + updateRowStates(); + updateSpeedbump(); mNotificationPanel.setQsExpansionEnabled(provisioned && mUserSetup); } + private void updateSpeedbump() { + int speedbumpIndex = -1; + int currentIndex = 0; + for (int i = 0; i < mNotificationData.size(); i++) { + Entry entry = mNotificationData.get(i); + if (entry.row.getParent() == null) { + // This view isn't even added, so the stack scroller doesn't + // know about it. Ignore completely. + continue; + } + if (entry.row.getVisibility() != View.GONE && + mNotificationData.isAmbient(entry.key)) { + speedbumpIndex = currentIndex; + break; + } + currentIndex++; + } + mStackScroller.updateSpeedBumpIndex(speedbumpIndex); + } + @Override - protected void updateNotificationIcons() { + protected void updateNotifications() { + // TODO: Move this into updateNotificationIcons()? if (mNotificationIcons == null) return; - loadNotificationShade(); + updateNotificationShade(); + updateNotificationIcons(); + } + private void updateNotificationIcons() { final LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(mIconSize + 2*mIconHPadding, mNaturalBarHeight); int N = mNotificationData.size(); if (DEBUG) { - Log.d(TAG, "refreshing icons: " + N + " notifications, mNotificationIcons=" + mNotificationIcons); + Log.d(TAG, "refreshing icons: " + N + " notifications, mNotificationIcons=" + + mNotificationIcons); } ArrayList<View> toShow = new ArrayList<View>(); @@ -1211,7 +1263,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, final boolean provisioned = isDeviceProvisioned(); // If the device hasn't been through Setup, we only show system notifications for (int i=0; i<N; i++) { - Entry ent = mNotificationData.get(N-i-1); + Entry ent = mNotificationData.get(i); if (!((provisioned && ent.notification.getScore() >= HIDE_ICONS_BELOW_SCORE) || showNotificationEvenIfUnprovisioned(ent.notification))) continue; if (!notificationIsForCurrentProfiles(ent.notification)) continue; @@ -2388,7 +2440,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, public void userSwitched(int newUserId) { if (MULTIUSER_DEBUG) mNotificationPanelDebugText.setText("USER " + newUserId); animateCollapsePanels(); - updateNotificationIcons(); + updateNotifications(); resetUserSetupObserver(); } @@ -2774,7 +2826,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, mKeyguardStatusView.setVisibility(View.VISIBLE); mKeyguardIndicationTextView.setVisibility(View.VISIBLE); mKeyguardIndicationTextView.switchIndication(mKeyguardHotwordPhrase); - mNotificationPanel.closeQs(); + mNotificationPanel.resetViews(); } else { mKeyguardStatusView.setVisibility(View.GONE); mKeyguardIndicationTextView.setVisibility(View.GONE); @@ -2793,10 +2845,8 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, updateStackScrollerState(); updatePublicMode(); - updateRowStates(); - updateSpeedBump(); + updateNotifications(); checkBarModes(); - updateNotificationIcons(); updateCarrierLabelVisibility(false); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SwipeAffordanceView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SwipeAffordanceView.java deleted file mode 100644 index 049c5fc..0000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SwipeAffordanceView.java +++ /dev/null @@ -1,222 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.systemui.statusbar.phone; - -import android.content.Context; -import android.content.res.TypedArray; -import android.os.SystemClock; -import android.util.AttributeSet; -import android.view.MotionEvent; -import android.view.View; -import android.view.ViewConfiguration; -import android.view.animation.AccelerateInterpolator; -import android.view.animation.DecelerateInterpolator; -import android.widget.Button; - -import com.android.systemui.R; -import com.android.systemui.statusbar.policy.KeyButtonView; - -/** - * A swipeable button for affordances on the lockscreen. This is used for the camera and phone - * affordance. - */ -public class SwipeAffordanceView extends KeyButtonView { - - private static final int SWIPE_DIRECTION_START = 0; - private static final int SWIPE_DIRECTION_END = 1; - - private static final int SWIPE_DIRECTION_LEFT = 0; - private static final int SWIPE_DIRECTION_RIGHT = 1; - - private AffordanceListener mListener; - private int mScaledTouchSlop; - private float mDragDistance; - private int mResolvedSwipeDirection; - private int mSwipeDirection; - - public SwipeAffordanceView(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public SwipeAffordanceView(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - TypedArray a = context.getTheme().obtainStyledAttributes( - attrs, - R.styleable.SwipeAffordanceView, - 0, 0); - try { - mSwipeDirection = a.getInt(R.styleable.SwipeAffordanceView_swipeDirection, 0); - } finally { - a.recycle(); - } - } - - @Override - public void onRtlPropertiesChanged(int layoutDirection) { - super.onRtlPropertiesChanged(layoutDirection); - if (!isLayoutRtl()) { - mResolvedSwipeDirection = mSwipeDirection; - } else { - mResolvedSwipeDirection = mSwipeDirection == SWIPE_DIRECTION_START - ? SWIPE_DIRECTION_RIGHT - : SWIPE_DIRECTION_LEFT; - } - } - - @Override - protected void onFinishInflate() { - super.onFinishInflate(); - mDragDistance = getResources().getDimension(R.dimen.affordance_drag_distance); - mScaledTouchSlop = ViewConfiguration.get(mContext).getScaledTouchSlop(); - } - - public void enableAccessibility(boolean touchExplorationEnabled) { - - // Add a touch handler or accessibility click listener for camera button. - if (touchExplorationEnabled) { - setOnTouchListener(null); - setOnClickListener(mClickListener); - } else { - setOnTouchListener(mTouchListener); - setOnClickListener(null); - } - } - - public void setAffordanceListener(AffordanceListener listener) { - mListener = listener; - } - - private void onActionPerformed() { - if (mListener != null) { - mListener.onActionPerformed(this); - } - } - - private void onUserActivity(long when) { - if (mListener != null) { - mListener.onUserActivity(when); - } - } - - private final OnClickListener mClickListener = new OnClickListener() { - @Override - public void onClick(View v) { - onActionPerformed(); - } - }; - - private final OnTouchListener mTouchListener = new OnTouchListener() { - private float mStartX; - private boolean mTouchSlopReached; - private boolean mSkipCancelAnimation; - - @Override - public boolean onTouch(final View view, MotionEvent event) { - float realX = event.getRawX(); - switch (event.getAction()) { - case MotionEvent.ACTION_DOWN: - mStartX = realX; - mTouchSlopReached = false; - mSkipCancelAnimation = false; - break; - case MotionEvent.ACTION_MOVE: - if (mResolvedSwipeDirection == SWIPE_DIRECTION_LEFT - ? realX > mStartX - : realX < mStartX) { - realX = mStartX; - } - if (mResolvedSwipeDirection == SWIPE_DIRECTION_LEFT - ? realX < mStartX - mDragDistance - : realX > mStartX + mDragDistance) { - view.setPressed(true); - onUserActivity(event.getEventTime()); - } else { - view.setPressed(false); - } - if (mResolvedSwipeDirection == SWIPE_DIRECTION_LEFT - ? realX < mStartX - mScaledTouchSlop - : realX > mStartX + mScaledTouchSlop) { - mTouchSlopReached = true; - } - view.setTranslationX(mResolvedSwipeDirection == SWIPE_DIRECTION_LEFT - ? Math.max(realX - mStartX, -mDragDistance) - : Math.min(realX - mStartX, mDragDistance)); - break; - case MotionEvent.ACTION_UP: - if (mResolvedSwipeDirection == SWIPE_DIRECTION_LEFT - ? realX < mStartX - mDragDistance - : realX > mStartX + mDragDistance) { - onActionPerformed(); - view.animate().x(mResolvedSwipeDirection == SWIPE_DIRECTION_LEFT - ? -view.getWidth() - : ((View) view.getParent()).getWidth() + view.getWidth()) - .setInterpolator(new AccelerateInterpolator(2f)).withEndAction( - new Runnable() { - @Override - public void run() { - view.setTranslationX(0); - } - }); - mSkipCancelAnimation = true; - } - if (mResolvedSwipeDirection == SWIPE_DIRECTION_LEFT - ? realX < mStartX - mScaledTouchSlop - : realX > mStartX + mScaledTouchSlop) { - mTouchSlopReached = true; - } - if (!mTouchSlopReached) { - mSkipCancelAnimation = true; - view.animate().translationX(mResolvedSwipeDirection == SWIPE_DIRECTION_LEFT - ? -mDragDistance / 2 - : mDragDistance / 2). - setInterpolator(new DecelerateInterpolator()).withEndAction( - new Runnable() { - @Override - public void run() { - view.animate().translationX(0). - setInterpolator(new AccelerateInterpolator()); - } - }); - } - case MotionEvent.ACTION_CANCEL: - view.setPressed(false); - if (!mSkipCancelAnimation) { - view.animate().translationX(0) - .setInterpolator(new AccelerateInterpolator(2f)); - } - break; - } - return true; - } - }; - - public interface AffordanceListener { - - /** - * Called when the view would like to report user activity. - * - * @param when The timestamp of the user activity in {@link SystemClock#uptimeMillis} time - * base. - */ - void onUserActivity(long when); - - /** - * Called when the action of the affordance has been performed. - */ - void onActionPerformed(SwipeAffordanceView view); - } -} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/AnimationFilter.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AnimationFilter.java index 5e2d06b..cf56fa57 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/AnimationFilter.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AnimationFilter.java @@ -27,6 +27,7 @@ public class AnimationFilter { boolean animateZ; boolean animateScale; boolean animateHeight; + boolean animateTopInset; boolean animateDimmed; boolean hasDelays; @@ -60,6 +61,11 @@ public class AnimationFilter { return this; } + public AnimationFilter animateTopInset() { + animateTopInset = true; + return this; + } + public AnimationFilter animateDimmed() { animateDimmed = true; return this; @@ -84,6 +90,7 @@ public class AnimationFilter { animateZ |= filter.animateZ; animateScale |= filter.animateScale; animateHeight |= filter.animateHeight; + animateTopInset |= filter.animateTopInset; animateDimmed |= filter.animateDimmed; hasDelays |= filter.hasDelays; } @@ -94,6 +101,7 @@ public class AnimationFilter { animateZ = false; animateScale = false; animateHeight = false; + animateTopInset = false; animateDimmed = false; hasDelays = false; } 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 079b184..58176b9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java @@ -1599,6 +1599,7 @@ public class NotificationStackScrollLayout extends ViewGroup new AnimationFilter() .animateAlpha() .animateHeight() + .animateTopInset() .animateY() .animateZ() .hasDelays(), @@ -1607,6 +1608,7 @@ public class NotificationStackScrollLayout extends ViewGroup new AnimationFilter() .animateAlpha() .animateHeight() + .animateTopInset() .animateY() .animateZ() .hasDelays(), @@ -1615,6 +1617,7 @@ public class NotificationStackScrollLayout extends ViewGroup new AnimationFilter() .animateAlpha() .animateHeight() + .animateTopInset() .animateY() .animateZ() .hasDelays(), @@ -1623,6 +1626,7 @@ public class NotificationStackScrollLayout extends ViewGroup new AnimationFilter() .animateAlpha() .animateHeight() + .animateTopInset() .animateY() .animateDimmed() .animateScale() @@ -1651,6 +1655,7 @@ public class NotificationStackScrollLayout extends ViewGroup new AnimationFilter() .animateAlpha() .animateHeight() + .animateTopInset() .animateY() .animateZ() }; 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 bd2541a..2b52c7e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java @@ -49,6 +49,7 @@ public class StackScrollAlgorithm { private int mBottomStackPeekSize; private int mZDistanceBetweenElements; private int mZBasicHeight; + private int mRoundedRectCornerRadius; private StackIndentationFunctor mTopStackIndentationFunctor; private StackIndentationFunctor mBottomStackIndentationFunctor; @@ -111,6 +112,8 @@ public class StackScrollAlgorithm { mZBasicHeight = (MAX_ITEMS_IN_BOTTOM_STACK + 1) * mZDistanceBetweenElements; mBottomStackSlowDownLength = context.getResources() .getDimensionPixelSize(R.dimen.bottom_stack_slow_down_length); + mRoundedRectCornerRadius = context.getResources().getDimensionPixelSize( + com.android.internal.R.dimen.notification_quantum_rounded_rect_radius); } @@ -146,6 +149,67 @@ public class StackScrollAlgorithm { handleDraggedViews(ambientState, resultState, algorithmState); updateDimmedActivated(ambientState, resultState, algorithmState); + updateClipping(resultState, algorithmState); + } + + private void updateClipping(StackScrollState resultState, + StackScrollAlgorithmState algorithmState) { + float previousNotificationEnd = 0; + float previousNotificationStart = 0; + boolean previousNotificationIsSwiped = false; + int childCount = algorithmState.visibleChildren.size(); + for (int i = 0; i < childCount; i++) { + ExpandableView child = algorithmState.visibleChildren.get(i); + StackScrollState.ViewState state = resultState.getViewStateForView(child); + float newYTranslation = state.yTranslation; + int newHeight = state.height; + // apply clipping and shadow + float newNotificationEnd = newYTranslation + newHeight; + + // In the unlocked shade we have to clip a little bit higher because of the rounded + // corners of the notifications. + float clippingCorrection = state.dimmed ? 0 : mRoundedRectCornerRadius; + + // When the previous notification is swiped, we don't clip the content to the + // bottom of it. + float clipHeight = previousNotificationIsSwiped + ? newHeight + : newNotificationEnd - (previousNotificationEnd - clippingCorrection); + + updateChildClippingAndBackground(state, newHeight, clipHeight, + (int) (newHeight - (previousNotificationStart - newYTranslation))); + + if (!child.isTransparent()) { + // Only update the previous values if we are not transparent, + // otherwise we would clip to a transparent view. + previousNotificationStart = newYTranslation + child.getClipTopAmount(); + previousNotificationEnd = newNotificationEnd; + previousNotificationIsSwiped = child.getTranslationX() != 0; + } + } + } + + /** + * Updates the shadow outline and the clipping for a view. + * + * @param state the viewState to update + * @param realHeight the currently applied height of the view + * @param clipHeight the desired clip height, the rest of the view will be clipped from the top + * @param backgroundHeight the desired background height. The shadows of the view will be + * based on this height and the content will be clipped from the top + */ + private void updateChildClippingAndBackground(StackScrollState.ViewState state, int realHeight, + float clipHeight, int backgroundHeight) { + if (realHeight > clipHeight) { + state.topOverLap = (int) (realHeight - clipHeight); + } else { + state.topOverLap = 0; + } + if (realHeight > backgroundHeight) { + state.clipTopAmount = (realHeight - backgroundHeight); + } else { + state.clipTopAmount = 0; + } } /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollState.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollState.java index 44e10be..94cb16d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollState.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollState.java @@ -16,7 +16,6 @@ package com.android.systemui.statusbar.stack; -import android.graphics.Outline; import android.graphics.Rect; import android.util.Log; import android.view.View; @@ -37,15 +36,12 @@ public class StackScrollState { private static final String CHILD_NOT_FOUND_TAG = "StackScrollStateNoSuchChild"; private final ViewGroup mHostView; - private final int mRoundedRectCornerRadius; private Map<ExpandableView, ViewState> mStateMap; private final Rect mClipRect = new Rect(); public StackScrollState(ViewGroup hostView) { mHostView = hostView; mStateMap = new HashMap<ExpandableView, ViewState>(); - mRoundedRectCornerRadius = mHostView.getResources().getDimensionPixelSize( - com.android.internal.R.dimen.notification_quantum_rounded_rect_radius); } public ViewGroup getHostView() { @@ -83,9 +79,6 @@ public class StackScrollState { */ public void apply() { int numChildren = mHostView.getChildCount(); - float previousNotificationEnd = 0; - float previousNotificationStart = 0; - boolean previousNotificationIsSwiped = false; for (int i = 0; i < numChildren; i++) { ExpandableView child = (ExpandableView) mHostView.getChildAt(i); ViewState state = mStateMap.get(child); @@ -155,39 +148,41 @@ public class StackScrollState { // apply dimming child.setDimmed(state.dimmed, false /* animate */); - // apply clipping and shadow - float newNotificationEnd = newYTranslation + newHeight; - - // In the unlocked shade we have to clip a little bit higher because of the rounded - // corners of the notifications. - float clippingCorrection = state.dimmed ? 0 : mRoundedRectCornerRadius; - - // When the previous notification is swiped, we don't clip the content to the - // bottom of it. - float clipHeight = previousNotificationIsSwiped - ? newHeight - : newNotificationEnd - (previousNotificationEnd - clippingCorrection); - - updateChildClippingAndBackground(child, newHeight, - clipHeight, - (int) (newHeight - (previousNotificationStart - newYTranslation))); - - if (!child.isTransparent()) { - // Only update the previous values if we are not transparent, - // otherwise we would clip to a transparent view. - previousNotificationStart = newYTranslation + child.getClipTopAmount(); - previousNotificationEnd = newNotificationEnd; - previousNotificationIsSwiped = child.getTranslationX() != 0; + float oldClipTopAmount = child.getClipTopAmount(); + if (oldClipTopAmount != state.clipTopAmount) { + child.setClipTopAmount(state.clipTopAmount); + } + + if (state.topOverLap != 0) { + updateChildClip(child, newHeight, state.topOverLap); + } else { + child.setClipBounds(null); } if(child instanceof SpeedBumpView) { - performSpeedBumpAnimation(i, (SpeedBumpView) child, newNotificationEnd, + float speedBumpEnd = newYTranslation + newHeight; + performSpeedBumpAnimation(i, (SpeedBumpView) child, speedBumpEnd, newYTranslation); } } } } + /** + * Updates the clipping of a view + * + * @param child the view to update + * @param height the currently applied height of the view + * @param clipInset how much should this view be clipped from the top + */ + private void updateChildClip(View child, int height, int clipInset) { + mClipRect.set(0, + clipInset, + child.getWidth(), + height); + child.setClipBounds(mClipRect); + } + private void performSpeedBumpAnimation(int i, SpeedBumpView speedBump, float speedBumpEnd, float speedBumpStart) { View nextChild = getNextChildNotGone(i); @@ -216,45 +211,6 @@ public class StackScrollState { return null; } - /** - * Updates the shadow outline and the clipping for a view. - * - * @param child the view to update - * @param realHeight the currently applied height of the view - * @param clipHeight the desired clip height, the rest of the view will be clipped from the top - * @param backgroundHeight the desired background height. The shadows of the view will be - * based on this height and the content will be clipped from the top - */ - private void updateChildClippingAndBackground(ExpandableView child, int realHeight, - float clipHeight, int backgroundHeight) { - if (realHeight > clipHeight) { - updateChildClip(child, realHeight, clipHeight); - } else { - child.setClipBounds(null); - } - if (realHeight > backgroundHeight) { - child.setClipTopAmount(realHeight - backgroundHeight); - } else { - child.setClipTopAmount(0); - } - } - - /** - * Updates the clipping of a view - * - * @param child the view to update - * @param height the currently applied height of the view - * @param clipHeight the desired clip height, the rest of the view will be clipped from the top - */ - private void updateChildClip(View child, int height, float clipHeight) { - int clipInset = (int) (height - clipHeight); - mClipRect.set(0, - clipInset, - child.getWidth(), - height); - child.setClipBounds(mClipRect); - } - public static class ViewState { // These are flags such that we can create masks for filtering. @@ -276,6 +232,18 @@ public class StackScrollState { boolean dimmed; /** + * The amount which the view should be clipped from the top. This is calculated to + * perceive consistent shadows. + */ + int clipTopAmount; + + /** + * How much does the child overlap with the previous view on the top? Can be used for + * a clipping optimization + */ + int topOverLap; + + /** * The index of the view, only accounting for views not equal to GONE */ int notGoneIndex; 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 f019e6c..f41ab3a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java @@ -133,10 +133,11 @@ public class StackStateAnimator { boolean scaleChanging = child.getScaleX() != viewState.scale; boolean alphaChanging = alpha != child.getAlpha(); boolean heightChanging = viewState.height != child.getActualHeight(); + boolean topInsetChanging = viewState.clipTopAmount != child.getClipTopAmount(); boolean wasAdded = mNewAddChildren.contains(child); boolean hasDelays = mAnimationFilter.hasDelays; boolean isDelayRelevant = yTranslationChanging || zTranslationChanging || scaleChanging || - alphaChanging || heightChanging; + alphaChanging || heightChanging || topInsetChanging; long delay = 0; if (hasDelays && isDelayRelevant || wasAdded) { delay = calculateChildAnimationDelay(viewState, finalState); @@ -167,6 +168,11 @@ public class StackStateAnimator { startHeightAnimation(child, viewState, delay); } + // start top inset animation + if (topInsetChanging) { + startInsetAnimation(child, viewState, delay); + } + // start dimmed animation child.setDimmed(viewState.dimmed, mAnimationFilter.animateDimmed); @@ -280,6 +286,64 @@ public class StackStateAnimator { child.setTag(TAG_END_HEIGHT, newEndValue); } + private void startInsetAnimation(final ExpandableView child, + StackScrollState.ViewState viewState, long delay) { + Integer previousStartValue = getChildTag(child, TAG_START_TOP_INSET); + Integer previousEndValue = getChildTag(child, TAG_END_TOP_INSET); + int newEndValue = viewState.clipTopAmount; + if (previousEndValue != null && previousEndValue == newEndValue) { + return; + } + ValueAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_TOP_INSET); + if (!mAnimationFilter.animateTopInset) { + // just a local update was performed + if (previousAnimator != null) { + // we need to increase all animation keyframes of the previous animator by the + // relative change to the end value + PropertyValuesHolder[] values = previousAnimator.getValues(); + int relativeDiff = newEndValue - previousEndValue; + int newStartValue = previousStartValue + relativeDiff; + values[0].setIntValues(newStartValue, newEndValue); + child.setTag(TAG_START_TOP_INSET, newStartValue); + child.setTag(TAG_END_TOP_INSET, newEndValue); + previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime()); + return; + } else { + // no new animation needed, let's just apply the value + child.setClipTopAmount(newEndValue); + return; + } + } + + ValueAnimator animator = ValueAnimator.ofInt(child.getClipTopAmount(), newEndValue); + animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + child.setClipTopAmount((int) animation.getAnimatedValue()); + } + }); + animator.setInterpolator(mFastOutSlowInInterpolator); + long newDuration = cancelAnimatorAndGetNewDuration(previousAnimator); + animator.setDuration(newDuration); + if (delay > 0 && (previousAnimator == null || !previousAnimator.isRunning())) { + animator.setStartDelay(delay); + } + animator.addListener(getGlobalAnimationFinishedListener()); + // remove the tag when the animation is finished + animator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + child.setTag(TAG_ANIMATOR_TOP_INSET, null); + child.setTag(TAG_START_TOP_INSET, null); + child.setTag(TAG_END_TOP_INSET, null); + } + }); + startAnimator(animator); + child.setTag(TAG_ANIMATOR_TOP_INSET, animator); + child.setTag(TAG_START_TOP_INSET, child.getClipTopAmount()); + child.setTag(TAG_END_TOP_INSET, newEndValue); + } + private void startAlphaAnimation(final ExpandableView child, final StackScrollState.ViewState viewState, long delay) { Float previousStartValue = getChildTag(child,TAG_START_ALPHA); 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 25147b4..c2bd1cb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java @@ -17,12 +17,12 @@ package com.android.systemui.statusbar.tv; import android.os.IBinder; +import android.service.notification.NotificationListenerService.Ranking; import android.service.notification.StatusBarNotification; import android.view.View; import android.view.ViewGroup.LayoutParams; import android.view.WindowManager; -import com.android.internal.policy.IKeyguardShowCallback; import com.android.internal.statusbar.StatusBarIcon; import com.android.systemui.statusbar.BaseStatusBar; @@ -50,7 +50,7 @@ public class TvStatusBar extends BaseStatusBar { } @Override - public void addNotificationInternal(StatusBarNotification notification) { + public void addNotificationInternal(StatusBarNotification notification, Ranking ranking) { } @Override @@ -58,7 +58,7 @@ public class TvStatusBar extends BaseStatusBar { } @Override - protected void removeNotificationInternal(String key) { + protected void removeNotificationInternal(String key, Ranking ranking) { } @Override @@ -117,7 +117,7 @@ public class TvStatusBar extends BaseStatusBar { } @Override - protected void updateNotificationIcons() { + protected void updateNotifications() { } @Override |