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