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