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