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