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