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