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