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