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