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