BaseStatusBar.java revision 0a6a1234c2ee5c6b810c875389016ce89022cee1
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 java.util.ArrayList; 20 21import android.app.ActivityManagerNative; 22import android.app.KeyguardManager; 23import android.app.Notification; 24import android.app.PendingIntent; 25import android.content.Context; 26import android.content.Intent; 27import android.content.pm.ApplicationInfo; 28import android.content.pm.PackageManager.NameNotFoundException; 29import android.graphics.Rect; 30import android.net.Uri; 31import android.os.Build; 32import android.os.Handler; 33import android.os.IBinder; 34import android.os.Message; 35import android.os.RemoteException; 36import android.os.ServiceManager; 37import android.provider.Settings; 38import android.text.TextUtils; 39import android.util.Log; 40import android.util.Slog; 41import android.view.Display; 42import android.view.IWindowManager; 43import android.view.LayoutInflater; 44import android.view.MenuItem; 45import android.view.MotionEvent; 46import android.view.View; 47import android.view.ViewGroup; 48import android.view.ViewGroup.LayoutParams; 49import android.view.WindowManager; 50import android.view.WindowManagerImpl; 51import android.widget.ImageView; 52import android.widget.LinearLayout; 53import android.widget.RemoteViews; 54import android.widget.PopupMenu; 55 56import com.android.internal.statusbar.IStatusBarService; 57import com.android.internal.statusbar.StatusBarIcon; 58import com.android.internal.statusbar.StatusBarIconList; 59import com.android.internal.statusbar.StatusBarNotification; 60import com.android.internal.widget.SizeAdaptiveLayout; 61import com.android.systemui.SearchPanelView; 62import com.android.systemui.SystemUI; 63import com.android.systemui.recent.RecentsPanelView; 64import com.android.systemui.recent.RecentTasksLoader; 65import com.android.systemui.recent.TaskDescription; 66import com.android.systemui.statusbar.CommandQueue; 67import com.android.systemui.statusbar.policy.NotificationRowLayout; 68import com.android.systemui.statusbar.tablet.StatusBarPanel; 69 70import com.android.systemui.R; 71 72public abstract class BaseStatusBar extends SystemUI implements 73 CommandQueue.Callbacks, RecentsPanelView.OnRecentsPanelVisibilityChangedListener { 74 static final String TAG = "StatusBar"; 75 private static final boolean DEBUG = false; 76 77 protected static final int MSG_OPEN_RECENTS_PANEL = 1020; 78 protected static final int MSG_CLOSE_RECENTS_PANEL = 1021; 79 protected static final int MSG_PRELOAD_RECENT_APPS = 1022; 80 protected static final int MSG_CANCEL_PRELOAD_RECENT_APPS = 1023; 81 protected static final int MSG_OPEN_SEARCH_PANEL = 1024; 82 protected static final int MSG_CLOSE_SEARCH_PANEL = 1025; 83 protected static final int MSG_SHOW_INTRUDER = 1026; 84 protected static final int MSG_HIDE_INTRUDER = 1027; 85 86 protected static final boolean ENABLE_INTRUDERS = false; 87 88 public static final int EXPANDED_LEAVE_ALONE = -10000; 89 public static final int EXPANDED_FULL_OPEN = -10001; 90 91 protected CommandQueue mCommandQueue; 92 protected IStatusBarService mBarService; 93 protected H mHandler = createHandler(); 94 95 // all notifications 96 protected NotificationData mNotificationData = new NotificationData(); 97 protected NotificationRowLayout mPile; 98 99 protected StatusBarNotification mCurrentlyIntrudingNotification; 100 101 // used to notify status bar for suppressing notification LED 102 protected boolean mPanelSlightlyVisible; 103 104 // Search panel 105 protected SearchPanelView mSearchPanelView; 106 107 // Recent apps 108 protected RecentsPanelView mRecentsPanel; 109 protected RecentTasksLoader mRecentTasksLoader; 110 111 protected PopupMenu mNotificationBlamePopup; 112 113 // UI-specific methods 114 115 /** 116 * Create all windows necessary for the status bar (including navigation, overlay panels, etc) 117 * and add them to the window manager. 118 */ 119 protected abstract void createAndAddWindows(); 120 121 protected Display mDisplay; 122 private IWindowManager mWindowManager; 123 124 125 public IWindowManager getWindowManager() { 126 return mWindowManager; 127 } 128 129 public Display getDisplay() { 130 return mDisplay; 131 } 132 133 public IStatusBarService getStatusBarService() { 134 return mBarService; 135 } 136 137 public void start() { 138 mDisplay = ((WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE)) 139 .getDefaultDisplay(); 140 141 mWindowManager = IWindowManager.Stub.asInterface( 142 ServiceManager.getService(Context.WINDOW_SERVICE)); 143 144 mBarService = IStatusBarService.Stub.asInterface( 145 ServiceManager.getService(Context.STATUS_BAR_SERVICE)); 146 147 // Connect in to the status bar manager service 148 StatusBarIconList iconList = new StatusBarIconList(); 149 ArrayList<IBinder> notificationKeys = new ArrayList<IBinder>(); 150 ArrayList<StatusBarNotification> notifications = new ArrayList<StatusBarNotification>(); 151 mCommandQueue = new CommandQueue(this, iconList); 152 153 int[] switches = new int[7]; 154 ArrayList<IBinder> binders = new ArrayList<IBinder>(); 155 try { 156 mBarService.registerStatusBar(mCommandQueue, iconList, notificationKeys, notifications, 157 switches, binders); 158 } catch (RemoteException ex) { 159 // If the system process isn't there we're doomed anyway. 160 } 161 162 createAndAddWindows(); 163 164 disable(switches[0]); 165 setSystemUiVisibility(switches[1], 0xffffffff); 166 topAppWindowChanged(switches[2] != 0); 167 // StatusBarManagerService has a back up of IME token and it's restored here. 168 setImeWindowStatus(binders.get(0), switches[3], switches[4]); 169 setHardKeyboardStatus(switches[5] != 0, switches[6] != 0); 170 171 // Set up the initial icon state 172 int N = iconList.size(); 173 int viewIndex = 0; 174 for (int i=0; i<N; i++) { 175 StatusBarIcon icon = iconList.getIcon(i); 176 if (icon != null) { 177 addIcon(iconList.getSlot(i), i, viewIndex, icon); 178 viewIndex++; 179 } 180 } 181 182 // Set up the initial notification state 183 N = notificationKeys.size(); 184 if (N == notifications.size()) { 185 for (int i=0; i<N; i++) { 186 addNotification(notificationKeys.get(i), notifications.get(i)); 187 } 188 } else { 189 Log.wtf(TAG, "Notification list length mismatch: keys=" + N 190 + " notifications=" + notifications.size()); 191 } 192 193 if (DEBUG) { 194 Slog.d(TAG, String.format( 195 "init: icons=%d disabled=0x%08x lights=0x%08x menu=0x%08x imeButton=0x%08x", 196 iconList.size(), 197 switches[0], 198 switches[1], 199 switches[2], 200 switches[3] 201 )); 202 } 203 } 204 205 protected View updateNotificationVetoButton(View row, StatusBarNotification n) { 206 View vetoButton = row.findViewById(R.id.veto); 207 if (n.isClearable()) { 208 final String _pkg = n.pkg; 209 final String _tag = n.tag; 210 final int _id = n.id; 211 vetoButton.setOnClickListener(new View.OnClickListener() { 212 public void onClick(View v) { 213 try { 214 mBarService.onNotificationClear(_pkg, _tag, _id); 215 } catch (RemoteException ex) { 216 // system process is dead if we're here. 217 } 218 } 219 }); 220 vetoButton.setVisibility(View.VISIBLE); 221 } else { 222 vetoButton.setVisibility(View.GONE); 223 } 224 return vetoButton; 225 } 226 227 228 protected void applyLegacyRowBackground(StatusBarNotification sbn, View content) { 229 if (sbn.notification.contentView.getLayoutId() != 230 com.android.internal.R.layout.notification_template_base) { 231 int version = 0; 232 try { 233 ApplicationInfo info = mContext.getPackageManager().getApplicationInfo(sbn.pkg, 0); 234 version = info.targetSdkVersion; 235 } catch (NameNotFoundException ex) { 236 Slog.e(TAG, "Failed looking up ApplicationInfo for " + sbn.pkg, ex); 237 } 238 if (version > 0 && version < Build.VERSION_CODES.GINGERBREAD) { 239 content.setBackgroundResource(R.drawable.notification_row_legacy_bg); 240 } else { 241 content.setBackgroundResource(com.android.internal.R.drawable.notification_bg); 242 } 243 } 244 } 245 246 private void startApplicationDetailsActivity(String packageName) { 247 Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, 248 Uri.fromParts("package", packageName, null)); 249 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 250 mContext.startActivity(intent); 251 } 252 253 protected View.OnLongClickListener getNotificationLongClicker() { 254 return new View.OnLongClickListener() { 255 @Override 256 public boolean onLongClick(View v) { 257 final String packageNameF = (String) v.getTag(); 258 if (packageNameF == null) return false; 259 if (v.getWindowToken() == null) return false; 260 mNotificationBlamePopup = new PopupMenu(mContext, v); 261 mNotificationBlamePopup.getMenuInflater().inflate( 262 R.menu.notification_popup_menu, 263 mNotificationBlamePopup.getMenu()); 264 mNotificationBlamePopup.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() { 265 public boolean onMenuItemClick(MenuItem item) { 266 if (item.getItemId() == R.id.notification_inspect_item) { 267 startApplicationDetailsActivity(packageNameF); 268 animateCollapse(); 269 } else { 270 return false; 271 } 272 return true; 273 } 274 }); 275 mNotificationBlamePopup.show(); 276 277 return true; 278 } 279 }; 280 } 281 282 public void dismissPopups() { 283 if (mNotificationBlamePopup != null) { 284 mNotificationBlamePopup.dismiss(); 285 mNotificationBlamePopup = null; 286 } 287 } 288 289 public void dismissIntruder() { 290 // pass 291 } 292 293 @Override 294 public void toggleRecentApps() { 295 int msg = (mRecentsPanel.getVisibility() == View.VISIBLE) 296 ? MSG_CLOSE_RECENTS_PANEL : MSG_OPEN_RECENTS_PANEL; 297 mHandler.removeMessages(msg); 298 mHandler.sendEmptyMessage(msg); 299 } 300 301 @Override 302 public void preloadRecentApps() { 303 int msg = MSG_PRELOAD_RECENT_APPS; 304 mHandler.removeMessages(msg); 305 mHandler.sendEmptyMessage(msg); 306 } 307 308 @Override 309 public void cancelPreloadRecentApps() { 310 int msg = MSG_CANCEL_PRELOAD_RECENT_APPS; 311 mHandler.removeMessages(msg); 312 mHandler.sendEmptyMessage(msg); 313 } 314 315 @Override 316 public void showSearchPanel() { 317 int msg = MSG_OPEN_SEARCH_PANEL; 318 mHandler.removeMessages(msg); 319 mHandler.sendEmptyMessage(msg); 320 } 321 322 @Override 323 public void hideSearchPanel() { 324 int msg = MSG_CLOSE_SEARCH_PANEL; 325 mHandler.removeMessages(msg); 326 mHandler.sendEmptyMessage(msg); 327 } 328 329 @Override 330 public void onRecentsPanelVisibilityChanged(boolean visible) { 331 } 332 333 protected abstract WindowManager.LayoutParams getRecentsLayoutParams( 334 LayoutParams layoutParams); 335 336 protected abstract WindowManager.LayoutParams getSearchLayoutParams( 337 LayoutParams layoutParams); 338 339 protected void updateRecentsPanel(int recentsResId) { 340 // Recents Panel 341 boolean visible = false; 342 ArrayList<TaskDescription> recentTasksList = null; 343 boolean firstScreenful = false; 344 if (mRecentsPanel != null) { 345 visible = mRecentsPanel.isShowing(); 346 WindowManagerImpl.getDefault().removeView(mRecentsPanel); 347 if (visible) { 348 recentTasksList = mRecentsPanel.getRecentTasksList(); 349 firstScreenful = mRecentsPanel.getFirstScreenful(); 350 } 351 } 352 353 // Provide RecentsPanelView with a temporary parent to allow layout params to work. 354 LinearLayout tmpRoot = new LinearLayout(mContext); 355 mRecentsPanel = (RecentsPanelView) LayoutInflater.from(mContext).inflate( 356 recentsResId, tmpRoot, false); 357 mRecentsPanel.setRecentTasksLoader(mRecentTasksLoader); 358 mRecentTasksLoader.setRecentsPanel(mRecentsPanel); 359 mRecentsPanel.setOnTouchListener( 360 new TouchOutsideListener(MSG_CLOSE_RECENTS_PANEL, mRecentsPanel)); 361 mRecentsPanel.setVisibility(View.GONE); 362 363 364 WindowManager.LayoutParams lp = getRecentsLayoutParams(mRecentsPanel.getLayoutParams()); 365 366 WindowManagerImpl.getDefault().addView(mRecentsPanel, lp); 367 mRecentsPanel.setBar(this); 368 if (visible) { 369 mRecentsPanel.show(true, false, recentTasksList, firstScreenful); 370 } 371 372 } 373 374 protected void updateSearchPanel() { 375 // Search Panel 376 boolean visible = false; 377 if (mSearchPanelView != null) { 378 visible = mSearchPanelView.isShowing(); 379 WindowManagerImpl.getDefault().removeView(mSearchPanelView); 380 } 381 382 // Provide SearchPanel with a temporary parent to allow layout params to work. 383 LinearLayout tmpRoot = new LinearLayout(mContext); 384 mSearchPanelView = (SearchPanelView) LayoutInflater.from(mContext).inflate( 385 R.layout.status_bar_search_panel, tmpRoot, false); 386 mSearchPanelView.setOnTouchListener( 387 new TouchOutsideListener(MSG_CLOSE_SEARCH_PANEL, mSearchPanelView)); 388 mSearchPanelView.setVisibility(View.GONE); 389 390 WindowManager.LayoutParams lp = getSearchLayoutParams(mSearchPanelView.getLayoutParams()); 391 392 WindowManagerImpl.getDefault().addView(mSearchPanelView, lp); 393 mSearchPanelView.setBar(this); 394 if (visible) { 395 mSearchPanelView.show(true, false); 396 } 397 } 398 399 protected H createHandler() { 400 return new H(); 401 } 402 403 protected class H extends Handler { 404 public void handleMessage(Message m) { 405 switch (m.what) { 406 case MSG_OPEN_RECENTS_PANEL: 407 if (DEBUG) Slog.d(TAG, "opening recents panel"); 408 if (mRecentsPanel != null) { 409 mRecentsPanel.show(true, true); 410 } 411 break; 412 case MSG_CLOSE_RECENTS_PANEL: 413 if (DEBUG) Slog.d(TAG, "closing recents panel"); 414 if (mRecentsPanel != null && mRecentsPanel.isShowing()) { 415 mRecentsPanel.show(false, true); 416 } 417 break; 418 case MSG_PRELOAD_RECENT_APPS: 419 if (DEBUG) Slog.d(TAG, "preloading recents"); 420 mRecentsPanel.preloadRecentTasksList(); 421 break; 422 case MSG_CANCEL_PRELOAD_RECENT_APPS: 423 if (DEBUG) Slog.d(TAG, "cancel preloading recents"); 424 mRecentsPanel.clearRecentTasksList(); 425 break; 426 case MSG_OPEN_SEARCH_PANEL: 427 if (DEBUG) Slog.d(TAG, "opening search panel"); 428 if (mSearchPanelView != null && mSearchPanelView.isAssistantAvailable()) { 429 mSearchPanelView.show(true, true); 430 } 431 break; 432 case MSG_CLOSE_SEARCH_PANEL: 433 if (DEBUG) Slog.d(TAG, "closing search panel"); 434 if (mSearchPanelView != null && mSearchPanelView.isShowing()) { 435 mSearchPanelView.show(false, true); 436 } 437 break; 438 } 439 } 440 } 441 442 public class TouchOutsideListener implements View.OnTouchListener { 443 private int mMsg; 444 private StatusBarPanel mPanel; 445 446 public TouchOutsideListener(int msg, StatusBarPanel panel) { 447 mMsg = msg; 448 mPanel = panel; 449 } 450 451 public boolean onTouch(View v, MotionEvent ev) { 452 final int action = ev.getAction(); 453 if (action == MotionEvent.ACTION_OUTSIDE 454 || (action == MotionEvent.ACTION_DOWN 455 && !mPanel.isInContentArea((int)ev.getX(), (int)ev.getY()))) { 456 mHandler.removeMessages(mMsg); 457 mHandler.sendEmptyMessage(mMsg); 458 return true; 459 } 460 return false; 461 } 462 } 463 464 protected void workAroundBadLayerDrawableOpacity(View v) { 465 } 466 467 protected boolean inflateViews(NotificationData.Entry entry, ViewGroup parent) { 468 int rowHeight = 469 mContext.getResources().getDimensionPixelSize(R.dimen.notification_height); 470 int minHeight = 471 mContext.getResources().getDimensionPixelSize(R.dimen.notification_min_height); 472 int maxHeight = 473 mContext.getResources().getDimensionPixelSize(R.dimen.notification_max_height); 474 StatusBarNotification sbn = entry.notification; 475 RemoteViews oneU = sbn.notification.contentView; 476 RemoteViews large = sbn.notification.bigContentView; 477 if (oneU == null) { 478 return false; 479 } 480 481 // create the row view 482 LayoutInflater inflater = (LayoutInflater)mContext.getSystemService( 483 Context.LAYOUT_INFLATER_SERVICE); 484 View row = inflater.inflate(R.layout.status_bar_notification_row, parent, false); 485 486 // for blaming (see SwipeHelper.setLongPressListener) 487 row.setTag(sbn.pkg); 488 489 // XXX: temporary: while testing big notifications, auto-expand all of them 490 ViewGroup.LayoutParams lp = row.getLayoutParams(); 491 Boolean expandable = Boolean.FALSE; 492 if (large != null) { 493 lp.height = ViewGroup.LayoutParams.WRAP_CONTENT; 494 expandable = Boolean.TRUE; 495 } else { 496 lp.height = rowHeight; 497 } 498 row.setLayoutParams(lp); 499 row.setTag(R.id.expandable_tag, expandable); 500 workAroundBadLayerDrawableOpacity(row); 501 View vetoButton = updateNotificationVetoButton(row, sbn); 502 vetoButton.setContentDescription(mContext.getString( 503 R.string.accessibility_remove_notification)); 504 505 // NB: the large icon is now handled entirely by the template 506 507 // bind the click event to the content area 508 ViewGroup content = (ViewGroup)row.findViewById(R.id.content); 509 ViewGroup adaptive = (ViewGroup)row.findViewById(R.id.adaptive); 510 511 // Ensure that R.id.content is properly set to 64dp high if 1U 512 lp = content.getLayoutParams(); 513 if (large == null) { 514 lp.height = minHeight; 515 } 516 content.setLayoutParams(lp); 517 518 content.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS); 519 520 PendingIntent contentIntent = sbn.notification.contentIntent; 521 if (contentIntent != null) { 522 final View.OnClickListener listener = new NotificationClicker(contentIntent, 523 sbn.pkg, sbn.tag, sbn.id); 524 content.setOnClickListener(listener); 525 } else { 526 content.setOnClickListener(null); 527 } 528 529 View expandedOneU = null; 530 View expandedLarge = null; 531 Exception exception = null; 532 try { 533 expandedOneU = oneU.apply(mContext, adaptive); 534 if (large != null) { 535 expandedLarge = large.apply(mContext, adaptive); 536 } 537 } 538 catch (RuntimeException e) { 539 exception = e; 540 } 541 if (expandedOneU == null && expandedLarge == null) { 542 final String ident = sbn.pkg + "/0x" + Integer.toHexString(sbn.id); 543 Slog.e(TAG, "couldn't inflate view for notification " + ident, exception); 544 return false; 545 } else { 546 if (expandedOneU != null) { 547 SizeAdaptiveLayout.LayoutParams params = 548 new SizeAdaptiveLayout.LayoutParams(expandedOneU.getLayoutParams()); 549 params.minHeight = minHeight; 550 params.maxHeight = minHeight; 551 adaptive.addView(expandedOneU, params); 552 } 553 if (expandedLarge != null) { 554 SizeAdaptiveLayout.LayoutParams params = 555 new SizeAdaptiveLayout.LayoutParams(expandedLarge.getLayoutParams()); 556 params.minHeight = minHeight+1; 557 params.maxHeight = SizeAdaptiveLayout.LayoutParams.UNBOUNDED; 558 adaptive.addView(expandedLarge, params); 559 } 560 row.setDrawingCacheEnabled(true); 561 } 562 563 applyLegacyRowBackground(sbn, content); 564 565 entry.row = row; 566 entry.content = content; 567 entry.expanded = expandedOneU; 568 entry.expandedLarge = expandedOneU; 569 570 return true; 571 } 572 573 public NotificationClicker makeClicker(PendingIntent intent, String pkg, String tag, int id) { 574 return new NotificationClicker(intent, pkg, tag, id); 575 } 576 577 private class NotificationClicker implements View.OnClickListener { 578 private PendingIntent mIntent; 579 private String mPkg; 580 private String mTag; 581 private int mId; 582 583 NotificationClicker(PendingIntent intent, String pkg, String tag, int id) { 584 mIntent = intent; 585 mPkg = pkg; 586 mTag = tag; 587 mId = id; 588 } 589 590 public void onClick(View v) { 591 try { 592 // The intent we are sending is for the application, which 593 // won't have permission to immediately start an activity after 594 // the user switches to home. We know it is safe to do at this 595 // point, so make sure new activity switches are now allowed. 596 ActivityManagerNative.getDefault().resumeAppSwitches(); 597 // Also, notifications can be launched from the lock screen, 598 // so dismiss the lock screen when the activity starts. 599 ActivityManagerNative.getDefault().dismissKeyguardOnNextActivity(); 600 } catch (RemoteException e) { 601 } 602 603 if (mIntent != null) { 604 int[] pos = new int[2]; 605 v.getLocationOnScreen(pos); 606 Intent overlay = new Intent(); 607 overlay.setSourceBounds( 608 new Rect(pos[0], pos[1], pos[0]+v.getWidth(), pos[1]+v.getHeight())); 609 try { 610 mIntent.send(mContext, 0, overlay); 611 } catch (PendingIntent.CanceledException e) { 612 // the stack trace isn't very helpful here. Just log the exception message. 613 Slog.w(TAG, "Sending contentIntent failed: " + e); 614 } 615 616 KeyguardManager kgm = 617 (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE); 618 if (kgm != null) kgm.exitKeyguardSecurely(null); 619 } 620 621 try { 622 mBarService.onNotificationClick(mPkg, mTag, mId); 623 } catch (RemoteException ex) { 624 // system process is dead if we're here. 625 } 626 627 // close the shade if it was open 628 animateCollapse(); 629 visibilityChanged(false); 630 631 // If this click was on the intruder alert, hide that instead 632// mHandler.sendEmptyMessage(MSG_HIDE_INTRUDER); 633 } 634 } 635 /** 636 * The LEDs are turned o)ff when the notification panel is shown, even just a little bit. 637 * This was added last-minute and is inconsistent with the way the rest of the notifications 638 * are handled, because the notification isn't really cancelled. The lights are just 639 * turned off. If any other notifications happen, the lights will turn back on. Steve says 640 * this is what he wants. (see bug 1131461) 641 */ 642 protected void visibilityChanged(boolean visible) { 643 if (mPanelSlightlyVisible != visible) { 644 mPanelSlightlyVisible = visible; 645 try { 646 mBarService.onPanelRevealed(); 647 } catch (RemoteException ex) { 648 // Won't fail unless the world has ended. 649 } 650 } 651 } 652 653 /** 654 * Cancel this notification and tell the StatusBarManagerService / NotificationManagerService 655 * about the failure. 656 * 657 * WARNING: this will call back into us. Don't hold any locks. 658 */ 659 void handleNotificationError(IBinder key, StatusBarNotification n, String message) { 660 removeNotification(key); 661 try { 662 mBarService.onNotificationError(n.pkg, n.tag, n.id, n.uid, n.initialPid, message); 663 } catch (RemoteException ex) { 664 // The end is nigh. 665 } 666 } 667 668 protected StatusBarNotification removeNotificationViews(IBinder key) { 669 NotificationData.Entry entry = mNotificationData.remove(key); 670 if (entry == null) { 671 Slog.w(TAG, "removeNotification for unknown key: " + key); 672 return null; 673 } 674 // Remove the expanded view. 675 ViewGroup rowParent = (ViewGroup)entry.row.getParent(); 676 if (rowParent != null) rowParent.removeView(entry.row); 677 updateNotificationIcons(); 678 679 return entry.notification; 680 } 681 682 protected StatusBarIconView addNotificationViews(IBinder key, 683 StatusBarNotification notification) { 684 if (DEBUG) { 685 Slog.d(TAG, "addNotificationViews(key=" + key + ", notification=" + notification); 686 } 687 // Construct the icon. 688 final StatusBarIconView iconView = new StatusBarIconView(mContext, 689 notification.pkg + "/0x" + Integer.toHexString(notification.id), 690 notification.notification); 691 iconView.setScaleType(ImageView.ScaleType.CENTER_INSIDE); 692 693 final StatusBarIcon ic = new StatusBarIcon(notification.pkg, 694 notification.notification.icon, 695 notification.notification.iconLevel, 696 notification.notification.number, 697 notification.notification.tickerText); 698 if (!iconView.set(ic)) { 699 handleNotificationError(key, notification, "Couldn't create icon: " + ic); 700 return null; 701 } 702 // Construct the expanded view. 703 NotificationData.Entry entry = new NotificationData.Entry(key, notification, iconView); 704 if (!inflateViews(entry, mPile)) { 705 handleNotificationError(key, notification, "Couldn't expand RemoteViews for: " 706 + notification); 707 return null; 708 } 709 710 // Add the expanded view and icon. 711 int pos = mNotificationData.add(entry); 712 if (DEBUG) { 713 Slog.d(TAG, "addNotificationViews: added at " + pos); 714 } 715 updateNotificationIcons(); 716 717 return iconView; 718 } 719 720 protected abstract void haltTicker(); 721 protected abstract void setAreThereNotifications(); 722 protected abstract void updateNotificationIcons(); 723 protected abstract void tick(IBinder key, StatusBarNotification n, boolean firstTime); 724 protected abstract void updateExpandedViewPos(int expandedPosition); 725 726 protected boolean isTopNotification(ViewGroup parent, NotificationData.Entry entry) { 727 return parent.indexOfChild(entry.row) == 0; 728 } 729 730 public void updateNotification(IBinder key, StatusBarNotification notification) { 731 if (DEBUG) Slog.d(TAG, "updateNotification(" + key + " -> " + notification + ")"); 732 733 final NotificationData.Entry oldEntry = mNotificationData.findByKey(key); 734 if (oldEntry == null) { 735 Slog.w(TAG, "updateNotification for unknown key: " + key); 736 return; 737 } 738 739 final StatusBarNotification oldNotification = oldEntry.notification; 740 741 // XXX: modify when we do something more intelligent with the two content views 742 final RemoteViews oldContentView = (oldNotification.notification.bigContentView != null) 743 ? oldNotification.notification.bigContentView 744 : oldNotification.notification.contentView; 745 final RemoteViews contentView = (notification.notification.bigContentView != null) 746 ? notification.notification.bigContentView 747 : notification.notification.contentView; 748 749 if (DEBUG) { 750 Slog.d(TAG, "old notification: when=" + oldNotification.notification.when 751 + " ongoing=" + oldNotification.isOngoing() 752 + " expanded=" + oldEntry.expanded 753 + " contentView=" + oldContentView 754 + " rowParent=" + oldEntry.row.getParent()); 755 Slog.d(TAG, "new notification: when=" + notification.notification.when 756 + " ongoing=" + oldNotification.isOngoing() 757 + " contentView=" + contentView); 758 } 759 760 // Can we just reapply the RemoteViews in place? If when didn't change, the order 761 // didn't change. 762 boolean contentsUnchanged = oldEntry.expanded != null 763 && contentView != null && oldContentView != null 764 && contentView.getPackage() != null 765 && oldContentView.getPackage() != null 766 && oldContentView.getPackage().equals(contentView.getPackage()) 767 && oldContentView.getLayoutId() == contentView.getLayoutId(); 768 ViewGroup rowParent = (ViewGroup) oldEntry.row.getParent(); 769 boolean orderUnchanged = notification.notification.when==oldNotification.notification.when 770 && notification.score == oldNotification.score; 771 // score now encompasses/supersedes isOngoing() 772 773 boolean updateTicker = notification.notification.tickerText != null 774 && !TextUtils.equals(notification.notification.tickerText, 775 oldEntry.notification.notification.tickerText); 776 boolean isTopAnyway = isTopNotification(rowParent, oldEntry); 777 if (contentsUnchanged && (orderUnchanged || isTopAnyway)) { 778 if (DEBUG) Slog.d(TAG, "reusing notification for key: " + key); 779 oldEntry.notification = notification; 780 try { 781 // Reapply the RemoteViews 782 contentView.reapply(mContext, oldEntry.content); 783 // update the contentIntent 784 final PendingIntent contentIntent = notification.notification.contentIntent; 785 if (contentIntent != null) { 786 final View.OnClickListener listener = makeClicker(contentIntent, 787 notification.pkg, notification.tag, notification.id); 788 oldEntry.content.setOnClickListener(listener); 789 } else { 790 oldEntry.content.setOnClickListener(null); 791 } 792 // Update the icon. 793 final StatusBarIcon ic = new StatusBarIcon(notification.pkg, 794 notification.notification.icon, notification.notification.iconLevel, 795 notification.notification.number, 796 notification.notification.tickerText); 797 if (!oldEntry.icon.set(ic)) { 798 handleNotificationError(key, notification, "Couldn't update icon: " + ic); 799 return; 800 } 801 } 802 catch (RuntimeException e) { 803 // It failed to add cleanly. Log, and remove the view from the panel. 804 Slog.w(TAG, "Couldn't reapply views for package " + contentView.getPackage(), e); 805 removeNotificationViews(key); 806 addNotificationViews(key, notification); 807 } 808 } else { 809 if (DEBUG) Slog.d(TAG, "not reusing notification for key: " + key); 810 removeNotificationViews(key); 811 addNotificationViews(key, notification); 812 } 813 814 // Update the veto button accordingly (and as a result, whether this row is 815 // swipe-dismissable) 816 updateNotificationVetoButton(oldEntry.row, notification); 817 818 // Restart the ticker if it's still running 819 if (updateTicker) { 820 haltTicker(); 821 tick(key, notification, false); 822 } 823 824 // Recalculate the position of the sliding windows and the titles. 825 setAreThereNotifications(); 826 updateExpandedViewPos(EXPANDED_LEAVE_ALONE); 827 828 // See if we need to update the intruder. 829 if (ENABLE_INTRUDERS && oldNotification == mCurrentlyIntrudingNotification) { 830 if (DEBUG) Slog.d(TAG, "updating the current intruder:" + notification); 831 // XXX: this is a hack for Alarms. The real implementation will need to *update* 832 // the intruder. 833 if (notification.notification.fullScreenIntent == null) { // TODO(dsandler): consistent logic with add() 834 if (DEBUG) Slog.d(TAG, "no longer intrudes!"); 835 mHandler.sendEmptyMessage(MSG_HIDE_INTRUDER); 836 } 837 } 838 } 839} 840