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