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