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