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