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