OSDN Git Service

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