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