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