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