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