BaseStatusBar.java revision b120ac5b6935407d9eefe8548ff185ea63e4a29b
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[7] != 0);
461        setHardKeyboardStatus(switches[5] != 0, switches[6] != 0);
462
463        // Set up the initial icon state
464        int N = iconList.size();
465        int viewIndex = 0;
466        for (int i=0; i<N; i++) {
467            StatusBarIcon icon = iconList.getIcon(i);
468            if (icon != null) {
469                addIcon(iconList.getSlot(i), i, viewIndex, icon);
470                viewIndex++;
471            }
472        }
473
474        // Set up the initial notification state.
475        try {
476            mNotificationListener.registerAsSystemService(mContext,
477                    new ComponentName(mContext.getPackageName(), getClass().getCanonicalName()),
478                    UserHandle.USER_ALL);
479        } catch (RemoteException e) {
480            Log.e(TAG, "Unable to register notification listener", e);
481        }
482
483
484        if (DEBUG) {
485            Log.d(TAG, String.format(
486                    "init: icons=%d disabled=0x%08x lights=0x%08x menu=0x%08x imeButton=0x%08x",
487                   iconList.size(),
488                   switches[0],
489                   switches[1],
490                   switches[2],
491                   switches[3]
492                   ));
493        }
494
495        mCurrentUserId = ActivityManager.getCurrentUser();
496
497        IntentFilter filter = new IntentFilter();
498        filter.addAction(Intent.ACTION_USER_SWITCHED);
499        filter.addAction(Intent.ACTION_USER_ADDED);
500        filter.addAction(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED);
501        mContext.registerReceiver(mBroadcastReceiver, filter);
502
503        updateCurrentProfilesCache();
504    }
505
506    public void userSwitched(int newUserId) {
507        // should be overridden
508    }
509
510    public boolean isHeadsUp(String key) {
511      return mHeadsUpNotificationView != null && mHeadsUpNotificationView.isShowing(key);
512    }
513
514    @Override  // NotificationData.Environment
515    public boolean isNotificationForCurrentProfiles(StatusBarNotification n) {
516        final int thisUserId = mCurrentUserId;
517        final int notificationUserId = n.getUserId();
518        if (DEBUG && MULTIUSER_DEBUG) {
519            Log.v(TAG, String.format("%s: current userid: %d, notification userid: %d",
520                    n, thisUserId, notificationUserId));
521        }
522        synchronized (mCurrentProfiles) {
523            return notificationUserId == UserHandle.USER_ALL
524                    || mCurrentProfiles.get(notificationUserId) != null;
525        }
526    }
527
528    /**
529     * Takes the necessary steps to prepare the status bar for starting an activity, then starts it.
530     * @param action A dismiss action that is called if it's safe to start the activity.
531     */
532    protected void dismissKeyguardThenExecute(OnDismissAction action) {
533        action.onDismiss();
534    }
535
536    @Override
537    protected void onConfigurationChanged(Configuration newConfig) {
538        final Locale locale = mContext.getResources().getConfiguration().locale;
539        final int ld = TextUtils.getLayoutDirectionFromLocale(locale);
540        final float fontScale = newConfig.fontScale;
541
542        if (! locale.equals(mLocale) || ld != mLayoutDirection || fontScale != mFontScale) {
543            if (DEBUG) {
544                Log.v(TAG, String.format(
545                        "config changed locale/LD: %s (%d) -> %s (%d)", mLocale, mLayoutDirection,
546                        locale, ld));
547            }
548            mLocale = locale;
549            mLayoutDirection = ld;
550            refreshLayout(ld);
551        }
552    }
553
554    protected View updateNotificationVetoButton(View row, StatusBarNotification n) {
555        View vetoButton = row.findViewById(R.id.veto);
556        if (n.isClearable() || (mHeadsUpNotificationView.getEntry() != null
557                && mHeadsUpNotificationView.getEntry().row == row)) {
558            final String _pkg = n.getPackageName();
559            final String _tag = n.getTag();
560            final int _id = n.getId();
561            final int _userId = n.getUserId();
562            vetoButton.setOnClickListener(new View.OnClickListener() {
563                    public void onClick(View v) {
564                        // Accessibility feedback
565                        v.announceForAccessibility(
566                                mContext.getString(R.string.accessibility_notification_dismissed));
567                        try {
568                            mBarService.onNotificationClear(_pkg, _tag, _id, _userId);
569
570                        } catch (RemoteException ex) {
571                            // system process is dead if we're here.
572                        }
573                    }
574                });
575            vetoButton.setVisibility(View.VISIBLE);
576        } else {
577            vetoButton.setVisibility(View.GONE);
578        }
579        vetoButton.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
580        return vetoButton;
581    }
582
583
584    protected void applyLegacyRowBackground(StatusBarNotification sbn,
585            NotificationData.Entry entry) {
586        int version = 0;
587        try {
588            ApplicationInfo info = mContext.getPackageManager().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
609    public boolean isMediaNotification(NotificationData.Entry entry) {
610        // TODO: confirm that there's a valid media key
611        return entry.expandedBig != null &&
612               entry.expandedBig.findViewById(com.android.internal.R.id.media_action_area) != null;
613    }
614
615    private void startAppNotificationSettingsActivity(String packageName, int appUid) {
616        Intent intent = new Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS);
617        intent.putExtra(Settings.EXTRA_APP_PACKAGE, packageName);
618        intent.putExtra(Settings.EXTRA_APP_UID, appUid);
619        TaskStackBuilder.create(mContext).addNextIntentWithParentStack(intent)
620                .startActivities(null, new UserHandle(UserHandle.getUserId(appUid)));
621    }
622
623    protected SwipeHelper.LongPressListener getNotificationLongClicker() {
624        return new SwipeHelper.LongPressListener() {
625            @Override
626            public boolean onLongPress(View v, int x, int y) {
627                dismissPopups();
628
629                if (v.getWindowToken() == null) return false;
630
631                // Assume we are a status_bar_notification_row
632                final View guts = v.findViewById(R.id.notification_guts);
633                if (guts == null) return false;
634
635                // Already showing?
636                if (guts.getVisibility() == View.VISIBLE) return false;
637
638                guts.setVisibility(View.VISIBLE);
639                final double horz = Math.max(v.getWidth() - x, x);
640                final double vert = Math.max(v.getHeight() - y, y);
641                final float r = (float) Math.hypot(horz, vert);
642                final Animator a
643                        = ViewAnimationUtils.createCircularReveal(guts, x, y, 0, r);
644                a.setDuration(400);
645                a.setInterpolator(mLinearOutSlowIn);
646                a.start();
647
648                mNotificationGutsExposed = guts;
649
650                return true;
651            }
652        };
653    }
654
655    public void dismissPopups() {
656        if (mNotificationGutsExposed != null) {
657            final View v = mNotificationGutsExposed;
658            mNotificationGutsExposed = null;
659
660            final int x = (v.getLeft() + v.getRight()) / 2;
661            final int y = (v.getTop() + v.getBottom()) / 2;
662            final Animator a = ViewAnimationUtils.createCircularReveal(v,
663                    x, y, x, 0);
664            a.setDuration(200);
665            a.setInterpolator(mFastOutLinearIn);
666            a.addListener(new AnimatorListenerAdapter() {
667                @Override
668                public void onAnimationEnd(Animator animation) {
669                    super.onAnimationEnd(animation);
670                    v.setVisibility(View.GONE);
671                }
672            });
673            a.start();
674        }
675    }
676
677    public void onHeadsUpDismissed() {
678    }
679
680    @Override
681    public void showRecentApps(boolean triggeredFromAltTab) {
682        int msg = MSG_SHOW_RECENT_APPS;
683        mHandler.removeMessages(msg);
684        mHandler.obtainMessage(msg, triggeredFromAltTab ? 1 : 0, 0).sendToTarget();
685    }
686
687    @Override
688    public void hideRecentApps(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) {
689        int msg = MSG_HIDE_RECENT_APPS;
690        mHandler.removeMessages(msg);
691        mHandler.obtainMessage(msg, triggeredFromAltTab ? 1 : 0,
692                triggeredFromHomeKey ? 1 : 0).sendToTarget();
693    }
694
695    @Override
696    public void toggleRecentApps() {
697        int msg = MSG_TOGGLE_RECENTS_APPS;
698        mHandler.removeMessages(msg);
699        mHandler.sendEmptyMessage(msg);
700    }
701
702    @Override
703    public void preloadRecentApps() {
704        int msg = MSG_PRELOAD_RECENT_APPS;
705        mHandler.removeMessages(msg);
706        mHandler.sendEmptyMessage(msg);
707    }
708
709    @Override
710    public void cancelPreloadRecentApps() {
711        int msg = MSG_CANCEL_PRELOAD_RECENT_APPS;
712        mHandler.removeMessages(msg);
713        mHandler.sendEmptyMessage(msg);
714    }
715
716    /** Jumps to the next affiliated task in the group. */
717    public void showNextAffiliatedTask() {
718        int msg = MSG_SHOW_NEXT_AFFILIATED_TASK;
719        mHandler.removeMessages(msg);
720        mHandler.sendEmptyMessage(msg);
721    }
722
723    /** Jumps to the previous affiliated task in the group. */
724    public void showPreviousAffiliatedTask() {
725        int msg = MSG_SHOW_PREV_AFFILIATED_TASK;
726        mHandler.removeMessages(msg);
727        mHandler.sendEmptyMessage(msg);
728    }
729
730    @Override
731    public void showSearchPanel() {
732        int msg = MSG_OPEN_SEARCH_PANEL;
733        mHandler.removeMessages(msg);
734        mHandler.sendEmptyMessage(msg);
735    }
736
737    @Override
738    public void hideSearchPanel() {
739        int msg = MSG_CLOSE_SEARCH_PANEL;
740        mHandler.removeMessages(msg);
741        mHandler.sendEmptyMessage(msg);
742    }
743
744    protected abstract WindowManager.LayoutParams getSearchLayoutParams(
745            LayoutParams layoutParams);
746
747    protected void updateSearchPanel() {
748        // Search Panel
749        boolean visible = false;
750        if (mSearchPanelView != null) {
751            visible = mSearchPanelView.isShowing();
752            mWindowManager.removeView(mSearchPanelView);
753        }
754
755        // Provide SearchPanel with a temporary parent to allow layout params to work.
756        LinearLayout tmpRoot = new LinearLayout(mContext);
757        mSearchPanelView = (SearchPanelView) LayoutInflater.from(mContext).inflate(
758                 R.layout.status_bar_search_panel, tmpRoot, false);
759        mSearchPanelView.setOnTouchListener(
760                 new TouchOutsideListener(MSG_CLOSE_SEARCH_PANEL, mSearchPanelView));
761        mSearchPanelView.setVisibility(View.GONE);
762
763        WindowManager.LayoutParams lp = getSearchLayoutParams(mSearchPanelView.getLayoutParams());
764
765        mWindowManager.addView(mSearchPanelView, lp);
766        mSearchPanelView.setBar(this);
767        if (visible) {
768            mSearchPanelView.show(true, false);
769        }
770    }
771
772    protected H createHandler() {
773         return new H();
774    }
775
776    static void sendCloseSystemWindows(Context context, String reason) {
777        if (ActivityManagerNative.isSystemReady()) {
778            try {
779                ActivityManagerNative.getDefault().closeSystemDialogs(reason);
780            } catch (RemoteException e) {
781            }
782        }
783    }
784
785    protected abstract View getStatusBarView();
786
787    protected View.OnTouchListener mRecentsPreloadOnTouchListener = new View.OnTouchListener() {
788        // additional optimization when we have software system buttons - start loading the recent
789        // tasks on touch down
790        @Override
791        public boolean onTouch(View v, MotionEvent event) {
792            int action = event.getAction() & MotionEvent.ACTION_MASK;
793            if (action == MotionEvent.ACTION_DOWN) {
794                preloadRecents();
795            } else if (action == MotionEvent.ACTION_CANCEL) {
796                cancelPreloadingRecents();
797            } else if (action == MotionEvent.ACTION_UP) {
798                if (!v.isPressed()) {
799                    cancelPreloadingRecents();
800                }
801
802            }
803            return false;
804        }
805    };
806
807    /** Proxy for RecentsComponent */
808
809    protected void showRecents(boolean triggeredFromAltTab) {
810        if (mRecents != null) {
811            sendCloseSystemWindows(mContext, SYSTEM_DIALOG_REASON_RECENT_APPS);
812            mRecents.showRecents(triggeredFromAltTab, getStatusBarView());
813        }
814    }
815
816    protected void hideRecents(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) {
817        if (mRecents != null) {
818            mRecents.hideRecents(triggeredFromAltTab, triggeredFromHomeKey);
819        }
820    }
821
822    protected void toggleRecents() {
823        if (mRecents != null) {
824            sendCloseSystemWindows(mContext, SYSTEM_DIALOG_REASON_RECENT_APPS);
825            mRecents.toggleRecents(mDisplay, mLayoutDirection, getStatusBarView());
826        }
827    }
828
829    protected void preloadRecents() {
830        if (mRecents != null) {
831            mRecents.preloadRecents();
832        }
833    }
834
835    protected void cancelPreloadingRecents() {
836        if (mRecents != null) {
837            mRecents.cancelPreloadingRecents();
838        }
839    }
840
841    protected void showRecentsNextAffiliatedTask() {
842        if (mRecents != null) {
843            mRecents.showNextAffiliatedTask();
844        }
845    }
846
847    protected void showRecentsPreviousAffiliatedTask() {
848        if (mRecents != null) {
849            mRecents.showPrevAffiliatedTask();
850        }
851    }
852
853    @Override
854    public void onVisibilityChanged(boolean visible) {
855        // Do nothing
856    }
857
858    public abstract void resetHeadsUpDecayTimer();
859
860    public abstract void scheduleHeadsUpOpen();
861
862    public abstract void scheduleHeadsUpClose();
863
864    public abstract void scheduleHeadsUpEscalation();
865
866    /**
867     * Save the current "public" (locked and secure) state of the lockscreen.
868     */
869    public void setLockscreenPublicMode(boolean publicMode) {
870        mLockscreenPublicMode = publicMode;
871    }
872
873    public boolean isLockscreenPublicMode() {
874        return mLockscreenPublicMode;
875    }
876
877    /**
878     * Has the given user chosen to allow their private (full) notifications to be shown even
879     * when the lockscreen is in "public" (secure & locked) mode?
880     */
881    public boolean userAllowsPrivateNotificationsInPublic(int userHandle) {
882        if (userHandle == UserHandle.USER_ALL) {
883            return true;
884        }
885
886        if (mUsersAllowingPrivateNotifications.indexOfKey(userHandle) < 0) {
887            final boolean allowed = 0 != Settings.Secure.getIntForUser(
888                    mContext.getContentResolver(),
889                    Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0, userHandle);
890            final int dpmFlags = mDevicePolicyManager.getKeyguardDisabledFeatures(null /* admin */,
891                    userHandle);
892            final boolean allowedByDpm = (dpmFlags
893                    & DevicePolicyManager.KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS) == 0;
894            mUsersAllowingPrivateNotifications.append(userHandle, allowed && allowedByDpm);
895            return allowed;
896        }
897
898        return mUsersAllowingPrivateNotifications.get(userHandle);
899    }
900
901    /**
902     * Returns true if we're on a secure lockscreen and the user wants to hide "sensitive"
903     * notification data. If so, private notifications should show their (possibly
904     * auto-generated) publicVersion, and secret notifications should be totally invisible.
905     */
906    @Override  // NotificationData.Environment
907    public boolean shouldHideSensitiveContents(int userid) {
908        return isLockscreenPublicMode() && !userAllowsPrivateNotificationsInPublic(userid);
909    }
910
911    public void onNotificationClear(StatusBarNotification notification) {
912        try {
913            mBarService.onNotificationClear(
914                    notification.getPackageName(),
915                    notification.getTag(),
916                    notification.getId(),
917                    notification.getUserId());
918        } catch (android.os.RemoteException ex) {
919            // oh well
920        }
921    }
922
923    protected class H extends Handler {
924        public void handleMessage(Message m) {
925            switch (m.what) {
926             case MSG_SHOW_RECENT_APPS:
927                 showRecents(m.arg1 > 0);
928                 break;
929             case MSG_HIDE_RECENT_APPS:
930                 hideRecents(m.arg1 > 0, m.arg2 > 0);
931                 break;
932             case MSG_TOGGLE_RECENTS_APPS:
933                 toggleRecents();
934                 break;
935             case MSG_PRELOAD_RECENT_APPS:
936                  preloadRecents();
937                  break;
938             case MSG_CANCEL_PRELOAD_RECENT_APPS:
939                  cancelPreloadingRecents();
940                  break;
941             case MSG_SHOW_NEXT_AFFILIATED_TASK:
942                  showRecentsNextAffiliatedTask();
943                  break;
944             case MSG_SHOW_PREV_AFFILIATED_TASK:
945                  showRecentsPreviousAffiliatedTask();
946                  break;
947             case MSG_OPEN_SEARCH_PANEL:
948                 if (DEBUG) Log.d(TAG, "opening search panel");
949                 if (mSearchPanelView != null && mSearchPanelView.isAssistantAvailable()) {
950                     mSearchPanelView.show(true, true);
951                 }
952                 break;
953             case MSG_CLOSE_SEARCH_PANEL:
954                 if (DEBUG) Log.d(TAG, "closing search panel");
955                 if (mSearchPanelView != null && mSearchPanelView.isShowing()) {
956                     mSearchPanelView.show(false, true);
957                 }
958                 break;
959            }
960        }
961    }
962
963    public class TouchOutsideListener implements View.OnTouchListener {
964        private int mMsg;
965        private StatusBarPanel mPanel;
966
967        public TouchOutsideListener(int msg, StatusBarPanel panel) {
968            mMsg = msg;
969            mPanel = panel;
970        }
971
972        public boolean onTouch(View v, MotionEvent ev) {
973            final int action = ev.getAction();
974            if (action == MotionEvent.ACTION_OUTSIDE
975                || (action == MotionEvent.ACTION_DOWN
976                    && !mPanel.isInContentArea((int)ev.getX(), (int)ev.getY()))) {
977                mHandler.removeMessages(mMsg);
978                mHandler.sendEmptyMessage(mMsg);
979                return true;
980            }
981            return false;
982        }
983    }
984
985    protected void workAroundBadLayerDrawableOpacity(View v) {
986    }
987
988    private boolean inflateViews(NotificationData.Entry entry, ViewGroup parent) {
989            return inflateViews(entry, parent, false);
990    }
991
992    protected boolean inflateViewsForHeadsUp(NotificationData.Entry entry, ViewGroup parent) {
993            return inflateViews(entry, parent, true);
994    }
995
996    private boolean inflateViews(NotificationData.Entry entry, ViewGroup parent, boolean isHeadsUp) {
997        PackageManager pmUser = getPackageManagerForUser(
998                entry.notification.getUser().getIdentifier());
999
1000        int maxHeight = mRowMaxHeight;
1001        StatusBarNotification sbn = entry.notification;
1002        RemoteViews contentView = sbn.getNotification().contentView;
1003        RemoteViews bigContentView = sbn.getNotification().bigContentView;
1004
1005        if (isHeadsUp) {
1006            maxHeight =
1007                    mContext.getResources().getDimensionPixelSize(R.dimen.notification_mid_height);
1008            bigContentView = sbn.getNotification().headsUpContentView;
1009        }
1010
1011        if (contentView == null) {
1012            return false;
1013        }
1014
1015        if (DEBUG) {
1016            Log.v(TAG, "publicNotification: " + sbn.getNotification().publicVersion);
1017        }
1018
1019        Notification publicNotification = sbn.getNotification().publicVersion;
1020
1021        ExpandableNotificationRow row;
1022
1023        // Stash away previous user expansion state so we can restore it at
1024        // the end.
1025        boolean hasUserChangedExpansion = false;
1026        boolean userExpanded = false;
1027        boolean userLocked = false;
1028
1029        if (entry.row != null) {
1030            row = entry.row;
1031            hasUserChangedExpansion = row.hasUserChangedExpansion();
1032            userExpanded = row.isUserExpanded();
1033            userLocked = row.isUserLocked();
1034            entry.reset();
1035            if (hasUserChangedExpansion) {
1036                row.setUserExpanded(userExpanded);
1037            }
1038        } else {
1039            // create the row view
1040            LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(
1041                    Context.LAYOUT_INFLATER_SERVICE);
1042            row = (ExpandableNotificationRow) inflater.inflate(R.layout.status_bar_notification_row,
1043                    parent, false);
1044            row.setExpansionLogger(this, entry.notification.getKey());
1045        }
1046
1047        // the notification inspector (see SwipeHelper.setLongPressListener)
1048        row.setTag(sbn.getPackageName());
1049        final View guts = row.findViewById(R.id.notification_guts);
1050        final String pkg = entry.notification.getPackageName();
1051        String appname = pkg;
1052        Drawable pkgicon = null;
1053        int appUid = -1;
1054        try {
1055            final ApplicationInfo info = pmUser.getApplicationInfo(pkg,
1056                PackageManager.GET_UNINSTALLED_PACKAGES | PackageManager.GET_DISABLED_COMPONENTS);
1057            if (info != null) {
1058                appname = String.valueOf(pmUser.getApplicationLabel(info));
1059                pkgicon = pmUser.getApplicationIcon(info);
1060                appUid = info.uid;
1061            }
1062        } catch (NameNotFoundException e) {
1063            // app is gone, just show package name and generic icon
1064            pkgicon = pmUser.getDefaultActivityIcon();
1065        }
1066        ((ImageView) row.findViewById(android.R.id.icon)).setImageDrawable(pkgicon);
1067        ((DateTimeView) row.findViewById(R.id.timestamp)).setTime(entry.notification.getPostTime());
1068        ((TextView) row.findViewById(R.id.pkgname)).setText(appname);
1069        final View settingsButton = guts.findViewById(R.id.notification_inspect_item);
1070        if (appUid >= 0) {
1071            final int appUidF = appUid;
1072            settingsButton.setOnClickListener(new View.OnClickListener() {
1073                public void onClick(View v) {
1074                    dismissKeyguardThenExecute(new OnDismissAction() {
1075                        public boolean onDismiss() {
1076                            startAppNotificationSettingsActivity(pkg, appUidF);
1077                            animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE);
1078                            visibilityChanged(false);
1079                            return true;
1080                        }
1081                    });
1082                }
1083            });
1084        } else {
1085            settingsButton.setVisibility(View.GONE);
1086        }
1087
1088        workAroundBadLayerDrawableOpacity(row);
1089        View vetoButton = updateNotificationVetoButton(row, sbn);
1090        vetoButton.setContentDescription(mContext.getString(
1091                R.string.accessibility_remove_notification));
1092
1093        // NB: the large icon is now handled entirely by the template
1094
1095        // bind the click event to the content area
1096        NotificationContentView expanded =
1097                (NotificationContentView) row.findViewById(R.id.expanded);
1098        NotificationContentView expandedPublic =
1099                (NotificationContentView) row.findViewById(R.id.expandedPublic);
1100
1101        row.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
1102
1103        PendingIntent contentIntent = sbn.getNotification().contentIntent;
1104        if (contentIntent != null) {
1105            final View.OnClickListener listener = makeClicker(contentIntent, sbn.getKey(),
1106                    isHeadsUp);
1107            row.setOnClickListener(listener);
1108        } else {
1109            row.setOnClickListener(null);
1110        }
1111
1112        // set up the adaptive layout
1113        View contentViewLocal = null;
1114        View bigContentViewLocal = null;
1115        try {
1116            contentViewLocal = contentView.apply(mContext, expanded,
1117                    mOnClickHandler);
1118            if (bigContentView != null) {
1119                bigContentViewLocal = bigContentView.apply(mContext, expanded,
1120                        mOnClickHandler);
1121            }
1122        }
1123        catch (RuntimeException e) {
1124            final String ident = sbn.getPackageName() + "/0x" + Integer.toHexString(sbn.getId());
1125            Log.e(TAG, "couldn't inflate view for notification " + ident, e);
1126            return false;
1127        }
1128
1129        if (contentViewLocal != null) {
1130            contentViewLocal.setIsRootNamespace(true);
1131            expanded.setContractedChild(contentViewLocal);
1132        }
1133        if (bigContentViewLocal != null) {
1134            bigContentViewLocal.setIsRootNamespace(true);
1135            expanded.setExpandedChild(bigContentViewLocal);
1136        }
1137
1138        // now the public version
1139        View publicViewLocal = null;
1140        if (publicNotification != null) {
1141            try {
1142                publicViewLocal = publicNotification.contentView.apply(mContext, expandedPublic,
1143                        mOnClickHandler);
1144
1145                if (publicViewLocal != null) {
1146                    publicViewLocal.setIsRootNamespace(true);
1147                    expandedPublic.setContractedChild(publicViewLocal);
1148                }
1149            }
1150            catch (RuntimeException e) {
1151                final String ident = sbn.getPackageName() + "/0x" + Integer.toHexString(sbn.getId());
1152                Log.e(TAG, "couldn't inflate public view for notification " + ident, e);
1153                publicViewLocal = null;
1154            }
1155        }
1156
1157        if (publicViewLocal == null) {
1158            // Add a basic notification template
1159            publicViewLocal = LayoutInflater.from(mContext).inflate(
1160                    com.android.internal.R.layout.notification_template_material_base,
1161                    expandedPublic, true);
1162
1163            final TextView title = (TextView) publicViewLocal.findViewById(com.android.internal.R.id.title);
1164            try {
1165                title.setText(pmUser.getApplicationLabel(
1166                        pmUser.getApplicationInfo(entry.notification.getPackageName(), 0)));
1167            } catch (NameNotFoundException e) {
1168                title.setText(entry.notification.getPackageName());
1169            }
1170
1171            final ImageView icon = (ImageView) publicViewLocal.findViewById(
1172                    com.android.internal.R.id.icon);
1173            final ImageView profileIcon = (ImageView) publicViewLocal.findViewById(
1174                    com.android.internal.R.id.profile_icon);
1175
1176            final StatusBarIcon ic = new StatusBarIcon(entry.notification.getPackageName(),
1177                    entry.notification.getUser(),
1178                    entry.notification.getNotification().icon,
1179                    entry.notification.getNotification().iconLevel,
1180                    entry.notification.getNotification().number,
1181                    entry.notification.getNotification().tickerText);
1182
1183            Drawable iconDrawable = StatusBarIconView.getIcon(mContext, ic);
1184            icon.setImageDrawable(iconDrawable);
1185            if (mNotificationColorUtil.isGrayscale(iconDrawable)) {
1186                icon.setBackgroundResource(
1187                        com.android.internal.R.drawable.notification_icon_legacy_bg_inset);
1188            }
1189
1190            if (profileIcon != null) {
1191                Drawable profileDrawable
1192                        = mUserManager.getBadgeForUser(entry.notification.getUser(), 0);
1193                if (profileDrawable != null) {
1194                    profileIcon.setImageDrawable(profileDrawable);
1195                    profileIcon.setVisibility(View.VISIBLE);
1196                } else {
1197                    profileIcon.setVisibility(View.GONE);
1198                }
1199            }
1200
1201            final View privateTime = contentViewLocal.findViewById(com.android.internal.R.id.time);
1202            if (privateTime != null && privateTime.getVisibility() == View.VISIBLE) {
1203                final View timeStub = publicViewLocal.findViewById(com.android.internal.R.id.time);
1204                timeStub.setVisibility(View.VISIBLE);
1205                final DateTimeView dateTimeView = (DateTimeView)
1206                        publicViewLocal.findViewById(com.android.internal.R.id.time);
1207                dateTimeView.setTime(entry.notification.getNotification().when);
1208            }
1209
1210            final TextView text = (TextView) publicViewLocal.findViewById(
1211                com.android.internal.R.id.text);
1212            if (text != null) {
1213                text.setText(R.string.notification_hidden_text);
1214                text.setTextAppearance(mContext,
1215                        R.style.TextAppearance_StatusBar_Material_EventContent_Parenthetical);
1216            }
1217
1218            entry.autoRedacted = true;
1219        }
1220
1221        row.setClearable(sbn.isClearable());
1222
1223        if (MULTIUSER_DEBUG) {
1224            TextView debug = (TextView) row.findViewById(R.id.debug_info);
1225            if (debug != null) {
1226                debug.setVisibility(View.VISIBLE);
1227                debug.setText("CU " + mCurrentUserId +" NU " + entry.notification.getUserId());
1228            }
1229        }
1230        entry.row = row;
1231        entry.row.setHeightRange(mRowMinHeight, maxHeight);
1232        entry.row.setOnActivatedListener(this);
1233        entry.expanded = contentViewLocal;
1234        entry.expandedPublic = publicViewLocal;
1235        entry.setBigContentView(bigContentViewLocal);
1236
1237        applyLegacyRowBackground(sbn, entry);
1238
1239        // Restore previous flags.
1240        if (hasUserChangedExpansion) {
1241            // Note: setUserExpanded() conveniently ignores calls with
1242            //       userExpanded=true if !isExpandable().
1243            row.setUserExpanded(userExpanded);
1244        }
1245        row.setUserLocked(userLocked);
1246
1247        return true;
1248    }
1249
1250    public NotificationClicker makeClicker(PendingIntent intent, String notificationKey,
1251            boolean forHun) {
1252        return new NotificationClicker(intent, notificationKey, forHun);
1253    }
1254
1255    protected class NotificationClicker implements View.OnClickListener {
1256        private PendingIntent mIntent;
1257        private final String mNotificationKey;
1258        private boolean mIsHeadsUp;
1259
1260        public NotificationClicker(PendingIntent intent, String notificationKey, boolean forHun) {
1261            mIntent = intent;
1262            mNotificationKey = notificationKey;
1263            mIsHeadsUp = forHun;
1264        }
1265
1266        public void onClick(final View v) {
1267            final boolean keyguardShowing = mStatusBarKeyguardViewManager.isShowing();
1268            dismissKeyguardThenExecute(new OnDismissAction() {
1269                public boolean onDismiss() {
1270                    if (mIsHeadsUp) {
1271                        mHeadsUpNotificationView.clear();
1272                    }
1273                    AsyncTask.execute(new Runnable() {
1274                        @Override
1275                        public void run() {
1276                            if (keyguardShowing) {
1277                                try {
1278                                    ActivityManagerNative.getDefault()
1279                                            .keyguardWaitingForActivityDrawn();
1280                                    // The intent we are sending is for the application, which
1281                                    // won't have permission to immediately start an activity after
1282                                    // the user switches to home.  We know it is safe to do at this
1283                                    // point, so make sure new activity switches are now allowed.
1284                                    ActivityManagerNative.getDefault().resumeAppSwitches();
1285                                } catch (RemoteException e) {
1286                                }
1287                            }
1288
1289                            if (mIntent != null) {
1290                                try {
1291                                    mIntent.send();
1292                                } catch (PendingIntent.CanceledException e) {
1293                                    // the stack trace isn't very helpful here.
1294                                    // Just log the exception message.
1295                                    Log.w(TAG, "Sending contentIntent failed: " + e);
1296
1297                                    // TODO: Dismiss Keyguard.
1298                                }
1299                                if (mIntent.isActivity()) {
1300                                    overrideActivityPendingAppTransition(keyguardShowing);
1301                                }
1302                            }
1303
1304                            try {
1305                                mBarService.onNotificationClick(mNotificationKey);
1306                            } catch (RemoteException ex) {
1307                                // system process is dead if we're here.
1308                            }
1309                        }
1310                    });
1311
1312                    // close the shade if it was open
1313                    animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE, true /* force */);
1314                    visibilityChanged(false);
1315
1316                    return mIntent != null && mIntent.isActivity();
1317                }
1318            });
1319        }
1320    }
1321
1322    public void animateCollapsePanels(int flags, boolean force) {
1323    }
1324
1325    public void overrideActivityPendingAppTransition(boolean keyguardShowing) {
1326        if (keyguardShowing) {
1327            try {
1328                mWindowManagerService.overridePendingAppTransition(null, 0, 0, null);
1329            } catch (RemoteException e) {
1330                Log.w(TAG, "Error overriding app transition: " + e);
1331            }
1332        }
1333    }
1334
1335    /**
1336     * The LEDs are turned o)ff when the notification panel is shown, even just a little bit.
1337     * This was added last-minute and is inconsistent with the way the rest of the notifications
1338     * are handled, because the notification isn't really cancelled.  The lights are just
1339     * turned off.  If any other notifications happen, the lights will turn back on.  Steve says
1340     * this is what he wants. (see bug 1131461)
1341     */
1342    protected void visibilityChanged(boolean visible) {
1343        if (mPanelSlightlyVisible != visible) {
1344            mPanelSlightlyVisible = visible;
1345            if (!visible) {
1346                dismissPopups();
1347            }
1348            try {
1349                if (visible) {
1350                    mBarService.onPanelRevealed();
1351                } else {
1352                    mBarService.onPanelHidden();
1353                }
1354            } catch (RemoteException ex) {
1355                // Won't fail unless the world has ended.
1356            }
1357        }
1358    }
1359
1360    /**
1361     * Cancel this notification and tell the StatusBarManagerService / NotificationManagerService
1362     * about the failure.
1363     *
1364     * WARNING: this will call back into us.  Don't hold any locks.
1365     */
1366    void handleNotificationError(StatusBarNotification n, String message) {
1367        removeNotification(n.getKey(), null);
1368        try {
1369            mBarService.onNotificationError(n.getPackageName(), n.getTag(), n.getId(), n.getUid(),
1370                    n.getInitialPid(), message, n.getUserId());
1371        } catch (RemoteException ex) {
1372            // The end is nigh.
1373        }
1374    }
1375
1376    protected StatusBarNotification removeNotificationViews(String key, RankingMap ranking) {
1377        NotificationData.Entry entry = mNotificationData.remove(key, ranking);
1378        if (entry == null) {
1379            Log.w(TAG, "removeNotification for unknown key: " + key);
1380            return null;
1381        }
1382        updateNotifications();
1383        return entry.notification;
1384    }
1385
1386    protected NotificationData.Entry createNotificationViews(StatusBarNotification sbn) {
1387        if (DEBUG) {
1388            Log.d(TAG, "createNotificationViews(notification=" + sbn);
1389        }
1390        // Construct the icon.
1391        Notification n = sbn.getNotification();
1392        final StatusBarIconView iconView = new StatusBarIconView(mContext,
1393                sbn.getPackageName() + "/0x" + Integer.toHexString(sbn.getId()), n);
1394        iconView.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
1395
1396        final StatusBarIcon ic = new StatusBarIcon(sbn.getPackageName(),
1397                sbn.getUser(),
1398                    n.icon,
1399                    n.iconLevel,
1400                    n.number,
1401                    n.tickerText);
1402        if (!iconView.set(ic)) {
1403            handleNotificationError(sbn, "Couldn't create icon: " + ic);
1404            return null;
1405        }
1406        // Construct the expanded view.
1407        NotificationData.Entry entry = new NotificationData.Entry(sbn, iconView);
1408        if (!inflateViews(entry, mStackScroller)) {
1409            handleNotificationError(sbn, "Couldn't expand RemoteViews for: " + sbn);
1410            return null;
1411        }
1412        return entry;
1413    }
1414
1415    protected void addNotificationViews(Entry entry, RankingMap ranking) {
1416        if (entry == null) {
1417            return;
1418        }
1419        // Add the expanded view and icon.
1420        mNotificationData.add(entry, ranking);
1421        updateNotifications();
1422    }
1423
1424    /**
1425     * @return The number of notifications we show on Keyguard.
1426     */
1427    protected abstract int getMaxKeyguardNotifications();
1428
1429    /**
1430     * Updates expanded, dimmed and locked states of notification rows.
1431     */
1432    protected void updateRowStates() {
1433        int maxKeyguardNotifications = getMaxKeyguardNotifications();
1434        mKeyguardIconOverflowContainer.getIconsView().removeAllViews();
1435
1436        ArrayList<Entry> activeNotifications = mNotificationData.getActiveNotifications();
1437        final int N = activeNotifications.size();
1438
1439        int visibleNotifications = 0;
1440        boolean onKeyguard = mState == StatusBarState.KEYGUARD;
1441        for (int i = 0; i < N; i++) {
1442            NotificationData.Entry entry = activeNotifications.get(i);
1443            if (onKeyguard) {
1444                entry.row.setExpansionDisabled(true);
1445            } else {
1446                entry.row.setExpansionDisabled(false);
1447                if (!entry.row.isUserLocked()) {
1448                    boolean top = (i == 0);
1449                    entry.row.setSystemExpanded(top);
1450                }
1451            }
1452            boolean showOnKeyguard = shouldShowOnKeyguard(entry.notification);
1453            if (onKeyguard && (visibleNotifications >= maxKeyguardNotifications
1454                    || !showOnKeyguard)) {
1455                entry.row.setVisibility(View.GONE);
1456                if (showOnKeyguard) {
1457                    mKeyguardIconOverflowContainer.getIconsView().addNotification(entry);
1458                }
1459            } else {
1460                boolean wasGone = entry.row.getVisibility() == View.GONE;
1461                entry.row.setVisibility(View.VISIBLE);
1462                if (wasGone) {
1463                    // notify the scroller of a child addition
1464                    mStackScroller.generateAddAnimation(entry.row, true /* fromMoreCard */);
1465                }
1466                visibleNotifications++;
1467            }
1468        }
1469
1470        if (onKeyguard && mKeyguardIconOverflowContainer.getIconsView().getChildCount() > 0) {
1471            mKeyguardIconOverflowContainer.setVisibility(View.VISIBLE);
1472        } else {
1473            mKeyguardIconOverflowContainer.setVisibility(View.GONE);
1474        }
1475
1476        mStackScroller.changeViewPosition(mKeyguardIconOverflowContainer,
1477                mStackScroller.getChildCount() - 3);
1478        mStackScroller.changeViewPosition(mEmptyShadeView, mStackScroller.getChildCount() - 2);
1479        mStackScroller.changeViewPosition(mDismissView, mStackScroller.getChildCount() - 1);
1480    }
1481
1482    private boolean shouldShowOnKeyguard(StatusBarNotification sbn) {
1483        return mShowLockscreenNotifications && !mNotificationData.isAmbient(sbn.getKey());
1484    }
1485
1486    protected void setZenMode(int mode) {
1487        if (!isDeviceProvisioned()) return;
1488        mZenMode = mode;
1489        updateNotifications();
1490    }
1491
1492    // extended in PhoneStatusBar
1493    protected void setShowLockscreenNotifications(boolean show) {
1494        mShowLockscreenNotifications = show;
1495    }
1496
1497    private void updateLockscreenNotificationSetting() {
1498        final boolean show = Settings.Secure.getIntForUser(mContext.getContentResolver(),
1499                Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS,
1500                1,
1501                mCurrentUserId) != 0;
1502        final int dpmFlags = mDevicePolicyManager.getKeyguardDisabledFeatures(
1503                null /* admin */, mCurrentUserId);
1504        final boolean allowedByDpm = (dpmFlags
1505                & DevicePolicyManager.KEYGUARD_DISABLE_SECURE_NOTIFICATIONS) == 0;
1506        setShowLockscreenNotifications(show && allowedByDpm);
1507    }
1508
1509    protected abstract void haltTicker();
1510    protected abstract void setAreThereNotifications();
1511    protected abstract void updateNotifications();
1512    protected abstract void tick(StatusBarNotification n, boolean firstTime);
1513    protected abstract void updateExpandedViewPos(int expandedPosition);
1514    protected abstract boolean shouldDisableNavbarGestures();
1515
1516    public abstract void addNotification(StatusBarNotification notification,
1517            RankingMap ranking);
1518    protected abstract void updateNotificationRanking(RankingMap ranking);
1519    public abstract void removeNotification(String key, RankingMap ranking);
1520
1521    public void updateNotification(StatusBarNotification notification, RankingMap ranking) {
1522        if (DEBUG) Log.d(TAG, "updateNotification(" + notification + ")");
1523
1524        final String key = notification.getKey();
1525        boolean wasHeadsUp = isHeadsUp(key);
1526        Entry oldEntry;
1527        if (wasHeadsUp) {
1528            oldEntry = mHeadsUpNotificationView.getEntry();
1529        } else {
1530            oldEntry = mNotificationData.get(key);
1531        }
1532        if (oldEntry == null) {
1533            return;
1534        }
1535
1536        final StatusBarNotification oldNotification = oldEntry.notification;
1537
1538        // XXX: modify when we do something more intelligent with the two content views
1539        final RemoteViews oldContentView = oldNotification.getNotification().contentView;
1540        Notification n = notification.getNotification();
1541        final RemoteViews contentView = n.contentView;
1542        final RemoteViews oldBigContentView = oldNotification.getNotification().bigContentView;
1543        final RemoteViews bigContentView = n.bigContentView;
1544        final RemoteViews oldHeadsUpContentView = oldNotification.getNotification().headsUpContentView;
1545        final RemoteViews headsUpContentView = n.headsUpContentView;
1546        final Notification oldPublicNotification = oldNotification.getNotification().publicVersion;
1547        final RemoteViews oldPublicContentView = oldPublicNotification != null
1548                ? oldPublicNotification.contentView : null;
1549        final Notification publicNotification = n.publicVersion;
1550        final RemoteViews publicContentView = publicNotification != null
1551                ? publicNotification.contentView : null;
1552
1553        if (DEBUG) {
1554            Log.d(TAG, "old notification: when=" + oldNotification.getNotification().when
1555                    + " ongoing=" + oldNotification.isOngoing()
1556                    + " expanded=" + oldEntry.expanded
1557                    + " contentView=" + oldContentView
1558                    + " bigContentView=" + oldBigContentView
1559                    + " publicView=" + oldPublicContentView
1560                    + " rowParent=" + oldEntry.row.getParent());
1561            Log.d(TAG, "new notification: when=" + n.when
1562                    + " ongoing=" + oldNotification.isOngoing()
1563                    + " contentView=" + contentView
1564                    + " bigContentView=" + bigContentView
1565                    + " publicView=" + publicContentView);
1566        }
1567
1568        // Can we just reapply the RemoteViews in place?
1569
1570        // 1U is never null
1571        boolean contentsUnchanged = oldEntry.expanded != null
1572                && contentView.getPackage() != null
1573                && oldContentView.getPackage() != null
1574                && oldContentView.getPackage().equals(contentView.getPackage())
1575                && oldContentView.getLayoutId() == contentView.getLayoutId();
1576        // large view may be null
1577        boolean bigContentsUnchanged =
1578                (oldEntry.getBigContentView() == null && bigContentView == null)
1579                || ((oldEntry.getBigContentView() != null && bigContentView != null)
1580                    && bigContentView.getPackage() != null
1581                    && oldBigContentView.getPackage() != null
1582                    && oldBigContentView.getPackage().equals(bigContentView.getPackage())
1583                    && oldBigContentView.getLayoutId() == bigContentView.getLayoutId());
1584        boolean headsUpContentsUnchanged =
1585                (oldHeadsUpContentView == null && headsUpContentView == null)
1586                || ((oldHeadsUpContentView != null && headsUpContentView != null)
1587                    && headsUpContentView.getPackage() != null
1588                    && oldHeadsUpContentView.getPackage() != null
1589                    && oldHeadsUpContentView.getPackage().equals(headsUpContentView.getPackage())
1590                    && oldHeadsUpContentView.getLayoutId() == headsUpContentView.getLayoutId());
1591        boolean publicUnchanged  =
1592                (oldPublicContentView == null && publicContentView == null)
1593                || ((oldPublicContentView != null && publicContentView != null)
1594                        && publicContentView.getPackage() != null
1595                        && oldPublicContentView.getPackage() != null
1596                        && oldPublicContentView.getPackage().equals(publicContentView.getPackage())
1597                        && oldPublicContentView.getLayoutId() == publicContentView.getLayoutId());
1598        boolean updateTicker = n.tickerText != null
1599                && !TextUtils.equals(n.tickerText,
1600                oldEntry.notification.getNotification().tickerText);
1601
1602        final boolean shouldInterrupt = shouldInterrupt(notification);
1603        final boolean alertAgain = alertAgain(oldEntry);
1604        boolean updateSuccessful = false;
1605        if (contentsUnchanged && bigContentsUnchanged && headsUpContentsUnchanged
1606                && publicUnchanged) {
1607            if (DEBUG) Log.d(TAG, "reusing notification for key: " + key);
1608            oldEntry.notification = notification;
1609            try {
1610                if (oldEntry.icon != null) {
1611                    // Update the icon
1612                    final StatusBarIcon ic = new StatusBarIcon(notification.getPackageName(),
1613                            notification.getUser(),
1614                            n.icon,
1615                            n.iconLevel,
1616                            n.number,
1617                            n.tickerText);
1618                    oldEntry.icon.setNotification(n);
1619                    if (!oldEntry.icon.set(ic)) {
1620                        handleNotificationError(notification, "Couldn't update icon: " + ic);
1621                        return;
1622                    }
1623                }
1624
1625                if (wasHeadsUp) {
1626                    if (shouldInterrupt) {
1627                        updateHeadsUpViews(oldEntry, notification);
1628                        if (alertAgain) {
1629                            resetHeadsUpDecayTimer();
1630                        }
1631                    } else {
1632                        // we updated the notification above, so release to build a new shade entry
1633                        mHeadsUpNotificationView.releaseAndClose();
1634                        return;
1635                    }
1636                } else {
1637                    if (shouldInterrupt && alertAgain) {
1638                        removeNotificationViews(key, ranking);
1639                        addNotification(notification, ranking);  //this will pop the headsup
1640                    } else {
1641                        updateNotificationViews(oldEntry, notification);
1642                    }
1643                }
1644                mNotificationData.updateRanking(ranking);
1645                updateNotifications();
1646                updateSuccessful = true;
1647            }
1648            catch (RuntimeException e) {
1649                // It failed to add cleanly.  Log, and remove the view from the panel.
1650                Log.w(TAG, "Couldn't reapply views for package " + contentView.getPackage(), e);
1651            }
1652        }
1653        if (!updateSuccessful) {
1654            if (DEBUG) Log.d(TAG, "not reusing notification for key: " + key);
1655            if (wasHeadsUp) {
1656                if (shouldInterrupt) {
1657                    if (DEBUG) Log.d(TAG, "rebuilding heads up for key: " + key);
1658                    Entry newEntry = new Entry(notification, null);
1659                    ViewGroup holder = mHeadsUpNotificationView.getHolder();
1660                    if (inflateViewsForHeadsUp(newEntry, holder)) {
1661                        mHeadsUpNotificationView.showNotification(newEntry);
1662                        if (alertAgain) {
1663                            resetHeadsUpDecayTimer();
1664                        }
1665                    } else {
1666                        Log.w(TAG, "Couldn't create new updated headsup for package "
1667                                + contentView.getPackage());
1668                    }
1669                } else {
1670                    if (DEBUG) Log.d(TAG, "releasing heads up for key: " + key);
1671                    oldEntry.notification = notification;
1672                    mHeadsUpNotificationView.releaseAndClose();
1673                    return;
1674                }
1675            } else {
1676                if (shouldInterrupt && alertAgain) {
1677                    if (DEBUG) Log.d(TAG, "reposting to invoke heads up for key: " + key);
1678                    removeNotificationViews(key, ranking);
1679                    addNotification(notification, ranking);  //this will pop the headsup
1680                } else {
1681                    if (DEBUG) Log.d(TAG, "rebuilding update in place for key: " + key);
1682                    oldEntry.notification = notification;
1683                    final StatusBarIcon ic = new StatusBarIcon(notification.getPackageName(),
1684                            notification.getUser(),
1685                            n.icon,
1686                            n.iconLevel,
1687                            n.number,
1688                            n.tickerText);
1689                    oldEntry.icon.setNotification(n);
1690                    oldEntry.icon.set(ic);
1691                    inflateViews(oldEntry, mStackScroller, wasHeadsUp);
1692                    mNotificationData.updateRanking(ranking);
1693                    updateNotifications();
1694                }
1695            }
1696        }
1697
1698        // Update the veto button accordingly (and as a result, whether this row is
1699        // swipe-dismissable)
1700        updateNotificationVetoButton(oldEntry.row, notification);
1701
1702        // Is this for you?
1703        boolean isForCurrentUser = isNotificationForCurrentProfiles(notification);
1704        if (DEBUG) Log.d(TAG, "notification is " + (isForCurrentUser ? "" : "not ") + "for you");
1705
1706        // Restart the ticker if it's still running
1707        if (updateTicker && isForCurrentUser) {
1708            haltTicker();
1709            tick(notification, false);
1710        }
1711
1712        // Recalculate the position of the sliding windows and the titles.
1713        setAreThereNotifications();
1714        updateExpandedViewPos(EXPANDED_LEAVE_ALONE);
1715    }
1716
1717    private void updateNotificationViews(NotificationData.Entry entry,
1718            StatusBarNotification notification) {
1719        updateNotificationViews(entry, notification, false);
1720    }
1721
1722    private void updateHeadsUpViews(NotificationData.Entry entry,
1723            StatusBarNotification notification) {
1724        updateNotificationViews(entry, notification, true);
1725    }
1726
1727    private void updateNotificationViews(NotificationData.Entry entry,
1728            StatusBarNotification notification, boolean isHeadsUp) {
1729        final RemoteViews contentView = notification.getNotification().contentView;
1730        final RemoteViews bigContentView = isHeadsUp
1731                ? notification.getNotification().headsUpContentView
1732                : notification.getNotification().bigContentView;
1733        final Notification publicVersion = notification.getNotification().publicVersion;
1734        final RemoteViews publicContentView = publicVersion != null ? publicVersion.contentView
1735                : null;
1736
1737        // Reapply the RemoteViews
1738        contentView.reapply(mContext, entry.expanded, mOnClickHandler);
1739        if (bigContentView != null && entry.getBigContentView() != null) {
1740            bigContentView.reapply(mContext, entry.getBigContentView(),
1741                    mOnClickHandler);
1742        }
1743        if (publicContentView != null && entry.getPublicContentView() != null) {
1744            publicContentView.reapply(mContext, entry.getPublicContentView(), mOnClickHandler);
1745        }
1746        // update the contentIntent
1747        final PendingIntent contentIntent = notification.getNotification().contentIntent;
1748        if (contentIntent != null) {
1749            final View.OnClickListener listener = makeClicker(contentIntent, notification.getKey(),
1750                    isHeadsUp);
1751            entry.row.setOnClickListener(listener);
1752        } else {
1753            entry.row.setOnClickListener(null);
1754        }
1755        entry.row.notifyContentUpdated();
1756    }
1757
1758    protected void notifyHeadsUpScreenOn(boolean screenOn) {
1759        if (!screenOn) {
1760            scheduleHeadsUpEscalation();
1761        }
1762    }
1763
1764    private boolean alertAgain(Entry entry) {
1765        final StatusBarNotification sbn = entry.notification;
1766        return entry == null || !entry.hasInterrupted()
1767                || (sbn.getNotification().flags & Notification.FLAG_ONLY_ALERT_ONCE) == 0;
1768    }
1769
1770    protected boolean shouldInterrupt(StatusBarNotification sbn) {
1771        Notification notification = sbn.getNotification();
1772        // some predicates to make the boolean logic legible
1773        boolean isNoisy = (notification.defaults & Notification.DEFAULT_SOUND) != 0
1774                || (notification.defaults & Notification.DEFAULT_VIBRATE) != 0
1775                || notification.sound != null
1776                || notification.vibrate != null;
1777        boolean isHighPriority = sbn.getScore() >= INTERRUPTION_THRESHOLD;
1778        boolean isFullscreen = notification.fullScreenIntent != null;
1779        boolean hasTicker = mHeadsUpTicker && !TextUtils.isEmpty(notification.tickerText);
1780        boolean isAllowed = notification.extras.getInt(Notification.EXTRA_AS_HEADS_UP,
1781                Notification.HEADS_UP_ALLOWED) != Notification.HEADS_UP_NEVER;
1782
1783        final KeyguardTouchDelegate keyguard = KeyguardTouchDelegate.getInstance(mContext);
1784        boolean interrupt = (isFullscreen || (isHighPriority && (isNoisy || hasTicker)))
1785                && isAllowed
1786                && mPowerManager.isScreenOn()
1787                && !keyguard.isShowingAndNotOccluded()
1788                && !keyguard.isInputRestricted();
1789        try {
1790            interrupt = interrupt && !mDreamManager.isDreaming();
1791        } catch (RemoteException e) {
1792            Log.d(TAG, "failed to query dream manager", e);
1793        }
1794        if (DEBUG) Log.d(TAG, "interrupt: " + interrupt);
1795        return interrupt;
1796    }
1797
1798    public boolean inKeyguardRestrictedInputMode() {
1799        return KeyguardTouchDelegate.getInstance(mContext).isInputRestricted();
1800    }
1801
1802    public void setInteracting(int barWindow, boolean interacting) {
1803        // hook for subclasses
1804    }
1805
1806    public void setBouncerShowing(boolean bouncerShowing) {
1807        mBouncerShowing = bouncerShowing;
1808    }
1809
1810    /**
1811     * @return Whether the security bouncer from Keyguard is showing.
1812     */
1813    public boolean isBouncerShowing() {
1814        return mBouncerShowing;
1815    }
1816
1817    public void destroy() {
1818        if (mSearchPanelView != null) {
1819            mWindowManager.removeViewImmediate(mSearchPanelView);
1820        }
1821        mContext.unregisterReceiver(mBroadcastReceiver);
1822        try {
1823            mNotificationListener.unregisterAsSystemService();
1824        } catch (RemoteException e) {
1825            // Ignore.
1826        }
1827    }
1828
1829    /**
1830     * @return a PackageManger for userId or if userId is < 0 (USER_ALL etc) then
1831     *         return PackageManager for mContext
1832     */
1833    protected PackageManager getPackageManagerForUser(int userId) {
1834        Context contextForUser = mContext;
1835        // UserHandle defines special userId as negative values, e.g. USER_ALL
1836        if (userId >= 0) {
1837            try {
1838                // Create a context for the correct user so if a package isn't installed
1839                // for user 0 we can still load information about the package.
1840                contextForUser =
1841                        mContext.createPackageContextAsUser(mContext.getPackageName(),
1842                        Context.CONTEXT_RESTRICTED,
1843                        new UserHandle(userId));
1844            } catch (NameNotFoundException e) {
1845                // Shouldn't fail to find the package name for system ui.
1846            }
1847        }
1848        return contextForUser.getPackageManager();
1849    }
1850
1851    @Override
1852    public void logNotificationExpansion(String key, boolean userAction, boolean expanded) {
1853        try {
1854            mBarService.onNotificationExpansionChanged(key, userAction, expanded);
1855        } catch (RemoteException e) {
1856            // Ignore.
1857        }
1858    }
1859}
1860