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