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