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