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