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