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