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