SystemUI support for notification visibility.

In this implementation, DISABLE_NOTIFICATION_TICKER (which was never
really used on its own and can be safely subsumed by
DISABLE_NOTIFICATION_ICONS) is now DISABLE_PRIVATE_NOTIFICATIONS;
when this SystemUI bit is set by the keyguard, SystemUI knows to switch
its presentation into "public" mode, in which
VISIBILITY_PRIVATE notifications are replaced with their
publicVersion's contentView (or a placeholder view,
synthesized by SystemUI, that leaks no additional
information about the notification). VISIBILITY_SECRET
notifications are suppressed altogether in this mode.

This behavior is enabled but not activated by default. To
turn it on, run:

  $ adb shell settings put secure lock_screen_allow_notifications 1

and restart SystemUI.

Change-Id: Id660bef7737580e16a83f60567c22b53ee81c602
This commit is contained in:
Dan Sandler
2014-01-23 15:11:54 -05:00
parent 0bf2ed8ae3
commit a5e0f415d3
8 changed files with 194 additions and 35 deletions

View File

@@ -38,8 +38,11 @@ public class StatusBarManager {
public static final int DISABLE_NOTIFICATION_ICONS = View.STATUS_BAR_DISABLE_NOTIFICATION_ICONS;
public static final int DISABLE_NOTIFICATION_ALERTS
= View.STATUS_BAR_DISABLE_NOTIFICATION_ALERTS;
@Deprecated
public static final int DISABLE_NOTIFICATION_TICKER
= View.STATUS_BAR_DISABLE_NOTIFICATION_TICKER;
public static final int DISABLE_PRIVATE_NOTIFICATIONS
= View.STATUS_BAR_DISABLE_NOTIFICATION_TICKER;
public static final int DISABLE_SYSTEM_INFO = View.STATUS_BAR_DISABLE_SYSTEM_INFO;
public static final int DISABLE_HOME = View.STATUS_BAR_DISABLE_HOME;
public static final int DISABLE_RECENT = View.STATUS_BAR_DISABLE_RECENT;

View File

@@ -3467,6 +3467,14 @@ public final class Settings {
public static final String LOCK_SCREEN_OWNER_INFO_ENABLED =
"lock_screen_owner_info_enabled";
/**
* This preference enables expanding the notification panel even over a securely
* locked screen, showing only "public" notifications in this case.
* @hide
*/
public static final String LOCK_SCREEN_ALLOW_NOTIFICATIONS =
"lock_screen_allow_notifications";
/**
* The Logging ID (a unique 64-bit value) as a hex string.
* Used as a pseudonymous identifier for logging.

View File

@@ -149,6 +149,18 @@ public class KeyguardViewMediator {
*/
private static final boolean ENABLE_INSECURE_STATUS_BAR_EXPAND = true;
/**
* Allow the user to expand the status bar when a SECURE keyguard is engaged
* and {@link Settings.Secure#LOCK_SCREEN_ALLOW_NOTIFICATIONS} is set
* (private notifications will be masked).
*/
private static final boolean ENABLE_SECURE_STATUS_BAR_EXPAND = true;
/**
* Default value of {@link Settings.Secure#LOCK_SCREEN_ALLOW_NOTIFICATIONS}.
*/
private static final boolean ALLOW_NOTIFICATIONS_DEFAULT = false;
/** The stream type that the lock sounds are tied to. */
private int mMasterStreamType;
@@ -245,6 +257,11 @@ public class KeyguardViewMediator {
private int mUnlockSoundId;
private int mLockSoundStreamId;
/**
* Tracks value of {@link Settings.Secure#LOCK_SCREEN_ALLOW_NOTIFICATIONS}.
*/
private boolean mAllowNotificationsWhenSecure;
/**
* The volume applied to the lock/unlock sounds.
*/
@@ -894,6 +911,13 @@ public class KeyguardViewMediator {
return;
}
// note whether notification access should be allowed
mAllowNotificationsWhenSecure = ENABLE_SECURE_STATUS_BAR_EXPAND
&& 0 != Settings.Secure.getInt(
mContext.getContentResolver(),
Settings.Secure.LOCK_SCREEN_ALLOW_NOTIFICATIONS,
ALLOW_NOTIFICATIONS_DEFAULT ? 1 : 0);
// if the keyguard is already showing, don't bother
if (mKeyguardViewManager.isShowing()) {
if (DEBUG) Log.d(TAG, "doKeyguard: not showing because it is already showing");
@@ -1278,13 +1302,15 @@ public class KeyguardViewMediator {
// (like recents). Temporary enable/disable (e.g. the "back" button) are
// done in KeyguardHostView.
flags |= StatusBarManager.DISABLE_RECENT;
if (isSecure() || !ENABLE_INSECURE_STATUS_BAR_EXPAND) {
if ((isSecure() && !mAllowNotificationsWhenSecure)
|| !ENABLE_INSECURE_STATUS_BAR_EXPAND) {
// showing secure lockscreen; disable expanding.
flags |= StatusBarManager.DISABLE_EXPAND;
}
if (isSecure()) {
// showing secure lockscreen; disable ticker.
flags |= StatusBarManager.DISABLE_NOTIFICATION_TICKER;
// showing secure lockscreen; disable ticker and switch private notifications
// to show their public versions, if available.
flags |= StatusBarManager.DISABLE_PRIVATE_NOTIFICATIONS;
}
if (!isAssistantAvailable()) {
flags |= StatusBarManager.DISABLE_SEARCH;

View File

@@ -25,7 +25,7 @@
android:paddingStart="8dp"
/>
<com.android.systemui.statusbar.LatestItemView android:id="@+id/content"
<com.android.systemui.statusbar.LatestItemView android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/notification_divider_height"
@@ -34,10 +34,13 @@
android:clickable="true"
>
<com.android.internal.widget.SizeAdaptiveLayout android:id="@+id/adaptive"
<com.android.internal.widget.SizeAdaptiveLayout android:id="@+id/expanded"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<com.android.internal.widget.SizeAdaptiveLayout android:id="@+id/expandedPublic"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</com.android.systemui.statusbar.LatestItemView>
<View

View File

@@ -26,6 +26,7 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.Configuration;
import android.database.ContentObserver;
@@ -130,6 +131,7 @@ public abstract class BaseStatusBar extends SystemUI implements
protected IDreamManager mDreamManager;
PowerManager mPowerManager;
protected int mRowHeight;
private boolean mPublicMode = false;
// UI-specific methods
@@ -548,6 +550,14 @@ public abstract class BaseStatusBar extends SystemUI implements
public abstract void resetHeadsUpDecayTimer();
public void setPublicMode(boolean publicMode) {
mPublicMode = publicMode;
}
public boolean isPublicMode() {
return mPublicMode;
}
protected class H extends Handler {
public void handleMessage(Message m) {
Intent intent;
@@ -625,6 +635,11 @@ public abstract class BaseStatusBar extends SystemUI implements
return false;
}
Log.v(TAG, "publicNotification: "
+ sbn.getNotification().publicVersion);
Notification publicNotification = sbn.getNotification().publicVersion;
// create the row view
LayoutInflater inflater = (LayoutInflater)mContext.getSystemService(
Context.LAYOUT_INFLATER_SERVICE);
@@ -642,8 +657,10 @@ public abstract class BaseStatusBar extends SystemUI implements
// NB: the large icon is now handled entirely by the template
// bind the click event to the content area
ViewGroup content = (ViewGroup)row.findViewById(R.id.content);
ViewGroup adaptive = (ViewGroup)row.findViewById(R.id.adaptive);
ViewGroup content = (ViewGroup)row.findViewById(R.id.container);
SizeAdaptiveLayout expanded = (SizeAdaptiveLayout)row.findViewById(R.id.expanded);
SizeAdaptiveLayout expandedPublic
= (SizeAdaptiveLayout)row.findViewById(R.id.expandedPublic);
content.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
@@ -656,12 +673,13 @@ public abstract class BaseStatusBar extends SystemUI implements
content.setOnClickListener(null);
}
// set up the adaptive layout
View contentViewLocal = null;
View bigContentViewLocal = null;
try {
contentViewLocal = contentView.apply(mContext, adaptive, mOnClickHandler);
contentViewLocal = contentView.apply(mContext, expanded, mOnClickHandler);
if (bigContentView != null) {
bigContentViewLocal = bigContentView.apply(mContext, adaptive, mOnClickHandler);
bigContentViewLocal = bigContentView.apply(mContext, expanded, mOnClickHandler);
}
}
catch (RuntimeException e) {
@@ -675,15 +693,70 @@ public abstract class BaseStatusBar extends SystemUI implements
new SizeAdaptiveLayout.LayoutParams(contentViewLocal.getLayoutParams());
params.minHeight = minHeight;
params.maxHeight = minHeight;
adaptive.addView(contentViewLocal, params);
expanded.addView(contentViewLocal, params);
}
if (bigContentViewLocal != null) {
SizeAdaptiveLayout.LayoutParams params =
new SizeAdaptiveLayout.LayoutParams(bigContentViewLocal.getLayoutParams());
params.minHeight = minHeight+1;
params.maxHeight = maxHeight;
adaptive.addView(bigContentViewLocal, params);
expanded.addView(bigContentViewLocal, params);
}
PackageManager pm = mContext.getPackageManager();
// now the public version
View publicViewLocal = null;
if (publicNotification != null) {
try {
publicViewLocal = publicNotification.contentView.apply(mContext,
expandedPublic, mOnClickHandler);
if (publicViewLocal != null) {
SizeAdaptiveLayout.LayoutParams params =
new SizeAdaptiveLayout.LayoutParams(publicViewLocal.getLayoutParams());
params.minHeight = minHeight;
params.maxHeight = minHeight;
expandedPublic.addView(publicViewLocal, params);
}
}
catch (RuntimeException e) {
final String ident = sbn.getPackageName() + "/0x" + Integer.toHexString(sbn.getId());
Log.e(TAG, "couldn't inflate public view for notification " + ident, e);
publicViewLocal = null;
}
}
if (publicViewLocal == null) {
// Add a basic notification template
publicViewLocal = LayoutInflater.from(mContext).inflate(
com.android.internal.R.layout.notification_template_base, expandedPublic, true);
final TextView title = (TextView) publicViewLocal.findViewById(com.android.internal.R.id.title);
try {
title.setText(pm.getApplicationLabel(
pm.getApplicationInfo(entry.notification.getPackageName(), 0)));
} catch (NameNotFoundException e) {
title.setText(entry.notification.getPackageName());
}
final ImageView icon = (ImageView) publicViewLocal.findViewById(com.android.internal.R.id.icon);
final StatusBarIcon ic = new StatusBarIcon(entry.notification.getPackageName(),
entry.notification.getUser(),
entry.notification.getNotification().icon,
entry.notification.getNotification().iconLevel,
entry.notification.getNotification().number,
entry.notification.getNotification().tickerText);
icon.setImageDrawable(StatusBarIconView.getIcon(mContext, ic));
final TextView text = (TextView) publicViewLocal.findViewById(com.android.internal.R.id.text);
text.setText("Unlock your device to see this notification.");
// TODO: fill out "time" as well
}
row.setDrawingCacheEnabled(true);
applyLegacyRowBackground(sbn, content);
@@ -699,6 +772,7 @@ public abstract class BaseStatusBar extends SystemUI implements
entry.row.setRowHeight(mRowHeight);
entry.content = content;
entry.expanded = contentViewLocal;
entry.expandedPublic = publicViewLocal;
entry.setBigContentView(bigContentViewLocal);
return true;
@@ -904,6 +978,12 @@ public abstract class BaseStatusBar extends SystemUI implements
final RemoteViews contentView = notification.getNotification().contentView;
final RemoteViews oldBigContentView = oldNotification.getNotification().bigContentView;
final RemoteViews bigContentView = notification.getNotification().bigContentView;
final Notification oldPublicNotification = oldNotification.getNotification().publicVersion;
final RemoteViews oldPublicContentView = oldPublicNotification != null
? oldPublicNotification.contentView : null;
final Notification publicNotification = notification.getNotification().publicVersion;
final RemoteViews publicContentView = publicNotification != null
? publicNotification.contentView : null;
if (DEBUG) {
Log.d(TAG, "old notification: when=" + oldNotification.getNotification().when
@@ -911,11 +991,13 @@ public abstract class BaseStatusBar extends SystemUI implements
+ " expanded=" + oldEntry.expanded
+ " contentView=" + oldContentView
+ " bigContentView=" + oldBigContentView
+ " publicView=" + oldPublicContentView
+ " rowParent=" + oldEntry.row.getParent());
Log.d(TAG, "new notification: when=" + notification.getNotification().when
+ " ongoing=" + oldNotification.isOngoing()
+ " contentView=" + contentView
+ " bigContentView=" + bigContentView);
+ " bigContentView=" + bigContentView
+ " publicView=" + publicContentView);
}
// Can we just reapply the RemoteViews in place? If when didn't change, the order
@@ -935,8 +1017,17 @@ public abstract class BaseStatusBar extends SystemUI implements
&& oldBigContentView.getPackage() != null
&& oldBigContentView.getPackage().equals(bigContentView.getPackage())
&& oldBigContentView.getLayoutId() == bigContentView.getLayoutId());
boolean publicUnchanged =
(oldPublicContentView == null && publicContentView == null)
|| ((oldPublicContentView != null && publicContentView != null)
&& publicContentView.getPackage() != null
&& oldPublicContentView.getPackage() != null
&& oldPublicContentView.getPackage().equals(publicContentView.getPackage())
&& oldPublicContentView.getLayoutId() == publicContentView.getLayoutId());
ViewGroup rowParent = (ViewGroup) oldEntry.row.getParent();
boolean orderUnchanged = notification.getNotification().when== oldNotification.getNotification().when
boolean orderUnchanged =
notification.getNotification().when == oldNotification.getNotification().when
&& notification.getScore() == oldNotification.getScore();
// score now encompasses/supersedes isOngoing()
@@ -944,7 +1035,8 @@ public abstract class BaseStatusBar extends SystemUI implements
&& !TextUtils.equals(notification.getNotification().tickerText,
oldEntry.notification.getNotification().tickerText);
boolean isTopAnyway = isTopNotification(rowParent, oldEntry);
if (contentsUnchanged && bigContentsUnchanged && (orderUnchanged || isTopAnyway)) {
if (contentsUnchanged && bigContentsUnchanged && publicUnchanged
&& (orderUnchanged || isTopAnyway)) {
if (DEBUG) Log.d(TAG, "reusing notification for key: " + key);
oldEntry.notification = notification;
try {
@@ -1018,11 +1110,17 @@ public abstract class BaseStatusBar extends SystemUI implements
StatusBarNotification notification) {
final RemoteViews contentView = notification.getNotification().contentView;
final RemoteViews bigContentView = notification.getNotification().bigContentView;
final RemoteViews publicContentView
= notification.getNotification().publicVersion.contentView;
// Reapply the RemoteViews
contentView.reapply(mContext, entry.expanded, mOnClickHandler);
if (bigContentView != null && entry.getBigContentView() != null) {
bigContentView.reapply(mContext, entry.getBigContentView(), mOnClickHandler);
}
if (publicContentView != null && entry.getPublicContentView() != null) {
publicContentView.reapply(mContext, entry.getPublicContentView(), mOnClickHandler);
}
// update the contentIntent
final PendingIntent contentIntent = notification.getNotification().contentIntent;
if (contentIntent != null) {

View File

@@ -18,9 +18,12 @@ package com.android.systemui.statusbar;
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import com.android.systemui.R;
public class ExpandableNotificationRow extends FrameLayout {
private int mRowHeight;
@@ -30,6 +33,8 @@ public class ExpandableNotificationRow extends FrameLayout {
private boolean mUserExpanded;
/** is the user touching this row */
private boolean mUserLocked;
/** are we showing the "public" version */
private boolean mShowingPublic;
public ExpandableNotificationRow(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -76,4 +81,16 @@ public class ExpandableNotificationRow extends FrameLayout {
}
setLayoutParams(lp);
}
public void setShowingPublic(boolean show) {
mShowingPublic = show;
final ViewGroup publicLayout = (ViewGroup) findViewById(R.id.expandedPublic);
// bail out if no public version
if (publicLayout.getChildCount() == 0) return;
// TODO: animation?
publicLayout.setVisibility(show ? View.VISIBLE : View.GONE);
findViewById(R.id.expanded).setVisibility(show ? View.GONE : View.VISIBLE);
}
}

View File

@@ -35,6 +35,7 @@ public class NotificationData {
public ExpandableNotificationRow row; // the outer expanded view
public View content; // takes the click events and sends the PendingIntent
public View expanded; // the inflated RemoteViews
public View expandedPublic; // for insecure lockscreens
public ImageView largeIcon;
private View expandedBig;
private boolean interruption;
@@ -51,6 +52,7 @@ public class NotificationData {
public View getBigContentView() {
return expandedBig;
}
public View getPublicContentView() { return expandedPublic; }
/**
* Set the flag indicating that this is being touched by the user.
*/
@@ -108,19 +110,6 @@ public class NotificationData {
return i;
}
public int add(IBinder key, StatusBarNotification notification, ExpandableNotificationRow row,
View content, View expanded, StatusBarIconView icon) {
Entry entry = new Entry();
entry.key = key;
entry.notification = notification;
entry.row = row;
entry.content = content;
entry.expanded = expanded;
entry.icon = icon;
entry.largeIcon = null; // TODO add support for large icons
return add(entry);
}
public Entry remove(IBinder key) {
Entry e = findByKey(key);
if (e != null) {

View File

@@ -514,7 +514,6 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode {
mScrollView.setVerticalScrollBarEnabled(false); // less drawing during pulldowns
if (!mNotificationPanelIsFullScreenWidth) {
mScrollView.setSystemUiVisibility(
View.STATUS_BAR_DISABLE_NOTIFICATION_TICKER |
View.STATUS_BAR_DISABLE_NOTIFICATION_ICONS |
View.STATUS_BAR_DISABLE_CLOCK);
}
@@ -621,7 +620,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode {
mQS = new QuickSettings(mContext, mSettingsContainer);
if (!mNotificationPanelIsFullScreenWidth) {
mSettingsContainer.setSystemUiVisibility(
View.STATUS_BAR_DISABLE_NOTIFICATION_TICKER
View.STATUS_BAR_DISABLE_NOTIFICATION_ICONS
| View.STATUS_BAR_DISABLE_SYSTEM_INFO);
}
if (mSettingsPanel != null) {
@@ -1031,7 +1030,12 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode {
Entry ent = mNotificationData.get(N-i-1);
if (!(provisioned || showNotificationEvenIfUnprovisioned(ent.notification))) continue;
if (!notificationIsForCurrentUser(ent.notification)) continue;
toShow.add(ent.row);
final int vis = ent.notification.getNotification().visibility;
if (vis != Notification.VISIBILITY_SECRET) {
// when isPublicMode() we show the public form of VISIBILITY_PRIVATE notifications
ent.row.setShowingPublic(isPublicMode() && vis == Notification.VISIBILITY_PRIVATE);
toShow.add(ent.row);
}
}
ArrayList<View> toRemove = new ArrayList<View>();
@@ -1082,6 +1086,12 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode {
if (!((provisioned && ent.notification.getScore() >= HIDE_ICONS_BELOW_SCORE)
|| showNotificationEvenIfUnprovisioned(ent.notification))) continue;
if (!notificationIsForCurrentUser(ent.notification)) continue;
if (isPublicMode()
&& ent.notification.getNotification().visibility
== Notification.VISIBILITY_SECRET) {
// in "public" mode (atop a secure keyguard), secret notifs are totally hidden
continue;
}
toShow.add(ent.icon);
}
@@ -1243,8 +1253,8 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode {
flagdbg.append(((diff & StatusBarManager.DISABLE_NOTIFICATION_ICONS) != 0) ? "* " : " ");
flagdbg.append(((state & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) != 0) ? "ALERTS" : "alerts");
flagdbg.append(((diff & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) != 0) ? "* " : " ");
flagdbg.append(((state & StatusBarManager.DISABLE_NOTIFICATION_TICKER) != 0) ? "TICKER" : "ticker");
flagdbg.append(((diff & StatusBarManager.DISABLE_NOTIFICATION_TICKER) != 0) ? "* " : " ");
flagdbg.append(((state & StatusBarManager.DISABLE_PRIVATE_NOTIFICATIONS) != 0) ? "PRIVATE" : "private");
flagdbg.append(((diff & StatusBarManager.DISABLE_PRIVATE_NOTIFICATIONS) != 0) ? "* " : " ");
flagdbg.append(((state & StatusBarManager.DISABLE_SYSTEM_INFO) != 0) ? "SYSTEM_INFO" : "system_info");
flagdbg.append(((diff & StatusBarManager.DISABLE_SYSTEM_INFO) != 0) ? "* " : " ");
flagdbg.append(((state & StatusBarManager.DISABLE_BACK) != 0) ? "BACK" : "back");
@@ -1329,10 +1339,15 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode {
.setDuration(175)
.start();
}
} else if ((diff & StatusBarManager.DISABLE_NOTIFICATION_TICKER) != 0) {
if (mTicking && (state & StatusBarManager.DISABLE_NOTIFICATION_TICKER) != 0) {
haltTicker();
} else if ((diff & StatusBarManager.DISABLE_PRIVATE_NOTIFICATIONS) != 0) {
if ((state & StatusBarManager.DISABLE_PRIVATE_NOTIFICATIONS) != 0) {
// we are outside a secure keyguard, so we need to switch to "public" mode
setPublicMode(true);
} else {
// user has authenticated the device; full notifications may be shown
setPublicMode(false);
}
updateNotificationIcons();
}
}