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