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