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