Merge "Put suppressed notifications behind a summary notification."
This commit is contained in:
committed by
Android (Google) Code Review
commit
3c86a27d3e
@ -526,4 +526,12 @@
|
|||||||
<string name="description_direction_up">Slide up for <xliff:g id="target_description" example="Unlock">%s</xliff:g>.</string>
|
<string name="description_direction_up">Slide up for <xliff:g id="target_description" example="Unlock">%s</xliff:g>.</string>
|
||||||
<!-- Description of the left direction in which one can to slide the handle in the Slide unlock screen. [CHAR LIMIT=NONE] -->
|
<!-- Description of the left direction in which one can to slide the handle in the Slide unlock screen. [CHAR LIMIT=NONE] -->
|
||||||
<string name="description_direction_left">"Slide left for <xliff:g id="target_description" example="Unlock">%s</xliff:g>.</string>
|
<string name="description_direction_left">"Slide left for <xliff:g id="target_description" example="Unlock">%s</xliff:g>.</string>
|
||||||
|
|
||||||
|
<!-- Zen mode: Summary notification content title. [CHAR LIMIT=NONE] -->
|
||||||
|
<plurals name="zen_mode_notification_title">
|
||||||
|
<item quantity="one">Notification hidden</item>
|
||||||
|
<item quantity="other">%d notifications hidden</item>
|
||||||
|
</plurals>
|
||||||
|
<!-- Zen mode: Summary notification content text. [CHAR LIMIT=NONE] -->
|
||||||
|
<string name="zen_mode_notification_text">Touch to show</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
@ -109,9 +109,6 @@ public abstract class BaseStatusBar extends SystemUI implements
|
|||||||
public static final int EXPANDED_LEAVE_ALONE = -10000;
|
public static final int EXPANDED_LEAVE_ALONE = -10000;
|
||||||
public static final int EXPANDED_FULL_OPEN = -10001;
|
public static final int EXPANDED_FULL_OPEN = -10001;
|
||||||
|
|
||||||
private static final String EXTRA_INTERCEPT = "android.intercept";
|
|
||||||
private static final float INTERCEPTED_ALPHA = .2f;
|
|
||||||
|
|
||||||
protected CommandQueue mCommandQueue;
|
protected CommandQueue mCommandQueue;
|
||||||
protected IStatusBarService mBarService;
|
protected IStatusBarService mBarService;
|
||||||
protected H mHandler = createHandler();
|
protected H mHandler = createHandler();
|
||||||
@ -1049,7 +1046,6 @@ public abstract class BaseStatusBar extends SystemUI implements
|
|||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
Log.d(TAG, "addNotificationViews: added at " + pos);
|
Log.d(TAG, "addNotificationViews: added at " + pos);
|
||||||
}
|
}
|
||||||
updateInterceptedState(entry);
|
|
||||||
updateExpansionStates();
|
updateExpansionStates();
|
||||||
updateNotificationIcons();
|
updateNotificationIcons();
|
||||||
}
|
}
|
||||||
@ -1082,32 +1078,10 @@ public abstract class BaseStatusBar extends SystemUI implements
|
|||||||
|
|
||||||
protected void setZenMode(int mode) {
|
protected void setZenMode(int mode) {
|
||||||
if (!isDeviceProvisioned()) return;
|
if (!isDeviceProvisioned()) return;
|
||||||
final boolean change = mZenMode != mode;
|
|
||||||
mZenMode = mode;
|
mZenMode = mode;
|
||||||
final int N = mNotificationData.size();
|
|
||||||
for (int i = 0; i < N; i++) {
|
|
||||||
final NotificationData.Entry entry = mNotificationData.get(i);
|
|
||||||
if (change && !shouldIntercept()) {
|
|
||||||
entry.notification.getNotification().extras.putBoolean(EXTRA_INTERCEPT, false);
|
|
||||||
}
|
|
||||||
updateInterceptedState(entry);
|
|
||||||
}
|
|
||||||
updateNotificationIcons();
|
updateNotificationIcons();
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean shouldIntercept() {
|
|
||||||
return mZenMode != Settings.Global.ZEN_MODE_OFF;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected boolean shouldIntercept(Notification n) {
|
|
||||||
return shouldIntercept() && n.extras.getBoolean(EXTRA_INTERCEPT);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateInterceptedState(NotificationData.Entry entry) {
|
|
||||||
final boolean intercepted = shouldIntercept(entry.notification.getNotification());
|
|
||||||
entry.row.findViewById(R.id.container).setAlpha(intercepted ? INTERCEPTED_ALPHA : 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected abstract void haltTicker();
|
protected abstract void haltTicker();
|
||||||
protected abstract void setAreThereNotifications();
|
protected abstract void setAreThereNotifications();
|
||||||
protected abstract void updateNotificationIcons();
|
protected abstract void updateNotificationIcons();
|
||||||
@ -1312,7 +1286,6 @@ public abstract class BaseStatusBar extends SystemUI implements
|
|||||||
} else {
|
} else {
|
||||||
entry.content.setOnClickListener(null);
|
entry.content.setOnClickListener(null);
|
||||||
}
|
}
|
||||||
updateInterceptedState(entry);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void notifyHeadsUpScreenOn(boolean screenOn) {
|
protected void notifyHeadsUpScreenOn(boolean screenOn) {
|
||||||
|
@ -0,0 +1,119 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2014 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.android.systemui.statusbar;
|
||||||
|
|
||||||
|
import android.app.Notification;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.os.Binder;
|
||||||
|
import android.os.IBinder;
|
||||||
|
import android.os.Process;
|
||||||
|
import android.service.notification.StatusBarNotification;
|
||||||
|
import android.util.ArrayMap;
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
|
import com.android.systemui.R;
|
||||||
|
import com.android.systemui.statusbar.NotificationData.Entry;
|
||||||
|
import com.android.systemui.statusbar.phone.PhoneStatusBar;
|
||||||
|
|
||||||
|
public class InterceptedNotifications {
|
||||||
|
private static final String TAG = "InterceptedNotifications";
|
||||||
|
private static final String EXTRA_INTERCEPT = "android.intercept";
|
||||||
|
|
||||||
|
private final Context mContext;
|
||||||
|
private final PhoneStatusBar mBar;
|
||||||
|
private final ArrayMap<IBinder, StatusBarNotification> mIntercepted
|
||||||
|
= new ArrayMap<IBinder, StatusBarNotification>();
|
||||||
|
|
||||||
|
private Binder mSynKey;
|
||||||
|
|
||||||
|
public InterceptedNotifications(Context context, PhoneStatusBar bar) {
|
||||||
|
mContext = context;
|
||||||
|
mBar = bar;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void releaseIntercepted() {
|
||||||
|
final int n = mIntercepted.size();
|
||||||
|
for (int i = 0; i < n; i++) {
|
||||||
|
final IBinder key = mIntercepted.keyAt(i);
|
||||||
|
final StatusBarNotification sbn = mIntercepted.valueAt(i);
|
||||||
|
sbn.getNotification().extras.putBoolean(EXTRA_INTERCEPT, false);
|
||||||
|
mBar.addNotification(key, sbn);
|
||||||
|
}
|
||||||
|
mIntercepted.clear();
|
||||||
|
updateSyntheticNotification();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean tryIntercept(IBinder key, StatusBarNotification notification) {
|
||||||
|
if (!notification.getNotification().extras.getBoolean(EXTRA_INTERCEPT)) return false;
|
||||||
|
mIntercepted.put(key, notification);
|
||||||
|
updateSyntheticNotification();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void remove(IBinder key) {
|
||||||
|
if (mIntercepted.remove(key) != null) {
|
||||||
|
updateSyntheticNotification();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isSyntheticEntry(Entry ent) {
|
||||||
|
return mSynKey != null && ent.key.equals(mSynKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void update(IBinder key, StatusBarNotification notification) {
|
||||||
|
if (mIntercepted.containsKey(key)) {
|
||||||
|
mIntercepted.put(key, notification);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateSyntheticNotification() {
|
||||||
|
if (mIntercepted.isEmpty()) {
|
||||||
|
if (mSynKey != null) {
|
||||||
|
mBar.removeNotification(mSynKey);
|
||||||
|
mSynKey = null;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final Notification n = new Notification.Builder(mContext)
|
||||||
|
.setSmallIcon(R.drawable.stat_sys_zen_limited)
|
||||||
|
.setContentTitle(mContext.getResources().getQuantityString(
|
||||||
|
R.plurals.zen_mode_notification_title,
|
||||||
|
mIntercepted.size(), mIntercepted.size()))
|
||||||
|
.setContentText(mContext.getString(R.string.zen_mode_notification_text))
|
||||||
|
.setOngoing(true)
|
||||||
|
.build();
|
||||||
|
final StatusBarNotification sbn = new StatusBarNotification(mContext.getPackageName(),
|
||||||
|
mContext.getBasePackageName(),
|
||||||
|
TAG.hashCode(), TAG, Process.myUid(), Process.myPid(), 0, n,
|
||||||
|
mBar.getCurrentUserHandle());
|
||||||
|
if (mSynKey == null) {
|
||||||
|
mSynKey = new Binder();
|
||||||
|
mBar.addNotification(mSynKey, sbn);
|
||||||
|
} else {
|
||||||
|
mBar.updateNotification(mSynKey, sbn);
|
||||||
|
}
|
||||||
|
final NotificationData.Entry entry = mBar.mNotificationData.findByKey(mSynKey);
|
||||||
|
entry.content.setOnClickListener(mSynClickListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
private final View.OnClickListener mSynClickListener = new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
releaseIntercepted();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
@ -59,6 +59,7 @@ import android.os.RemoteException;
|
|||||||
import android.os.SystemClock;
|
import android.os.SystemClock;
|
||||||
import android.os.UserHandle;
|
import android.os.UserHandle;
|
||||||
import android.provider.Settings;
|
import android.provider.Settings;
|
||||||
|
import android.provider.Settings.Global;
|
||||||
import android.service.notification.StatusBarNotification;
|
import android.service.notification.StatusBarNotification;
|
||||||
import android.util.DisplayMetrics;
|
import android.util.DisplayMetrics;
|
||||||
import android.util.EventLog;
|
import android.util.EventLog;
|
||||||
@ -89,11 +90,11 @@ import com.android.systemui.R;
|
|||||||
import com.android.systemui.statusbar.BaseStatusBar;
|
import com.android.systemui.statusbar.BaseStatusBar;
|
||||||
import com.android.systemui.statusbar.CommandQueue;
|
import com.android.systemui.statusbar.CommandQueue;
|
||||||
import com.android.systemui.statusbar.GestureRecorder;
|
import com.android.systemui.statusbar.GestureRecorder;
|
||||||
|
import com.android.systemui.statusbar.InterceptedNotifications;
|
||||||
import com.android.systemui.statusbar.NotificationData;
|
import com.android.systemui.statusbar.NotificationData;
|
||||||
import com.android.systemui.statusbar.NotificationData.Entry;
|
import com.android.systemui.statusbar.NotificationData.Entry;
|
||||||
import com.android.systemui.statusbar.SignalClusterView;
|
import com.android.systemui.statusbar.SignalClusterView;
|
||||||
import com.android.systemui.statusbar.StatusBarIconView;
|
import com.android.systemui.statusbar.StatusBarIconView;
|
||||||
|
|
||||||
import com.android.systemui.statusbar.policy.BatteryController;
|
import com.android.systemui.statusbar.policy.BatteryController;
|
||||||
import com.android.systemui.statusbar.policy.BluetoothController;
|
import com.android.systemui.statusbar.policy.BluetoothController;
|
||||||
import com.android.systemui.statusbar.policy.DateView;
|
import com.android.systemui.statusbar.policy.DateView;
|
||||||
@ -101,7 +102,6 @@ import com.android.systemui.statusbar.policy.HeadsUpNotificationView;
|
|||||||
import com.android.systemui.statusbar.policy.LocationController;
|
import com.android.systemui.statusbar.policy.LocationController;
|
||||||
import com.android.systemui.statusbar.policy.NetworkController;
|
import com.android.systemui.statusbar.policy.NetworkController;
|
||||||
import com.android.systemui.statusbar.policy.RotationLockController;
|
import com.android.systemui.statusbar.policy.RotationLockController;
|
||||||
|
|
||||||
import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
|
import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
|
||||||
|
|
||||||
import java.io.FileDescriptor;
|
import java.io.FileDescriptor;
|
||||||
@ -347,6 +347,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode {
|
|||||||
}};
|
}};
|
||||||
|
|
||||||
private Runnable mOnFlipRunnable;
|
private Runnable mOnFlipRunnable;
|
||||||
|
private InterceptedNotifications mIntercepted;
|
||||||
|
|
||||||
public void setOnFlipRunnable(Runnable onFlipRunnable) {
|
public void setOnFlipRunnable(Runnable onFlipRunnable) {
|
||||||
mOnFlipRunnable = onFlipRunnable;
|
mOnFlipRunnable = onFlipRunnable;
|
||||||
@ -357,7 +358,11 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode {
|
|||||||
super.setZenMode(mode);
|
super.setZenMode(mode);
|
||||||
if (mModeIcon == null) return;
|
if (mModeIcon == null) return;
|
||||||
if (!isDeviceProvisioned()) return;
|
if (!isDeviceProvisioned()) return;
|
||||||
mModeIcon.setVisibility(mode != Settings.Global.ZEN_MODE_OFF ? View.VISIBLE : View.GONE);
|
final boolean zen = mode != Settings.Global.ZEN_MODE_OFF;
|
||||||
|
mModeIcon.setVisibility(zen ? View.VISIBLE : View.GONE);
|
||||||
|
if (!zen) {
|
||||||
|
mIntercepted.releaseIntercepted();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -365,7 +370,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode {
|
|||||||
mDisplay = ((WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE))
|
mDisplay = ((WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE))
|
||||||
.getDefaultDisplay();
|
.getDefaultDisplay();
|
||||||
updateDisplaySize();
|
updateDisplaySize();
|
||||||
|
mIntercepted = new InterceptedNotifications(mContext, this);
|
||||||
super.start(); // calls createAndAddWindows()
|
super.start(); // calls createAndAddWindows()
|
||||||
|
|
||||||
addNavigationBar();
|
addNavigationBar();
|
||||||
@ -931,49 +936,54 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode {
|
|||||||
mStatusIcons.removeViewAt(viewIndex);
|
mStatusIcons.removeViewAt(viewIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public UserHandle getCurrentUserHandle() {
|
||||||
|
return new UserHandle(mCurrentUserId);
|
||||||
|
}
|
||||||
|
|
||||||
public void addNotification(IBinder key, StatusBarNotification notification) {
|
public void addNotification(IBinder key, StatusBarNotification notification) {
|
||||||
if (DEBUG) Log.d(TAG, "addNotification score=" + notification.getScore());
|
if (DEBUG) Log.d(TAG, "addNotification score=" + notification.getScore());
|
||||||
Entry shadeEntry = createNotificationViews(key, notification);
|
Entry shadeEntry = createNotificationViews(key, notification);
|
||||||
if (shadeEntry == null) {
|
if (shadeEntry == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!shouldIntercept(notification.getNotification())) {
|
if (mZenMode != Global.ZEN_MODE_OFF && mIntercepted.tryIntercept(key, notification)) {
|
||||||
if (mUseHeadsUp && shouldInterrupt(notification)) {
|
return;
|
||||||
if (DEBUG) Log.d(TAG, "launching notification in heads up mode");
|
}
|
||||||
Entry interruptionCandidate = new Entry(key, notification, null);
|
if (mUseHeadsUp && shouldInterrupt(notification)) {
|
||||||
ViewGroup holder = mHeadsUpNotificationView.getHolder();
|
if (DEBUG) Log.d(TAG, "launching notification in heads up mode");
|
||||||
if (inflateViewsForHeadsUp(interruptionCandidate, holder)) {
|
Entry interruptionCandidate = new Entry(key, notification, null);
|
||||||
mInterruptingNotificationTime = System.currentTimeMillis();
|
ViewGroup holder = mHeadsUpNotificationView.getHolder();
|
||||||
mInterruptingNotificationEntry = interruptionCandidate;
|
if (inflateViewsForHeadsUp(interruptionCandidate, holder)) {
|
||||||
shadeEntry.setInterruption();
|
mInterruptingNotificationTime = System.currentTimeMillis();
|
||||||
|
mInterruptingNotificationEntry = interruptionCandidate;
|
||||||
|
shadeEntry.setInterruption();
|
||||||
|
|
||||||
// 1. Populate mHeadsUpNotificationView
|
// 1. Populate mHeadsUpNotificationView
|
||||||
mHeadsUpNotificationView.setNotification(mInterruptingNotificationEntry);
|
mHeadsUpNotificationView.setNotification(mInterruptingNotificationEntry);
|
||||||
|
|
||||||
// 2. Animate mHeadsUpNotificationView in
|
// 2. Animate mHeadsUpNotificationView in
|
||||||
mHandler.sendEmptyMessage(MSG_SHOW_HEADS_UP);
|
mHandler.sendEmptyMessage(MSG_SHOW_HEADS_UP);
|
||||||
|
|
||||||
// 3. Set alarm to age the notification off
|
// 3. Set alarm to age the notification off
|
||||||
resetHeadsUpDecayTimer();
|
resetHeadsUpDecayTimer();
|
||||||
}
|
}
|
||||||
} else if (notification.getNotification().fullScreenIntent != null) {
|
} else if (notification.getNotification().fullScreenIntent != null) {
|
||||||
// Stop screensaver if the notification has a full-screen intent.
|
// Stop screensaver if the notification has a full-screen intent.
|
||||||
// (like an incoming phone call)
|
// (like an incoming phone call)
|
||||||
awakenDreams();
|
awakenDreams();
|
||||||
|
|
||||||
// not immersive & a full-screen alert should be shown
|
// not immersive & a full-screen alert should be shown
|
||||||
if (DEBUG) Log.d(TAG, "Notification has fullScreenIntent; sending fullScreenIntent");
|
if (DEBUG) Log.d(TAG, "Notification has fullScreenIntent; sending fullScreenIntent");
|
||||||
try {
|
try {
|
||||||
notification.getNotification().fullScreenIntent.send();
|
notification.getNotification().fullScreenIntent.send();
|
||||||
} catch (PendingIntent.CanceledException e) {
|
} catch (PendingIntent.CanceledException e) {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// usual case: status bar visible & not immersive
|
// usual case: status bar visible & not immersive
|
||||||
|
|
||||||
// show the ticker if there isn't already a heads up
|
// show the ticker if there isn't already a heads up
|
||||||
if (mInterruptingNotificationEntry == null) {
|
if (mInterruptingNotificationEntry == null) {
|
||||||
tick(null, notification, true);
|
tick(null, notification, true);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
addNotificationViews(shadeEntry);
|
addNotificationViews(shadeEntry);
|
||||||
@ -991,6 +1001,12 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updateNotification(IBinder key, StatusBarNotification notification) {
|
||||||
|
super.updateNotification(key, notification);
|
||||||
|
mIntercepted.update(key, notification);
|
||||||
|
}
|
||||||
|
|
||||||
public void removeNotification(IBinder key) {
|
public void removeNotification(IBinder key) {
|
||||||
StatusBarNotification old = removeNotificationViews(key);
|
StatusBarNotification old = removeNotificationViews(key);
|
||||||
if (SPEW) Log.d(TAG, "removeNotification key=" + key + " old=" + old);
|
if (SPEW) Log.d(TAG, "removeNotification key=" + key + " old=" + old);
|
||||||
@ -1012,7 +1028,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode {
|
|||||||
animateCollapsePanels();
|
animateCollapsePanels();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
mIntercepted.remove(key);
|
||||||
setAreThereNotifications();
|
setAreThereNotifications();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1129,7 +1145,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode {
|
|||||||
// in "public" mode (atop a secure keyguard), secret notifs are totally hidden
|
// in "public" mode (atop a secure keyguard), secret notifs are totally hidden
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (shouldIntercept(ent.notification.getNotification())) {
|
if (mIntercepted.isSyntheticEntry(ent)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
toShow.add(ent.icon);
|
toShow.add(ent.icon);
|
||||||
|
Reference in New Issue
Block a user