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