OSDN Git Service

Zoom notifications on lockscreen after tapping.
[android-x86/frameworks-base.git] / packages / SystemUI / src / com / android / systemui / statusbar / BaseStatusBar.java
index 9a0749d..2ea5add 100644 (file)
@@ -26,10 +26,13 @@ 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.pm.UserInfo;
 import android.content.res.Configuration;
 import android.database.ContentObserver;
 import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
 import android.net.Uri;
 import android.os.Build;
 import android.os.Handler;
@@ -39,12 +42,15 @@ import android.os.PowerManager;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.UserHandle;
+import android.os.UserManager;
 import android.provider.Settings;
 import android.service.dreams.DreamService;
 import android.service.dreams.IDreamManager;
 import android.service.notification.StatusBarNotification;
 import android.text.TextUtils;
 import android.util.Log;
+import android.util.SparseArray;
+import android.util.SparseBooleanArray;
 import android.view.Display;
 import android.view.IWindowManager;
 import android.view.LayoutInflater;
@@ -64,19 +70,20 @@ import android.widget.TextView;
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.internal.statusbar.StatusBarIcon;
 import com.android.internal.statusbar.StatusBarIconList;
+import com.android.internal.util.LegacyNotificationUtil;
 import com.android.internal.widget.SizeAdaptiveLayout;
 import com.android.systemui.R;
 import com.android.systemui.RecentsComponent;
 import com.android.systemui.SearchPanelView;
 import com.android.systemui.SystemUI;
 import com.android.systemui.statusbar.phone.KeyguardTouchDelegate;
-import com.android.systemui.statusbar.policy.NotificationRowLayout;
+import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
 
 import java.util.ArrayList;
 import java.util.Locale;
 
 public abstract class BaseStatusBar extends SystemUI implements
