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