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