Integrate Heads-up notifications into the shade
Change-Id: I4ca0fb4e76e7c974490538c168da0564fe97e0ae
This commit is contained in:
@@ -1,12 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<set xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
>
|
||||
<translate
|
||||
android:interpolator="@android:interpolator/overshoot"
|
||||
android:fromYDelta="-50%" android:toYDelta="0"
|
||||
android:duration="@android:integer/config_shortAnimTime" />
|
||||
<alpha
|
||||
android:interpolator="@android:interpolator/decelerate_quad"
|
||||
android:fromAlpha="0.0" android:toAlpha="1.0"
|
||||
android:duration="@android:integer/config_shortAnimTime" />
|
||||
</set>
|
||||
@@ -1,12 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<set xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
>
|
||||
<translate
|
||||
android:interpolator="@android:interpolator/overshoot"
|
||||
android:fromYDelta="0" android:toYDelta="-50%"
|
||||
android:duration="@android:integer/config_shortAnimTime" />
|
||||
<alpha
|
||||
android:interpolator="@android:interpolator/accelerate_quad"
|
||||
android:fromAlpha="1.0" android:toAlpha="0.0"
|
||||
android:duration="@android:integer/config_shortAnimTime" />
|
||||
</set>
|
||||
@@ -1,33 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Copyright (C) 2014 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<!-- extends FrameLayout -->
|
||||
<com.android.systemui.statusbar.policy.HeadsUpNotificationView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_width="match_parent"
|
||||
android:background="@drawable/heads_up_scrim">
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="@dimen/notification_panel_width"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="@integer/notification_panel_layout_gravity"
|
||||
android:paddingStart="@dimen/notification_side_padding"
|
||||
android:paddingEnd="@dimen/notification_side_padding"
|
||||
android:elevation="8dp"
|
||||
android:id="@+id/content_holder" />
|
||||
|
||||
</com.android.systemui.statusbar.policy.HeadsUpNotificationView>
|
||||
@@ -147,7 +147,7 @@
|
||||
<integer name="heads_up_default_snooze_length_ms">60000</integer>
|
||||
|
||||
<!-- Minimum display time for a heads up notification, in milliseconds. -->
|
||||
<integer name="heads_up_notification_minimum_time">3000</integer>
|
||||
<integer name="heads_up_notification_minimum_time">2000</integer>
|
||||
|
||||
<!-- milliseconds before the heads up notification accepts touches. -->
|
||||
<integer name="heads_up_sensitivity_delay">700</integer>
|
||||
|
||||
@@ -330,7 +330,6 @@
|
||||
keyguard_clock_height_fraction_* for the difference between min and max.-->
|
||||
<dimen name="keyguard_clock_notifications_margin_min">24dp</dimen>
|
||||
<dimen name="keyguard_clock_notifications_margin_max">36dp</dimen>
|
||||
<dimen name="heads_up_window_height">250dp</dimen>
|
||||
|
||||
<!-- The minimum amount the user needs to swipe to go to the camera / phone. -->
|
||||
<dimen name="keyguard_min_swipe_amount">110dp</dimen>
|
||||
|
||||
@@ -205,11 +205,6 @@
|
||||
<style name="Animation.StatusBar">
|
||||
</style>
|
||||
|
||||
<style name="Animation.StatusBar.HeadsUp">
|
||||
<item name="android:windowEnterAnimation">@anim/heads_up_enter</item>
|
||||
<item name="android:windowExitAnimation">@anim/heads_up_exit</item>
|
||||
</style>
|
||||
|
||||
<style name="systemui_theme" parent="@android:style/Theme.DeviceDefault">
|
||||
<item name="android:colorPrimary">@color/system_primary_color</item>
|
||||
<item name="android:colorControlActivated">@color/system_accent_color</item>
|
||||
|
||||
@@ -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,26 +1869,117 @@ 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;
|
||||
Notification n = notification.getNotification();
|
||||
if (DEBUG) {
|
||||
logUpdate(entry, n);
|
||||
}
|
||||
boolean applyInPlace = shouldApplyInPlace(entry, n);
|
||||
final boolean shouldInterrupt = shouldInterrupt(notification);
|
||||
final boolean alertAgain = shouldInterrupt && alertAgain(entry, n);
|
||||
boolean isHeadsUp = shouldInterrupt && alertAgain;
|
||||
|
||||
entry.notification = notification;
|
||||
mGroupManager.onEntryUpdated(entry, entry.notification);
|
||||
|
||||
boolean updateSuccessful = false;
|
||||
if (applyInPlace) {
|
||||
// We can just reapply the notifications in place
|
||||
if (DEBUG) Log.d(TAG, "reusing notification for key: " + key);
|
||||
try {
|
||||
if (entry.icon != null) {
|
||||
// Update the icon
|
||||
final StatusBarIcon ic = new StatusBarIcon(notification.getPackageName(),
|
||||
notification.getUser(),
|
||||
n.icon,
|
||||
n.iconLevel,
|
||||
n.number,
|
||||
n.tickerText);
|
||||
entry.icon.setNotification(n);
|
||||
if (!entry.icon.set(ic)) {
|
||||
handleNotificationError(notification, "Couldn't update icon: " + ic);
|
||||
return;
|
||||
}
|
||||
}
|
||||
updateNotificationViews(entry, notification, 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 " + n.contentView.getPackage(), e);
|
||||
}
|
||||
}
|
||||
if (!updateSuccessful) {
|
||||
if (DEBUG) Log.d(TAG, "not reusing notification for key: " + key);
|
||||
final StatusBarIcon ic = new StatusBarIcon(notification.getPackageName(),
|
||||
notification.getUser(),
|
||||
n.icon,
|
||||
n.iconLevel,
|
||||
n.number,
|
||||
n.tickerText);
|
||||
entry.icon.setNotification(n);
|
||||
entry.icon.set(ic);
|
||||
inflateViews(entry, mStackScroller, 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(entry.row, notification);
|
||||
|
||||
// Is this for you?
|
||||
boolean isForCurrentUser = isNotificationForCurrentProfiles(notification);
|
||||
if (DEBUG) Log.d(TAG, "notification is " + (isForCurrentUser ? "" : "not ") + "for you");
|
||||
|
||||
// Recalculate the position of the sliding windows and the titles.
|
||||
setAreThereNotifications();
|
||||
}
|
||||
|
||||
private void updateHeadsUp(String key, Entry entry, boolean shouldInterrupt,
|
||||
boolean alertAgain) {
|
||||
final boolean wasHeadsUp = isHeadsUp(key);
|
||||
if (wasHeadsUp) {
|
||||
mHeadsUpManager.updateNotification(entry, alertAgain);
|
||||
if (!shouldInterrupt) {
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return whether we can just reapply the RemoteViews in place when it is updated
|
||||
*/
|
||||
private boolean shouldApplyInPlace(Entry entry, Notification n) {
|
||||
StatusBarNotification oldNotification = entry.notification;
|
||||
// XXX: modify when we do something more intelligent with the two content views
|
||||
final RemoteViews oldContentView = oldNotification.getNotification().contentView;
|
||||
Notification n = notification.getNotification();
|
||||
final RemoteViews contentView = n.contentView;
|
||||
final RemoteViews oldBigContentView = oldNotification.getNotification().bigContentView;
|
||||
final RemoteViews bigContentView = n.bigContentView;
|
||||
final RemoteViews oldHeadsUpContentView = oldNotification.getNotification().headsUpContentView;
|
||||
final RemoteViews oldHeadsUpContentView
|
||||
= oldNotification.getNotification().headsUpContentView;
|
||||
final RemoteViews headsUpContentView = n.headsUpContentView;
|
||||
final Notification oldPublicNotification = oldNotification.getNotification().publicVersion;
|
||||
final RemoteViews oldPublicContentView = oldPublicNotification != null
|
||||
@@ -1916,34 +1987,15 @@ public abstract class BaseStatusBar extends SystemUI implements
|
||||
final Notification publicNotification = n.publicVersion;
|
||||
final RemoteViews publicContentView = publicNotification != null
|
||||
? publicNotification.contentView : null;
|
||||
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "old notification: when=" + oldNotification.getNotification().when
|
||||
+ " ongoing=" + oldNotification.isOngoing()
|
||||
+ " expanded=" + oldEntry.expanded
|
||||
+ " contentView=" + oldContentView
|
||||
+ " bigContentView=" + oldBigContentView
|
||||
+ " publicView=" + oldPublicContentView
|
||||
+ " rowParent=" + oldEntry.row.getParent());
|
||||
Log.d(TAG, "new notification: when=" + n.when
|
||||
+ " ongoing=" + oldNotification.isOngoing()
|
||||
+ " contentView=" + contentView
|
||||
+ " bigContentView=" + bigContentView
|
||||
+ " publicView=" + publicContentView);
|
||||
}
|
||||
|
||||
// Can we just reapply the RemoteViews in place?
|
||||
|
||||
// 1U is never null
|
||||
boolean contentsUnchanged = oldEntry.expanded != null
|
||||
boolean contentsUnchanged = entry.expanded != null
|
||||
&& contentView.getPackage() != null
|
||||
&& oldContentView.getPackage() != null
|
||||
&& oldContentView.getPackage().equals(contentView.getPackage())
|
||||
&& oldContentView.getLayoutId() == contentView.getLayoutId();
|
||||
// large view may be null
|
||||
boolean bigContentsUnchanged =
|
||||
(oldEntry.getBigContentView() == null && bigContentView == null)
|
||||
|| ((oldEntry.getBigContentView() != null && bigContentView != null)
|
||||
(entry.getBigContentView() == null && bigContentView == null)
|
||||
|| ((entry.getBigContentView() != null && bigContentView != null)
|
||||
&& bigContentView.getPackage() != null
|
||||
&& oldBigContentView.getPackage() != null
|
||||
&& oldBigContentView.getPackage().equals(bigContentView.getPackage())
|
||||
@@ -1962,123 +2014,8 @@ public abstract class BaseStatusBar extends SystemUI implements
|
||||
&& oldPublicContentView.getPackage() != null
|
||||
&& oldPublicContentView.getPackage().equals(publicContentView.getPackage())
|
||||
&& oldPublicContentView.getLayoutId() == publicContentView.getLayoutId());
|
||||
|
||||
final boolean shouldInterrupt = shouldInterrupt(notification);
|
||||
final boolean alertAgain = shouldInterrupt && alertAgain(oldEntry, n);
|
||||
boolean updateSuccessful = false;
|
||||
if (contentsUnchanged && bigContentsUnchanged && headsUpContentsUnchanged
|
||||
&& publicUnchanged) {
|
||||
if (DEBUG) Log.d(TAG, "reusing notification for key: " + key);
|
||||
oldEntry.notification = notification;
|
||||
mGroupManager.onEntryUpdated(oldEntry, oldNotification);
|
||||
try {
|
||||
if (oldEntry.icon != null) {
|
||||
// Update the icon
|
||||
final StatusBarIcon ic = new StatusBarIcon(notification.getPackageName(),
|
||||
notification.getUser(),
|
||||
n.icon,
|
||||
n.iconLevel,
|
||||
n.number,
|
||||
n.tickerText);
|
||||
oldEntry.icon.setNotification(n);
|
||||
if (!oldEntry.icon.set(ic)) {
|
||||
handleNotificationError(notification, "Couldn't update icon: " + ic);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (wasHeadsUp) {
|
||||
// Release may hang on to the views for a bit, so we should always update them.
|
||||
updateHeadsUpViews(oldEntry, notification);
|
||||
mHeadsUpNotificationView.updateNotification(oldEntry, alertAgain);
|
||||
if (!shouldInterrupt) {
|
||||
// we updated the notification above, so release to build a new shade entry
|
||||
mHeadsUpNotificationView.release();
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (shouldInterrupt && alertAgain) {
|
||||
mStackScroller.setRemoveAnimationEnabled(false);
|
||||
removeNotificationViews(key, ranking);
|
||||
mStackScroller.setRemoveAnimationEnabled(true);
|
||||
addNotification(notification, ranking, oldEntry); //this will pop the headsup
|
||||
} else {
|
||||
updateNotificationViews(oldEntry, notification);
|
||||
}
|
||||
}
|
||||
mNotificationData.updateRanking(ranking);
|
||||
updateNotifications();
|
||||
updateSuccessful = true;
|
||||
}
|
||||
catch (RuntimeException e) {
|
||||
// It failed to add cleanly. Log, and remove the view from the panel.
|
||||
Log.w(TAG, "Couldn't reapply views for package " + contentView.getPackage(), e);
|
||||
}
|
||||
}
|
||||
if (!updateSuccessful) {
|
||||
if (DEBUG) Log.d(TAG, "not reusing notification for key: " + key);
|
||||
if (wasHeadsUp) {
|
||||
if (DEBUG) Log.d(TAG, "rebuilding heads up for key: " + key);
|
||||
ViewGroup holder = mHeadsUpNotificationView.getHolder();
|
||||
if (inflateViewsForHeadsUp(oldEntry, holder)) {
|
||||
mHeadsUpNotificationView.updateNotification(oldEntry, alertAgain);
|
||||
} else {
|
||||
Log.w(TAG, "Couldn't create new updated headsup for package "
|
||||
+ contentView.getPackage());
|
||||
}
|
||||
if (!shouldInterrupt) {
|
||||
if (DEBUG) Log.d(TAG, "releasing heads up for key: " + key);
|
||||
oldEntry.notification = notification;
|
||||
mGroupManager.onEntryUpdated(oldEntry, oldNotification);
|
||||
mHeadsUpNotificationView.release();
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (shouldInterrupt && alertAgain) {
|
||||
if (DEBUG) Log.d(TAG, "reposting to invoke heads up for key: " + key);
|
||||
mStackScroller.setRemoveAnimationEnabled(false);
|
||||
removeNotificationViews(key, ranking);
|
||||
mStackScroller.setRemoveAnimationEnabled(true);
|
||||
addNotification(notification, ranking, oldEntry); //this will pop the headsup
|
||||
} else {
|
||||
if (DEBUG) Log.d(TAG, "rebuilding update in place for key: " + key);
|
||||
oldEntry.notification = notification;
|
||||
mGroupManager.onEntryUpdated(oldEntry, oldNotification);
|
||||
final StatusBarIcon ic = new StatusBarIcon(notification.getPackageName(),
|
||||
notification.getUser(),
|
||||
n.icon,
|
||||
n.iconLevel,
|
||||
n.number,
|
||||
n.tickerText);
|
||||
oldEntry.icon.setNotification(n);
|
||||
oldEntry.icon.set(ic);
|
||||
inflateViews(oldEntry, mStackScroller, wasHeadsUp);
|
||||
mNotificationData.updateRanking(ranking);
|
||||
updateNotifications();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update the veto button accordingly (and as a result, whether this row is
|
||||
// swipe-dismissable)
|
||||
updateNotificationVetoButton(oldEntry.row, notification);
|
||||
|
||||
// Is this for you?
|
||||
boolean isForCurrentUser = isNotificationForCurrentProfiles(notification);
|
||||
if (DEBUG) Log.d(TAG, "notification is " + (isForCurrentUser ? "" : "not ") + "for you");
|
||||
|
||||
// Recalculate the position of the sliding windows and the titles.
|
||||
setAreThereNotifications();
|
||||
}
|
||||
|
||||
private void updateNotificationViews(NotificationData.Entry entry,
|
||||
StatusBarNotification notification) {
|
||||
updateNotificationViews(entry, notification, false);
|
||||
}
|
||||
|
||||
private void updateHeadsUpViews(NotificationData.Entry entry,
|
||||
StatusBarNotification notification) {
|
||||
updateNotificationViews(entry, notification, true);
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -492,6 +508,15 @@ public class NotificationPanelView extends PanelView implements
|
||||
super.fling(vel, expand);
|
||||
}
|
||||
|
||||
@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) {
|
||||
@@ -499,16 +524,27 @@ public class NotificationPanelView extends PanelView implements
|
||||
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) {
|
||||
@@ -1708,6 +1766,17 @@ public class NotificationPanelView extends PanelView implements
|
||||
mAfforanceHelper.onConfigurationChanged();
|
||||
}
|
||||
|
||||
@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) {
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
boolean isHeadsUp = mUseHeadsUp && shouldInterrupt(notification);
|
||||
|
||||
// do not show the notification in the shade, yet.
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,261 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2014 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.android.systemui.statusbar.policy;
|
||||
|
||||
import static org.mockito.Matchers.anyInt;
|
||||
import static org.mockito.Mockito.inOrder;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.reset;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.app.Notification;
|
||||
import android.os.*;
|
||||
import android.service.notification.StatusBarNotification;
|
||||
import com.android.systemui.SwipeHelper;
|
||||
import com.android.systemui.SysuiTestCase;
|
||||
import com.android.systemui.statusbar.ExpandableNotificationRow;
|
||||
import com.android.systemui.statusbar.NotificationData;
|
||||
import com.android.systemui.statusbar.phone.PhoneStatusBar;
|
||||
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.InOrder;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
/**
|
||||
* Test the Heads Up Notification.
|
||||
*
|
||||
* Specifically the policy that a notificaiton must remain visibile for a minimum period of time.
|
||||
*/
|
||||
public class HeadsUpNotificationTest extends SysuiTestCase {
|
||||
private static final String TAG = "HeadsUpNotificationTest";
|
||||
|
||||
private static int TOUCH_SENSITIVITY = 100;
|
||||
private static int NOTIFICATION_DECAY = 10000;
|
||||
private static int MINIMUM_DISPLAY_TIME = 3000;
|
||||
private static int SNOOZE_TIME = 60000;
|
||||
private static long TOO_SOON = 1000L; // less than MINIMUM_DISPLAY_TIME
|
||||
private static long LATER = 5000L; // more than MINIMUM_DISPLAY_TIME
|
||||
private static long REMAINING_VISIBILITY = MINIMUM_DISPLAY_TIME - TOO_SOON;
|
||||
|
||||
protected HeadsUpNotificationView mHeadsUp;
|
||||
|
||||
@Mock protected PhoneStatusBar mMockStatusBar;
|
||||
@Mock private HeadsUpNotificationView.Clock mClock;
|
||||
@Mock private SwipeHelper mMockSwipeHelper;
|
||||
@Mock private HeadsUpNotificationView.EdgeSwipeHelper mMockEdgeSwipeHelper;
|
||||
|
||||
@Override
|
||||
protected void setUp() throws Exception {
|
||||
super.setUp();
|
||||
|
||||
MockitoAnnotations.initMocks(this);
|
||||
|
||||
mHeadsUp = new HeadsUpNotificationView(mContext,
|
||||
mClock, mMockSwipeHelper, mMockEdgeSwipeHelper,
|
||||
NOTIFICATION_DECAY, MINIMUM_DISPLAY_TIME, TOUCH_SENSITIVITY, SNOOZE_TIME);
|
||||
mHeadsUp.setBar(mMockStatusBar);
|
||||
}
|
||||
|
||||
private NotificationData.Entry makeNotification(String key) {
|
||||
StatusBarNotification sbn = mock(StatusBarNotification.class);
|
||||
when(sbn.getKey()).thenReturn(key);
|
||||
return new NotificationData.Entry(sbn, null);
|
||||
}
|
||||
|
||||
public void testPostAndDecay() {
|
||||
NotificationData.Entry a = makeNotification("a");
|
||||
mHeadsUp.showNotification(a);
|
||||
Mockito.verify(mMockStatusBar, never()).scheduleHeadsUpClose();
|
||||
Mockito.verify(mMockStatusBar, times(1)).scheduleHeadsUpOpen();
|
||||
ArgumentCaptor<Long> decayArg = ArgumentCaptor.forClass(Long.class);
|
||||
Mockito.verify(mMockStatusBar).scheduleHeadsUpDecay(decayArg.capture());
|
||||
// New notification gets a full decay time.
|
||||
assertEquals(NOTIFICATION_DECAY, (long) decayArg.getValue());
|
||||
}
|
||||
|
||||
public void testPostAndDeleteTooSoon() {
|
||||
when(mClock.currentTimeMillis()).thenReturn(0L);
|
||||
NotificationData.Entry a = makeNotification("a");
|
||||
mHeadsUp.showNotification(a);
|
||||
reset(mMockStatusBar);
|
||||
|
||||
when(mClock.currentTimeMillis()).thenReturn(TOO_SOON);
|
||||
mHeadsUp.removeNotification(a.key);
|
||||
ArgumentCaptor<Long> decayArg = ArgumentCaptor.forClass(Long.class);
|
||||
Mockito.verify(mMockStatusBar, never()).scheduleHeadsUpClose();
|
||||
Mockito.verify(mMockStatusBar).scheduleHeadsUpDecay(decayArg.capture());
|
||||
// Leave the window up for the balance of the minumum time.
|
||||
assertEquals(REMAINING_VISIBILITY, (long) decayArg.getValue());
|
||||
}
|
||||
|
||||
public void testPostAndDeleteLater() {
|
||||
when(mClock.currentTimeMillis()).thenReturn(0L);
|
||||
NotificationData.Entry a = makeNotification("a");
|
||||
mHeadsUp.showNotification(a);
|
||||
reset(mMockStatusBar);
|
||||
|
||||
when(mClock.currentTimeMillis()).thenReturn(LATER);
|
||||
mHeadsUp.removeNotification(a.key);
|
||||
// Delete closes immediately if the minimum time window is satisfied.
|
||||
Mockito.verify(mMockStatusBar, times(1)).scheduleHeadsUpClose();
|
||||
Mockito.verify(mMockStatusBar, never()).scheduleHeadsUpDecay(anyInt());
|
||||
}
|
||||
|
||||
// This is a bad test. It should not care that there is a call to scheduleHeadsUpClose(),
|
||||
// but it happens that there will be one, so it is important that it happen before the
|
||||
// call to scheduleHeadsUpOpen(), so that the final state is open.
|
||||
// Maybe mMockStatusBar should instead be a fake that tracks the open/closed state.
|
||||
public void testPostAndReplaceTooSoon() {
|
||||
InOrder callOrder = inOrder(mMockStatusBar);
|
||||
when(mClock.currentTimeMillis()).thenReturn(0L);
|
||||
NotificationData.Entry a = makeNotification("a");
|
||||
mHeadsUp.showNotification(a);
|
||||
reset(mMockStatusBar);
|
||||
|
||||
when(mClock.currentTimeMillis()).thenReturn(TOO_SOON);
|
||||
NotificationData.Entry b = makeNotification("b");
|
||||
mHeadsUp.showNotification(b);
|
||||
Mockito.verify(mMockStatusBar, times(1)).scheduleHeadsUpClose();
|
||||
ArgumentCaptor<Long> decayArg = ArgumentCaptor.forClass(Long.class);
|
||||
Mockito.verify(mMockStatusBar, times(1)).scheduleHeadsUpDecay(decayArg.capture());
|
||||
// New notification gets a full decay time.
|
||||
assertEquals(NOTIFICATION_DECAY, (long) decayArg.getValue());
|
||||
|
||||
// Make sure close was called before open, so that the heads up stays open.
|
||||
callOrder.verify(mMockStatusBar).scheduleHeadsUpClose();
|
||||
callOrder.verify(mMockStatusBar).scheduleHeadsUpOpen();
|
||||
}
|
||||
|
||||
public void testPostAndUpdateAlertAgain() {
|
||||
when(mClock.currentTimeMillis()).thenReturn(0L);
|
||||
NotificationData.Entry a = makeNotification("a");
|
||||
mHeadsUp.showNotification(a);
|
||||
reset(mMockStatusBar);
|
||||
|
||||
when(mClock.currentTimeMillis()).thenReturn(TOO_SOON);
|
||||
mHeadsUp.updateNotification(a, true);
|
||||
Mockito.verify(mMockStatusBar, never()).scheduleHeadsUpClose();
|
||||
ArgumentCaptor<Long> decayArg = ArgumentCaptor.forClass(Long.class);
|
||||
Mockito.verify(mMockStatusBar, times(1)).scheduleHeadsUpDecay(decayArg.capture());
|
||||
// Alert again gets a full decay time.
|
||||
assertEquals(NOTIFICATION_DECAY, (long) decayArg.getValue());
|
||||
}
|
||||
|
||||
public void testPostAndUpdateAlertAgainFastFail() {
|
||||
when(mClock.currentTimeMillis()).thenReturn(0L);
|
||||
NotificationData.Entry a = makeNotification("a");
|
||||
mHeadsUp.showNotification(a);
|
||||
reset(mMockStatusBar);
|
||||
|
||||
when(mClock.currentTimeMillis()).thenReturn(TOO_SOON);
|
||||
NotificationData.Entry a_prime = makeNotification("a");
|
||||
mHeadsUp.updateNotification(a_prime, true);
|
||||
Mockito.verify(mMockStatusBar, never()).scheduleHeadsUpClose();
|
||||
ArgumentCaptor<Long> decayArg = ArgumentCaptor.forClass(Long.class);
|
||||
Mockito.verify(mMockStatusBar, times(1)).scheduleHeadsUpDecay(decayArg.capture());
|
||||
// Alert again gets a full decay time.
|
||||
assertEquals(NOTIFICATION_DECAY, (long) decayArg.getValue());
|
||||
}
|
||||
|
||||
public void testPostAndUpdateNoAlertAgain() {
|
||||
when(mClock.currentTimeMillis()).thenReturn(0L);
|
||||
NotificationData.Entry a = makeNotification("a");
|
||||
mHeadsUp.showNotification(a);
|
||||
reset(mMockStatusBar);
|
||||
|
||||
when(mClock.currentTimeMillis()).thenReturn(TOO_SOON);
|
||||
mHeadsUp.updateNotification(a, false);
|
||||
Mockito.verify(mMockStatusBar, never()).scheduleHeadsUpClose();
|
||||
Mockito.verify(mMockStatusBar, never()).scheduleHeadsUpDecay(anyInt());
|
||||
}
|
||||
|
||||
public void testPostAndUpdateNoAlertAgainFastFail() {
|
||||
when(mClock.currentTimeMillis()).thenReturn(0L);
|
||||
NotificationData.Entry a = makeNotification("a");
|
||||
mHeadsUp.showNotification(a);
|
||||
reset(mMockStatusBar);
|
||||
|
||||
when(mClock.currentTimeMillis()).thenReturn(TOO_SOON);
|
||||
NotificationData.Entry a_prime = makeNotification("a");
|
||||
mHeadsUp.updateNotification(a_prime, false);
|
||||
Mockito.verify(mMockStatusBar, never()).scheduleHeadsUpClose();
|
||||
Mockito.verify(mMockStatusBar, never()).scheduleHeadsUpDecay(anyInt());
|
||||
}
|
||||
|
||||
public void testPostAndUpdateLowPriorityTooSoon() {
|
||||
when(mClock.currentTimeMillis()).thenReturn(0L);
|
||||
NotificationData.Entry a = makeNotification("a");
|
||||
mHeadsUp.showNotification(a);
|
||||
reset(mMockStatusBar);
|
||||
|
||||
when(mClock.currentTimeMillis()).thenReturn(TOO_SOON);
|
||||
mHeadsUp.release();
|
||||
Mockito.verify(mMockStatusBar, never()).scheduleHeadsUpClose();
|
||||
ArgumentCaptor<Long> decayArg = ArgumentCaptor.forClass(Long.class);
|
||||
Mockito.verify(mMockStatusBar, times(1)).scheduleHeadsUpDecay(decayArg.capture());
|
||||
// Down grade on update leaves the window up for the balance of the minumum time.
|
||||
assertEquals(REMAINING_VISIBILITY, (long) decayArg.getValue());
|
||||
}
|
||||
|
||||
public void testPostAndUpdateLowPriorityTooSoonFastFail() {
|
||||
when(mClock.currentTimeMillis()).thenReturn(0L);
|
||||
NotificationData.Entry a = makeNotification("a");
|
||||
mHeadsUp.showNotification(a);
|
||||
reset(mMockStatusBar);
|
||||
|
||||
when(mClock.currentTimeMillis()).thenReturn(TOO_SOON);
|
||||
NotificationData.Entry a_prime = makeNotification("a");
|
||||
mHeadsUp.updateNotification(a_prime, false);
|
||||
mHeadsUp.release();
|
||||
Mockito.verify(mMockStatusBar, never()).scheduleHeadsUpClose();
|
||||
ArgumentCaptor<Long> decayArg = ArgumentCaptor.forClass(Long.class);
|
||||
Mockito.verify(mMockStatusBar, times(1)).scheduleHeadsUpDecay(decayArg.capture());
|
||||
// Down grade on update leaves the window up for the balance of the minumum time.
|
||||
assertEquals(REMAINING_VISIBILITY, (long) decayArg.getValue());
|
||||
}
|
||||
|
||||
public void testPostAndUpdateLowPriorityLater() {
|
||||
when(mClock.currentTimeMillis()).thenReturn(0L);
|
||||
NotificationData.Entry a = makeNotification("a");
|
||||
mHeadsUp.showNotification(a);
|
||||
reset(mMockStatusBar);
|
||||
|
||||
when(mClock.currentTimeMillis()).thenReturn(LATER);
|
||||
mHeadsUp.release();
|
||||
// Down grade on update closes immediately if the minimum time window is satisfied.
|
||||
Mockito.verify(mMockStatusBar, times(1)).scheduleHeadsUpClose();
|
||||
Mockito.verify(mMockStatusBar, never()).scheduleHeadsUpDecay(anyInt());
|
||||
}
|
||||
|
||||
public void testPostAndUpdateLowPriorityLaterFastFail() {
|
||||
when(mClock.currentTimeMillis()).thenReturn(0L);
|
||||
NotificationData.Entry a = makeNotification("a");
|
||||
mHeadsUp.showNotification(a);
|
||||
reset(mMockStatusBar);
|
||||
|
||||
when(mClock.currentTimeMillis()).thenReturn(LATER);
|
||||
NotificationData.Entry a_prime = makeNotification("a");
|
||||
mHeadsUp.updateNotification(a_prime, false);
|
||||
mHeadsUp.release();
|
||||
// Down grade on update closes immediately if the minimum time window is satisfied.
|
||||
Mockito.verify(mMockStatusBar, times(1)).scheduleHeadsUpClose();
|
||||
Mockito.verify(mMockStatusBar, never()).scheduleHeadsUpDecay(anyInt());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user