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