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