-        CommandQueue.Callbacks {
+        CommandQueue.Callbacks, LatestItemView.OnActivatedListener {
     public static final String TAG = "StatusBar";
     public static final boolean DEBUG = false;
     public static final boolean MULTIUSER_DEBUG = false;
@@ -93,8 +100,8 @@ public abstract class BaseStatusBar extends SystemUI implements
 
     protected static final boolean ENABLE_HEADS_UP = true;
     // scores above this threshold should be displayed in heads up mode.
-    protected static final int INTERRUPTION_THRESHOLD = 11;
-    protected static final String SETTING_HEADS_UP = "heads_up_enabled";
+    protected static final int INTERRUPTION_THRESHOLD = 10;
+    protected static final String SETTING_HEADS_UP_TICKER = "ticker_gets_heads_up";
 
     // Should match the value in PhoneWindowManager
     public static final String SYSTEM_DIALOG_REASON_RECENT_APPS = "recentapps";
@@ -108,7 +115,7 @@ public abstract class BaseStatusBar extends SystemUI implements
 
     // all notifications
     protected NotificationData mNotificationData = new NotificationData();
-    protected NotificationRowLayout mPile;
+    protected NotificationStackScrollLayout mStackScroller;
 
     protected NotificationData.Entry mInterruptingNotificationEntry;
     protected long mInterruptingNotificationTime;
@@ -122,14 +129,24 @@ public abstract class BaseStatusBar extends SystemUI implements
     protected PopupMenu mNotificationBlamePopup;
 
     protected int mCurrentUserId = 0;
+    final protected SparseArray<UserInfo> mCurrentProfiles = new SparseArray<UserInfo>();
 
     protected int mLayoutDirection = -1; // invalid
     private Locale mLocale;
     protected boolean mUseHeadsUp = false;
+    protected boolean mHeadsUpTicker = false;
 
     protected IDreamManager mDreamManager;
     PowerManager mPowerManager;
-    protected int mRowHeight;
+    protected int mRowMinHeight;
+    protected int mRowMaxHeight;
+
+    // public mode, private notifications, etc
+    private boolean mLockscreenPublicMode = false;
+    private final SparseBooleanArray mUsersAllowingPrivateNotifications = new SparseBooleanArray();
+    private LegacyNotificationUtil mLegacyNotificationUtil = LegacyNotificationUtil.getInstance();
+
+    private UserManager mUserManager;
 
     // UI-specific methods
 
@@ -149,15 +166,16 @@ public abstract class BaseStatusBar extends SystemUI implements
 
     private RecentsComponent mRecents;
 
-    public IStatusBarService getStatusBarService() {
-        return mBarService;
-    }
+    protected int mZenMode;
+
+    protected boolean mOnKeyguard;
+    protected NotificationOverflowContainer mKeyguardIconOverflowContainer;
 
     public boolean isDeviceProvisioned() {
         return mDeviceProvisioned;
     }
 
-    private ContentObserver mProvisioningObserver = new ContentObserver(new Handler()) {
+    protected final ContentObserver mSettingsObserver = new ContentObserver(mHandler) {
         @Override
         public void onChange(boolean selfChange) {
             final boolean provisioned = 0 != Settings.Global.getInt(
@@ -166,6 +184,20 @@ public abstract class BaseStatusBar extends SystemUI implements
                 mDeviceProvisioned = provisioned;
                 updateNotificationIcons();
             }
+            final int mode = Settings.Global.getInt(mContext.getContentResolver(),
+                    Settings.Global.ZEN_MODE, Settings.Global.ZEN_MODE_OFF);
+            setZenMode(mode);
+        }
+    };
+
+    private final ContentObserver mLockscreenSettingsObserver = new ContentObserver(mHandler) {
+        @Override
+        public void onChange(boolean selfChange) {
+            // We don't know which user changed LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS,
+            // so we just dump our cache ...
+            mUsersAllowingPrivateNotifications.clear();
+            // ... and refresh all the notifications
+            updateNotificationIcons();
         }
     };
 
@@ -207,12 +239,26 @@ public abstract class BaseStatusBar extends SystemUI implements
             String action = intent.getAction();
             if (Intent.ACTION_USER_SWITCHED.equals(action)) {
                 mCurrentUserId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
+                updateCurrentProfilesCache();
                 if (true) Log.v(TAG, "userId " + mCurrentUserId + " is in the house");
                 userSwitched(mCurrentUserId);
+            } else if (Intent.ACTION_USER_ADDED.equals(action)) {
+                updateCurrentProfilesCache();
             }
         }
     };
 
+    private void updateCurrentProfilesCache() {
+        synchronized (mCurrentProfiles) {
+            mCurrentProfiles.clear();
+            if (mUserManager != null) {
+                for (UserInfo user : mUserManager.getProfiles(mCurrentUserId)) {
+                    mCurrentProfiles.put(user.id, user);
+                }
+            }
+        }
+    }
+
     public void start() {
         mWindowManager = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
         mWindowManagerService = WindowManagerGlobal.getWindowManagerService();
@@ -222,10 +268,19 @@ public abstract class BaseStatusBar extends SystemUI implements
                 ServiceManager.checkService(DreamService.DREAM_SERVICE));
         mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
 
-        mProvisioningObserver.onChange(false); // set up
+        mSettingsObserver.onChange(false); // set up
         mContext.getContentResolver().registerContentObserver(
                 Settings.Global.getUriFor(Settings.Global.DEVICE_PROVISIONED), true,
-                mProvisioningObserver);
+                mSettingsObserver);
+        mContext.getContentResolver().registerContentObserver(
+                Settings.Global.getUriFor(Settings.Global.ZEN_MODE), false,
+                mSettingsObserver);
+
+        mContext.getContentResolver().registerContentObserver(
+                Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS),
+                true,
+                mLockscreenSettingsObserver,
+                UserHandle.USER_ALL);
 
         mBarService = IStatusBarService.Stub.asInterface(
                 ServiceManager.getService(Context.STATUS_BAR_SERVICE));
@@ -235,6 +290,8 @@ public abstract class BaseStatusBar extends SystemUI implements
         mLocale = mContext.getResources().getConfiguration().locale;
         mLayoutDirection = TextUtils.getLayoutDirectionFromLocale(mLocale);
 
+        mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
+
         // Connect in to the status bar manager service
         StatusBarIconList iconList = new StatusBarIconList();
         ArrayList<IBinder> notificationKeys = new ArrayList<IBinder>();
@@ -296,22 +353,27 @@ public abstract class BaseStatusBar extends SystemUI implements
 
         IntentFilter filter = new IntentFilter();
         filter.addAction(Intent.ACTION_USER_SWITCHED);
