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