BaseStatusBar.java revision 738cfc91053fc888397ec2d139e0798f8c95b3ca
1 2/* 3 * Copyright (C) 2010 The Android Open Source Project 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18package com.android.systemui.statusbar; 19 20import com.android.internal.statusbar.IStatusBarService; 21import com.android.internal.statusbar.StatusBarIcon; 22import com.android.internal.statusbar.StatusBarIconList; 23import com.android.internal.statusbar.StatusBarNotification; 24import com.android.internal.widget.SizeAdaptiveLayout; 25import com.android.systemui.R; 26import com.android.systemui.SearchPanelView; 27import com.android.systemui.SystemUI; 28import com.android.systemui.SystemUIApplication; 29import com.android.systemui.recent.RecentTasksLoader; 30import com.android.systemui.recent.RecentsActivity; 31import com.android.systemui.recent.TaskDescription; 32import com.android.systemui.statusbar.policy.NotificationRowLayout; 33import com.android.systemui.statusbar.tablet.StatusBarPanel; 34 35import android.app.ActivityManager; 36import android.app.ActivityManagerNative; 37import android.app.ActivityOptions; 38import android.app.KeyguardManager; 39import android.app.PendingIntent; 40import android.app.Service; 41import android.app.TaskStackBuilder; 42import android.content.ActivityNotFoundException; 43import android.content.BroadcastReceiver; 44import android.content.Context; 45import android.content.Intent; 46import android.content.IntentFilter; 47import android.content.pm.ApplicationInfo; 48import android.content.pm.PackageManager.NameNotFoundException; 49import android.content.res.Configuration; 50import android.content.res.Resources; 51import android.database.ContentObserver; 52import android.graphics.Bitmap; 53import android.graphics.Paint; 54import android.graphics.Rect; 55import android.net.Uri; 56import android.os.Build; 57import android.os.Handler; 58import android.os.IBinder; 59import android.os.Message; 60import android.os.RemoteException; 61import android.os.ServiceManager; 62import android.os.UserHandle; 63import android.provider.Settings; 64import android.text.TextUtils; 65import android.util.DisplayMetrics; 66import android.util.Log; 67import android.util.Slog; 68import android.view.Display; 69import android.view.IWindowManager; 70import android.view.LayoutInflater; 71import android.view.MenuItem; 72import android.view.MotionEvent; 73import android.view.View; 74import android.view.ViewGroup; 75import android.view.WindowManagerGlobal; 76import android.view.ViewGroup.LayoutParams; 77import android.view.WindowManager; 78import android.widget.ImageView; 79import android.widget.LinearLayout; 80import android.widget.PopupMenu; 81import android.widget.RemoteViews; 82import android.widget.TextView; 83 84import java.util.ArrayList; 85 86public abstract class BaseStatusBar extends SystemUI implements 87 CommandQueue.Callbacks { 88 public static final String TAG = "StatusBar"; 89 public static final boolean DEBUG = false; 90 public static final boolean MULTIUSER_DEBUG = false; 91 92 protected static final int MSG_TOGGLE_RECENTS_PANEL = 1020; 93 protected static final int MSG_CLOSE_RECENTS_PANEL = 1021; 94 protected static final int MSG_PRELOAD_RECENT_APPS = 1022; 95 protected static final int MSG_CANCEL_PRELOAD_RECENT_APPS = 1023; 96 protected static final int MSG_OPEN_SEARCH_PANEL = 1024; 97 protected static final int MSG_CLOSE_SEARCH_PANEL = 1025; 98 protected static final int MSG_SHOW_INTRUDER = 1026; 99 protected static final int MSG_HIDE_INTRUDER = 1027; 100 101 protected static final boolean ENABLE_INTRUDERS = false; 102 103 // Should match the value in PhoneWindowManager 104 public static final String SYSTEM_DIALOG_REASON_RECENT_APPS = "recentapps"; 105 106 public static final int EXPANDED_LEAVE_ALONE = -10000; 107 public static final int EXPANDED_FULL_OPEN = -10001; 108 109 protected CommandQueue mCommandQueue; 110 protected IStatusBarService mBarService; 111 protected H mHandler = createHandler(); 112 113 // all notifications 114 protected NotificationData mNotificationData = new NotificationData(); 115 protected NotificationRowLayout mPile; 116 117 protected StatusBarNotification mCurrentlyIntrudingNotification; 118 119 // used to notify status bar for suppressing notification LED 120 protected boolean mPanelSlightlyVisible; 121 122 // Search panel 123 protected SearchPanelView mSearchPanelView; 124 125 protected PopupMenu mNotificationBlamePopup; 126 127 protected int mCurrentUserId = 0; 128 129 // UI-specific methods 130 131 /** 132 * Create all windows necessary for the status bar (including navigation, overlay panels, etc) 133 * and add them to the window manager. 134 */ 135 protected abstract void createAndAddWindows(); 136 137 protected WindowManager mWindowManager; 138 protected IWindowManager mWindowManagerService; 139 protected Display mDisplay; 140 141 private boolean mDeviceProvisioned = false; 142 143 public IStatusBarService getStatusBarService() { 144 return mBarService; 145 } 146 147 public boolean isDeviceProvisioned() { 148 return mDeviceProvisioned; 149 } 150 151 private ContentObserver mProvisioningObserver = new ContentObserver(new Handler()) { 152 @Override 153 public void onChange(boolean selfChange) { 154 final boolean provisioned = 0 != Settings.Global.getInt( 155 mContext.getContentResolver(), Settings.Global.DEVICE_PROVISIONED, 0); 156 if (provisioned != mDeviceProvisioned) { 157 mDeviceProvisioned = provisioned; 158 updateNotificationIcons(); 159 } 160 } 161 }; 162 163 private RemoteViews.OnClickHandler mOnClickHandler = new RemoteViews.OnClickHandler() { 164 @Override 165 public boolean onClickHandler(View view, PendingIntent pendingIntent, Intent fillInIntent) { 166 if (DEBUG) { 167 Slog.v(TAG, "Notification click handler invoked for intent: " + pendingIntent); 168 } 169 final boolean isActivity = pendingIntent.isActivity(); 170 if (isActivity) { 171 try { 172 // The intent we are sending is for the application, which 173 // won't have permission to immediately start an activity after 174 // the user switches to home. We know it is safe to do at this 175 // point, so make sure new activity switches are now allowed. 176 ActivityManagerNative.getDefault().resumeAppSwitches(); 177 // Also, notifications can be launched from the lock screen, 178 // so dismiss the lock screen when the activity starts. 179 ActivityManagerNative.getDefault().dismissKeyguardOnNextActivity(); 180 } catch (RemoteException e) { 181 } 182 } 183 184 boolean handled = super.onClickHandler(view, pendingIntent, fillInIntent); 185 186 if (isActivity && handled) { 187 // close the shade if it was open 188 animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE); 189 visibilityChanged(false); 190 } 191 return handled; 192 } 193 }; 194 195 public void start() { 196 mWindowManager = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE); 197 mWindowManagerService = WindowManagerGlobal.getWindowManagerService(); 198 mDisplay = mWindowManager.getDefaultDisplay(); 199 200 mProvisioningObserver.onChange(false); // set up 201 mContext.getContentResolver().registerContentObserver( 202 Settings.Global.getUriFor(Settings.Global.DEVICE_PROVISIONED), true, 203 mProvisioningObserver); 204 205 mBarService = IStatusBarService.Stub.asInterface( 206 ServiceManager.getService(Context.STATUS_BAR_SERVICE)); 207 208 // Connect in to the status bar manager service 209 StatusBarIconList iconList = new StatusBarIconList(); 210 ArrayList<IBinder> notificationKeys = new ArrayList<IBinder>(); 211 ArrayList<StatusBarNotification> notifications = new ArrayList<StatusBarNotification>(); 212 mCommandQueue = new CommandQueue(this, iconList); 213 214 int[] switches = new int[7]; 215 ArrayList<IBinder> binders = new ArrayList<IBinder>(); 216 try { 217 mBarService.registerStatusBar(mCommandQueue, iconList, notificationKeys, notifications, 218 switches, binders); 219 } catch (RemoteException ex) { 220 // If the system process isn't there we're doomed anyway. 221 } 222 223 createAndAddWindows(); 224 225 disable(switches[0]); 226 setSystemUiVisibility(switches[1], 0xffffffff); 227 topAppWindowChanged(switches[2] != 0); 228 // StatusBarManagerService has a back up of IME token and it's restored here. 229 setImeWindowStatus(binders.get(0), switches[3], switches[4]); 230 setHardKeyboardStatus(switches[5] != 0, switches[6] != 0); 231 232 // Set up the initial icon state 233 int N = iconList.size(); 234 int viewIndex = 0; 235 for (int i=0; i<N; i++) { 236 StatusBarIcon icon = iconList.getIcon(i); 237 if (icon != null) { 238 addIcon(iconList.getSlot(i), i, viewIndex, icon); 239 viewIndex++; 240 } 241 } 242 243 // Set up the initial notification state 244 N = notificationKeys.size(); 245 if (N == notifications.size()) { 246 for (int i=0; i<N; i++) { 247 addNotification(notificationKeys.get(i), notifications.get(i)); 248 } 249 } else { 250 Log.wtf(TAG, "Notification list length mismatch: keys=" + N 251 + " notifications=" + notifications.size()); 252 } 253 254 if (DEBUG) { 255 Slog.d(TAG, String.format( 256 "init: icons=%d disabled=0x%08x lights=0x%08x menu=0x%08x imeButton=0x%08x", 257 iconList.size(), 258 switches[0], 259 switches[1], 260 switches[2], 261 switches[3] 262 )); 263 } 264 265 mCurrentUserId = ActivityManager.getCurrentUser(); 266 267 IntentFilter filter = new IntentFilter(); 268 filter.addAction(Intent.ACTION_USER_SWITCHED); 269 mContext.registerReceiver(new BroadcastReceiver() { 270 @Override 271 public void onReceive(Context context, Intent intent) { 272 String action = intent.getAction(); 273 if (Intent.ACTION_USER_SWITCHED.equals(action)) { 274 mCurrentUserId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1); 275 if (true) Slog.v(TAG, "userId " + mCurrentUserId + " is in the house"); 276 userSwitched(mCurrentUserId); 277 } 278 }}, filter); 279 } 280 281 public void userSwitched(int newUserId) { 282 // should be overridden 283 } 284 285 public boolean notificationIsForCurrentUser(StatusBarNotification n) { 286 final int thisUserId = mCurrentUserId; 287 final int notificationUserId = n.getUserId(); 288 if (DEBUG && MULTIUSER_DEBUG) { 289 Slog.v(TAG, String.format("%s: current userid: %d, notification userid: %d", 290 n, thisUserId, notificationUserId)); 291 } 292 return notificationUserId == UserHandle.USER_ALL 293 || thisUserId == notificationUserId; 294 } 295 296 protected View updateNotificationVetoButton(View row, StatusBarNotification n) { 297 View vetoButton = row.findViewById(R.id.veto); 298 if (n.isClearable()) { 299 final String _pkg = n.pkg; 300 final String _tag = n.tag; 301 final int _id = n.id; 302 vetoButton.setOnClickListener(new View.OnClickListener() { 303 public void onClick(View v) { 304 // Accessibility feedback 305 v.announceForAccessibility( 306 mContext.getString(R.string.accessibility_notification_dismissed)); 307 try { 308 mBarService.onNotificationClear(_pkg, _tag, _id); 309 310 } catch (RemoteException ex) { 311 // system process is dead if we're here. 312 } 313 } 314 }); 315 vetoButton.setVisibility(View.VISIBLE); 316 } else { 317 vetoButton.setVisibility(View.GONE); 318 } 319 vetoButton.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO); 320 return vetoButton; 321 } 322 323 324 protected void applyLegacyRowBackground(StatusBarNotification sbn, View content) { 325 if (sbn.notification.contentView.getLayoutId() != 326 com.android.internal.R.layout.notification_template_base) { 327 int version = 0; 328 try { 329 ApplicationInfo info = mContext.getPackageManager().getApplicationInfo(sbn.pkg, 0); 330 version = info.targetSdkVersion; 331 } catch (NameNotFoundException ex) { 332 Slog.e(TAG, "Failed looking up ApplicationInfo for " + sbn.pkg, ex); 333 } 334 if (version > 0 && version < Build.VERSION_CODES.GINGERBREAD) { 335 content.setBackgroundResource(R.drawable.notification_row_legacy_bg); 336 } else { 337 content.setBackgroundResource(com.android.internal.R.drawable.notification_bg); 338 } 339 } 340 } 341 342 private void startApplicationDetailsActivity(String packageName) { 343 Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, 344 Uri.fromParts("package", packageName, null)); 345 intent.setComponent(intent.resolveActivity(mContext.getPackageManager())); 346 TaskStackBuilder.create(mContext).addNextIntentWithParentStack(intent).startActivities( 347 null, UserHandle.CURRENT); 348 } 349 350 protected View.OnLongClickListener getNotificationLongClicker() { 351 return new View.OnLongClickListener() { 352 @Override 353 public boolean onLongClick(View v) { 354 final String packageNameF = (String) v.getTag(); 355 if (packageNameF == null) return false; 356 if (v.getWindowToken() == null) return false; 357 mNotificationBlamePopup = new PopupMenu(mContext, v); 358 mNotificationBlamePopup.getMenuInflater().inflate( 359 R.menu.notification_popup_menu, 360 mNotificationBlamePopup.getMenu()); 361 mNotificationBlamePopup.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() { 362 public boolean onMenuItemClick(MenuItem item) { 363 if (item.getItemId() == R.id.notification_inspect_item) { 364 startApplicationDetailsActivity(packageNameF); 365 animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE); 366 } else { 367 return false; 368 } 369 return true; 370 } 371 }); 372 mNotificationBlamePopup.show(); 373 374 return true; 375 } 376 }; 377 } 378 379 public void dismissPopups() { 380 if (mNotificationBlamePopup != null) { 381 mNotificationBlamePopup.dismiss(); 382 mNotificationBlamePopup = null; 383 } 384 } 385 386 public void dismissIntruder() { 387 // pass 388 } 389 390 @Override 391 public void toggleRecentApps() { 392 int msg = MSG_TOGGLE_RECENTS_PANEL; 393 mHandler.removeMessages(msg); 394 mHandler.sendEmptyMessage(msg); 395 } 396 397 @Override 398 public void preloadRecentApps() { 399 int msg = MSG_PRELOAD_RECENT_APPS; 400 mHandler.removeMessages(msg); 401 mHandler.sendEmptyMessage(msg); 402 } 403 404 @Override 405 public void cancelPreloadRecentApps() { 406 int msg = MSG_CANCEL_PRELOAD_RECENT_APPS; 407 mHandler.removeMessages(msg); 408 mHandler.sendEmptyMessage(msg); 409 } 410 411 @Override 412 public void showSearchPanel() { 413 int msg = MSG_OPEN_SEARCH_PANEL; 414 mHandler.removeMessages(msg); 415 mHandler.sendEmptyMessage(msg); 416 } 417 418 @Override 419 public void hideSearchPanel() { 420 int msg = MSG_CLOSE_SEARCH_PANEL; 421 mHandler.removeMessages(msg); 422 mHandler.sendEmptyMessage(msg); 423 } 424 425 protected abstract WindowManager.LayoutParams getRecentsLayoutParams( 426 LayoutParams layoutParams); 427 428 protected abstract WindowManager.LayoutParams getSearchLayoutParams( 429 LayoutParams layoutParams); 430 431 protected RecentTasksLoader getRecentTasksLoader() { 432 final SystemUIApplication app = (SystemUIApplication) ((Service) mContext).getApplication(); 433 return app.getRecentTasksLoader(); 434 } 435 436 protected void updateSearchPanel() { 437 // Search Panel 438 boolean visible = false; 439 if (mSearchPanelView != null) { 440 visible = mSearchPanelView.isShowing(); 441 mWindowManager.removeView(mSearchPanelView); 442 } 443 444 // Provide SearchPanel with a temporary parent to allow layout params to work. 445 LinearLayout tmpRoot = new LinearLayout(mContext); 446 mSearchPanelView = (SearchPanelView) LayoutInflater.from(mContext).inflate( 447 R.layout.status_bar_search_panel, tmpRoot, false); 448 mSearchPanelView.setOnTouchListener( 449 new TouchOutsideListener(MSG_CLOSE_SEARCH_PANEL, mSearchPanelView)); 450 mSearchPanelView.setVisibility(View.GONE); 451 452 WindowManager.LayoutParams lp = getSearchLayoutParams(mSearchPanelView.getLayoutParams()); 453 454 mWindowManager.addView(mSearchPanelView, lp); 455 mSearchPanelView.setBar(this); 456 if (visible) { 457 mSearchPanelView.show(true, false); 458 } 459 } 460 461 protected H createHandler() { 462 return new H(); 463 } 464 465 static void sendCloseSystemWindows(Context context, String reason) { 466 if (ActivityManagerNative.isSystemReady()) { 467 try { 468 ActivityManagerNative.getDefault().closeSystemDialogs(reason); 469 } catch (RemoteException e) { 470 } 471 } 472 } 473 474 protected abstract View getStatusBarView(); 475 476 protected void toggleRecentsActivity() { 477 try { 478 final RecentTasksLoader recentTasksLoader = getRecentTasksLoader(); 479 TaskDescription firstTask = recentTasksLoader.getFirstTask(); 480 481 Intent intent = new Intent(RecentsActivity.TOGGLE_RECENTS_INTENT); 482 intent.setClassName("com.android.systemui", 483 "com.android.systemui.recent.RecentsActivity"); 484 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK 485 | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); 486 487 if (firstTask == null) { 488 // The correct window animation will be applied via the activity's style 489 mContext.startActivityAsUser(intent, new UserHandle( 490 UserHandle.USER_CURRENT)); 491 } else { 492 Bitmap first = firstTask.getThumbnail(); 493 final Resources res = mContext.getResources(); 494 495 float thumbWidth = res 496 .getDimensionPixelSize(R.dimen.status_bar_recents_thumbnail_width); 497 float thumbHeight = res 498 .getDimensionPixelSize(R.dimen.status_bar_recents_thumbnail_height); 499 if (first == null) { 500 throw new RuntimeException("Recents thumbnail is null"); 501 } 502 if (first.getWidth() != thumbWidth || first.getHeight() != thumbHeight) { 503 first = Bitmap.createScaledBitmap(first, (int) thumbWidth, (int) thumbHeight, 504 true); 505 if (first == null) { 506 throw new RuntimeException("Recents thumbnail is null"); 507 } 508 } 509 510 511 DisplayMetrics dm = new DisplayMetrics(); 512 mDisplay.getMetrics(dm); 513 // calculate it here, but consider moving it elsewhere 514 // first, determine which orientation you're in. 515 // todo: move the system_bar layouts to sw600dp ? 516 final Configuration config = res.getConfiguration(); 517 int x, y; 518 519 if (config.orientation == Configuration.ORIENTATION_PORTRAIT) { 520 float appLabelLeftMargin = res 521 .getDimensionPixelSize(R.dimen.status_bar_recents_app_label_left_margin); 522 float appLabelWidth = res 523 .getDimensionPixelSize(R.dimen.status_bar_recents_app_label_width); 524 float thumbLeftMargin = res 525 .getDimensionPixelSize(R.dimen.status_bar_recents_thumbnail_left_margin); 526 float thumbBgPadding = res 527 .getDimensionPixelSize(R.dimen.status_bar_recents_thumbnail_bg_padding); 528 529 float width = appLabelLeftMargin + 530 +appLabelWidth 531 + thumbLeftMargin 532 + thumbWidth 533 + 2 * thumbBgPadding; 534 535 x = (int) ((dm.widthPixels - width) / 2f + appLabelLeftMargin + appLabelWidth 536 + thumbBgPadding + thumbLeftMargin); 537 y = (int) (dm.heightPixels 538 - res.getDimensionPixelSize(R.dimen.status_bar_recents_thumbnail_height) - thumbBgPadding); 539 } else { // if (config.orientation == Configuration.ORIENTATION_LANDSCAPE) { 540 float thumbTopMargin = res 541 .getDimensionPixelSize(R.dimen.status_bar_recents_thumbnail_top_margin); 542 float thumbBgPadding = res 543 .getDimensionPixelSize(R.dimen.status_bar_recents_thumbnail_bg_padding); 544 float textPadding = res 545 .getDimensionPixelSize(R.dimen.status_bar_recents_text_description_padding); 546 float labelTextSize = res 547 .getDimensionPixelSize(R.dimen.status_bar_recents_app_label_text_size); 548 Paint p = new Paint(); 549 p.setTextSize(labelTextSize); 550 float labelTextHeight = p.getFontMetricsInt().bottom 551 - p.getFontMetricsInt().top; 552 float descriptionTextSize = res 553 .getDimensionPixelSize(R.dimen.status_bar_recents_app_description_text_size); 554 p.setTextSize(descriptionTextSize); 555 float descriptionTextHeight = p.getFontMetricsInt().bottom 556 - p.getFontMetricsInt().top; 557 558 float statusBarHeight = res 559 .getDimensionPixelSize(com.android.internal.R.dimen.status_bar_height); 560 float recentsItemTopPadding = statusBarHeight; 561 562 float height = thumbTopMargin 563 + thumbHeight 564 + 2 * thumbBgPadding + textPadding + labelTextHeight 565 + recentsItemTopPadding + textPadding + descriptionTextHeight; 566 float recentsItemRightPadding = res 567 .getDimensionPixelSize(R.dimen.status_bar_recents_item_padding); 568 float recentsScrollViewRightPadding = res 569 .getDimensionPixelSize(R.dimen.status_bar_recents_right_glow_margin); 570 x = (int) (dm.widthPixels - res 571 .getDimensionPixelSize(R.dimen.status_bar_recents_thumbnail_width) 572 - thumbBgPadding - recentsItemRightPadding - recentsScrollViewRightPadding); 573 y = (int) ((dm.heightPixels - statusBarHeight - height) / 2f + thumbTopMargin 574 + recentsItemTopPadding + thumbBgPadding + statusBarHeight); 575 } 576 577 final SystemUIApplication app = 578 (SystemUIApplication) ((Service) mContext).getApplication(); 579 app.setWaitingForWinAnimStart(true); 580 ActivityOptions opts = ActivityOptions.makeThumbnailScaleDownAnimation( 581 getStatusBarView(), 582 first, x, y, 583 new ActivityOptions.OnAnimationStartedListener() { 584 public void onAnimationStarted() { 585 app.onWindowAnimationStart(); 586 } 587 }); 588 mContext.startActivityAsUser(intent, opts.toBundle(), new UserHandle( 589 UserHandle.USER_CURRENT)); 590 } 591 return; 592 } catch (ActivityNotFoundException e) { 593 Log.e(TAG, "Failed to launch RecentAppsIntent", e); 594 } 595 } 596 597 protected class H extends Handler { 598 public void handleMessage(Message m) { 599 switch (m.what) { 600 case MSG_TOGGLE_RECENTS_PANEL: 601 if (DEBUG) Slog.d(TAG, "toggle recents panel"); 602 toggleRecentsActivity(); 603 break; 604 case MSG_CLOSE_RECENTS_PANEL: 605 if (DEBUG) Slog.d(TAG, "closing recents panel"); 606 Intent intent = new Intent(RecentsActivity.CLOSE_RECENTS_INTENT); 607 intent.setPackage("com.android.systemui"); 608 mContext.sendBroadcastAsUser(intent, new UserHandle(UserHandle.USER_CURRENT)); 609 break; 610 case MSG_PRELOAD_RECENT_APPS: 611 if (DEBUG) Slog.d(TAG, "preloading recents"); 612 getRecentTasksLoader().preloadRecentTasksList(); 613 break; 614 case MSG_CANCEL_PRELOAD_RECENT_APPS: 615 if (DEBUG) Slog.d(TAG, "cancel preloading recents"); 616 getRecentTasksLoader().cancelPreloadingRecentTasksList(); 617 break; 618 case MSG_OPEN_SEARCH_PANEL: 619 if (DEBUG) Slog.d(TAG, "opening search panel"); 620 if (mSearchPanelView != null && mSearchPanelView.isAssistantAvailable()) { 621 mSearchPanelView.show(true, true); 622 } 623 break; 624 case MSG_CLOSE_SEARCH_PANEL: 625 if (DEBUG) Slog.d(TAG, "closing search panel"); 626 if (mSearchPanelView != null && mSearchPanelView.isShowing()) { 627 mSearchPanelView.show(false, true); 628 } 629 break; 630 } 631 } 632 } 633 634 public class TouchOutsideListener implements View.OnTouchListener { 635 private int mMsg; 636 private StatusBarPanel mPanel; 637 638 public TouchOutsideListener(int msg, StatusBarPanel panel) { 639 mMsg = msg; 640 mPanel = panel; 641 } 642 643 public boolean onTouch(View v, MotionEvent ev) { 644 final int action = ev.getAction(); 645 if (action == MotionEvent.ACTION_OUTSIDE 646 || (action == MotionEvent.ACTION_DOWN 647 && !mPanel.isInContentArea((int)ev.getX(), (int)ev.getY()))) { 648 mHandler.removeMessages(mMsg); 649 mHandler.sendEmptyMessage(mMsg); 650 return true; 651 } 652 return false; 653 } 654 } 655 656 protected void workAroundBadLayerDrawableOpacity(View v) { 657 } 658 659 protected boolean inflateViews(NotificationData.Entry entry, ViewGroup parent) { 660 int minHeight = 661 mContext.getResources().getDimensionPixelSize(R.dimen.notification_min_height); 662 int maxHeight = 663 mContext.getResources().getDimensionPixelSize(R.dimen.notification_max_height); 664 StatusBarNotification sbn = entry.notification; 665 RemoteViews oneU = sbn.notification.contentView; 666 RemoteViews large = sbn.notification.bigContentView; 667 if (oneU == null) { 668 return false; 669 } 670 671 // create the row view 672 LayoutInflater inflater = (LayoutInflater)mContext.getSystemService( 673 Context.LAYOUT_INFLATER_SERVICE); 674 View row = inflater.inflate(R.layout.status_bar_notification_row, parent, false); 675 676 // for blaming (see SwipeHelper.setLongPressListener) 677 row.setTag(sbn.pkg); 678 679 workAroundBadLayerDrawableOpacity(row); 680 View vetoButton = updateNotificationVetoButton(row, sbn); 681 vetoButton.setContentDescription(mContext.getString( 682 R.string.accessibility_remove_notification)); 683 684 // NB: the large icon is now handled entirely by the template 685 686 // bind the click event to the content area 687 ViewGroup content = (ViewGroup)row.findViewById(R.id.content); 688 ViewGroup adaptive = (ViewGroup)row.findViewById(R.id.adaptive); 689 690 content.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS); 691 692 PendingIntent contentIntent = sbn.notification.contentIntent; 693 if (contentIntent != null) { 694 final View.OnClickListener listener = new NotificationClicker(contentIntent, 695 sbn.pkg, sbn.tag, sbn.id); 696 content.setOnClickListener(listener); 697 } else { 698 content.setOnClickListener(null); 699 } 700 701 // TODO(cwren) normalize variable names with those in updateNotification 702 View expandedOneU = null; 703 View expandedLarge = null; 704 try { 705 expandedOneU = oneU.apply(mContext, adaptive, mOnClickHandler); 706 if (large != null) { 707 expandedLarge = large.apply(mContext, adaptive, mOnClickHandler); 708 } 709 } 710 catch (RuntimeException e) { 711 final String ident = sbn.pkg + "/0x" + Integer.toHexString(sbn.id); 712 Slog.e(TAG, "couldn't inflate view for notification " + ident, e); 713 return false; 714 } 715 716 if (expandedOneU != null) { 717 SizeAdaptiveLayout.LayoutParams params = 718 new SizeAdaptiveLayout.LayoutParams(expandedOneU.getLayoutParams()); 719 params.minHeight = minHeight; 720 params.maxHeight = minHeight; 721 adaptive.addView(expandedOneU, params); 722 } 723 if (expandedLarge != null) { 724 SizeAdaptiveLayout.LayoutParams params = 725 new SizeAdaptiveLayout.LayoutParams(expandedLarge.getLayoutParams()); 726 params.minHeight = minHeight+1; 727 params.maxHeight = maxHeight; 728 adaptive.addView(expandedLarge, params); 729 } 730 row.setDrawingCacheEnabled(true); 731 732 applyLegacyRowBackground(sbn, content); 733 734 row.setTag(R.id.expandable_tag, Boolean.valueOf(large != null)); 735 736 if (MULTIUSER_DEBUG) { 737 TextView debug = (TextView) row.findViewById(R.id.debug_info); 738 if (debug != null) { 739 debug.setVisibility(View.VISIBLE); 740 debug.setText("U " + entry.notification.getUserId()); 741 } 742 } 743 entry.row = row; 744 entry.content = content; 745 entry.expanded = expandedOneU; 746 entry.setLargeView(expandedLarge); 747 748 return true; 749 } 750 751 public NotificationClicker makeClicker(PendingIntent intent, String pkg, String tag, int id) { 752 return new NotificationClicker(intent, pkg, tag, id); 753 } 754 755 private class NotificationClicker implements View.OnClickListener { 756 private PendingIntent mIntent; 757 private String mPkg; 758 private String mTag; 759 private int mId; 760 761 NotificationClicker(PendingIntent intent, String pkg, String tag, int id) { 762 mIntent = intent; 763 mPkg = pkg; 764 mTag = tag; 765 mId = id; 766 } 767 768 public void onClick(View v) { 769 try { 770 // The intent we are sending is for the application, which 771 // won't have permission to immediately start an activity after 772 // the user switches to home. We know it is safe to do at this 773 // point, so make sure new activity switches are now allowed. 774 ActivityManagerNative.getDefault().resumeAppSwitches(); 775 // Also, notifications can be launched from the lock screen, 776 // so dismiss the lock screen when the activity starts. 777 ActivityManagerNative.getDefault().dismissKeyguardOnNextActivity(); 778 } catch (RemoteException e) { 779 } 780 781 if (mIntent != null) { 782 int[] pos = new int[2]; 783 v.getLocationOnScreen(pos); 784 Intent overlay = new Intent(); 785 overlay.setSourceBounds( 786 new Rect(pos[0], pos[1], pos[0]+v.getWidth(), pos[1]+v.getHeight())); 787 try { 788 mIntent.send(mContext, 0, overlay); 789 } catch (PendingIntent.CanceledException e) { 790 // the stack trace isn't very helpful here. Just log the exception message. 791 Slog.w(TAG, "Sending contentIntent failed: " + e); 792 } 793 794 KeyguardManager kgm = 795 (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE); 796 if (kgm != null) kgm.exitKeyguardSecurely(null); 797 } 798 799 try { 800 mBarService.onNotificationClick(mPkg, mTag, mId); 801 } catch (RemoteException ex) { 802 // system process is dead if we're here. 803 } 804 805 // close the shade if it was open 806 animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE); 807 visibilityChanged(false); 808 809 // If this click was on the intruder alert, hide that instead 810// mHandler.sendEmptyMessage(MSG_HIDE_INTRUDER); 811 } 812 } 813 /** 814 * The LEDs are turned o)ff when the notification panel is shown, even just a little bit. 815 * This was added last-minute and is inconsistent with the way the rest of the notifications 816 * are handled, because the notification isn't really cancelled. The lights are just 817 * turned off. If any other notifications happen, the lights will turn back on. Steve says 818 * this is what he wants. (see bug 1131461) 819 */ 820 protected void visibilityChanged(boolean visible) { 821 if (mPanelSlightlyVisible != visible) { 822 mPanelSlightlyVisible = visible; 823 try { 824 mBarService.onPanelRevealed(); 825 } catch (RemoteException ex) { 826 // Won't fail unless the world has ended. 827 } 828 } 829 } 830 831 /** 832 * Cancel this notification and tell the StatusBarManagerService / NotificationManagerService 833 * about the failure. 834 * 835 * WARNING: this will call back into us. Don't hold any locks. 836 */ 837 void handleNotificationError(IBinder key, StatusBarNotification n, String message) { 838 removeNotification(key); 839 try { 840 mBarService.onNotificationError(n.pkg, n.tag, n.id, n.uid, n.initialPid, message); 841 } catch (RemoteException ex) { 842 // The end is nigh. 843 } 844 } 845 846 protected StatusBarNotification removeNotificationViews(IBinder key) { 847 NotificationData.Entry entry = mNotificationData.remove(key); 848 if (entry == null) { 849 Slog.w(TAG, "removeNotification for unknown key: " + key); 850 return null; 851 } 852 // Remove the expanded view. 853 ViewGroup rowParent = (ViewGroup)entry.row.getParent(); 854 if (rowParent != null) rowParent.removeView(entry.row); 855 updateExpansionStates(); 856 updateNotificationIcons(); 857 858 return entry.notification; 859 } 860 861 protected StatusBarIconView addNotificationViews(IBinder key, 862 StatusBarNotification notification) { 863 if (DEBUG) { 864 Slog.d(TAG, "addNotificationViews(key=" + key + ", notification=" + notification); 865 } 866 // Construct the icon. 867 final StatusBarIconView iconView = new StatusBarIconView(mContext, 868 notification.pkg + "/0x" + Integer.toHexString(notification.id), 869 notification.notification); 870 iconView.setScaleType(ImageView.ScaleType.CENTER_INSIDE); 871 872 final StatusBarIcon ic = new StatusBarIcon(notification.pkg, 873 notification.user, 874 notification.notification.icon, 875 notification.notification.iconLevel, 876 notification.notification.number, 877 notification.notification.tickerText); 878 if (!iconView.set(ic)) { 879 handleNotificationError(key, notification, "Couldn't create icon: " + ic); 880 return null; 881 } 882 // Construct the expanded view. 883 NotificationData.Entry entry = new NotificationData.Entry(key, notification, iconView); 884 if (!inflateViews(entry, mPile)) { 885 handleNotificationError(key, notification, "Couldn't expand RemoteViews for: " 886 + notification); 887 return null; 888 } 889 890 // Add the expanded view and icon. 891 int pos = mNotificationData.add(entry); 892 if (DEBUG) { 893 Slog.d(TAG, "addNotificationViews: added at " + pos); 894 } 895 updateExpansionStates(); 896 updateNotificationIcons(); 897 898 return iconView; 899 } 900 901 protected boolean expandView(NotificationData.Entry entry, boolean expand) { 902 int rowHeight = 903 mContext.getResources().getDimensionPixelSize(R.dimen.notification_row_min_height); 904 ViewGroup.LayoutParams lp = entry.row.getLayoutParams(); 905 if (entry.expandable() && expand) { 906 if (DEBUG) Slog.d(TAG, "setting expanded row height to WRAP_CONTENT"); 907 lp.height = ViewGroup.LayoutParams.WRAP_CONTENT; 908 } else { 909 if (DEBUG) Slog.d(TAG, "setting collapsed row height to " + rowHeight); 910 lp.height = rowHeight; 911 } 912 entry.row.setLayoutParams(lp); 913 return expand; 914 } 915 916 protected void updateExpansionStates() { 917 int N = mNotificationData.size(); 918 for (int i = 0; i < N; i++) { 919 NotificationData.Entry entry = mNotificationData.get(i); 920 if (!entry.userLocked()) { 921 if (i == (N-1)) { 922 if (DEBUG) Slog.d(TAG, "expanding top notification at " + i); 923 expandView(entry, true); 924 } else { 925 if (!entry.userExpanded()) { 926 if (DEBUG) Slog.d(TAG, "collapsing notification at " + i); 927 expandView(entry, false); 928 } else { 929 if (DEBUG) Slog.d(TAG, "ignoring user-modified notification at " + i); 930 } 931 } 932 } else { 933 if (DEBUG) Slog.d(TAG, "ignoring notification being held by user at " + i); 934 } 935 } 936 } 937 938 protected abstract void haltTicker(); 939 protected abstract void setAreThereNotifications(); 940 protected abstract void updateNotificationIcons(); 941 protected abstract void tick(IBinder key, StatusBarNotification n, boolean firstTime); 942 protected abstract void updateExpandedViewPos(int expandedPosition); 943 protected abstract int getExpandedViewMaxHeight(); 944 protected abstract boolean shouldDisableNavbarGestures(); 945 946 protected boolean isTopNotification(ViewGroup parent, NotificationData.Entry entry) { 947 return parent != null && parent.indexOfChild(entry.row) == 0; 948 } 949 950 public void updateNotification(IBinder key, StatusBarNotification notification) { 951 if (DEBUG) Slog.d(TAG, "updateNotification(" + key + " -> " + notification + ")"); 952 953 final NotificationData.Entry oldEntry = mNotificationData.findByKey(key); 954 if (oldEntry == null) { 955 Slog.w(TAG, "updateNotification for unknown key: " + key); 956 return; 957 } 958 959 final StatusBarNotification oldNotification = oldEntry.notification; 960 961 // XXX: modify when we do something more intelligent with the two content views 962 final RemoteViews oldContentView = oldNotification.notification.contentView; 963 final RemoteViews contentView = notification.notification.contentView; 964 final RemoteViews oldBigContentView = oldNotification.notification.bigContentView; 965 final RemoteViews bigContentView = notification.notification.bigContentView; 966 967 if (DEBUG) { 968 Slog.d(TAG, "old notification: when=" + oldNotification.notification.when 969 + " ongoing=" + oldNotification.isOngoing() 970 + " expanded=" + oldEntry.expanded 971 + " contentView=" + oldContentView 972 + " bigContentView=" + oldBigContentView 973 + " rowParent=" + oldEntry.row.getParent()); 974 Slog.d(TAG, "new notification: when=" + notification.notification.when 975 + " ongoing=" + oldNotification.isOngoing() 976 + " contentView=" + contentView 977 + " bigContentView=" + bigContentView); 978 } 979 980 // Can we just reapply the RemoteViews in place? If when didn't change, the order 981 // didn't change. 982 983 // 1U is never null 984 boolean contentsUnchanged = oldEntry.expanded != null 985 && contentView.getPackage() != null 986 && oldContentView.getPackage() != null 987 && oldContentView.getPackage().equals(contentView.getPackage()) 988 && oldContentView.getLayoutId() == contentView.getLayoutId(); 989 // large view may be null 990 boolean bigContentsUnchanged = 991 (oldEntry.getLargeView() == null && bigContentView == null) 992 || ((oldEntry.getLargeView() != null && bigContentView != null) 993 && bigContentView.getPackage() != null 994 && oldBigContentView.getPackage() != null 995 && oldBigContentView.getPackage().equals(bigContentView.getPackage()) 996 && oldBigContentView.getLayoutId() == bigContentView.getLayoutId()); 997 ViewGroup rowParent = (ViewGroup) oldEntry.row.getParent(); 998 boolean orderUnchanged = notification.notification.when==oldNotification.notification.when 999 && notification.score == oldNotification.score; 1000 // score now encompasses/supersedes isOngoing() 1001 1002 boolean updateTicker = notification.notification.tickerText != null 1003 && !TextUtils.equals(notification.notification.tickerText, 1004 oldEntry.notification.notification.tickerText); 1005 boolean isTopAnyway = isTopNotification(rowParent, oldEntry); 1006 if (contentsUnchanged && bigContentsUnchanged && (orderUnchanged || isTopAnyway)) { 1007 if (DEBUG) Slog.d(TAG, "reusing notification for key: " + key); 1008 oldEntry.notification = notification; 1009 try { 1010 // Reapply the RemoteViews 1011 contentView.reapply(mContext, oldEntry.expanded, mOnClickHandler); 1012 if (bigContentView != null && oldEntry.getLargeView() != null) { 1013 bigContentView.reapply(mContext, oldEntry.getLargeView(), mOnClickHandler); 1014 } 1015 // update the contentIntent 1016 final PendingIntent contentIntent = notification.notification.contentIntent; 1017 if (contentIntent != null) { 1018 final View.OnClickListener listener = makeClicker(contentIntent, 1019 notification.pkg, notification.tag, notification.id); 1020 oldEntry.content.setOnClickListener(listener); 1021 } else { 1022 oldEntry.content.setOnClickListener(null); 1023 } 1024 // Update the icon. 1025 final StatusBarIcon ic = new StatusBarIcon(notification.pkg, 1026 notification.user, 1027 notification.notification.icon, notification.notification.iconLevel, 1028 notification.notification.number, 1029 notification.notification.tickerText); 1030 if (!oldEntry.icon.set(ic)) { 1031 handleNotificationError(key, notification, "Couldn't update icon: " + ic); 1032 return; 1033 } 1034 updateExpansionStates(); 1035 } 1036 catch (RuntimeException e) { 1037 // It failed to add cleanly. Log, and remove the view from the panel. 1038 Slog.w(TAG, "Couldn't reapply views for package " + contentView.getPackage(), e); 1039 removeNotificationViews(key); 1040 addNotificationViews(key, notification); 1041 } 1042 } else { 1043 if (DEBUG) Slog.d(TAG, "not reusing notification for key: " + key); 1044 if (DEBUG) Slog.d(TAG, "contents was " + (contentsUnchanged ? "unchanged" : "changed")); 1045 if (DEBUG) Slog.d(TAG, "order was " + (orderUnchanged ? "unchanged" : "changed")); 1046 if (DEBUG) Slog.d(TAG, "notification is " + (isTopAnyway ? "top" : "not top")); 1047 final boolean wasExpanded = oldEntry.userExpanded(); 1048 removeNotificationViews(key); 1049 addNotificationViews(key, notification); 1050 if (wasExpanded) { 1051 final NotificationData.Entry newEntry = mNotificationData.findByKey(key); 1052 expandView(newEntry, true); 1053 newEntry.setUserExpanded(true); 1054 } 1055 } 1056 1057 // Update the veto button accordingly (and as a result, whether this row is 1058 // swipe-dismissable) 1059 updateNotificationVetoButton(oldEntry.row, notification); 1060 1061 // Is this for you? 1062 boolean isForCurrentUser = notificationIsForCurrentUser(notification); 1063 if (DEBUG) Slog.d(TAG, "notification is " + (isForCurrentUser ? "" : "not ") + "for you"); 1064 1065 // Restart the ticker if it's still running 1066 if (updateTicker && isForCurrentUser) { 1067 haltTicker(); 1068 tick(key, notification, false); 1069 } 1070 1071 // Recalculate the position of the sliding windows and the titles. 1072 setAreThereNotifications(); 1073 updateExpandedViewPos(EXPANDED_LEAVE_ALONE); 1074 1075 // See if we need to update the intruder. 1076 if (ENABLE_INTRUDERS && oldNotification == mCurrentlyIntrudingNotification) { 1077 if (DEBUG) Slog.d(TAG, "updating the current intruder:" + notification); 1078 // XXX: this is a hack for Alarms. The real implementation will need to *update* 1079 // the intruder. 1080 if (notification.notification.fullScreenIntent == null) { // TODO(dsandler): consistent logic with add() 1081 if (DEBUG) Slog.d(TAG, "no longer intrudes!"); 1082 mHandler.sendEmptyMessage(MSG_HIDE_INTRUDER); 1083 } 1084 } 1085 } 1086 1087 // Q: What kinds of notifications should show during setup? 1088 // A: Almost none! Only things coming from the system (package is "android") that also 1089 // have special "kind" tags marking them as relevant for setup (see below). 1090 protected boolean showNotificationEvenIfUnprovisioned(StatusBarNotification sbn) { 1091 if ("android".equals(sbn.pkg)) { 1092 if (sbn.notification.kind != null) { 1093 for (String aKind : sbn.notification.kind) { 1094 // IME switcher, created by InputMethodManagerService 1095 if ("android.system.imeswitcher".equals(aKind)) return true; 1096 // OTA availability & errors, created by SystemUpdateService 1097 if ("android.system.update".equals(aKind)) return true; 1098 } 1099 } 1100 } 1101 return false; 1102 } 1103 1104 public boolean inKeyguardRestrictedInputMode() { 1105 KeyguardManager km = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE); 1106 return km.inKeyguardRestrictedInputMode(); 1107 } 1108} 1109