TabletStatusBar.java revision ddbba42b812ab397a75db78aba6b48fecf0e31f6
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.tablet; 18 19import java.io.FileDescriptor; 20import java.io.PrintWriter; 21import java.util.ArrayList; 22import java.util.Map; 23import java.util.IdentityHashMap; 24 25import android.animation.LayoutTransition; 26import android.animation.ObjectAnimator; 27import android.animation.AnimatorSet; 28import android.app.ActivityManagerNative; 29import android.app.PendingIntent; 30import android.app.Notification; 31import android.app.StatusBarManager; 32import android.content.Context; 33import android.content.Intent; 34import android.content.res.Resources; 35import android.graphics.PixelFormat; 36import android.graphics.Rect; 37import android.graphics.drawable.Drawable; 38import android.graphics.drawable.LayerDrawable; 39import android.os.Handler; 40import android.os.IBinder; 41import android.os.Message; 42import android.os.RemoteException; 43import android.text.TextUtils; 44import android.util.Slog; 45import android.view.animation.Animation; 46import android.view.animation.AnimationUtils; 47import android.view.Gravity; 48import android.view.LayoutInflater; 49import android.view.MotionEvent; 50import android.view.VelocityTracker; 51import android.view.View; 52import android.view.ViewConfiguration; 53import android.view.ViewGroup; 54import android.view.WindowManager; 55import android.view.WindowManagerImpl; 56import android.widget.FrameLayout; 57import android.widget.ImageView; 58import android.widget.LinearLayout; 59import android.widget.RemoteViews; 60import android.widget.ScrollView; 61import android.widget.TextSwitcher; 62import android.widget.TextView; 63 64import com.android.internal.statusbar.StatusBarIcon; 65import com.android.internal.statusbar.StatusBarNotification; 66 67import com.android.systemui.R; 68import com.android.systemui.statusbar.*; 69import com.android.systemui.statusbar.policy.BatteryController; 70import com.android.systemui.statusbar.policy.NetworkController; 71import com.android.systemui.recent.RecentApplicationsActivity; 72 73public class TabletStatusBar extends StatusBar { 74 public static final boolean DEBUG = false; 75 public static final String TAG = "TabletStatusBar"; 76 77 public static final int MSG_OPEN_NOTIFICATION_PANEL = 1000; 78 public static final int MSG_CLOSE_NOTIFICATION_PANEL = 1001; 79 public static final int MSG_OPEN_NOTIFICATION_PEEK = 1002; 80 public static final int MSG_CLOSE_NOTIFICATION_PEEK = 1003; 81 public static final int MSG_OPEN_RECENTS_PANEL = 1020; 82 public static final int MSG_CLOSE_RECENTS_PANEL = 1021; 83 public static final int MSG_HIDE_SHADOWS = 1030; 84 public static final int MSG_SHOW_SHADOWS = 1031; 85 public static final int MSG_RESTORE_SHADOWS = 1032; 86 87 private static final int MAX_IMAGE_LEVEL = 10000; 88 private static final boolean USE_2D_RECENTS = true; 89 90 public static final int LIGHTS_ON_DELAY = 5000; 91 92 int mIconSize; 93 94 H mHandler = new H(); 95 96 // tracking all current notifications 97 private NotificationData mNotns = new NotificationData(); 98 99 TabletStatusBarView mStatusBarView; 100 View mNotificationArea; 101 View mNotificationTrigger; 102 NotificationIconArea mNotificationIconArea; 103 View mNavigationArea; 104 105 View mBackButton; 106 View mHomeButton; 107 View mMenuButton; 108 View mRecentButton; 109 110 InputMethodButton mInputMethodButton; 111 112 NotificationPanel mNotificationPanel; 113 NotificationPeekPanel mNotificationPeekWindow; 114 ViewGroup mNotificationPeekRow; 115 int mNotificationPeekIndex; 116 LayoutTransition mNotificationPeekScrubLeft, mNotificationPeekScrubRight; 117 118 int mNotificationPeekTapDuration; 119 int mNotificationFlingVelocity; 120 121 ViewGroup mPile; 122 123 BatteryController mBatteryController; 124 NetworkController mNetworkController; 125 126 View mBarContents; 127 128 // lights out support 129 View mBackShadow, mHomeShadow, mRecentShadow, mMenuShadow, mNotificationShadow; 130 ShadowController mShadowController; 131 132 NotificationIconArea.IconLayout mIconLayout; 133 134 TabletTicker mTicker; 135 136 // for disabling the status bar 137 int mDisabled = 0; 138 139 boolean mNotificationsOn = true; 140 private RecentAppsPanel mRecentsPanel; 141 142 protected void addPanelWindows() { 143 final Context context = mContext; 144 145 final Resources res = context.getResources(); 146 final int barHeight= res.getDimensionPixelSize( 147 com.android.internal.R.dimen.status_bar_height); 148 149 // Notification Panel 150 mNotificationPanel = (NotificationPanel)View.inflate(context, 151 R.layout.sysbar_panel_notifications, null); 152 mNotificationPanel.setVisibility(View.GONE); 153 mNotificationPanel.setOnTouchListener( 154 new TouchOutsideListener(MSG_CLOSE_NOTIFICATION_PANEL, mNotificationPanel)); 155 156 // the battery and network icons 157 mBatteryController.addIconView((ImageView)mNotificationPanel.findViewById(R.id.battery)); 158 mBatteryController.addLabelView( 159 (TextView)mNotificationPanel.findViewById(R.id.battery_text)); 160 mNetworkController.addCombinedSignalIconView( 161 (ImageView)mNotificationPanel.findViewById(R.id.network)); 162 mNetworkController.addLabelView( 163 (TextView)mNotificationPanel.findViewById(R.id.network_text)); 164 165 mStatusBarView.setIgnoreChildren(0, mNotificationTrigger, mNotificationPanel); 166 167 WindowManager.LayoutParams lp = new WindowManager.LayoutParams( 168 ViewGroup.LayoutParams.MATCH_PARENT, 169 ViewGroup.LayoutParams.MATCH_PARENT, 170 WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL, 171 WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN 172 | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM 173 | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH, 174 PixelFormat.TRANSLUCENT); 175 lp.gravity = Gravity.BOTTOM | Gravity.RIGHT; 176 lp.setTitle("NotificationPanel"); 177 178 WindowManagerImpl.getDefault().addView(mNotificationPanel, lp); 179 180 // Notification preview window 181 mNotificationPeekWindow = (NotificationPeekPanel) View.inflate(context, 182 R.layout.sysbar_panel_notification_peek, null); 183 mNotificationPeekRow = (ViewGroup) mNotificationPeekWindow.findViewById(R.id.content); 184 mNotificationPeekWindow.setVisibility(View.GONE); 185 mNotificationPeekWindow.setOnTouchListener( 186 new TouchOutsideListener(MSG_CLOSE_NOTIFICATION_PANEL, mNotificationPeekWindow)); 187 mNotificationPeekScrubRight = new LayoutTransition(); 188 mNotificationPeekScrubRight.setAnimator(LayoutTransition.APPEARING, 189 ObjectAnimator.ofInt(null, "left", -512, 0)); 190 mNotificationPeekScrubRight.setAnimator(LayoutTransition.DISAPPEARING, 191 ObjectAnimator.ofInt(null, "left", -512, 0)); 192 mNotificationPeekScrubRight.setDuration(500); 193 194 mNotificationPeekScrubLeft = new LayoutTransition(); 195 mNotificationPeekScrubLeft.setAnimator(LayoutTransition.APPEARING, 196 ObjectAnimator.ofInt(null, "left", 512, 0)); 197 mNotificationPeekScrubLeft.setAnimator(LayoutTransition.DISAPPEARING, 198 ObjectAnimator.ofInt(null, "left", 512, 0)); 199 mNotificationPeekScrubLeft.setDuration(500); 200 201 // XXX: setIgnoreChildren? 202 lp = new WindowManager.LayoutParams( 203 512, // ViewGroup.LayoutParams.WRAP_CONTENT, 204 ViewGroup.LayoutParams.WRAP_CONTENT, 205 WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL, 206 WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN 207 | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM 208 | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH, 209 PixelFormat.TRANSLUCENT); 210 lp.gravity = Gravity.BOTTOM | Gravity.RIGHT; 211 lp.setTitle("NotificationPeekWindow"); 212 lp.windowAnimations = com.android.internal.R.style.Animation_Toast; 213 214 WindowManagerImpl.getDefault().addView(mNotificationPeekWindow, lp); 215 216 // Recents Panel 217 if (USE_2D_RECENTS) { 218 mRecentsPanel = (RecentAppsPanel) View.inflate(context, R.layout.sysbar_panel_recent, 219 null); 220 mRecentsPanel.setVisibility(View.GONE); 221 mRecentsPanel.setOnTouchListener(new TouchOutsideListener(MSG_CLOSE_RECENTS_PANEL, 222 mRecentsPanel)); 223 mStatusBarView.setIgnoreChildren(2, mRecentButton, mRecentsPanel); 224 225 lp = new WindowManager.LayoutParams( 226 ViewGroup.LayoutParams.WRAP_CONTENT, 227 ViewGroup.LayoutParams.WRAP_CONTENT, 228 WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL, 229 WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN 230 | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM 231 | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH, 232 PixelFormat.TRANSLUCENT); 233 lp.gravity = Gravity.BOTTOM | Gravity.LEFT; 234 lp.setTitle("RecentsPanel"); 235 lp.windowAnimations = R.style.Animation_RecentPanel; 236 237 WindowManagerImpl.getDefault().addView(mRecentsPanel, lp); 238 mRecentsPanel.setBar(this); 239 } 240 } 241 242 @Override 243 public void start() { 244 super.start(); // will add the main bar view 245 } 246 247 protected View makeStatusBarView() { 248 final Context context = mContext; 249 final Resources res = context.getResources(); 250 251 mIconSize = res.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_icon_size); 252 253 final TabletStatusBarView sb = (TabletStatusBarView)View.inflate( 254 context, R.layout.status_bar, null); 255 mStatusBarView = sb; 256 257 sb.setHandler(mHandler); 258 259 mBarContents = sb.findViewById(R.id.bar_contents); 260 261 // the whole right-hand side of the bar 262 mNotificationArea = sb.findViewById(R.id.notificationArea); 263 264 // the button to open the notification area 265 mNotificationTrigger = sb.findViewById(R.id.notificationTrigger); 266 mNotificationTrigger.setOnClickListener(mOnClickListener); 267 268 // the more notifications icon 269 mNotificationIconArea = (NotificationIconArea)sb.findViewById(R.id.notificationIcons); 270 271 // where the icons go 272 mIconLayout = (NotificationIconArea.IconLayout) sb.findViewById(R.id.icons); 273 mIconLayout.setOnTouchListener(new NotificationIconTouchListener()); 274 275 ViewConfiguration vc = ViewConfiguration.get(context); 276 mNotificationPeekTapDuration = vc.getTapTimeout(); 277 mNotificationFlingVelocity = 300; // px/s 278 279 mTicker = new TabletTicker(context); 280 281 // The icons 282 mBatteryController = new BatteryController(mContext); 283 mBatteryController.addIconView((ImageView)sb.findViewById(R.id.battery)); 284 mNetworkController = new NetworkController(mContext); 285 mNetworkController.addCombinedSignalIconView((ImageView)sb.findViewById(R.id.network)); 286 287 // The navigation buttons 288 mNavigationArea = sb.findViewById(R.id.navigationArea); 289 mBackButton = mNavigationArea.findViewById(R.id.back); 290 mHomeButton = mNavigationArea.findViewById(R.id.home); 291 mMenuButton = mNavigationArea.findViewById(R.id.menu); 292 mRecentButton = mNavigationArea.findViewById(R.id.recent_apps); 293 Slog.d(TAG, "rec=" + mRecentButton + ", listener=" + mOnClickListener); 294 mRecentButton.setOnClickListener(mOnClickListener); 295 296 // The bar contents buttons 297 mInputMethodButton = (InputMethodButton) sb.findViewById(R.id.imeButton); 298 299 // "shadows" of the status bar features, for lights-out mode 300 mBackShadow = sb.findViewById(R.id.back_shadow); 301 mHomeShadow = sb.findViewById(R.id.home_shadow); 302 mRecentShadow = sb.findViewById(R.id.recent_shadow); 303 mMenuShadow = sb.findViewById(R.id.menu_shadow); 304 mNotificationShadow = sb.findViewById(R.id.notification_shadow); 305 306 mShadowController = new ShadowController(false); 307 mShadowController.add(mBackButton, mBackShadow); 308 mShadowController.add(mHomeButton, mHomeShadow); 309 mShadowController.add(mRecentButton, mRecentShadow); 310 mShadowController.add(mMenuButton, mMenuShadow); 311 mShadowController.add(mNotificationArea, mNotificationShadow); 312 313 // set the initial view visibility 314 setAreThereNotifications(); 315 refreshNotificationTrigger(); 316 317 // Add the windows 318 addPanelWindows(); 319 320 mPile = (ViewGroup)mNotificationPanel.findViewById(R.id.content); 321 mPile.removeAllViews(); 322 323 ScrollView scroller = (ScrollView)mPile.getParent(); 324 scroller.setFillViewport(true); 325 326 return sb; 327 } 328 329 protected int getStatusBarGravity() { 330 return Gravity.BOTTOM | Gravity.FILL_HORIZONTAL; 331 } 332 333 private class H extends Handler { 334 public void handleMessage(Message m) { 335 switch (m.what) { 336 case MSG_OPEN_NOTIFICATION_PEEK: 337 if (DEBUG) Slog.d(TAG, "opening notification peek window; arg=" + m.arg1); 338 if (m.arg1 >= 0) { 339 final int N = mNotns.size(); 340 if (mNotificationPeekIndex < N) { 341 NotificationData.Entry entry = mNotns.get(N-1-mNotificationPeekIndex); 342 entry.icon.setBackgroundColor(0); 343 } 344 345 final int peekIndex = m.arg1; 346 if (peekIndex < N) { 347 Slog.d(TAG, "loading peek: " + peekIndex); 348 NotificationData.Entry entry = mNotns.get(N-1-peekIndex); 349 NotificationData.Entry copy = new NotificationData.Entry( 350 entry.key, 351 entry.notification, 352 entry.icon); 353 inflateViews(copy, mNotificationPeekRow); 354 355 entry.icon.setBackgroundColor(0x20FFFFFF); 356 357// mNotificationPeekRow.setLayoutTransition( 358// peekIndex < mNotificationPeekIndex 359// ? mNotificationPeekScrubLeft 360// : mNotificationPeekScrubRight); 361 362 mNotificationPeekRow.removeAllViews(); 363 mNotificationPeekRow.addView(copy.row); 364 365 mNotificationPeekWindow.setVisibility(View.VISIBLE); 366 mNotificationPanel.setVisibility(View.GONE); 367 368 mNotificationPeekIndex = peekIndex; 369 } 370 } 371 break; 372 case MSG_CLOSE_NOTIFICATION_PEEK: 373 if (DEBUG) Slog.d(TAG, "closing notification peek window"); 374 mNotificationPeekWindow.setVisibility(View.GONE); 375 mNotificationPeekRow.removeAllViews(); 376 final int N = mNotns.size(); 377 if (mNotificationPeekIndex < N) { 378 NotificationData.Entry entry = mNotns.get(N-1-mNotificationPeekIndex); 379 entry.icon.setBackgroundColor(0); 380 } 381 break; 382 case MSG_OPEN_NOTIFICATION_PANEL: 383 if (DEBUG) Slog.d(TAG, "opening notifications panel"); 384 if (mNotificationPanel.getVisibility() == View.GONE) { 385 mNotificationPeekWindow.setVisibility(View.GONE); 386 mNotificationPanel.setVisibility(View.VISIBLE); 387 // synchronize with current shadow state 388 mShadowController.hideElement(mNotificationArea); 389 } 390 break; 391 case MSG_CLOSE_NOTIFICATION_PANEL: 392 if (DEBUG) Slog.d(TAG, "closing notifications panel"); 393 if (mNotificationPanel.getVisibility() == View.VISIBLE) { 394 mNotificationPanel.setVisibility(View.GONE); 395 // synchronize with current shadow state 396 mShadowController.showElement(mNotificationArea); 397 } 398 break; 399 case MSG_OPEN_RECENTS_PANEL: 400 if (DEBUG) Slog.d(TAG, "opening recents panel"); 401 if (mRecentsPanel != null) mRecentsPanel.setVisibility(View.VISIBLE); 402 break; 403 case MSG_CLOSE_RECENTS_PANEL: 404 if (DEBUG) Slog.d(TAG, "closing recents panel"); 405 if (mRecentsPanel != null) mRecentsPanel.setVisibility(View.GONE); 406 break; 407 case MSG_HIDE_SHADOWS: 408 if (DEBUG) Slog.d(TAG, "hiding shadows (lights on)"); 409 mShadowController.hideAllShadows(); 410 break; 411 case MSG_SHOW_SHADOWS: 412 if (DEBUG) Slog.d(TAG, "showing shadows (lights out)"); 413 animateCollapse(); 414 mShadowController.showAllShadows(); 415 break; 416 case MSG_RESTORE_SHADOWS: 417 if (DEBUG) Slog.d(TAG, "quickly re-showing shadows if appropriate"); 418 mShadowController.refresh(); 419 break; 420 } 421 } 422 } 423 424 public void refreshNotificationTrigger() { 425 /* 426 if (mNotificationTrigger == null) return; 427 428 int resId; 429 boolean panel = (mNotificationPanel != null 430 && mNotificationPanel.getVisibility() == View.VISIBLE); 431 if (!mNotificationsOn) { 432 resId = R.drawable.ic_sysbar_noti_dnd; 433 } else if (mNotns.size() > 0) { 434 resId = panel ? R.drawable.ic_sysbar_noti_avail_open : R.drawable.ic_sysbar_noti_avail; 435 } else { 436 resId = panel ? R.drawable.ic_sysbar_noti_none_open : R.drawable.ic_sysbar_noti_none; 437 } 438 //mNotificationTrigger.setImageResource(resId); 439 */ 440 } 441 442 public void addIcon(String slot, int index, int viewIndex, StatusBarIcon icon) { 443 if (DEBUG) Slog.d(TAG, "addIcon(" + slot + ") -> " + icon); 444 } 445 446 public void updateIcon(String slot, int index, int viewIndex, 447 StatusBarIcon old, StatusBarIcon icon) { 448 if (DEBUG) Slog.d(TAG, "updateIcon(" + slot + ") -> " + icon); 449 } 450 451 public void removeIcon(String slot, int index, int viewIndex) { 452 if (DEBUG) Slog.d(TAG, "removeIcon(" + slot + ")"); 453 } 454 455 public void addNotification(IBinder key, StatusBarNotification notification) { 456 if (DEBUG) Slog.d(TAG, "addNotification(" + key + " -> " + notification + ")"); 457 addNotificationViews(key, notification); 458 459 boolean immersive = false; 460 try { 461 immersive = ActivityManagerNative.getDefault().isTopActivityImmersive(); 462 //Slog.d(TAG, "Top activity is " + (immersive?"immersive":"not immersive")); 463 } catch (RemoteException ex) { 464 } 465 if (false && immersive) { 466 // TODO: immersive mode popups for tablet 467 } else if (notification.notification.fullScreenIntent != null) { 468 // not immersive & a full-screen alert should be shown 469 Slog.d(TAG, "Notification has fullScreenIntent and activity is not immersive;" 470 + " sending fullScreenIntent"); 471 try { 472 notification.notification.fullScreenIntent.send(); 473 } catch (PendingIntent.CanceledException e) { 474 } 475 } else { 476 tick(notification); 477 } 478 479 setAreThereNotifications(); 480 } 481 482 public void updateNotification(IBinder key, StatusBarNotification notification) { 483 if (DEBUG) Slog.d(TAG, "updateNotification(" + key + " -> " + notification + ") // TODO"); 484 485 final NotificationData.Entry oldEntry = mNotns.findByKey(key); 486 if (oldEntry == null) { 487 Slog.w(TAG, "updateNotification for unknown key: " + key); 488 return; 489 } 490 491 final StatusBarNotification oldNotification = oldEntry.notification; 492 final RemoteViews oldContentView = oldNotification.notification.contentView; 493 494 final RemoteViews contentView = notification.notification.contentView; 495 496 if (false) { 497 Slog.d(TAG, "old notification: when=" + oldNotification.notification.when 498 + " ongoing=" + oldNotification.isOngoing() 499 + " expanded=" + oldEntry.expanded 500 + " contentView=" + oldContentView); 501 Slog.d(TAG, "new notification: when=" + notification.notification.when 502 + " ongoing=" + oldNotification.isOngoing() 503 + " contentView=" + contentView); 504 } 505 506 // Can we just reapply the RemoteViews in place? If when didn't change, the order 507 // didn't change. 508 if (notification.notification.when == oldNotification.notification.when 509 && notification.isOngoing() == oldNotification.isOngoing() 510 && oldEntry.expanded != null 511 && contentView != null 512 && oldContentView != null 513 && contentView.getPackage() != null 514 && oldContentView.getPackage() != null 515 && oldContentView.getPackage().equals(contentView.getPackage()) 516 && oldContentView.getLayoutId() == contentView.getLayoutId()) { 517 if (DEBUG) Slog.d(TAG, "reusing notification for key: " + key); 518 oldEntry.notification = notification; 519 try { 520 // Reapply the RemoteViews 521 contentView.reapply(mContext, oldEntry.content); 522 // update the contentIntent 523 final PendingIntent contentIntent = notification.notification.contentIntent; 524 if (contentIntent != null) { 525 oldEntry.content.setOnClickListener(new NotificationClicker(contentIntent, 526 notification.pkg, notification.tag, notification.id)); 527 } else { 528 oldEntry.content.setOnClickListener(null); 529 } 530 // Update the icon. 531 final StatusBarIcon ic = new StatusBarIcon(notification.pkg, 532 notification.notification.icon, notification.notification.iconLevel, 533 notification.notification.number); 534 if (!oldEntry.icon.set(ic)) { 535 handleNotificationError(key, notification, "Couldn't update icon: " + ic); 536 return; 537 } 538 } 539 catch (RuntimeException e) { 540 // It failed to add cleanly. Log, and remove the view from the panel. 541 Slog.w(TAG, "Couldn't reapply views for package " + contentView.getPackage(), e); 542 removeNotificationViews(key); 543 addNotificationViews(key, notification); 544 } 545 } else { 546 if (DEBUG) Slog.d(TAG, "not reusing notification for key: " + key); 547 removeNotificationViews(key); 548 addNotificationViews(key, notification); 549 } 550 // TODO: ticker; immersive mode 551 552 setAreThereNotifications(); 553 } 554 555 public void removeNotification(IBinder key) { 556 if (DEBUG) Slog.d(TAG, "removeNotification(" + key + ") // TODO"); 557 removeNotificationViews(key); 558 setAreThereNotifications(); 559 } 560 561 public void disable(int state) { 562 int old = mDisabled; 563 int diff = state ^ old; 564 mDisabled = state; 565 566 // act accordingly 567 if ((diff & StatusBarManager.DISABLE_EXPAND) != 0) { 568 if ((state & StatusBarManager.DISABLE_EXPAND) != 0) { 569 Slog.d(TAG, "DISABLE_EXPAND: yes"); 570 animateCollapse(); 571 } 572 } 573 if ((diff & StatusBarManager.DISABLE_NOTIFICATION_ICONS) != 0) { 574 if ((state & StatusBarManager.DISABLE_NOTIFICATION_ICONS) != 0) { 575 Slog.d(TAG, "DISABLE_NOTIFICATION_ICONS: yes"); 576 // synchronize with current shadow state 577 mShadowController.hideElement(mNotificationArea); 578 mTicker.halt(); 579 } else { 580 Slog.d(TAG, "DISABLE_NOTIFICATION_ICONS: no"); 581 // synchronize with current shadow state 582 mShadowController.showElement(mNotificationArea); 583 } 584 } else if ((diff & StatusBarManager.DISABLE_NOTIFICATION_TICKER) != 0) { 585 if ((state & StatusBarManager.DISABLE_NOTIFICATION_TICKER) != 0) { 586 mTicker.halt(); 587 } 588 } 589 if ((diff & StatusBarManager.DISABLE_NAVIGATION) != 0) { 590 if ((state & StatusBarManager.DISABLE_NAVIGATION) != 0) { 591 Slog.d(TAG, "DISABLE_NAVIGATION: yes"); 592 mNavigationArea.setVisibility(View.GONE); 593 } else { 594 Slog.d(TAG, "DISABLE_NAVIGATION: no"); 595 mNavigationArea.setVisibility(View.VISIBLE); 596 } 597 } 598 } 599 600 private boolean hasTicker(Notification n) { 601 return n.tickerView != null || !TextUtils.isEmpty(n.tickerText); 602 } 603 604 private void tick(StatusBarNotification n) { 605 // Don't show the ticker when the windowshade is open. 606 if (mNotificationPanel.getVisibility() == View.VISIBLE) { 607 return; 608 } 609 // Show the ticker if one is requested. Also don't do this 610 // until status bar window is attached to the window manager, 611 // because... well, what's the point otherwise? And trying to 612 // run a ticker without being attached will crash! 613 if (hasTicker(n.notification) && mStatusBarView.getWindowToken() != null) { 614 if (0 == (mDisabled & (StatusBarManager.DISABLE_NOTIFICATION_ICONS 615 | StatusBarManager.DISABLE_NOTIFICATION_TICKER))) { 616 mTicker.add(n); 617 } 618 } 619 } 620 621 public void animateExpand() { 622 mHandler.removeMessages(MSG_OPEN_NOTIFICATION_PANEL); 623 mHandler.sendEmptyMessage(MSG_OPEN_NOTIFICATION_PANEL); 624 } 625 626 public void animateCollapse() { 627 mHandler.removeMessages(MSG_CLOSE_NOTIFICATION_PANEL); 628 mHandler.sendEmptyMessage(MSG_CLOSE_NOTIFICATION_PANEL); 629 mHandler.removeMessages(MSG_CLOSE_RECENTS_PANEL); 630 mHandler.sendEmptyMessage(MSG_CLOSE_RECENTS_PANEL); 631 } 632 633 // called by StatusBar 634 @Override 635 public void setLightsOn(boolean on) { 636 mHandler.removeMessages(MSG_SHOW_SHADOWS); 637 mHandler.removeMessages(MSG_HIDE_SHADOWS); 638 mHandler.sendEmptyMessage(on ? MSG_HIDE_SHADOWS : MSG_SHOW_SHADOWS); 639 } 640 641 public void setMenuKeyVisible(boolean visible) { 642 if (DEBUG) { 643 Slog.d(TAG, (visible?"showing":"hiding") + " the MENU button"); 644 } 645 mMenuButton.setVisibility(visible ? View.VISIBLE : View.GONE); 646 } 647 648 public void setIMEButtonVisible(boolean visible) { 649 if (DEBUG) { 650 Slog.d(TAG, (visible?"showing":"hiding") + " the IME button"); 651 } 652 mInputMethodButton.setIMEButtonVisible(visible); 653 } 654 655 private void setAreThereNotifications() { 656 final boolean hasClearable = mNotns.hasClearableItems(); 657 658 //Slog.d(TAG, "setAreThereNotifications hasClerable=" + hasClearable); 659 660 /* 661 mOngoingTitle.setVisibility(ongoing ? View.VISIBLE : View.GONE); 662 mLatestTitle.setVisibility(latest ? View.VISIBLE : View.GONE); 663 664 if (ongoing || latest) { 665 mNoNotificationsTitle.setVisibility(View.GONE); 666 } else { 667 mNoNotificationsTitle.setVisibility(View.VISIBLE); 668 } 669 */ 670 } 671 672 /** 673 * Cancel this notification and tell the status bar service about the failure. Hold no locks. 674 */ 675 void handleNotificationError(IBinder key, StatusBarNotification n, String message) { 676 removeNotification(key); 677 try { 678 mBarService.onNotificationError(n.pkg, n.tag, n.id, n.uid, n.initialPid, message); 679 } catch (RemoteException ex) { 680 // The end is nigh. 681 } 682 } 683 684 private View.OnClickListener mOnClickListener = new View.OnClickListener() { 685 public void onClick(View v) { 686 if (v == mNotificationTrigger) { 687 onClickNotificationTrigger(); 688 } else if (v == mRecentButton) { 689 onClickRecentButton(); 690 } 691 } 692 }; 693 694 public void onClickNotificationTrigger() { 695 if (DEBUG) Slog.d(TAG, "clicked notification icons; disabled=" + mDisabled); 696 if ((mDisabled & StatusBarManager.DISABLE_EXPAND) == 0) { 697 if (!mNotificationsOn) { 698 mNotificationsOn = true; 699 mIconLayout.setVisibility(View.VISIBLE); // TODO: animation 700 refreshNotificationTrigger(); 701 } else { 702 int msg = (mNotificationPanel.getVisibility() == View.GONE) 703 ? MSG_OPEN_NOTIFICATION_PANEL 704 : MSG_CLOSE_NOTIFICATION_PANEL; 705 mHandler.removeMessages(msg); 706 mHandler.sendEmptyMessage(msg); 707 } 708 } 709 } 710 711 public void onClickRecentButton() { 712 if (DEBUG) Slog.d(TAG, "clicked recent apps; disabled=" + mDisabled); 713 if (mRecentsPanel == null) { 714 Intent intent = new Intent(); 715 intent.setClass(mContext, RecentApplicationsActivity.class); 716 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK 717 | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); 718 mContext.startActivity(intent); 719 } else { 720 if ((mDisabled & StatusBarManager.DISABLE_EXPAND) == 0) { 721 int msg = (mRecentsPanel.getVisibility() == View.GONE) 722 ? MSG_OPEN_RECENTS_PANEL 723 : MSG_CLOSE_RECENTS_PANEL; 724 mHandler.removeMessages(msg); 725 mHandler.sendEmptyMessage(msg); 726 } 727 } 728 } 729 730 private class NotificationClicker implements View.OnClickListener { 731 private PendingIntent mIntent; 732 private String mPkg; 733 private String mTag; 734 private int mId; 735 736 NotificationClicker(PendingIntent intent, String pkg, String tag, int id) { 737 mIntent = intent; 738 mPkg = pkg; 739 mTag = tag; 740 mId = id; 741 } 742 743 public void onClick(View v) { 744 try { 745 // The intent we are sending is for the application, which 746 // won't have permission to immediately start an activity after 747 // the user switches to home. We know it is safe to do at this 748 // point, so make sure new activity switches are now allowed. 749 ActivityManagerNative.getDefault().resumeAppSwitches(); 750 } catch (RemoteException e) { 751 } 752 753 if (mIntent != null) { 754 int[] pos = new int[2]; 755 v.getLocationOnScreen(pos); 756 Intent overlay = new Intent(); 757 overlay.setSourceBounds( 758 new Rect(pos[0], pos[1], pos[0]+v.getWidth(), pos[1]+v.getHeight())); 759 try { 760 mIntent.send(mContext, 0, overlay); 761 } catch (PendingIntent.CanceledException e) { 762 // the stack trace isn't very helpful here. Just log the exception message. 763 Slog.w(TAG, "Sending contentIntent failed: " + e); 764 } 765 } 766 767 try { 768 mBarService.onNotificationClick(mPkg, mTag, mId); 769 } catch (RemoteException ex) { 770 // system process is dead if we're here. 771 } 772 773 // close the shade if it was open 774 animateCollapse(); 775 776 // If this click was on the intruder alert, hide that instead 777// mHandler.sendEmptyMessage(MSG_HIDE_INTRUDER); 778 } 779 } 780 781 StatusBarNotification removeNotificationViews(IBinder key) { 782 NotificationData.Entry entry = mNotns.remove(key); 783 if (entry == null) { 784 Slog.w(TAG, "removeNotification for unknown key: " + key); 785 return null; 786 } 787 // Remove the expanded view. 788 ViewGroup rowParent = (ViewGroup)entry.row.getParent(); 789 if (rowParent != null) rowParent.removeView(entry.row); 790 // Remove the icon. 791// ViewGroup iconParent = (ViewGroup)entry.icon.getParent(); 792// if (iconParent != null) iconParent.removeView(entry.icon); 793 refreshIcons(); 794 795 return entry.notification; 796 } 797 798 private class NotificationIconTouchListener implements View.OnTouchListener { 799 VelocityTracker mVT; 800 801 public NotificationIconTouchListener() { 802 } 803 804 public boolean onTouch(View v, MotionEvent event) { 805 boolean peeking = mNotificationPeekWindow.getVisibility() != View.GONE; 806 boolean panelShowing = mNotificationPanel.getVisibility() != View.GONE; 807 if (panelShowing) return false; 808 809 switch (event.getAction()) { 810 case MotionEvent.ACTION_DOWN: 811 mVT = VelocityTracker.obtain(); 812 813 // fall through 814 case MotionEvent.ACTION_OUTSIDE: 815 case MotionEvent.ACTION_MOVE: 816 // peek and switch icons if necessary 817 int numIcons = mIconLayout.getChildCount(); 818 int peekIndex = 819 (int)((float)event.getX() * numIcons / mIconLayout.getWidth()); 820 if (peekIndex > numIcons - 1) peekIndex = numIcons - 1; 821 else if (peekIndex < 0) peekIndex = 0; 822 823 if (!peeking || mNotificationPeekIndex != peekIndex) { 824 if (DEBUG) Slog.d(TAG, "will peek at notification #" + peekIndex); 825 Message peekMsg = mHandler.obtainMessage(MSG_OPEN_NOTIFICATION_PEEK); 826 peekMsg.arg1 = peekIndex; 827 828 mHandler.removeMessages(MSG_OPEN_NOTIFICATION_PEEK); 829 830 // no delay if we're scrubbing left-right 831 mHandler.sendMessageDelayed(peekMsg, 832 peeking ? 0 : mNotificationPeekTapDuration); 833 } 834 835 // check for fling 836 if (mVT != null) { 837 mVT.addMovement(event); 838 mVT.computeCurrentVelocity(1000); 839 // require a little more oomph once we're already in peekaboo mode 840 if (!panelShowing && ( 841 (peeking && mVT.getYVelocity() < -mNotificationFlingVelocity*3) 842 || (mVT.getYVelocity() < -mNotificationFlingVelocity))) { 843 mHandler.removeMessages(MSG_OPEN_NOTIFICATION_PEEK); 844 mHandler.removeMessages(MSG_OPEN_NOTIFICATION_PANEL); 845 mHandler.sendEmptyMessage(MSG_CLOSE_NOTIFICATION_PEEK); 846 mHandler.sendEmptyMessage(MSG_OPEN_NOTIFICATION_PANEL); 847 } 848 } 849 return true; 850 case MotionEvent.ACTION_UP: 851 case MotionEvent.ACTION_CANCEL: 852 mHandler.removeMessages(MSG_OPEN_NOTIFICATION_PEEK); 853 if (peeking) { 854 mHandler.sendEmptyMessageDelayed(MSG_CLOSE_NOTIFICATION_PEEK, 250); 855 } 856 mVT.recycle(); 857 mVT = null; 858 return true; 859 } 860 return false; 861 } 862 } 863 864 StatusBarIconView addNotificationViews(IBinder key, StatusBarNotification notification) { 865 if (DEBUG) { 866 Slog.d(TAG, "addNotificationViews(key=" + key + ", notification=" + notification); 867 } 868 // Construct the icon. 869 final StatusBarIconView iconView = new StatusBarIconView(mContext, 870 notification.pkg + "/0x" + Integer.toHexString(notification.id)); 871 iconView.setScaleType(ImageView.ScaleType.CENTER_INSIDE); 872 873 final StatusBarIcon ic = new StatusBarIcon(notification.pkg, 874 notification.notification.icon, 875 notification.notification.iconLevel, 876 notification.notification.number); 877 if (!iconView.set(ic)) { 878 handleNotificationError(key, notification, "Couldn't attach StatusBarIcon: " + ic); 879 return null; 880 } 881 // Construct the expanded view. 882 NotificationData.Entry entry = new NotificationData.Entry(key, notification, iconView); 883 if (!inflateViews(entry, mPile)) { 884 handleNotificationError(key, notification, "Couldn't expand RemoteViews for: " 885 + notification); 886 return null; 887 } 888 889 // Add the icon. 890 mNotns.add(entry); 891 refreshIcons(); 892 893 return iconView; 894 } 895 896 private void refreshIcons() { 897 // XXX: need to implement a new limited linear layout class 898 // to avoid removing & readding everything 899 900 final int ICON_LIMIT = 4; 901 final LinearLayout.LayoutParams params 902 = new LinearLayout.LayoutParams(mIconSize, mIconSize); 903 904 int N = mNotns.size(); 905 906 if (DEBUG) { 907 Slog.d(TAG, "refreshing icons: " + N + " notifications, mIconLayout=" + mIconLayout); 908 } 909 910 ArrayList<View> toShow = new ArrayList<View>(); 911 912 for (int i=0; i<ICON_LIMIT; i++) { 913 if (i>=N) break; 914 toShow.add(mNotns.get(N-i-1).icon); 915 } 916 917 ArrayList<View> toRemove = new ArrayList<View>(); 918 for (int i=0; i<mIconLayout.getChildCount(); i++) { 919 View child = mIconLayout.getChildAt(i); 920 if (!toShow.contains(child)) { 921 toRemove.add(child); 922 } 923 } 924 925 for (View remove : toRemove) { 926 mIconLayout.removeView(remove); 927 } 928 929 for (int i=0; i<toShow.size(); i++) { 930 View v = toShow.get(i); 931 if (v.getParent() == null) { 932 mIconLayout.addView(toShow.get(i), i, params); 933 } 934 } 935 936 loadNotificationPanel(); 937 refreshNotificationTrigger(); 938 } 939 940 private void loadNotificationPanel() { 941 int N = mNotns.size(); 942 943 ArrayList<View> toShow = new ArrayList<View>(); 944 945 for (int i=0; i<N; i++) { 946 View row = mNotns.get(N-i-1).row; 947 toShow.add(row); 948 } 949 950 ArrayList<View> toRemove = new ArrayList<View>(); 951 for (int i=0; i<mPile.getChildCount(); i++) { 952 View child = mPile.getChildAt(i); 953 if (!toShow.contains(child)) { 954 toRemove.add(child); 955 } 956 } 957 958 for (View remove : toRemove) { 959 mPile.removeView(remove); 960 } 961 962 for (int i=0; i<toShow.size(); i++) { 963 View v = toShow.get(i); 964 if (v.getParent() == null) { 965 mPile.addView(toShow.get(i)); 966 } 967 } 968 } 969 970 void workAroundBadLayerDrawableOpacity(View v) { 971 LayerDrawable d = (LayerDrawable)v.getBackground(); 972 v.setBackgroundDrawable(null); 973 d.setOpacity(PixelFormat.TRANSLUCENT); 974 v.setBackgroundDrawable(d); 975 } 976 977 private boolean inflateViews(NotificationData.Entry entry, ViewGroup parent) { 978 StatusBarNotification sbn = entry.notification; 979 RemoteViews remoteViews = sbn.notification.contentView; 980 if (remoteViews == null) { 981 return false; 982 } 983 984 // create the row view 985 LayoutInflater inflater = (LayoutInflater)mContext.getSystemService( 986 Context.LAYOUT_INFLATER_SERVICE); 987 View row = inflater.inflate(R.layout.status_bar_latest_event, parent, false); 988 workAroundBadLayerDrawableOpacity(row); 989 View vetoButton = row.findViewById(R.id.veto); 990 if (entry.notification.isClearable()) { 991 final String _pkg = sbn.pkg; 992 final String _tag = sbn.tag; 993 final int _id = sbn.id; 994 vetoButton.setOnClickListener(new View.OnClickListener() { 995 public void onClick(View v) { 996 try { 997 mBarService.onNotificationClear(_pkg, _tag, _id); 998 } catch (RemoteException ex) { 999 // system process is dead if we're here. 1000 } 1001 } 1002 }); 1003 } else { 1004 vetoButton.setVisibility(View.INVISIBLE); 1005 } 1006 1007 // the large icon 1008 ImageView largeIcon = (ImageView)row.findViewById(R.id.large_icon); 1009 if (sbn.notification.largeIcon != null) { 1010 largeIcon.setImageBitmap(sbn.notification.largeIcon); 1011 } else { 1012 largeIcon.getLayoutParams().width = 0; 1013 largeIcon.setVisibility(View.INVISIBLE); 1014 } 1015 1016 // bind the click event to the content area 1017 ViewGroup content = (ViewGroup)row.findViewById(R.id.content); 1018 // XXX: update to allow controls within notification views 1019 content.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS); 1020// content.setOnFocusChangeListener(mFocusChangeListener); 1021 PendingIntent contentIntent = sbn.notification.contentIntent; 1022 if (contentIntent != null) { 1023 content.setOnClickListener(new NotificationClicker(contentIntent, 1024 sbn.pkg, sbn.tag, sbn.id)); 1025 } else { 1026 content.setOnClickListener(null); 1027 } 1028 1029 View expanded = null; 1030 Exception exception = null; 1031 try { 1032 expanded = remoteViews.apply(mContext, content); 1033 } 1034 catch (RuntimeException e) { 1035 exception = e; 1036 } 1037 if (expanded == null) { 1038 final String ident = sbn.pkg + "/0x" + Integer.toHexString(sbn.id); 1039 Slog.e(TAG, "couldn't inflate view for notification " + ident, exception); 1040 return false; 1041 } else { 1042 content.addView(expanded); 1043 row.setDrawingCacheEnabled(true); 1044 } 1045 1046 entry.row = row; 1047 entry.content = content; 1048 entry.expanded = expanded; 1049 1050 return true; 1051 } 1052 1053 public class ShadowController { 1054 boolean mShowShadows; 1055 Map<View, View> mShadowsForElements = new IdentityHashMap<View, View>(7); 1056 Map<View, View> mElementsForShadows = new IdentityHashMap<View, View>(7); 1057 LayoutTransition mElementTransition, mShadowTransition; 1058 1059 View mTouchTarget; 1060 1061 ShadowController(boolean showShadows) { 1062 mShowShadows = showShadows; 1063 mTouchTarget = null; 1064 1065 mElementTransition = new LayoutTransition(); 1066// AnimatorSet s = new AnimatorSet(); 1067// s.play(ObjectAnimator.ofInt(null, "top", 48, 0)) 1068// .with(ObjectAnimator.ofFloat(null, "scaleY", 0.5f, 1f)) 1069// .with(ObjectAnimator.ofFloat(null, "alpha", 0.5f, 1f)) 1070// ; 1071 mElementTransition.setAnimator(LayoutTransition.APPEARING, //s); 1072 ObjectAnimator.ofInt(null, "top", 48, 0)); 1073 mElementTransition.setDuration(LayoutTransition.APPEARING, 100); 1074 mElementTransition.setStartDelay(LayoutTransition.APPEARING, 0); 1075 1076// s = new AnimatorSet(); 1077// s.play(ObjectAnimator.ofInt(null, "top", 0, 48)) 1078// .with(ObjectAnimator.ofFloat(null, "scaleY", 1f, 0.5f)) 1079// .with(ObjectAnimator.ofFloat(null, "alpha", 1f, 0.5f)) 1080// ; 1081 mElementTransition.setAnimator(LayoutTransition.DISAPPEARING, //s); 1082 ObjectAnimator.ofInt(null, "top", 0, 48)); 1083 mElementTransition.setDuration(LayoutTransition.DISAPPEARING, 400); 1084 1085 mShadowTransition = new LayoutTransition(); 1086 mShadowTransition.setAnimator(LayoutTransition.APPEARING, 1087 ObjectAnimator.ofFloat(null, "alpha", 0f, 1f)); 1088 mShadowTransition.setDuration(LayoutTransition.APPEARING, 200); 1089 mShadowTransition.setStartDelay(LayoutTransition.APPEARING, 100); 1090 mShadowTransition.setAnimator(LayoutTransition.DISAPPEARING, 1091 ObjectAnimator.ofFloat(null, "alpha", 1f, 0f)); 1092 mShadowTransition.setDuration(LayoutTransition.DISAPPEARING, 100); 1093 1094 ViewGroup bar = (ViewGroup) TabletStatusBar.this.mBarContents; 1095 bar.setLayoutTransition(mElementTransition); 1096 ViewGroup nav = (ViewGroup) TabletStatusBar.this.mNavigationArea; 1097 nav.setLayoutTransition(mElementTransition); 1098 ViewGroup shadowGroup = (ViewGroup) bar.findViewById(R.id.shadows); 1099 shadowGroup.setLayoutTransition(mShadowTransition); 1100 } 1101 1102 public void add(View element, View shadow) { 1103 shadow.setOnTouchListener(makeTouchListener()); 1104 mShadowsForElements.put(element, shadow); 1105 mElementsForShadows.put(shadow, element); 1106 } 1107 1108 public boolean getShadowState() { 1109 return mShowShadows; 1110 } 1111 1112 public View.OnTouchListener makeTouchListener() { 1113 return new View.OnTouchListener() { 1114 public boolean onTouch(View v, MotionEvent ev) { 1115 final int action = ev.getAction(); 1116 1117 if (DEBUG) Slog.d(TAG, "ShadowController: v=" + v + ", ev=" + ev); 1118 1119 // currently redirecting events? 1120 if (mTouchTarget == null) { 1121 mTouchTarget = mElementsForShadows.get(v); 1122 } 1123 1124 if (mTouchTarget != null && mTouchTarget.getVisibility() != View.GONE) { 1125 boolean last = false; 1126 switch (action) { 1127 case MotionEvent.ACTION_CANCEL: 1128 case MotionEvent.ACTION_UP: 1129 mHandler.removeMessages(MSG_RESTORE_SHADOWS); 1130 if (mShowShadows) { 1131 mHandler.sendEmptyMessageDelayed(MSG_RESTORE_SHADOWS, 1132 v == mNotificationShadow ? 5000 : 500); 1133 } 1134 last = true; 1135 break; 1136 case MotionEvent.ACTION_DOWN: 1137 mHandler.removeMessages(MSG_RESTORE_SHADOWS); 1138 setElementShadow(mTouchTarget, false); 1139 break; 1140 } 1141 mTouchTarget.dispatchTouchEvent(ev); 1142 if (last) mTouchTarget = null; 1143 return true; 1144 } 1145 1146 return false; 1147 } 1148 }; 1149 } 1150 1151 public void refresh() { 1152 for (View element : mShadowsForElements.keySet()) { 1153 setElementShadow(element, mShowShadows); 1154 } 1155 } 1156 1157 public void showAllShadows() { 1158 mShowShadows = true; 1159 refresh(); 1160 } 1161 1162 public void hideAllShadows() { 1163 mShowShadows = false; 1164 refresh(); 1165 } 1166 1167 // Use View.INVISIBLE for things hidden due to shadowing, and View.GONE for things that are 1168 // disabled (and should not be shadowed or re-shown) 1169 public void setElementShadow(View button, boolean shade) { 1170 View shadow = mShadowsForElements.get(button); 1171 if (shadow != null) { 1172 if (button.getVisibility() != View.GONE) { 1173 shadow.setVisibility(shade ? View.VISIBLE : View.INVISIBLE); 1174 button.setVisibility(shade ? View.INVISIBLE : View.VISIBLE); 1175 } 1176 } 1177 } 1178 1179 // Hide both element and shadow, using default layout animations. 1180 public void hideElement(View button) { 1181 Slog.d(TAG, "hiding: " + button); 1182 View shadow = mShadowsForElements.get(button); 1183 if (shadow != null) { 1184 shadow.setVisibility(View.GONE); 1185 } 1186 button.setVisibility(View.GONE); 1187 } 1188 1189 // Honoring the current shadow state. 1190 public void showElement(View button) { 1191 Slog.d(TAG, "showing: " + button); 1192 View shadow = mShadowsForElements.get(button); 1193 if (shadow != null) { 1194 shadow.setVisibility(mShowShadows ? View.VISIBLE : View.INVISIBLE); 1195 } 1196 button.setVisibility(mShowShadows ? View.INVISIBLE : View.VISIBLE); 1197 } 1198 } 1199 1200 public class TouchOutsideListener implements View.OnTouchListener { 1201 private int mMsg; 1202 private StatusBarPanel mPanel; 1203 1204 public TouchOutsideListener(int msg, StatusBarPanel panel) { 1205 mMsg = msg; 1206 mPanel = panel; 1207 } 1208 1209 public boolean onTouch(View v, MotionEvent ev) { 1210 final int action = ev.getAction(); 1211 if (action == MotionEvent.ACTION_OUTSIDE 1212 || (action == MotionEvent.ACTION_DOWN 1213 && !mPanel.isInContentArea((int)ev.getX(), (int)ev.getY()))) { 1214 mHandler.removeMessages(mMsg); 1215 mHandler.sendEmptyMessage(mMsg); 1216 return true; 1217 } 1218 return false; 1219 } 1220 } 1221 1222 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 1223 pw.print("mDisabled=0x"); 1224 pw.println(Integer.toHexString(mDisabled)); 1225 } 1226} 1227 1228 1229