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