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