BaseStatusBar.java revision 70cf4c104cc538678724ee61ae5538f76f2e0077
1/* 2 * Copyright (C) 2010 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.android.systemui.statusbar; 18 19import android.app.ActivityManager; 20import android.app.ActivityManagerNative; 21import android.app.Notification; 22import android.app.PendingIntent; 23import android.app.TaskStackBuilder; 24import android.content.BroadcastReceiver; 25import android.content.Context; 26import android.content.Intent; 27import android.content.IntentFilter; 28import android.content.pm.ApplicationInfo; 29import android.content.pm.PackageManager; 30import android.content.pm.PackageManager.NameNotFoundException; 31import android.content.res.Configuration; 32import android.database.ContentObserver; 33import android.graphics.Rect; 34import android.net.Uri; 35import android.os.Build; 36import android.os.Handler; 37import android.os.IBinder; 38import android.os.Message; 39import android.os.PowerManager; 40import android.os.RemoteException; 41import android.os.ServiceManager; 42import android.os.UserHandle; 43import android.provider.Settings; 44import android.service.dreams.DreamService; 45import android.service.dreams.IDreamManager; 46import android.service.notification.StatusBarNotification; 47import android.text.TextUtils; 48import android.util.Log; 49import android.view.Display; 50import android.view.IWindowManager; 51import android.view.LayoutInflater; 52import android.view.MenuItem; 53import android.view.MotionEvent; 54import android.view.View; 55import android.view.ViewGroup; 56import android.view.ViewGroup.LayoutParams; 57import android.view.WindowManager; 58import android.view.WindowManagerGlobal; 59import android.widget.ImageView; 60import android.widget.LinearLayout; 61import android.widget.PopupMenu; 62import android.widget.RemoteViews; 63import android.widget.TextView; 64 65import com.android.internal.statusbar.IStatusBarService; 66import com.android.internal.statusbar.StatusBarIcon; 67import com.android.internal.statusbar.StatusBarIconList; 68import com.android.internal.widget.SizeAdaptiveLayout; 69import com.android.systemui.R; 70import com.android.systemui.RecentsComponent; 71import com.android.systemui.SearchPanelView; 72import com.android.systemui.SystemUI; 73import com.android.systemui.statusbar.phone.KeyguardTouchDelegate; 74import com.android.systemui.statusbar.policy.NotificationRowLayout; 75 76import java.util.ArrayList; 77import java.util.Locale; 78 79public abstract class BaseStatusBar extends SystemUI implements 80 CommandQueue.Callbacks { 81 public static final String TAG = "StatusBar"; 82 public static final boolean DEBUG = false; 83 public static final boolean MULTIUSER_DEBUG = false; 84 85 protected static final int MSG_TOGGLE_RECENTS_PANEL = 1020; 86 protected static final int MSG_CLOSE_RECENTS_PANEL = 1021; 87 protected static final int MSG_PRELOAD_RECENT_APPS = 1022; 88 protected static final int MSG_CANCEL_PRELOAD_RECENT_APPS = 1023; 89 protected static final int MSG_OPEN_SEARCH_PANEL = 1024; 90 protected static final int MSG_CLOSE_SEARCH_PANEL = 1025; 91 protected static final int MSG_SHOW_HEADS_UP = 1026; 92 protected static final int MSG_HIDE_HEADS_UP = 1027; 93 protected static final int MSG_ESCALATE_HEADS_UP = 1028; 94 95 protected static final boolean ENABLE_HEADS_UP = true; 96 // scores above this threshold should be displayed in heads up mode. 97 protected static final int INTERRUPTION_THRESHOLD = 11; 98 protected static final String SETTING_HEADS_UP = "heads_up_enabled"; 99 100 // Should match the value in PhoneWindowManager 101 public static final String SYSTEM_DIALOG_REASON_RECENT_APPS = "recentapps"; 102 103 public static final int EXPANDED_LEAVE_ALONE = -10000; 104 public static final int EXPANDED_FULL_OPEN = -10001; 105 106 protected CommandQueue mCommandQueue; 107 protected IStatusBarService mBarService; 108 protected H mHandler = createHandler(); 109 110 // all notifications 111 protected NotificationData mNotificationData = new NotificationData(); 112 protected NotificationRowLayout mPile; 113 114 protected NotificationData.Entry mInterruptingNotificationEntry; 115 protected long mInterruptingNotificationTime; 116 117 // used to notify status bar for suppressing notification LED 118 protected boolean mPanelSlightlyVisible; 119 120 // Search panel 121 protected SearchPanelView mSearchPanelView; 122 123 protected PopupMenu mNotificationBlamePopup; 124 125 protected int mCurrentUserId = 0; 126 127 protected int mLayoutDirection = -1; // invalid 128 private Locale mLocale; 129 protected boolean mUseHeadsUp = false; 130 131 protected IDreamManager mDreamManager; 132 PowerManager mPowerManager; 133 protected int mRowHeight; 134 private boolean mPublicMode = false; 135 136 // UI-specific methods 137 138 /** 139 * Create all windows necessary for the status bar (including navigation, overlay panels, etc) 140 * and add them to the window manager. 141 */ 142 protected abstract void createAndAddWindows(); 143 144 protected WindowManager mWindowManager; 145 protected IWindowManager mWindowManagerService; 146 protected abstract void refreshLayout(int layoutDirection); 147 148 protected Display mDisplay; 149 150 private boolean mDeviceProvisioned = false; 151 152 private RecentsComponent mRecents; 153 154 public IStatusBarService getStatusBarService() { 155 return mBarService; 156 } 157 158 public boolean isDeviceProvisioned() { 159 return mDeviceProvisioned; 160 } 161 162 private ContentObserver mProvisioningObserver = new ContentObserver(new Handler()) { 163 @Override 164 public void onChange(boolean selfChange) { 165 final boolean provisioned = 0 != Settings.Global.getInt( 166 mContext.getContentResolver(), Settings.Global.DEVICE_PROVISIONED, 0); 167 if (provisioned != mDeviceProvisioned) { 168 mDeviceProvisioned = provisioned; 169 updateNotificationIcons(); 170 } 171 } 172 }; 173 174 private RemoteViews.OnClickHandler mOnClickHandler = new RemoteViews.OnClickHandler() { 175 @Override 176 public boolean onClickHandler(View view, PendingIntent pendingIntent, Intent fillInIntent) { 177 if (DEBUG) { 178 Log.v(TAG, "Notification click handler invoked for intent: " + pendingIntent); 179 } 180 final boolean isActivity = pendingIntent.isActivity(); 181 if (isActivity) { 182 try { 183 // The intent we are sending is for the application, which 184 // won't have permission to immediately start an activity after 185 // the user switches to home. We know it is safe to do at this 186 // point, so make sure new activity switches are now allowed. 187 ActivityManagerNative.getDefault().resumeAppSwitches(); 188 // Also, notifications can be launched from the lock screen, 189 // so dismiss the lock screen when the activity starts. 190 ActivityManagerNative.getDefault().dismissKeyguardOnNextActivity(); 191 } catch (RemoteException e) { 192 } 193 } 194 195 boolean handled = super.onClickHandler(view, pendingIntent, fillInIntent); 196 197 if (isActivity && handled) { 198 // close the shade if it was open 199 animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE); 200 visibilityChanged(false); 201 } 202 return handled; 203 } 204 }; 205 206 private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { 207 @Override 208 public void onReceive(Context context, Intent intent) { 209 String action = intent.getAction(); 210 if (Intent.ACTION_USER_SWITCHED.equals(action)) { 211 mCurrentUserId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1); 212 if (true) Log.v(TAG, "userId " + mCurrentUserId + " is in the house"); 213 userSwitched(mCurrentUserId); 214 } 215 } 216 }; 217 218 public void start() { 219 mWindowManager = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE); 220 mWindowManagerService = WindowManagerGlobal.getWindowManagerService(); 221 mDisplay = mWindowManager.getDefaultDisplay(); 222 223 mDreamManager = IDreamManager.Stub.asInterface( 224 ServiceManager.checkService(DreamService.DREAM_SERVICE)); 225 mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); 226 227 mProvisioningObserver.onChange(false); // set up 228 mContext.getContentResolver().registerContentObserver( 229 Settings.Global.getUriFor(Settings.Global.DEVICE_PROVISIONED), true, 230 mProvisioningObserver); 231 232 mBarService = IStatusBarService.Stub.asInterface( 233 ServiceManager.getService(Context.STATUS_BAR_SERVICE)); 234 235 mRecents = getComponent(RecentsComponent.class); 236 237 mLocale = mContext.getResources().getConfiguration().locale; 238 mLayoutDirection = TextUtils.getLayoutDirectionFromLocale(mLocale); 239 240 // Connect in to the status bar manager service 241 StatusBarIconList iconList = new StatusBarIconList(); 242 ArrayList<IBinder> notificationKeys = new ArrayList<IBinder>(); 243 ArrayList<StatusBarNotification> notifications = new ArrayList<StatusBarNotification>(); 244 mCommandQueue = new CommandQueue(this, iconList); 245 246 int[] switches = new int[7]; 247 ArrayList<IBinder> binders = new ArrayList<IBinder>(); 248 try { 249 mBarService.registerStatusBar(mCommandQueue, iconList, notificationKeys, notifications, 250 switches, binders); 251 } catch (RemoteException ex) { 252 // If the system process isn't there we're doomed anyway. 253 } 254 255 createAndAddWindows(); 256 257 disable(switches[0]); 258 setSystemUiVisibility(switches[1], 0xffffffff); 259 topAppWindowChanged(switches[2] != 0); 260 // StatusBarManagerService has a back up of IME token and it's restored here. 261 setImeWindowStatus(binders.get(0), switches[3], switches[4]); 262 setHardKeyboardStatus(switches[5] != 0, switches[6] != 0); 263 264 // Set up the initial icon state 265 int N = iconList.size(); 266 int viewIndex = 0; 267 for (int i=0; i<N; i++) { 268 StatusBarIcon icon = iconList.getIcon(i); 269 if (icon != null) { 270 addIcon(iconList.getSlot(i), i, viewIndex, icon); 271 viewIndex++; 272 } 273 } 274 275 // Set up the initial notification state 276 N = notificationKeys.size(); 277 if (N == notifications.size()) { 278 for (int i=0; i<N; i++) { 279 addNotification(notificationKeys.get(i), notifications.get(i)); 280 } 281 } else { 282 Log.wtf(TAG, "Notification list length mismatch: keys=" + N 283 + " notifications=" + notifications.size()); 284 } 285 286 if (DEBUG) { 287 Log.d(TAG, String.format( 288 "init: icons=%d disabled=0x%08x lights=0x%08x menu=0x%08x imeButton=0x%08x", 289 iconList.size(), 290 switches[0], 291 switches[1], 292 switches[2], 293 switches[3] 294 )); 295 } 296 297 mCurrentUserId = ActivityManager.getCurrentUser(); 298 299 IntentFilter filter = new IntentFilter(); 300 filter.addAction(Intent.ACTION_USER_SWITCHED); 301 mContext.registerReceiver(mBroadcastReceiver, filter); 302 } 303 304 public void userSwitched(int newUserId) { 305 // should be overridden 306 } 307 308 public boolean notificationIsForCurrentUser(StatusBarNotification n) { 309 final int thisUserId = mCurrentUserId; 310 final int notificationUserId = n.getUserId(); 311 if (DEBUG && MULTIUSER_DEBUG) { 312 Log.v(TAG, String.format("%s: current userid: %d, notification userid: %d", 313 n, thisUserId, notificationUserId)); 314 } 315 return notificationUserId == UserHandle.USER_ALL 316 || thisUserId == notificationUserId; 317 } 318 319 @Override 320 protected void onConfigurationChanged(Configuration newConfig) { 321 final Locale locale = mContext.getResources().getConfiguration().locale; 322 final int ld = TextUtils.getLayoutDirectionFromLocale(locale); 323 if (! locale.equals(mLocale) || ld != mLayoutDirection) { 324 if (DEBUG) { 325 Log.v(TAG, String.format( 326 "config changed locale/LD: %s (%d) -> %s (%d)", mLocale, mLayoutDirection, 327 locale, ld)); 328 } 329 mLocale = locale; 330 mLayoutDirection = ld; 331 refreshLayout(ld); 332 } 333 } 334 335 protected View updateNotificationVetoButton(View row, StatusBarNotification n) { 336 View vetoButton = row.findViewById(R.id.veto); 337 if (n.isClearable() || (mInterruptingNotificationEntry != null 338 && mInterruptingNotificationEntry.row == row)) { 339 final String _pkg = n.getPackageName(); 340 final String _tag = n.getTag(); 341 final int _id = n.getId(); 342 vetoButton.setOnClickListener(new View.OnClickListener() { 343 public void onClick(View v) { 344 // Accessibility feedback 345 v.announceForAccessibility( 346 mContext.getString(R.string.accessibility_notification_dismissed)); 347 try { 348 mBarService.onNotificationClear(_pkg, _tag, _id); 349 350 } catch (RemoteException ex) { 351 // system process is dead if we're here. 352 } 353 } 354 }); 355 vetoButton.setVisibility(View.VISIBLE); 356 } else { 357 vetoButton.setVisibility(View.GONE); 358 } 359 vetoButton.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO); 360 return vetoButton; 361 } 362 363 364 protected void applyLegacyRowBackground(StatusBarNotification sbn, View content) { 365 if (sbn.getNotification().contentView.getLayoutId() != 366 com.android.internal.R.layout.notification_template_base) { 367 int version = 0; 368 try { 369 ApplicationInfo info = mContext.getPackageManager().getApplicationInfo(sbn.getPackageName(), 0); 370 version = info.targetSdkVersion; 371 } catch (NameNotFoundException ex) { 372 Log.e(TAG, "Failed looking up ApplicationInfo for " + sbn.getPackageName(), ex); 373 } 374 if (version > 0 && version < Build.VERSION_CODES.GINGERBREAD) { 375 content.setBackgroundResource(R.drawable.notification_row_legacy_bg); 376 } else { 377 content.setBackgroundResource(com.android.internal.R.drawable.notification_bg); 378 } 379 } 380 } 381 382 private void startApplicationDetailsActivity(String packageName) { 383 Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, 384 Uri.fromParts("package", packageName, null)); 385 intent.setComponent(intent.resolveActivity(mContext.getPackageManager())); 386 TaskStackBuilder.create(mContext).addNextIntentWithParentStack(intent).startActivities( 387 null, UserHandle.CURRENT); 388 } 389 390 protected View.OnLongClickListener getNotificationLongClicker() { 391 return new View.OnLongClickListener() { 392 @Override 393 public boolean onLongClick(View v) { 394 final String packageNameF = (String) v.getTag(); 395 if (packageNameF == null) return false; 396 if (v.getWindowToken() == null) return false; 397 mNotificationBlamePopup = new PopupMenu(mContext, v); 398 mNotificationBlamePopup.getMenuInflater().inflate( 399 R.menu.notification_popup_menu, 400 mNotificationBlamePopup.getMenu()); 401 mNotificationBlamePopup.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() { 402 public boolean onMenuItemClick(MenuItem item) { 403 if (item.getItemId() == R.id.notification_inspect_item) { 404 startApplicationDetailsActivity(packageNameF); 405 animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE); 406 } else { 407 return false; 408 } 409 return true; 410 } 411 }); 412 mNotificationBlamePopup.show(); 413 414 return true; 415 } 416 }; 417 } 418 419 public void dismissPopups() { 420 if (mNotificationBlamePopup != null) { 421 mNotificationBlamePopup.dismiss(); 422 mNotificationBlamePopup = null; 423 } 424 } 425 426 public void onHeadsUpDismissed() { 427 } 428 429 @Override 430 public void toggleRecentApps() { 431 int msg = MSG_TOGGLE_RECENTS_PANEL; 432 mHandler.removeMessages(msg); 433 mHandler.sendEmptyMessage(msg); 434 } 435 436 @Override 437 public void preloadRecentApps() { 438 int msg = MSG_PRELOAD_RECENT_APPS; 439 mHandler.removeMessages(msg); 440 mHandler.sendEmptyMessage(msg); 441 } 442 443 @Override 444 public void cancelPreloadRecentApps() { 445 int msg = MSG_CANCEL_PRELOAD_RECENT_APPS; 446 mHandler.removeMessages(msg); 447 mHandler.sendEmptyMessage(msg); 448 } 449 450 @Override 451 public void showSearchPanel() { 452 int msg = MSG_OPEN_SEARCH_PANEL; 453 mHandler.removeMessages(msg); 454 mHandler.sendEmptyMessage(msg); 455 } 456 457 @Override 458 public void hideSearchPanel() { 459 int msg = MSG_CLOSE_SEARCH_PANEL; 460 mHandler.removeMessages(msg); 461 mHandler.sendEmptyMessage(msg); 462 } 463 464 protected abstract WindowManager.LayoutParams getSearchLayoutParams( 465 LayoutParams layoutParams); 466 467 protected void updateSearchPanel() { 468 // Search Panel 469 boolean visible = false; 470 if (mSearchPanelView != null) { 471 visible = mSearchPanelView.isShowing(); 472 mWindowManager.removeView(mSearchPanelView); 473 } 474 475 // Provide SearchPanel with a temporary parent to allow layout params to work. 476 LinearLayout tmpRoot = new LinearLayout(mContext); 477 mSearchPanelView = (SearchPanelView) LayoutInflater.from(mContext).inflate( 478 R.layout.status_bar_search_panel, tmpRoot, false); 479 mSearchPanelView.setOnTouchListener( 480 new TouchOutsideListener(MSG_CLOSE_SEARCH_PANEL, mSearchPanelView)); 481 mSearchPanelView.setVisibility(View.GONE); 482 483 WindowManager.LayoutParams lp = getSearchLayoutParams(mSearchPanelView.getLayoutParams()); 484 485 mWindowManager.addView(mSearchPanelView, lp); 486 mSearchPanelView.setBar(this); 487 if (visible) { 488 mSearchPanelView.show(true, false); 489 } 490 } 491 492 protected H createHandler() { 493 return new H(); 494 } 495 496 static void sendCloseSystemWindows(Context context, String reason) { 497 if (ActivityManagerNative.isSystemReady()) { 498 try { 499 ActivityManagerNative.getDefault().closeSystemDialogs(reason); 500 } catch (RemoteException e) { 501 } 502 } 503 } 504 505 protected abstract View getStatusBarView(); 506 507 protected View.OnTouchListener mRecentsPreloadOnTouchListener = new View.OnTouchListener() { 508 // additional optimization when we have software system buttons - start loading the recent 509 // tasks on touch down 510 @Override 511 public boolean onTouch(View v, MotionEvent event) { 512 int action = event.getAction() & MotionEvent.ACTION_MASK; 513 if (action == MotionEvent.ACTION_DOWN) { 514 preloadRecentTasksList(); 515 } else if (action == MotionEvent.ACTION_CANCEL) { 516 cancelPreloadingRecentTasksList(); 517 } else if (action == MotionEvent.ACTION_UP) { 518 if (!v.isPressed()) { 519 cancelPreloadingRecentTasksList(); 520 } 521 522 } 523 return false; 524 } 525 }; 526 527 protected void toggleRecentsActivity() { 528 if (mRecents != null) { 529 mRecents.toggleRecents(mDisplay, mLayoutDirection, getStatusBarView()); 530 } 531 } 532 533 protected void preloadRecentTasksList() { 534 if (mRecents != null) { 535 mRecents.preloadRecentTasksList(); 536 } 537 } 538 539 protected void cancelPreloadingRecentTasksList() { 540 if (mRecents != null) { 541 mRecents.cancelPreloadingRecentTasksList(); 542 } 543 } 544 545 protected void closeRecents() { 546 if (mRecents != null) { 547 mRecents.closeRecents(); 548 } 549 } 550 551 public abstract void resetHeadsUpDecayTimer(); 552 553 public void setPublicMode(boolean publicMode) { 554 mPublicMode = publicMode; 555 } 556 557 public boolean isPublicMode() { 558 return mPublicMode; 559 } 560 561 protected class H extends Handler { 562 public void handleMessage(Message m) { 563 Intent intent; 564 switch (m.what) { 565 case MSG_TOGGLE_RECENTS_PANEL: 566 toggleRecentsActivity(); 567 break; 568 case MSG_CLOSE_RECENTS_PANEL: 569 closeRecents(); 570 break; 571 case MSG_PRELOAD_RECENT_APPS: 572 preloadRecentTasksList(); 573 break; 574 case MSG_CANCEL_PRELOAD_RECENT_APPS: 575 cancelPreloadingRecentTasksList(); 576 break; 577 case MSG_OPEN_SEARCH_PANEL: 578 if (DEBUG) Log.d(TAG, "opening search panel"); 579 if (mSearchPanelView != null && mSearchPanelView.isAssistantAvailable()) { 580 mSearchPanelView.show(true, true); 581 onShowSearchPanel(); 582 } 583 break; 584 case MSG_CLOSE_SEARCH_PANEL: 585 if (DEBUG) Log.d(TAG, "closing search panel"); 586 if (mSearchPanelView != null && mSearchPanelView.isShowing()) { 587 mSearchPanelView.show(false, true); 588 onHideSearchPanel(); 589 } 590 break; 591 } 592 } 593 } 594 595 public class TouchOutsideListener implements View.OnTouchListener { 596 private int mMsg; 597 private StatusBarPanel mPanel; 598 599 public TouchOutsideListener(int msg, StatusBarPanel panel) { 600 mMsg = msg; 601 mPanel = panel; 602 } 603 604 public boolean onTouch(View v, MotionEvent ev) { 605 final int action = ev.getAction(); 606 if (action == MotionEvent.ACTION_OUTSIDE 607 || (action == MotionEvent.ACTION_DOWN 608 && !mPanel.isInContentArea((int)ev.getX(), (int)ev.getY()))) { 609 mHandler.removeMessages(mMsg); 610 mHandler.sendEmptyMessage(mMsg); 611 return true; 612 } 613 return false; 614 } 615 } 616 617 protected void workAroundBadLayerDrawableOpacity(View v) { 618 } 619 620 protected void onHideSearchPanel() { 621 } 622 623 protected void onShowSearchPanel() { 624 } 625 626 public boolean inflateViews(NotificationData.Entry entry, ViewGroup parent) { 627 return inflateViews(entry, parent, false); 628 } 629 630 public boolean inflateViewsForHeadsUp(NotificationData.Entry entry, ViewGroup parent) { 631 return inflateViews(entry, parent, true); 632 } 633 634 public boolean inflateViews(NotificationData.Entry entry, ViewGroup parent, boolean isHeadsUp) { 635 int minHeight = 636 mContext.getResources().getDimensionPixelSize(R.dimen.notification_min_height); 637 int maxHeight = 638 mContext.getResources().getDimensionPixelSize(R.dimen.notification_max_height); 639 StatusBarNotification sbn = entry.notification; 640 RemoteViews contentView = sbn.getNotification().contentView; 641 RemoteViews bigContentView = sbn.getNotification().bigContentView; 642 if (contentView == null) { 643 return false; 644 } 645 646 Log.v(TAG, "publicNotification: " 647 + sbn.getNotification().publicVersion); 648 649 Notification publicNotification = sbn.getNotification().publicVersion; 650 651 // create the row view 652 LayoutInflater inflater = (LayoutInflater)mContext.getSystemService( 653 Context.LAYOUT_INFLATER_SERVICE); 654 ExpandableNotificationRow row = (ExpandableNotificationRow) inflater.inflate( 655 R.layout.status_bar_notification_row, parent, false); 656 657 // for blaming (see SwipeHelper.setLongPressListener) 658 row.setTag(sbn.getPackageName()); 659 660 workAroundBadLayerDrawableOpacity(row); 661 View vetoButton = updateNotificationVetoButton(row, sbn); 662 vetoButton.setContentDescription(mContext.getString( 663 R.string.accessibility_remove_notification)); 664 665 // NB: the large icon is now handled entirely by the template 666 667 // bind the click event to the content area 668 ViewGroup content = (ViewGroup)row.findViewById(R.id.container); 669 SizeAdaptiveLayout expanded = (SizeAdaptiveLayout)row.findViewById(R.id.expanded); 670 SizeAdaptiveLayout expandedPublic 671 = (SizeAdaptiveLayout)row.findViewById(R.id.expandedPublic); 672 673 content.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS); 674 675 PendingIntent contentIntent = sbn.getNotification().contentIntent; 676 if (contentIntent != null) { 677 final View.OnClickListener listener = makeClicker(contentIntent, 678 sbn.getPackageName(), sbn.getTag(), sbn.getId(), isHeadsUp); 679 content.setOnClickListener(listener); 680 } else { 681 content.setOnClickListener(null); 682 } 683 684 // set up the adaptive layout 685 View contentViewLocal = null; 686 View bigContentViewLocal = null; 687 try { 688 contentViewLocal = contentView.apply(mContext, expanded, mOnClickHandler); 689 if (bigContentView != null) { 690 bigContentViewLocal = bigContentView.apply(mContext, expanded, mOnClickHandler); 691 } 692 } 693 catch (RuntimeException e) { 694 final String ident = sbn.getPackageName() + "/0x" + Integer.toHexString(sbn.getId()); 695 Log.e(TAG, "couldn't inflate view for notification " + ident, e); 696 return false; 697 } 698 699 if (contentViewLocal != null) { 700 SizeAdaptiveLayout.LayoutParams params = 701 new SizeAdaptiveLayout.LayoutParams(contentViewLocal.getLayoutParams()); 702 params.minHeight = minHeight; 703 params.maxHeight = minHeight; 704 expanded.addView(contentViewLocal, params); 705 } 706 if (bigContentViewLocal != null) { 707 SizeAdaptiveLayout.LayoutParams params = 708 new SizeAdaptiveLayout.LayoutParams(bigContentViewLocal.getLayoutParams()); 709 params.minHeight = minHeight+1; 710 params.maxHeight = maxHeight; 711 expanded.addView(bigContentViewLocal, params); 712 } 713 714 PackageManager pm = mContext.getPackageManager(); 715 716 // now the public version 717 View publicViewLocal = null; 718 if (publicNotification != null) { 719 try { 720 publicViewLocal = publicNotification.contentView.apply(mContext, 721 expandedPublic, mOnClickHandler); 722 723 if (publicViewLocal != null) { 724 SizeAdaptiveLayout.LayoutParams params = 725 new SizeAdaptiveLayout.LayoutParams(publicViewLocal.getLayoutParams()); 726 params.minHeight = minHeight; 727 params.maxHeight = minHeight; 728 expandedPublic.addView(publicViewLocal, params); 729 } 730 } 731 catch (RuntimeException e) { 732 final String ident = sbn.getPackageName() + "/0x" + Integer.toHexString(sbn.getId()); 733 Log.e(TAG, "couldn't inflate public view for notification " + ident, e); 734 publicViewLocal = null; 735 } 736 } 737 738 if (publicViewLocal == null) { 739 // Add a basic notification template 740 publicViewLocal = LayoutInflater.from(mContext).inflate( 741 com.android.internal.R.layout.notification_template_base, expandedPublic, true); 742 743 final TextView title = (TextView) publicViewLocal.findViewById(com.android.internal.R.id.title); 744 try { 745 title.setText(pm.getApplicationLabel( 746 pm.getApplicationInfo(entry.notification.getPackageName(), 0))); 747 } catch (NameNotFoundException e) { 748 title.setText(entry.notification.getPackageName()); 749 } 750 751 final ImageView icon = (ImageView) publicViewLocal.findViewById(com.android.internal.R.id.icon); 752 753 final StatusBarIcon ic = new StatusBarIcon(entry.notification.getPackageName(), 754 entry.notification.getUser(), 755 entry.notification.getNotification().icon, 756 entry.notification.getNotification().iconLevel, 757 entry.notification.getNotification().number, 758 entry.notification.getNotification().tickerText); 759 760 icon.setImageDrawable(StatusBarIconView.getIcon(mContext, ic)); 761 762 final TextView text = (TextView) publicViewLocal.findViewById(com.android.internal.R.id.text); 763 text.setText("Unlock your device to see this notification."); 764 765 // TODO: fill out "time" as well 766 } 767 768 row.setDrawingCacheEnabled(true); 769 770 applyLegacyRowBackground(sbn, content); 771 772 if (MULTIUSER_DEBUG) { 773 TextView debug = (TextView) row.findViewById(R.id.debug_info); 774 if (debug != null) { 775 debug.setVisibility(View.VISIBLE); 776 debug.setText("U " + entry.notification.getUserId()); 777 } 778 } 779 entry.row = row; 780 entry.row.setRowHeight(mRowHeight); 781 entry.content = content; 782 entry.expanded = contentViewLocal; 783 entry.expandedPublic = publicViewLocal; 784 entry.setBigContentView(bigContentViewLocal); 785 786 return true; 787 } 788 789 public NotificationClicker makeClicker(PendingIntent intent, String pkg, String tag, int id, 790 boolean forHun) { 791 return new NotificationClicker(intent, pkg, tag, id, forHun); 792 } 793 794 protected class NotificationClicker implements View.OnClickListener { 795 private PendingIntent mIntent; 796 private String mPkg; 797 private String mTag; 798 private int mId; 799 private boolean mIsHeadsUp; 800 801 public NotificationClicker(PendingIntent intent, String pkg, String tag, int id, 802 boolean forHun) { 803 mIntent = intent; 804 mPkg = pkg; 805 mTag = tag; 806 mId = id; 807 mIsHeadsUp = forHun; 808 } 809 810 public void onClick(View v) { 811 try { 812 // The intent we are sending is for the application, which 813 // won't have permission to immediately start an activity after 814 // the user switches to home. We know it is safe to do at this 815 // point, so make sure new activity switches are now allowed. 816 ActivityManagerNative.getDefault().resumeAppSwitches(); 817 // Also, notifications can be launched from the lock screen, 818 // so dismiss the lock screen when the activity starts. 819 ActivityManagerNative.getDefault().dismissKeyguardOnNextActivity(); 820 } catch (RemoteException e) { 821 } 822 823 if (mIntent != null) { 824 int[] pos = new int[2]; 825 v.getLocationOnScreen(pos); 826 Intent overlay = new Intent(); 827 overlay.setSourceBounds( 828 new Rect(pos[0], pos[1], pos[0]+v.getWidth(), pos[1]+v.getHeight())); 829 try { 830 mIntent.send(mContext, 0, overlay); 831 } catch (PendingIntent.CanceledException e) { 832 // the stack trace isn't very helpful here. Just log the exception message. 833 Log.w(TAG, "Sending contentIntent failed: " + e); 834 } 835 836 KeyguardTouchDelegate.getInstance(mContext).dismiss(); 837 } 838 839 try { 840 if (mIsHeadsUp) { 841 mHandler.sendEmptyMessage(MSG_HIDE_HEADS_UP); 842 } 843 mBarService.onNotificationClick(mPkg, mTag, mId); 844 } catch (RemoteException ex) { 845 // system process is dead if we're here. 846 } 847 848 // close the shade if it was open 849 animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE); 850 visibilityChanged(false); 851 } 852 } 853 854 /** 855 * The LEDs are turned o)ff when the notification panel is shown, even just a little bit. 856 * This was added last-minute and is inconsistent with the way the rest of the notifications 857 * are handled, because the notification isn't really cancelled. The lights are just 858 * turned off. If any other notifications happen, the lights will turn back on. Steve says 859 * this is what he wants. (see bug 1131461) 860 */ 861 protected void visibilityChanged(boolean visible) { 862 if (mPanelSlightlyVisible != visible) { 863 mPanelSlightlyVisible = visible; 864 try { 865 mBarService.onPanelRevealed(); 866 } catch (RemoteException ex) { 867 // Won't fail unless the world has ended. 868 } 869 } 870 } 871 872 /** 873 * Cancel this notification and tell the StatusBarManagerService / NotificationManagerService 874 * about the failure. 875 * 876 * WARNING: this will call back into us. Don't hold any locks. 877 */ 878 void handleNotificationError(IBinder key, StatusBarNotification n, String message) { 879 removeNotification(key); 880 try { 881 mBarService.onNotificationError(n.getPackageName(), n.getTag(), n.getId(), n.getUid(), n.getInitialPid(), message); 882 } catch (RemoteException ex) { 883 // The end is nigh. 884 } 885 } 886 887 protected StatusBarNotification removeNotificationViews(IBinder key) { 888 NotificationData.Entry entry = mNotificationData.remove(key); 889 if (entry == null) { 890 Log.w(TAG, "removeNotification for unknown key: " + key); 891 return null; 892 } 893 // Remove the expanded view. 894 ViewGroup rowParent = (ViewGroup)entry.row.getParent(); 895 if (rowParent != null) rowParent.removeView(entry.row); 896 updateExpansionStates(); 897 updateNotificationIcons(); 898 899 return entry.notification; 900 } 901 902 protected NotificationData.Entry createNotificationViews(IBinder key, 903 StatusBarNotification notification) { 904 if (DEBUG) { 905 Log.d(TAG, "createNotificationViews(key=" + key + ", notification=" + notification); 906 } 907 // Construct the icon. 908 final StatusBarIconView iconView = new StatusBarIconView(mContext, 909 notification.getPackageName() + "/0x" + Integer.toHexString(notification.getId()), 910 notification.getNotification()); 911 iconView.setScaleType(ImageView.ScaleType.CENTER_INSIDE); 912 913 final StatusBarIcon ic = new StatusBarIcon(notification.getPackageName(), 914 notification.getUser(), 915 notification.getNotification().icon, 916 notification.getNotification().iconLevel, 917 notification.getNotification().number, 918 notification.getNotification().tickerText); 919 if (!iconView.set(ic)) { 920 handleNotificationError(key, notification, "Couldn't create icon: " + ic); 921 return null; 922 } 923 // Construct the expanded view. 924 NotificationData.Entry entry = new NotificationData.Entry(key, notification, iconView); 925 if (!inflateViews(entry, mPile)) { 926 handleNotificationError(key, notification, "Couldn't expand RemoteViews for: " 927 + notification); 928 return null; 929 } 930 return entry; 931 } 932 933 protected void addNotificationViews(NotificationData.Entry entry) { 934 // Add the expanded view and icon. 935 int pos = mNotificationData.add(entry); 936 if (DEBUG) { 937 Log.d(TAG, "addNotificationViews: added at " + pos); 938 } 939 updateExpansionStates(); 940 updateNotificationIcons(); 941 } 942 943 private void addNotificationViews(IBinder key, StatusBarNotification notification) { 944 addNotificationViews(createNotificationViews(key, notification)); 945 } 946 947 protected void updateExpansionStates() { 948 int N = mNotificationData.size(); 949 for (int i = 0; i < N; i++) { 950 NotificationData.Entry entry = mNotificationData.get(i); 951 if (!entry.row.isUserLocked()) { 952 if (i == (N-1)) { 953 if (DEBUG) Log.d(TAG, "expanding top notification at " + i); 954 entry.row.setExpanded(true); 955 } else { 956 if (!entry.row.isUserExpanded()) { 957 if (DEBUG) Log.d(TAG, "collapsing notification at " + i); 958 entry.row.setExpanded(false); 959 } else { 960 if (DEBUG) Log.d(TAG, "ignoring user-modified notification at " + i); 961 } 962 } 963 } else { 964 if (DEBUG) Log.d(TAG, "ignoring notification being held by user at " + i); 965 } 966 } 967 } 968 969 protected abstract void haltTicker(); 970 protected abstract void setAreThereNotifications(); 971 protected abstract void updateNotificationIcons(); 972 protected abstract void tick(IBinder key, StatusBarNotification n, boolean firstTime); 973 protected abstract void updateExpandedViewPos(int expandedPosition); 974 protected abstract int getExpandedViewMaxHeight(); 975 protected abstract boolean shouldDisableNavbarGestures(); 976 977 protected boolean isTopNotification(ViewGroup parent, NotificationData.Entry entry) { 978 return parent != null && parent.indexOfChild(entry.row) == 0; 979 } 980 981 public void updateNotification(IBinder key, StatusBarNotification notification) { 982 if (DEBUG) Log.d(TAG, "updateNotification(" + key + " -> " + notification + ")"); 983 984 final NotificationData.Entry oldEntry = mNotificationData.findByKey(key); 985 if (oldEntry == null) { 986 Log.w(TAG, "updateNotification for unknown key: " + key); 987 return; 988 } 989 990 final StatusBarNotification oldNotification = oldEntry.notification; 991 992 // XXX: modify when we do something more intelligent with the two content views 993 final RemoteViews oldContentView = oldNotification.getNotification().contentView; 994 final RemoteViews contentView = notification.getNotification().contentView; 995 final RemoteViews oldBigContentView = oldNotification.getNotification().bigContentView; 996 final RemoteViews bigContentView = notification.getNotification().bigContentView; 997 final Notification oldPublicNotification = oldNotification.getNotification().publicVersion; 998 final RemoteViews oldPublicContentView = oldPublicNotification != null 999 ? oldPublicNotification.contentView : null; 1000 final Notification publicNotification = notification.getNotification().publicVersion; 1001 final RemoteViews publicContentView = publicNotification != null 1002 ? publicNotification.contentView : null; 1003 1004 if (DEBUG) { 1005 Log.d(TAG, "old notification: when=" + oldNotification.getNotification().when 1006 + " ongoing=" + oldNotification.isOngoing() 1007 + " expanded=" + oldEntry.expanded 1008 + " contentView=" + oldContentView 1009 + " bigContentView=" + oldBigContentView 1010 + " publicView=" + oldPublicContentView 1011 + " rowParent=" + oldEntry.row.getParent()); 1012 Log.d(TAG, "new notification: when=" + notification.getNotification().when 1013 + " ongoing=" + oldNotification.isOngoing() 1014 + " contentView=" + contentView 1015 + " bigContentView=" + bigContentView 1016 + " publicView=" + publicContentView); 1017 } 1018 1019 // Can we just reapply the RemoteViews in place? If when didn't change, the order 1020 // didn't change. 1021 1022 // 1U is never null 1023 boolean contentsUnchanged = oldEntry.expanded != null 1024 && contentView.getPackage() != null 1025 && oldContentView.getPackage() != null 1026 && oldContentView.getPackage().equals(contentView.getPackage()) 1027 && oldContentView.getLayoutId() == contentView.getLayoutId(); 1028 // large view may be null 1029 boolean bigContentsUnchanged = 1030 (oldEntry.getBigContentView() == null && bigContentView == null) 1031 || ((oldEntry.getBigContentView() != null && bigContentView != null) 1032 && bigContentView.getPackage() != null 1033 && oldBigContentView.getPackage() != null 1034 && oldBigContentView.getPackage().equals(bigContentView.getPackage()) 1035 && oldBigContentView.getLayoutId() == bigContentView.getLayoutId()); 1036 boolean publicUnchanged = 1037 (oldPublicContentView == null && publicContentView == null) 1038 || ((oldPublicContentView != null && publicContentView != null) 1039 && publicContentView.getPackage() != null 1040 && oldPublicContentView.getPackage() != null 1041 && oldPublicContentView.getPackage().equals(publicContentView.getPackage()) 1042 && oldPublicContentView.getLayoutId() == publicContentView.getLayoutId()); 1043 1044 ViewGroup rowParent = (ViewGroup) oldEntry.row.getParent(); 1045 boolean orderUnchanged = 1046 notification.getNotification().when == oldNotification.getNotification().when 1047 && notification.getScore() == oldNotification.getScore(); 1048 // score now encompasses/supersedes isOngoing() 1049 1050 boolean updateTicker = notification.getNotification().tickerText != null 1051 && !TextUtils.equals(notification.getNotification().tickerText, 1052 oldEntry.notification.getNotification().tickerText); 1053 boolean isTopAnyway = isTopNotification(rowParent, oldEntry); 1054 if (contentsUnchanged && bigContentsUnchanged && publicUnchanged 1055 && (orderUnchanged || isTopAnyway)) { 1056 if (DEBUG) Log.d(TAG, "reusing notification for key: " + key); 1057 oldEntry.notification = notification; 1058 try { 1059 updateNotificationViews(oldEntry, notification); 1060 1061 if (ENABLE_HEADS_UP && mInterruptingNotificationEntry != null 1062 && oldNotification == mInterruptingNotificationEntry.notification) { 1063 if (!shouldInterrupt(notification)) { 1064 if (DEBUG) Log.d(TAG, "no longer interrupts!"); 1065 mHandler.sendEmptyMessage(MSG_HIDE_HEADS_UP); 1066 } else { 1067 if (DEBUG) Log.d(TAG, "updating the current heads up:" + notification); 1068 mInterruptingNotificationEntry.notification = notification; 1069 updateHeadsUpViews(mInterruptingNotificationEntry, notification); 1070 } 1071 } 1072 1073 // Update the icon. 1074 final StatusBarIcon ic = new StatusBarIcon(notification.getPackageName(), 1075 notification.getUser(), 1076 notification.getNotification().icon, notification.getNotification().iconLevel, 1077 notification.getNotification().number, 1078 notification.getNotification().tickerText); 1079 if (!oldEntry.icon.set(ic)) { 1080 handleNotificationError(key, notification, "Couldn't update icon: " + ic); 1081 return; 1082 } 1083 updateExpansionStates(); 1084 } 1085 catch (RuntimeException e) { 1086 // It failed to add cleanly. Log, and remove the view from the panel. 1087 Log.w(TAG, "Couldn't reapply views for package " + contentView.getPackage(), e); 1088 removeNotificationViews(key); 1089 addNotificationViews(key, notification); 1090 } 1091 } else { 1092 if (DEBUG) Log.d(TAG, "not reusing notification for key: " + key); 1093 if (DEBUG) Log.d(TAG, "contents was " + (contentsUnchanged ? "unchanged" : "changed")); 1094 if (DEBUG) Log.d(TAG, "order was " + (orderUnchanged ? "unchanged" : "changed")); 1095 if (DEBUG) Log.d(TAG, "notification is " + (isTopAnyway ? "top" : "not top")); 1096 final boolean wasExpanded = oldEntry.row.isUserExpanded(); 1097 removeNotificationViews(key); 1098 addNotificationViews(key, notification); // will also replace the heads up 1099 if (wasExpanded) { 1100 final NotificationData.Entry newEntry = mNotificationData.findByKey(key); 1101 newEntry.row.setExpanded(true); 1102 newEntry.row.setUserExpanded(true); 1103 } 1104 } 1105 1106 // Update the veto button accordingly (and as a result, whether this row is 1107 // swipe-dismissable) 1108 updateNotificationVetoButton(oldEntry.row, notification); 1109 1110 // Is this for you? 1111 boolean isForCurrentUser = notificationIsForCurrentUser(notification); 1112 if (DEBUG) Log.d(TAG, "notification is " + (isForCurrentUser ? "" : "not ") + "for you"); 1113 1114 // Restart the ticker if it's still running 1115 if (updateTicker && isForCurrentUser) { 1116 haltTicker(); 1117 tick(key, notification, false); 1118 } 1119 1120 // Recalculate the position of the sliding windows and the titles. 1121 setAreThereNotifications(); 1122 updateExpandedViewPos(EXPANDED_LEAVE_ALONE); 1123 } 1124 1125 private void updateNotificationViews(NotificationData.Entry entry, 1126 StatusBarNotification notification) { 1127 updateNotificationViews(entry, notification, false); 1128 } 1129 1130 private void updateHeadsUpViews(NotificationData.Entry entry, 1131 StatusBarNotification notification) { 1132 updateNotificationViews(entry, notification, true); 1133 } 1134 1135 private void updateNotificationViews(NotificationData.Entry entry, 1136 StatusBarNotification notification, boolean isHeadsUp) { 1137 final RemoteViews contentView = notification.getNotification().contentView; 1138 final RemoteViews bigContentView = notification.getNotification().bigContentView; 1139 final Notification publicVersion = notification.getNotification().publicVersion; 1140 final RemoteViews publicContentView = publicVersion != null ? publicVersion.contentView 1141 : null; 1142 1143 // Reapply the RemoteViews 1144 contentView.reapply(mContext, entry.expanded, mOnClickHandler); 1145 if (bigContentView != null && entry.getBigContentView() != null) { 1146 bigContentView.reapply(mContext, entry.getBigContentView(), mOnClickHandler); 1147 } 1148 if (publicContentView != null && entry.getPublicContentView() != null) { 1149 publicContentView.reapply(mContext, entry.getPublicContentView(), mOnClickHandler); 1150 } 1151 // update the contentIntent 1152 final PendingIntent contentIntent = notification.getNotification().contentIntent; 1153 if (contentIntent != null) { 1154 final View.OnClickListener listener = makeClicker(contentIntent, 1155 notification.getPackageName(), notification.getTag(), notification.getId(), 1156 isHeadsUp); 1157 entry.content.setOnClickListener(listener); 1158 } else { 1159 entry.content.setOnClickListener(null); 1160 } 1161 } 1162 1163 protected void notifyHeadsUpScreenOn(boolean screenOn) { 1164 if (!screenOn && mInterruptingNotificationEntry != null) { 1165 mHandler.sendEmptyMessage(MSG_ESCALATE_HEADS_UP); 1166 } 1167 } 1168 1169 protected boolean shouldInterrupt(StatusBarNotification sbn) { 1170 Notification notification = sbn.getNotification(); 1171 // some predicates to make the boolean logic legible 1172 boolean isNoisy = (notification.defaults & Notification.DEFAULT_SOUND) != 0 1173 || (notification.defaults & Notification.DEFAULT_VIBRATE) != 0 1174 || notification.sound != null 1175 || notification.vibrate != null; 1176 boolean isHighPriority = sbn.getScore() >= INTERRUPTION_THRESHOLD; 1177 boolean isFullscreen = notification.fullScreenIntent != null; 1178 boolean isAllowed = notification.extras.getInt(Notification.EXTRA_AS_HEADS_UP, 1179 Notification.HEADS_UP_ALLOWED) != Notification.HEADS_UP_NEVER; 1180 1181 final KeyguardTouchDelegate keyguard = KeyguardTouchDelegate.getInstance(mContext); 1182 boolean interrupt = (isFullscreen || (isHighPriority && isNoisy)) 1183 && isAllowed 1184 && mPowerManager.isScreenOn() 1185 && !keyguard.isShowingAndNotHidden() 1186 && !keyguard.isInputRestricted(); 1187 try { 1188 interrupt = interrupt && !mDreamManager.isDreaming(); 1189 } catch (RemoteException e) { 1190 Log.d(TAG, "failed to query dream manager", e); 1191 } 1192 if (DEBUG) Log.d(TAG, "interrupt: " + interrupt); 1193 return interrupt; 1194 } 1195 1196 // Q: What kinds of notifications should show during setup? 1197 // A: Almost none! Only things coming from the system (package is "android") that also 1198 // have special "kind" tags marking them as relevant for setup (see below). 1199 protected boolean showNotificationEvenIfUnprovisioned(StatusBarNotification sbn) { 1200 if ("android".equals(sbn.getPackageName())) { 1201 if (sbn.getNotification().kind != null) { 1202 for (String aKind : sbn.getNotification().kind) { 1203 // IME switcher, created by InputMethodManagerService 1204 if ("android.system.imeswitcher".equals(aKind)) return true; 1205 // OTA availability & errors, created by SystemUpdateService 1206 if ("android.system.update".equals(aKind)) return true; 1207 } 1208 } 1209 } 1210 return false; 1211 } 1212 1213 public boolean inKeyguardRestrictedInputMode() { 1214 return KeyguardTouchDelegate.getInstance(mContext).isInputRestricted(); 1215 } 1216 1217 public void setInteracting(int barWindow, boolean interacting) { 1218 // hook for subclasses 1219 } 1220 1221 public void destroy() { 1222 if (mSearchPanelView != null) { 1223 mWindowManager.removeViewImmediate(mSearchPanelView); 1224 } 1225 mContext.unregisterReceiver(mBroadcastReceiver); 1226 } 1227} 1228