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