TabletStatusBar.java revision b8027d87d87fbf24a6091ef183e519157876df41
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 mNotificationArea.setVisibility(View.GONE); 721 } 722 } 723 } 724 725 // called by TabletTicker when it's done with all queued ticks 726 public void doneTicking() { 727 mNotificationArea.setVisibility(View.VISIBLE); 728 } 729 730 public void animateExpand() { 731 mHandler.removeMessages(MSG_OPEN_NOTIFICATION_PANEL); 732 mHandler.sendEmptyMessage(MSG_OPEN_NOTIFICATION_PANEL); 733 } 734 735 public void animateCollapse() { 736 mHandler.removeMessages(MSG_CLOSE_NOTIFICATION_PANEL); 737 mHandler.sendEmptyMessage(MSG_CLOSE_NOTIFICATION_PANEL); 738 mHandler.removeMessages(MSG_CLOSE_RECENTS_PANEL); 739 mHandler.sendEmptyMessage(MSG_CLOSE_RECENTS_PANEL); 740 } 741 742 // called by StatusBar 743 @Override 744 public void setLightsOn(boolean on) { 745 // Policy note: if the frontmost activity needs the menu key, we assume it is a legacy app 746 // that can't handle lights-out mode. 747 if (mMenuButton.getVisibility() == View.VISIBLE) { 748 on = true; 749 } 750 mHandler.removeMessages(MSG_HIDE_CHROME); 751 mHandler.removeMessages(MSG_SHOW_CHROME); 752 mHandler.sendEmptyMessage(on ? MSG_SHOW_CHROME : MSG_HIDE_CHROME); 753 } 754 755 public void setMenuKeyVisible(boolean visible) { 756 if (DEBUG) { 757 Slog.d(TAG, (visible?"showing":"hiding") + " the MENU button"); 758 } 759 mMenuButton.setVisibility(visible ? View.VISIBLE : View.GONE); 760 761 // See above re: lights-out policy for legacy apps. 762 if (visible) setLightsOn(true); 763 } 764 765 public void setIMEButtonVisible(IBinder token, boolean visible) { 766 if (DEBUG) { 767 Slog.d(TAG, (visible?"showing":"hiding") + " the IME button"); 768 } 769 mInputMethodSwitchButton.setIMEButtonVisible(token, visible); 770 mBackButton.setImageResource( 771 visible ? R.drawable.ic_sysbar_back_ime : R.drawable.ic_sysbar_back); 772 if (FAKE_SPACE_BAR) { 773 mFakeSpaceBar.setVisibility(visible ? View.VISIBLE : View.GONE); 774 } 775 } 776 777 private boolean isImmersive() { 778 try { 779 return ActivityManagerNative.getDefault().isTopActivityImmersive(); 780 //Slog.d(TAG, "Top activity is " + (immersive?"immersive":"not immersive")); 781 } catch (RemoteException ex) { 782 // the end is nigh 783 return false; 784 } 785 } 786 787 private void setAreThereNotifications() { 788 final boolean hasClearable = mNotns.hasClearableItems(); 789 } 790 791 /** 792 * Cancel this notification and tell the status bar service about the failure. Hold no locks. 793 */ 794 void handleNotificationError(IBinder key, StatusBarNotification n, String message) { 795 removeNotification(key); 796 try { 797 mBarService.onNotificationError(n.pkg, n.tag, n.id, n.uid, n.initialPid, message); 798 } catch (RemoteException ex) { 799 // The end is nigh. 800 } 801 } 802 803 private void sendKey(KeyEvent key) { 804 try { 805 if (DEBUG) Slog.d(TAG, "injecting key event: " + key); 806 mWindowManager.injectInputEventNoWait(key); 807 } catch (RemoteException ex) { 808 } 809 } 810 811 private View.OnClickListener mOnClickListener = new View.OnClickListener() { 812 public void onClick(View v) { 813 if (v == mNotificationTrigger) { 814 onClickNotificationTrigger(); 815 } else if (v == mRecentButton) { 816 onClickRecentButton(); 817 } 818 } 819 }; 820 821 public void onClickNotificationTrigger() { 822 if (DEBUG) Slog.d(TAG, "clicked notification icons; disabled=" + mDisabled); 823 if ((mDisabled & StatusBarManager.DISABLE_EXPAND) == 0) { 824 if (!mNotificationsOn) { 825 mNotificationsOn = true; 826 mIconLayout.setVisibility(View.VISIBLE); // TODO: animation 827 } else { 828 int msg = !mNotificationPanel.isShowing() 829 ? MSG_OPEN_NOTIFICATION_PANEL 830 : MSG_CLOSE_NOTIFICATION_PANEL; 831 mHandler.removeMessages(msg); 832 mHandler.sendEmptyMessage(msg); 833 } 834 } 835 } 836 837 public void onClickRecentButton() { 838 if (DEBUG) Slog.d(TAG, "clicked recent apps; disabled=" + mDisabled); 839 if (mRecentsPanel == null) { 840 Intent intent = new Intent(); 841 intent.setClass(mContext, RecentApplicationsActivity.class); 842 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK 843 | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); 844 mContext.startActivity(intent); 845 } else { 846 if ((mDisabled & StatusBarManager.DISABLE_EXPAND) == 0) { 847 int msg = (mRecentsPanel.getVisibility() == View.GONE) 848 ? MSG_OPEN_RECENTS_PANEL 849 : MSG_CLOSE_RECENTS_PANEL; 850 mHandler.removeMessages(msg); 851 mHandler.sendEmptyMessage(msg); 852 } 853 } 854 } 855 856 public NotificationClicker makeClicker(PendingIntent intent, String pkg, String tag, int id) { 857 return new NotificationClicker(intent, pkg, tag, id); 858 } 859 860 private class NotificationClicker implements View.OnClickListener { 861 private PendingIntent mIntent; 862 private String mPkg; 863 private String mTag; 864 private int mId; 865 866 NotificationClicker(PendingIntent intent, String pkg, String tag, int id) { 867 mIntent = intent; 868 mPkg = pkg; 869 mTag = tag; 870 mId = id; 871 } 872 873 public void onClick(View v) { 874 try { 875 // The intent we are sending is for the application, which 876 // won't have permission to immediately start an activity after 877 // the user switches to home. We know it is safe to do at this 878 // point, so make sure new activity switches are now allowed. 879 ActivityManagerNative.getDefault().resumeAppSwitches(); 880 } catch (RemoteException e) { 881 } 882 883 if (mIntent != null) { 884 int[] pos = new int[2]; 885 v.getLocationOnScreen(pos); 886 Intent overlay = new Intent(); 887 overlay.setSourceBounds( 888 new Rect(pos[0], pos[1], pos[0]+v.getWidth(), pos[1]+v.getHeight())); 889 try { 890 mIntent.send(mContext, 0, overlay); 891 } catch (PendingIntent.CanceledException e) { 892 // the stack trace isn't very helpful here. Just log the exception message. 893 Slog.w(TAG, "Sending contentIntent failed: " + e); 894 } 895 } 896 897 try { 898 mBarService.onNotificationClick(mPkg, mTag, mId); 899 } catch (RemoteException ex) { 900 // system process is dead if we're here. 901 } 902 903 // close the shade if it was open 904 animateCollapse(); 905 906 // If this click was on the intruder alert, hide that instead 907// mHandler.sendEmptyMessage(MSG_HIDE_INTRUDER); 908 } 909 } 910 911 StatusBarNotification removeNotificationViews(IBinder key) { 912 NotificationData.Entry entry = mNotns.remove(key); 913 if (entry == null) { 914 Slog.w(TAG, "removeNotification for unknown key: " + key); 915 return null; 916 } 917 // Remove the expanded view. 918 ViewGroup rowParent = (ViewGroup)entry.row.getParent(); 919 if (rowParent != null) rowParent.removeView(entry.row); 920 921 if (key == mNotificationPeekKey) { 922 // must close the peek as well, since it's gone 923 mHandler.sendEmptyMessage(MSG_CLOSE_NOTIFICATION_PEEK); 924 } 925 // Remove the icon. 926// ViewGroup iconParent = (ViewGroup)entry.icon.getParent(); 927// if (iconParent != null) iconParent.removeView(entry.icon); 928 updateNotificationIcons(); 929 930 return entry.notification; 931 } 932 933 private class NotificationIconTouchListener implements View.OnTouchListener { 934 VelocityTracker mVT; 935 936 public NotificationIconTouchListener() { 937 } 938 939 public boolean onTouch(View v, MotionEvent event) { 940 boolean peeking = mNotificationPeekWindow.getVisibility() != View.GONE; 941 boolean panelShowing = mNotificationPanel.isShowing(); 942 if (panelShowing) return false; 943 944 switch (event.getAction()) { 945 case MotionEvent.ACTION_DOWN: 946 mVT = VelocityTracker.obtain(); 947 948 // fall through 949 case MotionEvent.ACTION_OUTSIDE: 950 case MotionEvent.ACTION_MOVE: 951 // peek and switch icons if necessary 952 int numIcons = mIconLayout.getChildCount(); 953 int peekIndex = (int)((float)event.getX() * numIcons / mIconLayout.getWidth()); 954 if (peekIndex > numIcons - 1) peekIndex = numIcons - 1; 955 else if (peekIndex < 0) peekIndex = 0; 956 957 if (!peeking || mNotificationPeekIndex != peekIndex) { 958 if (DEBUG) Slog.d(TAG, "will peek at notification #" + peekIndex); 959 Message peekMsg = mHandler.obtainMessage(MSG_OPEN_NOTIFICATION_PEEK); 960 peekMsg.arg1 = peekIndex; 961 962 mHandler.removeMessages(MSG_OPEN_NOTIFICATION_PEEK); 963 964 // no delay if we're scrubbing left-right 965 mHandler.sendMessage(peekMsg); 966 } 967 968 // check for fling 969 if (mVT != null) { 970 mVT.addMovement(event); 971 mVT.computeCurrentVelocity(1000); 972 // require a little more oomph once we're already in peekaboo mode 973 if (!panelShowing && ( 974 (peeking && mVT.getYVelocity() < -mNotificationFlingVelocity*3) 975 || (mVT.getYVelocity() < -mNotificationFlingVelocity))) { 976 mHandler.removeMessages(MSG_OPEN_NOTIFICATION_PEEK); 977 mHandler.removeMessages(MSG_OPEN_NOTIFICATION_PANEL); 978 mHandler.sendEmptyMessage(MSG_CLOSE_NOTIFICATION_PEEK); 979 mHandler.sendEmptyMessage(MSG_OPEN_NOTIFICATION_PANEL); 980 } 981 } 982 return true; 983 case MotionEvent.ACTION_UP: 984 case MotionEvent.ACTION_CANCEL: 985 mHandler.removeMessages(MSG_OPEN_NOTIFICATION_PEEK); 986 if (peeking) { 987 mHandler.sendEmptyMessageDelayed(MSG_CLOSE_NOTIFICATION_PEEK, 5000); 988 } 989 mVT.recycle(); 990 mVT = null; 991 return true; 992 } 993 return false; 994 } 995 } 996 997 StatusBarIconView addNotificationViews(IBinder key, StatusBarNotification notification) { 998 if (DEBUG) { 999 Slog.d(TAG, "addNotificationViews(key=" + key + ", notification=" + notification); 1000 } 1001 // Construct the icon. 1002 final StatusBarIconView iconView = new StatusBarIconView(mContext, 1003 notification.pkg + "/0x" + Integer.toHexString(notification.id)); 1004 iconView.setScaleType(ImageView.ScaleType.CENTER_INSIDE); 1005 1006 final StatusBarIcon ic = new StatusBarIcon(notification.pkg, 1007 notification.notification.icon, 1008 notification.notification.iconLevel, 1009 notification.notification.number); 1010 if (!iconView.set(ic)) { 1011 handleNotificationError(key, notification, "Couldn't attach StatusBarIcon: " + ic); 1012 return null; 1013 } 1014 // Construct the expanded view. 1015 NotificationData.Entry entry = new NotificationData.Entry(key, notification, iconView); 1016 if (!inflateViews(entry, mPile)) { 1017 handleNotificationError(key, notification, "Couldn't expand RemoteViews for: " 1018 + notification); 1019 return null; 1020 } 1021 1022 // Add the icon. 1023 mNotns.add(entry); 1024 updateNotificationIcons(); 1025 1026 return iconView; 1027 } 1028 1029 private void reloadAllNotificationIcons() { 1030 if (mIconLayout == null) return; 1031 mIconLayout.removeAllViews(); 1032 updateNotificationIcons(); 1033 } 1034 1035 private void updateNotificationIcons() { 1036 // XXX: need to implement a new limited linear layout class 1037 // to avoid removing & readding everything 1038 1039 if (mIconLayout == null) return; 1040 1041 final LinearLayout.LayoutParams params 1042 = new LinearLayout.LayoutParams(mIconSize + 2*mIconHPadding, mBarHeight); 1043 1044 int N = mNotns.size(); 1045 1046 if (DEBUG) { 1047 Slog.d(TAG, "refreshing icons: " + N + " notifications, mIconLayout=" + mIconLayout); 1048 } 1049 1050 ArrayList<View> toShow = new ArrayList<View>(); 1051 1052 for (int i=0; i<MAX_NOTIFICATION_ICONS; i++) { 1053 if (i>=N) break; 1054 toShow.add(mNotns.get(N-i-1).icon); 1055 } 1056 1057 ArrayList<View> toRemove = new ArrayList<View>(); 1058 for (int i=0; i<mIconLayout.getChildCount(); i++) { 1059 View child = mIconLayout.getChildAt(i); 1060 if (!toShow.contains(child)) { 1061 toRemove.add(child); 1062 } 1063 } 1064 1065 for (View remove : toRemove) { 1066 mIconLayout.removeView(remove); 1067 } 1068 1069 for (int i=0; i<toShow.size(); i++) { 1070 View v = toShow.get(i); 1071 v.setPadding(mIconHPadding, 0, mIconHPadding, 0); 1072 if (v.getParent() == null) { 1073 mIconLayout.addView(v, i, params); 1074 } 1075 } 1076 1077 loadNotificationPanel(); 1078 } 1079 1080 private void loadNotificationPanel() { 1081 int N = mNotns.size(); 1082 1083 ArrayList<View> toShow = new ArrayList<View>(); 1084 1085 for (int i=0; i<N; i++) { 1086 View row = mNotns.get(N-i-1).row; 1087 toShow.add(row); 1088 } 1089 1090 ArrayList<View> toRemove = new ArrayList<View>(); 1091 for (int i=0; i<mPile.getChildCount(); i++) { 1092 View child = mPile.getChildAt(i); 1093 if (!toShow.contains(child)) { 1094 toRemove.add(child); 1095 } 1096 } 1097 1098 for (View remove : toRemove) { 1099 mPile.removeView(remove); 1100 } 1101 1102 for (int i=0; i<toShow.size(); i++) { 1103 View v = toShow.get(i); 1104 if (v.getParent() == null) { 1105 mPile.addView(toShow.get(i)); 1106 } 1107 } 1108 } 1109 1110 void workAroundBadLayerDrawableOpacity(View v) { 1111 LayerDrawable d = (LayerDrawable)v.getBackground(); 1112 v.setBackgroundDrawable(null); 1113 d.setOpacity(PixelFormat.TRANSLUCENT); 1114 v.setBackgroundDrawable(d); 1115 } 1116 1117 private boolean inflateViews(NotificationData.Entry entry, ViewGroup parent) { 1118 StatusBarNotification sbn = entry.notification; 1119 RemoteViews remoteViews = sbn.notification.contentView; 1120 if (remoteViews == null) { 1121 return false; 1122 } 1123 1124 // create the row view 1125 LayoutInflater inflater = (LayoutInflater)mContext.getSystemService( 1126 Context.LAYOUT_INFLATER_SERVICE); 1127 View row = inflater.inflate(R.layout.status_bar_notification_row, parent, false); 1128 workAroundBadLayerDrawableOpacity(row); 1129 View vetoButton = row.findViewById(R.id.veto); 1130 if (entry.notification.isClearable()) { 1131 final String _pkg = sbn.pkg; 1132 final String _tag = sbn.tag; 1133 final int _id = sbn.id; 1134 vetoButton.setOnClickListener(new View.OnClickListener() { 1135 public void onClick(View v) { 1136 try { 1137 mBarService.onNotificationClear(_pkg, _tag, _id); 1138 } catch (RemoteException ex) { 1139 // system process is dead if we're here. 1140 } 1141 } 1142 }); 1143 } else { 1144 vetoButton.setVisibility(View.INVISIBLE); 1145 } 1146 1147 // the large icon 1148 ImageView largeIcon = (ImageView)row.findViewById(R.id.large_icon); 1149 if (sbn.notification.largeIcon != null) { 1150 largeIcon.setImageBitmap(sbn.notification.largeIcon); 1151 } else { 1152 largeIcon.getLayoutParams().width = 0; 1153 largeIcon.setVisibility(View.INVISIBLE); 1154 } 1155 1156 // bind the click event to the content area 1157 ViewGroup content = (ViewGroup)row.findViewById(R.id.content); 1158 // XXX: update to allow controls within notification views 1159 content.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS); 1160// content.setOnFocusChangeListener(mFocusChangeListener); 1161 PendingIntent contentIntent = sbn.notification.contentIntent; 1162 if (contentIntent != null) { 1163 content.setOnClickListener(new NotificationClicker(contentIntent, 1164 sbn.pkg, sbn.tag, sbn.id)); 1165 } else { 1166 content.setOnClickListener(null); 1167 } 1168 1169 View expanded = null; 1170 Exception exception = null; 1171 try { 1172 expanded = remoteViews.apply(mContext, content); 1173 } 1174 catch (RuntimeException e) { 1175 exception = e; 1176 } 1177 if (expanded == null) { 1178 final String ident = sbn.pkg + "/0x" + Integer.toHexString(sbn.id); 1179 Slog.e(TAG, "couldn't inflate view for notification " + ident, exception); 1180 return false; 1181 } else { 1182 content.addView(expanded); 1183 row.setDrawingCacheEnabled(true); 1184 } 1185 1186 entry.row = row; 1187 entry.content = content; 1188 entry.expanded = expanded; 1189 1190 return true; 1191 } 1192 1193/* 1194 public class ShadowController { 1195 boolean mShowShadows; 1196 Map<View, View> mShadowsForElements = new IdentityHashMap<View, View>(7); 1197 Map<View, View> mElementsForShadows = new IdentityHashMap<View, View>(7); 1198 LayoutTransition mElementTransition, mShadowTransition; 1199 1200 View mTouchTarget; 1201 1202 ShadowController(boolean showShadows) { 1203 mShowShadows = showShadows; 1204 mTouchTarget = null; 1205 1206 mElementTransition = new LayoutTransition(); 1207// AnimatorSet s = new AnimatorSet(); 1208// s.play(ObjectAnimator.ofInt(null, "top", 48, 0)) 1209// .with(ObjectAnimator.ofFloat(null, "scaleY", 0.5f, 1f)) 1210// .with(ObjectAnimator.ofFloat(null, "alpha", 0.5f, 1f)) 1211// ; 1212 mElementTransition.setAnimator(LayoutTransition.APPEARING, //s); 1213 ObjectAnimator.ofInt(null, "top", 48, 0)); 1214 mElementTransition.setDuration(LayoutTransition.APPEARING, 100); 1215 mElementTransition.setStartDelay(LayoutTransition.APPEARING, 0); 1216 1217// s = new AnimatorSet(); 1218// s.play(ObjectAnimator.ofInt(null, "top", 0, 48)) 1219// .with(ObjectAnimator.ofFloat(null, "scaleY", 1f, 0.5f)) 1220// .with(ObjectAnimator.ofFloat(null, "alpha", 1f, 0.5f)) 1221// ; 1222 mElementTransition.setAnimator(LayoutTransition.DISAPPEARING, //s); 1223 ObjectAnimator.ofInt(null, "top", 0, 48)); 1224 mElementTransition.setDuration(LayoutTransition.DISAPPEARING, 400); 1225 1226 mShadowTransition = new LayoutTransition(); 1227 mShadowTransition.setAnimator(LayoutTransition.APPEARING, 1228 ObjectAnimator.ofFloat(null, "alpha", 0f, 1f)); 1229 mShadowTransition.setDuration(LayoutTransition.APPEARING, 200); 1230 mShadowTransition.setStartDelay(LayoutTransition.APPEARING, 100); 1231 mShadowTransition.setAnimator(LayoutTransition.DISAPPEARING, 1232 ObjectAnimator.ofFloat(null, "alpha", 1f, 0f)); 1233 mShadowTransition.setDuration(LayoutTransition.DISAPPEARING, 100); 1234 1235 ViewGroup bar = (ViewGroup) TabletStatusBar.this.mBarContents; 1236 bar.setLayoutTransition(mElementTransition); 1237 ViewGroup nav = (ViewGroup) TabletStatusBar.this.mNavigationArea; 1238 nav.setLayoutTransition(mElementTransition); 1239 ViewGroup shadowGroup = (ViewGroup) bar.findViewById(R.id.shadows); 1240 shadowGroup.setLayoutTransition(mShadowTransition); 1241 } 1242 1243 public void add(View element, View shadow) { 1244 shadow.setOnTouchListener(makeTouchListener()); 1245 mShadowsForElements.put(element, shadow); 1246 mElementsForShadows.put(shadow, element); 1247 } 1248 1249 public boolean getShadowState() { 1250 return mShowShadows; 1251 } 1252 1253 public View.OnTouchListener makeTouchListener() { 1254 return new View.OnTouchListener() { 1255 public boolean onTouch(View v, MotionEvent ev) { 1256 final int action = ev.getAction(); 1257 1258 if (DEBUG) Slog.d(TAG, "ShadowController: v=" + v + ", ev=" + ev); 1259 1260 // currently redirecting events? 1261 if (mTouchTarget == null) { 1262 mTouchTarget = mElementsForShadows.get(v); 1263 } 1264 1265 if (mTouchTarget != null && mTouchTarget.getVisibility() != View.GONE) { 1266 boolean last = false; 1267 switch (action) { 1268 case MotionEvent.ACTION_CANCEL: 1269 case MotionEvent.ACTION_UP: 1270 mHandler.removeMessages(MSG_RESTORE_SHADOWS); 1271 if (mShowShadows) { 1272 mHandler.sendEmptyMessageDelayed(MSG_RESTORE_SHADOWS, 1273 v == mNotificationShadow ? 5000 : 500); 1274 } 1275 last = true; 1276 break; 1277 case MotionEvent.ACTION_DOWN: 1278 mHandler.removeMessages(MSG_RESTORE_SHADOWS); 1279 setElementShadow(mTouchTarget, false); 1280 break; 1281 } 1282 mTouchTarget.dispatchTouchEvent(ev); 1283 if (last) mTouchTarget = null; 1284 return true; 1285 } 1286 1287 return false; 1288 } 1289 }; 1290 } 1291 1292 public void refresh() { 1293 for (View element : mShadowsForElements.keySet()) { 1294 setElementShadow(element, mShowShadows); 1295 } 1296 } 1297 1298 public void showAllShadows() { 1299 mShowShadows = true; 1300 refresh(); 1301 } 1302 1303 public void hideAllShadows() { 1304 mShowShadows = false; 1305 refresh(); 1306 } 1307 1308 // Use View.INVISIBLE for things hidden due to shadowing, and View.GONE for things that are 1309 // disabled (and should not be shadowed or re-shown) 1310 public void setElementShadow(View button, boolean shade) { 1311 View shadow = mShadowsForElements.get(button); 1312 if (shadow != null) { 1313 if (button.getVisibility() != View.GONE) { 1314 shadow.setVisibility(shade ? View.VISIBLE : View.INVISIBLE); 1315 button.setVisibility(shade ? View.INVISIBLE : View.VISIBLE); 1316 } 1317 } 1318 } 1319 1320 // Hide both element and shadow, using default layout animations. 1321 public void hideElement(View button) { 1322 Slog.d(TAG, "hiding: " + button); 1323 View shadow = mShadowsForElements.get(button); 1324 if (shadow != null) { 1325 shadow.setVisibility(View.GONE); 1326 } 1327 button.setVisibility(View.GONE); 1328 } 1329 1330 // Honoring the current shadow state. 1331 public void showElement(View button) { 1332 Slog.d(TAG, "showing: " + button); 1333 View shadow = mShadowsForElements.get(button); 1334 if (shadow != null) { 1335 shadow.setVisibility(mShowShadows ? View.VISIBLE : View.INVISIBLE); 1336 } 1337 button.setVisibility(mShowShadows ? View.INVISIBLE : View.VISIBLE); 1338 } 1339 } 1340 */ 1341 1342 public class TouchOutsideListener implements View.OnTouchListener { 1343 private int mMsg; 1344 private StatusBarPanel mPanel; 1345 1346 public TouchOutsideListener(int msg, StatusBarPanel panel) { 1347 mMsg = msg; 1348 mPanel = panel; 1349 } 1350 1351 public boolean onTouch(View v, MotionEvent ev) { 1352 final int action = ev.getAction(); 1353 if (action == MotionEvent.ACTION_OUTSIDE 1354 || (action == MotionEvent.ACTION_DOWN 1355 && !mPanel.isInContentArea((int)ev.getX(), (int)ev.getY()))) { 1356 mHandler.removeMessages(mMsg); 1357 mHandler.sendEmptyMessage(mMsg); 1358 return true; 1359 } 1360 return false; 1361 } 1362 } 1363 1364 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 1365 pw.print("mDisabled=0x"); 1366 pw.println(Integer.toHexString(mDisabled)); 1367 } 1368} 1369 1370 1371