BaseStatusBar.java revision 80343f646f9686528212f82163a77ef48e30f4c3
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.recent.RecentTasksLoader; 29import com.android.systemui.recent.RecentsActivity; 30import com.android.systemui.recent.TaskDescription; 31import com.android.systemui.statusbar.policy.NotificationRowLayout; 32import com.android.systemui.statusbar.tablet.StatusBarPanel; 33 34import android.app.ActivityManager; 35import android.app.ActivityManagerNative; 36import android.app.ActivityOptions; 37import android.app.KeyguardManager; 38import android.app.PendingIntent; 39import android.app.Service; 40import android.app.TaskStackBuilder; 41import android.content.ActivityNotFoundException; 42import android.content.BroadcastReceiver; 43import android.content.Context; 44import android.content.Intent; 45import android.content.IntentFilter; 46import android.content.pm.ApplicationInfo; 47import android.content.pm.PackageManager.NameNotFoundException; 48import android.content.res.Configuration; 49import android.content.res.Resources; 50import android.database.ContentObserver; 51import android.graphics.Bitmap; 52import android.graphics.Paint; 53import android.graphics.Rect; 54import android.net.Uri; 55import android.os.Build; 56import android.os.Handler; 57import android.os.IBinder; 58import android.os.Message; 59import android.os.RemoteException; 60import android.os.ServiceManager; 61import android.os.UserHandle; 62import android.provider.Settings; 63import android.text.TextUtils; 64import android.util.DisplayMetrics; 65import android.util.Log; 66import android.util.Slog; 67import android.view.Display; 68import android.view.IWindowManager; 69import android.view.LayoutInflater; 70import android.view.MenuItem; 71import android.view.MotionEvent; 72import android.view.View; 73import android.view.ViewGroup; 74import android.view.ViewGroup.LayoutParams; 75import android.view.WindowManager; 76import android.view.WindowManagerGlobal; 77import android.widget.ImageView; 78import android.widget.LinearLayout; 79import android.widget.PopupMenu; 80import android.widget.RemoteViews; 81import android.widget.TextView; 82 83import java.util.ArrayList; 84import java.util.List; 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 432 protected void updateSearchPanel() { 433 // Search Panel 434 boolean visible = false; 435 if (mSearchPanelView != null) { 436 visible = mSearchPanelView.isShowing(); 437 mWindowManager.removeView(mSearchPanelView); 438 } 439 440 // Provide SearchPanel with a temporary parent to allow layout params to work. 441 LinearLayout tmpRoot = new LinearLayout(mContext); 442 mSearchPanelView = (SearchPanelView) LayoutInflater.from(mContext).inflate( 443 R.layout.status_bar_search_panel, tmpRoot, false); 444 mSearchPanelView.setOnTouchListener( 445 new TouchOutsideListener(MSG_CLOSE_SEARCH_PANEL, mSearchPanelView)); 446 mSearchPanelView.setVisibility(View.GONE); 447 448 WindowManager.LayoutParams lp = getSearchLayoutParams(mSearchPanelView.getLayoutParams()); 449 450 mWindowManager.addView(mSearchPanelView, lp); 451 mSearchPanelView.setBar(this); 452 if (visible) { 453 mSearchPanelView.show(true, false); 454 } 455 } 456 457 protected H createHandler() { 458 return new H(); 459 } 460 461 static void sendCloseSystemWindows(Context context, String reason) { 462 if (ActivityManagerNative.isSystemReady()) { 463 try { 464 ActivityManagerNative.getDefault().closeSystemDialogs(reason); 465 } catch (RemoteException e) { 466 } 467 } 468 } 469 470 protected abstract View getStatusBarView(); 471 472 protected void toggleRecentsActivity() { 473 try { 474 475 TaskDescription firstTask = RecentTasksLoader.getInstance(mContext).getFirstTask(); 476 477 Intent intent = new Intent(RecentsActivity.TOGGLE_RECENTS_INTENT); 478 intent.setClassName("com.android.systemui", 479 "com.android.systemui.recent.RecentsActivity"); 480 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK 481 | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); 482 483 if (firstTask == null) { 484 ActivityOptions opts = ActivityOptions.makeCustomAnimation(mContext, 485 R.anim.recents_launch_from_launcher_enter, 486 R.anim.recents_launch_from_launcher_exit); 487 mContext.startActivityAsUser(intent, opts.toBundle(), new UserHandle( 488 UserHandle.USER_CURRENT)); 489 } else { 490 Bitmap first = firstTask.getThumbnail(); 491 final Resources res = mContext.getResources(); 492 493 float thumbWidth = res 494 .getDimensionPixelSize(R.dimen.status_bar_recents_thumbnail_width); 495 float thumbHeight = res 496 .getDimensionPixelSize(R.dimen.status_bar_recents_thumbnail_height); 497 if (first == null) { 498 throw new RuntimeException("Recents thumbnail is null"); 499 } 500 if (first.getWidth() != thumbWidth || first.getHeight() != thumbHeight) { 501 first = Bitmap.createScaledBitmap(first, (int) thumbWidth, (int) thumbHeight, 502 true); 503 if (first == null) { 504 throw new RuntimeException("Recents thumbnail is null"); 505 } 506 } 507 508 509 DisplayMetrics dm = new DisplayMetrics(); 510 mDisplay.getMetrics(dm); 511 // calculate it here, but consider moving it elsewhere 512 // first, determine which orientation you're in. 513 // todo: move the system_bar layouts to sw600dp ? 514 final Configuration config = res.getConfiguration(); 515 int x, y; 516 517 if (config.orientation == Configuration.ORIENTATION_PORTRAIT) { 518 float appLabelLeftMargin = res 519 .getDimensionPixelSize(R.dimen.status_bar_recents_app_label_left_margin); 520 float appLabelWidth = res 521 .getDimensionPixelSize(R.dimen.status_bar_recents_app_label_width); 522 float thumbLeftMargin = res 523 .getDimensionPixelSize(R.dimen.status_bar_recents_thumbnail_left_margin); 524 float thumbBgPadding = res 525 .getDimensionPixelSize(R.dimen.status_bar_recents_thumbnail_bg_padding); 526 527 float width = appLabelLeftMargin + 528 +appLabelWidth 529 + thumbLeftMargin 530 + thumbWidth 531 + 2 * thumbBgPadding; 532 533 x = (int) ((dm.widthPixels - width) / 2f + appLabelLeftMargin + appLabelWidth 534 + thumbBgPadding + thumbLeftMargin); 535 y = (int) (dm.heightPixels 536 - res.getDimensionPixelSize(R.dimen.status_bar_recents_thumbnail_height) - thumbBgPadding); 537 } else { // if (config.orientation == Configuration.ORIENTATION_LANDSCAPE) { 538 float thumbTopMargin = res 539 .getDimensionPixelSize(R.dimen.status_bar_recents_thumbnail_top_margin); 540 float thumbBgPadding = res 541 .getDimensionPixelSize(R.dimen.status_bar_recents_thumbnail_bg_padding); 542 float textPadding = res 543 .getDimensionPixelSize(R.dimen.status_bar_recents_text_description_padding); 544 float labelTextSize = res 545 .getDimensionPixelSize(R.dimen.status_bar_recents_app_label_text_size); 546 Paint p = new Paint(); 547 p.setTextSize(labelTextSize); 548 float labelTextHeight = p.getFontMetricsInt().bottom 549 - p.getFontMetricsInt().top; 550 float descriptionTextSize = res 551 .getDimensionPixelSize(R.dimen.status_bar_recents_app_description_text_size); 552 p.setTextSize(descriptionTextSize); 553 float descriptionTextHeight = p.getFontMetricsInt().bottom 554 - p.getFontMetricsInt().top; 555 556 float statusBarHeight = res 557 .getDimensionPixelSize(com.android.internal.R.dimen.status_bar_height); 558 float recentsItemTopPadding = statusBarHeight; 559 560 float height = thumbTopMargin 561 + thumbHeight 562 + 2 * thumbBgPadding + textPadding + labelTextHeight 563 + recentsItemTopPadding + textPadding + descriptionTextHeight; 564 float recentsItemRightPadding = res 565 .getDimensionPixelSize(R.dimen.status_bar_recents_item_padding); 566 float recentsScrollViewRightPadding = res 567 .getDimensionPixelSize(R.dimen.status_bar_recents_right_glow_margin); 568 x = (int) (dm.widthPixels - res 569 .getDimensionPixelSize(R.dimen.status_bar_recents_thumbnail_width) 570 - thumbBgPadding - recentsItemRightPadding - recentsScrollViewRightPadding); 571 y = (int) ((dm.heightPixels - statusBarHeight - height) / 2f + thumbTopMargin 572 + recentsItemTopPadding + thumbBgPadding + statusBarHeight); 573 } 574 575 ActivityOptions opts = ActivityOptions.makeThumbnailScaleDownAnimation( 576 getStatusBarView(), 577 first, x, y, 578 new ActivityOptions.OnAnimationStartedListener() { 579 public void onAnimationStarted() { 580 Intent intent = new Intent(RecentsActivity.WINDOW_ANIMATION_START_INTENT); 581 intent.setPackage("com.android.systemui"); 582 mContext.sendBroadcastAsUser(intent, new UserHandle(UserHandle.USER_CURRENT)); 583 } 584 }); 585 intent.putExtra(RecentsActivity.WAITING_FOR_WINDOW_ANIMATION_PARAM, true); 586 mContext.startActivityAsUser(intent, opts.toBundle(), new UserHandle( 587 UserHandle.USER_CURRENT)); 588 } 589 return; 590 } catch (ActivityNotFoundException e) { 591 Log.e(TAG, "Failed to launch RecentAppsIntent", e); 592 } 593 } 594 595 protected View.OnTouchListener mRecentsPreloadOnTouchListener = new View.OnTouchListener() { 596 // additional optimization when we have software system buttons - start loading the recent 597 // tasks on touch down 598 @Override 599 public boolean onTouch(View v, MotionEvent event) { 600 int action = event.getAction() & MotionEvent.ACTION_MASK; 601 if (action == MotionEvent.ACTION_DOWN) { 602 preloadRecentTasksList(); 603 } else if (action == MotionEvent.ACTION_CANCEL) { 604 cancelPreloadingRecentTasksList(); 605 } else if (action == MotionEvent.ACTION_UP) { 606 if (!v.isPressed()) { 607 cancelPreloadingRecentTasksList(); 608 } 609 610 } 611 return false; 612 } 613 }; 614 615 protected void preloadRecentTasksList() { 616 if (DEBUG) Slog.d(TAG, "preloading recents"); 617 Intent intent = new Intent(RecentsActivity.PRELOAD_INTENT); 618 intent.setClassName("com.android.systemui", 619 "com.android.systemui.recent.RecentsPreloadReceiver"); 620 mContext.sendBroadcastAsUser(intent, new UserHandle(UserHandle.USER_CURRENT)); 621 622 RecentTasksLoader.getInstance(mContext).preloadFirstTask(); 623 } 624 625 protected void cancelPreloadingRecentTasksList() { 626 if (DEBUG) Slog.d(TAG, "cancel preloading recents"); 627 Intent intent = new Intent(RecentsActivity.CANCEL_PRELOAD_INTENT); 628 intent.setClassName("com.android.systemui", 629 "com.android.systemui.recent.RecentsPreloadReceiver"); 630 mContext.sendBroadcastAsUser(intent, new UserHandle(UserHandle.USER_CURRENT)); 631 632 RecentTasksLoader.getInstance(mContext).cancelPreloadingFirstTask(); 633 } 634 635 protected class H extends Handler { 636 public void handleMessage(Message m) { 637 Intent intent; 638 switch (m.what) { 639 case MSG_TOGGLE_RECENTS_PANEL: 640 if (DEBUG) Slog.d(TAG, "toggle recents panel"); 641 toggleRecentsActivity(); 642 break; 643 case MSG_CLOSE_RECENTS_PANEL: 644 if (DEBUG) Slog.d(TAG, "closing recents panel"); 645 intent = new Intent(RecentsActivity.CLOSE_RECENTS_INTENT); 646 intent.setPackage("com.android.systemui"); 647 mContext.sendBroadcastAsUser(intent, new UserHandle(UserHandle.USER_CURRENT)); 648 break; 649 case MSG_PRELOAD_RECENT_APPS: 650 preloadRecentTasksList(); 651 break; 652 case MSG_CANCEL_PRELOAD_RECENT_APPS: 653 cancelPreloadingRecentTasksList(); 654 break; 655 case MSG_OPEN_SEARCH_PANEL: 656 if (DEBUG) Slog.d(TAG, "opening search panel"); 657 if (mSearchPanelView != null && mSearchPanelView.isAssistantAvailable()) { 658 mSearchPanelView.show(true, true); 659 } 660 break; 661 case MSG_CLOSE_SEARCH_PANEL: 662 if (DEBUG) Slog.d(TAG, "closing search panel"); 663 if (mSearchPanelView != null && mSearchPanelView.isShowing()) { 664 mSearchPanelView.show(false, true); 665 } 666 break; 667 } 668 } 669 } 670 671 public class TouchOutsideListener implements View.OnTouchListener { 672 private int mMsg; 673 private StatusBarPanel mPanel; 674 675 public TouchOutsideListener(int msg, StatusBarPanel panel) { 676 mMsg = msg; 677 mPanel = panel; 678 } 679 680 public boolean onTouch(View v, MotionEvent ev) { 681 final int action = ev.getAction(); 682 if (action == MotionEvent.ACTION_OUTSIDE 683 || (action == MotionEvent.ACTION_DOWN 684 && !mPanel.isInContentArea((int)ev.getX(), (int)ev.getY()))) { 685 mHandler.removeMessages(mMsg); 686 mHandler.sendEmptyMessage(mMsg); 687 return true; 688 } 689 return false; 690 } 691 } 692 693 protected void workAroundBadLayerDrawableOpacity(View v) { 694 } 695 696 protected boolean inflateViews(NotificationData.Entry entry, ViewGroup parent) { 697 int minHeight = 698 mContext.getResources().getDimensionPixelSize(R.dimen.notification_min_height); 699 int maxHeight = 700 mContext.getResources().getDimensionPixelSize(R.dimen.notification_max_height); 701 StatusBarNotification sbn = entry.notification; 702 RemoteViews oneU = sbn.notification.contentView; 703 RemoteViews large = sbn.notification.bigContentView; 704 if (oneU == null) { 705 return false; 706 } 707 708 // create the row view 709 LayoutInflater inflater = (LayoutInflater)mContext.getSystemService( 710 Context.LAYOUT_INFLATER_SERVICE); 711 View row = inflater.inflate(R.layout.status_bar_notification_row, parent, false); 712 713 // for blaming (see SwipeHelper.setLongPressListener) 714 row.setTag(sbn.pkg); 715 716 workAroundBadLayerDrawableOpacity(row); 717 View vetoButton = updateNotificationVetoButton(row, sbn); 718 vetoButton.setContentDescription(mContext.getString( 719 R.string.accessibility_remove_notification)); 720 721 // NB: the large icon is now handled entirely by the template 722 723 // bind the click event to the content area 724 ViewGroup content = (ViewGroup)row.findViewById(R.id.content); 725 ViewGroup adaptive = (ViewGroup)row.findViewById(R.id.adaptive); 726 727 content.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS); 728 729 PendingIntent contentIntent = sbn.notification.contentIntent; 730 if (contentIntent != null) { 731 final View.OnClickListener listener = new NotificationClicker(contentIntent, 732 sbn.pkg, sbn.tag, sbn.id); 733 content.setOnClickListener(listener); 734 } else { 735 content.setOnClickListener(null); 736 } 737 738 // TODO(cwren) normalize variable names with those in updateNotification 739 View expandedOneU = null; 740 View expandedLarge = null; 741 try { 742 expandedOneU = oneU.apply(mContext, adaptive, mOnClickHandler); 743 if (large != null) { 744 expandedLarge = large.apply(mContext, adaptive, mOnClickHandler); 745 } 746 } 747 catch (RuntimeException e) { 748 final String ident = sbn.pkg + "/0x" + Integer.toHexString(sbn.id); 749 Slog.e(TAG, "couldn't inflate view for notification " + ident, e); 750 return false; 751 } 752 753 if (expandedOneU != null) { 754 SizeAdaptiveLayout.LayoutParams params = 755 new SizeAdaptiveLayout.LayoutParams(expandedOneU.getLayoutParams()); 756 params.minHeight = minHeight; 757 params.maxHeight = minHeight; 758 adaptive.addView(expandedOneU, params); 759 } 760 if (expandedLarge != null) { 761 SizeAdaptiveLayout.LayoutParams params = 762 new SizeAdaptiveLayout.LayoutParams(expandedLarge.getLayoutParams()); 763 params.minHeight = minHeight+1; 764 params.maxHeight = maxHeight; 765 adaptive.addView(expandedLarge, params); 766 } 767 row.setDrawingCacheEnabled(true); 768 769 applyLegacyRowBackground(sbn, content); 770 771 row.setTag(R.id.expandable_tag, Boolean.valueOf(large != null)); 772 773 if (MULTIUSER_DEBUG) { 774 TextView debug = (TextView) row.findViewById(R.id.debug_info); 775 if (debug != null) { 776 debug.setVisibility(View.VISIBLE); 777 debug.setText("U " + entry.notification.getUserId()); 778 } 779 } 780 entry.row = row; 781 entry.content = content; 782 entry.expanded = expandedOneU; 783 entry.setLargeView(expandedLarge); 784 785 return true; 786 } 787 788 public NotificationClicker makeClicker(PendingIntent intent, String pkg, String tag, int id) { 789 return new NotificationClicker(intent, pkg, tag, id); 790 } 791 792 private class NotificationClicker implements View.OnClickListener { 793 private PendingIntent mIntent; 794 private String mPkg; 795 private String mTag; 796 private int mId; 797 798 NotificationClicker(PendingIntent intent, String pkg, String tag, int id) { 799 mIntent = intent; 800 mPkg = pkg; 801 mTag = tag; 802 mId = id; 803 } 804 805 public void onClick(View v) { 806 try { 807 // The intent we are sending is for the application, which 808 // won't have permission to immediately start an activity after 809 // the user switches to home. We know it is safe to do at this 810 // point, so make sure new activity switches are now allowed. 811 ActivityManagerNative.getDefault().resumeAppSwitches(); 812 // Also, notifications can be launched from the lock screen, 813 // so dismiss the lock screen when the activity starts. 814 ActivityManagerNative.getDefault().dismissKeyguardOnNextActivity(); 815 } catch (RemoteException e) { 816 } 817 818 if (mIntent != null) { 819 int[] pos = new int[2]; 820 v.getLocationOnScreen(pos); 821 Intent overlay = new Intent(); 822 overlay.setSourceBounds( 823 new Rect(pos[0], pos[1], pos[0]+v.getWidth(), pos[1]+v.getHeight())); 824 try { 825 mIntent.send(mContext, 0, overlay); 826 } catch (PendingIntent.CanceledException e) { 827 // the stack trace isn't very helpful here. Just log the exception message. 828 Slog.w(TAG, "Sending contentIntent failed: " + e); 829 } 830 831 KeyguardManager kgm = 832 (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE); 833 if (kgm != null) kgm.exitKeyguardSecurely(null); 834 } 835 836 try { 837 mBarService.onNotificationClick(mPkg, mTag, mId); 838 } catch (RemoteException ex) { 839 // system process is dead if we're here. 840 } 841 842 // close the shade if it was open 843 animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE); 844 visibilityChanged(false); 845 846 // If this click was on the intruder alert, hide that instead 847// mHandler.sendEmptyMessage(MSG_HIDE_INTRUDER); 848 } 849 } 850 /** 851 * The LEDs are turned o)ff when the notification panel is shown, even just a little bit. 852 * This was added last-minute and is inconsistent with the way the rest of the notifications 853 * are handled, because the notification isn't really cancelled. The lights are just 854 * turned off. If any other notifications happen, the lights will turn back on. Steve says 855 * this is what he wants. (see bug 1131461) 856 */ 857 protected void visibilityChanged(boolean visible) { 858 if (mPanelSlightlyVisible != visible) { 859 mPanelSlightlyVisible = visible; 860 try { 861 mBarService.onPanelRevealed(); 862 } catch (RemoteException ex) { 863 // Won't fail unless the world has ended. 864 } 865 } 866 } 867 868 /** 869 * Cancel this notification and tell the StatusBarManagerService / NotificationManagerService 870 * about the failure. 871 * 872 * WARNING: this will call back into us. Don't hold any locks. 873 */ 874 void handleNotificationError(IBinder key, StatusBarNotification n, String message) { 875 removeNotification(key); 876 try { 877 mBarService.onNotificationError(n.pkg, n.tag, n.id, n.uid, n.initialPid, message); 878 } catch (RemoteException ex) { 879 // The end is nigh. 880 } 881 } 882 883 protected StatusBarNotification removeNotificationViews(IBinder key) { 884 NotificationData.Entry entry = mNotificationData.remove(key); 885 if (entry == null) { 886 Slog.w(TAG, "removeNotification for unknown key: " + key); 887 return null; 888 } 889 // Remove the expanded view. 890 ViewGroup rowParent = (ViewGroup)entry.row.getParent(); 891 if (rowParent != null) rowParent.removeView(entry.row); 892 updateExpansionStates(); 893 updateNotificationIcons(); 894 895 return entry.notification; 896 } 897 898 protected StatusBarIconView addNotificationViews(IBinder key, 899 StatusBarNotification notification) { 900 if (DEBUG) { 901 Slog.d(TAG, "addNotificationViews(key=" + key + ", notification=" + notification); 902 } 903 // Construct the icon. 904 final StatusBarIconView iconView = new StatusBarIconView(mContext, 905 notification.pkg + "/0x" + Integer.toHexString(notification.id), 906 notification.notification); 907 iconView.setScaleType(ImageView.ScaleType.CENTER_INSIDE); 908 909 final StatusBarIcon ic = new StatusBarIcon(notification.pkg, 910 notification.user, 911 notification.notification.icon, 912 notification.notification.iconLevel, 913 notification.notification.number, 914 notification.notification.tickerText); 915 if (!iconView.set(ic)) { 916 handleNotificationError(key, notification, "Couldn't create icon: " + ic); 917 return null; 918 } 919 // Construct the expanded view. 920 NotificationData.Entry entry = new NotificationData.Entry(key, notification, iconView); 921 if (!inflateViews(entry, mPile)) { 922 handleNotificationError(key, notification, "Couldn't expand RemoteViews for: " 923 + notification); 924 return null; 925 } 926 927 // Add the expanded view and icon. 928 int pos = mNotificationData.add(entry); 929 if (DEBUG) { 930 Slog.d(TAG, "addNotificationViews: added at " + pos); 931 } 932 updateExpansionStates(); 933 updateNotificationIcons(); 934 935 return iconView; 936 } 937 938 protected boolean expandView(NotificationData.Entry entry, boolean expand) { 939 int rowHeight = 940 mContext.getResources().getDimensionPixelSize(R.dimen.notification_row_min_height); 941 ViewGroup.LayoutParams lp = entry.row.getLayoutParams(); 942 if (entry.expandable() && expand) { 943 if (DEBUG) Slog.d(TAG, "setting expanded row height to WRAP_CONTENT"); 944 lp.height = ViewGroup.LayoutParams.WRAP_CONTENT; 945 } else { 946 if (DEBUG) Slog.d(TAG, "setting collapsed row height to " + rowHeight); 947 lp.height = rowHeight; 948 } 949 entry.row.setLayoutParams(lp); 950 return expand; 951 } 952 953 protected void updateExpansionStates() { 954 int N = mNotificationData.size(); 955 for (int i = 0; i < N; i++) { 956 NotificationData.Entry entry = mNotificationData.get(i); 957 if (!entry.userLocked()) { 958 if (i == (N-1)) { 959 if (DEBUG) Slog.d(TAG, "expanding top notification at " + i); 960 expandView(entry, true); 961 } else { 962 if (!entry.userExpanded()) { 963 if (DEBUG) Slog.d(TAG, "collapsing notification at " + i); 964 expandView(entry, false); 965 } else { 966 if (DEBUG) Slog.d(TAG, "ignoring user-modified notification at " + i); 967 } 968 } 969 } else { 970 if (DEBUG) Slog.d(TAG, "ignoring notification being held by user at " + i); 971 } 972 } 973 } 974 975 protected abstract void haltTicker(); 976 protected abstract void setAreThereNotifications(); 977 protected abstract void updateNotificationIcons(); 978 protected abstract void tick(IBinder key, StatusBarNotification n, boolean firstTime); 979 protected abstract void updateExpandedViewPos(int expandedPosition); 980 protected abstract int getExpandedViewMaxHeight(); 981 protected abstract boolean shouldDisableNavbarGestures(); 982 983 protected boolean isTopNotification(ViewGroup parent, NotificationData.Entry entry) { 984 return parent != null && parent.indexOfChild(entry.row) == 0; 985 } 986 987 public void updateNotification(IBinder key, StatusBarNotification notification) { 988 if (DEBUG) Slog.d(TAG, "updateNotification(" + key + " -> " + notification + ")"); 989 990 final NotificationData.Entry oldEntry = mNotificationData.findByKey(key); 991 if (oldEntry == null) { 992 Slog.w(TAG, "updateNotification for unknown key: " + key); 993 return; 994 } 995 996 final StatusBarNotification oldNotification = oldEntry.notification; 997 998 // XXX: modify when we do something more intelligent with the two content views 999 final RemoteViews oldContentView = oldNotification.notification.contentView; 1000 final RemoteViews contentView = notification.notification.contentView; 1001 final RemoteViews oldBigContentView = oldNotification.notification.bigContentView; 1002 final RemoteViews bigContentView = notification.notification.bigContentView; 1003 1004 if (DEBUG) { 1005 Slog.d(TAG, "old notification: when=" + oldNotification.notification.when 1006 + " ongoing=" + oldNotification.isOngoing() 1007 + " expanded=" + oldEntry.expanded 1008 + " contentView=" + oldContentView 1009 + " bigContentView=" + oldBigContentView 1010 + " rowParent=" + oldEntry.row.getParent()); 1011 Slog.d(TAG, "new notification: when=" + notification.notification.when 1012 + " ongoing=" + oldNotification.isOngoing() 1013 + " contentView=" + contentView 1014 + " bigContentView=" + bigContentView); 1015 } 1016 1017 // Can we just reapply the RemoteViews in place? If when didn't change, the order 1018 // didn't change. 1019 1020 // 1U is never null 1021 boolean contentsUnchanged = oldEntry.expanded != null 1022 && contentView.getPackage() != null 1023 && oldContentView.getPackage() != null 1024 && oldContentView.getPackage().equals(contentView.getPackage()) 1025 && oldContentView.getLayoutId() == contentView.getLayoutId(); 1026 // large view may be null 1027 boolean bigContentsUnchanged = 1028 (oldEntry.getLargeView() == null && bigContentView == null) 1029 || ((oldEntry.getLargeView() != null && bigContentView != null) 1030 && bigContentView.getPackage() != null 1031 && oldBigContentView.getPackage() != null 1032 && oldBigContentView.getPackage().equals(bigContentView.getPackage()) 1033 && oldBigContentView.getLayoutId() == bigContentView.getLayoutId()); 1034 ViewGroup rowParent = (ViewGroup) oldEntry.row.getParent(); 1035 boolean orderUnchanged = notification.notification.when==oldNotification.notification.when 1036 && notification.score == oldNotification.score; 1037 // score now encompasses/supersedes isOngoing() 1038 1039 boolean updateTicker = notification.notification.tickerText != null 1040 && !TextUtils.equals(notification.notification.tickerText, 1041 oldEntry.notification.notification.tickerText); 1042 boolean isTopAnyway = isTopNotification(rowParent, oldEntry); 1043 if (contentsUnchanged && bigContentsUnchanged && (orderUnchanged || isTopAnyway)) { 1044 if (DEBUG) Slog.d(TAG, "reusing notification for key: " + key); 1045 oldEntry.notification = notification; 1046 try { 1047 // Reapply the RemoteViews 1048 contentView.reapply(mContext, oldEntry.expanded, mOnClickHandler); 1049 if (bigContentView != null && oldEntry.getLargeView() != null) { 1050 bigContentView.reapply(mContext, oldEntry.getLargeView(), mOnClickHandler); 1051 } 1052 // update the contentIntent 1053 final PendingIntent contentIntent = notification.notification.contentIntent; 1054 if (contentIntent != null) { 1055 final View.OnClickListener listener = makeClicker(contentIntent, 1056 notification.pkg, notification.tag, notification.id); 1057 oldEntry.content.setOnClickListener(listener); 1058 } else { 1059 oldEntry.content.setOnClickListener(null); 1060 } 1061 // Update the icon. 1062 final StatusBarIcon ic = new StatusBarIcon(notification.pkg, 1063 notification.user, 1064 notification.notification.icon, notification.notification.iconLevel, 1065 notification.notification.number, 1066 notification.notification.tickerText); 1067 if (!oldEntry.icon.set(ic)) { 1068 handleNotificationError(key, notification, "Couldn't update icon: " + ic); 1069 return; 1070 } 1071 updateExpansionStates(); 1072 } 1073 catch (RuntimeException e) { 1074 // It failed to add cleanly. Log, and remove the view from the panel. 1075 Slog.w(TAG, "Couldn't reapply views for package " + contentView.getPackage(), e); 1076 removeNotificationViews(key); 1077 addNotificationViews(key, notification); 1078 } 1079 } else { 1080 if (DEBUG) Slog.d(TAG, "not reusing notification for key: " + key); 1081 if (DEBUG) Slog.d(TAG, "contents was " + (contentsUnchanged ? "unchanged" : "changed")); 1082 if (DEBUG) Slog.d(TAG, "order was " + (orderUnchanged ? "unchanged" : "changed")); 1083 if (DEBUG) Slog.d(TAG, "notification is " + (isTopAnyway ? "top" : "not top")); 1084 final boolean wasExpanded = oldEntry.userExpanded(); 1085 removeNotificationViews(key); 1086 addNotificationViews(key, notification); 1087 if (wasExpanded) { 1088 final NotificationData.Entry newEntry = mNotificationData.findByKey(key); 1089 expandView(newEntry, true); 1090 newEntry.setUserExpanded(true); 1091 } 1092 } 1093 1094 // Update the veto button accordingly (and as a result, whether this row is 1095 // swipe-dismissable) 1096 updateNotificationVetoButton(oldEntry.row, notification); 1097 1098 // Is this for you? 1099 boolean isForCurrentUser = notificationIsForCurrentUser(notification); 1100 if (DEBUG) Slog.d(TAG, "notification is " + (isForCurrentUser ? "" : "not ") + "for you"); 1101 1102 // Restart the ticker if it's still running 1103 if (updateTicker && isForCurrentUser) { 1104 haltTicker(); 1105 tick(key, notification, false); 1106 } 1107 1108 // Recalculate the position of the sliding windows and the titles. 1109 setAreThereNotifications(); 1110 updateExpandedViewPos(EXPANDED_LEAVE_ALONE); 1111 1112 // See if we need to update the intruder. 1113 if (ENABLE_INTRUDERS && oldNotification == mCurrentlyIntrudingNotification) { 1114 if (DEBUG) Slog.d(TAG, "updating the current intruder:" + notification); 1115 // XXX: this is a hack for Alarms. The real implementation will need to *update* 1116 // the intruder. 1117 if (notification.notification.fullScreenIntent == null) { // TODO(dsandler): consistent logic with add() 1118 if (DEBUG) Slog.d(TAG, "no longer intrudes!"); 1119 mHandler.sendEmptyMessage(MSG_HIDE_INTRUDER); 1120 } 1121 } 1122 } 1123 1124 // Q: What kinds of notifications should show during setup? 1125 // A: Almost none! Only things coming from the system (package is "android") that also 1126 // have special "kind" tags marking them as relevant for setup (see below). 1127 protected boolean showNotificationEvenIfUnprovisioned(StatusBarNotification sbn) { 1128 if ("android".equals(sbn.pkg)) { 1129 if (sbn.notification.kind != null) { 1130 for (String aKind : sbn.notification.kind) { 1131 // IME switcher, created by InputMethodManagerService 1132 if ("android.system.imeswitcher".equals(aKind)) return true; 1133 // OTA availability & errors, created by SystemUpdateService 1134 if ("android.system.update".equals(aKind)) return true; 1135 } 1136 } 1137 } 1138 return false; 1139 } 1140 1141 public boolean inKeyguardRestrictedInputMode() { 1142 KeyguardManager km = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE); 1143 return km.inKeyguardRestrictedInputMode(); 1144 } 1145} 1146