BaseStatusBar.java revision 572bbd42a473980c2d59af80d378f6270ba6860a
1/*
2 * Copyright (C) 2010 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.systemui.statusbar;
18
19import android.app.ActivityManager;
20import android.app.ActivityManagerNative;
21import android.app.Notification;
22import android.app.PendingIntent;
23import android.app.TaskStackBuilder;
24import android.content.BroadcastReceiver;
25import android.content.Context;
26import android.content.Intent;
27import android.content.IntentFilter;
28import android.content.pm.ApplicationInfo;
29import android.content.pm.PackageManager;
30import android.content.pm.PackageManager.NameNotFoundException;
31import android.content.pm.UserInfo;
32import android.content.res.Configuration;
33import android.database.ContentObserver;
34import android.graphics.Rect;
35import android.graphics.drawable.Drawable;
36import android.net.Uri;
37import android.os.Build;
38import android.os.Handler;
39import android.os.IBinder;
40import android.os.Message;
41import android.os.PowerManager;
42import android.os.RemoteException;
43import android.os.ServiceManager;
44import android.os.UserHandle;
45import android.os.UserManager;
46import android.provider.Settings;
47import android.service.dreams.DreamService;
48import android.service.dreams.IDreamManager;
49import android.service.notification.StatusBarNotification;
50import android.text.TextUtils;
51import android.util.Log;
52import android.util.SparseArray;
53import android.util.SparseBooleanArray;
54import android.view.Display;
55import android.view.IWindowManager;
56import android.view.LayoutInflater;
57import android.view.MenuItem;
58import android.view.MotionEvent;
59import android.view.View;
60import android.view.ViewGroup;
61import android.view.ViewGroup.LayoutParams;
62import android.view.WindowManager;
63import android.view.WindowManagerGlobal;
64import android.widget.ImageView;
65import android.widget.LinearLayout;
66import android.widget.PopupMenu;
67import android.widget.RemoteViews;
68import android.widget.TextView;
69
70import com.android.internal.statusbar.IStatusBarService;
71import com.android.internal.statusbar.StatusBarIcon;
72import com.android.internal.statusbar.StatusBarIconList;
73import com.android.internal.util.LegacyNotificationUtil;
74import com.android.systemui.R;
75import com.android.systemui.RecentsComponent;
76import com.android.systemui.SearchPanelView;
77import com.android.systemui.SystemUI;
78import com.android.systemui.statusbar.phone.KeyguardTouchDelegate;
79import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
80
81import java.util.ArrayList;
82import java.util.Locale;
83
84public abstract class BaseStatusBar extends SystemUI implements
85        CommandQueue.Callbacks, ActivatableNotificationView.OnActivatedListener {
86    public static final String TAG = "StatusBar";
87    public static final boolean DEBUG = false;
88    public static final boolean MULTIUSER_DEBUG = false;
89
90    protected static final int MSG_TOGGLE_RECENTS_PANEL = 1020;
91    protected static final int MSG_CLOSE_RECENTS_PANEL = 1021;
92    protected static final int MSG_PRELOAD_RECENT_APPS = 1022;
93    protected static final int MSG_CANCEL_PRELOAD_RECENT_APPS = 1023;
94    protected static final int MSG_OPEN_SEARCH_PANEL = 1024;
95    protected static final int MSG_CLOSE_SEARCH_PANEL = 1025;
96    protected static final int MSG_SHOW_HEADS_UP = 1026;
97    protected static final int MSG_HIDE_HEADS_UP = 1027;
98    protected static final int MSG_ESCALATE_HEADS_UP = 1028;
99
100    protected static final boolean ENABLE_HEADS_UP = true;
101    // scores above this threshold should be displayed in heads up mode.
102    protected static final int INTERRUPTION_THRESHOLD = 10;
103    protected static final String SETTING_HEADS_UP_TICKER = "ticker_gets_heads_up";
104
105    // Should match the value in PhoneWindowManager
106    public static final String SYSTEM_DIALOG_REASON_RECENT_APPS = "recentapps";
107
108    public static final int EXPANDED_LEAVE_ALONE = -10000;
109    public static final int EXPANDED_FULL_OPEN = -10001;
110
111    protected CommandQueue mCommandQueue;
112    protected IStatusBarService mBarService;
113    protected H mHandler = createHandler();
114
115    // all notifications
116    protected NotificationData mNotificationData = new NotificationData();
117    protected NotificationStackScrollLayout mStackScroller;
118
119    protected NotificationData.Entry mInterruptingNotificationEntry;
120    protected long mInterruptingNotificationTime;
121
122    // used to notify status bar for suppressing notification LED
123    protected boolean mPanelSlightlyVisible;
124
125    // Search panel
126    protected SearchPanelView mSearchPanelView;
127
128    protected PopupMenu mNotificationBlamePopup;
129
130    protected int mCurrentUserId = 0;
131    final protected SparseArray<UserInfo> mCurrentProfiles = new SparseArray<UserInfo>();
132
133    protected int mLayoutDirection = -1; // invalid
134    private Locale mLocale;
135    protected boolean mUseHeadsUp = false;
136    protected boolean mHeadsUpTicker = false;
137
138    protected IDreamManager mDreamManager;
139    PowerManager mPowerManager;
140    protected int mRowMinHeight;
141    protected int mRowMaxHeight;
142
143    // public mode, private notifications, etc
144    private boolean mLockscreenPublicMode = false;
145    private final SparseBooleanArray mUsersAllowingPrivateNotifications = new SparseBooleanArray();
146    private LegacyNotificationUtil mLegacyNotificationUtil = LegacyNotificationUtil.getInstance();
147
148    private UserManager mUserManager;
149
150    // UI-specific methods
151
152    /**
153     * Create all windows necessary for the status bar (including navigation, overlay panels, etc)
154     * and add them to the window manager.
155     */
156    protected abstract void createAndAddWindows();
157
158    protected WindowManager mWindowManager;
159    protected IWindowManager mWindowManagerService;
160    protected abstract void refreshLayout(int layoutDirection);
161
162    protected Display mDisplay;
163
164    private boolean mDeviceProvisioned = false;
165
166    private RecentsComponent mRecents;
167
168    protected int mZenMode;
169
170    protected boolean mOnKeyguard;
171    protected NotificationOverflowContainer mKeyguardIconOverflowContainer;
172
173    public boolean isDeviceProvisioned() {
174        return mDeviceProvisioned;
175    }
176
177    protected final ContentObserver mSettingsObserver = new ContentObserver(mHandler) {
178        @Override
179        public void onChange(boolean selfChange) {
180            final boolean provisioned = 0 != Settings.Global.getInt(
181                    mContext.getContentResolver(), Settings.Global.DEVICE_PROVISIONED, 0);
182            if (provisioned != mDeviceProvisioned) {
183                mDeviceProvisioned = provisioned;
184                updateNotificationIcons();
185            }
186            final int mode = Settings.Global.getInt(mContext.getContentResolver(),
187                    Settings.Global.ZEN_MODE, Settings.Global.ZEN_MODE_OFF);
188            setZenMode(mode);
189        }
190    };
191
192    private final ContentObserver mLockscreenSettingsObserver = new ContentObserver(mHandler) {
193        @Override
194        public void onChange(boolean selfChange) {
195            // We don't know which user changed LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS,
196            // so we just dump our cache ...
197            mUsersAllowingPrivateNotifications.clear();
198            // ... and refresh all the notifications
199            updateNotificationIcons();
200        }
201    };
202
203    private RemoteViews.OnClickHandler mOnClickHandler = new RemoteViews.OnClickHandler() {
204        @Override
205        public boolean onClickHandler(View view, PendingIntent pendingIntent, Intent fillInIntent) {
206            if (DEBUG) {
207                Log.v(TAG, "Notification click handler invoked for intent: " + pendingIntent);
208            }
209            final boolean isActivity = pendingIntent.isActivity();
210            if (isActivity) {
211                try {
212                    // The intent we are sending is for the application, which
213                    // won't have permission to immediately start an activity after
214                    // the user switches to home.  We know it is safe to do at this
215                    // point, so make sure new activity switches are now allowed.
216                    ActivityManagerNative.getDefault().resumeAppSwitches();
217                    // Also, notifications can be launched from the lock screen,
218                    // so dismiss the lock screen when the activity starts.
219                    ActivityManagerNative.getDefault().dismissKeyguardOnNextActivity();
220                } catch (RemoteException e) {
221                }
222            }
223
224            boolean handled = super.onClickHandler(view, pendingIntent, fillInIntent);
225
226            if (isActivity && handled) {
227                // close the shade if it was open
228                animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE);
229                visibilityChanged(false);
230            }
231            return handled;
232        }
233    };
234
235    private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
236        @Override
237        public void onReceive(Context context, Intent intent) {
238            String action = intent.getAction();
239            if (Intent.ACTION_USER_SWITCHED.equals(action)) {
240                mCurrentUserId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
241                updateCurrentProfilesCache();
242                if (true) Log.v(TAG, "userId " + mCurrentUserId + " is in the house");
243                userSwitched(mCurrentUserId);
244            } else if (Intent.ACTION_USER_ADDED.equals(action)) {
245                updateCurrentProfilesCache();
246            }
247        }
248    };
249
250    private void updateCurrentProfilesCache() {
251        synchronized (mCurrentProfiles) {
252            mCurrentProfiles.clear();
253            if (mUserManager != null) {
254                for (UserInfo user : mUserManager.getProfiles(mCurrentUserId)) {
255                    mCurrentProfiles.put(user.id, user);
256                }
257            }
258        }
259    }
260
261    public void start() {
262        mWindowManager = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
263        mWindowManagerService = WindowManagerGlobal.getWindowManagerService();
264        mDisplay = mWindowManager.getDefaultDisplay();
265
266        mDreamManager = IDreamManager.Stub.asInterface(
267                ServiceManager.checkService(DreamService.DREAM_SERVICE));
268        mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
269
270        mSettingsObserver.onChange(false); // set up
271        mContext.getContentResolver().registerContentObserver(
272                Settings.Global.getUriFor(Settings.Global.DEVICE_PROVISIONED), true,
273                mSettingsObserver);
274        mContext.getContentResolver().registerContentObserver(
275                Settings.Global.getUriFor(Settings.Global.ZEN_MODE), false,
276                mSettingsObserver);
277
278        mContext.getContentResolver().registerContentObserver(
279                Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS),
280                true,
281                mLockscreenSettingsObserver,
282                UserHandle.USER_ALL);
283
284        mBarService = IStatusBarService.Stub.asInterface(
285                ServiceManager.getService(Context.STATUS_BAR_SERVICE));
286
287        mRecents = getComponent(RecentsComponent.class);
288
289        mLocale = mContext.getResources().getConfiguration().locale;
290        mLayoutDirection = TextUtils.getLayoutDirectionFromLocale(mLocale);
291
292        mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
293
294        // Connect in to the status bar manager service
295        StatusBarIconList iconList = new StatusBarIconList();
296        ArrayList<IBinder> notificationKeys = new ArrayList<IBinder>();
297        ArrayList<StatusBarNotification> notifications = new ArrayList<StatusBarNotification>();
298        mCommandQueue = new CommandQueue(this, iconList);
299
300        int[] switches = new int[7];
301        ArrayList<IBinder> binders = new ArrayList<IBinder>();
302        try {
303            mBarService.registerStatusBar(mCommandQueue, iconList, notificationKeys, notifications,
304                    switches, binders);
305        } catch (RemoteException ex) {
306            // If the system process isn't there we're doomed anyway.
307        }
308
309        createAndAddWindows();
310
311        disable(switches[0]);
312        setSystemUiVisibility(switches[1], 0xffffffff);
313        topAppWindowChanged(switches[2] != 0);
314        // StatusBarManagerService has a back up of IME token and it's restored here.
315        setImeWindowStatus(binders.get(0), switches[3], switches[4]);
316        setHardKeyboardStatus(switches[5] != 0, switches[6] != 0);
317
318        // Set up the initial icon state
319        int N = iconList.size();
320        int viewIndex = 0;
321        for (int i=0; i<N; i++) {
322            StatusBarIcon icon = iconList.getIcon(i);
323            if (icon != null) {
324                addIcon(iconList.getSlot(i), i, viewIndex, icon);
325                viewIndex++;
326            }
327        }
328
329        // Set up the initial notification state
330        N = notificationKeys.size();
331        if (N == notifications.size()) {
332            for (int i=0; i<N; i++) {
333                addNotification(notificationKeys.get(i), notifications.get(i));
334            }
335        } else {
336            Log.wtf(TAG, "Notification list length mismatch: keys=" + N
337                    + " notifications=" + notifications.size());
338        }
339
340        if (DEBUG) {
341            Log.d(TAG, String.format(
342                    "init: icons=%d disabled=0x%08x lights=0x%08x menu=0x%08x imeButton=0x%08x",
343                   iconList.size(),
344                   switches[0],
345                   switches[1],
346                   switches[2],
347                   switches[3]
348                   ));
349        }
350
351        mCurrentUserId = ActivityManager.getCurrentUser();
352
353        IntentFilter filter = new IntentFilter();
354        filter.addAction(Intent.ACTION_USER_SWITCHED);
355        filter.addAction(Intent.ACTION_USER_ADDED);
356        mContext.registerReceiver(mBroadcastReceiver, filter);
357
358        updateCurrentProfilesCache();
359    }
360
361    public void userSwitched(int newUserId) {
362        // should be overridden
363    }
364
365    public boolean notificationIsForCurrentProfiles(StatusBarNotification n) {
366        final int thisUserId = mCurrentUserId;
367        final int notificationUserId = n.getUserId();
368        if (DEBUG && MULTIUSER_DEBUG) {
369            Log.v(TAG, String.format("%s: current userid: %d, notification userid: %d",
370                    n, thisUserId, notificationUserId));
371        }
372        synchronized (mCurrentProfiles) {
373            return notificationUserId == UserHandle.USER_ALL
374                    || mCurrentProfiles.get(notificationUserId) != null;
375        }
376    }
377
378    @Override
379    protected void onConfigurationChanged(Configuration newConfig) {
380        final Locale locale = mContext.getResources().getConfiguration().locale;
381        final int ld = TextUtils.getLayoutDirectionFromLocale(locale);
382        if (! locale.equals(mLocale) || ld != mLayoutDirection) {
383            if (DEBUG) {
384                Log.v(TAG, String.format(
385                        "config changed locale/LD: %s (%d) -> %s (%d)", mLocale, mLayoutDirection,
386                        locale, ld));
387            }
388            mLocale = locale;
389            mLayoutDirection = ld;
390            refreshLayout(ld);
391        }
392    }
393
394    protected View updateNotificationVetoButton(View row, StatusBarNotification n) {
395        View vetoButton = row.findViewById(R.id.veto);
396        if (n.isClearable() || (mInterruptingNotificationEntry != null
397                && mInterruptingNotificationEntry.row == row)) {
398            final String _pkg = n.getPackageName();
399            final String _tag = n.getTag();
400            final int _id = n.getId();
401            final int _userId = n.getUserId();
402            vetoButton.setOnClickListener(new View.OnClickListener() {
403                    public void onClick(View v) {
404                        // Accessibility feedback
405                        v.announceForAccessibility(
406                                mContext.getString(R.string.accessibility_notification_dismissed));
407                        try {
408                            mBarService.onNotificationClear(_pkg, _tag, _id, _userId);
409
410                        } catch (RemoteException ex) {
411                            // system process is dead if we're here.
412                        }
413                    }
414                });
415            vetoButton.setVisibility(View.VISIBLE);
416        } else {
417            vetoButton.setVisibility(View.GONE);
418        }
419        vetoButton.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
420        return vetoButton;
421    }
422
423
424    protected void applyLegacyRowBackground(StatusBarNotification sbn,
425            NotificationData.Entry entry) {
426        if (entry.expanded.getId() != com.android.internal.R.id.status_bar_latest_event_content) {
427            int version = 0;
428            try {
429                ApplicationInfo info = mContext.getPackageManager().getApplicationInfo(sbn.getPackageName(), 0);
430                version = info.targetSdkVersion;
431            } catch (NameNotFoundException ex) {
432                Log.e(TAG, "Failed looking up ApplicationInfo for " + sbn.getPackageName(), ex);
433            }
434            if (version > 0 && version < Build.VERSION_CODES.GINGERBREAD) {
435                entry.row.setBackgroundResource(R.drawable.notification_row_legacy_bg);
436            } else if (version < Build.VERSION_CODES.L) {
437                entry.row.setBackgroundResourceIds(
438                        com.android.internal.R.drawable.notification_bg,
439                        com.android.internal.R.drawable.notification_bg_dim);
440            }
441        }
442    }
443
444    private void startApplicationDetailsActivity(String packageName) {
445        Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
446                Uri.fromParts("package", packageName, null));
447        intent.setComponent(intent.resolveActivity(mContext.getPackageManager()));
448        TaskStackBuilder.create(mContext).addNextIntentWithParentStack(intent).startActivities(
449                null, UserHandle.CURRENT);
450    }
451
452    protected View.OnLongClickListener getNotificationLongClicker() {
453        return new View.OnLongClickListener() {
454            @Override
455            public boolean onLongClick(View v) {
456                final String packageNameF = (String) v.getTag();
457                if (packageNameF == null) return false;
458                if (v.getWindowToken() == null) return false;
459                mNotificationBlamePopup = new PopupMenu(mContext, v);
460                mNotificationBlamePopup.getMenuInflater().inflate(
461                        R.menu.notification_popup_menu,
462                        mNotificationBlamePopup.getMenu());
463                mNotificationBlamePopup.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
464                    public boolean onMenuItemClick(MenuItem item) {
465                        if (item.getItemId() == R.id.notification_inspect_item) {
466                            startApplicationDetailsActivity(packageNameF);
467                            animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE);
468                        } else {
469                            return false;
470                        }
471                        return true;
472                    }
473                });
474                mNotificationBlamePopup.show();
475
476                return true;
477            }
478        };
479    }
480
481    public void dismissPopups() {
482        if (mNotificationBlamePopup != null) {
483            mNotificationBlamePopup.dismiss();
484            mNotificationBlamePopup = null;
485        }
486    }
487
488    public void onHeadsUpDismissed() {
489    }
490
491    @Override
492    public void toggleRecentApps() {
493        int msg = MSG_TOGGLE_RECENTS_PANEL;
494        mHandler.removeMessages(msg);
495        mHandler.sendEmptyMessage(msg);
496    }
497
498    @Override
499    public void preloadRecentApps() {
500        int msg = MSG_PRELOAD_RECENT_APPS;
501        mHandler.removeMessages(msg);
502        mHandler.sendEmptyMessage(msg);
503    }
504
505    @Override
506    public void cancelPreloadRecentApps() {
507        int msg = MSG_CANCEL_PRELOAD_RECENT_APPS;
508        mHandler.removeMessages(msg);
509        mHandler.sendEmptyMessage(msg);
510    }
511
512    @Override
513    public void showSearchPanel() {
514        int msg = MSG_OPEN_SEARCH_PANEL;
515        mHandler.removeMessages(msg);
516        mHandler.sendEmptyMessage(msg);
517    }
518
519    @Override
520    public void hideSearchPanel() {
521        int msg = MSG_CLOSE_SEARCH_PANEL;
522        mHandler.removeMessages(msg);
523        mHandler.sendEmptyMessage(msg);
524    }
525
526    protected abstract WindowManager.LayoutParams getSearchLayoutParams(
527            LayoutParams layoutParams);
528
529    protected void updateSearchPanel() {
530        // Search Panel
531        boolean visible = false;
532        if (mSearchPanelView != null) {
533            visible = mSearchPanelView.isShowing();
534            mWindowManager.removeView(mSearchPanelView);
535        }
536
537        // Provide SearchPanel with a temporary parent to allow layout params to work.
538        LinearLayout tmpRoot = new LinearLayout(mContext);
539        mSearchPanelView = (SearchPanelView) LayoutInflater.from(mContext).inflate(
540                 R.layout.status_bar_search_panel, tmpRoot, false);
541        mSearchPanelView.setOnTouchListener(
542                 new TouchOutsideListener(MSG_CLOSE_SEARCH_PANEL, mSearchPanelView));
543        mSearchPanelView.setVisibility(View.GONE);
544
545        WindowManager.LayoutParams lp = getSearchLayoutParams(mSearchPanelView.getLayoutParams());
546
547        mWindowManager.addView(mSearchPanelView, lp);
548        mSearchPanelView.setBar(this);
549        if (visible) {
550            mSearchPanelView.show(true, false);
551        }
552    }
553
554    protected H createHandler() {
555         return new H();
556    }
557
558    static void sendCloseSystemWindows(Context context, String reason) {
559        if (ActivityManagerNative.isSystemReady()) {
560            try {
561                ActivityManagerNative.getDefault().closeSystemDialogs(reason);
562            } catch (RemoteException e) {
563            }
564        }
565    }
566
567    protected abstract View getStatusBarView();
568
569    protected View.OnTouchListener mRecentsPreloadOnTouchListener = new View.OnTouchListener() {
570        // additional optimization when we have software system buttons - start loading the recent
571        // tasks on touch down
572        @Override
573        public boolean onTouch(View v, MotionEvent event) {
574            int action = event.getAction() & MotionEvent.ACTION_MASK;
575            if (action == MotionEvent.ACTION_DOWN) {
576                preloadRecentTasksList();
577            } else if (action == MotionEvent.ACTION_CANCEL) {
578                cancelPreloadingRecentTasksList();
579            } else if (action == MotionEvent.ACTION_UP) {
580                if (!v.isPressed()) {
581                    cancelPreloadingRecentTasksList();
582                }
583
584            }
585            return false;
586        }
587    };
588
589    protected void toggleRecentsActivity() {
590        if (mRecents != null) {
591            sendCloseSystemWindows(mContext, SYSTEM_DIALOG_REASON_RECENT_APPS);
592            mRecents.toggleRecents(mDisplay, mLayoutDirection, getStatusBarView());
593        }
594    }
595
596    protected void preloadRecentTasksList() {
597        if (mRecents != null) {
598            mRecents.preloadRecentTasksList();
599        }
600    }
601
602    protected void cancelPreloadingRecentTasksList() {
603        if (mRecents != null) {
604            mRecents.cancelPreloadingRecentTasksList();
605        }
606    }
607
608    protected void closeRecents() {
609        if (mRecents != null) {
610            mRecents.closeRecents();
611        }
612    }
613
614    public abstract void resetHeadsUpDecayTimer();
615
616    /**
617     * Save the current "public" (locked and secure) state of the lockscreen.
618     */
619    public void setLockscreenPublicMode(boolean publicMode) {
620        mLockscreenPublicMode = publicMode;
621    }
622
623    public boolean isLockscreenPublicMode() {
624        return mLockscreenPublicMode;
625    }
626
627    /**
628     * Has the given user chosen to allow their private (full) notifications to be shown even
629     * when the lockscreen is in "public" (secure & locked) mode?
630     */
631    public boolean userAllowsPrivateNotificationsInPublic(int userHandle) {
632        if (userHandle == UserHandle.USER_ALL) {
633            return true;
634        }
635
636        if (mUsersAllowingPrivateNotifications.indexOfKey(userHandle) < 0) {
637            final boolean allowed = 0 != Settings.Secure.getIntForUser(
638                    mContext.getContentResolver(),
639                    Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0, userHandle);
640            mUsersAllowingPrivateNotifications.append(userHandle, allowed);
641            return allowed;
642        }
643
644        return mUsersAllowingPrivateNotifications.get(userHandle);
645    }
646
647    protected class H extends Handler {
648        public void handleMessage(Message m) {
649            Intent intent;
650            switch (m.what) {
651             case MSG_TOGGLE_RECENTS_PANEL:
652                 toggleRecentsActivity();
653                 break;
654             case MSG_CLOSE_RECENTS_PANEL:
655                 closeRecents();
656                 break;
657             case MSG_PRELOAD_RECENT_APPS:
658                  preloadRecentTasksList();
659                  break;
660             case MSG_CANCEL_PRELOAD_RECENT_APPS:
661                  cancelPreloadingRecentTasksList();
662                  break;
663             case MSG_OPEN_SEARCH_PANEL:
664                 if (DEBUG) Log.d(TAG, "opening search panel");
665                 if (mSearchPanelView != null && mSearchPanelView.isAssistantAvailable()) {
666                     mSearchPanelView.show(true, true);
667                     onShowSearchPanel();
668                 }
669                 break;
670             case MSG_CLOSE_SEARCH_PANEL:
671                 if (DEBUG) Log.d(TAG, "closing search panel");
672                 if (mSearchPanelView != null && mSearchPanelView.isShowing()) {
673                     mSearchPanelView.show(false, true);
674                     onHideSearchPanel();
675                 }
676                 break;
677            }
678        }
679    }
680
681    public class TouchOutsideListener implements View.OnTouchListener {
682        private int mMsg;
683        private StatusBarPanel mPanel;
684
685        public TouchOutsideListener(int msg, StatusBarPanel panel) {
686            mMsg = msg;
687            mPanel = panel;
688        }
689
690        public boolean onTouch(View v, MotionEvent ev) {
691            final int action = ev.getAction();
692            if (action == MotionEvent.ACTION_OUTSIDE
693                || (action == MotionEvent.ACTION_DOWN
694                    && !mPanel.isInContentArea((int)ev.getX(), (int)ev.getY()))) {
695                mHandler.removeMessages(mMsg);
696                mHandler.sendEmptyMessage(mMsg);
697                return true;
698            }
699            return false;
700        }
701    }
702
703    protected void workAroundBadLayerDrawableOpacity(View v) {
704    }
705
706    protected void onHideSearchPanel() {
707    }
708
709    protected void onShowSearchPanel() {
710    }
711
712    public boolean inflateViews(NotificationData.Entry entry, ViewGroup parent) {
713            return inflateViews(entry, parent, false);
714    }
715
716    public boolean inflateViewsForHeadsUp(NotificationData.Entry entry, ViewGroup parent) {
717            return inflateViews(entry, parent, true);
718    }
719
720    public boolean inflateViews(NotificationData.Entry entry, ViewGroup parent, boolean isHeadsUp) {
721        int minHeight =
722                mContext.getResources().getDimensionPixelSize(R.dimen.notification_min_height);
723        int maxHeight =
724                mContext.getResources().getDimensionPixelSize(R.dimen.notification_max_height);
725        StatusBarNotification sbn = entry.notification;
726        RemoteViews contentView = sbn.getNotification().contentView;
727        RemoteViews bigContentView = sbn.getNotification().bigContentView;
728
729        if (isHeadsUp) {
730            maxHeight =
731                    mContext.getResources().getDimensionPixelSize(R.dimen.notification_mid_height);
732            bigContentView = sbn.getNotification().headsUpContentView;
733        }
734
735        if (contentView == null) {
736            return false;
737        }
738
739        if (DEBUG) {
740            Log.v(TAG, "publicNotification: " + sbn.getNotification().publicVersion);
741        }
742
743        Notification publicNotification = sbn.getNotification().publicVersion;
744
745        // create the row view
746        LayoutInflater inflater = (LayoutInflater)mContext.getSystemService(
747                Context.LAYOUT_INFLATER_SERVICE);
748        ExpandableNotificationRow row = (ExpandableNotificationRow) inflater.inflate(
749                R.layout.status_bar_notification_row, parent, false);
750
751        // for blaming (see SwipeHelper.setLongPressListener)
752        row.setTag(sbn.getPackageName());
753
754        workAroundBadLayerDrawableOpacity(row);
755        View vetoButton = updateNotificationVetoButton(row, sbn);
756        vetoButton.setContentDescription(mContext.getString(
757                R.string.accessibility_remove_notification));
758
759        // NB: the large icon is now handled entirely by the template
760
761        // bind the click event to the content area
762        NotificationContentView expanded =
763                (NotificationContentView) row.findViewById(R.id.expanded);
764        NotificationContentView expandedPublic =
765                (NotificationContentView) row.findViewById(R.id.expandedPublic);
766
767        row.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
768
769        PendingIntent contentIntent = sbn.getNotification().contentIntent;
770        if (contentIntent != null) {
771            final View.OnClickListener listener = makeClicker(contentIntent,
772                    sbn.getPackageName(), sbn.getTag(), sbn.getId(), isHeadsUp, sbn.getUserId());
773            row.setOnClickListener(listener);
774        } else {
775            row.setOnClickListener(null);
776        }
777
778        // set up the adaptive layout
779        View contentViewLocal = null;
780        View bigContentViewLocal = null;
781        try {
782            contentViewLocal = contentView.apply(mContext, expanded,
783                    mOnClickHandler);
784            if (bigContentView != null) {
785                bigContentViewLocal = bigContentView.apply(mContext, expanded,
786                        mOnClickHandler);
787            }
788        }
789        catch (RuntimeException e) {
790            final String ident = sbn.getPackageName() + "/0x" + Integer.toHexString(sbn.getId());
791            Log.e(TAG, "couldn't inflate view for notification " + ident, e);
792            return false;
793        }
794
795        if (contentViewLocal != null) {
796            contentViewLocal.setIsRootNamespace(true);
797            expanded.setContractedChild(contentViewLocal);
798        }
799        if (bigContentViewLocal != null) {
800            bigContentViewLocal.setIsRootNamespace(true);
801            expanded.setExpandedChild(bigContentViewLocal);
802        }
803
804        PackageManager pm = mContext.getPackageManager();
805
806        // now the public version
807        View publicViewLocal = null;
808        if (publicNotification != null) {
809            try {
810                publicViewLocal = publicNotification.contentView.apply(mContext, expandedPublic,
811                        mOnClickHandler);
812
813                if (publicViewLocal != null) {
814                    publicViewLocal.setIsRootNamespace(true);
815                    expandedPublic.setContractedChild(publicViewLocal);
816                }
817            }
818            catch (RuntimeException e) {
819                final String ident = sbn.getPackageName() + "/0x" + Integer.toHexString(sbn.getId());
820                Log.e(TAG, "couldn't inflate public view for notification " + ident, e);
821                publicViewLocal = null;
822            }
823        }
824
825        if (publicViewLocal == null) {
826            // Add a basic notification template
827            publicViewLocal = LayoutInflater.from(mContext).inflate(
828                    com.android.internal.R.layout.notification_template_quantum_base,
829                    expandedPublic, true);
830
831            final TextView title = (TextView) publicViewLocal.findViewById(com.android.internal.R.id.title);
832            try {
833                title.setText(pm.getApplicationLabel(
834                        pm.getApplicationInfo(entry.notification.getPackageName(), 0)));
835            } catch (NameNotFoundException e) {
836                title.setText(entry.notification.getPackageName());
837            }
838
839            final ImageView icon = (ImageView) publicViewLocal.findViewById(com.android.internal.R.id.icon);
840
841            final StatusBarIcon ic = new StatusBarIcon(entry.notification.getPackageName(),
842                    entry.notification.getUser(),
843                    entry.notification.getNotification().icon,
844                    entry.notification.getNotification().iconLevel,
845                    entry.notification.getNotification().number,
846                    entry.notification.getNotification().tickerText);
847
848            Drawable iconDrawable = StatusBarIconView.getIcon(mContext, ic);
849            icon.setImageDrawable(iconDrawable);
850            if (mLegacyNotificationUtil.isGrayscale(iconDrawable)) {
851                icon.setBackgroundResource(
852                        com.android.internal.R.drawable.notification_icon_legacy_bg_inset);
853            }
854
855            final TextView text = (TextView) publicViewLocal.findViewById(com.android.internal.R.id.text);
856            text.setText("Unlock your device to see this notification.");
857
858            // TODO: fill out "time" as well
859        }
860
861        row.setDrawingCacheEnabled(true);
862
863        if (MULTIUSER_DEBUG) {
864            TextView debug = (TextView) row.findViewById(R.id.debug_info);
865            if (debug != null) {
866                debug.setVisibility(View.VISIBLE);
867                debug.setText("CU " + mCurrentUserId +" NU " + entry.notification.getUserId());
868            }
869        }
870        entry.row = row;
871        entry.row.setHeightRange(mRowMinHeight, mRowMaxHeight);
872        entry.row.setOnActivatedListener(this);
873        entry.expanded = contentViewLocal;
874        entry.expandedPublic = publicViewLocal;
875        entry.setBigContentView(bigContentViewLocal);
876
877        applyLegacyRowBackground(sbn, entry);
878
879        return true;
880    }
881
882    public NotificationClicker makeClicker(PendingIntent intent, String pkg, String tag,
883            int id, boolean forHun, int userId) {
884        return new NotificationClicker(intent, pkg, tag, id, forHun, userId);
885    }
886
887    protected class NotificationClicker implements View.OnClickListener {
888        private PendingIntent mIntent;
889        private String mPkg;
890        private String mTag;
891        private int mId;
892        private boolean mIsHeadsUp;
893        private int mUserId;
894
895        public NotificationClicker(PendingIntent intent, String pkg, String tag, int id,
896                boolean forHun, int userId) {
897            mIntent = intent;
898            mPkg = pkg;
899            mTag = tag;
900            mId = id;
901            mIsHeadsUp = forHun;
902            mUserId = userId;
903        }
904
905        public void onClick(View v) {
906            try {
907                // The intent we are sending is for the application, which
908                // won't have permission to immediately start an activity after
909                // the user switches to home.  We know it is safe to do at this
910                // point, so make sure new activity switches are now allowed.
911                ActivityManagerNative.getDefault().resumeAppSwitches();
912                // Also, notifications can be launched from the lock screen,
913                // so dismiss the lock screen when the activity starts.
914                ActivityManagerNative.getDefault().dismissKeyguardOnNextActivity();
915            } catch (RemoteException e) {
916            }
917
918            if (mIntent != null) {
919                int[] pos = new int[2];
920                v.getLocationOnScreen(pos);
921                Intent overlay = new Intent();
922                overlay.setSourceBounds(
923                        new Rect(pos[0], pos[1], pos[0]+v.getWidth(), pos[1]+v.getHeight()));
924                try {
925                    mIntent.send(mContext, 0, overlay);
926                } catch (PendingIntent.CanceledException e) {
927                    // the stack trace isn't very helpful here.  Just log the exception message.
928                    Log.w(TAG, "Sending contentIntent failed: " + e);
929                }
930
931                KeyguardTouchDelegate.getInstance(mContext).dismiss();
932            }
933
934            try {
935                if (mIsHeadsUp) {
936                    mHandler.sendEmptyMessage(MSG_HIDE_HEADS_UP);
937                }
938                mBarService.onNotificationClick(mPkg, mTag, mId, mUserId);
939            } catch (RemoteException ex) {
940                // system process is dead if we're here.
941            }
942
943            // close the shade if it was open
944            animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE);
945            visibilityChanged(false);
946        }
947    }
948
949    /**
950     * The LEDs are turned o)ff when the notification panel is shown, even just a little bit.
951     * This was added last-minute and is inconsistent with the way the rest of the notifications
952     * are handled, because the notification isn't really cancelled.  The lights are just
953     * turned off.  If any other notifications happen, the lights will turn back on.  Steve says
954     * this is what he wants. (see bug 1131461)
955     */
956    protected void visibilityChanged(boolean visible) {
957        if (mPanelSlightlyVisible != visible) {
958            mPanelSlightlyVisible = visible;
959            try {
960                if (visible) {
961                    mBarService.onPanelRevealed();
962                } else {
963                    mBarService.onPanelHidden();
964                }
965            } catch (RemoteException ex) {
966                // Won't fail unless the world has ended.
967            }
968        }
969    }
970
971    /**
972     * Cancel this notification and tell the StatusBarManagerService / NotificationManagerService
973     * about the failure.
974     *
975     * WARNING: this will call back into us.  Don't hold any locks.
976     */
977    void handleNotificationError(IBinder key, StatusBarNotification n, String message) {
978        removeNotification(key);
979        try {
980            mBarService.onNotificationError(n.getPackageName(), n.getTag(), n.getId(), n.getUid(),
981                    n.getInitialPid(), message, n.getUserId());
982        } catch (RemoteException ex) {
983            // The end is nigh.
984        }
985    }
986
987    protected StatusBarNotification removeNotificationViews(IBinder key) {
988        NotificationData.Entry entry = mNotificationData.remove(key);
989        if (entry == null) {
990            Log.w(TAG, "removeNotification for unknown key: " + key);
991            return null;
992        }
993        // Remove the expanded view.
994        ViewGroup rowParent = (ViewGroup)entry.row.getParent();
995        if (rowParent != null) rowParent.removeView(entry.row);
996        updateRowStates();
997        updateNotificationIcons();
998
999        return entry.notification;
1000    }
1001
1002    protected NotificationData.Entry createNotificationViews(IBinder key,
1003            StatusBarNotification notification) {
1004        if (DEBUG) {
1005            Log.d(TAG, "createNotificationViews(key=" + key + ", notification=" + notification);
1006        }
1007        // Construct the icon.
1008        final StatusBarIconView iconView = new StatusBarIconView(mContext,
1009                notification.getPackageName() + "/0x" + Integer.toHexString(notification.getId()),
1010                notification.getNotification());
1011        iconView.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
1012
1013        final StatusBarIcon ic = new StatusBarIcon(notification.getPackageName(),
1014                notification.getUser(),
1015                    notification.getNotification().icon,
1016                    notification.getNotification().iconLevel,
1017                    notification.getNotification().number,
1018                    notification.getNotification().tickerText);
1019        if (!iconView.set(ic)) {
1020            handleNotificationError(key, notification, "Couldn't create icon: " + ic);
1021            return null;
1022        }
1023        // Construct the expanded view.
1024        NotificationData.Entry entry = new NotificationData.Entry(key, notification, iconView);
1025        if (!inflateViews(entry, mStackScroller)) {
1026            handleNotificationError(key, notification, "Couldn't expand RemoteViews for: "
1027                    + notification);
1028            return null;
1029        }
1030        return entry;
1031    }
1032
1033    protected void addNotificationViews(NotificationData.Entry entry) {
1034        // Add the expanded view and icon.
1035        int pos = mNotificationData.add(entry);
1036        if (DEBUG) {
1037            Log.d(TAG, "addNotificationViews: added at " + pos);
1038        }
1039        updateRowStates();
1040        updateNotificationIcons();
1041    }
1042
1043    private void addNotificationViews(IBinder key, StatusBarNotification notification) {
1044        addNotificationViews(createNotificationViews(key, notification));
1045    }
1046
1047    /**
1048     * @return The number of notifications we show on Keyguard.
1049     */
1050    protected abstract int getMaxKeyguardNotifications();
1051
1052    /**
1053     * Updates expanded, dimmed and locked states of notification rows.
1054     */
1055    protected void updateRowStates() {
1056        int maxKeyguardNotifications = getMaxKeyguardNotifications();
1057        mKeyguardIconOverflowContainer.getIconsView().removeAllViews();
1058        int n = mNotificationData.size();
1059        int visibleNotifications = 0;
1060        for (int i = n-1; i >= 0; i--) {
1061            NotificationData.Entry entry = mNotificationData.get(i);
1062            if (mOnKeyguard) {
1063                entry.row.setSystemExpanded(false);
1064            } else {
1065                if (!entry.row.isUserLocked()) {
1066                    boolean top = (i == n-1);
1067                    entry.row.setSystemExpanded(top || entry.row.isUserExpanded());
1068                }
1069            }
1070            entry.row.setDimmed(mOnKeyguard);
1071            entry.row.setLocked(mOnKeyguard);
1072            boolean showOnKeyguard = shouldShowOnKeyguard(entry.notification);
1073            if (mOnKeyguard && (visibleNotifications >= maxKeyguardNotifications
1074                    || !showOnKeyguard)) {
1075                entry.row.setVisibility(View.GONE);
1076                if (showOnKeyguard) {
1077                    mKeyguardIconOverflowContainer.getIconsView().addNotification(entry);
1078                }
1079            } else {
1080                if (entry.row.getVisibility() == View.GONE) {
1081                    // notify the scroller of a child addition
1082                    mStackScroller.generateAddAnimation(entry.row);
1083                }
1084                entry.row.setVisibility(View.VISIBLE);
1085                visibleNotifications++;
1086            }
1087        }
1088
1089        if (mOnKeyguard && mKeyguardIconOverflowContainer.getIconsView().getChildCount() > 0) {
1090            mKeyguardIconOverflowContainer.setVisibility(View.VISIBLE);
1091        } else {
1092            mKeyguardIconOverflowContainer.setVisibility(View.GONE);
1093        }
1094    }
1095
1096    @Override
1097    public void onActivated(View view) {
1098        int n = mNotificationData.size();
1099        for (int i = 0; i < n; i++) {
1100            NotificationData.Entry entry = mNotificationData.get(i);
1101            if (entry.row.getVisibility() != View.GONE) {
1102                if (view == entry.row) {
1103                    entry.row.getActivator().activate();
1104                } else {
1105                    entry.row.getActivator().activateInverse();
1106                }
1107            }
1108        }
1109        if (mKeyguardIconOverflowContainer.getVisibility() != View.GONE) {
1110            if (view == mKeyguardIconOverflowContainer) {
1111                mKeyguardIconOverflowContainer.getActivator().activate();
1112            } else {
1113                mKeyguardIconOverflowContainer.getActivator().activateInverse();
1114            }
1115        }
1116    }
1117
1118    @Override
1119    public void onReset(View view) {
1120        int n = mNotificationData.size();
1121        for (int i = 0; i < n; i++) {
1122            NotificationData.Entry entry = mNotificationData.get(i);
1123            if (entry.row.getVisibility() != View.GONE) {
1124                entry.row.getActivator().reset();
1125            }
1126        }
1127        if (mKeyguardIconOverflowContainer.getVisibility() != View.GONE) {
1128            mKeyguardIconOverflowContainer.getActivator().reset();
1129        }
1130    }
1131
1132    private boolean shouldShowOnKeyguard(StatusBarNotification sbn) {
1133        return sbn.getNotification().priority >= Notification.PRIORITY_LOW;
1134    }
1135
1136    protected void setZenMode(int mode) {
1137        if (!isDeviceProvisioned()) return;
1138        mZenMode = mode;
1139        updateNotificationIcons();
1140    }
1141
1142    protected abstract void haltTicker();
1143    protected abstract void setAreThereNotifications();
1144    protected abstract void updateNotificationIcons();
1145    protected abstract void tick(IBinder key, StatusBarNotification n, boolean firstTime);
1146    protected abstract void updateExpandedViewPos(int expandedPosition);
1147    protected abstract int getExpandedViewMaxHeight();
1148    protected abstract boolean shouldDisableNavbarGestures();
1149
1150    protected boolean isTopNotification(ViewGroup parent, NotificationData.Entry entry) {
1151        return parent != null && parent.indexOfChild(entry.row) == 0;
1152    }
1153
1154    public void updateNotification(IBinder key, StatusBarNotification notification) {
1155        if (DEBUG) Log.d(TAG, "updateNotification(" + key + " -> " + notification + ")");
1156
1157        final NotificationData.Entry oldEntry = mNotificationData.findByKey(key);
1158        if (oldEntry == null) {
1159            Log.w(TAG, "updateNotification for unknown key: " + key);
1160            return;
1161        }
1162
1163        final StatusBarNotification oldNotification = oldEntry.notification;
1164
1165        // XXX: modify when we do something more intelligent with the two content views
1166        final RemoteViews oldContentView = oldNotification.getNotification().contentView;
1167        final RemoteViews contentView = notification.getNotification().contentView;
1168        final RemoteViews oldBigContentView = oldNotification.getNotification().bigContentView;
1169        final RemoteViews bigContentView = notification.getNotification().bigContentView;
1170        final RemoteViews oldHeadsUpContentView = oldNotification.getNotification().headsUpContentView;
1171        final RemoteViews headsUpContentView = notification.getNotification().headsUpContentView;
1172        final Notification oldPublicNotification = oldNotification.getNotification().publicVersion;
1173        final RemoteViews oldPublicContentView = oldPublicNotification != null
1174                ? oldPublicNotification.contentView : null;
1175        final Notification publicNotification = notification.getNotification().publicVersion;
1176        final RemoteViews publicContentView = publicNotification != null
1177                ? publicNotification.contentView : null;
1178
1179        if (DEBUG) {
1180            Log.d(TAG, "old notification: when=" + oldNotification.getNotification().when
1181                    + " ongoing=" + oldNotification.isOngoing()
1182                    + " expanded=" + oldEntry.expanded
1183                    + " contentView=" + oldContentView
1184                    + " bigContentView=" + oldBigContentView
1185                    + " publicView=" + oldPublicContentView
1186                    + " rowParent=" + oldEntry.row.getParent());
1187            Log.d(TAG, "new notification: when=" + notification.getNotification().when
1188                    + " ongoing=" + oldNotification.isOngoing()
1189                    + " contentView=" + contentView
1190                    + " bigContentView=" + bigContentView
1191                    + " publicView=" + publicContentView);
1192        }
1193
1194        // Can we just reapply the RemoteViews in place?  If when didn't change, the order
1195        // didn't change.
1196
1197        // 1U is never null
1198        boolean contentsUnchanged = oldEntry.expanded != null
1199                && contentView.getPackage() != null
1200                && oldContentView.getPackage() != null
1201                && oldContentView.getPackage().equals(contentView.getPackage())
1202                && oldContentView.getLayoutId() == contentView.getLayoutId();
1203        // large view may be null
1204        boolean bigContentsUnchanged =
1205                (oldEntry.getBigContentView() == null && bigContentView == null)
1206                || ((oldEntry.getBigContentView() != null && bigContentView != null)
1207                    && bigContentView.getPackage() != null
1208                    && oldBigContentView.getPackage() != null
1209                    && oldBigContentView.getPackage().equals(bigContentView.getPackage())
1210                    && oldBigContentView.getLayoutId() == bigContentView.getLayoutId());
1211        boolean headsUpContentsUnchanged =
1212                (oldHeadsUpContentView == null && headsUpContentView == null)
1213                || ((oldHeadsUpContentView != null && headsUpContentView != null)
1214                    && headsUpContentView.getPackage() != null
1215                    && oldHeadsUpContentView.getPackage() != null
1216                    && oldHeadsUpContentView.getPackage().equals(headsUpContentView.getPackage())
1217                    && oldHeadsUpContentView.getLayoutId() == headsUpContentView.getLayoutId());
1218        boolean publicUnchanged  =
1219                (oldPublicContentView == null && publicContentView == null)
1220                || ((oldPublicContentView != null && publicContentView != null)
1221                        && publicContentView.getPackage() != null
1222                        && oldPublicContentView.getPackage() != null
1223                        && oldPublicContentView.getPackage().equals(publicContentView.getPackage())
1224                        && oldPublicContentView.getLayoutId() == publicContentView.getLayoutId());
1225
1226        ViewGroup rowParent = (ViewGroup) oldEntry.row.getParent();
1227        boolean orderUnchanged =
1228                   notification.getNotification().when == oldNotification.getNotification().when
1229                && notification.getScore() == oldNotification.getScore();
1230                // score now encompasses/supersedes isOngoing()
1231
1232        boolean updateTicker = notification.getNotification().tickerText != null
1233                && !TextUtils.equals(notification.getNotification().tickerText,
1234                        oldEntry.notification.getNotification().tickerText);
1235        boolean isTopAnyway = isTopNotification(rowParent, oldEntry);
1236        if (contentsUnchanged && bigContentsUnchanged && headsUpContentsUnchanged && publicUnchanged
1237                && (orderUnchanged || isTopAnyway)) {
1238            if (DEBUG) Log.d(TAG, "reusing notification for key: " + key);
1239            oldEntry.notification = notification;
1240            try {
1241                updateNotificationViews(oldEntry, notification);
1242
1243                if (ENABLE_HEADS_UP && mInterruptingNotificationEntry != null
1244                        && oldNotification == mInterruptingNotificationEntry.notification) {
1245                    if (!shouldInterrupt(notification)) {
1246                        if (DEBUG) Log.d(TAG, "no longer interrupts!");
1247                        mHandler.sendEmptyMessage(MSG_HIDE_HEADS_UP);
1248                    } else {
1249                        if (DEBUG) Log.d(TAG, "updating the current heads up:" + notification);
1250                        mInterruptingNotificationEntry.notification = notification;
1251                        updateHeadsUpViews(mInterruptingNotificationEntry, notification);
1252                    }
1253                }
1254
1255                // Update the icon.
1256                final StatusBarIcon ic = new StatusBarIcon(notification.getPackageName(),
1257                        notification.getUser(),
1258                        notification.getNotification().icon, notification.getNotification().iconLevel,
1259                        notification.getNotification().number,
1260                        notification.getNotification().tickerText);
1261                if (!oldEntry.icon.set(ic)) {
1262                    handleNotificationError(key, notification, "Couldn't update icon: " + ic);
1263                    return;
1264                }
1265                updateRowStates();
1266            }
1267            catch (RuntimeException e) {
1268                // It failed to add cleanly.  Log, and remove the view from the panel.
1269                Log.w(TAG, "Couldn't reapply views for package " + contentView.getPackage(), e);
1270                removeNotificationViews(key);
1271                addNotificationViews(key, notification);
1272            }
1273        } else {
1274            if (DEBUG) Log.d(TAG, "not reusing notification for key: " + key);
1275            if (DEBUG) Log.d(TAG, "contents was " + (contentsUnchanged ? "unchanged" : "changed"));
1276            if (DEBUG) Log.d(TAG, "order was " + (orderUnchanged ? "unchanged" : "changed"));
1277            if (DEBUG) Log.d(TAG, "notification is " + (isTopAnyway ? "top" : "not top"));
1278            removeNotificationViews(key);
1279            addNotificationViews(key, notification);  // will also replace the heads up
1280            final NotificationData.Entry newEntry = mNotificationData.findByKey(key);
1281            final boolean userChangedExpansion = oldEntry.row.hasUserChangedExpansion();
1282            if (userChangedExpansion) {
1283                boolean userExpanded = oldEntry.row.isUserExpanded();
1284                newEntry.row.applyExpansionToLayout(userExpanded);
1285                newEntry.row.setUserExpanded(userExpanded);
1286            }
1287        }
1288
1289        // Update the veto button accordingly (and as a result, whether this row is
1290        // swipe-dismissable)
1291        updateNotificationVetoButton(oldEntry.row, notification);
1292
1293        // Is this for you?
1294        boolean isForCurrentUser = notificationIsForCurrentProfiles(notification);
1295        if (DEBUG) Log.d(TAG, "notification is " + (isForCurrentUser ? "" : "not ") + "for you");
1296
1297        // Restart the ticker if it's still running
1298        if (updateTicker && isForCurrentUser) {
1299            haltTicker();
1300            tick(key, notification, false);
1301        }
1302
1303        // Recalculate the position of the sliding windows and the titles.
1304        setAreThereNotifications();
1305        updateExpandedViewPos(EXPANDED_LEAVE_ALONE);
1306    }
1307
1308    private void updateNotificationViews(NotificationData.Entry entry,
1309            StatusBarNotification notification) {
1310        updateNotificationViews(entry, notification, false);
1311    }
1312
1313    private void updateHeadsUpViews(NotificationData.Entry entry,
1314            StatusBarNotification notification) {
1315        updateNotificationViews(entry, notification, true);
1316    }
1317
1318    private void updateNotificationViews(NotificationData.Entry entry,
1319            StatusBarNotification notification, boolean isHeadsUp) {
1320        final RemoteViews contentView = notification.getNotification().contentView;
1321        final RemoteViews bigContentView = isHeadsUp
1322                ? notification.getNotification().headsUpContentView
1323                : notification.getNotification().bigContentView;
1324        final Notification publicVersion = notification.getNotification().publicVersion;
1325        final RemoteViews publicContentView = publicVersion != null ? publicVersion.contentView
1326                : null;
1327
1328        // Reapply the RemoteViews
1329        contentView.reapply(mContext, entry.expanded, mOnClickHandler);
1330        if (bigContentView != null && entry.getBigContentView() != null) {
1331            bigContentView.reapply(mContext, entry.getBigContentView(),
1332                    mOnClickHandler);
1333        }
1334        if (publicContentView != null && entry.getPublicContentView() != null) {
1335            publicContentView.reapply(mContext, entry.getPublicContentView(), mOnClickHandler);
1336        }
1337        // update the contentIntent
1338        final PendingIntent contentIntent = notification.getNotification().contentIntent;
1339        if (contentIntent != null) {
1340            final View.OnClickListener listener = makeClicker(contentIntent,
1341                    notification.getPackageName(), notification.getTag(), notification.getId(),
1342                    isHeadsUp, notification.getUserId());
1343            entry.row.setOnClickListener(listener);
1344        } else {
1345            entry.row.setOnClickListener(null);
1346        }
1347        entry.row.notifyContentUpdated();
1348    }
1349
1350    protected void notifyHeadsUpScreenOn(boolean screenOn) {
1351        if (!screenOn && mInterruptingNotificationEntry != null) {
1352            mHandler.sendEmptyMessage(MSG_ESCALATE_HEADS_UP);
1353        }
1354    }
1355
1356    protected boolean shouldInterrupt(StatusBarNotification sbn) {
1357        Notification notification = sbn.getNotification();
1358        // some predicates to make the boolean logic legible
1359        boolean isNoisy = (notification.defaults & Notification.DEFAULT_SOUND) != 0
1360                || (notification.defaults & Notification.DEFAULT_VIBRATE) != 0
1361                || notification.sound != null
1362                || notification.vibrate != null;
1363        boolean isHighPriority = sbn.getScore() >= INTERRUPTION_THRESHOLD;
1364        boolean isFullscreen = notification.fullScreenIntent != null;
1365        boolean hasTicker = mHeadsUpTicker && !TextUtils.isEmpty(notification.tickerText);
1366        boolean isAllowed = notification.extras.getInt(Notification.EXTRA_AS_HEADS_UP,
1367                Notification.HEADS_UP_ALLOWED) != Notification.HEADS_UP_NEVER;
1368
1369        final KeyguardTouchDelegate keyguard = KeyguardTouchDelegate.getInstance(mContext);
1370        boolean interrupt = (isFullscreen || (isHighPriority && (isNoisy || hasTicker)))
1371                && isAllowed
1372                && mPowerManager.isScreenOn()
1373                && !keyguard.isShowingAndNotOccluded()
1374                && !keyguard.isInputRestricted();
1375        try {
1376            interrupt = interrupt && !mDreamManager.isDreaming();
1377        } catch (RemoteException e) {
1378            Log.d(TAG, "failed to query dream manager", e);
1379        }
1380        if (DEBUG) Log.d(TAG, "interrupt: " + interrupt);
1381        return interrupt;
1382    }
1383
1384    // Q: What kinds of notifications should show during setup?
1385    // A: Almost none! Only things coming from the system (package is "android") that also
1386    // have special "kind" tags marking them as relevant for setup (see below).
1387    protected boolean showNotificationEvenIfUnprovisioned(StatusBarNotification sbn) {
1388        return "android".equals(sbn.getPackageName())
1389                && sbn.getNotification().extras.getBoolean(Notification.EXTRA_ALLOW_DURING_SETUP);
1390    }
1391
1392    public boolean inKeyguardRestrictedInputMode() {
1393        return KeyguardTouchDelegate.getInstance(mContext).isInputRestricted();
1394    }
1395
1396    public void setInteracting(int barWindow, boolean interacting) {
1397        // hook for subclasses
1398    }
1399
1400    public void destroy() {
1401        if (mSearchPanelView != null) {
1402            mWindowManager.removeViewImmediate(mSearchPanelView);
1403        }
1404        mContext.unregisterReceiver(mBroadcastReceiver);
1405    }
1406}
1407