+        filter.addAction(Intent.ACTION_USER_ADDED);
         mContext.registerReceiver(mBroadcastReceiver, filter);
+
+        updateCurrentProfilesCache();
     }
 
     public void userSwitched(int newUserId) {
         // should be overridden
     }
 
-    public boolean notificationIsForCurrentUser(StatusBarNotification n) {
+    public boolean notificationIsForCurrentProfiles(StatusBarNotification n) {
         final int thisUserId = mCurrentUserId;
         final int notificationUserId = n.getUserId();
         if (DEBUG && MULTIUSER_DEBUG) {
             Log.v(TAG, String.format("%s: current userid: %d, notification userid: %d",
                     n, thisUserId, notificationUserId));
         }
-        return notificationUserId == UserHandle.USER_ALL
-                || thisUserId == notificationUserId;
+        synchronized (mCurrentProfiles) {
+            return notificationUserId == UserHandle.USER_ALL
+                    || mCurrentProfiles.get(notificationUserId) != null;
+        }
     }
 
     @Override
@@ -337,13 +399,14 @@ public abstract class BaseStatusBar extends SystemUI implements
             final String _pkg = n.getPackageName();
             final String _tag = n.getTag();
             final int _id = n.getId();
+            final int _userId = n.getUserId();
             vetoButton.setOnClickListener(new View.OnClickListener() {
                     public void onClick(View v) {
                         // Accessibility feedback
                         v.announceForAccessibility(
                                 mContext.getString(R.string.accessibility_notification_dismissed));
                         try {
-                            mBarService.onNotificationClear(_pkg, _tag, _id);
+                            mBarService.onNotificationClear(_pkg, _tag, _id, _userId);
 
                         } catch (RemoteException ex) {
                             // system process is dead if we're here.
@@ -359,9 +422,9 @@ public abstract class BaseStatusBar extends SystemUI implements
     }
 
 
-    protected void applyLegacyRowBackground(StatusBarNotification sbn, View content) {
-        if (sbn.getNotification().contentView.getLayoutId() !=
-                com.android.internal.R.layout.notification_template_base) {
+    protected void applyLegacyRowBackground(StatusBarNotification sbn,
+            NotificationData.Entry entry) {
+        if (entry.expanded.getId() != com.android.internal.R.id.status_bar_latest_event_content) {
             int version = 0;
             try {
                 ApplicationInfo info = mContext.getPackageManager().getApplicationInfo(sbn.getPackageName(), 0);
@@ -370,9 +433,11 @@ public abstract class BaseStatusBar extends SystemUI implements
                 Log.e(TAG, "Failed looking up ApplicationInfo for " + sbn.getPackageName(), ex);
             }
             if (version > 0 && version < Build.VERSION_CODES.GINGERBREAD) {
-                content.setBackgroundResource(R.drawable.notification_row_legacy_bg);
-            } else {
-                content.setBackgroundResource(com.android.internal.R.drawable.notification_bg);
+                entry.row.setBackgroundResource(R.drawable.notification_row_legacy_bg);
+            } else if (version < Build.VERSION_CODES.L) {
+                entry.row.setBackgroundResourceIds(
+                        com.android.internal.R.drawable.notification_bg,
+                        com.android.internal.R.drawable.notification_bg_dim);
             }
         }
     }
@@ -524,6 +589,7 @@ public abstract class BaseStatusBar extends SystemUI implements
 
     protected void toggleRecentsActivity() {
         if (mRecents != null) {
+            sendCloseSystemWindows(mContext, SYSTEM_DIALOG_REASON_RECENT_APPS);
             mRecents.toggleRecents(mDisplay, mLayoutDirection, getStatusBarView());
         }
     }
@@ -548,6 +614,37 @@ public abstract class BaseStatusBar extends SystemUI implements
 
     public abstract void resetHeadsUpDecayTimer();
 
+    /**
+     * Save the current "public" (locked and secure) state of the lockscreen.
+     */
+    public void setLockscreenPublicMode(boolean publicMode) {
+        mLockscreenPublicMode = publicMode;
+    }
+
+    public boolean isLockscreenPublicMode() {
+        return mLockscreenPublicMode;
+    }
+
+    /**
+     * Has the given user chosen to allow their private (full) notifications to be shown even
+     * when the lockscreen is in "public" (secure & locked) mode?
+     */
+    public boolean userAllowsPrivateNotificationsInPublic(int userHandle) {
+        if (userHandle == UserHandle.USER_ALL) {
+            return true;
+        }
+
+        if (mUsersAllowingPrivateNotifications.indexOfKey(userHandle) < 0) {
+            final boolean allowed = 0 != Settings.Secure.getIntForUser(
+                    mContext.getContentResolver(),
+                    Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0, userHandle);
+            mUsersAllowingPrivateNotifications.append(userHandle, allowed);
+            return allowed;
+        }
+
+        return mUsersAllowingPrivateNotifications.get(userHandle);
+    }
+
     protected class H extends Handler {
         public void handleMessage(Message m) {
             Intent intent;
@@ -614,6 +711,14 @@ public abstract class BaseStatusBar extends SystemUI implements
     }
 
     public boolean inflateViews(NotificationData.Entry entry, ViewGroup parent) {
+            return inflateViews(entry, parent, false);
+    }
+
+    public boolean inflateViewsForHeadsUp(NotificationData.Entry entry, ViewGroup parent) {
+            return inflateViews(entry, parent, true);
+    }
+
+    public boolean inflateViews(NotificationData.Entry entry, ViewGroup parent, boolean isHeadsUp) {
         int minHeight =
                 mContext.getResources().getDimensionPixelSize(R.dimen.notification_min_height);
         int maxHeight =
@@ -621,10 +726,22 @@ public abstract class BaseStatusBar extends SystemUI implements
         StatusBarNotification sbn = entry.notification;
         RemoteViews contentView = sbn.getNotification().contentView;
         RemoteViews bigContentView = sbn.getNotification().bigContentView;
+
+        if (isHeadsUp) {
+            maxHeight =
+                    mContext.getResources().getDimensionPixelSize(R.dimen.notification_mid_height);
+            bigContentView = sbn.getNotification().headsUpContentView;
+        }
+
         if (contentView == null) {
             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,26 +759,31 @@ 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);
 
         PendingIntent contentIntent = sbn.getNotification().contentIntent;
         if (contentIntent != null) {
-            final View.OnClickListener listener = new NotificationClicker(contentIntent,
-                    sbn.getPackageName(), sbn.getTag(), sbn.getId());
+            final View.OnClickListener listener = makeClicker(contentIntent,
+                    sbn.getPackageName(), sbn.getTag(), sbn.getId(), isHeadsUp, sbn.getUserId());
             content.setOnClickListener(listener);
         } else {
             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) {
@@ -671,41 +793,108 @@ public abstract class BaseStatusBar extends SystemUI implements
         }
 
         if (contentViewLocal != null) {
+            contentViewLocal.setIsRootNamespace(true);
             SizeAdaptiveLayout.LayoutParams params =
                     new SizeAdaptiveLayout.LayoutParams(contentViewLocal.getLayoutParams());
             params.minHeight = minHeight;
             params.maxHeight = minHeight;
-            adaptive.addView(contentViewLocal, params);
+            expanded.addView(contentViewLocal, params);
         }
         if (bigContentViewLocal != null) {
+            bigContentViewLocal.setIsRootNamespace(true);
             SizeAdaptiveLayout.LayoutParams params =
                     new SizeAdaptiveLayout.LayoutParams(bigContentViewLocal.getLayoutParams());
             params.minHeight = minHeight+1;
             params.maxHeight = maxHeight;
-            adaptive.addView(bigContentViewLocal, params);
+            expanded.addView(bigContentViewLocal, params);
         }
-        row.setDrawingCacheEnabled(true);
 
-        applyLegacyRowBackground(sbn, content);
+        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) {
+                    publicViewLocal.setIsRootNamespace(true);
+                    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_quantum_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);
+
+            Drawable iconDrawable = StatusBarIconView.getIcon(mContext, ic);
+            icon.setImageDrawable(iconDrawable);
+            if (mLegacyNotificationUtil.isGrayscale(iconDrawable)) {
+                icon.setBackgroundResource(
+                        com.android.internal.R.drawable.notification_icon_legacy_bg_inset);
+            }
+
+            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);
 
         if (MULTIUSER_DEBUG) {
             TextView debug = (TextView) row.findViewById(R.id.debug_info);
             if (debug != null) {
                 debug.setVisibility(View.VISIBLE);
-                debug.setText("U " + entry.notification.getUserId());
+                debug.setText("CU " + mCurrentUserId +" NU " + entry.notification.getUserId());
             }
         }
         entry.row = row;
-        entry.row.setRowHeight(mRowHeight);
+        entry.row.setHeightRange(mRowMinHeight, mRowMaxHeight);
+        entry.row.setOnActivatedListener(this);
         entry.content = content;
         entry.expanded = contentViewLocal;
+        entry.expandedPublic = publicViewLocal;
         entry.setBigContentView(bigContentViewLocal);
 
+        applyLegacyRowBackground(sbn, entry);
+
         return true;
     }
 
-    public NotificationClicker makeClicker(PendingIntent intent, String pkg, String tag, int id) {
-        return new NotificationClicker(intent, pkg, tag, id);
+    public NotificationClicker makeClicker(PendingIntent intent, String pkg, String tag,
+            int id, boolean forHun, int userId) {
+        return new NotificationClicker(intent, pkg, tag, id, forHun, userId);
     }
 
     protected class NotificationClicker implements View.OnClickListener {
@@ -713,12 +902,17 @@ public abstract class BaseStatusBar extends SystemUI implements
         private String mPkg;
         private String mTag;
         private int mId;
+        private boolean mIsHeadsUp;
+        private int mUserId;
 
-        public NotificationClicker(PendingIntent intent, String pkg, String tag, int id) {
+        public NotificationClicker(PendingIntent intent, String pkg, String tag, int id,
+                boolean forHun, int userId) {
             mIntent = intent;
             mPkg = pkg;
             mTag = tag;
             mId = id;
+            mIsHeadsUp = forHun;
+            mUserId = userId;
         }
 
         public void onClick(View v) {
@@ -751,7 +945,10 @@ public abstract class BaseStatusBar extends SystemUI implements
             }
 
             try {
-                mBarService.onNotificationClick(mPkg, mTag, mId);
+                if (mIsHeadsUp) {
+                    mHandler.sendEmptyMessage(MSG_HIDE_HEADS_UP);
+                }
+                mBarService.onNotificationClick(mPkg, mTag, mId, mUserId);
             } catch (RemoteException ex) {
                 // system process is dead if we're here.
             }
@@ -761,6 +958,7 @@ public abstract class BaseStatusBar extends SystemUI implements
             visibilityChanged(false);
         }
     }
+
     /**
      * The LEDs are turned o)ff when the notification panel is shown, even just a little bit.
      * This was added last-minute and is inconsistent with the way the rest of the notifications
@@ -772,7 +970,11 @@ public abstract class BaseStatusBar extends SystemUI implements
         if (mPanelSlightlyVisible != visible) {
             mPanelSlightlyVisible = visible;
             try {
-                mBarService.onPanelRevealed();
+                if (visible) {
+                    mBarService.onPanelRevealed();
+                } else {
+                    mBarService.onPanelHidden();
+                }
             } catch (RemoteException ex) {
                 // Won't fail unless the world has ended.
             }
@@ -788,7 +990,8 @@ public abstract class BaseStatusBar extends SystemUI implements
     void handleNotificationError(IBinder key, StatusBarNotification n, String message) {
         removeNotification(key);
         try {
-            mBarService.onNotificationError(n.getPackageName(), n.getTag(), n.getId(), n.getUid(), n.getInitialPid(), message);
+            mBarService.onNotificationError(n.getPackageName(), n.getTag(), n.getId(), n.getUid(),
+                    n.getInitialPid(), message, n.getUserId());
         } catch (RemoteException ex) {
             // The end is nigh.
         }
@@ -803,7 +1006,7 @@ public abstract class BaseStatusBar extends SystemUI implements
         // Remove the expanded view.
         ViewGroup rowParent = (ViewGroup)entry.row.getParent();
         if (rowParent != null) rowParent.removeView(entry.row);
-        updateExpansionStates();
+        updateRowStates();
         updateNotificationIcons();
 
         return entry.notification;
@@ -832,7 +1035,7 @@ public abstract class BaseStatusBar extends SystemUI implements
         }
         // Construct the expanded view.
         NotificationData.Entry entry = new NotificationData.Entry(key, notification, iconView);
-        if (!inflateViews(entry, mPile)) {
+        if (!inflateViews(entry, mStackScroller)) {
             handleNotificationError(key, notification, "Couldn't expand RemoteViews for: "
                     + notification);
             return null;
@@ -846,7 +1049,7 @@ public abstract class BaseStatusBar extends SystemUI implements
         if (DEBUG) {
             Log.d(TAG, "addNotificationViews: added at " + pos);
         }
-        updateExpansionStates();
+        updateRowStates();
         updateNotificationIcons();
     }
 
@@ -854,28 +1057,97 @@ public abstract class BaseStatusBar extends SystemUI implements
         addNotificationViews(createNotificationViews(key, notification));
     }
 
-    protected void updateExpansionStates() {
-        int N = mNotificationData.size();
-        for (int i = 0; i < N; i++) {
+    /**
+     * @return The number of notifications we show on Keyguard.
+     */
+    protected abstract int getMaxKeyguardNotifications();
+
+    /**
+     * Updates expanded, dimmed and locked states of notification rows.
+     */
+    protected void updateRowStates() {
+        int maxKeyguardNotifications = getMaxKeyguardNotifications();
+        mKeyguardIconOverflowContainer.getIconsView().removeAllViews();
+        int n = mNotificationData.size();
+        int visibleNotifications = 0;
+        for (int i = n-1; i >= 0; i--) {
+            NotificationData.Entry entry = mNotificationData.get(i);
+            if (mOnKeyguard) {
+                entry.row.setSystemExpanded(false);
+            } else {
+                if (!entry.row.isUserLocked()) {
+                    boolean top = (i == n-1);
+                    entry.row.setSystemExpanded(top || entry.row.isUserExpanded());
+                }
+            }
+            entry.row.setDimmed(mOnKeyguard);
+            entry.row.setLocked(mOnKeyguard);
+            boolean showOnKeyguard = shouldShowOnKeyguard(entry.notification);
+            if (mOnKeyguard && (visibleNotifications >= maxKeyguardNotifications
+                    || !showOnKeyguard)) {
+                entry.row.setVisibility(View.GONE);
+                if (showOnKeyguard) {
+                    mKeyguardIconOverflowContainer.getIconsView().addNotification(entry);
+                }
+            } else {
+                entry.row.setVisibility(View.VISIBLE);
+                visibleNotifications++;
+            }
+        }
+
+        if (mOnKeyguard && mKeyguardIconOverflowContainer.getIconsView().getChildCount() > 0) {
+            mKeyguardIconOverflowContainer.setVisibility(View.VISIBLE);
+        } else {
+            mKeyguardIconOverflowContainer.setVisibility(View.GONE);
+        }
+    }
+
+    @Override
+    public void onActivated(View view) {
+        int n = mNotificationData.size();
+        for (int i = 0; i < n; i++) {
             NotificationData.Entry entry = mNotificationData.get(i);
-            if (!entry.row.isUserLocked()) {
-                if (i == (N-1)) {
-                    if (DEBUG) Log.d(TAG, "expanding top notification at " + i);
-                    entry.row.setExpanded(true);
+            if (entry.row.getVisibility() != View.GONE) {
+                if (view == entry.row) {
+                    entry.row.getActivator().activate();
                 } else {
-                    if (!entry.row.isUserExpanded()) {
-                        if (DEBUG) Log.d(TAG, "collapsing notification at " + i);
-                        entry.row.setExpanded(false);
-                    } else {
-                        if (DEBUG) Log.d(TAG, "ignoring user-modified notification at " + i);
-                    }
+                    entry.row.getActivator().activateInverse();
                 }
+            }
+        }
+        if (mKeyguardIconOverflowContainer.getVisibility() != View.GONE) {
+            if (view == mKeyguardIconOverflowContainer) {
+                mKeyguardIconOverflowContainer.getActivator().activate();
             } else {
-                if (DEBUG) Log.d(TAG, "ignoring notification being held by user at " + i);
+                mKeyguardIconOverflowContainer.getActivator().activateInverse();
             }
         }
     }
 
+    @Override
+    public void onReset(View view) {
+        int n = mNotificationData.size();
+        for (int i = 0; i < n; i++) {
+            NotificationData.Entry entry = mNotificationData.get(i);
+            if (entry.row.getVisibility() != View.GONE) {
+                entry.row.getActivator().reset();
+            }
+        }
+        if (mKeyguardIconOverflowContainer.getVisibility() != View.GONE) {
+            mKeyguardIconOverflowContainer.getActivator().reset();
+        }
+    }
+
+    private boolean shouldShowOnKeyguard(StatusBarNotification sbn) {
+        return sbn.getNotification().priority >= Notification.PRIORITY_LOW;
+    }
+
+    protected void setZenMode(int mode) {
+        if (!isDeviceProvisioned()) return;
+        mZenMode = mode;
+        updateNotificationIcons();
+    }
+
     protected abstract void haltTicker();
     protected abstract void setAreThereNotifications();
     protected abstract void updateNotificationIcons();
@@ -904,6 +1176,14 @@ 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 RemoteViews oldHeadsUpContentView = oldNotification.getNotification().headsUpContentView;
+        final RemoteViews headsUpContentView = notification.getNotification().headsUpContentView;
+        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 +1191,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 +1217,24 @@ public abstract class BaseStatusBar extends SystemUI implements
                     && oldBigContentView.getPackage() != null
                     && oldBigContentView.getPackage().equals(bigContentView.getPackage())
                     && oldBigContentView.getLayoutId() == bigContentView.getLayoutId());
+        boolean headsUpContentsUnchanged =
+                (oldHeadsUpContentView == null && headsUpContentView == null)
+                || ((oldHeadsUpContentView != null && headsUpContentView != null)
+                    && headsUpContentView.getPackage() != null
+                    && oldHeadsUpContentView.getPackage() != null
+                    && oldHeadsUpContentView.getPackage().equals(headsUpContentView.getPackage())
+                    && oldHeadsUpContentView.getLayoutId() == headsUpContentView.getLayoutId());
+        boolean publicUnchanged  =
+                (oldPublicContentView == null && publicContentView == null)
+                || ((oldPublicContentView != null && publicContentView != null)
+                        && publicContentView.getPackage() != null
+                        && oldPublicContentView.getPackage() != null
+                        && oldPublicContentView.getPackage().equals(publicContentView.getPackage())
+                        && oldPublicContentView.getLayoutId() == publicContentView.getLayoutId());
+
         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 +1242,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 && headsUpContentsUnchanged && publicUnchanged
+                && (orderUnchanged || isTopAnyway)) {
             if (DEBUG) Log.d(TAG, "reusing notification for key: " + key);
             oldEntry.notification = notification;
             try {
@@ -958,7 +1257,7 @@ public abstract class BaseStatusBar extends SystemUI implements
                     } else {
                         if (DEBUG) Log.d(TAG, "updating the current heads up:" + notification);
                         mInterruptingNotificationEntry.notification = notification;
-                        updateNotificationViews(mInterruptingNotificationEntry, notification);
+                        updateHeadsUpViews(mInterruptingNotificationEntry, notification);
                     }
                 }
 
@@ -972,7 +1271,7 @@ public abstract class BaseStatusBar extends SystemUI implements
                     handleNotificationError(key, notification, "Couldn't update icon: " + ic);
                     return;
                 }
-                updateExpansionStates();
+                updateRowStates();
             }
             catch (RuntimeException e) {
                 // It failed to add cleanly.  Log, and remove the view from the panel.
@@ -985,13 +1284,14 @@ public abstract class BaseStatusBar extends SystemUI implements
             if (DEBUG) Log.d(TAG, "contents was " + (contentsUnchanged ? "unchanged" : "changed"));
             if (DEBUG) Log.d(TAG, "order was " + (orderUnchanged ? "unchanged" : "changed"));
             if (DEBUG) Log.d(TAG, "notification is " + (isTopAnyway ? "top" : "not top"));
-            final boolean wasExpanded = oldEntry.row.isUserExpanded();
             removeNotificationViews(key);
             addNotificationViews(key, notification);  // will also replace the heads up
-            if (wasExpanded) {
-                final NotificationData.Entry newEntry = mNotificationData.findByKey(key);
-                newEntry.row.setExpanded(true);
-                newEntry.row.setUserExpanded(true);
+            final NotificationData.Entry newEntry = mNotificationData.findByKey(key);
+            final boolean userChangedExpansion = oldEntry.row.hasUserChangedExpansion();
+            if (userChangedExpansion) {
+                boolean userExpanded = oldEntry.row.isUserExpanded();
+                newEntry.row.applyExpansionToLayout(userExpanded);
+                newEntry.row.setUserExpanded(userExpanded);
             }
         }
 
@@ -1000,7 +1300,7 @@ public abstract class BaseStatusBar extends SystemUI implements
         updateNotificationVetoButton(oldEntry.row, notification);
 
         // Is this for you?
-        boolean isForCurrentUser = notificationIsForCurrentUser(notification);
+        boolean isForCurrentUser = notificationIsForCurrentProfiles(notification);
         if (DEBUG) Log.d(TAG, "notification is " + (isForCurrentUser ? "" : "not ") + "for you");
 
         // Restart the ticker if it's still running
@@ -1016,18 +1316,39 @@ public abstract class BaseStatusBar extends SystemUI implements
 
     private void updateNotificationViews(NotificationData.Entry entry,
             StatusBarNotification notification) {
+        updateNotificationViews(entry, notification, false);
+    }
+
+    private void updateHeadsUpViews(NotificationData.Entry entry,
+            StatusBarNotification notification) {
+        updateNotificationViews(entry, notification, true);
+    }
+
+    private void updateNotificationViews(NotificationData.Entry entry,
+            StatusBarNotification notification, boolean isHeadsUp) {
         final RemoteViews contentView = notification.getNotification().contentView;
-        final RemoteViews bigContentView = notification.getNotification().bigContentView;
+        final RemoteViews bigContentView = isHeadsUp
+                ? notification.getNotification().headsUpContentView
+                : notification.getNotification().bigContentView;
+        final Notification publicVersion = notification.getNotification().publicVersion;
+        final RemoteViews publicContentView = publicVersion != null ? publicVersion.contentView
+                : null;
+
         // Reapply the RemoteViews
         contentView.reapply(mContext, entry.expanded, mOnClickHandler);
         if (bigContentView != null && entry.getBigContentView() != null) {
-            bigContentView.reapply(mContext, entry.getBigContentView(), mOnClickHandler);
+            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) {
             final View.OnClickListener listener = makeClicker(contentIntent,
-                    notification.getPackageName(), notification.getTag(), notification.getId());
+                    notification.getPackageName(), notification.getTag(), notification.getId(),
+                    isHeadsUp, notification.getUserId());
             entry.content.setOnClickListener(listener);
         } else {
             entry.content.setOnClickListener(null);
@@ -1049,14 +1370,15 @@ public abstract class BaseStatusBar extends SystemUI implements
                 || notification.vibrate != null;
         boolean isHighPriority = sbn.getScore() >= INTERRUPTION_THRESHOLD;
         boolean isFullscreen = notification.fullScreenIntent != null;
+        boolean hasTicker = mHeadsUpTicker && !TextUtils.isEmpty(notification.tickerText);
         boolean isAllowed = notification.extras.getInt(Notification.EXTRA_AS_HEADS_UP,
                 Notification.HEADS_UP_ALLOWED) != Notification.HEADS_UP_NEVER;
 
         final KeyguardTouchDelegate keyguard = KeyguardTouchDelegate.getInstance(mContext);
-        boolean interrupt = (isFullscreen || (isHighPriority && isNoisy))
+        boolean interrupt = (isFullscreen || (isHighPriority && (isNoisy || hasTicker)))
                 && isAllowed
                 && mPowerManager.isScreenOn()
-                && !keyguard.isShowingAndNotHidden()
+                && !keyguard.isShowingAndNotOccluded()
                 && !keyguard.isInputRestricted();
         try {
             interrupt = interrupt && !mDreamManager.isDreaming();