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