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