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