BaseStatusBar.java revision 9214eff52adf56b7c9532e40b784d52f9f94b6d1
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.ComponentName; 26import android.content.Context; 27import android.content.Intent; 28import android.content.IntentFilter; 29import android.content.pm.ApplicationInfo; 30import android.content.pm.PackageManager; 31import android.content.pm.PackageManager.NameNotFoundException; 32import android.content.pm.UserInfo; 33import android.content.res.Configuration; 34import android.database.ContentObserver; 35import android.graphics.Rect; 36import android.graphics.drawable.Drawable; 37import android.net.Uri; 38import android.os.Build; 39import android.os.Handler; 40import android.os.IBinder; 41import android.os.Message; 42import android.os.PowerManager; 43import android.os.RemoteException; 44import android.os.ServiceManager; 45import android.os.UserHandle; 46import android.os.UserManager; 47import android.provider.Settings; 48import android.service.dreams.DreamService; 49import android.service.dreams.IDreamManager; 50import android.service.notification.NotificationListenerService; 51import android.service.notification.NotificationListenerService.RankingMap; 52import android.service.notification.StatusBarNotification; 53import android.text.TextUtils; 54import android.util.Log; 55import android.util.SparseArray; 56import android.util.SparseBooleanArray; 57import android.view.Display; 58import android.view.IWindowManager; 59import android.view.LayoutInflater; 60import android.view.MenuItem; 61import android.view.MotionEvent; 62import android.view.View; 63import android.view.ViewGroup; 64import android.view.ViewGroup.LayoutParams; 65import android.view.WindowManager; 66import android.view.WindowManagerGlobal; 67import android.widget.ImageView; 68import android.widget.LinearLayout; 69import android.widget.PopupMenu; 70import android.widget.RemoteViews; 71import android.widget.TextView; 72 73import com.android.internal.statusbar.IStatusBarService; 74import com.android.internal.statusbar.StatusBarIcon; 75import com.android.internal.statusbar.StatusBarIconList; 76import com.android.internal.util.NotificationColorUtil; 77import com.android.systemui.R; 78import com.android.systemui.RecentsComponent; 79import com.android.systemui.SearchPanelView; 80import com.android.systemui.SystemUI; 81import com.android.systemui.statusbar.NotificationData.Entry; 82import com.android.systemui.statusbar.phone.KeyguardTouchDelegate; 83import com.android.systemui.statusbar.policy.HeadsUpNotificationView; 84import com.android.systemui.statusbar.stack.NotificationStackScrollLayout; 85 86import java.util.ArrayList; 87import java.util.Locale; 88 89import static com.android.keyguard.KeyguardHostView.OnDismissAction; 90 91public abstract class BaseStatusBar extends SystemUI implements 92 CommandQueue.Callbacks, ActivatableNotificationView.OnActivatedListener, 93 RecentsComponent.Callbacks { 94 public static final String TAG = "StatusBar"; 95 public static final boolean DEBUG = false; 96 public static final boolean MULTIUSER_DEBUG = false; 97 private static final boolean USE_NOTIFICATION_LISTENER = true; 98 99 protected static final int MSG_SHOW_RECENT_APPS = 1019; 100 protected static final int MSG_HIDE_RECENT_APPS = 1020; 101 protected static final int MSG_TOGGLE_RECENTS_APPS = 1021; 102 protected static final int MSG_PRELOAD_RECENT_APPS = 1022; 103 protected static final int MSG_CANCEL_PRELOAD_RECENT_APPS = 1023; 104 protected static final int MSG_OPEN_SEARCH_PANEL = 1024; 105 protected static final int MSG_CLOSE_SEARCH_PANEL = 1025; 106 protected static final int MSG_SHOW_HEADS_UP = 1026; 107 protected static final int MSG_HIDE_HEADS_UP = 1027; 108 protected static final int MSG_ESCALATE_HEADS_UP = 1028; 109 protected static final int MSG_DECAY_HEADS_UP = 1029; 110 111 protected static final boolean ENABLE_HEADS_UP = true; 112 // scores above this threshold should be displayed in heads up mode. 113 protected static final int INTERRUPTION_THRESHOLD = 10; 114 protected static final String SETTING_HEADS_UP_TICKER = "ticker_gets_heads_up"; 115 116 // Should match the value in PhoneWindowManager 117 public static final String SYSTEM_DIALOG_REASON_RECENT_APPS = "recentapps"; 118 119 public static final int EXPANDED_LEAVE_ALONE = -10000; 120 public static final int EXPANDED_FULL_OPEN = -10001; 121 122 /** If true, delays dismissing the Keyguard until the ActivityManager calls back. */ 123 protected static final boolean DELAY_DISMISS_TO_ACTIVITY_LAUNCH = false; 124 125 protected CommandQueue mCommandQueue; 126 protected IStatusBarService mBarService; 127 protected H mHandler = createHandler(); 128 129 // all notifications 130 protected NotificationData mNotificationData = new NotificationData(); 131 protected NotificationStackScrollLayout mStackScroller; 132 133 // for heads up notifications 134 protected HeadsUpNotificationView mHeadsUpNotificationView; 135 protected int mHeadsUpNotificationDecay; 136 protected long mInterruptingNotificationTime; 137 138 // used to notify status bar for suppressing notification LED 139 protected boolean mPanelSlightlyVisible; 140 141 // Search panel 142 protected SearchPanelView mSearchPanelView; 143 144 protected PopupMenu mNotificationBlamePopup; 145 146 protected int mCurrentUserId = 0; 147 final protected SparseArray<UserInfo> mCurrentProfiles = new SparseArray<UserInfo>(); 148 149 protected int mLayoutDirection = -1; // invalid 150 private Locale mLocale; 151 protected boolean mUseHeadsUp = false; 152 protected boolean mHeadsUpTicker = false; 153 154 protected IDreamManager mDreamManager; 155 PowerManager mPowerManager; 156 protected int mRowMinHeight; 157 protected int mRowMaxHeight; 158 159 // public mode, private notifications, etc 160 private boolean mLockscreenPublicMode = false; 161 private final SparseBooleanArray mUsersAllowingPrivateNotifications = new SparseBooleanArray(); 162 private NotificationColorUtil mNotificationColorUtil = NotificationColorUtil.getInstance(); 163 164 private UserManager mUserManager; 165 166 // UI-specific methods 167 168 /** 169 * Create all windows necessary for the status bar (including navigation, overlay panels, etc) 170 * and add them to the window manager. 171 */ 172 protected abstract void createAndAddWindows(); 173 174 protected WindowManager mWindowManager; 175 protected IWindowManager mWindowManagerService; 176 177 protected abstract void refreshLayout(int layoutDirection); 178 179 protected Display mDisplay; 180 181 private boolean mDeviceProvisioned = false; 182 183 private RecentsComponent mRecents; 184 185 protected int mZenMode; 186 187 /** 188 * The {@link StatusBarState} of the status bar. 189 */ 190 protected int mState; 191 protected boolean mBouncerShowing; 192 protected boolean mShowLockscreenNotifications; 193 194 protected NotificationOverflowContainer mKeyguardIconOverflowContainer; 195 196 public boolean isDeviceProvisioned() { 197 return mDeviceProvisioned; 198 } 199 200 protected final ContentObserver mSettingsObserver = new ContentObserver(mHandler) { 201 @Override 202 public void onChange(boolean selfChange) { 203 final boolean provisioned = 0 != Settings.Global.getInt( 204 mContext.getContentResolver(), Settings.Global.DEVICE_PROVISIONED, 0); 205 if (provisioned != mDeviceProvisioned) { 206 mDeviceProvisioned = provisioned; 207 updateNotifications(); 208 } 209 final int mode = Settings.Global.getInt(mContext.getContentResolver(), 210 Settings.Global.ZEN_MODE, Settings.Global.ZEN_MODE_OFF); 211 setZenMode(mode); 212 final boolean show = Settings.Global.getInt(mContext.getContentResolver(), 213 Settings.Global.LOCK_SCREEN_SHOW_NOTIFICATIONS, 1) != 0; 214 setShowLockscreenNotifications(show); 215 } 216 }; 217 218 private final ContentObserver mLockscreenSettingsObserver = new ContentObserver(mHandler) { 219 @Override 220 public void onChange(boolean selfChange) { 221 // We don't know which user changed LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 222 // so we just dump our cache ... 223 mUsersAllowingPrivateNotifications.clear(); 224 // ... and refresh all the notifications 225 updateNotifications(); 226 } 227 }; 228 229 private RemoteViews.OnClickHandler mOnClickHandler = new RemoteViews.OnClickHandler() { 230 @Override 231 public boolean onClickHandler( 232 final View view, final PendingIntent pendingIntent, final Intent fillInIntent) { 233 if (DEBUG) { 234 Log.v(TAG, "Notification click handler invoked for intent: " + pendingIntent); 235 } 236 final boolean isActivity = pendingIntent.isActivity(); 237 if (isActivity) { 238 dismissKeyguardThenExecute(new OnDismissAction() { 239 @Override 240 public boolean onDismiss() { 241 try { 242 // The intent we are sending is for the application, which 243 // won't have permission to immediately start an activity after 244 // the user switches to home. We know it is safe to do at this 245 // point, so make sure new activity switches are now allowed. 246 ActivityManagerNative.getDefault().resumeAppSwitches(); 247 // Also, notifications can be launched from the lock screen, 248 // so dismiss the lock screen when the activity starts. 249 ActivityManagerNative.getDefault().dismissKeyguardOnNextActivity(); 250 } catch (RemoteException e) { 251 } 252 253 boolean handled = superOnClickHandler(view, pendingIntent, fillInIntent); 254 255 // close the shade if it was open 256 if (handled) { 257 animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE); 258 visibilityChanged(false); 259 } 260 // Wait for activity start. 261 return handled && DELAY_DISMISS_TO_ACTIVITY_LAUNCH; 262 } 263 }); 264 return true; 265 } else { 266 return super.onClickHandler(view, pendingIntent, fillInIntent); 267 } 268 } 269 270 private boolean superOnClickHandler(View view, PendingIntent pendingIntent, 271 Intent fillInIntent) { 272 return super.onClickHandler(view, pendingIntent, fillInIntent); 273 } 274 }; 275 276 private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { 277 @Override 278 public void onReceive(Context context, Intent intent) { 279 String action = intent.getAction(); 280 if (Intent.ACTION_USER_SWITCHED.equals(action)) { 281 mCurrentUserId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1); 282 updateCurrentProfilesCache(); 283 if (true) Log.v(TAG, "userId " + mCurrentUserId + " is in the house"); 284 userSwitched(mCurrentUserId); 285 } else if (Intent.ACTION_USER_ADDED.equals(action)) { 286 updateCurrentProfilesCache(); 287 } 288 } 289 }; 290 291 private final NotificationListenerService mNotificationListener = 292 new NotificationListenerService() { 293 @Override 294 public void onListenerConnected() { 295 if (DEBUG) Log.d(TAG, "onListenerConnected"); 296 final StatusBarNotification[] notifications = getActiveNotifications(); 297 final RankingMap currentRanking = getCurrentRanking(); 298 mHandler.post(new Runnable() { 299 @Override 300 public void run() { 301 for (StatusBarNotification sbn : notifications) { 302 addNotificationInternal(sbn, currentRanking); 303 } 304 } 305 }); 306 } 307 308 @Override 309 public void onNotificationPosted(final StatusBarNotification sbn, 310 final RankingMap rankingMap) { 311 if (DEBUG) Log.d(TAG, "onNotificationPosted: " + sbn); 312 mHandler.post(new Runnable() { 313 @Override 314 public void run() { 315 if (mNotificationData.findByKey(sbn.getKey()) != null) { 316 updateNotificationInternal(sbn, rankingMap); 317 } else { 318 addNotificationInternal(sbn, rankingMap); 319 } 320 } 321 }); 322 } 323 324 @Override 325 public void onNotificationRemoved(final StatusBarNotification sbn, 326 final RankingMap rankingMap) { 327 if (DEBUG) Log.d(TAG, "onNotificationRemoved: " + sbn); 328 mHandler.post(new Runnable() { 329 @Override 330 public void run() { 331 removeNotificationInternal(sbn.getKey(), rankingMap); 332 } 333 }); 334 } 335 336 @Override 337 public void onNotificationRankingUpdate(final RankingMap rankingMap) { 338 if (DEBUG) Log.d(TAG, "onRankingUpdate"); 339 mHandler.post(new Runnable() { 340 @Override 341 public void run() { 342 updateRankingInternal(rankingMap); 343 } 344 }); 345 } 346 }; 347 348 private void updateCurrentProfilesCache() { 349 synchronized (mCurrentProfiles) { 350 mCurrentProfiles.clear(); 351 if (mUserManager != null) { 352 for (UserInfo user : mUserManager.getProfiles(mCurrentUserId)) { 353 mCurrentProfiles.put(user.id, user); 354 } 355 } 356 } 357 } 358 359 public void start() { 360 mWindowManager = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE); 361 mWindowManagerService = WindowManagerGlobal.getWindowManagerService(); 362 mDisplay = mWindowManager.getDefaultDisplay(); 363 364 mDreamManager = IDreamManager.Stub.asInterface( 365 ServiceManager.checkService(DreamService.DREAM_SERVICE)); 366 mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); 367 368 mSettingsObserver.onChange(false); // set up 369 mContext.getContentResolver().registerContentObserver( 370 Settings.Global.getUriFor(Settings.Global.DEVICE_PROVISIONED), true, 371 mSettingsObserver); 372 mContext.getContentResolver().registerContentObserver( 373 Settings.Global.getUriFor(Settings.Global.ZEN_MODE), false, 374 mSettingsObserver); 375 mContext.getContentResolver().registerContentObserver( 376 Settings.Global.getUriFor(Settings.Global.LOCK_SCREEN_SHOW_NOTIFICATIONS), false, 377 mSettingsObserver); 378 379 mContext.getContentResolver().registerContentObserver( 380 Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS), 381 true, 382 mLockscreenSettingsObserver, 383 UserHandle.USER_ALL); 384 385 mBarService = IStatusBarService.Stub.asInterface( 386 ServiceManager.getService(Context.STATUS_BAR_SERVICE)); 387 388 mRecents = getComponent(RecentsComponent.class); 389 mRecents.setCallback(this); 390 391 mLocale = mContext.getResources().getConfiguration().locale; 392 mLayoutDirection = TextUtils.getLayoutDirectionFromLocale(mLocale); 393 394 mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE); 395 396 // Connect in to the status bar manager service 397 StatusBarIconList iconList = new StatusBarIconList(); 398 ArrayList<StatusBarNotification> notifications = new ArrayList<StatusBarNotification>(); 399 mCommandQueue = new CommandQueue(this, iconList); 400 401 int[] switches = new int[8]; 402 ArrayList<IBinder> binders = new ArrayList<IBinder>(); 403 try { 404 mBarService.registerStatusBar(mCommandQueue, iconList, notifications, 405 switches, binders); 406 } catch (RemoteException ex) { 407 // If the system process isn't there we're doomed anyway. 408 } 409 410 createAndAddWindows(); 411 412 disable(switches[0]); 413 setSystemUiVisibility(switches[1], 0xffffffff); 414 topAppWindowChanged(switches[2] != 0); 415 // StatusBarManagerService has a back up of IME token and it's restored here. 416 setImeWindowStatus(binders.get(0), switches[3], switches[4], switches[7] != 0); 417 setHardKeyboardStatus(switches[5] != 0, switches[6] != 0); 418 419 // Set up the initial icon state 420 int N = iconList.size(); 421 int viewIndex = 0; 422 for (int i=0; i<N; i++) { 423 StatusBarIcon icon = iconList.getIcon(i); 424 if (icon != null) { 425 addIcon(iconList.getSlot(i), i, viewIndex, icon); 426 viewIndex++; 427 } 428 } 429 430 // Set up the initial notification state. 431 if (USE_NOTIFICATION_LISTENER) { 432 try { 433 mNotificationListener.registerAsSystemService( 434 new ComponentName(mContext.getPackageName(), getClass().getCanonicalName()), 435 UserHandle.USER_ALL); 436 } catch (RemoteException e) { 437 Log.e(TAG, "Unable to register notification listener", e); 438 } 439 } else { 440 N = notifications.size(); 441 for (int i=0; i<N; i++) { 442 addNotification(notifications.get(i)); 443 } 444 } 445 446 447 if (DEBUG) { 448 Log.d(TAG, String.format( 449 "init: icons=%d disabled=0x%08x lights=0x%08x menu=0x%08x imeButton=0x%08x", 450 iconList.size(), 451 switches[0], 452 switches[1], 453 switches[2], 454 switches[3] 455 )); 456 } 457 458 mCurrentUserId = ActivityManager.getCurrentUser(); 459 460 IntentFilter filter = new IntentFilter(); 461 filter.addAction(Intent.ACTION_USER_SWITCHED); 462 filter.addAction(Intent.ACTION_USER_ADDED); 463 mContext.registerReceiver(mBroadcastReceiver, filter); 464 465 updateCurrentProfilesCache(); 466 } 467 468 public void userSwitched(int newUserId) { 469 // should be overridden 470 } 471 472 public boolean notificationIsForCurrentProfiles(StatusBarNotification n) { 473 final int thisUserId = mCurrentUserId; 474 final int notificationUserId = n.getUserId(); 475 if (DEBUG && MULTIUSER_DEBUG) { 476 Log.v(TAG, String.format("%s: current userid: %d, notification userid: %d", 477 n, thisUserId, notificationUserId)); 478 } 479 synchronized (mCurrentProfiles) { 480 return notificationUserId == UserHandle.USER_ALL 481 || mCurrentProfiles.get(notificationUserId) != null; 482 } 483 } 484 485 /** 486 * Takes the necessary steps to prepare the status bar for starting an activity, then starts it. 487 * @param action A dismiss action that is called if it's safe to start the activity. 488 */ 489 protected void dismissKeyguardThenExecute(OnDismissAction action) { 490 action.onDismiss(); 491 } 492 493 @Override 494 protected void onConfigurationChanged(Configuration newConfig) { 495 final Locale locale = mContext.getResources().getConfiguration().locale; 496 final int ld = TextUtils.getLayoutDirectionFromLocale(locale); 497 if (! locale.equals(mLocale) || ld != mLayoutDirection) { 498 if (DEBUG) { 499 Log.v(TAG, String.format( 500 "config changed locale/LD: %s (%d) -> %s (%d)", mLocale, mLayoutDirection, 501 locale, ld)); 502 } 503 mLocale = locale; 504 mLayoutDirection = ld; 505 refreshLayout(ld); 506 } 507 } 508 509 protected View updateNotificationVetoButton(View row, StatusBarNotification n) { 510 View vetoButton = row.findViewById(R.id.veto); 511 if (n.isClearable() || (mHeadsUpNotificationView.getEntry() != null 512 && mHeadsUpNotificationView.getEntry().row == row)) { 513 final String _pkg = n.getPackageName(); 514 final String _tag = n.getTag(); 515 final int _id = n.getId(); 516 final int _userId = n.getUserId(); 517 vetoButton.setOnClickListener(new View.OnClickListener() { 518 public void onClick(View v) { 519 // Accessibility feedback 520 v.announceForAccessibility( 521 mContext.getString(R.string.accessibility_notification_dismissed)); 522 try { 523 mBarService.onNotificationClear(_pkg, _tag, _id, _userId); 524 525 } catch (RemoteException ex) { 526 // system process is dead if we're here. 527 } 528 } 529 }); 530 vetoButton.setVisibility(View.VISIBLE); 531 } else { 532 vetoButton.setVisibility(View.GONE); 533 } 534 vetoButton.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO); 535 return vetoButton; 536 } 537 538 539 protected void applyLegacyRowBackground(StatusBarNotification sbn, 540 NotificationData.Entry entry) { 541 int version = 0; 542 try { 543 ApplicationInfo info = mContext.getPackageManager().getApplicationInfo(sbn.getPackageName(), 0); 544 version = info.targetSdkVersion; 545 } catch (NameNotFoundException ex) { 546 Log.e(TAG, "Failed looking up ApplicationInfo for " + sbn.getPackageName(), ex); 547 } 548 549 if (entry.expanded.getId() != com.android.internal.R.id.status_bar_latest_event_content) { 550 // Using custom RemoteViews 551 if (version >= Build.VERSION_CODES.GINGERBREAD && version < Build.VERSION_CODES.L) { 552 entry.row.setBackgroundResourceIds( 553 com.android.internal.R.drawable.notification_bg, 554 com.android.internal.R.drawable.notification_bg_dim); 555 entry.legacy = true; 556 } 557 } else { 558 // Using platform templates 559 final int color = sbn.getNotification().color; 560 if (isMediaNotification(entry)) { 561 entry.row.setBackgroundResourceIds( 562 com.android.internal.R.drawable.notification_material_bg, 563 color, 564 com.android.internal.R.drawable.notification_material_bg_dim, 565 color); 566 } 567 } 568 } 569 570 private boolean isMediaNotification(NotificationData.Entry entry) { 571 // TODO: confirm that there's a valid media key 572 return entry.expandedBig != null && 573 entry.expandedBig.findViewById(com.android.internal.R.id.media_action_area) != null; 574 } 575 576 private void startApplicationDetailsActivity(String packageName) { 577 Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, 578 Uri.fromParts("package", packageName, null)); 579 intent.setComponent(intent.resolveActivity(mContext.getPackageManager())); 580 TaskStackBuilder.create(mContext).addNextIntentWithParentStack(intent).startActivities( 581 null, UserHandle.CURRENT); 582 } 583 584 protected View.OnLongClickListener getNotificationLongClicker() { 585 return new View.OnLongClickListener() { 586 @Override 587 public boolean onLongClick(View v) { 588 final String packageNameF = (String) v.getTag(); 589 if (packageNameF == null) return false; 590 if (v.getWindowToken() == null) return false; 591 mNotificationBlamePopup = new PopupMenu(mContext, v); 592 mNotificationBlamePopup.getMenuInflater().inflate( 593 R.menu.notification_popup_menu, 594 mNotificationBlamePopup.getMenu()); 595 mNotificationBlamePopup.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() { 596 public boolean onMenuItemClick(MenuItem item) { 597 if (item.getItemId() == R.id.notification_inspect_item) { 598 startApplicationDetailsActivity(packageNameF); 599 animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE); 600 } else { 601 return false; 602 } 603 return true; 604 } 605 }); 606 mNotificationBlamePopup.show(); 607 608 return true; 609 } 610 }; 611 } 612 613 public void dismissPopups() { 614 if (mNotificationBlamePopup != null) { 615 mNotificationBlamePopup.dismiss(); 616 mNotificationBlamePopup = null; 617 } 618 } 619 620 public void onHeadsUpDismissed() { 621 } 622 623 @Override 624 public void showRecentApps(boolean triggeredFromAltTab) { 625 int msg = MSG_SHOW_RECENT_APPS; 626 mHandler.removeMessages(msg); 627 mHandler.obtainMessage(msg, triggeredFromAltTab ? 1 : 0, 0).sendToTarget(); 628 } 629 630 @Override 631 public void hideRecentApps(boolean triggeredFromAltTab) { 632 int msg = MSG_HIDE_RECENT_APPS; 633 mHandler.removeMessages(msg); 634 mHandler.obtainMessage(msg, triggeredFromAltTab ? 1 : 0, 0).sendToTarget(); 635 } 636 637 @Override 638 public void toggleRecentApps() { 639 int msg = MSG_TOGGLE_RECENTS_APPS; 640 mHandler.removeMessages(msg); 641 mHandler.sendEmptyMessage(msg); 642 } 643 644 @Override 645 public void preloadRecentApps() { 646 int msg = MSG_PRELOAD_RECENT_APPS; 647 mHandler.removeMessages(msg); 648 mHandler.sendEmptyMessage(msg); 649 } 650 651 @Override 652 public void cancelPreloadRecentApps() { 653 int msg = MSG_CANCEL_PRELOAD_RECENT_APPS; 654 mHandler.removeMessages(msg); 655 mHandler.sendEmptyMessage(msg); 656 } 657 658 @Override 659 public void showSearchPanel() { 660 int msg = MSG_OPEN_SEARCH_PANEL; 661 mHandler.removeMessages(msg); 662 mHandler.sendEmptyMessage(msg); 663 } 664 665 @Override 666 public void hideSearchPanel() { 667 int msg = MSG_CLOSE_SEARCH_PANEL; 668 mHandler.removeMessages(msg); 669 mHandler.sendEmptyMessage(msg); 670 } 671 672 protected abstract WindowManager.LayoutParams getSearchLayoutParams( 673 LayoutParams layoutParams); 674 675 protected void updateSearchPanel() { 676 // Search Panel 677 boolean visible = false; 678 if (mSearchPanelView != null) { 679 visible = mSearchPanelView.isShowing(); 680 mWindowManager.removeView(mSearchPanelView); 681 } 682 683 // Provide SearchPanel with a temporary parent to allow layout params to work. 684 LinearLayout tmpRoot = new LinearLayout(mContext); 685 mSearchPanelView = (SearchPanelView) LayoutInflater.from(mContext).inflate( 686 R.layout.status_bar_search_panel, tmpRoot, false); 687 mSearchPanelView.setOnTouchListener( 688 new TouchOutsideListener(MSG_CLOSE_SEARCH_PANEL, mSearchPanelView)); 689 mSearchPanelView.setVisibility(View.GONE); 690 691 WindowManager.LayoutParams lp = getSearchLayoutParams(mSearchPanelView.getLayoutParams()); 692 693 mWindowManager.addView(mSearchPanelView, lp); 694 mSearchPanelView.setBar(this); 695 if (visible) { 696 mSearchPanelView.show(true, false); 697 } 698 } 699 700 protected H createHandler() { 701 return new H(); 702 } 703 704 static void sendCloseSystemWindows(Context context, String reason) { 705 if (ActivityManagerNative.isSystemReady()) { 706 try { 707 ActivityManagerNative.getDefault().closeSystemDialogs(reason); 708 } catch (RemoteException e) { 709 } 710 } 711 } 712 713 protected abstract View getStatusBarView(); 714 715 protected View.OnTouchListener mRecentsPreloadOnTouchListener = new View.OnTouchListener() { 716 // additional optimization when we have software system buttons - start loading the recent 717 // tasks on touch down 718 @Override 719 public boolean onTouch(View v, MotionEvent event) { 720 int action = event.getAction() & MotionEvent.ACTION_MASK; 721 if (action == MotionEvent.ACTION_DOWN) { 722 preloadRecents(); 723 } else if (action == MotionEvent.ACTION_CANCEL) { 724 cancelPreloadingRecents(); 725 } else if (action == MotionEvent.ACTION_UP) { 726 if (!v.isPressed()) { 727 cancelPreloadingRecents(); 728 } 729 730 } 731 return false; 732 } 733 }; 734 735 /** Proxy for RecentsComponent */ 736 737 protected void showRecents(boolean triggeredFromAltTab) { 738 if (mRecents != null) { 739 sendCloseSystemWindows(mContext, SYSTEM_DIALOG_REASON_RECENT_APPS); 740 mRecents.showRecents(triggeredFromAltTab, getStatusBarView()); 741 } 742 } 743 744 protected void hideRecents(boolean triggeredFromAltTab) { 745 if (mRecents != null) { 746 mRecents.hideRecents(triggeredFromAltTab); 747 } 748 } 749 750 protected void toggleRecents() { 751 if (mRecents != null) { 752 sendCloseSystemWindows(mContext, SYSTEM_DIALOG_REASON_RECENT_APPS); 753 mRecents.toggleRecents(mDisplay, mLayoutDirection, getStatusBarView()); 754 } 755 } 756 757 protected void preloadRecents() { 758 if (mRecents != null) { 759 mRecents.preloadRecents(); 760 } 761 } 762 763 protected void cancelPreloadingRecents() { 764 if (mRecents != null) { 765 mRecents.cancelPreloadingRecents(); 766 } 767 } 768 769 @Override 770 public void onVisibilityChanged(boolean visible) { 771 // Do nothing 772 } 773 774 public abstract void resetHeadsUpDecayTimer(); 775 776 public abstract void scheduleHeadsUpOpen(); 777 778 public abstract void scheduleHeadsUpClose(); 779 780 public abstract void scheduleHeadsUpEscalation(); 781 782 /** 783 * Save the current "public" (locked and secure) state of the lockscreen. 784 */ 785 public void setLockscreenPublicMode(boolean publicMode) { 786 mLockscreenPublicMode = publicMode; 787 } 788 789 public boolean isLockscreenPublicMode() { 790 return mLockscreenPublicMode; 791 } 792 793 /** 794 * Has the given user chosen to allow their private (full) notifications to be shown even 795 * when the lockscreen is in "public" (secure & locked) mode? 796 */ 797 public boolean userAllowsPrivateNotificationsInPublic(int userHandle) { 798 if (userHandle == UserHandle.USER_ALL) { 799 return true; 800 } 801 802 if (mUsersAllowingPrivateNotifications.indexOfKey(userHandle) < 0) { 803 final boolean allowed = 0 != Settings.Secure.getIntForUser( 804 mContext.getContentResolver(), 805 Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0, userHandle); 806 mUsersAllowingPrivateNotifications.append(userHandle, allowed); 807 return allowed; 808 } 809 810 return mUsersAllowingPrivateNotifications.get(userHandle); 811 } 812 813 public void onNotificationClear(StatusBarNotification notification) { 814 try { 815 mBarService.onNotificationClear( 816 notification.getPackageName(), 817 notification.getTag(), 818 notification.getId(), 819 notification.getUserId()); 820 } catch (android.os.RemoteException ex) { 821 // oh well 822 } 823 } 824 825 protected class H extends Handler { 826 public void handleMessage(Message m) { 827 Intent intent; 828 switch (m.what) { 829 case MSG_SHOW_RECENT_APPS: 830 showRecents(m.arg1 > 0); 831 break; 832 case MSG_HIDE_RECENT_APPS: 833 hideRecents(m.arg1 > 0); 834 break; 835 case MSG_TOGGLE_RECENTS_APPS: 836 toggleRecents(); 837 break; 838 case MSG_PRELOAD_RECENT_APPS: 839 preloadRecents(); 840 break; 841 case MSG_CANCEL_PRELOAD_RECENT_APPS: 842 cancelPreloadingRecents(); 843 break; 844 case MSG_OPEN_SEARCH_PANEL: 845 if (DEBUG) Log.d(TAG, "opening search panel"); 846 if (mSearchPanelView != null && mSearchPanelView.isAssistantAvailable()) { 847 mSearchPanelView.show(true, true); 848 onShowSearchPanel(); 849 } 850 break; 851 case MSG_CLOSE_SEARCH_PANEL: 852 if (DEBUG) Log.d(TAG, "closing search panel"); 853 if (mSearchPanelView != null && mSearchPanelView.isShowing()) { 854 mSearchPanelView.show(false, true); 855 onHideSearchPanel(); 856 } 857 break; 858 } 859 } 860 } 861 862 public class TouchOutsideListener implements View.OnTouchListener { 863 private int mMsg; 864 private StatusBarPanel mPanel; 865 866 public TouchOutsideListener(int msg, StatusBarPanel panel) { 867 mMsg = msg; 868 mPanel = panel; 869 } 870 871 public boolean onTouch(View v, MotionEvent ev) { 872 final int action = ev.getAction(); 873 if (action == MotionEvent.ACTION_OUTSIDE 874 || (action == MotionEvent.ACTION_DOWN 875 && !mPanel.isInContentArea((int)ev.getX(), (int)ev.getY()))) { 876 mHandler.removeMessages(mMsg); 877 mHandler.sendEmptyMessage(mMsg); 878 return true; 879 } 880 return false; 881 } 882 } 883 884 protected void workAroundBadLayerDrawableOpacity(View v) { 885 } 886 887 protected void onHideSearchPanel() { 888 } 889 890 protected void onShowSearchPanel() { 891 } 892 893 public boolean inflateViews(NotificationData.Entry entry, ViewGroup parent) { 894 return inflateViews(entry, parent, false); 895 } 896 897 public boolean inflateViewsForHeadsUp(NotificationData.Entry entry, ViewGroup parent) { 898 return inflateViews(entry, parent, true); 899 } 900 901 public boolean inflateViews(NotificationData.Entry entry, ViewGroup parent, boolean isHeadsUp) { 902 int minHeight = 903 mContext.getResources().getDimensionPixelSize(R.dimen.notification_min_height); 904 int maxHeight = 905 mContext.getResources().getDimensionPixelSize(R.dimen.notification_max_height); 906 StatusBarNotification sbn = entry.notification; 907 RemoteViews contentView = sbn.getNotification().contentView; 908 RemoteViews bigContentView = sbn.getNotification().bigContentView; 909 910 if (isHeadsUp) { 911 maxHeight = 912 mContext.getResources().getDimensionPixelSize(R.dimen.notification_mid_height); 913 bigContentView = sbn.getNotification().headsUpContentView; 914 } 915 916 if (contentView == null) { 917 return false; 918 } 919 920 if (DEBUG) { 921 Log.v(TAG, "publicNotification: " + sbn.getNotification().publicVersion); 922 } 923 924 Notification publicNotification = sbn.getNotification().publicVersion; 925 926 // create the row view 927 LayoutInflater inflater = (LayoutInflater)mContext.getSystemService( 928 Context.LAYOUT_INFLATER_SERVICE); 929 ExpandableNotificationRow row = (ExpandableNotificationRow) inflater.inflate( 930 R.layout.status_bar_notification_row, parent, false); 931 932 // for blaming (see SwipeHelper.setLongPressListener) 933 row.setTag(sbn.getPackageName()); 934 935 workAroundBadLayerDrawableOpacity(row); 936 View vetoButton = updateNotificationVetoButton(row, sbn); 937 vetoButton.setContentDescription(mContext.getString( 938 R.string.accessibility_remove_notification)); 939 940 // NB: the large icon is now handled entirely by the template 941 942 // bind the click event to the content area 943 NotificationContentView expanded = 944 (NotificationContentView) row.findViewById(R.id.expanded); 945 NotificationContentView expandedPublic = 946 (NotificationContentView) row.findViewById(R.id.expandedPublic); 947 948 row.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS); 949 950 PendingIntent contentIntent = sbn.getNotification().contentIntent; 951 if (contentIntent != null) { 952 final View.OnClickListener listener = makeClicker(contentIntent, sbn.getKey(), 953 isHeadsUp); 954 row.setOnClickListener(listener); 955 } else { 956 row.setOnClickListener(null); 957 } 958 959 // set up the adaptive layout 960 View contentViewLocal = null; 961 View bigContentViewLocal = null; 962 try { 963 contentViewLocal = contentView.apply(mContext, expanded, 964 mOnClickHandler); 965 if (bigContentView != null) { 966 bigContentViewLocal = bigContentView.apply(mContext, expanded, 967 mOnClickHandler); 968 } 969 } 970 catch (RuntimeException e) { 971 final String ident = sbn.getPackageName() + "/0x" + Integer.toHexString(sbn.getId()); 972 Log.e(TAG, "couldn't inflate view for notification " + ident, e); 973 return false; 974 } 975 976 if (contentViewLocal != null) { 977 contentViewLocal.setIsRootNamespace(true); 978 expanded.setContractedChild(contentViewLocal); 979 } 980 if (bigContentViewLocal != null) { 981 bigContentViewLocal.setIsRootNamespace(true); 982 expanded.setExpandedChild(bigContentViewLocal); 983 } 984 985 PackageManager pm = mContext.getPackageManager(); 986 987 // now the public version 988 View publicViewLocal = null; 989 if (publicNotification != null) { 990 try { 991 publicViewLocal = publicNotification.contentView.apply(mContext, expandedPublic, 992 mOnClickHandler); 993 994 if (publicViewLocal != null) { 995 publicViewLocal.setIsRootNamespace(true); 996 expandedPublic.setContractedChild(publicViewLocal); 997 } 998 } 999 catch (RuntimeException e) { 1000 final String ident = sbn.getPackageName() + "/0x" + Integer.toHexString(sbn.getId()); 1001 Log.e(TAG, "couldn't inflate public view for notification " + ident, e); 1002 publicViewLocal = null; 1003 } 1004 } 1005 1006 if (publicViewLocal == null) { 1007 // Add a basic notification template 1008 publicViewLocal = LayoutInflater.from(mContext).inflate( 1009 com.android.internal.R.layout.notification_template_material_base, 1010 expandedPublic, true); 1011 1012 final TextView title = (TextView) publicViewLocal.findViewById(com.android.internal.R.id.title); 1013 try { 1014 title.setText(pm.getApplicationLabel( 1015 pm.getApplicationInfo(entry.notification.getPackageName(), 0))); 1016 } catch (NameNotFoundException e) { 1017 title.setText(entry.notification.getPackageName()); 1018 } 1019 1020 final ImageView icon = (ImageView) publicViewLocal.findViewById( 1021 com.android.internal.R.id.icon); 1022 final ImageView profileIcon = (ImageView) publicViewLocal.findViewById( 1023 com.android.internal.R.id.profile_icon); 1024 1025 final StatusBarIcon ic = new StatusBarIcon(entry.notification.getPackageName(), 1026 entry.notification.getUser(), 1027 entry.notification.getNotification().icon, 1028 entry.notification.getNotification().iconLevel, 1029 entry.notification.getNotification().number, 1030 entry.notification.getNotification().tickerText); 1031 1032 Drawable iconDrawable = StatusBarIconView.getIcon(mContext, ic); 1033 icon.setImageDrawable(iconDrawable); 1034 if (mNotificationColorUtil.isGrayscale(iconDrawable)) { 1035 icon.setBackgroundResource( 1036 com.android.internal.R.drawable.notification_icon_legacy_bg_inset); 1037 } 1038 1039 if (profileIcon != null) { 1040 Drawable profileDrawable 1041 = mUserManager.getBadgeForUser(entry.notification.getUser()); 1042 if (profileDrawable != null) { 1043 profileIcon.setImageDrawable(profileDrawable); 1044 profileIcon.setVisibility(View.VISIBLE); 1045 } else { 1046 profileIcon.setVisibility(View.GONE); 1047 } 1048 } 1049 1050 final TextView text = (TextView) publicViewLocal.findViewById( 1051 com.android.internal.R.id.text); 1052 text.setText("Unlock your device to see this notification."); 1053 1054 entry.autoRedacted = true; 1055 // TODO: fill out "time" as well 1056 } 1057 1058 row.setDrawingCacheEnabled(true); 1059 1060 if (MULTIUSER_DEBUG) { 1061 TextView debug = (TextView) row.findViewById(R.id.debug_info); 1062 if (debug != null) { 1063 debug.setVisibility(View.VISIBLE); 1064 debug.setText("CU " + mCurrentUserId +" NU " + entry.notification.getUserId()); 1065 } 1066 } 1067 entry.row = row; 1068 entry.row.setHeightRange(mRowMinHeight, mRowMaxHeight); 1069 entry.row.setOnActivatedListener(this); 1070 entry.row.setIsBelowSpeedBump(isBelowSpeedBump(entry.notification)); 1071 entry.expanded = contentViewLocal; 1072 entry.expandedPublic = publicViewLocal; 1073 entry.setBigContentView(bigContentViewLocal); 1074 1075 applyLegacyRowBackground(sbn, entry); 1076 1077 return true; 1078 } 1079 1080 public NotificationClicker makeClicker(PendingIntent intent, String notificationKey, 1081 boolean forHun) { 1082 return new NotificationClicker(intent, notificationKey, forHun); 1083 } 1084 1085 protected class NotificationClicker implements View.OnClickListener { 1086 private PendingIntent mIntent; 1087 private final String mNotificationKey; 1088 private boolean mIsHeadsUp; 1089 1090 public NotificationClicker(PendingIntent intent, String notificationKey, boolean forHun) { 1091 mIntent = intent; 1092 mNotificationKey = notificationKey; 1093 mIsHeadsUp = forHun; 1094 } 1095 1096 public void onClick(final View v) { 1097 dismissKeyguardThenExecute(new OnDismissAction() { 1098 public boolean onDismiss() { 1099 try { 1100 // The intent we are sending is for the application, which 1101 // won't have permission to immediately start an activity after 1102 // the user switches to home. We know it is safe to do at this 1103 // point, so make sure new activity switches are now allowed. 1104 ActivityManagerNative.getDefault().resumeAppSwitches(); 1105 // Also, notifications can be launched from the lock screen, 1106 // so dismiss the lock screen when the activity starts. 1107 ActivityManagerNative.getDefault().dismissKeyguardOnNextActivity(); 1108 } catch (RemoteException e) { 1109 } 1110 1111 boolean sent = false; 1112 if (mIntent != null) { 1113 int[] pos = new int[2]; 1114 v.getLocationOnScreen(pos); 1115 Intent overlay = new Intent(); 1116 overlay.setSourceBounds(new Rect(pos[0], pos[1], 1117 pos[0] + v.getWidth(), pos[1] + v.getHeight())); 1118 try { 1119 mIntent.send(mContext, 0, overlay); 1120 sent = true; 1121 } catch (PendingIntent.CanceledException e) { 1122 // the stack trace isn't very helpful here. 1123 // Just log the exception message. 1124 Log.w(TAG, "Sending contentIntent failed: " + e); 1125 } 1126 } 1127 1128 try { 1129 if (mIsHeadsUp) { 1130 mHeadsUpNotificationView.clear(); 1131 } 1132 mBarService.onNotificationClick(mNotificationKey); 1133 } catch (RemoteException ex) { 1134 // system process is dead if we're here. 1135 } 1136 1137 // close the shade if it was open 1138 animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE); 1139 visibilityChanged(false); 1140 1141 boolean waitForActivityLaunch = sent && mIntent.isActivity(); 1142 return waitForActivityLaunch && DELAY_DISMISS_TO_ACTIVITY_LAUNCH; 1143 } 1144 }); 1145 } 1146 } 1147 1148 /** 1149 * The LEDs are turned o)ff when the notification panel is shown, even just a little bit. 1150 * This was added last-minute and is inconsistent with the way the rest of the notifications 1151 * are handled, because the notification isn't really cancelled. The lights are just 1152 * turned off. If any other notifications happen, the lights will turn back on. Steve says 1153 * this is what he wants. (see bug 1131461) 1154 */ 1155 protected void visibilityChanged(boolean visible) { 1156 if (mPanelSlightlyVisible != visible) { 1157 mPanelSlightlyVisible = visible; 1158 try { 1159 if (visible) { 1160 mBarService.onPanelRevealed(); 1161 } else { 1162 mBarService.onPanelHidden(); 1163 } 1164 } catch (RemoteException ex) { 1165 // Won't fail unless the world has ended. 1166 } 1167 } 1168 } 1169 1170 /** 1171 * Cancel this notification and tell the StatusBarManagerService / NotificationManagerService 1172 * about the failure. 1173 * 1174 * WARNING: this will call back into us. Don't hold any locks. 1175 */ 1176 void handleNotificationError(StatusBarNotification n, String message) { 1177 removeNotification(n.getKey()); 1178 try { 1179 mBarService.onNotificationError(n.getPackageName(), n.getTag(), n.getId(), n.getUid(), 1180 n.getInitialPid(), message, n.getUserId()); 1181 } catch (RemoteException ex) { 1182 // The end is nigh. 1183 } 1184 } 1185 1186 protected StatusBarNotification removeNotificationViews(String key, RankingMap ranking) { 1187 NotificationData.Entry entry = mNotificationData.remove(key, ranking); 1188 if (entry == null) { 1189 Log.w(TAG, "removeNotification for unknown key: " + key); 1190 return null; 1191 } 1192 updateNotifications(); 1193 return entry.notification; 1194 } 1195 1196 protected NotificationData.Entry createNotificationViews(StatusBarNotification notification) { 1197 if (DEBUG) { 1198 Log.d(TAG, "createNotificationViews(notification=" + notification); 1199 } 1200 // Construct the icon. 1201 final StatusBarIconView iconView = new StatusBarIconView(mContext, 1202 notification.getPackageName() + "/0x" + Integer.toHexString(notification.getId()), 1203 notification.getNotification()); 1204 iconView.setScaleType(ImageView.ScaleType.CENTER_INSIDE); 1205 1206 final StatusBarIcon ic = new StatusBarIcon(notification.getPackageName(), 1207 notification.getUser(), 1208 notification.getNotification().icon, 1209 notification.getNotification().iconLevel, 1210 notification.getNotification().number, 1211 notification.getNotification().tickerText); 1212 if (!iconView.set(ic)) { 1213 handleNotificationError(notification, "Couldn't create icon: " + ic); 1214 return null; 1215 } 1216 // Construct the expanded view. 1217 NotificationData.Entry entry = new NotificationData.Entry(notification, iconView); 1218 if (!inflateViews(entry, mStackScroller)) { 1219 handleNotificationError(notification, "Couldn't expand RemoteViews for: " 1220 + notification); 1221 return null; 1222 } 1223 return entry; 1224 } 1225 1226 protected void addNotificationViews(Entry entry, RankingMap ranking) { 1227 if (entry == null) { 1228 return; 1229 } 1230 // Add the expanded view and icon. 1231 mNotificationData.add(entry, ranking); 1232 updateNotifications(); 1233 } 1234 1235 private void addNotificationViews(StatusBarNotification notification, RankingMap ranking) { 1236 addNotificationViews(createNotificationViews(notification), ranking); 1237 } 1238 1239 /** 1240 * @return The number of notifications we show on Keyguard. 1241 */ 1242 protected abstract int getMaxKeyguardNotifications(); 1243 1244 /** 1245 * Updates expanded, dimmed and locked states of notification rows. 1246 */ 1247 protected void updateRowStates() { 1248 int maxKeyguardNotifications = getMaxKeyguardNotifications(); 1249 mKeyguardIconOverflowContainer.getIconsView().removeAllViews(); 1250 final int N = mNotificationData.size(); 1251 int visibleNotifications = 0; 1252 boolean onKeyguard = mState == StatusBarState.KEYGUARD; 1253 for (int i = 0; i < N; i++) { 1254 NotificationData.Entry entry = mNotificationData.get(i); 1255 if (onKeyguard) { 1256 entry.row.setExpansionDisabled(true); 1257 } else { 1258 entry.row.setExpansionDisabled(false); 1259 if (!entry.row.isUserLocked()) { 1260 boolean top = (i == 0); 1261 entry.row.setSystemExpanded(top); 1262 } 1263 } 1264 boolean showOnKeyguard = shouldShowOnKeyguard(entry.notification); 1265 if (onKeyguard && (visibleNotifications >= maxKeyguardNotifications 1266 || !showOnKeyguard)) { 1267 entry.row.setVisibility(View.GONE); 1268 if (showOnKeyguard) { 1269 mKeyguardIconOverflowContainer.getIconsView().addNotification(entry); 1270 } 1271 } else { 1272 boolean wasGone = entry.row.getVisibility() == View.GONE; 1273 entry.row.setVisibility(View.VISIBLE); 1274 if (wasGone) { 1275 // notify the scroller of a child addition 1276 mStackScroller.generateAddAnimation(entry.row); 1277 } 1278 visibleNotifications++; 1279 } 1280 } 1281 1282 if (onKeyguard && mKeyguardIconOverflowContainer.getIconsView().getChildCount() > 0) { 1283 mKeyguardIconOverflowContainer.setVisibility(View.VISIBLE); 1284 } else { 1285 mKeyguardIconOverflowContainer.setVisibility(View.GONE); 1286 } 1287 // Move overflow container to last position. 1288 mStackScroller.changeViewPosition(mKeyguardIconOverflowContainer, 1289 mStackScroller.getChildCount() - 1); 1290 } 1291 1292 private boolean shouldShowOnKeyguard(StatusBarNotification sbn) { 1293 return sbn.getNotification().priority >= Notification.PRIORITY_LOW; 1294 } 1295 1296 protected void setZenMode(int mode) { 1297 if (!isDeviceProvisioned()) return; 1298 mZenMode = mode; 1299 updateNotifications(); 1300 } 1301 1302 protected void setShowLockscreenNotifications(boolean show) { 1303 mShowLockscreenNotifications = show; 1304 } 1305 1306 protected abstract void haltTicker(); 1307 protected abstract void setAreThereNotifications(); 1308 protected abstract void updateNotifications(); 1309 protected abstract void tick(StatusBarNotification n, boolean firstTime); 1310 protected abstract void updateExpandedViewPos(int expandedPosition); 1311 protected abstract boolean shouldDisableNavbarGestures(); 1312 1313 @Override 1314 public void addNotification(StatusBarNotification notification) { 1315 if (!USE_NOTIFICATION_LISTENER) { 1316 addNotificationInternal(notification, null); 1317 } 1318 } 1319 1320 public abstract void addNotificationInternal(StatusBarNotification notification, 1321 RankingMap ranking); 1322 1323 protected abstract void updateRankingInternal(RankingMap ranking); 1324 1325 @Override 1326 public void removeNotification(String key) { 1327 if (!USE_NOTIFICATION_LISTENER) { 1328 removeNotificationInternal(key, null); 1329 } 1330 } 1331 1332 public abstract void removeNotificationInternal(String key, RankingMap ranking); 1333 1334 public void updateNotification(StatusBarNotification notification) { 1335 if (!USE_NOTIFICATION_LISTENER) { 1336 updateNotificationInternal(notification, null); 1337 } 1338 } 1339 1340 public void updateNotificationInternal(StatusBarNotification notification, RankingMap ranking) { 1341 if (DEBUG) Log.d(TAG, "updateNotification(" + notification + ")"); 1342 1343 final NotificationData.Entry oldEntry = mNotificationData.findByKey(notification.getKey()); 1344 if (oldEntry == null) { 1345 Log.w(TAG, "updateNotification for unknown key: " + notification.getKey()); 1346 return; 1347 } 1348 1349 final StatusBarNotification oldNotification = oldEntry.notification; 1350 1351 // XXX: modify when we do something more intelligent with the two content views 1352 final RemoteViews oldContentView = oldNotification.getNotification().contentView; 1353 final RemoteViews contentView = notification.getNotification().contentView; 1354 final RemoteViews oldBigContentView = oldNotification.getNotification().bigContentView; 1355 final RemoteViews bigContentView = notification.getNotification().bigContentView; 1356 final RemoteViews oldHeadsUpContentView = oldNotification.getNotification().headsUpContentView; 1357 final RemoteViews headsUpContentView = notification.getNotification().headsUpContentView; 1358 final Notification oldPublicNotification = oldNotification.getNotification().publicVersion; 1359 final RemoteViews oldPublicContentView = oldPublicNotification != null 1360 ? oldPublicNotification.contentView : null; 1361 final Notification publicNotification = notification.getNotification().publicVersion; 1362 final RemoteViews publicContentView = publicNotification != null 1363 ? publicNotification.contentView : null; 1364 1365 if (DEBUG) { 1366 Log.d(TAG, "old notification: when=" + oldNotification.getNotification().when 1367 + " ongoing=" + oldNotification.isOngoing() 1368 + " expanded=" + oldEntry.expanded 1369 + " contentView=" + oldContentView 1370 + " bigContentView=" + oldBigContentView 1371 + " publicView=" + oldPublicContentView 1372 + " rowParent=" + oldEntry.row.getParent()); 1373 Log.d(TAG, "new notification: when=" + notification.getNotification().when 1374 + " ongoing=" + oldNotification.isOngoing() 1375 + " contentView=" + contentView 1376 + " bigContentView=" + bigContentView 1377 + " publicView=" + publicContentView); 1378 } 1379 1380 // Can we just reapply the RemoteViews in place? If when didn't change, the order 1381 // didn't change. 1382 1383 // 1U is never null 1384 boolean contentsUnchanged = oldEntry.expanded != null 1385 && contentView.getPackage() != null 1386 && oldContentView.getPackage() != null 1387 && oldContentView.getPackage().equals(contentView.getPackage()) 1388 && oldContentView.getLayoutId() == contentView.getLayoutId(); 1389 // large view may be null 1390 boolean bigContentsUnchanged = 1391 (oldEntry.getBigContentView() == null && bigContentView == null) 1392 || ((oldEntry.getBigContentView() != null && bigContentView != null) 1393 && bigContentView.getPackage() != null 1394 && oldBigContentView.getPackage() != null 1395 && oldBigContentView.getPackage().equals(bigContentView.getPackage()) 1396 && oldBigContentView.getLayoutId() == bigContentView.getLayoutId()); 1397 boolean headsUpContentsUnchanged = 1398 (oldHeadsUpContentView == null && headsUpContentView == null) 1399 || ((oldHeadsUpContentView != null && headsUpContentView != null) 1400 && headsUpContentView.getPackage() != null 1401 && oldHeadsUpContentView.getPackage() != null 1402 && oldHeadsUpContentView.getPackage().equals(headsUpContentView.getPackage()) 1403 && oldHeadsUpContentView.getLayoutId() == headsUpContentView.getLayoutId()); 1404 boolean publicUnchanged = 1405 (oldPublicContentView == null && publicContentView == null) 1406 || ((oldPublicContentView != null && publicContentView != null) 1407 && publicContentView.getPackage() != null 1408 && oldPublicContentView.getPackage() != null 1409 && oldPublicContentView.getPackage().equals(publicContentView.getPackage()) 1410 && oldPublicContentView.getLayoutId() == publicContentView.getLayoutId()); 1411 1412 1413 boolean updateTicker = notification.getNotification().tickerText != null 1414 && !TextUtils.equals(notification.getNotification().tickerText, 1415 oldEntry.notification.getNotification().tickerText); 1416 if (contentsUnchanged && bigContentsUnchanged && headsUpContentsUnchanged 1417 && publicUnchanged) { 1418 if (DEBUG) Log.d(TAG, "reusing notification for key: " + notification.getKey()); 1419 oldEntry.notification = notification; 1420 try { 1421 updateNotificationViews(oldEntry, notification); 1422 1423 if (ENABLE_HEADS_UP && mHeadsUpNotificationView.getEntry() != null 1424 && oldNotification == mHeadsUpNotificationView.getEntry().notification) { 1425 if (!shouldInterrupt(notification)) { 1426 if (DEBUG) Log.d(TAG, "no longer interrupts!"); 1427 scheduleHeadsUpClose(); 1428 } else { 1429 if (DEBUG) Log.d(TAG, "updating the current heads up:" + notification); 1430 mHeadsUpNotificationView.getEntry().notification = notification; 1431 updateHeadsUpViews(mHeadsUpNotificationView.getEntry(), notification); 1432 } 1433 } 1434 1435 // Update the icon. 1436 final StatusBarIcon ic = new StatusBarIcon(notification.getPackageName(), 1437 notification.getUser(), 1438 notification.getNotification().icon, notification.getNotification().iconLevel, 1439 notification.getNotification().number, 1440 notification.getNotification().tickerText); 1441 if (!oldEntry.icon.set(ic)) { 1442 handleNotificationError(notification, "Couldn't update icon: " + ic); 1443 return; 1444 } 1445 mNotificationData.updateRanking(ranking); 1446 updateNotifications(); 1447 } 1448 catch (RuntimeException e) { 1449 // It failed to add cleanly. Log, and remove the view from the panel. 1450 Log.w(TAG, "Couldn't reapply views for package " + contentView.getPackage(), e); 1451 removeNotificationViews(notification.getKey(), ranking); 1452 addNotificationViews(notification, ranking); 1453 } 1454 } else { 1455 if (DEBUG) Log.d(TAG, "not reusing notification for key: " + notification.getKey()); 1456 if (DEBUG) Log.d(TAG, "contents was " + (contentsUnchanged ? "unchanged" : "changed")); 1457 removeNotificationViews(notification.getKey(), ranking); 1458 addNotificationViews(notification, ranking); // will also replace the heads up 1459 final NotificationData.Entry newEntry = mNotificationData.findByKey( 1460 notification.getKey()); 1461 final boolean userChangedExpansion = oldEntry.row.hasUserChangedExpansion(); 1462 if (userChangedExpansion) { 1463 boolean userExpanded = oldEntry.row.isUserExpanded(); 1464 newEntry.row.setUserExpanded(userExpanded); 1465 newEntry.row.notifyHeightChanged(); 1466 } 1467 } 1468 1469 // Update the veto button accordingly (and as a result, whether this row is 1470 // swipe-dismissable) 1471 updateNotificationVetoButton(oldEntry.row, notification); 1472 1473 // Is this for you? 1474 boolean isForCurrentUser = notificationIsForCurrentProfiles(notification); 1475 if (DEBUG) Log.d(TAG, "notification is " + (isForCurrentUser ? "" : "not ") + "for you"); 1476 1477 // Restart the ticker if it's still running 1478 if (updateTicker && isForCurrentUser) { 1479 haltTicker(); 1480 tick(notification, false); 1481 } 1482 1483 // Recalculate the position of the sliding windows and the titles. 1484 setAreThereNotifications(); 1485 updateExpandedViewPos(EXPANDED_LEAVE_ALONE); 1486 } 1487 1488 private void updateNotificationViews(NotificationData.Entry entry, 1489 StatusBarNotification notification) { 1490 updateNotificationViews(entry, notification, false); 1491 } 1492 1493 private void updateHeadsUpViews(NotificationData.Entry entry, 1494 StatusBarNotification notification) { 1495 updateNotificationViews(entry, notification, true); 1496 } 1497 1498 private void updateNotificationViews(NotificationData.Entry entry, 1499 StatusBarNotification notification, boolean isHeadsUp) { 1500 final RemoteViews contentView = notification.getNotification().contentView; 1501 final RemoteViews bigContentView = isHeadsUp 1502 ? notification.getNotification().headsUpContentView 1503 : notification.getNotification().bigContentView; 1504 final Notification publicVersion = notification.getNotification().publicVersion; 1505 final RemoteViews publicContentView = publicVersion != null ? publicVersion.contentView 1506 : null; 1507 1508 // Reapply the RemoteViews 1509 contentView.reapply(mContext, entry.expanded, mOnClickHandler); 1510 if (bigContentView != null && entry.getBigContentView() != null) { 1511 bigContentView.reapply(mContext, entry.getBigContentView(), 1512 mOnClickHandler); 1513 } 1514 if (publicContentView != null && entry.getPublicContentView() != null) { 1515 publicContentView.reapply(mContext, entry.getPublicContentView(), mOnClickHandler); 1516 } 1517 // update the contentIntent 1518 final PendingIntent contentIntent = notification.getNotification().contentIntent; 1519 if (contentIntent != null) { 1520 final View.OnClickListener listener = makeClicker(contentIntent, notification.getKey(), 1521 isHeadsUp); 1522 entry.row.setOnClickListener(listener); 1523 } else { 1524 entry.row.setOnClickListener(null); 1525 } 1526 boolean wasBelow = entry.row.isBelowSpeedBump(); 1527 boolean nowBelow = isBelowSpeedBump(notification); 1528 if (wasBelow != nowBelow) { 1529 entry.row.setIsBelowSpeedBump(nowBelow); 1530 } 1531 entry.row.notifyContentUpdated(); 1532 } 1533 1534 private boolean isBelowSpeedBump(StatusBarNotification notification) { 1535 return notification.getNotification().priority == 1536 Notification.PRIORITY_MIN; 1537 } 1538 1539 protected void notifyHeadsUpScreenOn(boolean screenOn) { 1540 if (!screenOn) { 1541 scheduleHeadsUpEscalation(); 1542 } 1543 } 1544 1545 protected boolean shouldInterrupt(StatusBarNotification sbn) { 1546 Notification notification = sbn.getNotification(); 1547 // some predicates to make the boolean logic legible 1548 boolean isNoisy = (notification.defaults & Notification.DEFAULT_SOUND) != 0 1549 || (notification.defaults & Notification.DEFAULT_VIBRATE) != 0 1550 || notification.sound != null 1551 || notification.vibrate != null; 1552 boolean isHighPriority = sbn.getScore() >= INTERRUPTION_THRESHOLD; 1553 boolean isFullscreen = notification.fullScreenIntent != null; 1554 boolean hasTicker = mHeadsUpTicker && !TextUtils.isEmpty(notification.tickerText); 1555 boolean isAllowed = notification.extras.getInt(Notification.EXTRA_AS_HEADS_UP, 1556 Notification.HEADS_UP_ALLOWED) != Notification.HEADS_UP_NEVER; 1557 1558 final KeyguardTouchDelegate keyguard = KeyguardTouchDelegate.getInstance(mContext); 1559 boolean interrupt = (isFullscreen || (isHighPriority && (isNoisy || hasTicker))) 1560 && isAllowed 1561 && mPowerManager.isScreenOn() 1562 && !keyguard.isShowingAndNotOccluded() 1563 && !keyguard.isInputRestricted(); 1564 try { 1565 interrupt = interrupt && !mDreamManager.isDreaming(); 1566 } catch (RemoteException e) { 1567 Log.d(TAG, "failed to query dream manager", e); 1568 } 1569 if (DEBUG) Log.d(TAG, "interrupt: " + interrupt); 1570 return interrupt; 1571 } 1572 1573 // Q: What kinds of notifications should show during setup? 1574 // A: Almost none! Only things coming from the system (package is "android") that also 1575 // have special "kind" tags marking them as relevant for setup (see below). 1576 protected boolean showNotificationEvenIfUnprovisioned(StatusBarNotification sbn) { 1577 return "android".equals(sbn.getPackageName()) 1578 && sbn.getNotification().extras.getBoolean(Notification.EXTRA_ALLOW_DURING_SETUP); 1579 } 1580 1581 public boolean inKeyguardRestrictedInputMode() { 1582 return KeyguardTouchDelegate.getInstance(mContext).isInputRestricted(); 1583 } 1584 1585 public void setInteracting(int barWindow, boolean interacting) { 1586 // hook for subclasses 1587 } 1588 1589 public void setBouncerShowing(boolean bouncerShowing) { 1590 mBouncerShowing = bouncerShowing; 1591 } 1592 1593 /** 1594 * @return Whether the security bouncer from Keyguard is showing. 1595 */ 1596 public boolean isBouncerShowing() { 1597 return mBouncerShowing; 1598 } 1599 1600 public void destroy() { 1601 if (mSearchPanelView != null) { 1602 mWindowManager.removeViewImmediate(mSearchPanelView); 1603 } 1604 mContext.unregisterReceiver(mBroadcastReceiver); 1605 if (USE_NOTIFICATION_LISTENER) { 1606 try { 1607 mNotificationListener.unregisterAsSystemService(); 1608 } catch (RemoteException e) { 1609 // Ignore. 1610 } 1611 } 1612 } 1613} 1614