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