2 * Copyright (C) 2010 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.android.systemui.statusbar;
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.NameNotFoundException;
30 import android.content.res.Configuration;
31 import android.database.ContentObserver;
32 import android.graphics.Rect;
33 import android.net.Uri;
34 import android.os.Build;
35 import android.os.Handler;
36 import android.os.IBinder;
37 import android.os.Message;
38 import android.os.PowerManager;
39 import android.os.RemoteException;
40 import android.os.ServiceManager;
41 import android.os.UserHandle;
42 import android.provider.Settings;
43 import android.service.dreams.DreamService;
44 import android.service.dreams.IDreamManager;
45 import android.service.notification.StatusBarNotification;
46 import android.text.TextUtils;
47 import android.util.Log;
48 import android.view.Display;
49 import android.view.IWindowManager;
50 import android.view.LayoutInflater;
51 import android.view.MenuItem;
52 import android.view.MotionEvent;
53 import android.view.View;
54 import android.view.ViewGroup;
55 import android.view.ViewGroup.LayoutParams;
56 import android.view.WindowManager;
57 import android.view.WindowManagerGlobal;
58 import android.widget.ImageView;
59 import android.widget.LinearLayout;
60 import android.widget.PopupMenu;
61 import android.widget.RemoteViews;
62 import android.widget.TextView;
64 import com.android.internal.statusbar.IStatusBarService;
65 import com.android.internal.statusbar.StatusBarIcon;
66 import com.android.internal.statusbar.StatusBarIconList;
67 import com.android.internal.widget.SizeAdaptiveLayout;
68 import com.android.systemui.R;
69 import com.android.systemui.RecentsComponent;
70 import com.android.systemui.SearchPanelView;
71 import com.android.systemui.SystemUI;
72 import com.android.systemui.statusbar.phone.KeyguardTouchDelegate;
73 import com.android.systemui.statusbar.policy.NotificationRowLayout;
75 import java.util.ArrayList;
76 import java.util.Locale;
78 public abstract class BaseStatusBar extends SystemUI implements
79 CommandQueue.Callbacks {
80 public static final String TAG = "StatusBar";
81 public static final boolean DEBUG = false;
82 public static final boolean MULTIUSER_DEBUG = false;
84 protected static final int MSG_TOGGLE_RECENTS_PANEL = 1020;
85 protected static final int MSG_CLOSE_RECENTS_PANEL = 1021;
86 protected static final int MSG_PRELOAD_RECENT_APPS = 1022;
87 protected static final int MSG_CANCEL_PRELOAD_RECENT_APPS = 1023;
88 protected static final int MSG_OPEN_SEARCH_PANEL = 1024;
89 protected static final int MSG_CLOSE_SEARCH_PANEL = 1025;
90 protected static final int MSG_SHOW_HEADS_UP = 1026;
91 protected static final int MSG_HIDE_HEADS_UP = 1027;
92 protected static final int MSG_ESCALATE_HEADS_UP = 1028;
94 protected static final boolean ENABLE_HEADS_UP = true;
95 // scores above this threshold should be displayed in heads up mode.
96 protected static final int INTERRUPTION_THRESHOLD = 11;
97 protected static final String SETTING_HEADS_UP = "heads_up_enabled";
99 // Should match the value in PhoneWindowManager
100 public static final String SYSTEM_DIALOG_REASON_RECENT_APPS = "recentapps";
102 public static final int EXPANDED_LEAVE_ALONE = -10000;
103 public static final int EXPANDED_FULL_OPEN = -10001;
105 protected CommandQueue mCommandQueue;
106 protected IStatusBarService mBarService;
107 protected H mHandler = createHandler();
110 protected NotificationData mNotificationData = new NotificationData();
111 protected NotificationRowLayout mPile;
113 protected NotificationData.Entry mInterruptingNotificationEntry;
114 protected long mInterruptingNotificationTime;
116 // used to notify status bar for suppressing notification LED
117 protected boolean mPanelSlightlyVisible;
120 protected SearchPanelView mSearchPanelView;
122 protected PopupMenu mNotificationBlamePopup;
124 protected int mCurrentUserId = 0;
126 protected int mLayoutDirection = -1; // invalid
127 private Locale mLocale;
128 protected boolean mUseHeadsUp = false;
130 protected IDreamManager mDreamManager;
131 PowerManager mPowerManager;
132 protected int mRowHeight;
134 // UI-specific methods
137 * Create all windows necessary for the status bar (including navigation, overlay panels, etc)
138 * and add them to the window manager.
140 protected abstract void createAndAddWindows();
142 protected WindowManager mWindowManager;
143 protected IWindowManager mWindowManagerService;
144 protected abstract void refreshLayout(int layoutDirection);
146 protected Display mDisplay;
148 private boolean mDeviceProvisioned = false;
150 private RecentsComponent mRecents;
152 public IStatusBarService getStatusBarService() {
156 public boolean isDeviceProvisioned() {
157 return mDeviceProvisioned;
160 private ContentObserver mProvisioningObserver = new ContentObserver(new Handler()) {
162 public void onChange(boolean selfChange) {
163 final boolean provisioned = 0 != Settings.Global.getInt(
164 mContext.getContentResolver(), Settings.Global.DEVICE_PROVISIONED, 0);
165 if (provisioned != mDeviceProvisioned) {
166 mDeviceProvisioned = provisioned;
167 updateNotificationIcons();
172 private RemoteViews.OnClickHandler mOnClickHandler = new RemoteViews.OnClickHandler() {
174 public boolean onClickHandler(View view, PendingIntent pendingIntent, Intent fillInIntent) {
176 Log.v(TAG, "Notification click handler invoked for intent: " + pendingIntent);
178 final boolean isActivity = pendingIntent.isActivity();
181 // The intent we are sending is for the application, which
182 // won't have permission to immediately start an activity after
183 // the user switches to home. We know it is safe to do at this
184 // point, so make sure new activity switches are now allowed.
185 ActivityManagerNative.getDefault().resumeAppSwitches();
186 // Also, notifications can be launched from the lock screen,
187 // so dismiss the lock screen when the activity starts.
188 ActivityManagerNative.getDefault().dismissKeyguardOnNextActivity();
189 } catch (RemoteException e) {
193 boolean handled = super.onClickHandler(view, pendingIntent, fillInIntent);
195 if (isActivity && handled) {
196 // close the shade if it was open
197 animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE);
198 visibilityChanged(false);
204 private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
206 public void onReceive(Context context, Intent intent) {
207 String action = intent.getAction();
208 if (Intent.ACTION_USER_SWITCHED.equals(action)) {
209 mCurrentUserId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
210 if (true) Log.v(TAG, "userId " + mCurrentUserId + " is in the house");
211 userSwitched(mCurrentUserId);
216 public void start() {
217 mWindowManager = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
218 mWindowManagerService = WindowManagerGlobal.getWindowManagerService();
219 mDisplay = mWindowManager.getDefaultDisplay();
221 mDreamManager = IDreamManager.Stub.asInterface(
222 ServiceManager.checkService(DreamService.DREAM_SERVICE));
223 mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
225 mProvisioningObserver.onChange(false); // set up
226 mContext.getContentResolver().registerContentObserver(
227 Settings.Global.getUriFor(Settings.Global.DEVICE_PROVISIONED), true,
228 mProvisioningObserver);
230 mBarService = IStatusBarService.Stub.asInterface(
231 ServiceManager.getService(Context.STATUS_BAR_SERVICE));
233 mRecents = getComponent(RecentsComponent.class);
235 mLocale = mContext.getResources().getConfiguration().locale;
236 mLayoutDirection = TextUtils.getLayoutDirectionFromLocale(mLocale);
238 // Connect in to the status bar manager service
239 StatusBarIconList iconList = new StatusBarIconList();
240 ArrayList<IBinder> notificationKeys = new ArrayList<IBinder>();
241 ArrayList<StatusBarNotification> notifications = new ArrayList<StatusBarNotification>();
242 mCommandQueue = new CommandQueue(this, iconList);
244 int[] switches = new int[7];
245 ArrayList<IBinder> binders = new ArrayList<IBinder>();
247 mBarService.registerStatusBar(mCommandQueue, iconList, notificationKeys, notifications,
249 } catch (RemoteException ex) {
250 // If the system process isn't there we're doomed anyway.
253 createAndAddWindows();
255 disable(switches[0]);
256 setSystemUiVisibility(switches[1], 0xffffffff);
257 topAppWindowChanged(switches[2] != 0);
258 // StatusBarManagerService has a back up of IME token and it's restored here.
259 setImeWindowStatus(binders.get(0), switches[3], switches[4]);
260 setHardKeyboardStatus(switches[5] != 0, switches[6] != 0);
262 // Set up the initial icon state
263 int N = iconList.size();
265 for (int i=0; i<N; i++) {
266 StatusBarIcon icon = iconList.getIcon(i);
268 addIcon(iconList.getSlot(i), i, viewIndex, icon);
273 // Set up the initial notification state
274 N = notificationKeys.size();
275 if (N == notifications.size()) {
276 for (int i=0; i<N; i++) {
277 addNotification(notificationKeys.get(i), notifications.get(i));
280 Log.wtf(TAG, "Notification list length mismatch: keys=" + N
281 + " notifications=" + notifications.size());
285 Log.d(TAG, String.format(
286 "init: icons=%d disabled=0x%08x lights=0x%08x menu=0x%08x imeButton=0x%08x",
295 mCurrentUserId = ActivityManager.getCurrentUser();
297 IntentFilter filter = new IntentFilter();
298 filter.addAction(Intent.ACTION_USER_SWITCHED);
299 mContext.registerReceiver(mBroadcastReceiver, filter);
302 public void userSwitched(int newUserId) {
303 // should be overridden
306 public boolean notificationIsForCurrentUser(StatusBarNotification n) {
307 final int thisUserId = mCurrentUserId;
308 final int notificationUserId = n.getUserId();
309 if (DEBUG && MULTIUSER_DEBUG) {
310 Log.v(TAG, String.format("%s: current userid: %d, notification userid: %d",
311 n, thisUserId, notificationUserId));
313 return notificationUserId == UserHandle.USER_ALL
314 || thisUserId == notificationUserId;
318 protected void onConfigurationChanged(Configuration newConfig) {
319 final Locale locale = mContext.getResources().getConfiguration().locale;
320 final int ld = TextUtils.getLayoutDirectionFromLocale(locale);
321 if (! locale.equals(mLocale) || ld != mLayoutDirection) {
323 Log.v(TAG, String.format(
324 "config changed locale/LD: %s (%d) -> %s (%d)", mLocale, mLayoutDirection,
328 mLayoutDirection = ld;
333 protected View updateNotificationVetoButton(View row, StatusBarNotification n) {
334 View vetoButton = row.findViewById(R.id.veto);
335 if (n.isClearable() || (mInterruptingNotificationEntry != null
336 && mInterruptingNotificationEntry.row == row)) {
337 final String _pkg = n.getPackageName();
338 final String _tag = n.getTag();
339 final int _id = n.getId();
340 vetoButton.setOnClickListener(new View.OnClickListener() {
341 public void onClick(View v) {
342 // Accessibility feedback
343 v.announceForAccessibility(
344 mContext.getString(R.string.accessibility_notification_dismissed));
346 mBarService.onNotificationClear(_pkg, _tag, _id);
348 } catch (RemoteException ex) {
349 // system process is dead if we're here.
353 vetoButton.setVisibility(View.VISIBLE);
355 vetoButton.setVisibility(View.GONE);
357 vetoButton.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
362 protected void applyLegacyRowBackground(StatusBarNotification sbn, View content) {
363 if (sbn.getNotification().contentView.getLayoutId() !=
364 com.android.internal.R.layout.notification_template_base) {
367 ApplicationInfo info = mContext.getPackageManager().getApplicationInfo(sbn.getPackageName(), 0);
368 version = info.targetSdkVersion;
369 } catch (NameNotFoundException ex) {
370 Log.e(TAG, "Failed looking up ApplicationInfo for " + sbn.getPackageName(), ex);
372 if (version > 0 && version < Build.VERSION_CODES.GINGERBREAD) {
373 content.setBackgroundResource(R.drawable.notification_row_legacy_bg);
375 content.setBackgroundResource(com.android.internal.R.drawable.notification_bg);
380 private void startApplicationDetailsActivity(String packageName) {
381 Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
382 Uri.fromParts("package", packageName, null));
383 intent.setComponent(intent.resolveActivity(mContext.getPackageManager()));
384 TaskStackBuilder.create(mContext).addNextIntentWithParentStack(intent).startActivities(
385 null, UserHandle.CURRENT);
388 protected View.OnLongClickListener getNotificationLongClicker() {
389 return new View.OnLongClickListener() {
391 public boolean onLongClick(View v) {
392 final String packageNameF = (String) v.getTag();
393 if (packageNameF == null) return false;
394 if (v.getWindowToken() == null) return false;
395 mNotificationBlamePopup = new PopupMenu(mContext, v);
396 mNotificationBlamePopup.getMenuInflater().inflate(
397 R.menu.notification_popup_menu,
398 mNotificationBlamePopup.getMenu());
399 mNotificationBlamePopup.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
400 public boolean onMenuItemClick(MenuItem item) {
401 if (item.getItemId() == R.id.notification_inspect_item) {
402 startApplicationDetailsActivity(packageNameF);
403 animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE);
410 mNotificationBlamePopup.show();
417 public void dismissPopups() {
418 if (mNotificationBlamePopup != null) {
419 mNotificationBlamePopup.dismiss();
420 mNotificationBlamePopup = null;
424 public void onHeadsUpDismissed() {
428 public void toggleRecentApps() {
429 int msg = MSG_TOGGLE_RECENTS_PANEL;
430 mHandler.removeMessages(msg);
431 mHandler.sendEmptyMessage(msg);
435 public void preloadRecentApps() {
436 int msg = MSG_PRELOAD_RECENT_APPS;
437 mHandler.removeMessages(msg);
438 mHandler.sendEmptyMessage(msg);
442 public void cancelPreloadRecentApps() {
443 int msg = MSG_CANCEL_PRELOAD_RECENT_APPS;
444 mHandler.removeMessages(msg);
445 mHandler.sendEmptyMessage(msg);
449 public void showSearchPanel() {
450 int msg = MSG_OPEN_SEARCH_PANEL;
451 mHandler.removeMessages(msg);
452 mHandler.sendEmptyMessage(msg);
456 public void hideSearchPanel() {
457 int msg = MSG_CLOSE_SEARCH_PANEL;
458 mHandler.removeMessages(msg);
459 mHandler.sendEmptyMessage(msg);
462 protected abstract WindowManager.LayoutParams getSearchLayoutParams(
463 LayoutParams layoutParams);
465 protected void updateSearchPanel() {
467 boolean visible = false;
468 if (mSearchPanelView != null) {
469 visible = mSearchPanelView.isShowing();
470 mWindowManager.removeView(mSearchPanelView);
473 // Provide SearchPanel with a temporary parent to allow layout params to work.
474 LinearLayout tmpRoot = new LinearLayout(mContext);
475 mSearchPanelView = (SearchPanelView) LayoutInflater.from(mContext).inflate(
476 R.layout.status_bar_search_panel, tmpRoot, false);
477 mSearchPanelView.setOnTouchListener(
478 new TouchOutsideListener(MSG_CLOSE_SEARCH_PANEL, mSearchPanelView));
479 mSearchPanelView.setVisibility(View.GONE);
481 WindowManager.LayoutParams lp = getSearchLayoutParams(mSearchPanelView.getLayoutParams());
483 mWindowManager.addView(mSearchPanelView, lp);
484 mSearchPanelView.setBar(this);
486 mSearchPanelView.show(true, false);
490 protected H createHandler() {
494 static void sendCloseSystemWindows(Context context, String reason) {
495 if (ActivityManagerNative.isSystemReady()) {
497 ActivityManagerNative.getDefault().closeSystemDialogs(reason);
498 } catch (RemoteException e) {
503 protected abstract View getStatusBarView();
505 protected View.OnTouchListener mRecentsPreloadOnTouchListener = new View.OnTouchListener() {
506 // additional optimization when we have software system buttons - start loading the recent
507 // tasks on touch down
509 public boolean onTouch(View v, MotionEvent event) {
510 int action = event.getAction() & MotionEvent.ACTION_MASK;
511 if (action == MotionEvent.ACTION_DOWN) {
512 preloadRecentTasksList();
513 } else if (action == MotionEvent.ACTION_CANCEL) {
514 cancelPreloadingRecentTasksList();
515 } else if (action == MotionEvent.ACTION_UP) {
516 if (!v.isPressed()) {
517 cancelPreloadingRecentTasksList();
525 protected void toggleRecentsActivity() {
526 if (mRecents != null) {
527 mRecents.toggleRecents(mDisplay, mLayoutDirection, getStatusBarView());
531 protected void preloadRecentTasksList() {
532 if (mRecents != null) {
533 mRecents.preloadRecentTasksList();
537 protected void cancelPreloadingRecentTasksList() {
538 if (mRecents != null) {
539 mRecents.cancelPreloadingRecentTasksList();
543 protected void closeRecents() {
544 if (mRecents != null) {
545 mRecents.closeRecents();
549 public abstract void resetHeadsUpDecayTimer();
551 protected class H extends Handler {
552 public void handleMessage(Message m) {
555 case MSG_TOGGLE_RECENTS_PANEL:
556 toggleRecentsActivity();
558 case MSG_CLOSE_RECENTS_PANEL:
561 case MSG_PRELOAD_RECENT_APPS:
562 preloadRecentTasksList();
564 case MSG_CANCEL_PRELOAD_RECENT_APPS:
565 cancelPreloadingRecentTasksList();
567 case MSG_OPEN_SEARCH_PANEL:
568 if (DEBUG) Log.d(TAG, "opening search panel");
569 if (mSearchPanelView != null && mSearchPanelView.isAssistantAvailable()) {
570 mSearchPanelView.show(true, true);
574 case MSG_CLOSE_SEARCH_PANEL:
575 if (DEBUG) Log.d(TAG, "closing search panel");
576 if (mSearchPanelView != null && mSearchPanelView.isShowing()) {
577 mSearchPanelView.show(false, true);
585 public class TouchOutsideListener implements View.OnTouchListener {
587 private StatusBarPanel mPanel;
589 public TouchOutsideListener(int msg, StatusBarPanel panel) {
594 public boolean onTouch(View v, MotionEvent ev) {
595 final int action = ev.getAction();
596 if (action == MotionEvent.ACTION_OUTSIDE
597 || (action == MotionEvent.ACTION_DOWN
598 && !mPanel.isInContentArea((int)ev.getX(), (int)ev.getY()))) {
599 mHandler.removeMessages(mMsg);
600 mHandler.sendEmptyMessage(mMsg);
607 protected void workAroundBadLayerDrawableOpacity(View v) {
610 protected void onHideSearchPanel() {
613 protected void onShowSearchPanel() {
616 public boolean inflateViews(NotificationData.Entry entry, ViewGroup parent) {
618 mContext.getResources().getDimensionPixelSize(R.dimen.notification_min_height);
620 mContext.getResources().getDimensionPixelSize(R.dimen.notification_max_height);
621 StatusBarNotification sbn = entry.notification;
622 RemoteViews contentView = sbn.getNotification().contentView;
623 RemoteViews bigContentView = sbn.getNotification().bigContentView;
624 if (contentView == null) {
628 // create the row view
629 LayoutInflater inflater = (LayoutInflater)mContext.getSystemService(
630 Context.LAYOUT_INFLATER_SERVICE);
631 ExpandableNotificationRow row = (ExpandableNotificationRow) inflater.inflate(
632 R.layout.status_bar_notification_row, parent, false);
634 // for blaming (see SwipeHelper.setLongPressListener)
635 row.setTag(sbn.getPackageName());
637 workAroundBadLayerDrawableOpacity(row);
638 View vetoButton = updateNotificationVetoButton(row, sbn);
639 vetoButton.setContentDescription(mContext.getString(
640 R.string.accessibility_remove_notification));
642 // NB: the large icon is now handled entirely by the template
644 // bind the click event to the content area
645 ViewGroup content = (ViewGroup)row.findViewById(R.id.content);
646 ViewGroup adaptive = (ViewGroup)row.findViewById(R.id.adaptive);
648 content.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
650 PendingIntent contentIntent = sbn.getNotification().contentIntent;
651 if (contentIntent != null) {
652 final View.OnClickListener listener = new NotificationClicker(contentIntent,
653 sbn.getPackageName(), sbn.getTag(), sbn.getId());
654 content.setOnClickListener(listener);
656 content.setOnClickListener(null);
659 View contentViewLocal = null;
660 View bigContentViewLocal = null;
662 contentViewLocal = contentView.apply(mContext, adaptive, mOnClickHandler);
663 if (bigContentView != null) {
664 bigContentViewLocal = bigContentView.apply(mContext, adaptive, mOnClickHandler);
667 catch (RuntimeException e) {
668 final String ident = sbn.getPackageName() + "/0x" + Integer.toHexString(sbn.getId());
669 Log.e(TAG, "couldn't inflate view for notification " + ident, e);
673 if (contentViewLocal != null) {
674 SizeAdaptiveLayout.LayoutParams params =
675 new SizeAdaptiveLayout.LayoutParams(contentViewLocal.getLayoutParams());
676 params.minHeight = minHeight;
677 params.maxHeight = minHeight;
678 adaptive.addView(contentViewLocal, params);
680 if (bigContentViewLocal != null) {
681 SizeAdaptiveLayout.LayoutParams params =
682 new SizeAdaptiveLayout.LayoutParams(bigContentViewLocal.getLayoutParams());
683 params.minHeight = minHeight+1;
684 params.maxHeight = maxHeight;
685 adaptive.addView(bigContentViewLocal, params);
687 row.setDrawingCacheEnabled(true);
689 applyLegacyRowBackground(sbn, content);
691 if (MULTIUSER_DEBUG) {
692 TextView debug = (TextView) row.findViewById(R.id.debug_info);
694 debug.setVisibility(View.VISIBLE);
695 debug.setText("U " + entry.notification.getUserId());
699 entry.row.setRowHeight(mRowHeight);
700 entry.content = content;
701 entry.expanded = contentViewLocal;
702 entry.setBigContentView(bigContentViewLocal);
707 public NotificationClicker makeClicker(PendingIntent intent, String pkg, String tag, int id) {
708 return new NotificationClicker(intent, pkg, tag, id);
711 protected class NotificationClicker implements View.OnClickListener {
712 private PendingIntent mIntent;
717 public NotificationClicker(PendingIntent intent, String pkg, String tag, int id) {
724 public void onClick(View v) {
726 // The intent we are sending is for the application, which
727 // won't have permission to immediately start an activity after
728 // the user switches to home. We know it is safe to do at this
729 // point, so make sure new activity switches are now allowed.
730 ActivityManagerNative.getDefault().resumeAppSwitches();
731 // Also, notifications can be launched from the lock screen,
732 // so dismiss the lock screen when the activity starts.
733 ActivityManagerNative.getDefault().dismissKeyguardOnNextActivity();
734 } catch (RemoteException e) {
737 if (mIntent != null) {
738 int[] pos = new int[2];
739 v.getLocationOnScreen(pos);
740 Intent overlay = new Intent();
741 overlay.setSourceBounds(
742 new Rect(pos[0], pos[1], pos[0]+v.getWidth(), pos[1]+v.getHeight()));
744 mIntent.send(mContext, 0, overlay);
745 } catch (PendingIntent.CanceledException e) {
746 // the stack trace isn't very helpful here. Just log the exception message.
747 Log.w(TAG, "Sending contentIntent failed: " + e);
750 KeyguardTouchDelegate.getInstance(mContext).dismiss();
754 mBarService.onNotificationClick(mPkg, mTag, mId);
755 } catch (RemoteException ex) {
756 // system process is dead if we're here.
759 // close the shade if it was open
760 animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE);
761 visibilityChanged(false);
765 * The LEDs are turned o)ff when the notification panel is shown, even just a little bit.
766 * This was added last-minute and is inconsistent with the way the rest of the notifications
767 * are handled, because the notification isn't really cancelled. The lights are just
768 * turned off. If any other notifications happen, the lights will turn back on. Steve says
769 * this is what he wants. (see bug 1131461)
771 protected void visibilityChanged(boolean visible) {
772 if (mPanelSlightlyVisible != visible) {
773 mPanelSlightlyVisible = visible;
775 mBarService.onPanelRevealed();
776 } catch (RemoteException ex) {
777 // Won't fail unless the world has ended.
783 * Cancel this notification and tell the StatusBarManagerService / NotificationManagerService
786 * WARNING: this will call back into us. Don't hold any locks.
788 void handleNotificationError(IBinder key, StatusBarNotification n, String message) {
789 removeNotification(key);
791 mBarService.onNotificationError(n.getPackageName(), n.getTag(), n.getId(), n.getUid(), n.getInitialPid(), message);
792 } catch (RemoteException ex) {
797 protected StatusBarNotification removeNotificationViews(IBinder key) {
798 NotificationData.Entry entry = mNotificationData.remove(key);
800 Log.w(TAG, "removeNotification for unknown key: " + key);
803 // Remove the expanded view.
804 ViewGroup rowParent = (ViewGroup)entry.row.getParent();
805 if (rowParent != null) rowParent.removeView(entry.row);
806 updateExpansionStates();
807 updateNotificationIcons();
809 return entry.notification;
812 protected NotificationData.Entry createNotificationViews(IBinder key,
813 StatusBarNotification notification) {
815 Log.d(TAG, "createNotificationViews(key=" + key + ", notification=" + notification);
817 // Construct the icon.
818 final StatusBarIconView iconView = new StatusBarIconView(mContext,
819 notification.getPackageName() + "/0x" + Integer.toHexString(notification.getId()),
820 notification.getNotification());
821 iconView.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
823 final StatusBarIcon ic = new StatusBarIcon(notification.getPackageName(),
824 notification.getUser(),
825 notification.getNotification().icon,
826 notification.getNotification().iconLevel,
827 notification.getNotification().number,
828 notification.getNotification().tickerText);
829 if (!iconView.set(ic)) {
830 handleNotificationError(key, notification, "Couldn't create icon: " + ic);
833 // Construct the expanded view.
834 NotificationData.Entry entry = new NotificationData.Entry(key, notification, iconView);
835 if (!inflateViews(entry, mPile)) {
836 handleNotificationError(key, notification, "Couldn't expand RemoteViews for: "
843 protected void addNotificationViews(NotificationData.Entry entry) {
844 // Add the expanded view and icon.
845 int pos = mNotificationData.add(entry);
847 Log.d(TAG, "addNotificationViews: added at " + pos);
849 updateExpansionStates();
850 updateNotificationIcons();
853 private void addNotificationViews(IBinder key, StatusBarNotification notification) {
854 addNotificationViews(createNotificationViews(key, notification));
857 protected void updateExpansionStates() {
858 int N = mNotificationData.size();
859 for (int i = 0; i < N; i++) {
860 NotificationData.Entry entry = mNotificationData.get(i);
861 if (!entry.row.isUserLocked()) {
863 if (DEBUG) Log.d(TAG, "expanding top notification at " + i);
864 entry.row.setExpanded(true);
866 if (!entry.row.isUserExpanded()) {
867 if (DEBUG) Log.d(TAG, "collapsing notification at " + i);
868 entry.row.setExpanded(false);
870 if (DEBUG) Log.d(TAG, "ignoring user-modified notification at " + i);
874 if (DEBUG) Log.d(TAG, "ignoring notification being held by user at " + i);
879 protected abstract void haltTicker();
880 protected abstract void setAreThereNotifications();
881 protected abstract void updateNotificationIcons();
882 protected abstract void tick(IBinder key, StatusBarNotification n, boolean firstTime);
883 protected abstract void updateExpandedViewPos(int expandedPosition);
884 protected abstract int getExpandedViewMaxHeight();
885 protected abstract boolean shouldDisableNavbarGestures();
887 protected boolean isTopNotification(ViewGroup parent, NotificationData.Entry entry) {
888 return parent != null && parent.indexOfChild(entry.row) == 0;
891 public void updateNotification(IBinder key, StatusBarNotification notification) {
892 if (DEBUG) Log.d(TAG, "updateNotification(" + key + " -> " + notification + ")");
894 final NotificationData.Entry oldEntry = mNotificationData.findByKey(key);
895 if (oldEntry == null) {
896 Log.w(TAG, "updateNotification for unknown key: " + key);
900 final StatusBarNotification oldNotification = oldEntry.notification;
902 // XXX: modify when we do something more intelligent with the two content views
903 final RemoteViews oldContentView = oldNotification.getNotification().contentView;
904 final RemoteViews contentView = notification.getNotification().contentView;
905 final RemoteViews oldBigContentView = oldNotification.getNotification().bigContentView;
906 final RemoteViews bigContentView = notification.getNotification().bigContentView;
909 Log.d(TAG, "old notification: when=" + oldNotification.getNotification().when
910 + " ongoing=" + oldNotification.isOngoing()
911 + " expanded=" + oldEntry.expanded
912 + " contentView=" + oldContentView
913 + " bigContentView=" + oldBigContentView
914 + " rowParent=" + oldEntry.row.getParent());
915 Log.d(TAG, "new notification: when=" + notification.getNotification().when
916 + " ongoing=" + oldNotification.isOngoing()
917 + " contentView=" + contentView
918 + " bigContentView=" + bigContentView);
921 // Can we just reapply the RemoteViews in place? If when didn't change, the order
925 boolean contentsUnchanged = oldEntry.expanded != null
926 && contentView.getPackage() != null
927 && oldContentView.getPackage() != null
928 && oldContentView.getPackage().equals(contentView.getPackage())
929 && oldContentView.getLayoutId() == contentView.getLayoutId();
930 // large view may be null
931 boolean bigContentsUnchanged =
932 (oldEntry.getBigContentView() == null && bigContentView == null)
933 || ((oldEntry.getBigContentView() != null && bigContentView != null)
934 && bigContentView.getPackage() != null
935 && oldBigContentView.getPackage() != null
936 && oldBigContentView.getPackage().equals(bigContentView.getPackage())
937 && oldBigContentView.getLayoutId() == bigContentView.getLayoutId());
938 ViewGroup rowParent = (ViewGroup) oldEntry.row.getParent();
939 boolean orderUnchanged = notification.getNotification().when== oldNotification.getNotification().when
940 && notification.getScore() == oldNotification.getScore();
941 // score now encompasses/supersedes isOngoing()
943 boolean updateTicker = notification.getNotification().tickerText != null
944 && !TextUtils.equals(notification.getNotification().tickerText,
945 oldEntry.notification.getNotification().tickerText);
946 boolean isTopAnyway = isTopNotification(rowParent, oldEntry);
947 if (contentsUnchanged && bigContentsUnchanged && (orderUnchanged || isTopAnyway)) {
948 if (DEBUG) Log.d(TAG, "reusing notification for key: " + key);
949 oldEntry.notification = notification;
951 updateNotificationViews(oldEntry, notification);
953 if (ENABLE_HEADS_UP && mInterruptingNotificationEntry != null
954 && oldNotification == mInterruptingNotificationEntry.notification) {
955 if (!shouldInterrupt(notification)) {
956 if (DEBUG) Log.d(TAG, "no longer interrupts!");
957 mHandler.sendEmptyMessage(MSG_HIDE_HEADS_UP);
959 if (DEBUG) Log.d(TAG, "updating the current heads up:" + notification);
960 mInterruptingNotificationEntry.notification = notification;
961 updateNotificationViews(mInterruptingNotificationEntry, notification);
966 final StatusBarIcon ic = new StatusBarIcon(notification.getPackageName(),
967 notification.getUser(),
968 notification.getNotification().icon, notification.getNotification().iconLevel,
969 notification.getNotification().number,
970 notification.getNotification().tickerText);
971 if (!oldEntry.icon.set(ic)) {
972 handleNotificationError(key, notification, "Couldn't update icon: " + ic);
975 updateExpansionStates();
977 catch (RuntimeException e) {
978 // It failed to add cleanly. Log, and remove the view from the panel.
979 Log.w(TAG, "Couldn't reapply views for package " + contentView.getPackage(), e);
980 removeNotificationViews(key);
981 addNotificationViews(key, notification);
984 if (DEBUG) Log.d(TAG, "not reusing notification for key: " + key);
985 if (DEBUG) Log.d(TAG, "contents was " + (contentsUnchanged ? "unchanged" : "changed"));
986 if (DEBUG) Log.d(TAG, "order was " + (orderUnchanged ? "unchanged" : "changed"));
987 if (DEBUG) Log.d(TAG, "notification is " + (isTopAnyway ? "top" : "not top"));
988 final boolean wasExpanded = oldEntry.row.isUserExpanded();
989 removeNotificationViews(key);
990 addNotificationViews(key, notification); // will also replace the heads up
992 final NotificationData.Entry newEntry = mNotificationData.findByKey(key);
993 newEntry.row.setExpanded(true);
994 newEntry.row.setUserExpanded(true);
998 // Update the veto button accordingly (and as a result, whether this row is
999 // swipe-dismissable)
1000 updateNotificationVetoButton(oldEntry.row, notification);
1003 boolean isForCurrentUser = notificationIsForCurrentUser(notification);
1004 if (DEBUG) Log.d(TAG, "notification is " + (isForCurrentUser ? "" : "not ") + "for you");
1006 // Restart the ticker if it's still running
1007 if (updateTicker && isForCurrentUser) {
1009 tick(key, notification, false);
1012 // Recalculate the position of the sliding windows and the titles.
1013 setAreThereNotifications();
1014 updateExpandedViewPos(EXPANDED_LEAVE_ALONE);
1017 private void updateNotificationViews(NotificationData.Entry entry,
1018 StatusBarNotification notification) {
1019 final RemoteViews contentView = notification.getNotification().contentView;
1020 final RemoteViews bigContentView = notification.getNotification().bigContentView;
1021 // Reapply the RemoteViews
1022 contentView.reapply(mContext, entry.expanded, mOnClickHandler);
1023 if (bigContentView != null && entry.getBigContentView() != null) {
1024 bigContentView.reapply(mContext, entry.getBigContentView(), mOnClickHandler);
1026 // update the contentIntent
1027 final PendingIntent contentIntent = notification.getNotification().contentIntent;
1028 if (contentIntent != null) {
1029 final View.OnClickListener listener = makeClicker(contentIntent,
1030 notification.getPackageName(), notification.getTag(), notification.getId());
1031 entry.content.setOnClickListener(listener);
1033 entry.content.setOnClickListener(null);
1037 protected void notifyHeadsUpScreenOn(boolean screenOn) {
1038 if (!screenOn && mInterruptingNotificationEntry != null) {
1039 mHandler.sendEmptyMessage(MSG_ESCALATE_HEADS_UP);
1043 protected boolean shouldInterrupt(StatusBarNotification sbn) {
1044 Notification notification = sbn.getNotification();
1045 // some predicates to make the boolean logic legible
1046 boolean isNoisy = (notification.defaults & Notification.DEFAULT_SOUND) != 0
1047 || (notification.defaults & Notification.DEFAULT_VIBRATE) != 0
1048 || notification.sound != null
1049 || notification.vibrate != null;
1050 boolean isHighPriority = sbn.getScore() >= INTERRUPTION_THRESHOLD;
1051 boolean isFullscreen = notification.fullScreenIntent != null;
1052 boolean isAllowed = notification.extras.getInt(Notification.EXTRA_AS_HEADS_UP,
1053 Notification.HEADS_UP_ALLOWED) != Notification.HEADS_UP_NEVER;
1055 final KeyguardTouchDelegate keyguard = KeyguardTouchDelegate.getInstance(mContext);
1056 boolean interrupt = (isFullscreen || (isHighPriority && isNoisy))
1058 && mPowerManager.isScreenOn()
1059 && !keyguard.isShowingAndNotHidden()
1060 && !keyguard.isInputRestricted();
1062 interrupt = interrupt && !mDreamManager.isDreaming();
1063 } catch (RemoteException e) {
1064 Log.d(TAG, "failed to query dream manager", e);
1066 if (DEBUG) Log.d(TAG, "interrupt: " + interrupt);
1070 // Q: What kinds of notifications should show during setup?
1071 // A: Almost none! Only things coming from the system (package is "android") that also
1072 // have special "kind" tags marking them as relevant for setup (see below).
1073 protected boolean showNotificationEvenIfUnprovisioned(StatusBarNotification sbn) {
1074 if ("android".equals(sbn.getPackageName())) {
1075 if (sbn.getNotification().kind != null) {
1076 for (String aKind : sbn.getNotification().kind) {
1077 // IME switcher, created by InputMethodManagerService
1078 if ("android.system.imeswitcher".equals(aKind)) return true;
1079 // OTA availability & errors, created by SystemUpdateService
1080 if ("android.system.update".equals(aKind)) return true;
1087 public boolean inKeyguardRestrictedInputMode() {
1088 return KeyguardTouchDelegate.getInstance(mContext).isInputRestricted();
1091 public void setInteracting(int barWindow, boolean interacting) {
1092 // hook for subclasses
1095 public void destroy() {
1096 if (mSearchPanelView != null) {
1097 mWindowManager.removeViewImmediate(mSearchPanelView);
1099 mContext.unregisterReceiver(mBroadcastReceiver);