PhoneStatusBar.java revision 2f2022afa1eb85018368398bd150e9575fc099c9
1/* 2 * Copyright (C) 2010 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.android.systemui.statusbar.phone; 18 19import android.animation.ObjectAnimator; 20import android.app.ActivityManager; 21import android.app.ActivityManagerNative; 22import android.app.Dialog; 23import android.app.KeyguardManager; 24import android.app.Notification; 25import android.app.PendingIntent; 26import android.app.StatusBarManager; 27import android.content.BroadcastReceiver; 28import android.content.Context; 29import android.content.Intent; 30import android.content.IntentFilter; 31import android.content.res.Resources; 32import android.content.res.Configuration; 33import android.graphics.PixelFormat; 34import android.graphics.Rect; 35import android.graphics.drawable.Drawable; 36import android.os.IBinder; 37import android.os.RemoteException; 38import android.os.Handler; 39import android.os.Message; 40import android.os.ServiceManager; 41import android.os.SystemClock; 42import android.provider.Settings; 43import android.text.TextUtils; 44import android.util.DisplayMetrics; 45import android.util.Slog; 46import android.util.Log; 47import android.view.Display; 48import android.view.Gravity; 49import android.view.IWindowManager; 50import android.view.KeyEvent; 51import android.view.LayoutInflater; 52import android.view.MotionEvent; 53import android.view.VelocityTracker; 54import android.view.View; 55import android.view.ViewGroup; 56import android.view.ViewGroup.LayoutParams; 57import android.view.Window; 58import android.view.WindowManager; 59import android.view.WindowManagerImpl; 60import android.view.animation.Animation; 61import android.view.animation.AnimationUtils; 62import android.widget.ImageView; 63import android.widget.LinearLayout; 64import android.widget.RemoteViews; 65import android.widget.ScrollView; 66import android.widget.TextView; 67 68import java.io.FileDescriptor; 69import java.io.PrintWriter; 70import java.util.ArrayList; 71 72import com.android.internal.statusbar.StatusBarIcon; 73import com.android.internal.statusbar.StatusBarNotification; 74 75import com.android.systemui.R; 76import com.android.systemui.recent.RecentTasksLoader; 77import com.android.systemui.recent.RecentsPanelView; 78import com.android.systemui.recent.TaskDescription; 79import com.android.systemui.statusbar.NotificationData; 80import com.android.systemui.statusbar.StatusBar; 81import com.android.systemui.statusbar.StatusBarIconView; 82import com.android.systemui.statusbar.SignalClusterView; 83import com.android.systemui.statusbar.policy.DateView; 84import com.android.systemui.statusbar.policy.BatteryController; 85import com.android.systemui.statusbar.policy.LocationController; 86import com.android.systemui.statusbar.policy.NetworkController; 87import com.android.systemui.statusbar.policy.NotificationRowLayout; 88 89public class PhoneStatusBar extends StatusBar { 90 static final String TAG = "PhoneStatusBar"; 91 public static final boolean DEBUG = false; 92 public static final boolean SPEW = false; 93 public static final boolean DUMPTRUCK = true; // extra dumpsys info 94 95 // additional instrumentation for testing purposes; intended to be left on during development 96 public static final boolean CHATTY = DEBUG; 97 98 public static final String ACTION_STATUSBAR_START 99 = "com.android.internal.policy.statusbar.START"; 100 101 static final int EXPANDED_LEAVE_ALONE = -10000; 102 static final int EXPANDED_FULL_OPEN = -10001; 103 104 private static final int MSG_ANIMATE = 100; 105 private static final int MSG_ANIMATE_REVEAL = 101; 106 private static final int MSG_OPEN_NOTIFICATION_PANEL = 1000; 107 private static final int MSG_CLOSE_NOTIFICATION_PANEL = 1001; 108 private static final int MSG_SHOW_INTRUDER = 1002; 109 private static final int MSG_HIDE_INTRUDER = 1003; 110 private static final int MSG_OPEN_RECENTS_PANEL = 1020; 111 private static final int MSG_CLOSE_RECENTS_PANEL = 1021; 112 113 // will likely move to a resource or other tunable param at some point 114 private static final int INTRUDER_ALERT_DECAY_MS = 10000; 115 116 // fling gesture tuning parameters, scaled to display density 117 private float mSelfExpandVelocityPx; // classic value: 2000px/s 118 private float mSelfCollapseVelocityPx; // classic value: 2000px/s (will be negated to collapse "up") 119 private float mFlingExpandMinVelocityPx; // classic value: 200px/s 120 private float mFlingCollapseMinVelocityPx; // classic value: 200px/s 121 private float mCollapseMinDisplayFraction; // classic value: 0.08 (25px/min(320px,480px) on G1) 122 private float mExpandMinDisplayFraction; // classic value: 0.5 (drag open halfway to expand) 123 private float mFlingGestureMaxXVelocityPx; // classic value: 150px/s 124 125 private float mExpandAccelPx; // classic value: 2000px/s/s 126 private float mCollapseAccelPx; // classic value: 2000px/s/s (will be negated to collapse "up") 127 128 PhoneStatusBarPolicy mIconPolicy; 129 130 // These are no longer handled by the policy, because we need custom strategies for them 131 BatteryController mBatteryController; 132 LocationController mLocationController; 133 NetworkController mNetworkController; 134 135 int mNaturalBarHeight = -1; 136 int mIconSize = -1; 137 int mIconHPadding = -1; 138 Display mDisplay; 139 140 IWindowManager mWindowManager; 141 142 PhoneStatusBarView mStatusBarView; 143 int mPixelFormat; 144 H mHandler = new H(); 145 Object mQueueLock = new Object(); 146 147 // icons 148 LinearLayout mIcons; 149 IconMerger mNotificationIcons; 150 LinearLayout mStatusIcons; 151 152 // expanded notifications 153 Dialog mExpandedDialog; 154 ExpandedView mExpandedView; 155 WindowManager.LayoutParams mExpandedParams; 156 ScrollView mScrollView; 157 View mExpandedContents; 158 // top bar 159 TextView mNoNotificationsTitle; 160 View mClearButton; 161 View mSettingsButton; 162 163 // drag bar 164 CloseDragHandle mCloseView; 165 166 // all notifications 167 NotificationData mNotificationData = new NotificationData(); 168 NotificationRowLayout mPile; 169 170 // position 171 int[] mPositionTmp = new int[2]; 172 boolean mExpanded; 173 boolean mExpandedVisible; 174 175 // the date view 176 DateView mDateView; 177 178 // for immersive activities 179 private View mIntruderAlertView; 180 181 // on-screen navigation buttons 182 private NavigationBarView mNavigationBarView = null; 183 184 // the tracker view 185 TrackingView mTrackingView; 186 WindowManager.LayoutParams mTrackingParams; 187 int mTrackingPosition; // the position of the top of the tracking view. 188 private boolean mPanelSlightlyVisible; 189 190 // ticker 191 private Ticker mTicker; 192 private View mTickerView; 193 private boolean mTicking; 194 195 // Recent apps 196 private RecentsPanelView mRecentsPanel; 197 private RecentTasksLoader mRecentTasksLoader; 198 199 // Tracking finger for opening/closing. 200 int mEdgeBorder; // corresponds to R.dimen.status_bar_edge_ignore 201 boolean mTracking; 202 VelocityTracker mVelocityTracker; 203 204 static final int ANIM_FRAME_DURATION = (1000/60); 205 206 boolean mAnimating; 207 long mCurAnimationTime; 208 float mAnimY; 209 float mAnimVel; 210 float mAnimAccel; 211 long mAnimLastTime; 212 boolean mAnimatingReveal = false; 213 int mViewDelta; 214 int[] mAbsPos = new int[2]; 215 Runnable mPostCollapseCleanup = null; 216 217 218 // for disabling the status bar 219 int mDisabled = 0; 220 221 // tracking calls to View.setSystemUiVisibility() 222 int mSystemUiVisibility = View.SYSTEM_UI_FLAG_VISIBLE; 223 224 DisplayMetrics mDisplayMetrics = new DisplayMetrics(); 225 226 private class ExpandedDialog extends Dialog { 227 ExpandedDialog(Context context) { 228 super(context, com.android.internal.R.style.Theme_Translucent_NoTitleBar); 229 } 230 231 @Override 232 public boolean dispatchKeyEvent(KeyEvent event) { 233 boolean down = event.getAction() == KeyEvent.ACTION_DOWN; 234 switch (event.getKeyCode()) { 235 case KeyEvent.KEYCODE_BACK: 236 if (!down) { 237 animateCollapse(); 238 } 239 return true; 240 } 241 return super.dispatchKeyEvent(event); 242 } 243 } 244 245 @Override 246 public void start() { 247 mDisplay = ((WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE)) 248 .getDefaultDisplay(); 249 250 mWindowManager = IWindowManager.Stub.asInterface( 251 ServiceManager.getService(Context.WINDOW_SERVICE)); 252 253 super.start(); 254 255 addNavigationBar(); 256 257 //addIntruderView(); 258 259 // Lastly, call to the icon policy to install/update all the icons. 260 mIconPolicy = new PhoneStatusBarPolicy(mContext); 261 } 262 263 // ================================================================================ 264 // Constructing the view 265 // ================================================================================ 266 protected View makeStatusBarView() { 267 final Context context = mContext; 268 269 Resources res = context.getResources(); 270 271 mDisplay.getMetrics(mDisplayMetrics); 272 if (DEBUG) { 273 Slog.d(TAG, "makeStatusBarView: mDisplayMetrics=" + mDisplayMetrics); 274 mDisplayMetrics = res.getDisplayMetrics(); 275 Slog.d(TAG, "makeStatusBarView: mDisplayMetrics2=" + mDisplayMetrics); 276 } 277 loadDimens(); 278 279 mIconSize = res.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_icon_size); 280 281 ExpandedView expanded = (ExpandedView)View.inflate(context, 282 R.layout.status_bar_expanded, null); 283 if (DEBUG) { 284 expanded.setBackgroundColor(0x6000FF80); 285 } 286 expanded.mService = this; 287 288 mIntruderAlertView = View.inflate(context, R.layout.intruder_alert, null); 289 mIntruderAlertView.setVisibility(View.GONE); 290 mIntruderAlertView.setClickable(true); 291 292 PhoneStatusBarView sb = (PhoneStatusBarView)View.inflate(context, 293 R.layout.status_bar, null); 294 sb.mService = this; 295 mStatusBarView = sb; 296 297 try { 298 boolean showNav = res.getBoolean(com.android.internal.R.bool.config_showNavigationBar); 299 if (showNav) { 300 mNavigationBarView = 301 (NavigationBarView) View.inflate(context, R.layout.navigation_bar, null); 302 303 mNavigationBarView.setDisabledFlags(mDisabled); 304 } 305 } catch (Resources.NotFoundException ex) { 306 // no nav bar for you 307 } 308 309 // figure out which pixel-format to use for the status bar. 310 mPixelFormat = PixelFormat.OPAQUE; 311 mStatusIcons = (LinearLayout)sb.findViewById(R.id.statusIcons); 312 mNotificationIcons = (IconMerger)sb.findViewById(R.id.notificationIcons); 313 mIcons = (LinearLayout)sb.findViewById(R.id.icons); 314 mTickerView = sb.findViewById(R.id.ticker); 315 316 mExpandedDialog = new ExpandedDialog(context); 317 mExpandedView = expanded; 318 mPile = (NotificationRowLayout)expanded.findViewById(R.id.latestItems); 319 mExpandedContents = mPile; // was: expanded.findViewById(R.id.notificationLinearLayout); 320 mNoNotificationsTitle = (TextView)expanded.findViewById(R.id.noNotificationsTitle); 321 mNoNotificationsTitle.setVisibility(View.GONE); // disabling for now 322 323 mClearButton = expanded.findViewById(R.id.clear_all_button); 324 mClearButton.setOnClickListener(mClearButtonListener); 325 mClearButton.setAlpha(0f); 326 mClearButton.setEnabled(false); 327 mDateView = (DateView)expanded.findViewById(R.id.date); 328 mSettingsButton = expanded.findViewById(R.id.settings_button); 329 mSettingsButton.setOnClickListener(mSettingsButtonListener); 330 mScrollView = (ScrollView)expanded.findViewById(R.id.scroll); 331 332 mTicker = new MyTicker(context, sb); 333 334 TickerView tickerView = (TickerView)sb.findViewById(R.id.tickerText); 335 tickerView.mTicker = mTicker; 336 337 mTrackingView = (TrackingView)View.inflate(context, R.layout.status_bar_tracking, null); 338 mTrackingView.mService = this; 339 mCloseView = (CloseDragHandle)mTrackingView.findViewById(R.id.close); 340 mCloseView.mService = this; 341 342 mEdgeBorder = res.getDimensionPixelSize(R.dimen.status_bar_edge_ignore); 343 344 // set the inital view visibility 345 setAreThereNotifications(); 346 347 // Other icons 348 mLocationController = new LocationController(mContext); // will post a notification 349 mBatteryController = new BatteryController(mContext); 350 mBatteryController.addIconView((ImageView)sb.findViewById(R.id.battery)); 351 mNetworkController = new NetworkController(mContext); 352 final SignalClusterView signalCluster = 353 (SignalClusterView)sb.findViewById(R.id.signal_cluster); 354 mNetworkController.addSignalCluster(signalCluster); 355 signalCluster.setNetworkController(mNetworkController); 356 357 // Recents Panel 358 mRecentTasksLoader = new RecentTasksLoader(context); 359 updateRecentsPanel(); 360 361 // receive broadcasts 362 IntentFilter filter = new IntentFilter(); 363 filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED); 364 filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); 365 filter.addAction(Intent.ACTION_SCREEN_OFF); 366 context.registerReceiver(mBroadcastReceiver, filter); 367 368 return sb; 369 } 370 371 protected WindowManager.LayoutParams getRecentsLayoutParams(LayoutParams layoutParams) { 372 boolean opaque = false; 373 WindowManager.LayoutParams lp = new WindowManager.LayoutParams( 374 layoutParams.width, 375 layoutParams.height, 376 WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL, 377 WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN 378 | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM 379 | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH, 380 (opaque ? PixelFormat.OPAQUE : PixelFormat.TRANSLUCENT)); 381 if (ActivityManager.isHighEndGfx(mDisplay)) { 382 lp.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED; 383 } 384 lp.gravity = Gravity.BOTTOM | Gravity.LEFT; 385 lp.setTitle("RecentsPanel"); 386 lp.windowAnimations = R.style.Animation_RecentPanel; 387 lp.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED 388 | WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING; 389 return lp; 390 } 391 392 protected void updateRecentsPanel() { 393 // Recents Panel 394 boolean visible = false; 395 ArrayList<TaskDescription> recentTasksList = null; 396 if (mRecentsPanel != null) { 397 visible = mRecentsPanel.isShowing(); 398 WindowManagerImpl.getDefault().removeView(mRecentsPanel); 399 if (visible) { 400 recentTasksList = mRecentsPanel.getRecentTasksList(); 401 } 402 } 403 404 // Provide RecentsPanelView with a temporary parent to allow layout params to work. 405 LinearLayout tmpRoot = new LinearLayout(mContext); 406 mRecentsPanel = (RecentsPanelView) LayoutInflater.from(mContext).inflate( 407 R.layout.status_bar_recent_panel, tmpRoot, false); 408 mRecentsPanel.setRecentTasksLoader(mRecentTasksLoader); 409 mRecentTasksLoader.setRecentsPanel(mRecentsPanel); 410 mRecentsPanel.setOnTouchListener(new TouchOutsideListener(MSG_CLOSE_RECENTS_PANEL, 411 mRecentsPanel)); 412 mRecentsPanel.setVisibility(View.GONE); 413 WindowManager.LayoutParams lp = getRecentsLayoutParams(mRecentsPanel.getLayoutParams()); 414 415 WindowManagerImpl.getDefault().addView(mRecentsPanel, lp); 416 mRecentsPanel.setBar(this); 417 if (visible) { 418 mRecentsPanel.show(true, false, recentTasksList); 419 } 420 421 } 422 423 protected int getStatusBarGravity() { 424 return Gravity.TOP | Gravity.FILL_HORIZONTAL; 425 } 426 427 public int getStatusBarHeight() { 428 final Resources res = mContext.getResources(); 429 return res.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_height); 430 } 431 432 private View.OnClickListener mRecentsClickListener = new View.OnClickListener() { 433 public void onClick(View v) { 434 toggleRecentApps(); 435 } 436 }; 437 438 // For small-screen devices (read: phones) that lack hardware navigation buttons 439 private void addNavigationBar() { 440 if (mNavigationBarView == null) return; 441 442 mNavigationBarView.reorient(); 443 444 mNavigationBarView.getRecentsButton().setOnClickListener(mRecentsClickListener); 445 446 WindowManagerImpl.getDefault().addView( 447 mNavigationBarView, getNavigationBarLayoutParams()); 448 } 449 450 private void repositionNavigationBar() { 451 if (mNavigationBarView == null) return; 452 453 mNavigationBarView.reorient(); 454 455 mNavigationBarView.getRecentsButton().setOnClickListener(mRecentsClickListener); 456 457 WindowManagerImpl.getDefault().updateViewLayout( 458 mNavigationBarView, getNavigationBarLayoutParams()); 459 } 460 461 private WindowManager.LayoutParams getNavigationBarLayoutParams() { 462 WindowManager.LayoutParams lp = new WindowManager.LayoutParams( 463 LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT, 464 WindowManager.LayoutParams.TYPE_NAVIGATION_BAR, 465 0 466 | WindowManager.LayoutParams.FLAG_TOUCHABLE_WHEN_WAKING 467 | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE 468 | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL 469 | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH 470 | WindowManager.LayoutParams.FLAG_SLIPPERY, 471 PixelFormat.OPAQUE); 472 // this will allow the navbar to run in an overlay on devices that support this 473 if (ActivityManager.isHighEndGfx(mDisplay)) { 474 lp.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED; 475 } 476 477 lp.setTitle("NavigationBar"); 478 lp.windowAnimations = 0; 479 480 return lp; 481 } 482 483 private void addIntruderView() { 484 final int height = getStatusBarHeight(); 485 486 WindowManager.LayoutParams lp = new WindowManager.LayoutParams( 487 ViewGroup.LayoutParams.MATCH_PARENT, 488 ViewGroup.LayoutParams.WRAP_CONTENT, 489 WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL, 490 WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN 491 | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS 492 | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL 493 | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE 494 | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM 495 | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH, 496 PixelFormat.TRANSLUCENT); 497 lp.gravity = Gravity.TOP | Gravity.FILL_HORIZONTAL; 498 lp.y += height * 1.5; // FIXME 499 lp.setTitle("IntruderAlert"); 500 lp.packageName = mContext.getPackageName(); 501 lp.windowAnimations = R.style.Animation_StatusBar_IntruderAlert; 502 503 WindowManagerImpl.getDefault().addView(mIntruderAlertView, lp); 504 } 505 506 public void addIcon(String slot, int index, int viewIndex, StatusBarIcon icon) { 507 if (SPEW) Slog.d(TAG, "addIcon slot=" + slot + " index=" + index + " viewIndex=" + viewIndex 508 + " icon=" + icon); 509 StatusBarIconView view = new StatusBarIconView(mContext, slot, null); 510 view.set(icon); 511 mStatusIcons.addView(view, viewIndex, new LinearLayout.LayoutParams(mIconSize, mIconSize)); 512 } 513 514 public void updateIcon(String slot, int index, int viewIndex, 515 StatusBarIcon old, StatusBarIcon icon) { 516 if (SPEW) Slog.d(TAG, "updateIcon slot=" + slot + " index=" + index + " viewIndex=" + viewIndex 517 + " old=" + old + " icon=" + icon); 518 StatusBarIconView view = (StatusBarIconView)mStatusIcons.getChildAt(viewIndex); 519 view.set(icon); 520 } 521 522 public void removeIcon(String slot, int index, int viewIndex) { 523 if (SPEW) Slog.d(TAG, "removeIcon slot=" + slot + " index=" + index + " viewIndex=" + viewIndex); 524 mStatusIcons.removeViewAt(viewIndex); 525 } 526 527 public void addNotification(IBinder key, StatusBarNotification notification) { 528 StatusBarIconView iconView = addNotificationViews(key, notification); 529 if (iconView == null) return; 530 531 boolean immersive = false; 532 try { 533 immersive = ActivityManagerNative.getDefault().isTopActivityImmersive(); 534 if (DEBUG) { 535 Slog.d(TAG, "Top activity is " + (immersive?"immersive":"not immersive")); 536 } 537 } catch (RemoteException ex) { 538 } 539 if (immersive) { 540 if ((notification.notification.flags & Notification.FLAG_HIGH_PRIORITY) != 0) { 541 Slog.d(TAG, "Presenting high-priority notification in immersive activity"); 542 // special new transient ticker mode 543 // 1. Populate mIntruderAlertView 544 545 ImageView alertIcon = (ImageView) mIntruderAlertView.findViewById(R.id.alertIcon); 546 TextView alertText = (TextView) mIntruderAlertView.findViewById(R.id.alertText); 547 alertIcon.setImageDrawable(StatusBarIconView.getIcon( 548 alertIcon.getContext(), 549 iconView.getStatusBarIcon())); 550 alertText.setText(notification.notification.tickerText); 551 552 View button = mIntruderAlertView.findViewById(R.id.intruder_alert_content); 553 button.setOnClickListener( 554 new NotificationClicker(notification.notification.contentIntent, 555 notification.pkg, notification.tag, notification.id)); 556 557 // 2. Animate mIntruderAlertView in 558 mHandler.sendEmptyMessage(MSG_SHOW_INTRUDER); 559 560 // 3. Set alarm to age the notification off (TODO) 561 mHandler.removeMessages(MSG_HIDE_INTRUDER); 562 mHandler.sendEmptyMessageDelayed(MSG_HIDE_INTRUDER, INTRUDER_ALERT_DECAY_MS); 563 } 564 } else if (notification.notification.fullScreenIntent != null) { 565 // not immersive & a full-screen alert should be shown 566 Slog.d(TAG, "Notification has fullScreenIntent; sending fullScreenIntent"); 567 try { 568 notification.notification.fullScreenIntent.send(); 569 } catch (PendingIntent.CanceledException e) { 570 } 571 } else { 572 // usual case: status bar visible & not immersive 573 574 // show the ticker 575 tick(notification); 576 } 577 578 // Recalculate the position of the sliding windows and the titles. 579 setAreThereNotifications(); 580 updateExpandedViewPos(EXPANDED_LEAVE_ALONE); 581 } 582 583 public void updateNotification(IBinder key, StatusBarNotification notification) { 584 if (DEBUG) Slog.d(TAG, "updateNotification(" + key + " -> " + notification + ")"); 585 586 final NotificationData.Entry oldEntry = mNotificationData.findByKey(key); 587 if (oldEntry == null) { 588 Slog.w(TAG, "updateNotification for unknown key: " + key); 589 return; 590 } 591 592 final StatusBarNotification oldNotification = oldEntry.notification; 593 final RemoteViews oldContentView = oldNotification.notification.contentView; 594 595 final RemoteViews contentView = notification.notification.contentView; 596 597 598 if (DEBUG) { 599 Slog.d(TAG, "old notification: when=" + oldNotification.notification.when 600 + " ongoing=" + oldNotification.isOngoing() 601 + " expanded=" + oldEntry.expanded 602 + " contentView=" + oldContentView 603 + " rowParent=" + oldEntry.row.getParent()); 604 Slog.d(TAG, "new notification: when=" + notification.notification.when 605 + " ongoing=" + oldNotification.isOngoing() 606 + " contentView=" + contentView); 607 } 608 609 610 // Can we just reapply the RemoteViews in place? If when didn't change, the order 611 // didn't change. 612 boolean contentsUnchanged = oldEntry.expanded != null 613 && contentView != null && oldContentView != null 614 && contentView.getPackage() != null 615 && oldContentView.getPackage() != null 616 && oldContentView.getPackage().equals(contentView.getPackage()) 617 && oldContentView.getLayoutId() == contentView.getLayoutId(); 618 ViewGroup rowParent = (ViewGroup) oldEntry.row.getParent(); 619 boolean orderUnchanged = notification.notification.when==oldNotification.notification.when 620 && notification.priority == oldNotification.priority; 621 // priority now encompasses isOngoing() 622 623 boolean updateTicker = notification.notification.tickerText != null 624 && !TextUtils.equals(notification.notification.tickerText, 625 oldEntry.notification.notification.tickerText); 626 boolean isFirstAnyway = rowParent.indexOfChild(oldEntry.row) == 0; 627 if (contentsUnchanged && (orderUnchanged || isFirstAnyway)) { 628 if (DEBUG) Slog.d(TAG, "reusing notification for key: " + key); 629 oldEntry.notification = notification; 630 try { 631 // Reapply the RemoteViews 632 contentView.reapply(mContext, oldEntry.content); 633 // update the contentIntent 634 final PendingIntent contentIntent = notification.notification.contentIntent; 635 if (contentIntent != null) { 636 final View.OnClickListener listener = new NotificationClicker(contentIntent, 637 notification.pkg, notification.tag, notification.id); 638 oldEntry.largeIcon.setOnClickListener(listener); 639 oldEntry.content.setOnClickListener(listener); 640 } else { 641 oldEntry.largeIcon.setOnClickListener(null); 642 oldEntry.content.setOnClickListener(null); 643 } 644 // Update the icon. 645 final StatusBarIcon ic = new StatusBarIcon(notification.pkg, 646 notification.notification.icon, notification.notification.iconLevel, 647 notification.notification.number, 648 notification.notification.tickerText); 649 if (!oldEntry.icon.set(ic)) { 650 handleNotificationError(key, notification, "Couldn't update icon: " + ic); 651 return; 652 } 653 // Update the large icon 654 if (notification.notification.largeIcon != null) { 655 oldEntry.largeIcon.setImageBitmap(notification.notification.largeIcon); 656 } else { 657 oldEntry.largeIcon.getLayoutParams().width = 0; 658 oldEntry.largeIcon.setVisibility(View.INVISIBLE); 659 } 660 } 661 catch (RuntimeException e) { 662 // It failed to add cleanly. Log, and remove the view from the panel. 663 Slog.w(TAG, "Couldn't reapply views for package " + contentView.getPackage(), e); 664 removeNotificationViews(key); 665 addNotificationViews(key, notification); 666 } 667 } else { 668 if (SPEW) Slog.d(TAG, "not reusing notification"); 669 removeNotificationViews(key); 670 addNotificationViews(key, notification); 671 } 672 673 // Update the veto button accordingly (and as a result, whether this row is 674 // swipe-dismissable) 675 updateNotificationVetoButton(oldEntry.row, notification); 676 677 // Restart the ticker if it's still running 678 if (updateTicker) { 679 mTicker.halt(); 680 tick(notification); 681 } 682 683 // Recalculate the position of the sliding windows and the titles. 684 setAreThereNotifications(); 685 updateExpandedViewPos(EXPANDED_LEAVE_ALONE); 686 } 687 688 public void removeNotification(IBinder key) { 689 if (SPEW) Slog.d(TAG, "removeNotification key=" + key); 690 StatusBarNotification old = removeNotificationViews(key); 691 692 if (old != null) { 693 // Cancel the ticker if it's still running 694 mTicker.removeEntry(old); 695 696 // Recalculate the position of the sliding windows and the titles. 697 updateExpandedViewPos(EXPANDED_LEAVE_ALONE); 698 } 699 700 setAreThereNotifications(); 701 } 702 703 @Override 704 protected void onConfigurationChanged(Configuration newConfig) { 705 updateRecentsPanel(); 706 } 707 708 709 View[] makeNotificationView(StatusBarNotification notification, ViewGroup parent) { 710 Notification n = notification.notification; 711 RemoteViews remoteViews = n.contentView; 712 if (remoteViews == null) { 713 return null; 714 } 715 716 // create the row view 717 LayoutInflater inflater = (LayoutInflater)mContext.getSystemService( 718 Context.LAYOUT_INFLATER_SERVICE); 719 View row = inflater.inflate(R.layout.status_bar_notification_row, parent, false); 720 721 // wire up the veto button 722 View vetoButton = updateNotificationVetoButton(row, notification); 723 vetoButton.setContentDescription(mContext.getString( 724 R.string.accessibility_remove_notification)); 725 726 // the large icon 727 ImageView largeIcon = (ImageView)row.findViewById(R.id.large_icon); 728 if (notification.notification.largeIcon != null) { 729 largeIcon.setImageBitmap(notification.notification.largeIcon); 730 } else { 731 largeIcon.getLayoutParams().width = 0; 732 largeIcon.setVisibility(View.INVISIBLE); 733 } 734 735 // bind the click event to the content area 736 ViewGroup content = (ViewGroup)row.findViewById(R.id.content); 737 content.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS); 738 content.setOnFocusChangeListener(mFocusChangeListener); 739 PendingIntent contentIntent = n.contentIntent; 740 if (contentIntent != null) { 741 final View.OnClickListener listener = new NotificationClicker(contentIntent, 742 notification.pkg, notification.tag, notification.id); 743 largeIcon.setOnClickListener(listener); 744 content.setOnClickListener(listener); 745 } else { 746 largeIcon.setOnClickListener(null); 747 content.setOnClickListener(null); 748 } 749 750 View expanded = null; 751 Exception exception = null; 752 try { 753 expanded = remoteViews.apply(mContext, content); 754 } 755 catch (RuntimeException e) { 756 exception = e; 757 } 758 if (expanded == null) { 759 String ident = notification.pkg + "/0x" + Integer.toHexString(notification.id); 760 Slog.e(TAG, "couldn't inflate view for notification " + ident, exception); 761 return null; 762 } else { 763 content.addView(expanded); 764 row.setDrawingCacheEnabled(true); 765 } 766 767 return new View[] { row, content, expanded }; 768 } 769 770 StatusBarIconView addNotificationViews(IBinder key, StatusBarNotification notification) { 771 if (DEBUG) { 772 Slog.d(TAG, "addNotificationViews(key=" + key + ", notification=" + notification); 773 } 774 // Construct the icon. 775 final StatusBarIconView iconView = new StatusBarIconView(mContext, 776 notification.pkg + "/0x" + Integer.toHexString(notification.id), 777 notification.notification); 778 iconView.setScaleType(ImageView.ScaleType.CENTER_INSIDE); 779 780 final StatusBarIcon ic = new StatusBarIcon(notification.pkg, 781 notification.notification.icon, 782 notification.notification.iconLevel, 783 notification.notification.number, 784 notification.notification.tickerText); 785 if (!iconView.set(ic)) { 786 handleNotificationError(key, notification, "Couldn't create icon: " + ic); 787 return null; 788 } 789 // Construct the expanded view. 790 NotificationData.Entry entry = new NotificationData.Entry(key, notification, iconView); 791 if (!inflateViews(entry, mPile)) { 792 handleNotificationError(key, notification, "Couldn't expand RemoteViews for: " 793 + notification); 794 return null; 795 } 796 797 // Add the expanded view and icon. 798 int pos = mNotificationData.add(entry); 799 if (DEBUG) { 800 Slog.d(TAG, "addNotificationViews: added at " + pos); 801 } 802 updateNotificationIcons(); 803 804 return iconView; 805 } 806 807 private void loadNotificationShade() { 808 int N = mNotificationData.size(); 809 810 ArrayList<View> toShow = new ArrayList<View>(); 811 812 for (int i=0; i<N; i++) { 813 View row = mNotificationData.get(N-i-1).row; 814 toShow.add(row); 815 } 816 817 ArrayList<View> toRemove = new ArrayList<View>(); 818 for (int i=0; i<mPile.getChildCount(); i++) { 819 View child = mPile.getChildAt(i); 820 if (!toShow.contains(child)) { 821 toRemove.add(child); 822 } 823 } 824 825 for (View remove : toRemove) { 826 mPile.removeView(remove); 827 } 828 829 for (int i=0; i<toShow.size(); i++) { 830 View v = toShow.get(i); 831 if (v.getParent() == null) { 832 mPile.addView(v, 0); // the notification shade has newest at the top 833 } 834 } 835 } 836 837 private void reloadAllNotificationIcons() { 838 if (mNotificationIcons == null) return; 839 mNotificationIcons.removeAllViews(); 840 updateNotificationIcons(); 841 } 842 843 private void updateNotificationIcons() { 844 loadNotificationShade(); 845 846 final LinearLayout.LayoutParams params 847 = new LinearLayout.LayoutParams(mIconSize + 2*mIconHPadding, mNaturalBarHeight); 848 849 int N = mNotificationData.size(); 850 851 if (DEBUG) { 852 Slog.d(TAG, "refreshing icons: " + N + " notifications, mNotificationIcons=" + mNotificationIcons); 853 } 854 855 ArrayList<View> toShow = new ArrayList<View>(); 856 857 for (int i=0; i<N; i++) { 858 toShow.add(mNotificationData.get(N-i-1).icon); 859 } 860 861 ArrayList<View> toRemove = new ArrayList<View>(); 862 for (int i=0; i<mNotificationIcons.getChildCount(); i++) { 863 View child = mNotificationIcons.getChildAt(i); 864 if (!toShow.contains(child)) { 865 toRemove.add(child); 866 } 867 } 868 869 for (View remove : toRemove) { 870 mNotificationIcons.removeView(remove); 871 } 872 873 for (int i=0; i<toShow.size(); i++) { 874 View v = toShow.get(i); 875 if (v.getParent() == null) { 876 mNotificationIcons.addView(v, i, params); 877 } 878 } 879 } 880 881 private boolean inflateViews(NotificationData.Entry entry, ViewGroup parent) { 882 StatusBarNotification sbn = entry.notification; 883 RemoteViews remoteViews = sbn.notification.contentView; 884 if (remoteViews == null) { 885 return false; 886 } 887 888 // create the row view 889 LayoutInflater inflater = (LayoutInflater)mContext.getSystemService( 890 Context.LAYOUT_INFLATER_SERVICE); 891 View row = inflater.inflate(R.layout.status_bar_notification_row, parent, false); 892 View vetoButton = updateNotificationVetoButton(row, sbn); 893 vetoButton.setContentDescription(mContext.getString( 894 R.string.accessibility_remove_notification)); 895 896 // the large icon 897 ImageView largeIcon = (ImageView)row.findViewById(R.id.large_icon); 898 if (sbn.notification.largeIcon != null) { 899 largeIcon.setImageBitmap(sbn.notification.largeIcon); 900 largeIcon.setContentDescription(sbn.notification.tickerText); 901 } else { 902 largeIcon.getLayoutParams().width = 0; 903 largeIcon.setVisibility(View.INVISIBLE); 904 } 905 largeIcon.setContentDescription(sbn.notification.tickerText); 906 907 // bind the click event to the content area 908 ViewGroup content = (ViewGroup)row.findViewById(R.id.content); 909 // XXX: update to allow controls within notification views 910 content.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS); 911// content.setOnFocusChangeListener(mFocusChangeListener); 912 PendingIntent contentIntent = sbn.notification.contentIntent; 913 if (contentIntent != null) { 914 final View.OnClickListener listener = new NotificationClicker(contentIntent, 915 sbn.pkg, sbn.tag, sbn.id); 916 largeIcon.setOnClickListener(listener); 917 content.setOnClickListener(listener); 918 } else { 919 largeIcon.setOnClickListener(null); 920 content.setOnClickListener(null); 921 } 922 923 View expanded = null; 924 Exception exception = null; 925 try { 926 expanded = remoteViews.apply(mContext, content); 927 } 928 catch (RuntimeException e) { 929 exception = e; 930 } 931 if (expanded == null) { 932 final String ident = sbn.pkg + "/0x" + Integer.toHexString(sbn.id); 933 Slog.e(TAG, "couldn't inflate view for notification " + ident, exception); 934 return false; 935 } else { 936 content.addView(expanded); 937 row.setDrawingCacheEnabled(true); 938 } 939 940 entry.row = row; 941 entry.content = content; 942 entry.expanded = expanded; 943 entry.largeIcon = largeIcon; 944 945 return true; 946 } 947 948 StatusBarNotification removeNotificationViews(IBinder key) { 949 NotificationData.Entry entry = mNotificationData.remove(key); 950 if (entry == null) { 951 Slog.w(TAG, "removeNotification for unknown key: " + key); 952 return null; 953 } 954 // Remove the expanded view. 955 ViewGroup rowParent = (ViewGroup)entry.row.getParent(); 956 if (rowParent != null) rowParent.removeView(entry.row); 957 updateNotificationIcons(); 958 959 return entry.notification; 960 } 961 962 private void setAreThereNotifications() { 963 final boolean any = mNotificationData.size() > 0; 964 965 final boolean clearable = any && mNotificationData.hasClearableItems(); 966 967 if (DEBUG) { 968 Slog.d(TAG, "setAreThereNotifications: N=" + mNotificationData.size() 969 + " any=" + any + " clearable=" + clearable); 970 } 971 972 if (mClearButton.isShown()) { 973 if (clearable != (mClearButton.getAlpha() == 1.0f)) { 974 ObjectAnimator.ofFloat(mClearButton, "alpha", 975 clearable ? 1.0f : 0.0f) 976 .setDuration(250) 977 .start(); 978 } 979 } else { 980 mClearButton.setAlpha(clearable ? 1.0f : 0.0f); 981 } 982 mClearButton.setEnabled(clearable); 983 984 /* 985 if (mNoNotificationsTitle.isShown()) { 986 if (any != (mNoNotificationsTitle.getAlpha() == 0.0f)) { 987 ObjectAnimator a = ObjectAnimator.ofFloat(mNoNotificationsTitle, "alpha", 988 (any ? 0.0f : 0.75f)); 989 a.setDuration(any ? 0 : 500); 990 a.setStartDelay(any ? 250 : 1000); 991 a.start(); 992 } 993 } else { 994 mNoNotificationsTitle.setAlpha(any ? 0.0f : 0.75f); 995 } 996 */ 997 } 998 999 public void showClock(boolean show) { 1000 View clock = mStatusBarView.findViewById(R.id.clock); 1001 if (clock != null) { 1002 clock.setVisibility(show ? View.VISIBLE : View.GONE); 1003 } 1004 } 1005 1006 /** 1007 * State is one or more of the DISABLE constants from StatusBarManager. 1008 */ 1009 public void disable(int state) { 1010 final int old = mDisabled; 1011 final int diff = state ^ old; 1012 mDisabled = state; 1013 1014 if (DEBUG) { 1015 Slog.d(TAG, String.format("disable: 0x%08x -> 0x%08x (diff: 0x%08x)", 1016 old, state, diff)); 1017 } 1018 1019 StringBuilder flagdbg = new StringBuilder(); 1020 flagdbg.append("disable: < "); 1021 flagdbg.append(((state & StatusBarManager.DISABLE_EXPAND) != 0) ? "EXPAND" : "expand"); 1022 flagdbg.append(((diff & StatusBarManager.DISABLE_EXPAND) != 0) ? "* " : " "); 1023 flagdbg.append(((state & StatusBarManager.DISABLE_NOTIFICATION_ICONS) != 0) ? "ICONS" : "icons"); 1024 flagdbg.append(((diff & StatusBarManager.DISABLE_NOTIFICATION_ICONS) != 0) ? "* " : " "); 1025 flagdbg.append(((state & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) != 0) ? "ALERTS" : "alerts"); 1026 flagdbg.append(((diff & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) != 0) ? "* " : " "); 1027 flagdbg.append(((state & StatusBarManager.DISABLE_NOTIFICATION_TICKER) != 0) ? "TICKER" : "ticker"); 1028 flagdbg.append(((diff & StatusBarManager.DISABLE_NOTIFICATION_TICKER) != 0) ? "* " : " "); 1029 flagdbg.append(((state & StatusBarManager.DISABLE_SYSTEM_INFO) != 0) ? "SYSTEM_INFO" : "system_info"); 1030 flagdbg.append(((diff & StatusBarManager.DISABLE_SYSTEM_INFO) != 0) ? "* " : " "); 1031 flagdbg.append(((state & StatusBarManager.DISABLE_BACK) != 0) ? "BACK" : "back"); 1032 flagdbg.append(((diff & StatusBarManager.DISABLE_BACK) != 0) ? "* " : " "); 1033 flagdbg.append(((state & StatusBarManager.DISABLE_HOME) != 0) ? "HOME" : "home"); 1034 flagdbg.append(((diff & StatusBarManager.DISABLE_HOME) != 0) ? "* " : " "); 1035 flagdbg.append(((state & StatusBarManager.DISABLE_RECENT) != 0) ? "RECENT" : "recent"); 1036 flagdbg.append(((diff & StatusBarManager.DISABLE_RECENT) != 0) ? "* " : " "); 1037 flagdbg.append(((state & StatusBarManager.DISABLE_CLOCK) != 0) ? "CLOCK" : "clock"); 1038 flagdbg.append(((diff & StatusBarManager.DISABLE_CLOCK) != 0) ? "* " : " "); 1039 flagdbg.append(">"); 1040 Slog.d(TAG, flagdbg.toString()); 1041 1042 if ((diff & StatusBarManager.DISABLE_CLOCK) != 0) { 1043 boolean show = (state & StatusBarManager.DISABLE_CLOCK) == 0; 1044 showClock(show); 1045 } 1046 if ((diff & StatusBarManager.DISABLE_EXPAND) != 0) { 1047 if ((state & StatusBarManager.DISABLE_EXPAND) != 0) { 1048 animateCollapse(); 1049 } 1050 } 1051 1052 if ((diff & (StatusBarManager.DISABLE_HOME 1053 | StatusBarManager.DISABLE_RECENT 1054 | StatusBarManager.DISABLE_BACK)) != 0) { 1055 // the nav bar will take care of these 1056 if (mNavigationBarView != null) mNavigationBarView.setDisabledFlags(state); 1057 1058 if ((state & StatusBarManager.DISABLE_RECENT) != 0) { 1059 // close recents if it's visible 1060 mHandler.removeMessages(MSG_CLOSE_RECENTS_PANEL); 1061 mHandler.sendEmptyMessage(MSG_CLOSE_RECENTS_PANEL); 1062 } 1063 } 1064 1065 if ((diff & StatusBarManager.DISABLE_NOTIFICATION_ICONS) != 0) { 1066 if ((state & StatusBarManager.DISABLE_NOTIFICATION_ICONS) != 0) { 1067 if (mTicking) { 1068 mTicker.halt(); 1069 } else { 1070 setNotificationIconVisibility(false, com.android.internal.R.anim.fade_out); 1071 } 1072 } else { 1073 if (!mExpandedVisible) { 1074 setNotificationIconVisibility(true, com.android.internal.R.anim.fade_in); 1075 } 1076 } 1077 } else if ((diff & StatusBarManager.DISABLE_NOTIFICATION_TICKER) != 0) { 1078 if (mTicking && (state & StatusBarManager.DISABLE_NOTIFICATION_TICKER) != 0) { 1079 mTicker.halt(); 1080 } 1081 } 1082 } 1083 1084 /** 1085 * All changes to the status bar and notifications funnel through here and are batched. 1086 */ 1087 private class H extends Handler { 1088 public void handleMessage(Message m) { 1089 switch (m.what) { 1090 case MSG_ANIMATE: 1091 doAnimation(); 1092 break; 1093 case MSG_ANIMATE_REVEAL: 1094 doRevealAnimation(); 1095 break; 1096 case MSG_OPEN_NOTIFICATION_PANEL: 1097 animateExpand(); 1098 break; 1099 case MSG_CLOSE_NOTIFICATION_PANEL: 1100 animateCollapse(); 1101 break; 1102 case MSG_SHOW_INTRUDER: 1103 setIntruderAlertVisibility(true); 1104 break; 1105 case MSG_HIDE_INTRUDER: 1106 setIntruderAlertVisibility(false); 1107 break; 1108 case MSG_OPEN_RECENTS_PANEL: 1109 if (DEBUG) Slog.d(TAG, "opening recents panel"); 1110 if (mRecentsPanel != null) { 1111 mRecentsPanel.show(true, true); 1112 } 1113 break; 1114 case MSG_CLOSE_RECENTS_PANEL: 1115 if (DEBUG) Slog.d(TAG, "closing recents panel"); 1116 if (mRecentsPanel != null && mRecentsPanel.isShowing()) { 1117 mRecentsPanel.show(false, true); 1118 } 1119 break; 1120 } 1121 } 1122 } 1123 1124 View.OnFocusChangeListener mFocusChangeListener = new View.OnFocusChangeListener() { 1125 public void onFocusChange(View v, boolean hasFocus) { 1126 // Because 'v' is a ViewGroup, all its children will be (un)selected 1127 // too, which allows marqueeing to work. 1128 v.setSelected(hasFocus); 1129 } 1130 }; 1131 1132 private void makeExpandedVisible() { 1133 if (SPEW) Slog.d(TAG, "Make expanded visible: expanded visible=" + mExpandedVisible); 1134 if (mExpandedVisible) { 1135 return; 1136 } 1137 mExpandedVisible = true; 1138 visibilityChanged(true); 1139 1140 updateExpandedViewPos(EXPANDED_LEAVE_ALONE); 1141 mExpandedParams.flags &= ~WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; 1142 mExpandedParams.flags |= WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; 1143 if (DEBUG) { 1144 Slog.d(TAG, "makeExpandedVisible: expanded params = " + mExpandedParams); 1145 } 1146 mExpandedDialog.getWindow().setAttributes(mExpandedParams); 1147 mExpandedView.requestFocus(View.FOCUS_FORWARD); 1148 mTrackingView.setVisibility(View.VISIBLE); 1149 } 1150 1151 public void animateExpand() { 1152 if (SPEW) Slog.d(TAG, "Animate expand: expanded=" + mExpanded); 1153 if ((mDisabled & StatusBarManager.DISABLE_EXPAND) != 0) { 1154 return ; 1155 } 1156 if (mExpanded) { 1157 return; 1158 } 1159 1160 prepareTracking(0, true); 1161 performFling(0, mSelfExpandVelocityPx, true); 1162 } 1163 1164 public void animateCollapse() { 1165 animateCollapse(false); 1166 } 1167 1168 public void animateCollapse(boolean excludeRecents) { 1169 animateCollapse(excludeRecents, 1.0f); 1170 } 1171 1172 public void animateCollapse(boolean excludeRecents, float velocityMultiplier) { 1173 if (SPEW) { 1174 Slog.d(TAG, "animateCollapse(): mExpanded=" + mExpanded 1175 + " mExpandedVisible=" + mExpandedVisible 1176 + " mExpanded=" + mExpanded 1177 + " mAnimating=" + mAnimating 1178 + " mAnimY=" + mAnimY 1179 + " mAnimVel=" + mAnimVel); 1180 } 1181 1182 if (!excludeRecents) { 1183 mHandler.removeMessages(MSG_CLOSE_RECENTS_PANEL); 1184 mHandler.sendEmptyMessage(MSG_CLOSE_RECENTS_PANEL); 1185 } 1186 1187 if (!mExpandedVisible) { 1188 return; 1189 } 1190 1191 int y; 1192 if (mAnimating) { 1193 y = (int)mAnimY; 1194 } else { 1195 y = mDisplayMetrics.heightPixels-1; 1196 } 1197 // Let the fling think that we're open so it goes in the right direction 1198 // and doesn't try to re-open the windowshade. 1199 mExpanded = true; 1200 prepareTracking(y, false); 1201 performFling(y, -mSelfCollapseVelocityPx*velocityMultiplier, true); 1202 } 1203 1204 void performExpand() { 1205 if (SPEW) Slog.d(TAG, "performExpand: mExpanded=" + mExpanded); 1206 if ((mDisabled & StatusBarManager.DISABLE_EXPAND) != 0) { 1207 return ; 1208 } 1209 if (mExpanded) { 1210 return; 1211 } 1212 1213 mExpanded = true; 1214 makeExpandedVisible(); 1215 updateExpandedViewPos(EXPANDED_FULL_OPEN); 1216 1217 if (false) postStartTracing(); 1218 } 1219 1220 void performCollapse() { 1221 if (SPEW) Slog.d(TAG, "performCollapse: mExpanded=" + mExpanded 1222 + " mExpandedVisible=" + mExpandedVisible); 1223 1224 if (!mExpandedVisible) { 1225 return; 1226 } 1227 mExpandedVisible = false; 1228 visibilityChanged(false); 1229 mExpandedParams.flags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; 1230 mExpandedParams.flags &= ~WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; 1231 mExpandedDialog.getWindow().setAttributes(mExpandedParams); 1232 mTrackingView.setVisibility(View.GONE); 1233 1234 if ((mDisabled & StatusBarManager.DISABLE_NOTIFICATION_ICONS) == 0) { 1235 setNotificationIconVisibility(true, com.android.internal.R.anim.fade_in); 1236 } 1237 1238 if (!mExpanded) { 1239 return; 1240 } 1241 mExpanded = false; 1242 if (mPostCollapseCleanup != null) { 1243 mPostCollapseCleanup.run(); 1244 mPostCollapseCleanup = null; 1245 } 1246 } 1247 1248 void doAnimation() { 1249 if (mAnimating) { 1250 if (SPEW) Slog.d(TAG, "doAnimation"); 1251 if (SPEW) Slog.d(TAG, "doAnimation before mAnimY=" + mAnimY); 1252 incrementAnim(); 1253 if (SPEW) Slog.d(TAG, "doAnimation after mAnimY=" + mAnimY); 1254 if (mAnimY >= mDisplayMetrics.heightPixels-1) { 1255 if (SPEW) Slog.d(TAG, "Animation completed to expanded state."); 1256 mAnimating = false; 1257 updateExpandedViewPos(EXPANDED_FULL_OPEN); 1258 performExpand(); 1259 } 1260 else if (mAnimY < mStatusBarView.getHeight()) { 1261 if (SPEW) Slog.d(TAG, "Animation completed to collapsed state."); 1262 mAnimating = false; 1263 updateExpandedViewPos(0); 1264 performCollapse(); 1265 } 1266 else { 1267 updateExpandedViewPos((int)mAnimY); 1268 mCurAnimationTime += ANIM_FRAME_DURATION; 1269 mHandler.sendMessageAtTime(mHandler.obtainMessage(MSG_ANIMATE), mCurAnimationTime); 1270 } 1271 } 1272 } 1273 1274 void stopTracking() { 1275 mTracking = false; 1276 mVelocityTracker.recycle(); 1277 mVelocityTracker = null; 1278 } 1279 1280 void incrementAnim() { 1281 long now = SystemClock.uptimeMillis(); 1282 float t = ((float)(now - mAnimLastTime)) / 1000; // ms -> s 1283 final float y = mAnimY; 1284 final float v = mAnimVel; // px/s 1285 final float a = mAnimAccel; // px/s/s 1286 mAnimY = y + (v*t) + (0.5f*a*t*t); // px 1287 mAnimVel = v + (a*t); // px/s 1288 mAnimLastTime = now; // ms 1289 //Slog.d(TAG, "y=" + y + " v=" + v + " a=" + a + " t=" + t + " mAnimY=" + mAnimY 1290 // + " mAnimAccel=" + mAnimAccel); 1291 } 1292 1293 void doRevealAnimation() { 1294 final int h = mCloseView.getHeight() + mStatusBarView.getHeight(); 1295 if (mAnimatingReveal && mAnimating && mAnimY < h) { 1296 incrementAnim(); 1297 if (mAnimY >= h) { 1298 mAnimY = h; 1299 updateExpandedViewPos((int)mAnimY); 1300 } else { 1301 updateExpandedViewPos((int)mAnimY); 1302 mCurAnimationTime += ANIM_FRAME_DURATION; 1303 mHandler.sendMessageAtTime(mHandler.obtainMessage(MSG_ANIMATE_REVEAL), 1304 mCurAnimationTime); 1305 } 1306 } 1307 } 1308 1309 void prepareTracking(int y, boolean opening) { 1310 if (CHATTY) { 1311 Slog.d(TAG, "panel: beginning to track the user's touch, y=" + y + " opening=" + opening); 1312 } 1313 1314 // there are some race conditions that cause this to be inaccurate; let's recalculate it any 1315 // time we're about to drag the panel 1316 updateExpandedSize(); 1317 1318 mTracking = true; 1319 mVelocityTracker = VelocityTracker.obtain(); 1320 if (opening) { 1321 mAnimAccel = mExpandAccelPx; 1322 mAnimVel = mFlingExpandMinVelocityPx; 1323 mAnimY = mStatusBarView.getHeight(); 1324 updateExpandedViewPos((int)mAnimY); 1325 mAnimating = true; 1326 mAnimatingReveal = true; 1327 mHandler.removeMessages(MSG_ANIMATE); 1328 mHandler.removeMessages(MSG_ANIMATE_REVEAL); 1329 long now = SystemClock.uptimeMillis(); 1330 mAnimLastTime = now; 1331 mCurAnimationTime = now + ANIM_FRAME_DURATION; 1332 mAnimating = true; 1333 mHandler.sendMessageAtTime(mHandler.obtainMessage(MSG_ANIMATE_REVEAL), 1334 mCurAnimationTime); 1335 makeExpandedVisible(); 1336 } else { 1337 // it's open, close it? 1338 if (mAnimating) { 1339 mAnimating = false; 1340 mHandler.removeMessages(MSG_ANIMATE); 1341 } 1342 updateExpandedViewPos(y + mViewDelta); 1343 } 1344 } 1345 1346 void performFling(int y, float vel, boolean always) { 1347 if (CHATTY) { 1348 Slog.d(TAG, "panel: will fling, y=" + y + " vel=" + vel); 1349 } 1350 1351 mAnimatingReveal = false; 1352 1353 mAnimY = y; 1354 mAnimVel = vel; 1355 1356 //Slog.d(TAG, "starting with mAnimY=" + mAnimY + " mAnimVel=" + mAnimVel); 1357 1358 if (mExpanded) { 1359 if (!always && ( 1360 vel > mFlingCollapseMinVelocityPx 1361 || (y > (mDisplayMetrics.heightPixels*(1f-mCollapseMinDisplayFraction)) && 1362 vel > -mFlingExpandMinVelocityPx))) { 1363 // We are expanded, but they didn't move sufficiently to cause 1364 // us to retract. Animate back to the expanded position. 1365 mAnimAccel = mExpandAccelPx; 1366 if (vel < 0) { 1367 mAnimVel = 0; 1368 } 1369 } 1370 else { 1371 // We are expanded and are now going to animate away. 1372 mAnimAccel = -mCollapseAccelPx; 1373 if (vel > 0) { 1374 mAnimVel = 0; 1375 } 1376 } 1377 } else { 1378 if (always || ( 1379 vel > mFlingExpandMinVelocityPx 1380 || (y > (mDisplayMetrics.heightPixels*(1f-mExpandMinDisplayFraction)) && 1381 vel > -mFlingCollapseMinVelocityPx))) { 1382 // We are collapsed, and they moved enough to allow us to 1383 // expand. Animate in the notifications. 1384 mAnimAccel = mExpandAccelPx; 1385 if (vel < 0) { 1386 mAnimVel = 0; 1387 } 1388 } 1389 else { 1390 // We are collapsed, but they didn't move sufficiently to cause 1391 // us to retract. Animate back to the collapsed position. 1392 mAnimAccel = -mCollapseAccelPx; 1393 if (vel > 0) { 1394 mAnimVel = 0; 1395 } 1396 } 1397 } 1398 //Slog.d(TAG, "mAnimY=" + mAnimY + " mAnimVel=" + mAnimVel 1399 // + " mAnimAccel=" + mAnimAccel); 1400 1401 long now = SystemClock.uptimeMillis(); 1402 mAnimLastTime = now; 1403 mCurAnimationTime = now + ANIM_FRAME_DURATION; 1404 mAnimating = true; 1405 mHandler.removeMessages(MSG_ANIMATE); 1406 mHandler.removeMessages(MSG_ANIMATE_REVEAL); 1407 mHandler.sendMessageAtTime(mHandler.obtainMessage(MSG_ANIMATE), mCurAnimationTime); 1408 stopTracking(); 1409 } 1410 1411 boolean interceptTouchEvent(MotionEvent event) { 1412 if (SPEW) { 1413 Slog.d(TAG, "Touch: rawY=" + event.getRawY() + " event=" + event + " mDisabled=" 1414 + mDisabled); 1415 } else if (CHATTY) { 1416 if (event.getAction() != MotionEvent.ACTION_MOVE) { 1417 Slog.d(TAG, String.format( 1418 "panel: %s at (%f, %f) mDisabled=0x%08x", 1419 MotionEvent.actionToString(event.getAction()), 1420 event.getRawX(), event.getRawY(), mDisabled)); 1421 } 1422 } 1423 1424 if ((mDisabled & StatusBarManager.DISABLE_EXPAND) != 0) { 1425 return false; 1426 } 1427 1428 final int action = event.getAction(); 1429 final int statusBarSize = mStatusBarView.getHeight(); 1430 final int hitSize = statusBarSize*2; 1431 final int y = (int)event.getRawY(); 1432 if (action == MotionEvent.ACTION_DOWN) { 1433 if (!mExpanded) { 1434 mViewDelta = statusBarSize - y; 1435 } else { 1436 mTrackingView.getLocationOnScreen(mAbsPos); 1437 mViewDelta = mAbsPos[1] + mTrackingView.getHeight() - y; 1438 } 1439 if ((!mExpanded && y < hitSize) || 1440 (mExpanded && y > (mDisplayMetrics.heightPixels-hitSize))) { 1441 1442 // We drop events at the edge of the screen to make the windowshade come 1443 // down by accident less, especially when pushing open a device with a keyboard 1444 // that rotates (like g1 and droid) 1445 int x = (int)event.getRawX(); 1446 final int edgeBorder = mEdgeBorder; 1447 if (x >= edgeBorder && x < mDisplayMetrics.widthPixels - edgeBorder) { 1448 prepareTracking(y, !mExpanded);// opening if we're not already fully visible 1449 trackMovement(event); 1450 } 1451 } 1452 } else if (mTracking) { 1453 trackMovement(event); 1454 final int minY = statusBarSize + mCloseView.getHeight(); 1455 if (action == MotionEvent.ACTION_MOVE) { 1456 if (mAnimatingReveal && y < minY) { 1457 // nothing 1458 } else { 1459 mAnimatingReveal = false; 1460 updateExpandedViewPos(y + mViewDelta); 1461 } 1462 } else if (action == MotionEvent.ACTION_UP 1463 || action == MotionEvent.ACTION_CANCEL) { 1464 mVelocityTracker.computeCurrentVelocity(1000); 1465 1466 float yVel = mVelocityTracker.getYVelocity(); 1467 boolean negative = yVel < 0; 1468 1469 float xVel = mVelocityTracker.getXVelocity(); 1470 if (xVel < 0) { 1471 xVel = -xVel; 1472 } 1473 if (xVel > mFlingGestureMaxXVelocityPx) { 1474 xVel = mFlingGestureMaxXVelocityPx; // limit how much we care about the x axis 1475 } 1476 1477 float vel = (float)Math.hypot(yVel, xVel); 1478 if (negative) { 1479 vel = -vel; 1480 } 1481 1482 if (CHATTY) { 1483 Slog.d(TAG, String.format("gesture: vraw=(%f,%f) vnorm=(%f,%f) vlinear=%f", 1484 mVelocityTracker.getXVelocity(), 1485 mVelocityTracker.getYVelocity(), 1486 xVel, yVel, 1487 vel)); 1488 } 1489 1490 performFling(y + mViewDelta, vel, false); 1491 } 1492 1493 } 1494 return false; 1495 } 1496 1497 private void trackMovement(MotionEvent event) { 1498 // Add movement to velocity tracker using raw screen X and Y coordinates instead 1499 // of window coordinates because the window frame may be moving at the same time. 1500 float deltaX = event.getRawX() - event.getX(); 1501 float deltaY = event.getRawY() - event.getY(); 1502 event.offsetLocation(deltaX, deltaY); 1503 mVelocityTracker.addMovement(event); 1504 event.offsetLocation(-deltaX, -deltaY); 1505 } 1506 1507 @Override // CommandQueue 1508 public void setSystemUiVisibility(int vis) { 1509 final int old = mSystemUiVisibility; 1510 final int diff = vis ^ old; 1511 1512 if (diff != 0) { 1513 mSystemUiVisibility = vis; 1514 1515 if (0 != (diff & View.SYSTEM_UI_FLAG_LOW_PROFILE)) { 1516 final boolean lightsOut = (0 != (vis & View.SYSTEM_UI_FLAG_LOW_PROFILE)); 1517 if (lightsOut) { 1518 animateCollapse(); 1519 } 1520 if (mNavigationBarView != null) { 1521 mNavigationBarView.setLowProfile(lightsOut); 1522 } 1523 } 1524 1525 notifyUiVisibilityChanged(); 1526 } 1527 } 1528 1529 public void setLightsOn(boolean on) { 1530 Log.v(TAG, "setLightsOn(" + on + ")"); 1531 if (on) { 1532 setSystemUiVisibility(mSystemUiVisibility & ~View.SYSTEM_UI_FLAG_LOW_PROFILE); 1533 } else { 1534 setSystemUiVisibility(mSystemUiVisibility | View.SYSTEM_UI_FLAG_LOW_PROFILE); 1535 } 1536 } 1537 1538 private void notifyUiVisibilityChanged() { 1539 try { 1540 mWindowManager.statusBarVisibilityChanged(mSystemUiVisibility); 1541 } catch (RemoteException ex) { 1542 } 1543 } 1544 1545 public void topAppWindowChanged(boolean showMenu) { 1546 if (DEBUG) { 1547 Slog.d(TAG, (showMenu?"showing":"hiding") + " the MENU button"); 1548 } 1549 if (mNavigationBarView != null) { 1550 mNavigationBarView.setMenuVisibility(showMenu); 1551 } 1552 1553 // See above re: lights-out policy for legacy apps. 1554 if (showMenu) setLightsOn(true); 1555 } 1556 1557 // Not supported 1558 public void setImeWindowStatus(IBinder token, int vis, int backDisposition) { } 1559 @Override 1560 public void setHardKeyboardStatus(boolean available, boolean enabled) { } 1561 1562 public NotificationClicker makeClicker(PendingIntent intent, String pkg, String tag, int id) { 1563 return new NotificationClicker(intent, pkg, tag, id); 1564 } 1565 1566 private class NotificationClicker implements View.OnClickListener { 1567 private PendingIntent mIntent; 1568 private String mPkg; 1569 private String mTag; 1570 private int mId; 1571 1572 NotificationClicker(PendingIntent intent, String pkg, String tag, int id) { 1573 mIntent = intent; 1574 mPkg = pkg; 1575 mTag = tag; 1576 mId = id; 1577 } 1578 1579 public void onClick(View v) { 1580 try { 1581 // The intent we are sending is for the application, which 1582 // won't have permission to immediately start an activity after 1583 // the user switches to home. We know it is safe to do at this 1584 // point, so make sure new activity switches are now allowed. 1585 ActivityManagerNative.getDefault().resumeAppSwitches(); 1586 // Also, notifications can be launched from the lock screen, 1587 // so dismiss the lock screen when the activity starts. 1588 ActivityManagerNative.getDefault().dismissKeyguardOnNextActivity(); 1589 } catch (RemoteException e) { 1590 } 1591 1592 if (mIntent != null) { 1593 int[] pos = new int[2]; 1594 v.getLocationOnScreen(pos); 1595 Intent overlay = new Intent(); 1596 overlay.setSourceBounds( 1597 new Rect(pos[0], pos[1], pos[0]+v.getWidth(), pos[1]+v.getHeight())); 1598 try { 1599 mIntent.send(mContext, 0, overlay); 1600 } catch (PendingIntent.CanceledException e) { 1601 // the stack trace isn't very helpful here. Just log the exception message. 1602 Slog.w(TAG, "Sending contentIntent failed: " + e); 1603 } 1604 1605 KeyguardManager kgm = 1606 (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE); 1607 if (kgm != null) kgm.exitKeyguardSecurely(null); 1608 } 1609 1610 try { 1611 mBarService.onNotificationClick(mPkg, mTag, mId); 1612 } catch (RemoteException ex) { 1613 // system process is dead if we're here. 1614 } 1615 1616 // close the shade if it was open 1617 animateCollapse(); 1618 1619 // If this click was on the intruder alert, hide that instead 1620 mHandler.sendEmptyMessage(MSG_HIDE_INTRUDER); 1621 } 1622 } 1623 1624 private void tick(StatusBarNotification n) { 1625 // Show the ticker if one is requested. Also don't do this 1626 // until status bar window is attached to the window manager, 1627 // because... well, what's the point otherwise? And trying to 1628 // run a ticker without being attached will crash! 1629 if (n.notification.tickerText != null && mStatusBarView.getWindowToken() != null) { 1630 if (0 == (mDisabled & (StatusBarManager.DISABLE_NOTIFICATION_ICONS 1631 | StatusBarManager.DISABLE_NOTIFICATION_TICKER))) { 1632 mTicker.addEntry(n); 1633 } 1634 } 1635 } 1636 1637 /** 1638 * Cancel this notification and tell the StatusBarManagerService / NotificationManagerService 1639 * about the failure. 1640 * 1641 * WARNING: this will call back into us. Don't hold any locks. 1642 */ 1643 void handleNotificationError(IBinder key, StatusBarNotification n, String message) { 1644 removeNotification(key); 1645 try { 1646 mBarService.onNotificationError(n.pkg, n.tag, n.id, n.uid, n.initialPid, message); 1647 } catch (RemoteException ex) { 1648 // The end is nigh. 1649 } 1650 } 1651 1652 private class MyTicker extends Ticker { 1653 MyTicker(Context context, View sb) { 1654 super(context, sb); 1655 } 1656 1657 @Override 1658 public void tickerStarting() { 1659 mTicking = true; 1660 mIcons.setVisibility(View.GONE); 1661 mTickerView.setVisibility(View.VISIBLE); 1662 mTickerView.startAnimation(loadAnim(com.android.internal.R.anim.push_up_in, null)); 1663 mIcons.startAnimation(loadAnim(com.android.internal.R.anim.push_up_out, null)); 1664 } 1665 1666 @Override 1667 public void tickerDone() { 1668 mIcons.setVisibility(View.VISIBLE); 1669 mTickerView.setVisibility(View.GONE); 1670 mIcons.startAnimation(loadAnim(com.android.internal.R.anim.push_down_in, null)); 1671 mTickerView.startAnimation(loadAnim(com.android.internal.R.anim.push_down_out, 1672 mTickingDoneListener)); 1673 } 1674 1675 public void tickerHalting() { 1676 mIcons.setVisibility(View.VISIBLE); 1677 mTickerView.setVisibility(View.GONE); 1678 mIcons.startAnimation(loadAnim(com.android.internal.R.anim.fade_in, null)); 1679 mTickerView.startAnimation(loadAnim(com.android.internal.R.anim.fade_out, 1680 mTickingDoneListener)); 1681 } 1682 } 1683 1684 Animation.AnimationListener mTickingDoneListener = new Animation.AnimationListener() {; 1685 public void onAnimationEnd(Animation animation) { 1686 mTicking = false; 1687 } 1688 public void onAnimationRepeat(Animation animation) { 1689 } 1690 public void onAnimationStart(Animation animation) { 1691 } 1692 }; 1693 1694 private Animation loadAnim(int id, Animation.AnimationListener listener) { 1695 Animation anim = AnimationUtils.loadAnimation(mContext, id); 1696 if (listener != null) { 1697 anim.setAnimationListener(listener); 1698 } 1699 return anim; 1700 } 1701 1702 public String viewInfo(View v) { 1703 return "(" + v.getLeft() + "," + v.getTop() + ")(" + v.getRight() + "," + v.getBottom() 1704 + " " + v.getWidth() + "x" + v.getHeight() + ")"; 1705 } 1706 1707 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 1708 synchronized (mQueueLock) { 1709 pw.println("Current Status Bar state:"); 1710 pw.println(" mExpanded=" + mExpanded 1711 + ", mExpandedVisible=" + mExpandedVisible); 1712 pw.println(" mTicking=" + mTicking); 1713 pw.println(" mTracking=" + mTracking); 1714 pw.println(" mAnimating=" + mAnimating 1715 + ", mAnimY=" + mAnimY + ", mAnimVel=" + mAnimVel 1716 + ", mAnimAccel=" + mAnimAccel); 1717 pw.println(" mCurAnimationTime=" + mCurAnimationTime 1718 + " mAnimLastTime=" + mAnimLastTime); 1719 pw.println(" mAnimatingReveal=" + mAnimatingReveal 1720 + " mViewDelta=" + mViewDelta); 1721 pw.println(" mDisplayMetrics=" + mDisplayMetrics); 1722 pw.println(" mExpandedParams: " + mExpandedParams); 1723 pw.println(" mExpandedView: " + viewInfo(mExpandedView)); 1724 pw.println(" mExpandedDialog: " + mExpandedDialog); 1725 pw.println(" mTrackingParams: " + mTrackingParams); 1726 pw.println(" mTrackingView: " + viewInfo(mTrackingView)); 1727 pw.println(" mPile: " + viewInfo(mPile)); 1728 pw.println(" mNoNotificationsTitle: " + viewInfo(mNoNotificationsTitle)); 1729 pw.println(" mCloseView: " + viewInfo(mCloseView)); 1730 pw.println(" mTickerView: " + viewInfo(mTickerView)); 1731 pw.println(" mScrollView: " + viewInfo(mScrollView) 1732 + " scroll " + mScrollView.getScrollX() + "," + mScrollView.getScrollY()); 1733 } 1734 1735 if (DUMPTRUCK) { 1736 synchronized (mNotificationData) { 1737 int N = mNotificationData.size(); 1738 pw.println(" notification icons: " + N); 1739 for (int i=0; i<N; i++) { 1740 NotificationData.Entry e = mNotificationData.get(i); 1741 pw.println(" [" + i + "] key=" + e.key + " icon=" + e.icon); 1742 StatusBarNotification n = e.notification; 1743 pw.println(" pkg=" + n.pkg + " id=" + n.id + " priority=" + n.priority); 1744 pw.println(" notification=" + n.notification); 1745 pw.println(" tickerText=\"" + n.notification.tickerText + "\""); 1746 } 1747 } 1748 1749 int N = mStatusIcons.getChildCount(); 1750 pw.println(" system icons: " + N); 1751 for (int i=0; i<N; i++) { 1752 StatusBarIconView ic = (StatusBarIconView) mStatusIcons.getChildAt(i); 1753 pw.println(" [" + i + "] icon=" + ic); 1754 } 1755 1756 if (false) { 1757 pw.println("see the logcat for a dump of the views we have created."); 1758 // must happen on ui thread 1759 mHandler.post(new Runnable() { 1760 public void run() { 1761 mStatusBarView.getLocationOnScreen(mAbsPos); 1762 Slog.d(TAG, "mStatusBarView: ----- (" + mAbsPos[0] + "," + mAbsPos[1] 1763 + ") " + mStatusBarView.getWidth() + "x" 1764 + mStatusBarView.getHeight()); 1765 mStatusBarView.debug(); 1766 1767 mExpandedView.getLocationOnScreen(mAbsPos); 1768 Slog.d(TAG, "mExpandedView: ----- (" + mAbsPos[0] + "," + mAbsPos[1] 1769 + ") " + mExpandedView.getWidth() + "x" 1770 + mExpandedView.getHeight()); 1771 mExpandedView.debug(); 1772 1773 mTrackingView.getLocationOnScreen(mAbsPos); 1774 Slog.d(TAG, "mTrackingView: ----- (" + mAbsPos[0] + "," + mAbsPos[1] 1775 + ") " + mTrackingView.getWidth() + "x" 1776 + mTrackingView.getHeight()); 1777 mTrackingView.debug(); 1778 } 1779 }); 1780 } 1781 } 1782 1783 mNetworkController.dump(fd, pw, args); 1784 } 1785 1786 void onBarViewAttached() { 1787 WindowManager.LayoutParams lp; 1788 int pixelFormat; 1789 Drawable bg; 1790 1791 /// ---------- Tracking View -------------- 1792 bg = mTrackingView.getBackground(); 1793 if (bg != null) { 1794 pixelFormat = bg.getOpacity(); 1795 } 1796 1797 lp = new WindowManager.LayoutParams( 1798 ViewGroup.LayoutParams.MATCH_PARENT, 1799 ViewGroup.LayoutParams.MATCH_PARENT, 1800 WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL, 1801 0 1802 | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN 1803 | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS 1804 | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM, 1805 PixelFormat.TRANSLUCENT); 1806 if (ActivityManager.isHighEndGfx(mDisplay)) { 1807 lp.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED; 1808 } 1809// lp.token = mStatusBarView.getWindowToken(); 1810 lp.gravity = Gravity.TOP | Gravity.FILL_HORIZONTAL; 1811 lp.setTitle("TrackingView"); 1812 lp.y = mTrackingPosition; 1813 mTrackingParams = lp; 1814 1815 WindowManagerImpl.getDefault().addView(mTrackingView, lp); 1816 } 1817 1818 void onTrackingViewAttached() { 1819 WindowManager.LayoutParams lp; 1820 int pixelFormat; 1821 1822 /// ---------- Expanded View -------------- 1823 pixelFormat = PixelFormat.TRANSLUCENT; 1824 1825 lp = mExpandedDialog.getWindow().getAttributes(); 1826 lp.x = 0; 1827 mTrackingPosition = lp.y = mDisplayMetrics.heightPixels; // sufficiently large negative 1828 lp.type = WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL; 1829 lp.flags = 0 1830 | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN 1831 | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS 1832 | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL 1833 | WindowManager.LayoutParams.FLAG_DITHER 1834 | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; 1835 if (ActivityManager.isHighEndGfx(mDisplay)) { 1836 lp.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED; 1837 } 1838 lp.format = pixelFormat; 1839 lp.gravity = Gravity.TOP | Gravity.FILL_HORIZONTAL; 1840 lp.setTitle("StatusBarExpanded"); 1841 mExpandedParams = lp; 1842 updateExpandedSize(); 1843 mExpandedDialog.getWindow().setFormat(pixelFormat); 1844 1845 mExpandedDialog.getWindow().requestFeature(Window.FEATURE_NO_TITLE); 1846 mExpandedDialog.setContentView(mExpandedView, 1847 new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 1848 ViewGroup.LayoutParams.MATCH_PARENT)); 1849 mExpandedDialog.getWindow().setBackgroundDrawable(null); 1850 mExpandedDialog.show(); 1851 } 1852 1853 void setNotificationIconVisibility(boolean visible, int anim) { 1854 int old = mNotificationIcons.getVisibility(); 1855 int v = visible ? View.VISIBLE : View.INVISIBLE; 1856 if (old != v) { 1857 mNotificationIcons.setVisibility(v); 1858 mNotificationIcons.startAnimation(loadAnim(anim, null)); 1859 } 1860 } 1861 1862 void updateExpandedInvisiblePosition() { 1863 if (mTrackingView != null) { 1864 mTrackingPosition = -mDisplayMetrics.heightPixels; 1865 if (mTrackingParams != null) { 1866 mTrackingParams.y = mTrackingPosition; 1867 WindowManagerImpl.getDefault().updateViewLayout(mTrackingView, mTrackingParams); 1868 } 1869 } 1870 if (mExpandedParams != null) { 1871 mExpandedParams.y = -mDisplayMetrics.heightPixels; 1872 mExpandedDialog.getWindow().setAttributes(mExpandedParams); 1873 } 1874 } 1875 1876 void updateExpandedViewPos(int expandedPosition) { 1877 if (SPEW) { 1878 Slog.d(TAG, "updateExpandedViewPos before expandedPosition=" + expandedPosition 1879 + " mTrackingParams.y=" + ((mTrackingParams == null) ? "?" : mTrackingParams.y) 1880 + " mTrackingPosition=" + mTrackingPosition); 1881 } 1882 1883 int h = mStatusBarView.getHeight(); 1884 int disph = mDisplayMetrics.heightPixels; 1885 1886 // If the expanded view is not visible, make sure they're still off screen. 1887 // Maybe the view was resized. 1888 if (!mExpandedVisible) { 1889 updateExpandedInvisiblePosition(); 1890 return; 1891 } 1892 1893 // tracking view... 1894 int pos; 1895 if (expandedPosition == EXPANDED_FULL_OPEN) { 1896 pos = h; 1897 } 1898 else if (expandedPosition == EXPANDED_LEAVE_ALONE) { 1899 pos = mTrackingPosition; 1900 } 1901 else { 1902 if (expandedPosition <= disph) { 1903 pos = expandedPosition; 1904 } else { 1905 pos = disph; 1906 } 1907 pos -= disph-h; 1908 } 1909 mTrackingPosition = mTrackingParams.y = pos; 1910 mTrackingParams.height = disph-h; 1911 WindowManagerImpl.getDefault().updateViewLayout(mTrackingView, mTrackingParams); 1912 1913 if (mExpandedParams != null) { 1914 if (mCloseView.getWindowVisibility() == View.VISIBLE) { 1915 mCloseView.getLocationInWindow(mPositionTmp); 1916 final int closePos = mPositionTmp[1]; 1917 1918 mExpandedContents.getLocationInWindow(mPositionTmp); 1919 final int contentsBottom = mPositionTmp[1] + mExpandedContents.getHeight(); 1920 1921 mExpandedParams.y = pos + mTrackingView.getHeight() 1922 - (mTrackingParams.height-closePos) - contentsBottom; 1923 1924 if (SPEW) { 1925 Slog.d(PhoneStatusBar.TAG, 1926 "pos=" + pos + 1927 " trackingHeight=" + mTrackingView.getHeight() + 1928 " (trackingParams.height - closePos)=" + 1929 (mTrackingParams.height - closePos) + 1930 " contentsBottom=" + contentsBottom); 1931 } 1932 1933 } else { 1934 // If the tracking view is not yet visible, then we can't have 1935 // a good value of the close view location. We need to wait for 1936 // it to be visible to do a layout. 1937 mExpandedParams.y = -mDisplayMetrics.heightPixels; 1938 } 1939 int max = h; 1940 if (mExpandedParams.y > max) { 1941 mExpandedParams.y = max; 1942 } 1943 int min = mTrackingPosition; 1944 if (mExpandedParams.y < min) { 1945 mExpandedParams.y = min; 1946 } 1947 1948 boolean visible = (mTrackingPosition + mTrackingView.getHeight()) > h; 1949 if (!visible) { 1950 // if the contents aren't visible, move the expanded view way off screen 1951 // because the window itself extends below the content view. 1952 mExpandedParams.y = -disph; 1953 } 1954 mExpandedDialog.getWindow().setAttributes(mExpandedParams); 1955 1956 // As long as this isn't just a repositioning that's not supposed to affect 1957 // the user's perception of what's showing, call to say that the visibility 1958 // has changed. (Otherwise, someone else will call to do that). 1959 if (expandedPosition != EXPANDED_LEAVE_ALONE) { 1960 if (SPEW) Slog.d(TAG, "updateExpandedViewPos visibilityChanged(" + visible + ")"); 1961 visibilityChanged(visible); 1962 } 1963 } 1964 1965 if (SPEW) { 1966 Slog.d(TAG, "updateExpandedViewPos after expandedPosition=" + expandedPosition 1967 + " mTrackingParams.y=" + mTrackingParams.y 1968 + " mTrackingPosition=" + mTrackingPosition 1969 + " mExpandedParams.y=" + mExpandedParams.y 1970 + " mExpandedParams.height=" + mExpandedParams.height); 1971 } 1972 } 1973 1974 int getExpandedHeight(int disph) { 1975 if (DEBUG) { 1976 Slog.d(TAG, "getExpandedHeight(" + disph + "): sbView=" 1977 + mStatusBarView.getHeight() + " closeView=" + mCloseView.getHeight()); 1978 } 1979 return disph - mStatusBarView.getHeight() - mCloseView.getHeight(); 1980 } 1981 1982 void updateDisplaySize() { 1983 mDisplay.getMetrics(mDisplayMetrics); 1984 if (DEBUG) { 1985 Slog.d(TAG, "updateDisplaySize: " + mDisplayMetrics); 1986 } 1987 updateExpandedSize(); 1988 } 1989 1990 void updateExpandedSize() { 1991 if (DEBUG) { 1992 Slog.d(TAG, "updateExpandedSize()"); 1993 } 1994 if (mExpandedDialog != null && mExpandedParams != null && mDisplayMetrics != null) { 1995 mExpandedParams.width = mDisplayMetrics.widthPixels; 1996 mExpandedParams.height = getExpandedHeight(mDisplayMetrics.heightPixels); 1997 if (!mExpandedVisible) { 1998 updateExpandedInvisiblePosition(); 1999 } else { 2000 mExpandedDialog.getWindow().setAttributes(mExpandedParams); 2001 } 2002 if (DEBUG) { 2003 Slog.d(TAG, "updateExpandedSize: height=" + mExpandedParams.height + " " + 2004 (mExpandedVisible ? "VISIBLE":"INVISIBLE")); 2005 } 2006 } 2007 } 2008 2009 public void toggleRecentApps() { 2010 int msg = (mRecentsPanel.getVisibility() == View.GONE) 2011 ? MSG_OPEN_RECENTS_PANEL : MSG_CLOSE_RECENTS_PANEL; 2012 mHandler.removeMessages(msg); 2013 mHandler.sendEmptyMessage(msg); 2014 } 2015 2016 /** 2017 * The LEDs are turned o)ff when the notification panel is shown, even just a little bit. 2018 * This was added last-minute and is inconsistent with the way the rest of the notifications 2019 * are handled, because the notification isn't really cancelled. The lights are just 2020 * turned off. If any other notifications happen, the lights will turn back on. Steve says 2021 * this is what he wants. (see bug 1131461) 2022 */ 2023 void visibilityChanged(boolean visible) { 2024 if (mPanelSlightlyVisible != visible) { 2025 mPanelSlightlyVisible = visible; 2026 try { 2027 mBarService.onPanelRevealed(); 2028 } catch (RemoteException ex) { 2029 // Won't fail unless the world has ended. 2030 } 2031 } 2032 } 2033 2034 void performDisableActions(int net) { 2035 int old = mDisabled; 2036 int diff = net ^ old; 2037 mDisabled = net; 2038 2039 // act accordingly 2040 if ((diff & StatusBarManager.DISABLE_EXPAND) != 0) { 2041 if ((net & StatusBarManager.DISABLE_EXPAND) != 0) { 2042 Slog.d(TAG, "DISABLE_EXPAND: yes"); 2043 animateCollapse(); 2044 } 2045 } 2046 if ((diff & StatusBarManager.DISABLE_NOTIFICATION_ICONS) != 0) { 2047 if ((net & StatusBarManager.DISABLE_NOTIFICATION_ICONS) != 0) { 2048 Slog.d(TAG, "DISABLE_NOTIFICATION_ICONS: yes"); 2049 if (mTicking) { 2050 mNotificationIcons.setVisibility(View.INVISIBLE); 2051 mTicker.halt(); 2052 } else { 2053 setNotificationIconVisibility(false, com.android.internal.R.anim.fade_out); 2054 } 2055 } else { 2056 Slog.d(TAG, "DISABLE_NOTIFICATION_ICONS: no"); 2057 if (!mExpandedVisible) { 2058 setNotificationIconVisibility(true, com.android.internal.R.anim.fade_in); 2059 } 2060 } 2061 } else if ((diff & StatusBarManager.DISABLE_NOTIFICATION_TICKER) != 0) { 2062 if (mTicking && (net & StatusBarManager.DISABLE_NOTIFICATION_TICKER) != 0) { 2063 mTicker.halt(); 2064 } 2065 } 2066 } 2067 2068 private View.OnClickListener mClearButtonListener = new View.OnClickListener() { 2069 final int mini(int a, int b) { 2070 return (b>a?a:b); 2071 } 2072 public void onClick(View v) { 2073 synchronized (mNotificationData) { 2074 // animate-swipe all dismissable notifications, then animate the shade closed 2075 int numChildren = mPile.getChildCount(); 2076 2077 int scrollTop = mScrollView.getScrollY(); 2078 int scrollBottom = scrollTop + mScrollView.getHeight(); 2079 final ArrayList<View> snapshot = new ArrayList<View>(numChildren); 2080 for (int i=0; i<numChildren; i++) { 2081 final View child = mPile.getChildAt(i); 2082 if (mPile.canChildBeDismissed(child) && child.getBottom() > scrollTop && 2083 child.getTop() < scrollBottom) { 2084 snapshot.add(child); 2085 } 2086 } 2087 final int N = snapshot.size(); 2088 new Thread(new Runnable() { 2089 @Override 2090 public void run() { 2091 // Decrease the delay for every row we animate to give the sense of 2092 // accelerating the swipes 2093 final int ROW_DELAY_DECREMENT = 10; 2094 int currentDelay = 140; 2095 int totalDelay = 0; 2096 2097 // Set the shade-animating state to avoid doing other work during 2098 // all of these animations. In particular, avoid layout and 2099 // redrawing when collapsing the shade. 2100 mPile.setViewRemoval(false); 2101 2102 mPostCollapseCleanup = new Runnable() { 2103 public void run() { 2104 try { 2105 mPile.setViewRemoval(true); 2106 mBarService.onClearAllNotifications(); 2107 } catch (Exception ex) { } 2108 } 2109 }; 2110 2111 View sampleView = snapshot.get(0); 2112 int width = sampleView.getWidth(); 2113 final int velocity = (int)(width * 8); // 1000/8 = 125 ms duration 2114 for (View v : snapshot) { 2115 final View _v = v; 2116 mHandler.postDelayed(new Runnable() { 2117 @Override 2118 public void run() { 2119 mPile.dismissRowAnimated(_v, velocity); 2120 } 2121 }, totalDelay); 2122 currentDelay = Math.max(50, currentDelay - ROW_DELAY_DECREMENT); 2123 totalDelay += currentDelay; 2124 } 2125 // Delay the collapse animation until after all swipe animations have 2126 // finished. Provide some buffer because there may be some extra delay 2127 // before actually starting each swipe animation. Ideally, we'd 2128 // synchronize the end of those animations with the start of the collaps 2129 // exactly. 2130 mHandler.postDelayed(new Runnable() { 2131 public void run() { 2132 animateCollapse(false); 2133 } 2134 }, totalDelay + 225); 2135 } 2136 }).start(); 2137 } 2138 } 2139 }; 2140 2141 private View.OnClickListener mSettingsButtonListener = new View.OnClickListener() { 2142 public void onClick(View v) { 2143 v.getContext().startActivity(new Intent(Settings.ACTION_SETTINGS) 2144 .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)); 2145 animateCollapse(); 2146 } 2147 }; 2148 2149 private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { 2150 public void onReceive(Context context, Intent intent) { 2151 String action = intent.getAction(); 2152 if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action) 2153 || Intent.ACTION_SCREEN_OFF.equals(action)) { 2154 boolean excludeRecents = false; 2155 if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action)) { 2156 String reason = intent.getStringExtra("reason"); 2157 if (reason != null) { 2158 excludeRecents = reason.equals("recentapps"); 2159 } 2160 } 2161 animateCollapse(excludeRecents); 2162 } 2163 else if (Intent.ACTION_CONFIGURATION_CHANGED.equals(action)) { 2164 repositionNavigationBar(); 2165 updateResources(); 2166 } 2167 } 2168 }; 2169 2170 private void setIntruderAlertVisibility(boolean vis) { 2171 mIntruderAlertView.setVisibility(vis ? View.VISIBLE : View.GONE); 2172 } 2173 2174 /** 2175 * Reload some of our resources when the configuration changes. 2176 * 2177 * We don't reload everything when the configuration changes -- we probably 2178 * should, but getting that smooth is tough. Someday we'll fix that. In the 2179 * meantime, just update the things that we know change. 2180 */ 2181 void updateResources() { 2182 final Context context = mContext; 2183 final Resources res = context.getResources(); 2184 2185 if (mClearButton instanceof TextView) { 2186 ((TextView)mClearButton).setText(context.getText(R.string.status_bar_clear_all_button)); 2187 } 2188 mNoNotificationsTitle.setText(context.getText(R.string.status_bar_no_notifications_title)); 2189 2190 loadDimens(); 2191 } 2192 2193 protected void loadDimens() { 2194 final Resources res = mContext.getResources(); 2195 2196 mNaturalBarHeight = res.getDimensionPixelSize( 2197 com.android.internal.R.dimen.status_bar_height); 2198 2199 int newIconSize = res.getDimensionPixelSize( 2200 com.android.internal.R.dimen.status_bar_icon_size); 2201 int newIconHPadding = res.getDimensionPixelSize( 2202 R.dimen.status_bar_icon_padding); 2203 2204 if (newIconHPadding != mIconHPadding || newIconSize != mIconSize) { 2205// Slog.d(TAG, "size=" + newIconSize + " padding=" + newIconHPadding); 2206 mIconHPadding = newIconHPadding; 2207 mIconSize = newIconSize; 2208 //reloadAllNotificationIcons(); // reload the tray 2209 } 2210 2211 mEdgeBorder = res.getDimensionPixelSize(R.dimen.status_bar_edge_ignore); 2212 2213 mSelfExpandVelocityPx = res.getDimension(R.dimen.self_expand_velocity); 2214 mSelfCollapseVelocityPx = res.getDimension(R.dimen.self_collapse_velocity); 2215 mFlingExpandMinVelocityPx = res.getDimension(R.dimen.fling_expand_min_velocity); 2216 mFlingCollapseMinVelocityPx = res.getDimension(R.dimen.fling_collapse_min_velocity); 2217 2218 mCollapseMinDisplayFraction = res.getFraction(R.dimen.collapse_min_display_fraction, 1, 1); 2219 mExpandMinDisplayFraction = res.getFraction(R.dimen.expand_min_display_fraction, 1, 1); 2220 2221 mExpandAccelPx = res.getDimension(R.dimen.expand_accel); 2222 mCollapseAccelPx = res.getDimension(R.dimen.collapse_accel); 2223 2224 mFlingGestureMaxXVelocityPx = res.getDimension(R.dimen.fling_gesture_max_x_velocity); 2225 2226 if (false) Slog.v(TAG, "updateResources"); 2227 } 2228 2229 // 2230 // tracing 2231 // 2232 2233 void postStartTracing() { 2234 mHandler.postDelayed(mStartTracing, 3000); 2235 } 2236 2237 void vibrate() { 2238 android.os.Vibrator vib = (android.os.Vibrator)mContext.getSystemService( 2239 Context.VIBRATOR_SERVICE); 2240 vib.vibrate(250); 2241 } 2242 2243 Runnable mStartTracing = new Runnable() { 2244 public void run() { 2245 vibrate(); 2246 SystemClock.sleep(250); 2247 Slog.d(TAG, "startTracing"); 2248 android.os.Debug.startMethodTracing("/data/statusbar-traces/trace"); 2249 mHandler.postDelayed(mStopTracing, 10000); 2250 } 2251 }; 2252 2253 Runnable mStopTracing = new Runnable() { 2254 public void run() { 2255 android.os.Debug.stopMethodTracing(); 2256 Slog.d(TAG, "stopTracing"); 2257 vibrate(); 2258 } 2259 }; 2260 2261 public class TouchOutsideListener implements View.OnTouchListener { 2262 private int mMsg; 2263 private RecentsPanelView mPanel; 2264 2265 public TouchOutsideListener(int msg, RecentsPanelView panel) { 2266 mMsg = msg; 2267 mPanel = panel; 2268 } 2269 2270 public boolean onTouch(View v, MotionEvent ev) { 2271 final int action = ev.getAction(); 2272 if (action == MotionEvent.ACTION_OUTSIDE 2273 || (action == MotionEvent.ACTION_DOWN 2274 && !mPanel.isInContentArea((int)ev.getX(), (int)ev.getY()))) { 2275 mHandler.removeMessages(mMsg); 2276 mHandler.sendEmptyMessage(mMsg); 2277 return true; 2278 } 2279 return false; 2280 } 2281 } 2282} 2283 2284