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