BaseStatusBar.java revision 51c7510e493680b4aca1ed7695b35c52d2cd63ff
1/* 2 * Copyright (C) 2010 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.android.systemui.statusbar; 18 19import android.app.ActivityManager; 20import android.app.ActivityManagerNative; 21import android.app.KeyguardManager; 22import android.app.Notification; 23import android.app.PendingIntent; 24import android.app.TaskStackBuilder; 25import android.content.BroadcastReceiver; 26import android.content.Context; 27import android.content.Intent; 28import android.content.IntentFilter; 29import android.content.pm.ApplicationInfo; 30import android.content.pm.PackageManager.NameNotFoundException; 31import android.content.res.Configuration; 32import android.database.ContentObserver; 33import android.graphics.Rect; 34import android.net.Uri; 35import android.os.Build; 36import android.os.Handler; 37import android.os.IBinder; 38import android.os.Message; 39import android.os.PowerManager; 40import android.os.RemoteException; 41import android.os.ServiceManager; 42import android.os.UserHandle; 43import android.provider.Settings; 44import android.service.dreams.DreamService; 45import android.service.dreams.IDreamManager; 46import android.service.notification.StatusBarNotification; 47import android.text.TextUtils; 48import android.util.Log; 49import android.view.Display; 50import android.view.IWindowManager; 51import android.view.LayoutInflater; 52import android.view.MenuItem; 53import android.view.MotionEvent; 54import android.view.View; 55import android.view.ViewGroup; 56import android.view.ViewGroup.LayoutParams; 57import android.view.WindowManager; 58import android.view.WindowManagerGlobal; 59import android.widget.ImageView; 60import android.widget.LinearLayout; 61import android.widget.PopupMenu; 62import android.widget.RemoteViews; 63import android.widget.TextView; 64 65import com.android.internal.statusbar.IStatusBarService; 66import com.android.internal.statusbar.StatusBarIcon; 67import com.android.internal.statusbar.StatusBarIconList; 68import com.android.internal.widget.SizeAdaptiveLayout; 69import com.android.systemui.R; 70import com.android.systemui.RecentsComponent; 71import com.android.systemui.SearchPanelView; 72import com.android.systemui.SystemUI; 73import com.android.systemui.statusbar.policy.NotificationRowLayout; 74 75import java.util.ArrayList; 76import java.util.Locale; 77 78public 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; 83 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 93 protected static final boolean ENABLE_HEADS_UP = true; 94 // scores above this threshold should be displayed in heads up mode. 95 private static final int INTERRUPTION_THRESHOLD = 11; 96 97 // Should match the value in PhoneWindowManager 98 public static final String SYSTEM_DIALOG_REASON_RECENT_APPS = "recentapps"; 99 100 public static final int EXPANDED_LEAVE_ALONE = -10000; 101 public static final int EXPANDED_FULL_OPEN = -10001; 102 private static final String SETTING_HEADS_UP = "heads_up_enabled"; 103 104 protected CommandQueue mCommandQueue; 105 protected IStatusBarService mBarService; 106 protected H mHandler = createHandler(); 107 108 // all notifications 109 protected NotificationData mNotificationData = new NotificationData(); 110 protected NotificationRowLayout mPile; 111 112 protected NotificationData.Entry mInterruptingNotificationEntry; 113 protected long mInterruptingNotificationTime; 114 115 // used to notify status bar for suppressing notification LED 116 protected boolean mPanelSlightlyVisible; 117 118 // Search panel 119 protected SearchPanelView mSearchPanelView; 120 121 protected PopupMenu mNotificationBlamePopup; 122 123 protected int mCurrentUserId = 0; 124 125 protected int mLayoutDirection; 126 private Locale mLocale; 127 protected boolean mUseHeadsUp = true; 128 129 protected IDreamManager mDreamManager; 130 KeyguardManager mKeyguardManager; 131 PowerManager mPowerManager; 132 protected int mRowHeight; 133 134 // UI-specific methods 135 136 /** 137 * Create all windows necessary for the status bar (including navigation, overlay panels, etc) 138 * and add them to the window manager. 139 */ 140 protected abstract void createAndAddWindows(); 141 142 protected WindowManager mWindowManager; 143 protected IWindowManager mWindowManagerService; 144 protected abstract void refreshLayout(int layoutDirection); 145 146 protected Display mDisplay; 147 148 private boolean mDeviceProvisioned = false; 149 150 private RecentsComponent mRecents; 151 152 public IStatusBarService getStatusBarService() { 153 return mBarService; 154 } 155 156 public boolean isDeviceProvisioned() { 157 return mDeviceProvisioned; 158 } 159 160 private ContentObserver mProvisioningObserver = new ContentObserver(new Handler()) { 161 @Override 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(); 168 } 169 } 170 }; 171 172 final private ContentObserver mHeadsUpObserver = new ContentObserver(mHandler) { 173 @Override 174 public void onChange(boolean selfChange) { 175 mUseHeadsUp = ENABLE_HEADS_UP && 0 != Settings.Global.getInt( 176 mContext.getContentResolver(), SETTING_HEADS_UP, 0); 177 Log.d(TAG, "heads up is " + (mUseHeadsUp ? "enabled" : "disabled")); 178 if (!mUseHeadsUp) { 179 Log.d(TAG, "dismissing any existing heads up notification on disable event"); 180 mHandler.sendEmptyMessage(MSG_HIDE_HEADS_UP); 181 } 182 } 183 }; 184 185 private RemoteViews.OnClickHandler mOnClickHandler = new RemoteViews.OnClickHandler() { 186 @Override 187 public boolean onClickHandler(View view, PendingIntent pendingIntent, Intent fillInIntent) { 188 if (DEBUG) { 189 Log.v(TAG, "Notification click handler invoked for intent: " + pendingIntent); 190 } 191 final boolean isActivity = pendingIntent.isActivity(); 192 if (isActivity) { 193 try { 194 // The intent we are sending is for the application, which 195 // won't have permission to immediately start an activity after 196 // the user switches to home. We know it is safe to do at this 197 // point, so make sure new activity switches are now allowed. 198 ActivityManagerNative.getDefault().resumeAppSwitches(); 199 // Also, notifications can be launched from the lock screen, 200 // so dismiss the lock screen when the activity starts. 201 ActivityManagerNative.getDefault().dismissKeyguardOnNextActivity(); 202 } catch (RemoteException e) { 203 } 204 } 205 206 boolean handled = super.onClickHandler(view, pendingIntent, fillInIntent); 207 208 if (isActivity && handled) { 209 // close the shade if it was open 210 animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE); 211 visibilityChanged(false); 212 } 213 return handled; 214 } 215 }; 216 217 private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { 218 @Override 219 public void onReceive(Context context, Intent intent) { 220 String action = intent.getAction(); 221 if (Intent.ACTION_USER_SWITCHED.equals(action)) { 222 mCurrentUserId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1); 223 if (true) Log.v(TAG, "userId " + mCurrentUserId + " is in the house"); 224 userSwitched(mCurrentUserId); 225 } 226 } 227 }; 228 229 public void start() { 230 mWindowManager = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE); 231 mWindowManagerService = WindowManagerGlobal.getWindowManagerService(); 232 mDisplay = mWindowManager.getDefaultDisplay(); 233 234 mDreamManager = IDreamManager.Stub.asInterface( 235 ServiceManager.checkService(DreamService.DREAM_SERVICE)); 236 mKeyguardManager = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE); 237 mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); 238 239 mProvisioningObserver.onChange(false); // set up 240 mContext.getContentResolver().registerContentObserver( 241 Settings.Global.getUriFor(Settings.Global.DEVICE_PROVISIONED), true, 242 mProvisioningObserver); 243 244 mHeadsUpObserver.onChange(false); // set up 245 mContext.getContentResolver().registerContentObserver( 246 Settings.Global.getUriFor(SETTING_HEADS_UP), true, 247 mHeadsUpObserver); 248 249 mBarService = IStatusBarService.Stub.asInterface( 250 ServiceManager.getService(Context.STATUS_BAR_SERVICE)); 251 252 mRecents = getComponent(RecentsComponent.class); 253 254 mLocale = mContext.getResources().getConfiguration().locale; 255 mLayoutDirection = TextUtils.getLayoutDirectionFromLocale(mLocale); 256 257 // Connect in to the status bar manager service 258 StatusBarIconList iconList = new StatusBarIconList(); 259 ArrayList<IBinder> notificationKeys = new ArrayList<IBinder>(); 260 ArrayList<StatusBarNotification> notifications = new ArrayList<StatusBarNotification>(); 261 mCommandQueue = new CommandQueue(this, iconList); 262 263 int[] switches = new int[7]; 264 ArrayList<IBinder> binders = new ArrayList<IBinder>(); 265 try { 266 mBarService.registerStatusBar(mCommandQueue, iconList, notificationKeys, notifications, 267 switches, binders); 268 } catch (RemoteException ex) { 269 // If the system process isn't there we're doomed anyway. 270 } 271 272 createAndAddWindows(); 273 274 disable(switches[0]); 275 setSystemUiVisibility(switches[1], 0xffffffff); 276 topAppWindowChanged(switches[2] != 0); 277 // StatusBarManagerService has a back up of IME token and it's restored here. 278 setImeWindowStatus(binders.get(0), switches[3], switches[4]); 279 setHardKeyboardStatus(switches[5] != 0, switches[6] != 0); 280 281 // Set up the initial icon state 282 int N = iconList.size(); 283 int viewIndex = 0; 284 for (int i=0; i<N; i++) { 285 StatusBarIcon icon = iconList.getIcon(i); 286 if (icon != null) { 287 addIcon(iconList.getSlot(i), i, viewIndex, icon); 288 viewIndex++; 289 } 290 } 291 292 // Set up the initial notification state 293 N = notificationKeys.size(); 294 if (N == notifications.size()) { 295 for (int i=0; i<N; i++) { 296 addNotification(notificationKeys.get(i), notifications.get(i)); 297 } 298 } else { 299 Log.wtf(TAG, "Notification list length mismatch: keys=" + N 300 + " notifications=" + notifications.size()); 301 } 302 303 if (DEBUG) { 304 Log.d(TAG, String.format( 305 "init: icons=%d disabled=0x%08x lights=0x%08x menu=0x%08x imeButton=0x%08x", 306 iconList.size(), 307 switches[0], 308 switches[1], 309 switches[2], 310 switches[3] 311 )); 312 } 313 314 mCurrentUserId = ActivityManager.getCurrentUser(); 315 316 IntentFilter filter = new IntentFilter(); 317 filter.addAction(Intent.ACTION_USER_SWITCHED); 318 mContext.registerReceiver(mBroadcastReceiver, filter); 319 320 mLocale = mContext.getResources().getConfiguration().locale; 321 } 322 323 public void userSwitched(int newUserId) { 324 // should be overridden 325 } 326 327 public boolean notificationIsForCurrentUser(StatusBarNotification n) { 328 final int thisUserId = mCurrentUserId; 329 final int notificationUserId = n.getUserId(); 330 if (DEBUG && MULTIUSER_DEBUG) { 331 Log.v(TAG, String.format("%s: current userid: %d, notification userid: %d", 332 n, thisUserId, notificationUserId)); 333 } 334 return notificationUserId == UserHandle.USER_ALL 335 || thisUserId == notificationUserId; 336 } 337 338 @Override 339 protected void onConfigurationChanged(Configuration newConfig) { 340 final Locale newLocale = mContext.getResources().getConfiguration().locale; 341 if (! newLocale.equals(mLocale)) { 342 mLocale = newLocale; 343 mLayoutDirection = TextUtils.getLayoutDirectionFromLocale(mLocale); 344 refreshLayout(mLayoutDirection); 345 } 346 } 347 348 protected View updateNotificationVetoButton(View row, StatusBarNotification n) { 349 View vetoButton = row.findViewById(R.id.veto); 350 if (n.isClearable()) { 351 final String _pkg = n.getPackageName(); 352 final String _tag = n.getTag(); 353 final int _id = n.getId(); 354 vetoButton.setOnClickListener(new View.OnClickListener() { 355 public void onClick(View v) { 356 // Accessibility feedback 357 v.announceForAccessibility( 358 mContext.getString(R.string.accessibility_notification_dismissed)); 359 try { 360 mBarService.onNotificationClear(_pkg, _tag, _id); 361 362 } catch (RemoteException ex) { 363 // system process is dead if we're here. 364 } 365 } 366 }); 367 vetoButton.setVisibility(View.VISIBLE); 368 } else { 369 vetoButton.setVisibility(View.GONE); 370 } 371 vetoButton.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO); 372 return vetoButton; 373 } 374 375 376 protected void applyLegacyRowBackground(StatusBarNotification sbn, View content) { 377 if (sbn.getNotification().contentView.getLayoutId() != 378 com.android.internal.R.layout.notification_template_base) { 379 int version = 0; 380 try { 381 ApplicationInfo info = mContext.getPackageManager().getApplicationInfo(sbn.getPackageName(), 0); 382 version = info.targetSdkVersion; 383 } catch (NameNotFoundException ex) { 384 Log.e(TAG, "Failed looking up ApplicationInfo for " + sbn.getPackageName(), ex); 385 } 386 if (version > 0 && version < Build.VERSION_CODES.GINGERBREAD) { 387 content.setBackgroundResource(R.drawable.notification_row_legacy_bg); 388 } else { 389 content.setBackgroundResource(com.android.internal.R.drawable.notification_bg); 390 } 391 } 392 } 393 394 private void startApplicationDetailsActivity(String packageName) { 395 Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, 396 Uri.fromParts("package", packageName, null)); 397 intent.setComponent(intent.resolveActivity(mContext.getPackageManager())); 398 TaskStackBuilder.create(mContext).addNextIntentWithParentStack(intent).startActivities( 399 null, UserHandle.CURRENT); 400 } 401 402 protected View.OnLongClickListener getNotificationLongClicker() { 403 return new View.OnLongClickListener() { 404 @Override 405 public boolean onLongClick(View v) { 406 final String packageNameF = (String) v.getTag(); 407 if (packageNameF == null) return false; 408 if (v.getWindowToken() == null) return false; 409 mNotificationBlamePopup = new PopupMenu(mContext, v); 410 mNotificationBlamePopup.getMenuInflater().inflate( 411 R.menu.notification_popup_menu, 412 mNotificationBlamePopup.getMenu()); 413 mNotificationBlamePopup.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() { 414 public boolean onMenuItemClick(MenuItem item) { 415 if (item.getItemId() == R.id.notification_inspect_item) { 416 startApplicationDetailsActivity(packageNameF); 417 animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE); 418 } else { 419 return false; 420 } 421 return true; 422 } 423 }); 424 mNotificationBlamePopup.show(); 425 426 return true; 427 } 428 }; 429 } 430 431 public void dismissPopups() { 432 if (mNotificationBlamePopup != null) { 433 mNotificationBlamePopup.dismiss(); 434 mNotificationBlamePopup = null; 435 } 436 } 437 438 public void onHeadsUpDismissed() { 439 // pass 440 } 441 442 @Override 443 public void toggleRecentApps() { 444 int msg = MSG_TOGGLE_RECENTS_PANEL; 445 mHandler.removeMessages(msg); 446 mHandler.sendEmptyMessage(msg); 447 } 448 449 @Override 450 public void preloadRecentApps() { 451 int msg = MSG_PRELOAD_RECENT_APPS; 452 mHandler.removeMessages(msg); 453 mHandler.sendEmptyMessage(msg); 454 } 455 456 @Override 457 public void cancelPreloadRecentApps() { 458 int msg = MSG_CANCEL_PRELOAD_RECENT_APPS; 459 mHandler.removeMessages(msg); 460 mHandler.sendEmptyMessage(msg); 461 } 462 463 @Override 464 public void showSearchPanel() { 465 int msg = MSG_OPEN_SEARCH_PANEL; 466 mHandler.removeMessages(msg); 467 mHandler.sendEmptyMessage(msg); 468 } 469 470 @Override 471 public void hideSearchPanel() { 472 int msg = MSG_CLOSE_SEARCH_PANEL; 473 mHandler.removeMessages(msg); 474 mHandler.sendEmptyMessage(msg); 475 } 476 477 protected abstract WindowManager.LayoutParams getSearchLayoutParams( 478 LayoutParams layoutParams); 479 480 protected void updateSearchPanel() { 481 // Search Panel 482 boolean visible = false; 483 if (mSearchPanelView != null) { 484 visible = mSearchPanelView.isShowing(); 485 mWindowManager.removeView(mSearchPanelView); 486 } 487 488 // Provide SearchPanel with a temporary parent to allow layout params to work. 489 LinearLayout tmpRoot = new LinearLayout(mContext); 490 mSearchPanelView = (SearchPanelView) LayoutInflater.from(mContext).inflate( 491 R.layout.status_bar_search_panel, tmpRoot, false); 492 mSearchPanelView.setOnTouchListener( 493 new TouchOutsideListener(MSG_CLOSE_SEARCH_PANEL, mSearchPanelView)); 494 mSearchPanelView.setVisibility(View.GONE); 495 496 WindowManager.LayoutParams lp = getSearchLayoutParams(mSearchPanelView.getLayoutParams()); 497 498 mWindowManager.addView(mSearchPanelView, lp); 499 mSearchPanelView.setBar(this); 500 if (visible) { 501 mSearchPanelView.show(true, false); 502 } 503 } 504 505 protected H createHandler() { 506 return new H(); 507 } 508 509 static void sendCloseSystemWindows(Context context, String reason) { 510 if (ActivityManagerNative.isSystemReady()) { 511 try { 512 ActivityManagerNative.getDefault().closeSystemDialogs(reason); 513 } catch (RemoteException e) { 514 } 515 } 516 } 517 518 protected abstract View getStatusBarView(); 519 520 protected View.OnTouchListener mRecentsPreloadOnTouchListener = new View.OnTouchListener() { 521 // additional optimization when we have software system buttons - start loading the recent 522 // tasks on touch down 523 @Override 524 public boolean onTouch(View v, MotionEvent event) { 525 int action = event.getAction() & MotionEvent.ACTION_MASK; 526 if (action == MotionEvent.ACTION_DOWN) { 527 preloadRecentTasksList(); 528 } else if (action == MotionEvent.ACTION_CANCEL) { 529 cancelPreloadingRecentTasksList(); 530 } else if (action == MotionEvent.ACTION_UP) { 531 if (!v.isPressed()) { 532 cancelPreloadingRecentTasksList(); 533 } 534 535 } 536 return false; 537 } 538 }; 539 540 protected void toggleRecentsActivity() { 541 if (mRecents != null) { 542 mRecents.toggleRecents(mDisplay, mLayoutDirection, getStatusBarView()); 543 } 544 } 545 546 protected void preloadRecentTasksList() { 547 if (mRecents != null) { 548 mRecents.preloadRecentTasksList(); 549 } 550 } 551 552 protected void cancelPreloadingRecentTasksList() { 553 if (mRecents != null) { 554 mRecents.cancelPreloadingRecentTasksList(); 555 } 556 } 557 558 protected void closeRecents() { 559 if (mRecents != null) { 560 mRecents.closeRecents(); 561 } 562 } 563 564 public abstract void resetHeadsUpDecayTimer(); 565 566 protected class H extends Handler { 567 public void handleMessage(Message m) { 568 Intent intent; 569 switch (m.what) { 570 case MSG_TOGGLE_RECENTS_PANEL: 571 toggleRecentsActivity(); 572 break; 573 case MSG_CLOSE_RECENTS_PANEL: 574 closeRecents(); 575 break; 576 case MSG_PRELOAD_RECENT_APPS: 577 preloadRecentTasksList(); 578 break; 579 case MSG_CANCEL_PRELOAD_RECENT_APPS: 580 cancelPreloadingRecentTasksList(); 581 break; 582 case MSG_OPEN_SEARCH_PANEL: 583 if (DEBUG) Log.d(TAG, "opening search panel"); 584 if (mSearchPanelView != null && mSearchPanelView.isAssistantAvailable()) { 585 mSearchPanelView.show(true, true); 586 } 587 break; 588 case MSG_CLOSE_SEARCH_PANEL: 589 if (DEBUG) Log.d(TAG, "closing search panel"); 590 if (mSearchPanelView != null && mSearchPanelView.isShowing()) { 591 mSearchPanelView.show(false, true); 592 } 593 break; 594 } 595 } 596 } 597 598 public class TouchOutsideListener implements View.OnTouchListener { 599 private int mMsg; 600 private StatusBarPanel mPanel; 601 602 public TouchOutsideListener(int msg, StatusBarPanel panel) { 603 mMsg = msg; 604 mPanel = panel; 605 } 606 607 public boolean onTouch(View v, MotionEvent ev) { 608 final int action = ev.getAction(); 609 if (action == MotionEvent.ACTION_OUTSIDE 610 || (action == MotionEvent.ACTION_DOWN 611 && !mPanel.isInContentArea((int)ev.getX(), (int)ev.getY()))) { 612 mHandler.removeMessages(mMsg); 613 mHandler.sendEmptyMessage(mMsg); 614 return true; 615 } 616 return false; 617 } 618 } 619 620 protected void workAroundBadLayerDrawableOpacity(View v) { 621 } 622 623 public boolean inflateViews(NotificationData.Entry entry, ViewGroup parent) { 624 int minHeight = 625 mContext.getResources().getDimensionPixelSize(R.dimen.notification_min_height); 626 int maxHeight = 627 mContext.getResources().getDimensionPixelSize(R.dimen.notification_max_height); 628 StatusBarNotification sbn = entry.notification; 629 RemoteViews contentView = sbn.getNotification().contentView; 630 RemoteViews bigContentView = sbn.getNotification().bigContentView; 631 if (contentView == null) { 632 return false; 633 } 634 635 // create the row view 636 LayoutInflater inflater = (LayoutInflater)mContext.getSystemService( 637 Context.LAYOUT_INFLATER_SERVICE); 638 ExpandableNotificationRow row = (ExpandableNotificationRow) inflater.inflate( 639 R.layout.status_bar_notification_row, parent, false); 640 641 // for blaming (see SwipeHelper.setLongPressListener) 642 row.setTag(sbn.getPackageName()); 643 644 workAroundBadLayerDrawableOpacity(row); 645 View vetoButton = updateNotificationVetoButton(row, sbn); 646 vetoButton.setContentDescription(mContext.getString( 647 R.string.accessibility_remove_notification)); 648 649 // NB: the large icon is now handled entirely by the template 650 651 // bind the click event to the content area 652 ViewGroup content = (ViewGroup)row.findViewById(R.id.content); 653 ViewGroup adaptive = (ViewGroup)row.findViewById(R.id.adaptive); 654 655 content.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS); 656 657 PendingIntent contentIntent = sbn.getNotification().contentIntent; 658 if (contentIntent != null) { 659 final View.OnClickListener listener = new NotificationClicker(contentIntent, 660 sbn.getPackageName(), sbn.getTag(), sbn.getId()); 661 content.setOnClickListener(listener); 662 } else { 663 content.setOnClickListener(null); 664 } 665 666 View contentViewLocal = null; 667 View bigContentViewLocal = null; 668 try { 669 contentViewLocal = contentView.apply(mContext, adaptive, mOnClickHandler); 670 if (bigContentView != null) { 671 bigContentViewLocal = bigContentView.apply(mContext, adaptive, mOnClickHandler); 672 } 673 } 674 catch (RuntimeException e) { 675 final String ident = sbn.getPackageName() + "/0x" + Integer.toHexString(sbn.getId()); 676 Log.e(TAG, "couldn't inflate view for notification " + ident, e); 677 return false; 678 } 679 680 if (contentViewLocal != null) { 681 SizeAdaptiveLayout.LayoutParams params = 682 new SizeAdaptiveLayout.LayoutParams(contentViewLocal.getLayoutParams()); 683 params.minHeight = minHeight; 684 params.maxHeight = minHeight; 685 adaptive.addView(contentViewLocal, params); 686 } 687 if (bigContentViewLocal != null) { 688 SizeAdaptiveLayout.LayoutParams params = 689 new SizeAdaptiveLayout.LayoutParams(bigContentViewLocal.getLayoutParams()); 690 params.minHeight = minHeight+1; 691 params.maxHeight = maxHeight; 692 adaptive.addView(bigContentViewLocal, params); 693 } 694 row.setDrawingCacheEnabled(true); 695 696 applyLegacyRowBackground(sbn, content); 697 698 if (MULTIUSER_DEBUG) { 699 TextView debug = (TextView) row.findViewById(R.id.debug_info); 700 if (debug != null) { 701 debug.setVisibility(View.VISIBLE); 702 debug.setText("U " + entry.notification.getUserId()); 703 } 704 } 705 entry.row = row; 706 entry.row.setRowHeight(mRowHeight); 707 entry.content = content; 708 entry.expanded = contentViewLocal; 709 entry.setBigContentView(bigContentViewLocal); 710 711 return true; 712 } 713 714 public NotificationClicker makeClicker(PendingIntent intent, String pkg, String tag, int id) { 715 return new NotificationClicker(intent, pkg, tag, id); 716 } 717 718 protected class NotificationClicker implements View.OnClickListener { 719 private PendingIntent mIntent; 720 private String mPkg; 721 private String mTag; 722 private int mId; 723 724 public NotificationClicker(PendingIntent intent, String pkg, String tag, int id) { 725 mIntent = intent; 726 mPkg = pkg; 727 mTag = tag; 728 mId = id; 729 } 730 731 public void onClick(View v) { 732 try { 733 // The intent we are sending is for the application, which 734 // won't have permission to immediately start an activity after 735 // the user switches to home. We know it is safe to do at this 736 // point, so make sure new activity switches are now allowed. 737 ActivityManagerNative.getDefault().resumeAppSwitches(); 738 // Also, notifications can be launched from the lock screen, 739 // so dismiss the lock screen when the activity starts. 740 ActivityManagerNative.getDefault().dismissKeyguardOnNextActivity(); 741 } catch (RemoteException e) { 742 } 743 744 if (mIntent != null) { 745 int[] pos = new int[2]; 746 v.getLocationOnScreen(pos); 747 Intent overlay = new Intent(); 748 overlay.setSourceBounds( 749 new Rect(pos[0], pos[1], pos[0]+v.getWidth(), pos[1]+v.getHeight())); 750 try { 751 mIntent.send(mContext, 0, overlay); 752 } catch (PendingIntent.CanceledException e) { 753 // the stack trace isn't very helpful here. Just log the exception message. 754 Log.w(TAG, "Sending contentIntent failed: " + e); 755 } 756 757 KeyguardManager kgm = 758 (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE); 759 if (kgm != null) kgm.exitKeyguardSecurely(null); 760 } 761 762 try { 763 mBarService.onNotificationClick(mPkg, mTag, mId); 764 } catch (RemoteException ex) { 765 // system process is dead if we're here. 766 } 767 768 // close the shade if it was open 769 animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE); 770 visibilityChanged(false); 771 } 772 } 773 /** 774 * The LEDs are turned o)ff when the notification panel is shown, even just a little bit. 775 * This was added last-minute and is inconsistent with the way the rest of the notifications 776 * are handled, because the notification isn't really cancelled. The lights are just 777 * turned off. If any other notifications happen, the lights will turn back on. Steve says 778 * this is what he wants. (see bug 1131461) 779 */ 780 protected void visibilityChanged(boolean visible) { 781 if (mPanelSlightlyVisible != visible) { 782 mPanelSlightlyVisible = visible; 783 try { 784 mBarService.onPanelRevealed(); 785 } catch (RemoteException ex) { 786 // Won't fail unless the world has ended. 787 } 788 } 789 } 790 791 /** 792 * Cancel this notification and tell the StatusBarManagerService / NotificationManagerService 793 * about the failure. 794 * 795 * WARNING: this will call back into us. Don't hold any locks. 796 */ 797 void handleNotificationError(IBinder key, StatusBarNotification n, String message) { 798 removeNotification(key); 799 try { 800 mBarService.onNotificationError(n.getPackageName(), n.getTag(), n.getId(), n.getUid(), n.getInitialPid(), message); 801 } catch (RemoteException ex) { 802 // The end is nigh. 803 } 804 } 805 806 protected StatusBarNotification removeNotificationViews(IBinder key) { 807 NotificationData.Entry entry = mNotificationData.remove(key); 808 if (entry == null) { 809 Log.w(TAG, "removeNotification for unknown key: " + key); 810 return null; 811 } 812 // Remove the expanded view. 813 ViewGroup rowParent = (ViewGroup)entry.row.getParent(); 814 if (rowParent != null) rowParent.removeView(entry.row); 815 updateExpansionStates(); 816 updateNotificationIcons(); 817 818 return entry.notification; 819 } 820 821 protected StatusBarIconView addNotificationViews(IBinder key, 822 StatusBarNotification notification) { 823 if (DEBUG) { 824 Log.d(TAG, "addNotificationViews(key=" + key + ", notification=" + notification); 825 } 826 // Construct the icon. 827 final StatusBarIconView iconView = new StatusBarIconView(mContext, 828 notification.getPackageName() + "/0x" + Integer.toHexString(notification.getId()), 829 notification.getNotification()); 830 iconView.setScaleType(ImageView.ScaleType.CENTER_INSIDE); 831 832 final StatusBarIcon ic = new StatusBarIcon(notification.getPackageName(), 833 notification.getUser(), 834 notification.getNotification().icon, 835 notification.getNotification().iconLevel, 836 notification.getNotification().number, 837 notification.getNotification().tickerText); 838 if (!iconView.set(ic)) { 839 handleNotificationError(key, notification, "Couldn't create icon: " + ic); 840 return null; 841 } 842 // Construct the expanded view. 843 NotificationData.Entry entry = new NotificationData.Entry(key, notification, iconView); 844 if (!inflateViews(entry, mPile)) { 845 handleNotificationError(key, notification, "Couldn't expand RemoteViews for: " 846 + notification); 847 return null; 848 } 849 850 // Add the expanded view and icon. 851 int pos = mNotificationData.add(entry); 852 if (DEBUG) { 853 Log.d(TAG, "addNotificationViews: added at " + pos); 854 } 855 updateExpansionStates(); 856 updateNotificationIcons(); 857 858 return iconView; 859 } 860 861 protected void updateExpansionStates() { 862 int N = mNotificationData.size(); 863 for (int i = 0; i < N; i++) { 864 NotificationData.Entry entry = mNotificationData.get(i); 865 if (!entry.row.isUserLocked()) { 866 if (i == (N-1)) { 867 if (DEBUG) Log.d(TAG, "expanding top notification at " + i); 868 entry.row.setExpanded(true); 869 } else { 870 if (!entry.row.isUserExpanded()) { 871 if (DEBUG) Log.d(TAG, "collapsing notification at " + i); 872 entry.row.setExpanded(false); 873 } else { 874 if (DEBUG) Log.d(TAG, "ignoring user-modified notification at " + i); 875 } 876 } 877 } else { 878 if (DEBUG) Log.d(TAG, "ignoring notification being held by user at " + i); 879 } 880 } 881 } 882 883 protected abstract void haltTicker(); 884 protected abstract void setAreThereNotifications(); 885 protected abstract void updateNotificationIcons(); 886 protected abstract void tick(IBinder key, StatusBarNotification n, boolean firstTime); 887 protected abstract void updateExpandedViewPos(int expandedPosition); 888 protected abstract int getExpandedViewMaxHeight(); 889 protected abstract boolean shouldDisableNavbarGestures(); 890 891 protected boolean isTopNotification(ViewGroup parent, NotificationData.Entry entry) { 892 return parent != null && parent.indexOfChild(entry.row) == 0; 893 } 894 895 public void updateNotification(IBinder key, StatusBarNotification notification) { 896 if (DEBUG) Log.d(TAG, "updateNotification(" + key + " -> " + notification + ")"); 897 898 final NotificationData.Entry oldEntry = mNotificationData.findByKey(key); 899 if (oldEntry == null) { 900 Log.w(TAG, "updateNotification for unknown key: " + key); 901 return; 902 } 903 904 final StatusBarNotification oldNotification = oldEntry.notification; 905 906 // XXX: modify when we do something more intelligent with the two content views 907 final RemoteViews oldContentView = oldNotification.getNotification().contentView; 908 final RemoteViews contentView = notification.getNotification().contentView; 909 final RemoteViews oldBigContentView = oldNotification.getNotification().bigContentView; 910 final RemoteViews bigContentView = notification.getNotification().bigContentView; 911 912 if (DEBUG) { 913 Log.d(TAG, "old notification: when=" + oldNotification.getNotification().when 914 + " ongoing=" + oldNotification.isOngoing() 915 + " expanded=" + oldEntry.expanded 916 + " contentView=" + oldContentView 917 + " bigContentView=" + oldBigContentView 918 + " rowParent=" + oldEntry.row.getParent()); 919 Log.d(TAG, "new notification: when=" + notification.getNotification().when 920 + " ongoing=" + oldNotification.isOngoing() 921 + " contentView=" + contentView 922 + " bigContentView=" + bigContentView); 923 } 924 925 // Can we just reapply the RemoteViews in place? If when didn't change, the order 926 // didn't change. 927 928 // 1U is never null 929 boolean contentsUnchanged = oldEntry.expanded != null 930 && contentView.getPackage() != null 931 && oldContentView.getPackage() != null 932 && oldContentView.getPackage().equals(contentView.getPackage()) 933 && oldContentView.getLayoutId() == contentView.getLayoutId(); 934 // large view may be null 935 boolean bigContentsUnchanged = 936 (oldEntry.getBigContentView() == null && bigContentView == null) 937 || ((oldEntry.getBigContentView() != null && bigContentView != null) 938 && bigContentView.getPackage() != null 939 && oldBigContentView.getPackage() != null 940 && oldBigContentView.getPackage().equals(bigContentView.getPackage()) 941 && oldBigContentView.getLayoutId() == bigContentView.getLayoutId()); 942 ViewGroup rowParent = (ViewGroup) oldEntry.row.getParent(); 943 boolean orderUnchanged = notification.getNotification().when== oldNotification.getNotification().when 944 && notification.getScore() == oldNotification.getScore(); 945 // score now encompasses/supersedes isOngoing() 946 947 boolean updateTicker = notification.getNotification().tickerText != null 948 && !TextUtils.equals(notification.getNotification().tickerText, 949 oldEntry.notification.getNotification().tickerText); 950 boolean isTopAnyway = isTopNotification(rowParent, oldEntry); 951 if (contentsUnchanged && bigContentsUnchanged && (orderUnchanged || isTopAnyway)) { 952 if (DEBUG) Log.d(TAG, "reusing notification for key: " + key); 953 oldEntry.notification = notification; 954 try { 955 // Reapply the RemoteViews 956 contentView.reapply(mContext, oldEntry.expanded, mOnClickHandler); 957 if (bigContentView != null && oldEntry.getBigContentView() != null) { 958 bigContentView.reapply(mContext, oldEntry.getBigContentView(), mOnClickHandler); 959 } 960 // update the contentIntent 961 final PendingIntent contentIntent = notification.getNotification().contentIntent; 962 if (contentIntent != null) { 963 final View.OnClickListener listener = makeClicker(contentIntent, 964 notification.getPackageName(), notification.getTag(), notification.getId()); 965 oldEntry.content.setOnClickListener(listener); 966 } else { 967 oldEntry.content.setOnClickListener(null); 968 } 969 // Update the icon. 970 final StatusBarIcon ic = new StatusBarIcon(notification.getPackageName(), 971 notification.getUser(), 972 notification.getNotification().icon, notification.getNotification().iconLevel, 973 notification.getNotification().number, 974 notification.getNotification().tickerText); 975 if (!oldEntry.icon.set(ic)) { 976 handleNotificationError(key, notification, "Couldn't update icon: " + ic); 977 return; 978 } 979 updateExpansionStates(); 980 } 981 catch (RuntimeException e) { 982 // It failed to add cleanly. Log, and remove the view from the panel. 983 Log.w(TAG, "Couldn't reapply views for package " + contentView.getPackage(), e); 984 removeNotificationViews(key); 985 addNotificationViews(key, notification); 986 } 987 } else { 988 if (DEBUG) Log.d(TAG, "not reusing notification for key: " + key); 989 if (DEBUG) Log.d(TAG, "contents was " + (contentsUnchanged ? "unchanged" : "changed")); 990 if (DEBUG) Log.d(TAG, "order was " + (orderUnchanged ? "unchanged" : "changed")); 991 if (DEBUG) Log.d(TAG, "notification is " + (isTopAnyway ? "top" : "not top")); 992 final boolean wasExpanded = oldEntry.row.isUserExpanded(); 993 removeNotificationViews(key); 994 addNotificationViews(key, notification); 995 if (wasExpanded) { 996 final NotificationData.Entry newEntry = mNotificationData.findByKey(key); 997 newEntry.row.setExpanded(true); 998 newEntry.row.setUserExpanded(true); 999 } 1000 } 1001 1002 // Update the veto button accordingly (and as a result, whether this row is 1003 // swipe-dismissable) 1004 updateNotificationVetoButton(oldEntry.row, notification); 1005 1006 // Is this for you? 1007 boolean isForCurrentUser = notificationIsForCurrentUser(notification); 1008 if (DEBUG) Log.d(TAG, "notification is " + (isForCurrentUser ? "" : "not ") + "for you"); 1009 1010 // Restart the ticker if it's still running 1011 if (updateTicker && isForCurrentUser) { 1012 haltTicker(); 1013 tick(key, notification, false); 1014 } 1015 1016 // Recalculate the position of the sliding windows and the titles. 1017 setAreThereNotifications(); 1018 updateExpandedViewPos(EXPANDED_LEAVE_ALONE); 1019 1020 // See if we need to update the heads up. 1021 if (ENABLE_HEADS_UP && mInterruptingNotificationEntry != null 1022 && oldNotification == mInterruptingNotificationEntry.notification) { 1023 if (DEBUG) Log.d(TAG, "updating the current heads up:" + notification); 1024 // XXX: this is a hack for Alarms. The real implementation will need to *update* 1025 // the heads up. 1026 if (!shouldInterrupt(notification)) { 1027 if (DEBUG) Log.d(TAG, "no longer interrupts!"); 1028 mHandler.sendEmptyMessage(MSG_HIDE_HEADS_UP); 1029 } 1030 } 1031 } 1032 1033 protected boolean shouldInterrupt(StatusBarNotification sbn) { 1034 Notification notification = sbn.getNotification(); 1035 // some predicates to make the boolean logic legible 1036 boolean isNoisy = (notification.defaults & Notification.DEFAULT_SOUND) != 0 1037 || (notification.defaults & Notification.DEFAULT_VIBRATE) != 0 1038 || notification.sound != null 1039 || notification.vibrate != null; 1040 boolean isHighPriority = sbn.getScore() >= INTERRUPTION_THRESHOLD; 1041 boolean isFullscreen = notification.fullScreenIntent != null; 1042 boolean isAllowed = notification.extras.getInt(Notification.EXTRA_AS_HEADS_UP, 1043 Notification.HEADS_UP_ALLOWED) != Notification.HEADS_UP_NEVER; 1044 1045 boolean interrupt = (isFullscreen || (isHighPriority && isNoisy)) 1046 && isAllowed 1047 && mPowerManager.isScreenOn() 1048 && !mKeyguardManager.isKeyguardLocked(); 1049 try { 1050 interrupt = interrupt && !mDreamManager.isDreaming(); 1051 } catch (RemoteException e) { 1052 Log.d(TAG, "failed to query dream manager", e); 1053 } 1054 if (DEBUG) Log.d(TAG, "interrupt: " + interrupt); 1055 return interrupt; 1056 } 1057 1058 // Q: What kinds of notifications should show during setup? 1059 // A: Almost none! Only things coming from the system (package is "android") that also 1060 // have special "kind" tags marking them as relevant for setup (see below). 1061 protected boolean showNotificationEvenIfUnprovisioned(StatusBarNotification sbn) { 1062 if ("android".equals(sbn.getPackageName())) { 1063 if (sbn.getNotification().kind != null) { 1064 for (String aKind : sbn.getNotification().kind) { 1065 // IME switcher, created by InputMethodManagerService 1066 if ("android.system.imeswitcher".equals(aKind)) return true; 1067 // OTA availability & errors, created by SystemUpdateService 1068 if ("android.system.update".equals(aKind)) return true; 1069 } 1070 } 1071 } 1072 return false; 1073 } 1074 1075 public boolean inKeyguardRestrictedInputMode() { 1076 KeyguardManager km = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE); 1077 return km.inKeyguardRestrictedInputMode(); 1078 } 1079 1080 public void suspendAutohide() { 1081 // hook for subclasses 1082 } 1083 1084 public void resumeAutohide() { 1085 // hook for subclasses 1086 } 1087 1088 public void destroy() { 1089 if (mSearchPanelView != null) { 1090 mWindowManager.removeViewImmediate(mSearchPanelView); 1091 } 1092 mContext.unregisterReceiver(mBroadcastReceiver); 1093 } 1094} 1095