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