BaseStatusBar.java revision b605fecd9e4282823af754b3968b21205d64554a
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.app.ActivityManager;
20import android.app.ActivityManagerNative;
21import android.app.Notification;
22import android.app.PendingIntent;
23import android.app.TaskStackBuilder;
24import android.content.BroadcastReceiver;
25import android.content.Context;
26import android.content.Intent;
27import android.content.IntentFilter;
28import android.content.pm.ApplicationInfo;
29import android.content.pm.PackageManager;
30import android.content.pm.PackageManager.NameNotFoundException;
31import android.content.pm.UserInfo;
32import android.content.res.Configuration;
33import android.database.ContentObserver;
34import android.graphics.Rect;
35import android.graphics.drawable.Drawable;
36import android.net.Uri;
37import android.os.Build;
38import android.os.Handler;
39import android.os.IBinder;
40import android.os.Message;
41import android.os.PowerManager;
42import android.os.RemoteException;
43import android.os.ServiceManager;
44import android.os.UserHandle;
45import android.os.UserManager;
46import android.provider.Settings;
47import android.service.dreams.DreamService;
48import android.service.dreams.IDreamManager;
49import android.service.notification.StatusBarNotification;
50import android.text.TextUtils;
51import android.util.Log;
52import android.util.SparseArray;
53import android.util.SparseBooleanArray;
54import android.view.Display;
55import android.view.IWindowManager;
56import android.view.LayoutInflater;
57import android.view.MenuItem;
58import android.view.MotionEvent;
59import android.view.View;
60import android.view.ViewGroup;
61import android.view.ViewGroup.LayoutParams;
62import android.view.WindowManager;
63import android.view.WindowManagerGlobal;
64import android.widget.ImageView;
65import android.widget.LinearLayout;
66import android.widget.PopupMenu;
67import android.widget.RemoteViews;
68import android.widget.TextView;
69
70import com.android.internal.statusbar.IStatusBarService;
71import com.android.internal.statusbar.StatusBarIcon;
72import com.android.internal.statusbar.StatusBarIconList;
73import com.android.internal.util.LegacyNotificationUtil;
74import com.android.internal.widget.SizeAdaptiveLayout;
75import com.android.systemui.R;
76import com.android.systemui.RecentsComponent;
77import com.android.systemui.SearchPanelView;
78import com.android.systemui.SystemUI;
79import com.android.systemui.statusbar.phone.KeyguardTouchDelegate;
80import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
81
82import java.util.ArrayList;
83import java.util.Locale;
84
85public abstract class BaseStatusBar extends SystemUI implements
86        CommandQueue.Callbacks, ActivatableNotificationView.OnActivatedListener {
87    public static final String TAG = "StatusBar";
88    public static final boolean DEBUG = false;
89    public static final boolean MULTIUSER_DEBUG = false;
90
91    protected static final int MSG_TOGGLE_RECENTS_PANEL = 1020;
92    protected static final int MSG_CLOSE_RECENTS_PANEL = 1021;
93    protected static final int MSG_PRELOAD_RECENT_APPS = 1022;
94    protected static final int MSG_CANCEL_PRELOAD_RECENT_APPS = 1023;
95    protected static final int MSG_OPEN_SEARCH_PANEL = 1024;
96    protected static final int MSG_CLOSE_SEARCH_PANEL = 1025;
97    protected static final int MSG_SHOW_HEADS_UP = 1026;
98    protected static final int MSG_HIDE_HEADS_UP = 1027;
99    protected static final int MSG_ESCALATE_HEADS_UP = 1028;
100
101    protected static final boolean ENABLE_HEADS_UP = true;
102    // scores above this threshold should be displayed in heads up mode.
103    protected static final int INTERRUPTION_THRESHOLD = 10;
104    protected static final String SETTING_HEADS_UP_TICKER = "ticker_gets_heads_up";
105
106    // Should match the value in PhoneWindowManager
107    public static final String SYSTEM_DIALOG_REASON_RECENT_APPS = "recentapps";
108
109    public static final int EXPANDED_LEAVE_ALONE = -10000;
110    public static final int EXPANDED_FULL_OPEN = -10001;
111
112    protected CommandQueue mCommandQueue;
113    protected IStatusBarService mBarService;
114    protected H mHandler = createHandler();
115
116    // all notifications
117    protected NotificationData mNotificationData = new NotificationData();
118    protected NotificationStackScrollLayout mStackScroller;
119
120    protected NotificationData.Entry mInterruptingNotificationEntry;
121    protected long mInterruptingNotificationTime;
122
123    // used to notify status bar for suppressing notification LED
124    protected boolean mPanelSlightlyVisible;
125
126    // Search panel
127    protected SearchPanelView mSearchPanelView;
128
129    protected PopupMenu mNotificationBlamePopup;
130
131    protected int mCurrentUserId = 0;
132    final protected SparseArray<UserInfo> mCurrentProfiles = new SparseArray<UserInfo>();
133
134    protected int mLayoutDirection = -1; // invalid
135    private Locale mLocale;
136    protected boolean mUseHeadsUp = false;
137    protected boolean mHeadsUpTicker = false;
138
139    protected IDreamManager mDreamManager;
140    PowerManager mPowerManager;
141    protected int mRowMinHeight;
142    protected int mRowMaxHeight;
143
144    // public mode, private notifications, etc
145    private boolean mLockscreenPublicMode = false;
146    private final SparseBooleanArray mUsersAllowingPrivateNotifications = new SparseBooleanArray();
147    private LegacyNotificationUtil mLegacyNotificationUtil = LegacyNotificationUtil.getInstance();
148
149    private UserManager mUserManager;
150
151    // UI-specific methods
152
153    /**
154     * Create all windows necessary for the status bar (including navigation, overlay panels, etc)
155     * and add them to the window manager.
156     */
157    protected abstract void createAndAddWindows();
158
159    protected WindowManager mWindowManager;
160    protected IWindowManager mWindowManagerService;
161    protected abstract void refreshLayout(int layoutDirection);
162
163    protected Display mDisplay;
164
165    private boolean mDeviceProvisioned = false;
166
167    private RecentsComponent mRecents;
168
169    protected int mZenMode;
170
171    protected boolean mOnKeyguard;
172    protected NotificationOverflowContainer mKeyguardIconOverflowContainer;
173
174    public boolean isDeviceProvisioned() {
175        return mDeviceProvisioned;
176    }
177
178    protected final ContentObserver mSettingsObserver = new ContentObserver(mHandler) {
179        @Override
180        public void onChange(boolean selfChange) {
181            final boolean provisioned = 0 != Settings.Global.getInt(
182                    mContext.getContentResolver(), Settings.Global.DEVICE_PROVISIONED, 0);
183            if (provisioned != mDeviceProvisioned) {
184                mDeviceProvisioned = provisioned;
185                updateNotificationIcons();
186            }
187            final int mode = Settings.Global.getInt(mContext.getContentResolver(),
188                    Settings.Global.ZEN_MODE, Settings.Global.ZEN_MODE_OFF);
189            setZenMode(mode);
190        }
191    };
192
193    private final ContentObserver mLockscreenSettingsObserver = new ContentObserver(mHandler) {
194        @Override
195        public void onChange(boolean selfChange) {
196            // We don't know which user changed LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS,
197            // so we just dump our cache ...
198            mUsersAllowingPrivateNotifications.clear();
199            // ... and refresh all the notifications
200            updateNotificationIcons();
201        }
202    };
203
204    private RemoteViews.OnClickHandler mOnClickHandler = new RemoteViews.OnClickHandler() {
205        @Override
206        public boolean onClickHandler(View view, PendingIntent pendingIntent, Intent fillInIntent) {
207            if (DEBUG) {
208                Log.v(TAG, "Notification click handler invoked for intent: " + pendingIntent);
209            }
210            final boolean isActivity = pendingIntent.isActivity();
211            if (isActivity) {
212                try {
213                    // The intent we are sending is for the application, which
214                    // won't have permission to immediately start an activity after
215                    // the user switches to home.  We know it is safe to do at this
216                    // point, so make sure new activity switches are now allowed.
217                    ActivityManagerNative.getDefault().resumeAppSwitches();
218                    // Also, notifications can be launched from the lock screen,
219                    // so dismiss the lock screen when the activity starts.
220                    ActivityManagerNative.getDefault().dismissKeyguardOnNextActivity();
221                } catch (RemoteException e) {
222                }
223            }
224
225            boolean handled = super.onClickHandler(view, pendingIntent, fillInIntent);
226
227            if (isActivity && handled) {
228                // close the shade if it was open
229                animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE);
230                visibilityChanged(false);
231            }
232            return handled;
233        }
234    };
235
236    private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
237        @Override
238        public void onReceive(Context context, Intent intent) {
239            String action = intent.getAction();
240            if (Intent.ACTION_USER_SWITCHED.equals(action)) {
241                mCurrentUserId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
242                updateCurrentProfilesCache();
243                if (true) Log.v(TAG, "userId " + mCurrentUserId + " is in the house");
244                userSwitched(mCurrentUserId);
245            } else if (Intent.ACTION_USER_ADDED.equals(action)) {
246                updateCurrentProfilesCache();
247            }
248        }
249    };
250
251    private void updateCurrentProfilesCache() {
252        synchronized (mCurrentProfiles) {
253            mCurrentProfiles.clear();
254            if (mUserManager != null) {
255                for (UserInfo user : mUserManager.getProfiles(mCurrentUserId)) {
256                    mCurrentProfiles.put(user.id, user);
257                }
258            }
259        }
260    }
261
262    public void start() {
263        mWindowManager = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
264        mWindowManagerService = WindowManagerGlobal.getWindowManagerService();
265        mDisplay = mWindowManager.getDefaultDisplay();
266
267        mDreamManager = IDreamManager.Stub.asInterface(
268                ServiceManager.checkService(DreamService.DREAM_SERVICE));
269        mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
270
271        mSettingsObserver.onChange(false); // set up
272        mContext.getContentResolver().registerContentObserver(
273                Settings.Global.getUriFor(Settings.Global.DEVICE_PROVISIONED), true,
274                mSettingsObserver);
275        mContext.getContentResolver().registerContentObserver(
276                Settings.Global.getUriFor(Settings.Global.ZEN_MODE), false,
277                mSettingsObserver);
278
279        mContext.getContentResolver().registerContentObserver(
280                Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS),
281                true,
282                mLockscreenSettingsObserver,
283                UserHandle.USER_ALL);
284
285        mBarService = IStatusBarService.Stub.asInterface(
286                ServiceManager.getService(Context.STATUS_BAR_SERVICE));
287
288        mRecents = getComponent(RecentsComponent.class);
289
290        mLocale = mContext.getResources().getConfiguration().locale;
291        mLayoutDirection = TextUtils.getLayoutDirectionFromLocale(mLocale);
292
293        mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
294
295        // Connect in to the status bar manager service
296        StatusBarIconList iconList = new StatusBarIconList();
297        ArrayList<IBinder> notificationKeys = new ArrayList<IBinder>();
298        ArrayList<StatusBarNotification> notifications = new ArrayList<StatusBarNotification>();
299        mCommandQueue = new CommandQueue(this, iconList);
300
301        int[] switches = new int[8];
302        ArrayList<IBinder> binders = new ArrayList<IBinder>();
303        try {
304            mBarService.registerStatusBar(mCommandQueue, iconList, notificationKeys, notifications,
305                    switches, binders);
306        } catch (RemoteException ex) {
307            // If the system process isn't there we're doomed anyway.
308        }
309
310        createAndAddWindows();
311
312        disable(switches[0]);
313        setSystemUiVisibility(switches[1], 0xffffffff);
314        topAppWindowChanged(switches[2] != 0);
315        // StatusBarManagerService has a back up of IME token and it's restored here.
316        setImeWindowStatus(binders.get(0), switches[3], switches[4], switches[7] != 0);
317        setHardKeyboardStatus(switches[5] != 0, switches[6] != 0);
318
319        // Set up the initial icon state
320        int N = iconList.size();
321        int viewIndex = 0;
322        for (int i=0; i<N; i++) {
323            StatusBarIcon icon = iconList.getIcon(i);
324            if (icon != null) {
325                addIcon(iconList.getSlot(i), i, viewIndex, icon);
326                viewIndex++;
327            }
328        }
329
330        // Set up the initial notification state
331        N = notificationKeys.size();
332        if (N == notifications.size()) {
333            for (int i=0; i<N; i++) {
334                addNotification(notificationKeys.get(i), notifications.get(i));
335            }
336        } else {
337            Log.wtf(TAG, "Notification list length mismatch: keys=" + N
338                    + " notifications=" + notifications.size());
339        }
340
341        if (DEBUG) {
342            Log.d(TAG, String.format(
343                    "init: icons=%d disabled=0x%08x lights=0x%08x menu=0x%08x imeButton=0x%08x",
344                   iconList.size(),
345                   switches[0],
346                   switches[1],
347                   switches[2],
348                   switches[3]
349                   ));
350        }
351
352        mCurrentUserId = ActivityManager.getCurrentUser();
353
354        IntentFilter filter = new IntentFilter();
355        filter.addAction(Intent.ACTION_USER_SWITCHED);
356        filter.addAction(Intent.ACTION_USER_ADDED);
357        mContext.registerReceiver(mBroadcastReceiver, filter);
358
359        updateCurrentProfilesCache();
360    }
361
362    public void userSwitched(int newUserId) {
363        // should be overridden
364    }
365
366    public boolean notificationIsForCurrentProfiles(StatusBarNotification n) {
367        final int thisUserId = mCurrentUserId;
368        final int notificationUserId = n.getUserId();
369        if (DEBUG && MULTIUSER_DEBUG) {
370            Log.v(TAG, String.format("%s: current userid: %d, notification userid: %d",
371                    n, thisUserId, notificationUserId));
372        }
373        synchronized (mCurrentProfiles) {
374            return notificationUserId == UserHandle.USER_ALL
375                    || mCurrentProfiles.get(notificationUserId) != null;
376        }
377    }
378
379    @Override
380    protected void onConfigurationChanged(Configuration newConfig) {
381        final Locale locale = mContext.getResources().getConfiguration().locale;
382        final int ld = TextUtils.getLayoutDirectionFromLocale(locale);
383        if (! locale.equals(mLocale) || ld != mLayoutDirection) {
384            if (DEBUG) {
385                Log.v(TAG, String.format(
386                        "config changed locale/LD: %s (%d) -> %s (%d)", mLocale, mLayoutDirection,
387                        locale, ld));
388            }
389            mLocale = locale;
390            mLayoutDirection = ld;
391            refreshLayout(ld);
392        }
393    }
394
395    protected View updateNotificationVetoButton(View row, StatusBarNotification n) {
396        View vetoButton = row.findViewById(R.id.veto);
397        if (n.isClearable() || (mInterruptingNotificationEntry != null
398                && mInterruptingNotificationEntry.row == row)) {
399            final String _pkg = n.getPackageName();
400            final String _tag = n.getTag();
401            final int _id = n.getId();
402            final int _userId = n.getUserId();
403            vetoButton.setOnClickListener(new View.OnClickListener() {
404                    public void onClick(View v) {
405                        // Accessibility feedback
406                        v.announceForAccessibility(
407                                mContext.getString(R.string.accessibility_notification_dismissed));
408                        try {
409                            mBarService.onNotificationClear(_pkg, _tag, _id, _userId);
410
411                        } catch (RemoteException ex) {
412                            // system process is dead if we're here.
413                        }
414                    }
415                });
416            vetoButton.setVisibility(View.VISIBLE);
417        } else {
418            vetoButton.setVisibility(View.GONE);
419        }
420        vetoButton.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
421        return vetoButton;
422    }
423
424
425    protected void applyLegacyRowBackground(StatusBarNotification sbn,
426            NotificationData.Entry entry) {
427        if (entry.expanded.getId() != com.android.internal.R.id.status_bar_latest_event_content) {
428            int version = 0;
429            try {
430                ApplicationInfo info = mContext.getPackageManager().getApplicationInfo(sbn.getPackageName(), 0);
431                version = info.targetSdkVersion;
432            } catch (NameNotFoundException ex) {
433                Log.e(TAG, "Failed looking up ApplicationInfo for " + sbn.getPackageName(), ex);
434            }
435            if (version > 0 && version < Build.VERSION_CODES.GINGERBREAD) {
436                entry.row.setBackgroundResource(R.drawable.notification_row_legacy_bg);
437            } else if (version < Build.VERSION_CODES.L) {
438                entry.row.setBackgroundResourceIds(
439                        com.android.internal.R.drawable.notification_bg,
440                        com.android.internal.R.drawable.notification_bg_dim);
441            }
442        }
443    }
444
445    private void startApplicationDetailsActivity(String packageName) {
446        Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
447                Uri.fromParts("package", packageName, null));
448        intent.setComponent(intent.resolveActivity(mContext.getPackageManager()));
449        TaskStackBuilder.create(mContext).addNextIntentWithParentStack(intent).startActivities(
450                null, UserHandle.CURRENT);
451    }
452
453    protected View.OnLongClickListener getNotificationLongClicker() {
454        return new View.OnLongClickListener() {
455            @Override
456            public boolean onLongClick(View v) {
457                final String packageNameF = (String) v.getTag();
458                if (packageNameF == null) return false;
459                if (v.getWindowToken() == null) return false;
460                mNotificationBlamePopup = new PopupMenu(mContext, v);
461                mNotificationBlamePopup.getMenuInflater().inflate(
462                        R.menu.notification_popup_menu,
463                        mNotificationBlamePopup.getMenu());
464                mNotificationBlamePopup.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
465                    public boolean onMenuItemClick(MenuItem item) {
466                        if (item.getItemId() == R.id.notification_inspect_item) {
467                            startApplicationDetailsActivity(packageNameF);
468                            animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE);
469                        } else {
470                            return false;
471                        }
472                        return true;
473                    }
474                });
475                mNotificationBlamePopup.show();
476
477                return true;
478            }
479        };
480    }
481
482    public void dismissPopups() {
483        if (mNotificationBlamePopup != null) {
484            mNotificationBlamePopup.dismiss();
485            mNotificationBlamePopup = null;
486        }
487    }
488
489    public void onHeadsUpDismissed() {
490    }
491
492    @Override
493    public void toggleRecentApps() {
494        int msg = MSG_TOGGLE_RECENTS_PANEL;
495        mHandler.removeMessages(msg);
496        mHandler.sendEmptyMessage(msg);
497    }
498
499    @Override
500    public void preloadRecentApps() {
501        int msg = MSG_PRELOAD_RECENT_APPS;
502        mHandler.removeMessages(msg);
503        mHandler.sendEmptyMessage(msg);
504    }
505
506    @Override
507    public void cancelPreloadRecentApps() {
508        int msg = MSG_CANCEL_PRELOAD_RECENT_APPS;
509        mHandler.removeMessages(msg);
510        mHandler.sendEmptyMessage(msg);
511    }
512
513    @Override
514    public void showSearchPanel() {
515        int msg = MSG_OPEN_SEARCH_PANEL;
516        mHandler.removeMessages(msg);
517        mHandler.sendEmptyMessage(msg);
518    }
519
520    @Override
521    public void hideSearchPanel() {
522        int msg = MSG_CLOSE_SEARCH_PANEL;
523        mHandler.removeMessages(msg);
524        mHandler.sendEmptyMessage(msg);
525    }
526
527    protected abstract WindowManager.LayoutParams getSearchLayoutParams(
528            LayoutParams layoutParams);
529
530    protected void updateSearchPanel() {
531        // Search Panel
532        boolean visible = false;
533        if (mSearchPanelView != null) {
534            visible = mSearchPanelView.isShowing();
535            mWindowManager.removeView(mSearchPanelView);
536        }
537
538        // Provide SearchPanel with a temporary parent to allow layout params to work.
539        LinearLayout tmpRoot = new LinearLayout(mContext);
540        mSearchPanelView = (SearchPanelView) LayoutInflater.from(mContext).inflate(
541                 R.layout.status_bar_search_panel, tmpRoot, false);
542        mSearchPanelView.setOnTouchListener(
543                 new TouchOutsideListener(MSG_CLOSE_SEARCH_PANEL, mSearchPanelView));
544        mSearchPanelView.setVisibility(View.GONE);
545
546        WindowManager.LayoutParams lp = getSearchLayoutParams(mSearchPanelView.getLayoutParams());
547
548        mWindowManager.addView(mSearchPanelView, lp);
549        mSearchPanelView.setBar(this);
550        if (visible) {
551            mSearchPanelView.show(true, false);
552        }
553    }
554
555    protected H createHandler() {
556         return new H();
557    }
558
559    static void sendCloseSystemWindows(Context context, String reason) {
560        if (ActivityManagerNative.isSystemReady()) {
561            try {
562                ActivityManagerNative.getDefault().closeSystemDialogs(reason);
563            } catch (RemoteException e) {
564            }
565        }
566    }
567
568    protected abstract View getStatusBarView();
569
570    protected View.OnTouchListener mRecentsPreloadOnTouchListener = new View.OnTouchListener() {
571        // additional optimization when we have software system buttons - start loading the recent
572        // tasks on touch down
573        @Override
574        public boolean onTouch(View v, MotionEvent event) {
575            int action = event.getAction() & MotionEvent.ACTION_MASK;
576            if (action == MotionEvent.ACTION_DOWN) {
577                preloadRecentTasksList();
578            } else if (action == MotionEvent.ACTION_CANCEL) {
579                cancelPreloadingRecentTasksList();
580            } else if (action == MotionEvent.ACTION_UP) {
581                if (!v.isPressed()) {
582                    cancelPreloadingRecentTasksList();
583                }
584
585            }
586            return false;
587        }
588    };
589
590    protected void toggleRecentsActivity() {
591        if (mRecents != null) {
592            sendCloseSystemWindows(mContext, SYSTEM_DIALOG_REASON_RECENT_APPS);
593            mRecents.toggleRecents(mDisplay, mLayoutDirection, getStatusBarView());
594        }
595    }
596
597    protected void preloadRecentTasksList() {
598        if (mRecents != null) {
599            mRecents.preloadRecentTasksList();
600        }
601    }
602
603    protected void cancelPreloadingRecentTasksList() {
604        if (mRecents != null) {
605            mRecents.cancelPreloadingRecentTasksList();
606        }
607    }
608
609    protected void closeRecents() {
610        if (mRecents != null) {
611            mRecents.closeRecents();
612        }
613    }
614
615    public abstract void resetHeadsUpDecayTimer();
616
617    /**
618     * Save the current "public" (locked and secure) state of the lockscreen.
619     */
620    public void setLockscreenPublicMode(boolean publicMode) {
621        mLockscreenPublicMode = publicMode;
622    }
623
624    public boolean isLockscreenPublicMode() {
625        return mLockscreenPublicMode;
626    }
627
628    /**
629     * Has the given user chosen to allow their private (full) notifications to be shown even
630     * when the lockscreen is in "public" (secure & locked) mode?
631     */
632    public boolean userAllowsPrivateNotificationsInPublic(int userHandle) {
633        if (userHandle == UserHandle.USER_ALL) {
634            return true;
635        }
636
637        if (mUsersAllowingPrivateNotifications.indexOfKey(userHandle) < 0) {
638            final boolean allowed = 0 != Settings.Secure.getIntForUser(
639                    mContext.getContentResolver(),
640                    Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0, userHandle);
641            mUsersAllowingPrivateNotifications.append(userHandle, allowed);
642            return allowed;
643        }
644
645        return mUsersAllowingPrivateNotifications.get(userHandle);
646    }
647
648    protected class H extends Handler {
649        public void handleMessage(Message m) {
650            Intent intent;
651            switch (m.what) {
652             case MSG_TOGGLE_RECENTS_PANEL:
653                 toggleRecentsActivity();
654                 break;
655             case MSG_CLOSE_RECENTS_PANEL:
656                 closeRecents();
657                 break;
658             case MSG_PRELOAD_RECENT_APPS:
659                  preloadRecentTasksList();
660                  break;
661             case MSG_CANCEL_PRELOAD_RECENT_APPS:
662                  cancelPreloadingRecentTasksList();
663                  break;
664             case MSG_OPEN_SEARCH_PANEL:
665                 if (DEBUG) Log.d(TAG, "opening search panel");
666                 if (mSearchPanelView != null && mSearchPanelView.isAssistantAvailable()) {
667                     mSearchPanelView.show(true, true);
668                     onShowSearchPanel();
669                 }
670                 break;
671             case MSG_CLOSE_SEARCH_PANEL:
672                 if (DEBUG) Log.d(TAG, "closing search panel");
673                 if (mSearchPanelView != null && mSearchPanelView.isShowing()) {
674                     mSearchPanelView.show(false, true);
675                     onHideSearchPanel();
676                 }
677                 break;
678            }
679        }
680    }
681
682    public class TouchOutsideListener implements View.OnTouchListener {
683        private int mMsg;
684        private StatusBarPanel mPanel;
685
686        public TouchOutsideListener(int msg, StatusBarPanel panel) {
687            mMsg = msg;
688            mPanel = panel;
689        }
690
691        public boolean onTouch(View v, MotionEvent ev) {
692            final int action = ev.getAction();
693            if (action == MotionEvent.ACTION_OUTSIDE
694                || (action == MotionEvent.ACTION_DOWN
695                    && !mPanel.isInContentArea((int)ev.getX(), (int)ev.getY()))) {
696                mHandler.removeMessages(mMsg);
697                mHandler.sendEmptyMessage(mMsg);
698                return true;
699            }
700            return false;
701        }
702    }
703
704    protected void workAroundBadLayerDrawableOpacity(View v) {
705    }
706
707    protected void onHideSearchPanel() {
708    }
709
710    protected void onShowSearchPanel() {
711    }
712
713    public boolean inflateViews(NotificationData.Entry entry, ViewGroup parent) {
714            return inflateViews(entry, parent, false);
715    }
716
717    public boolean inflateViewsForHeadsUp(NotificationData.Entry entry, ViewGroup parent) {
718            return inflateViews(entry, parent, true);
719    }
720
721    public boolean inflateViews(NotificationData.Entry entry, ViewGroup parent, boolean isHeadsUp) {
722        int minHeight =
723                mContext.getResources().getDimensionPixelSize(R.dimen.notification_min_height);
724        int maxHeight =
725                mContext.getResources().getDimensionPixelSize(R.dimen.notification_max_height);
726        StatusBarNotification sbn = entry.notification;
727        RemoteViews contentView = sbn.getNotification().contentView;
728        RemoteViews bigContentView = sbn.getNotification().bigContentView;
729
730        if (isHeadsUp) {
731            maxHeight =
732                    mContext.getResources().getDimensionPixelSize(R.dimen.notification_mid_height);
733            bigContentView = sbn.getNotification().headsUpContentView;
734        }
735
736        if (contentView == null) {
737            return false;
738        }
739
740        if (DEBUG) {
741            Log.v(TAG, "publicNotification: " + sbn.getNotification().publicVersion);
742        }
743
744        Notification publicNotification = sbn.getNotification().publicVersion;
745
746        // create the row view
747        LayoutInflater inflater = (LayoutInflater)mContext.getSystemService(
748                Context.LAYOUT_INFLATER_SERVICE);
749        ExpandableNotificationRow row = (ExpandableNotificationRow) inflater.inflate(
750                R.layout.status_bar_notification_row, parent, false);
751
752        // for blaming (see SwipeHelper.setLongPressListener)
753        row.setTag(sbn.getPackageName());
754
755        workAroundBadLayerDrawableOpacity(row);
756        View vetoButton = updateNotificationVetoButton(row, sbn);
757        vetoButton.setContentDescription(mContext.getString(
758                R.string.accessibility_remove_notification));
759
760        // NB: the large icon is now handled entirely by the template
761
762        // bind the click event to the content area
763        SizeAdaptiveLayout expanded = (SizeAdaptiveLayout)row.findViewById(R.id.expanded);
764        SizeAdaptiveLayout expandedPublic
765                = (SizeAdaptiveLayout)row.findViewById(R.id.expandedPublic);
766
767        row.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
768
769        PendingIntent contentIntent = sbn.getNotification().contentIntent;
770        if (contentIntent != null) {
771            final View.OnClickListener listener = makeClicker(contentIntent,
772                    sbn.getPackageName(), sbn.getTag(), sbn.getId(), isHeadsUp, sbn.getUserId());
773            row.setOnClickListener(listener);
774        } else {
775            row.setOnClickListener(null);
776        }
777
778        // set up the adaptive layout
779        View contentViewLocal = null;
780        View bigContentViewLocal = null;
781        try {
782            contentViewLocal = contentView.apply(mContext, expanded,
783                    mOnClickHandler);
784            if (bigContentView != null) {
785                bigContentViewLocal = bigContentView.apply(mContext, expanded,
786                        mOnClickHandler);
787            }
788        }
789        catch (RuntimeException e) {
790            final String ident = sbn.getPackageName() + "/0x" + Integer.toHexString(sbn.getId());
791            Log.e(TAG, "couldn't inflate view for notification " + ident, e);
792            return false;
793        }
794
795        if (contentViewLocal != null) {
796            contentViewLocal.setIsRootNamespace(true);
797            SizeAdaptiveLayout.LayoutParams params =
798                    new SizeAdaptiveLayout.LayoutParams(contentViewLocal.getLayoutParams());
799            params.minHeight = minHeight;
800            params.maxHeight = minHeight;
801            expanded.addView(contentViewLocal, params);
802        }
803        if (bigContentViewLocal != null) {
804            bigContentViewLocal.setIsRootNamespace(true);
805            SizeAdaptiveLayout.LayoutParams params =
806                    new SizeAdaptiveLayout.LayoutParams(bigContentViewLocal.getLayoutParams());
807            params.minHeight = minHeight+1;
808            params.maxHeight = maxHeight;
809            expanded.addView(bigContentViewLocal, params);
810        }
811
812        PackageManager pm = mContext.getPackageManager();
813
814        // now the public version
815        View publicViewLocal = null;
816        if (publicNotification != null) {
817            try {
818                publicViewLocal = publicNotification.contentView.apply(mContext, expandedPublic,
819                        mOnClickHandler);
820
821                if (publicViewLocal != null) {
822                    publicViewLocal.setIsRootNamespace(true);
823                    SizeAdaptiveLayout.LayoutParams params =
824                            new SizeAdaptiveLayout.LayoutParams(publicViewLocal.getLayoutParams());
825                    params.minHeight = minHeight;
826                    params.maxHeight = minHeight;
827                    expandedPublic.addView(publicViewLocal, params);
828                }
829            }
830            catch (RuntimeException e) {
831                final String ident = sbn.getPackageName() + "/0x" + Integer.toHexString(sbn.getId());
832                Log.e(TAG, "couldn't inflate public view for notification " + ident, e);
833                publicViewLocal = null;
834            }
835        }
836
837        if (publicViewLocal == null) {
838            // Add a basic notification template
839            publicViewLocal = LayoutInflater.from(mContext).inflate(
840                    com.android.internal.R.layout.notification_template_quantum_base,
841                    expandedPublic, true);
842
843            final TextView title = (TextView) publicViewLocal.findViewById(com.android.internal.R.id.title);
844            try {
845                title.setText(pm.getApplicationLabel(
846                        pm.getApplicationInfo(entry.notification.getPackageName(), 0)));
847            } catch (NameNotFoundException e) {
848                title.setText(entry.notification.getPackageName());
849            }
850
851            final ImageView icon = (ImageView) publicViewLocal.findViewById(com.android.internal.R.id.icon);
852
853            final StatusBarIcon ic = new StatusBarIcon(entry.notification.getPackageName(),
854                    entry.notification.getUser(),
855                    entry.notification.getNotification().icon,
856                    entry.notification.getNotification().iconLevel,
857                    entry.notification.getNotification().number,
858                    entry.notification.getNotification().tickerText);
859
860            Drawable iconDrawable = StatusBarIconView.getIcon(mContext, ic);
861            icon.setImageDrawable(iconDrawable);
862            if (mLegacyNotificationUtil.isGrayscale(iconDrawable)) {
863                icon.setBackgroundResource(
864                        com.android.internal.R.drawable.notification_icon_legacy_bg_inset);
865            }
866
867            final TextView text = (TextView) publicViewLocal.findViewById(com.android.internal.R.id.text);
868            text.setText("Unlock your device to see this notification.");
869
870            // TODO: fill out "time" as well
871        }
872
873        row.setDrawingCacheEnabled(true);
874
875        if (MULTIUSER_DEBUG) {
876            TextView debug = (TextView) row.findViewById(R.id.debug_info);
877            if (debug != null) {
878                debug.setVisibility(View.VISIBLE);
879                debug.setText("CU " + mCurrentUserId +" NU " + entry.notification.getUserId());
880            }
881        }
882        entry.row = row;
883        entry.row.setHeightRange(mRowMinHeight, mRowMaxHeight);
884        entry.row.setOnActivatedListener(this);
885        entry.expanded = contentViewLocal;
886        entry.expandedPublic = publicViewLocal;
887        entry.setBigContentView(bigContentViewLocal);
888
889        applyLegacyRowBackground(sbn, entry);
890
891        return true;
892    }
893
894    public NotificationClicker makeClicker(PendingIntent intent, String pkg, String tag,
895            int id, boolean forHun, int userId) {
896        return new NotificationClicker(intent, pkg, tag, id, forHun, userId);
897    }
898
899    protected class NotificationClicker implements View.OnClickListener {
900        private PendingIntent mIntent;
901        private String mPkg;
902        private String mTag;
903        private int mId;
904        private boolean mIsHeadsUp;
905        private int mUserId;
906
907        public NotificationClicker(PendingIntent intent, String pkg, String tag, int id,
908                boolean forHun, int userId) {
909            mIntent = intent;
910            mPkg = pkg;
911            mTag = tag;
912            mId = id;
913            mIsHeadsUp = forHun;
914            mUserId = userId;
915        }
916
917        public void onClick(View v) {
918            try {
919                // The intent we are sending is for the application, which
920                // won't have permission to immediately start an activity after
921                // the user switches to home.  We know it is safe to do at this
922                // point, so make sure new activity switches are now allowed.
923                ActivityManagerNative.getDefault().resumeAppSwitches();
924                // Also, notifications can be launched from the lock screen,
925                // so dismiss the lock screen when the activity starts.
926                ActivityManagerNative.getDefault().dismissKeyguardOnNextActivity();
927            } catch (RemoteException e) {
928            }
929
930            if (mIntent != null) {
931                int[] pos = new int[2];
932                v.getLocationOnScreen(pos);
933                Intent overlay = new Intent();
934                overlay.setSourceBounds(
935                        new Rect(pos[0], pos[1], pos[0]+v.getWidth(), pos[1]+v.getHeight()));
936                try {
937                    mIntent.send(mContext, 0, overlay);
938                } catch (PendingIntent.CanceledException e) {
939                    // the stack trace isn't very helpful here.  Just log the exception message.
940                    Log.w(TAG, "Sending contentIntent failed: " + e);
941                }
942
943                KeyguardTouchDelegate.getInstance(mContext).dismiss();
944            }
945
946            try {
947                if (mIsHeadsUp) {
948                    mHandler.sendEmptyMessage(MSG_HIDE_HEADS_UP);
949                }
950                mBarService.onNotificationClick(mPkg, mTag, mId, mUserId);
951            } catch (RemoteException ex) {
952                // system process is dead if we're here.
953            }
954
955            // close the shade if it was open
956            animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE);
957            visibilityChanged(false);
958        }
959    }
960
961    /**
962     * The LEDs are turned o)ff when the notification panel is shown, even just a little bit.
963     * This was added last-minute and is inconsistent with the way the rest of the notifications
964     * are handled, because the notification isn't really cancelled.  The lights are just
965     * turned off.  If any other notifications happen, the lights will turn back on.  Steve says
966     * this is what he wants. (see bug 1131461)
967     */
968    protected void visibilityChanged(boolean visible) {
969        if (mPanelSlightlyVisible != visible) {
970            mPanelSlightlyVisible = visible;
971            try {
972                if (visible) {
973                    mBarService.onPanelRevealed();
974                } else {
975                    mBarService.onPanelHidden();
976                }
977            } catch (RemoteException ex) {
978                // Won't fail unless the world has ended.
979            }
980        }
981    }
982
983    /**
984     * Cancel this notification and tell the StatusBarManagerService / NotificationManagerService
985     * about the failure.
986     *
987     * WARNING: this will call back into us.  Don't hold any locks.
988     */
989    void handleNotificationError(IBinder key, StatusBarNotification n, String message) {
990        removeNotification(key);
991        try {
992            mBarService.onNotificationError(n.getPackageName(), n.getTag(), n.getId(), n.getUid(),
993                    n.getInitialPid(), message, n.getUserId());
994        } catch (RemoteException ex) {
995            // The end is nigh.
996        }
997    }
998
999    protected StatusBarNotification removeNotificationViews(IBinder key) {
1000        NotificationData.Entry entry = mNotificationData.remove(key);
1001        if (entry == null) {
1002            Log.w(TAG, "removeNotification for unknown key: " + key);
1003            return null;
1004        }
1005        // Remove the expanded view.
1006        ViewGroup rowParent = (ViewGroup)entry.row.getParent();
1007        if (rowParent != null) rowParent.removeView(entry.row);
1008        updateRowStates();
1009        updateNotificationIcons();
1010
1011        return entry.notification;
1012    }
1013
1014    protected NotificationData.Entry createNotificationViews(IBinder key,
1015            StatusBarNotification notification) {
1016        if (DEBUG) {
1017            Log.d(TAG, "createNotificationViews(key=" + key + ", notification=" + notification);
1018        }
1019        // Construct the icon.
1020        final StatusBarIconView iconView = new StatusBarIconView(mContext,
1021                notification.getPackageName() + "/0x" + Integer.toHexString(notification.getId()),
1022                notification.getNotification());
1023        iconView.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
1024
1025        final StatusBarIcon ic = new StatusBarIcon(notification.getPackageName(),
1026                notification.getUser(),
1027                    notification.getNotification().icon,
1028                    notification.getNotification().iconLevel,
1029                    notification.getNotification().number,
1030                    notification.getNotification().tickerText);
1031        if (!iconView.set(ic)) {
1032            handleNotificationError(key, notification, "Couldn't create icon: " + ic);
1033            return null;
1034        }
1035        // Construct the expanded view.
1036        NotificationData.Entry entry = new NotificationData.Entry(key, notification, iconView);
1037        if (!inflateViews(entry, mStackScroller)) {
1038            handleNotificationError(key, notification, "Couldn't expand RemoteViews for: "
1039                    + notification);
1040            return null;
1041        }
1042        return entry;
1043    }
1044
1045    protected void addNotificationViews(NotificationData.Entry entry) {
1046        // Add the expanded view and icon.
1047        int pos = mNotificationData.add(entry);
1048        if (DEBUG) {
1049            Log.d(TAG, "addNotificationViews: added at " + pos);
1050        }
1051        updateRowStates();
1052        updateNotificationIcons();
1053    }
1054
1055    private void addNotificationViews(IBinder key, StatusBarNotification notification) {
1056        addNotificationViews(createNotificationViews(key, notification));
1057    }
1058
1059    /**
1060     * @return The number of notifications we show on Keyguard.
1061     */
1062    protected abstract int getMaxKeyguardNotifications();
1063
1064    /**
1065     * Updates expanded, dimmed and locked states of notification rows.
1066     */
1067    protected void updateRowStates() {
1068        int maxKeyguardNotifications = getMaxKeyguardNotifications();
1069        mKeyguardIconOverflowContainer.getIconsView().removeAllViews();
1070        int n = mNotificationData.size();
1071        int visibleNotifications = 0;
1072        for (int i = n-1; i >= 0; i--) {
1073            NotificationData.Entry entry = mNotificationData.get(i);
1074            if (mOnKeyguard) {
1075                entry.row.setSystemExpanded(false);
1076            } else {
1077                if (!entry.row.isUserLocked()) {
1078                    boolean top = (i == n-1);
1079                    entry.row.setSystemExpanded(top || entry.row.isUserExpanded());
1080                }
1081            }
1082            entry.row.setDimmed(mOnKeyguard);
1083            entry.row.setLocked(mOnKeyguard);
1084            boolean showOnKeyguard = shouldShowOnKeyguard(entry.notification);
1085            if (mOnKeyguard && (visibleNotifications >= maxKeyguardNotifications
1086                    || !showOnKeyguard)) {
1087                entry.row.setVisibility(View.GONE);
1088                if (showOnKeyguard) {
1089                    mKeyguardIconOverflowContainer.getIconsView().addNotification(entry);
1090                }
1091            } else {
1092                entry.row.setVisibility(View.VISIBLE);
1093                visibleNotifications++;
1094            }
1095        }
1096
1097        if (mOnKeyguard && mKeyguardIconOverflowContainer.getIconsView().getChildCount() > 0) {
1098            mKeyguardIconOverflowContainer.setVisibility(View.VISIBLE);
1099        } else {
1100            mKeyguardIconOverflowContainer.setVisibility(View.GONE);
1101        }
1102    }
1103
1104    @Override
1105    public void onActivated(View view) {
1106        int n = mNotificationData.size();
1107        for (int i = 0; i < n; i++) {
1108            NotificationData.Entry entry = mNotificationData.get(i);
1109            if (entry.row.getVisibility() != View.GONE) {
1110                if (view == entry.row) {
1111                    entry.row.getActivator().activate();
1112                } else {
1113                    entry.row.getActivator().activateInverse();
1114                }
1115            }
1116        }
1117        if (mKeyguardIconOverflowContainer.getVisibility() != View.GONE) {
1118            if (view == mKeyguardIconOverflowContainer) {
1119                mKeyguardIconOverflowContainer.getActivator().activate();
1120            } else {
1121                mKeyguardIconOverflowContainer.getActivator().activateInverse();
1122            }
1123        }
1124    }
1125
1126    @Override
1127    public void onReset(View view) {
1128        int n = mNotificationData.size();
1129        for (int i = 0; i < n; i++) {
1130            NotificationData.Entry entry = mNotificationData.get(i);
1131            if (entry.row.getVisibility() != View.GONE) {
1132                entry.row.getActivator().reset();
1133            }
1134        }
1135        if (mKeyguardIconOverflowContainer.getVisibility() != View.GONE) {
1136            mKeyguardIconOverflowContainer.getActivator().reset();
1137        }
1138    }
1139
1140    private boolean shouldShowOnKeyguard(StatusBarNotification sbn) {
1141        return sbn.getNotification().priority >= Notification.PRIORITY_LOW;
1142    }
1143
1144    protected void setZenMode(int mode) {
1145        if (!isDeviceProvisioned()) return;
1146        mZenMode = mode;
1147        updateNotificationIcons();
1148    }
1149
1150    protected abstract void haltTicker();
1151    protected abstract void setAreThereNotifications();
1152    protected abstract void updateNotificationIcons();
1153    protected abstract void tick(IBinder key, StatusBarNotification n, boolean firstTime);
1154    protected abstract void updateExpandedViewPos(int expandedPosition);
1155    protected abstract int getExpandedViewMaxHeight();
1156    protected abstract boolean shouldDisableNavbarGestures();
1157
1158    protected boolean isTopNotification(ViewGroup parent, NotificationData.Entry entry) {
1159        return parent != null && parent.indexOfChild(entry.row) == 0;
1160    }
1161
1162    public void updateNotification(IBinder key, StatusBarNotification notification) {
1163        if (DEBUG) Log.d(TAG, "updateNotification(" + key + " -> " + notification + ")");
1164
1165        final NotificationData.Entry oldEntry = mNotificationData.findByKey(key);
1166        if (oldEntry == null) {
1167            Log.w(TAG, "updateNotification for unknown key: " + key);
1168            return;
1169        }
1170
1171        final StatusBarNotification oldNotification = oldEntry.notification;
1172
1173        // XXX: modify when we do something more intelligent with the two content views
1174        final RemoteViews oldContentView = oldNotification.getNotification().contentView;
1175        final RemoteViews contentView = notification.getNotification().contentView;
1176        final RemoteViews oldBigContentView = oldNotification.getNotification().bigContentView;
1177        final RemoteViews bigContentView = notification.getNotification().bigContentView;
1178        final RemoteViews oldHeadsUpContentView = oldNotification.getNotification().headsUpContentView;
1179        final RemoteViews headsUpContentView = notification.getNotification().headsUpContentView;
1180        final Notification oldPublicNotification = oldNotification.getNotification().publicVersion;
1181        final RemoteViews oldPublicContentView = oldPublicNotification != null
1182                ? oldPublicNotification.contentView : null;
1183        final Notification publicNotification = notification.getNotification().publicVersion;
1184        final RemoteViews publicContentView = publicNotification != null
1185                ? publicNotification.contentView : null;
1186
1187        if (DEBUG) {
1188            Log.d(TAG, "old notification: when=" + oldNotification.getNotification().when
1189                    + " ongoing=" + oldNotification.isOngoing()
1190                    + " expanded=" + oldEntry.expanded
1191                    + " contentView=" + oldContentView
1192                    + " bigContentView=" + oldBigContentView
1193                    + " publicView=" + oldPublicContentView
1194                    + " rowParent=" + oldEntry.row.getParent());
1195            Log.d(TAG, "new notification: when=" + notification.getNotification().when
1196                    + " ongoing=" + oldNotification.isOngoing()
1197                    + " contentView=" + contentView
1198                    + " bigContentView=" + bigContentView
1199                    + " publicView=" + publicContentView);
1200        }
1201
1202        // Can we just reapply the RemoteViews in place?  If when didn't change, the order
1203        // didn't change.
1204
1205        // 1U is never null
1206        boolean contentsUnchanged = oldEntry.expanded != null
1207                && contentView.getPackage() != null
1208                && oldContentView.getPackage() != null
1209                && oldContentView.getPackage().equals(contentView.getPackage())
1210                && oldContentView.getLayoutId() == contentView.getLayoutId();
1211        // large view may be null
1212        boolean bigContentsUnchanged =
1213                (oldEntry.getBigContentView() == null && bigContentView == null)
1214                || ((oldEntry.getBigContentView() != null && bigContentView != null)
1215                    && bigContentView.getPackage() != null
1216                    && oldBigContentView.getPackage() != null
1217                    && oldBigContentView.getPackage().equals(bigContentView.getPackage())
1218                    && oldBigContentView.getLayoutId() == bigContentView.getLayoutId());
1219        boolean headsUpContentsUnchanged =
1220                (oldHeadsUpContentView == null && headsUpContentView == null)
1221                || ((oldHeadsUpContentView != null && headsUpContentView != null)
1222                    && headsUpContentView.getPackage() != null
1223                    && oldHeadsUpContentView.getPackage() != null
1224                    && oldHeadsUpContentView.getPackage().equals(headsUpContentView.getPackage())
1225                    && oldHeadsUpContentView.getLayoutId() == headsUpContentView.getLayoutId());
1226        boolean publicUnchanged  =
1227                (oldPublicContentView == null && publicContentView == null)
1228                || ((oldPublicContentView != null && publicContentView != null)
1229                        && publicContentView.getPackage() != null
1230                        && oldPublicContentView.getPackage() != null
1231                        && oldPublicContentView.getPackage().equals(publicContentView.getPackage())
1232                        && oldPublicContentView.getLayoutId() == publicContentView.getLayoutId());
1233
1234        ViewGroup rowParent = (ViewGroup) oldEntry.row.getParent();
1235        boolean orderUnchanged =
1236                   notification.getNotification().when == oldNotification.getNotification().when
1237                && notification.getScore() == oldNotification.getScore();
1238                // score now encompasses/supersedes isOngoing()
1239
1240        boolean updateTicker = notification.getNotification().tickerText != null
1241                && !TextUtils.equals(notification.getNotification().tickerText,
1242                        oldEntry.notification.getNotification().tickerText);
1243        boolean isTopAnyway = isTopNotification(rowParent, oldEntry);
1244        if (contentsUnchanged && bigContentsUnchanged && headsUpContentsUnchanged && publicUnchanged
1245                && (orderUnchanged || isTopAnyway)) {
1246            if (DEBUG) Log.d(TAG, "reusing notification for key: " + key);
1247            oldEntry.notification = notification;
1248            try {
1249                updateNotificationViews(oldEntry, notification);
1250
1251                if (ENABLE_HEADS_UP && mInterruptingNotificationEntry != null
1252                        && oldNotification == mInterruptingNotificationEntry.notification) {
1253                    if (!shouldInterrupt(notification)) {
1254                        if (DEBUG) Log.d(TAG, "no longer interrupts!");
1255                        mHandler.sendEmptyMessage(MSG_HIDE_HEADS_UP);
1256                    } else {
1257                        if (DEBUG) Log.d(TAG, "updating the current heads up:" + notification);
1258                        mInterruptingNotificationEntry.notification = notification;
1259                        updateHeadsUpViews(mInterruptingNotificationEntry, notification);
1260                    }
1261                }
1262
1263                // Update the icon.
1264                final StatusBarIcon ic = new StatusBarIcon(notification.getPackageName(),
1265                        notification.getUser(),
1266                        notification.getNotification().icon, notification.getNotification().iconLevel,
1267                        notification.getNotification().number,
1268                        notification.getNotification().tickerText);
1269                if (!oldEntry.icon.set(ic)) {
1270                    handleNotificationError(key, notification, "Couldn't update icon: " + ic);
1271                    return;
1272                }
1273                updateRowStates();
1274            }
1275            catch (RuntimeException e) {
1276                // It failed to add cleanly.  Log, and remove the view from the panel.
1277                Log.w(TAG, "Couldn't reapply views for package " + contentView.getPackage(), e);
1278                removeNotificationViews(key);
1279                addNotificationViews(key, notification);
1280            }
1281        } else {
1282            if (DEBUG) Log.d(TAG, "not reusing notification for key: " + key);
1283            if (DEBUG) Log.d(TAG, "contents was " + (contentsUnchanged ? "unchanged" : "changed"));
1284            if (DEBUG) Log.d(TAG, "order was " + (orderUnchanged ? "unchanged" : "changed"));
1285            if (DEBUG) Log.d(TAG, "notification is " + (isTopAnyway ? "top" : "not top"));
1286            removeNotificationViews(key);
1287            addNotificationViews(key, notification);  // will also replace the heads up
1288            final NotificationData.Entry newEntry = mNotificationData.findByKey(key);
1289            final boolean userChangedExpansion = oldEntry.row.hasUserChangedExpansion();
1290            if (userChangedExpansion) {
1291                boolean userExpanded = oldEntry.row.isUserExpanded();
1292                newEntry.row.applyExpansionToLayout(userExpanded);
1293                newEntry.row.setUserExpanded(userExpanded);
1294            }
1295        }
1296
1297        // Update the veto button accordingly (and as a result, whether this row is
1298        // swipe-dismissable)
1299        updateNotificationVetoButton(oldEntry.row, notification);
1300
1301        // Is this for you?
1302        boolean isForCurrentUser = notificationIsForCurrentProfiles(notification);
1303        if (DEBUG) Log.d(TAG, "notification is " + (isForCurrentUser ? "" : "not ") + "for you");
1304
1305        // Restart the ticker if it's still running
1306        if (updateTicker && isForCurrentUser) {
1307            haltTicker();
1308            tick(key, notification, false);
1309        }
1310
1311        // Recalculate the position of the sliding windows and the titles.
1312        setAreThereNotifications();
1313        updateExpandedViewPos(EXPANDED_LEAVE_ALONE);
1314    }
1315
1316    private void updateNotificationViews(NotificationData.Entry entry,
1317            StatusBarNotification notification) {
1318        updateNotificationViews(entry, notification, false);
1319    }
1320
1321    private void updateHeadsUpViews(NotificationData.Entry entry,
1322            StatusBarNotification notification) {
1323        updateNotificationViews(entry, notification, true);
1324    }
1325
1326    private void updateNotificationViews(NotificationData.Entry entry,
1327            StatusBarNotification notification, boolean isHeadsUp) {
1328        final RemoteViews contentView = notification.getNotification().contentView;
1329        final RemoteViews bigContentView = isHeadsUp
1330                ? notification.getNotification().headsUpContentView
1331                : notification.getNotification().bigContentView;
1332        final Notification publicVersion = notification.getNotification().publicVersion;
1333        final RemoteViews publicContentView = publicVersion != null ? publicVersion.contentView
1334                : null;
1335
1336        // Reapply the RemoteViews
1337        contentView.reapply(mContext, entry.expanded, mOnClickHandler);
1338        if (bigContentView != null && entry.getBigContentView() != null) {
1339            bigContentView.reapply(mContext, entry.getBigContentView(),
1340                    mOnClickHandler);
1341        }
1342        if (publicContentView != null && entry.getPublicContentView() != null) {
1343            publicContentView.reapply(mContext, entry.getPublicContentView(), mOnClickHandler);
1344        }
1345        // update the contentIntent
1346        final PendingIntent contentIntent = notification.getNotification().contentIntent;
1347        if (contentIntent != null) {
1348            final View.OnClickListener listener = makeClicker(contentIntent,
1349                    notification.getPackageName(), notification.getTag(), notification.getId(),
1350                    isHeadsUp, notification.getUserId());
1351            entry.row.setOnClickListener(listener);
1352        } else {
1353            entry.row.setOnClickListener(null);
1354        }
1355    }
1356
1357    protected void notifyHeadsUpScreenOn(boolean screenOn) {
1358        if (!screenOn && mInterruptingNotificationEntry != null) {
1359            mHandler.sendEmptyMessage(MSG_ESCALATE_HEADS_UP);
1360        }
1361    }
1362
1363    protected boolean shouldInterrupt(StatusBarNotification sbn) {
1364        Notification notification = sbn.getNotification();
1365        // some predicates to make the boolean logic legible
1366        boolean isNoisy = (notification.defaults & Notification.DEFAULT_SOUND) != 0
1367                || (notification.defaults & Notification.DEFAULT_VIBRATE) != 0
1368                || notification.sound != null
1369                || notification.vibrate != null;
1370        boolean isHighPriority = sbn.getScore() >= INTERRUPTION_THRESHOLD;
1371        boolean isFullscreen = notification.fullScreenIntent != null;
1372        boolean hasTicker = mHeadsUpTicker && !TextUtils.isEmpty(notification.tickerText);
1373        boolean isAllowed = notification.extras.getInt(Notification.EXTRA_AS_HEADS_UP,
1374                Notification.HEADS_UP_ALLOWED) != Notification.HEADS_UP_NEVER;
1375
1376        final KeyguardTouchDelegate keyguard = KeyguardTouchDelegate.getInstance(mContext);
1377        boolean interrupt = (isFullscreen || (isHighPriority && (isNoisy || hasTicker)))
1378                && isAllowed
1379                && mPowerManager.isScreenOn()
1380                && !keyguard.isShowingAndNotOccluded()
1381                && !keyguard.isInputRestricted();
1382        try {
1383            interrupt = interrupt && !mDreamManager.isDreaming();
1384        } catch (RemoteException e) {
1385            Log.d(TAG, "failed to query dream manager", e);
1386        }
1387        if (DEBUG) Log.d(TAG, "interrupt: " + interrupt);
1388        return interrupt;
1389    }
1390
1391    // Q: What kinds of notifications should show during setup?
1392    // A: Almost none! Only things coming from the system (package is "android") that also
1393    // have special "kind" tags marking them as relevant for setup (see below).
1394    protected boolean showNotificationEvenIfUnprovisioned(StatusBarNotification sbn) {
1395        return "android".equals(sbn.getPackageName())
1396                && sbn.getNotification().extras.getBoolean(Notification.EXTRA_ALLOW_DURING_SETUP);
1397    }
1398
1399    public boolean inKeyguardRestrictedInputMode() {
1400        return KeyguardTouchDelegate.getInstance(mContext).isInputRestricted();
1401    }
1402
1403    public void setInteracting(int barWindow, boolean interacting) {
1404        // hook for subclasses
1405    }
1406
1407    public void destroy() {
1408        if (mSearchPanelView != null) {
1409            mWindowManager.removeViewImmediate(mSearchPanelView);
1410        }
1411        mContext.unregisterReceiver(mBroadcastReceiver);
1412    }
1413}
1414