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