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 17 package com.android.systemui.statusbar; 18 19 import java.util.ArrayList; 20 21 import android.app.ActivityManagerNative; 22 import android.app.KeyguardManager; 23 import android.app.Notification; 24 import android.app.PendingIntent; 25 import android.content.Context; 26 import android.content.Intent; 27 import android.content.pm.ApplicationInfo; 28 import android.content.pm.PackageManager.NameNotFoundException; 29 import android.database.ContentObserver; 30 import android.graphics.Rect; 31 import android.net.Uri; 32 import android.os.Build; 33 import android.os.Handler; 34 import android.os.IBinder; 35 import android.os.Message; 36 import android.os.RemoteException; 37 import android.os.ServiceManager; 38 import android.provider.Settings; 39 import android.text.TextUtils; 40 import android.util.Log; 41 import android.util.Slog; 42 import android.view.Display; 43 import android.view.IWindowManager; 44 import android.view.LayoutInflater; 45 import android.view.MenuItem; 46 import android.view.MotionEvent; 47 import android.view.View; 48 import android.view.ViewGroup; 49 import android.view.ViewGroup.LayoutParams; 50 import android.view.WindowManager; 51 import android.view.WindowManagerImpl; 52 import android.widget.ImageView; 53 import android.widget.LinearLayout; 54 import android.widget.RemoteViews; 55 import android.widget.PopupMenu; 56 57 import com.android.internal.statusbar.IStatusBarService; 58 import com.android.internal.statusbar.StatusBarIcon; 59 import com.android.internal.statusbar.StatusBarIconList; 60 import com.android.internal.statusbar.StatusBarNotification; 61 import com.android.internal.widget.SizeAdaptiveLayout; 62 import com.android.systemui.SearchPanelView; 63 import com.android.systemui.SystemUI; 64 import com.android.systemui.recent.RecentsPanelView; 65 import com.android.systemui.recent.RecentTasksLoader; 66 import com.android.systemui.recent.TaskDescription; 67 import com.android.systemui.statusbar.CommandQueue; 68 import com.android.systemui.statusbar.NotificationData.Entry; 69 import com.android.systemui.statusbar.policy.NotificationRowLayout; 70 import com.android.systemui.statusbar.tablet.StatusBarPanel; 71 72 import com.android.systemui.R; 73 74 public 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 expandedOneU = oneU.apply(mContext, adaptive, mOnClickHandler); 581 if (large != null) { 582 expandedLarge = large.apply(mContext, adaptive, mOnClickHandler); 583 } 584 } 585 catch (RuntimeException e) { 586 final String ident = sbn.pkg + "/0x" + Integer.toHexString(sbn.id); 587 Slog.e(TAG, "couldn't inflate view for notification " + ident, e); 588 return false; 589 } 590 591 if (expandedOneU != null) { 592 SizeAdaptiveLayout.LayoutParams params = 593 new SizeAdaptiveLayout.LayoutParams(expandedOneU.getLayoutParams()); 594 params.minHeight = minHeight; 595 params.maxHeight = minHeight; 596 adaptive.addView(expandedOneU, params); 597 } 598 if (expandedLarge != null) { 599 SizeAdaptiveLayout.LayoutParams params = 600 new SizeAdaptiveLayout.LayoutParams(expandedLarge.getLayoutParams()); 601 params.minHeight = minHeight+1; 602 params.maxHeight = maxHeight; 603 adaptive.addView(expandedLarge, params); 604 } 605 row.setDrawingCacheEnabled(true); 606 607 applyLegacyRowBackground(sbn, content); 608 609 row.setTag(R.id.expandable_tag, Boolean.valueOf(large != null)); 610 entry.row = row; 611 entry.content = content; 612 entry.expanded = expandedOneU; 613 entry.setLargeView(expandedLarge); 614 615 return true; 616 } 617 618 public NotificationClicker makeClicker(PendingIntent intent, String pkg, String tag, int id) { 619 return new NotificationClicker(intent, pkg, tag, id); 620 } 621 622 private class NotificationClicker implements View.OnClickListener { 623 private PendingIntent mIntent; 624 private String mPkg; 625 private String mTag; 626 private int mId; 627 628 NotificationClicker(PendingIntent intent, String pkg, String tag, int id) { 629 mIntent = intent; 630 mPkg = pkg; 631 mTag = tag; 632 mId = id; 633 } 634 635 public void onClick(View v) { 636 try { 637 // The intent we are sending is for the application, which 638 // won't have permission to immediately start an activity after 639 // the user switches to home. We know it is safe to do at this 640 // point, so make sure new activity switches are now allowed. 641 ActivityManagerNative.getDefault().resumeAppSwitches(); 642 // Also, notifications can be launched from the lock screen, 643 // so dismiss the lock screen when the activity starts. 644 ActivityManagerNative.getDefault().dismissKeyguardOnNextActivity(); 645 } catch (RemoteException e) { 646 } 647 648 if (mIntent != null) { 649 int[] pos = new int[2]; 650 v.getLocationOnScreen(pos); 651 Intent overlay = new Intent(); 652 overlay.setSourceBounds( 653 new Rect(pos[0], pos[1], pos[0]+v.getWidth(), pos[1]+v.getHeight())); 654 try { 655 mIntent.send(mContext, 0, overlay); 656 } catch (PendingIntent.CanceledException e) { 657 // the stack trace isn't very helpful here. Just log the exception message. 658 Slog.w(TAG, "Sending contentIntent failed: " + e); 659 } 660 661 KeyguardManager kgm = 662 (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE); 663 if (kgm != null) kgm.exitKeyguardSecurely(null); 664 } 665 666 try { 667 mBarService.onNotificationClick(mPkg, mTag, mId); 668 } catch (RemoteException ex) { 669 // system process is dead if we're here. 670 } 671 672 // close the shade if it was open 673 animateCollapse(CommandQueue.FLAG_EXCLUDE_NONE); 674 visibilityChanged(false); 675 676 // If this click was on the intruder alert, hide that instead 677 // mHandler.sendEmptyMessage(MSG_HIDE_INTRUDER); 678 } 679 } 680 /** 681 * The LEDs are turned o)ff when the notification panel is shown, even just a little bit. 682 * This was added last-minute and is inconsistent with the way the rest of the notifications 683 * are handled, because the notification isn't really cancelled. The lights are just 684 * turned off. If any other notifications happen, the lights will turn back on. Steve says 685 * this is what he wants. (see bug 1131461) 686 */ 687 protected void visibilityChanged(boolean visible) { 688 if (mPanelSlightlyVisible != visible) { 689 mPanelSlightlyVisible = visible; 690 try { 691 mBarService.onPanelRevealed(); 692 } catch (RemoteException ex) { 693 // Won't fail unless the world has ended. 694 } 695 } 696 } 697 698 /** 699 * Cancel this notification and tell the StatusBarManagerService / NotificationManagerService 700 * about the failure. 701 * 702 * WARNING: this will call back into us. Don't hold any locks. 703 */ 704 void handleNotificationError(IBinder key, StatusBarNotification n, String message) { 705 removeNotification(key); 706 try { 707 mBarService.onNotificationError(n.pkg, n.tag, n.id, n.uid, n.initialPid, message); 708 } catch (RemoteException ex) { 709 // The end is nigh. 710 } 711 } 712 713 protected StatusBarNotification removeNotificationViews(IBinder key) { 714 NotificationData.Entry entry = mNotificationData.remove(key); 715 if (entry == null) { 716 Slog.w(TAG, "removeNotification for unknown key: " + key); 717 return null; 718 } 719 // Remove the expanded view. 720 ViewGroup rowParent = (ViewGroup)entry.row.getParent(); 721 if (rowParent != null) rowParent.removeView(entry.row); 722 updateExpansionStates(); 723 updateNotificationIcons(); 724 725 return entry.notification; 726 } 727 728 protected StatusBarIconView addNotificationViews(IBinder key, 729 StatusBarNotification notification) { 730 if (DEBUG) { 731 Slog.d(TAG, "addNotificationViews(key=" + key + ", notification=" + notification); 732 } 733 // Construct the icon. 734 final StatusBarIconView iconView = new StatusBarIconView(mContext, 735 notification.pkg + "/0x" + Integer.toHexString(notification.id), 736 notification.notification); 737 iconView.setScaleType(ImageView.ScaleType.CENTER_INSIDE); 738 739 final StatusBarIcon ic = new StatusBarIcon(notification.pkg, 740 notification.notification.icon, 741 notification.notification.iconLevel, 742 notification.notification.number, 743 notification.notification.tickerText); 744 if (!iconView.set(ic)) { 745 handleNotificationError(key, notification, "Couldn't create icon: " + ic); 746 return null; 747 } 748 // Construct the expanded view. 749 NotificationData.Entry entry = new NotificationData.Entry(key, notification, iconView); 750 if (!inflateViews(entry, mPile)) { 751 handleNotificationError(key, notification, "Couldn't expand RemoteViews for: " 752 + notification); 753 return null; 754 } 755 756 // Add the expanded view and icon. 757 int pos = mNotificationData.add(entry); 758 if (DEBUG) { 759 Slog.d(TAG, "addNotificationViews: added at " + pos); 760 } 761 updateExpansionStates(); 762 updateNotificationIcons(); 763 764 return iconView; 765 } 766 767 protected boolean expandView(NotificationData.Entry entry, boolean expand) { 768 int rowHeight = 769 mContext.getResources().getDimensionPixelSize(R.dimen.notification_row_min_height); 770 ViewGroup.LayoutParams lp = entry.row.getLayoutParams(); 771 if (entry.expandable() && expand) { 772 if (DEBUG) Slog.d(TAG, "setting expanded row height to WRAP_CONTENT"); 773 lp.height = ViewGroup.LayoutParams.WRAP_CONTENT; 774 } else { 775 if (DEBUG) Slog.d(TAG, "setting collapsed row height to " + rowHeight); 776 lp.height = rowHeight; 777 } 778 entry.row.setLayoutParams(lp); 779 return expand; 780 } 781 782 protected void updateExpansionStates() { 783 int N = mNotificationData.size(); 784 for (int i = 0; i < N; i++) { 785 NotificationData.Entry entry = mNotificationData.get(i); 786 if (i == (N-1)) { 787 if (DEBUG) Slog.d(TAG, "expanding top notification at " + i); 788 expandView(entry, true); 789 } else { 790 if (!entry.userExpanded()) { 791 if (DEBUG) Slog.d(TAG, "collapsing notification at " + i); 792 expandView(entry, false); 793 } else { 794 if (DEBUG) Slog.d(TAG, "ignoring user-modified notification at " + i); 795 } 796 } 797 } 798 } 799 800 protected abstract void haltTicker(); 801 protected abstract void setAreThereNotifications(); 802 protected abstract void updateNotificationIcons(); 803 protected abstract void tick(IBinder key, StatusBarNotification n, boolean firstTime); 804 protected abstract void updateExpandedViewPos(int expandedPosition); 805 protected abstract int getExpandedViewMaxHeight(); 806 protected abstract boolean shouldDisableNavbarGestures(); 807 808 protected boolean isTopNotification(ViewGroup parent, NotificationData.Entry entry) { 809 return parent != null && parent.indexOfChild(entry.row) == 0; 810 } 811 812 public void updateNotification(IBinder key, StatusBarNotification notification) { 813 if (DEBUG) Slog.d(TAG, "updateNotification(" + key + " -> " + notification + ")"); 814 815 final NotificationData.Entry oldEntry = mNotificationData.findByKey(key); 816 if (oldEntry == null) { 817 Slog.w(TAG, "updateNotification for unknown key: " + key); 818 return; 819 } 820 821 final StatusBarNotification oldNotification = oldEntry.notification; 822 823 // XXX: modify when we do something more intelligent with the two content views 824 final RemoteViews oldContentView = oldNotification.notification.contentView; 825 final RemoteViews contentView = notification.notification.contentView; 826 final RemoteViews oldBigContentView = oldNotification.notification.bigContentView; 827 final RemoteViews bigContentView = notification.notification.bigContentView; 828 829 if (DEBUG) { 830 Slog.d(TAG, "old notification: when=" + oldNotification.notification.when 831 + " ongoing=" + oldNotification.isOngoing() 832 + " expanded=" + oldEntry.expanded 833 + " contentView=" + oldContentView 834 + " bigContentView=" + oldBigContentView 835 + " rowParent=" + oldEntry.row.getParent()); 836 Slog.d(TAG, "new notification: when=" + notification.notification.when 837 + " ongoing=" + oldNotification.isOngoing() 838 + " contentView=" + contentView 839 + " bigContentView=" + bigContentView); 840 } 841 842 // Can we just reapply the RemoteViews in place? If when didn't change, the order 843 // didn't change. 844 845 // 1U is never null 846 boolean contentsUnchanged = oldEntry.expanded != null 847 && contentView.getPackage() != null 848 && oldContentView.getPackage() != null 849 && oldContentView.getPackage().equals(contentView.getPackage()) 850 && oldContentView.getLayoutId() == contentView.getLayoutId(); 851 // large view may be null 852 boolean bigContentsUnchanged = 853 (oldEntry.getLargeView() == null && bigContentView == null) 854 || ((oldEntry.getLargeView() != null && bigContentView != null) 855 && bigContentView.getPackage() != null 856 && oldBigContentView.getPackage() != null 857 && oldBigContentView.getPackage().equals(bigContentView.getPackage()) 858 && oldBigContentView.getLayoutId() == bigContentView.getLayoutId()); 859 ViewGroup rowParent = (ViewGroup) oldEntry.row.getParent(); 860 boolean orderUnchanged = notification.notification.when==oldNotification.notification.when 861 && notification.score == oldNotification.score; 862 // score now encompasses/supersedes isOngoing() 863 864 boolean updateTicker = notification.notification.tickerText != null 865 && !TextUtils.equals(notification.notification.tickerText, 866 oldEntry.notification.notification.tickerText); 867 boolean isTopAnyway = isTopNotification(rowParent, oldEntry); 868 if (contentsUnchanged && bigContentsUnchanged && (orderUnchanged || isTopAnyway)) { 869 if (DEBUG) Slog.d(TAG, "reusing notification for key: " + key); 870 oldEntry.notification = notification; 871 try { 872 // Reapply the RemoteViews 873 contentView.reapply(mContext, oldEntry.expanded, mOnClickHandler); 874 if (bigContentView != null && oldEntry.getLargeView() != null) { 875 bigContentView.reapply(mContext, oldEntry.getLargeView(), mOnClickHandler); 876 } 877 // update the contentIntent 878 final PendingIntent contentIntent = notification.notification.contentIntent; 879 if (contentIntent != null) { 880 final View.OnClickListener listener = makeClicker(contentIntent, 881 notification.pkg, notification.tag, notification.id); 882 oldEntry.content.setOnClickListener(listener); 883 } else { 884 oldEntry.content.setOnClickListener(null); 885 } 886 // Update the icon. 887 final StatusBarIcon ic = new StatusBarIcon(notification.pkg, 888 notification.notification.icon, notification.notification.iconLevel, 889 notification.notification.number, 890 notification.notification.tickerText); 891 if (!oldEntry.icon.set(ic)) { 892 handleNotificationError(key, notification, "Couldn't update icon: " + ic); 893 return; 894 } 895 updateExpansionStates(); 896 } 897 catch (RuntimeException e) { 898 // It failed to add cleanly. Log, and remove the view from the panel. 899 Slog.w(TAG, "Couldn't reapply views for package " + contentView.getPackage(), e); 900 removeNotificationViews(key); 901 addNotificationViews(key, notification); 902 } 903 } else { 904 if (DEBUG) Slog.d(TAG, "not reusing notification for key: " + key); 905 if (DEBUG) Slog.d(TAG, "contents was " + (contentsUnchanged ? "unchanged" : "changed")); 906 if (DEBUG) Slog.d(TAG, "order was " + (orderUnchanged ? "unchanged" : "changed")); 907 if (DEBUG) Slog.d(TAG, "notification is " + (isTopAnyway ? "top" : "not top")); 908 final boolean wasExpanded = oldEntry.userExpanded(); 909 removeNotificationViews(key); 910 addNotificationViews(key, notification); 911 if (wasExpanded) { 912 final NotificationData.Entry newEntry = mNotificationData.findByKey(key); 913 expandView(newEntry, true); 914 newEntry.setUserExpanded(true); 915 } 916 } 917 918 // Update the veto button accordingly (and as a result, whether this row is 919 // swipe-dismissable) 920 updateNotificationVetoButton(oldEntry.row, notification); 921 922 // Restart the ticker if it's still running 923 if (updateTicker) { 924 haltTicker(); 925 tick(key, notification, false); 926 } 927 928 // Recalculate the position of the sliding windows and the titles. 929 setAreThereNotifications(); 930 updateExpandedViewPos(EXPANDED_LEAVE_ALONE); 931 932 // See if we need to update the intruder. 933 if (ENABLE_INTRUDERS && oldNotification == mCurrentlyIntrudingNotification) { 934 if (DEBUG) Slog.d(TAG, "updating the current intruder:" + notification); 935 // XXX: this is a hack for Alarms. The real implementation will need to *update* 936 // the intruder. 937 if (notification.notification.fullScreenIntent == null) { // TODO(dsandler): consistent logic with add() 938 if (DEBUG) Slog.d(TAG, "no longer intrudes!"); 939 mHandler.sendEmptyMessage(MSG_HIDE_INTRUDER); 940 } 941 } 942 } 943 944 // Q: What kinds of notifications should show during setup? 945 // A: Almost none! Only things coming from the system (package is "android") that also 946 // have special "kind" tags marking them as relevant for setup (see below). 947 protected boolean showNotificationEvenIfUnprovisioned(StatusBarNotification sbn) { 948 if ("android".equals(sbn.pkg)) { 949 if (sbn.notification.kind != null) { 950 for (String aKind : sbn.notification.kind) { 951 // IME switcher, created by InputMethodManagerService 952 if ("android.system.imeswitcher".equals(aKind)) return true; 953 // OTA availability & errors, created by SystemUpdateService 954 if ("android.system.update".equals(aKind)) return true; 955 } 956 } 957 } 958 return false; 959 } 960 961 public boolean inKeyguardRestrictedInputMode() { 962 KeyguardManager km = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE); 963 return km.inKeyguardRestrictedInputMode(); 964 } 965 } 966