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