TabletStatusBar.java revision a3b7b57a5211c210a61bb9bea118531eed08591a
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.Dialog; 27import android.app.PendingIntent; 28import android.app.Notification; 29import android.app.StatusBarManager; 30import android.content.Context; 31import android.content.Intent; 32import android.content.SharedPreferences; 33import android.content.res.Configuration; 34import android.content.res.Resources; 35import android.inputmethodservice.InputMethodService; 36import android.graphics.PixelFormat; 37import android.graphics.Point; 38import android.graphics.Rect; 39import android.graphics.drawable.LayerDrawable; 40import android.provider.Settings; 41import android.os.Handler; 42import android.os.IBinder; 43import android.os.Message; 44import android.os.RemoteException; 45import android.os.ServiceManager; 46import android.text.TextUtils; 47import android.util.Slog; 48import android.view.accessibility.AccessibilityEvent; 49import android.view.Display; 50import android.view.Gravity; 51import android.view.IWindowManager; 52import android.view.KeyEvent; 53import android.view.LayoutInflater; 54import android.view.MotionEvent; 55import android.view.SoundEffectConstants; 56import android.view.VelocityTracker; 57import android.view.View; 58import android.view.ViewConfiguration; 59import android.view.ViewGroup; 60import android.view.WindowManager; 61import android.view.WindowManagerImpl; 62import android.widget.ImageView; 63import android.widget.LinearLayout; 64import android.widget.RemoteViews; 65import android.widget.ScrollView; 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.BluetoothController; 75import com.android.systemui.statusbar.policy.CompatModeButton; 76import com.android.systemui.statusbar.policy.LocationController; 77import com.android.systemui.statusbar.policy.NetworkController; 78import com.android.systemui.statusbar.policy.Prefs; 79import com.android.systemui.recent.RecentsPanelView; 80 81public class TabletStatusBar extends StatusBar implements 82 HeightReceiver.OnBarHeightChangedListener, 83 InputMethodsPanel.OnHardKeyboardEnabledChangeListener { 84 public static final boolean DEBUG = false; 85 public static final boolean DEBUG_COMPAT_HELP = false; 86 public static final String TAG = "TabletStatusBar"; 87 88 89 public static final int MSG_OPEN_NOTIFICATION_PANEL = 1000; 90 public static final int MSG_CLOSE_NOTIFICATION_PANEL = 1001; 91 public static final int MSG_OPEN_NOTIFICATION_PEEK = 1002; 92 public static final int MSG_CLOSE_NOTIFICATION_PEEK = 1003; 93 public static final int MSG_OPEN_RECENTS_PANEL = 1020; 94 public static final int MSG_CLOSE_RECENTS_PANEL = 1021; 95 public static final int MSG_SHOW_CHROME = 1030; 96 public static final int MSG_HIDE_CHROME = 1031; 97 public static final int MSG_OPEN_INPUT_METHODS_PANEL = 1040; 98 public static final int MSG_CLOSE_INPUT_METHODS_PANEL = 1041; 99 public static final int MSG_OPEN_COMPAT_MODE_PANEL = 1050; 100 public static final int MSG_CLOSE_COMPAT_MODE_PANEL = 1051; 101 public static final int MSG_STOP_TICKER = 2000; 102 103 // Fitts' Law assistance for LatinIME; see policy.EventHole 104 private static final boolean FAKE_SPACE_BAR = true; 105 106 // Notification "peeking" (flyover preview of individual notifications) 107 final static boolean NOTIFICATION_PEEK_ENABLED = false; 108 final static int NOTIFICATION_PEEK_HOLD_THRESH = 200; // ms 109 final static int NOTIFICATION_PEEK_FADE_DELAY = 3000; // ms 110 111 // The height of the bar, as definied by the build. It may be taller if we're plugged 112 // into hdmi. 113 int mNaturalBarHeight = -1; 114 int mIconSize = -1; 115 int mIconHPadding = -1; 116 private int mMaxNotificationIcons = 5; 117 118 H mHandler = new H(); 119 120 IWindowManager mWindowManager; 121 122 // tracking all current notifications 123 private NotificationData mNotificationData = new NotificationData(); 124 125 TabletStatusBarView mStatusBarView; 126 View mNotificationArea; 127 View mNotificationTrigger; 128 NotificationIconArea mNotificationIconArea; 129 ViewGroup mNavigationArea; 130 131 boolean mNotificationDNDMode; 132 NotificationData.Entry mNotificationDNDDummyEntry; 133 134 ImageView mBackButton; 135 View mHomeButton; 136 View mMenuButton; 137 View mRecentButton; 138 139 ViewGroup mFeedbackIconArea; // notification icons, IME icon, compat icon 140 InputMethodButton mInputMethodSwitchButton; 141 CompatModeButton mCompatModeButton; 142 143 NotificationPanel mNotificationPanel; 144 WindowManager.LayoutParams mNotificationPanelParams; 145 NotificationPeekPanel mNotificationPeekWindow; 146 ViewGroup mNotificationPeekRow; 147 int mNotificationPeekIndex; 148 IBinder mNotificationPeekKey; 149 LayoutTransition mNotificationPeekScrubLeft, mNotificationPeekScrubRight; 150 151 int mNotificationPeekTapDuration; 152 int mNotificationFlingVelocity; 153 154 ViewGroup mPile; 155 156 HeightReceiver mHeightReceiver; 157 BatteryController mBatteryController; 158 BluetoothController mBluetoothController; 159 LocationController mLocationController; 160 NetworkController mNetworkController; 161 162 ViewGroup mBarContents; 163 LayoutTransition mBarContentsLayoutTransition; 164 165 // hide system chrome ("lights out") support 166 View mShadow; 167 168 NotificationIconArea.IconLayout mIconLayout; 169 170 TabletTicker mTicker; 171 172 View mFakeSpaceBar; 173 KeyEvent mSpaceBarKeyEvent = null; 174 175 View mCompatibilityHelpDialog = null; 176 177 // for disabling the status bar 178 int mDisabled = 0; 179 180 private RecentsPanelView mRecentsPanel; 181 private InputMethodsPanel mInputMethodsPanel; 182 private CompatModePanel mCompatModePanel; 183 184 private int mSystemUiVisibility = 0; 185 // used to notify status bar for suppressing notification LED 186 private boolean mPanelSlightlyVisible; 187 188 public Context getContext() { return mContext; } 189 190 protected void addPanelWindows() { 191 final Context context = mContext; 192 final Resources res = mContext.getResources(); 193 194 // Notification Panel 195 mNotificationPanel = (NotificationPanel)View.inflate(context, 196 R.layout.status_bar_notification_panel, null); 197 mNotificationPanel.setBar(this); 198 mNotificationPanel.show(false, false); 199 mNotificationPanel.setOnTouchListener( 200 new TouchOutsideListener(MSG_CLOSE_NOTIFICATION_PANEL, mNotificationPanel)); 201 202 // the battery icon 203 mBatteryController.addIconView((ImageView)mNotificationPanel.findViewById(R.id.battery)); 204 mBatteryController.addLabelView( 205 (TextView)mNotificationPanel.findViewById(R.id.battery_text)); 206 207 // Bt 208 mBluetoothController.addIconView( 209 (ImageView)mNotificationPanel.findViewById(R.id.bluetooth)); 210 211 // network icons: either a combo icon that switches between mobile and data, or distinct 212 // mobile and data icons 213 final ImageView comboRSSI = 214 (ImageView)mNotificationPanel.findViewById(R.id.network_signal); 215 if (comboRSSI != null) { 216 mNetworkController.addCombinedSignalIconView(comboRSSI); 217 } 218 final ImageView mobileRSSI = 219 (ImageView)mNotificationPanel.findViewById(R.id.mobile_signal); 220 if (mobileRSSI != null) { 221 mNetworkController.addPhoneSignalIconView(mobileRSSI); 222 } 223 final ImageView wifiRSSI = 224 (ImageView)mNotificationPanel.findViewById(R.id.wifi_signal); 225 if (wifiRSSI != null) { 226 mNetworkController.addWifiIconView(wifiRSSI); 227 } 228 229 mNetworkController.addDataTypeIconView( 230 (ImageView)mNotificationPanel.findViewById(R.id.network_type)); 231 mNetworkController.addDataDirectionOverlayIconView( 232 (ImageView)mNotificationPanel.findViewById(R.id.network_direction)); 233 mNetworkController.addLabelView( 234 (TextView)mNotificationPanel.findViewById(R.id.network_text)); 235 mNetworkController.addLabelView( 236 (TextView)mBarContents.findViewById(R.id.network_text)); 237 238 mStatusBarView.setIgnoreChildren(0, mNotificationTrigger, mNotificationPanel); 239 240 WindowManager.LayoutParams lp = mNotificationPanelParams = new WindowManager.LayoutParams( 241 res.getDimensionPixelSize(R.dimen.notification_panel_width), 242 getNotificationPanelHeight(), 243 WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL, 244 WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN 245 | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS 246 | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM 247 | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH 248 | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED, 249 PixelFormat.TRANSLUCENT); 250 lp.gravity = Gravity.BOTTOM | Gravity.RIGHT; 251 lp.setTitle("NotificationPanel"); 252 lp.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED 253 | WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING; 254 lp.windowAnimations = com.android.internal.R.style.Animation; // == no animation 255// lp.windowAnimations = com.android.internal.R.style.Animation_ZoomButtons; // simple fade 256 257 WindowManagerImpl.getDefault().addView(mNotificationPanel, lp); 258 259 // Notification preview window 260 if (NOTIFICATION_PEEK_ENABLED) { 261 mNotificationPeekWindow = (NotificationPeekPanel) View.inflate(context, 262 R.layout.status_bar_notification_peek, null); 263 mNotificationPeekWindow.setBar(this); 264 265 mNotificationPeekRow = (ViewGroup) mNotificationPeekWindow.findViewById(R.id.content); 266 mNotificationPeekWindow.setVisibility(View.GONE); 267 mNotificationPeekWindow.setOnTouchListener( 268 new TouchOutsideListener(MSG_CLOSE_NOTIFICATION_PEEK, mNotificationPeekWindow)); 269 mNotificationPeekScrubRight = new LayoutTransition(); 270 mNotificationPeekScrubRight.setAnimator(LayoutTransition.APPEARING, 271 ObjectAnimator.ofInt(null, "left", -512, 0)); 272 mNotificationPeekScrubRight.setAnimator(LayoutTransition.DISAPPEARING, 273 ObjectAnimator.ofInt(null, "left", -512, 0)); 274 mNotificationPeekScrubRight.setDuration(500); 275 276 mNotificationPeekScrubLeft = new LayoutTransition(); 277 mNotificationPeekScrubLeft.setAnimator(LayoutTransition.APPEARING, 278 ObjectAnimator.ofInt(null, "left", 512, 0)); 279 mNotificationPeekScrubLeft.setAnimator(LayoutTransition.DISAPPEARING, 280 ObjectAnimator.ofInt(null, "left", 512, 0)); 281 mNotificationPeekScrubLeft.setDuration(500); 282 283 // XXX: setIgnoreChildren? 284 lp = new WindowManager.LayoutParams( 285 512, // ViewGroup.LayoutParams.WRAP_CONTENT, 286 ViewGroup.LayoutParams.WRAP_CONTENT, 287 WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL, 288 WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS 289 | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM 290 | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH, 291 PixelFormat.TRANSLUCENT); 292 lp.gravity = Gravity.BOTTOM | Gravity.RIGHT; 293 lp.y = res.getDimensionPixelOffset(R.dimen.peek_window_y_offset); 294 lp.setTitle("NotificationPeekWindow"); 295 lp.windowAnimations = com.android.internal.R.style.Animation_Toast; 296 297 WindowManagerImpl.getDefault().addView(mNotificationPeekWindow, lp); 298 } 299 300 // Recents Panel 301 mRecentsPanel = (RecentsPanelView) View.inflate(context, 302 R.layout.status_bar_recent_panel, null); 303 mRecentsPanel.setVisibility(View.GONE); 304 mRecentsPanel.setOnTouchListener(new TouchOutsideListener(MSG_CLOSE_RECENTS_PANEL, 305 mRecentsPanel)); 306 mStatusBarView.setIgnoreChildren(2, mRecentButton, mRecentsPanel); 307 308 lp = new WindowManager.LayoutParams( 309 (int) res.getDimension(R.dimen.status_bar_recents_width), 310 ViewGroup.LayoutParams.MATCH_PARENT, 311 WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL, 312 WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN 313 | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM 314 | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH 315 | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED, 316 PixelFormat.TRANSLUCENT); 317 lp.gravity = Gravity.BOTTOM | Gravity.LEFT; 318 lp.setTitle("RecentsPanel"); 319 lp.windowAnimations = R.style.Animation_RecentPanel; 320 lp.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED 321 | WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING; 322 323 WindowManagerImpl.getDefault().addView(mRecentsPanel, lp); 324 mRecentsPanel.setBar(this); 325 326 // Input methods Panel 327 mInputMethodsPanel = (InputMethodsPanel) View.inflate(context, 328 R.layout.status_bar_input_methods_panel, null); 329 mInputMethodsPanel.setHardKeyboardEnabledChangeListener(this); 330 mInputMethodsPanel.setOnTouchListener(new TouchOutsideListener( 331 MSG_CLOSE_INPUT_METHODS_PANEL, mInputMethodsPanel)); 332 mInputMethodsPanel.setImeSwitchButton(mInputMethodSwitchButton); 333 mStatusBarView.setIgnoreChildren(3, mInputMethodSwitchButton, mInputMethodsPanel); 334 lp = new WindowManager.LayoutParams( 335 ViewGroup.LayoutParams.WRAP_CONTENT, 336 ViewGroup.LayoutParams.WRAP_CONTENT, 337 WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL, 338 WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN 339 | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM 340 | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH 341 | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED, 342 PixelFormat.TRANSLUCENT); 343 lp.gravity = Gravity.BOTTOM | Gravity.RIGHT; 344 lp.setTitle("InputMethodsPanel"); 345 lp.windowAnimations = R.style.Animation_RecentPanel; 346 347 WindowManagerImpl.getDefault().addView(mInputMethodsPanel, lp); 348 349 // Compatibility mode selector panel 350 mCompatModePanel = (CompatModePanel) View.inflate(context, 351 R.layout.status_bar_compat_mode_panel, null); 352 mCompatModePanel.setOnTouchListener(new TouchOutsideListener( 353 MSG_CLOSE_COMPAT_MODE_PANEL, mCompatModePanel)); 354 mCompatModePanel.setTrigger(mCompatModeButton); 355 mCompatModePanel.setVisibility(View.GONE); 356 mStatusBarView.setIgnoreChildren(4, mCompatModeButton, mCompatModePanel); 357 lp = new WindowManager.LayoutParams( 358 250, 359 ViewGroup.LayoutParams.WRAP_CONTENT, 360 WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL, 361 WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN 362 | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM 363 | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH 364 | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED, 365 PixelFormat.TRANSLUCENT); 366 lp.gravity = Gravity.BOTTOM | Gravity.RIGHT; 367 lp.setTitle("CompatModePanel"); 368 lp.windowAnimations = android.R.style.Animation_Dialog; 369 370 WindowManagerImpl.getDefault().addView(mCompatModePanel, lp); 371 } 372 373 private int getNotificationPanelHeight() { 374 final Resources res = mContext.getResources(); 375 final Display d = WindowManagerImpl.getDefault().getDefaultDisplay(); 376 final Point size = new Point(); 377 d.getRealSize(size); 378 return Math.max(res.getDimensionPixelSize(R.dimen.notification_panel_min_height), size.y); 379 } 380 381 @Override 382 public void start() { 383 super.start(); // will add the main bar view 384 } 385 386 @Override 387 protected void onConfigurationChanged(Configuration newConfig) { 388 mHeightReceiver.updateHeight(); // display size may have changed 389 loadDimens(); 390 mNotificationPanelParams.height = getNotificationPanelHeight(); 391 WindowManagerImpl.getDefault().updateViewLayout(mNotificationPanel, 392 mNotificationPanelParams); 393 } 394 395 protected void loadDimens() { 396 final Resources res = mContext.getResources(); 397 398 mNaturalBarHeight = res.getDimensionPixelSize( 399 com.android.internal.R.dimen.system_bar_height); 400 401 int newIconSize = res.getDimensionPixelSize( 402 com.android.internal.R.dimen.system_bar_icon_size); 403 int newIconHPadding = res.getDimensionPixelSize( 404 R.dimen.status_bar_icon_padding); 405 406 if (newIconHPadding != mIconHPadding || newIconSize != mIconSize) { 407// Slog.d(TAG, "size=" + newIconSize + " padding=" + newIconHPadding); 408 mIconHPadding = newIconHPadding; 409 mIconSize = newIconSize; 410 reloadAllNotificationIcons(); // reload the tray 411 } 412 413 final int numIcons = res.getInteger(R.integer.config_maxNotificationIcons); 414 if (numIcons != mMaxNotificationIcons) { 415 mMaxNotificationIcons = numIcons; 416 if (DEBUG) Slog.d(TAG, "max notification icons: " + mMaxNotificationIcons); 417 reloadAllNotificationIcons(); 418 } 419 } 420 421 protected View makeStatusBarView() { 422 final Context context = mContext; 423 424 mWindowManager = IWindowManager.Stub.asInterface( 425 ServiceManager.getService(Context.WINDOW_SERVICE)); 426 427 // This guy will listen for HDMI plugged broadcasts so we can resize the 428 // status bar as appropriate. 429 mHeightReceiver = new HeightReceiver(mContext); 430 mHeightReceiver.registerReceiver(); 431 loadDimens(); 432 433 final TabletStatusBarView sb = (TabletStatusBarView)View.inflate( 434 context, R.layout.status_bar, null); 435 mStatusBarView = sb; 436 437 sb.setHandler(mHandler); 438 439 mBarContents = (ViewGroup) sb.findViewById(R.id.bar_contents); 440 // layout transitions for the status bar's contents 441 mBarContentsLayoutTransition = new LayoutTransition(); 442 // add/removal will fade as normal 443 mBarContentsLayoutTransition.setAnimator(LayoutTransition.APPEARING, 444 ObjectAnimator.ofFloat(null, "alpha", 0f, 1f)); 445 mBarContentsLayoutTransition.setAnimator(LayoutTransition.DISAPPEARING, 446 ObjectAnimator.ofFloat(null, "alpha", 1f, 0f)); 447 // no animations for siblings on change: just jump into place please 448 mBarContentsLayoutTransition.setAnimator(LayoutTransition.CHANGE_APPEARING, null); 449 mBarContentsLayoutTransition.setAnimator(LayoutTransition.CHANGE_DISAPPEARING, null); 450 // quick like bunny 451 mBarContentsLayoutTransition.setDuration(250 * (DEBUG?10:1)); 452 mBarContents.setLayoutTransition(mBarContentsLayoutTransition); 453 454 // the whole right-hand side of the bar 455 mNotificationArea = sb.findViewById(R.id.notificationArea); 456 if (!NOTIFICATION_PEEK_ENABLED) { 457 mNotificationArea.setOnTouchListener(new NotificationTriggerTouchListener()); 458 } 459 460 // the button to open the notification area 461 mNotificationTrigger = sb.findViewById(R.id.notificationTrigger); 462 if (NOTIFICATION_PEEK_ENABLED) { 463 mNotificationTrigger.setOnTouchListener(new NotificationTriggerTouchListener()); 464 } 465 466 // the more notifications icon 467 mNotificationIconArea = (NotificationIconArea)sb.findViewById(R.id.notificationIcons); 468 469 // where the icons go 470 mIconLayout = (NotificationIconArea.IconLayout) sb.findViewById(R.id.icons); 471 if (NOTIFICATION_PEEK_ENABLED) { 472 mIconLayout.setOnTouchListener(new NotificationIconTouchListener()); 473 } 474 475 ViewConfiguration vc = ViewConfiguration.get(context); 476 mNotificationPeekTapDuration = vc.getTapTimeout(); 477 mNotificationFlingVelocity = 300; // px/s 478 479 mTicker = new TabletTicker(this); 480 481 // The icons 482 mLocationController = new LocationController(mContext); // will post a notification 483 484 mBatteryController = new BatteryController(mContext); 485 mBatteryController.addIconView((ImageView)sb.findViewById(R.id.battery)); 486 mBluetoothController = new BluetoothController(mContext); 487 mBluetoothController.addIconView((ImageView)sb.findViewById(R.id.bluetooth)); 488 489 mNetworkController = new NetworkController(mContext); 490 final SignalClusterView signalCluster = 491 (SignalClusterView)sb.findViewById(R.id.signal_cluster); 492 mNetworkController.addSignalCluster(signalCluster); 493 494 // The navigation buttons 495 mBackButton = (ImageView)sb.findViewById(R.id.back); 496 mNavigationArea = (ViewGroup) sb.findViewById(R.id.navigationArea); 497 mHomeButton = mNavigationArea.findViewById(R.id.home); 498 mMenuButton = mNavigationArea.findViewById(R.id.menu); 499 mRecentButton = mNavigationArea.findViewById(R.id.recent_apps); 500 mRecentButton.setOnClickListener(mOnClickListener); 501 mNavigationArea.setLayoutTransition(mBarContentsLayoutTransition); 502 // no multi-touch on the nav buttons 503 mNavigationArea.setMotionEventSplittingEnabled(false); 504 505 // The bar contents buttons 506 mFeedbackIconArea = (ViewGroup)sb.findViewById(R.id.feedbackIconArea); 507 mInputMethodSwitchButton = (InputMethodButton) sb.findViewById(R.id.imeSwitchButton); 508 // Overwrite the lister 509 mInputMethodSwitchButton.setOnClickListener(mOnClickListener); 510 511 mCompatModeButton = (CompatModeButton) sb.findViewById(R.id.compatModeButton); 512 mCompatModeButton.setOnClickListener(mOnClickListener); 513 514 // for redirecting errant bar taps to the IME 515 mFakeSpaceBar = sb.findViewById(R.id.fake_space_bar); 516 517 // "shadows" of the status bar features, for lights-out mode 518 mShadow = sb.findViewById(R.id.bar_shadow); 519 mShadow.setOnTouchListener( 520 new View.OnTouchListener() { 521 public boolean onTouch(View v, MotionEvent ev) { 522 if (ev.getAction() == MotionEvent.ACTION_DOWN) { 523 // even though setting the systemUI visibility below will turn these views 524 // on, we need them to come up faster so that they can catch this motion 525 // event 526 mShadow.setVisibility(View.GONE); 527 mBarContents.setVisibility(View.VISIBLE); 528 529 try { 530 mBarService.setSystemUiVisibility(View.STATUS_BAR_VISIBLE); 531 } catch (RemoteException ex) { 532 // system process dead 533 } 534 } 535 return false; 536 } 537 }); 538 539 // tuning parameters 540 final int LIGHTS_GOING_OUT_SYSBAR_DURATION = 600; 541 final int LIGHTS_GOING_OUT_SHADOW_DURATION = 1000; 542 final int LIGHTS_GOING_OUT_SHADOW_DELAY = 500; 543 544 final int LIGHTS_COMING_UP_SYSBAR_DURATION = 200; 545// final int LIGHTS_COMING_UP_SYSBAR_DELAY = 50; 546 final int LIGHTS_COMING_UP_SHADOW_DURATION = 0; 547 548 LayoutTransition xition = new LayoutTransition(); 549 xition.setAnimator(LayoutTransition.APPEARING, 550 ObjectAnimator.ofFloat(null, "alpha", 0.5f, 1f)); 551 xition.setDuration(LayoutTransition.APPEARING, LIGHTS_COMING_UP_SYSBAR_DURATION); 552 xition.setStartDelay(LayoutTransition.APPEARING, 0); 553 xition.setAnimator(LayoutTransition.DISAPPEARING, 554 ObjectAnimator.ofFloat(null, "alpha", 1f, 0f)); 555 xition.setDuration(LayoutTransition.DISAPPEARING, LIGHTS_GOING_OUT_SYSBAR_DURATION); 556 xition.setStartDelay(LayoutTransition.DISAPPEARING, 0); 557 ((ViewGroup)sb.findViewById(R.id.bar_contents_holder)).setLayoutTransition(xition); 558 559 xition = new LayoutTransition(); 560 xition.setAnimator(LayoutTransition.APPEARING, 561 ObjectAnimator.ofFloat(null, "alpha", 0f, 1f)); 562 xition.setDuration(LayoutTransition.APPEARING, LIGHTS_GOING_OUT_SHADOW_DURATION); 563 xition.setStartDelay(LayoutTransition.APPEARING, LIGHTS_GOING_OUT_SHADOW_DELAY); 564 xition.setAnimator(LayoutTransition.DISAPPEARING, 565 ObjectAnimator.ofFloat(null, "alpha", 1f, 0f)); 566 xition.setDuration(LayoutTransition.DISAPPEARING, LIGHTS_COMING_UP_SHADOW_DURATION); 567 xition.setStartDelay(LayoutTransition.DISAPPEARING, 0); 568 ((ViewGroup)sb.findViewById(R.id.bar_shadow_holder)).setLayoutTransition(xition); 569 570 // set the initial view visibility 571 setAreThereNotifications(); 572 573 // Add the windows 574 addPanelWindows(); 575 576 mPile = (ViewGroup)mNotificationPanel.findViewById(R.id.content); 577 mPile.removeAllViews(); 578 579 ScrollView scroller = (ScrollView)mPile.getParent(); 580 scroller.setFillViewport(true); 581 582 mHeightReceiver.addOnBarHeightChangedListener(this); 583 584 return sb; 585 } 586 587 public int getStatusBarHeight() { 588 return mHeightReceiver.getHeight(); 589 } 590 591 protected int getStatusBarGravity() { 592 return Gravity.BOTTOM | Gravity.FILL_HORIZONTAL; 593 } 594 595 public void onBarHeightChanged(int height) { 596 final WindowManager.LayoutParams lp 597 = (WindowManager.LayoutParams)mStatusBarView.getLayoutParams(); 598 if (lp == null) { 599 // haven't been added yet 600 return; 601 } 602 if (lp.height != height) { 603 lp.height = height; 604 final WindowManager wm = WindowManagerImpl.getDefault(); 605 wm.updateViewLayout(mStatusBarView, lp); 606 } 607 } 608 609 private class H extends Handler { 610 public void handleMessage(Message m) { 611 switch (m.what) { 612 case MSG_OPEN_NOTIFICATION_PEEK: 613 if (DEBUG) Slog.d(TAG, "opening notification peek window; arg=" + m.arg1); 614 615 if (m.arg1 >= 0) { 616 final int N = mNotificationData.size(); 617 618 if (!mNotificationDNDMode) { 619 if (mNotificationPeekIndex >= 0 && mNotificationPeekIndex < N) { 620 NotificationData.Entry entry = mNotificationData.get(N-1-mNotificationPeekIndex); 621 entry.icon.setBackgroundColor(0); 622 mNotificationPeekIndex = -1; 623 mNotificationPeekKey = null; 624 } 625 } 626 627 final int peekIndex = m.arg1; 628 if (peekIndex < N) { 629 //Slog.d(TAG, "loading peek: " + peekIndex); 630 NotificationData.Entry entry = 631 mNotificationDNDMode 632 ? mNotificationDNDDummyEntry 633 : mNotificationData.get(N-1-peekIndex); 634 NotificationData.Entry copy = new NotificationData.Entry( 635 entry.key, 636 entry.notification, 637 entry.icon); 638 inflateViews(copy, mNotificationPeekRow); 639 640 if (mNotificationDNDMode) { 641 copy.content.setOnClickListener(new View.OnClickListener() { 642 public void onClick(View v) { 643 SharedPreferences.Editor editor = Prefs.edit(mContext); 644 editor.putBoolean(Prefs.DO_NOT_DISTURB_PREF, false); 645 editor.apply(); 646 animateCollapse(); 647 visibilityChanged(false); 648 } 649 }); 650 } 651 652 entry.icon.setBackgroundColor(0x20FFFFFF); 653 654// mNotificationPeekRow.setLayoutTransition( 655// peekIndex < mNotificationPeekIndex 656// ? mNotificationPeekScrubLeft 657// : mNotificationPeekScrubRight); 658 659 mNotificationPeekRow.removeAllViews(); 660 mNotificationPeekRow.addView(copy.row); 661 662 mNotificationPeekWindow.setVisibility(View.VISIBLE); 663 mNotificationPanel.show(false, true); 664 665 mNotificationPeekIndex = peekIndex; 666 mNotificationPeekKey = entry.key; 667 } 668 } 669 break; 670 case MSG_CLOSE_NOTIFICATION_PEEK: 671 if (DEBUG) Slog.d(TAG, "closing notification peek window"); 672 mNotificationPeekWindow.setVisibility(View.GONE); 673 mNotificationPeekRow.removeAllViews(); 674 675 final int N = mNotificationData.size(); 676 if (mNotificationPeekIndex >= 0 && mNotificationPeekIndex < N) { 677 NotificationData.Entry entry = 678 mNotificationDNDMode 679 ? mNotificationDNDDummyEntry 680 : mNotificationData.get(N-1-mNotificationPeekIndex); 681 entry.icon.setBackgroundColor(0); 682 } 683 684 mNotificationPeekIndex = -1; 685 mNotificationPeekKey = null; 686 break; 687 case MSG_OPEN_NOTIFICATION_PANEL: 688 if (DEBUG) Slog.d(TAG, "opening notifications panel"); 689 if (!mNotificationPanel.isShowing()) { 690 if (NOTIFICATION_PEEK_ENABLED) { 691 mNotificationPeekWindow.setVisibility(View.GONE); 692 } 693 mNotificationPanel.show(true, true); 694 mNotificationArea.setVisibility(View.INVISIBLE); 695 mTicker.halt(); 696 } 697 break; 698 case MSG_CLOSE_NOTIFICATION_PANEL: 699 if (DEBUG) Slog.d(TAG, "closing notifications panel"); 700 if (mNotificationPanel.isShowing()) { 701 mNotificationPanel.show(false, true); 702 mNotificationArea.setVisibility(View.VISIBLE); 703 } 704 break; 705 case MSG_OPEN_RECENTS_PANEL: 706 if (DEBUG) Slog.d(TAG, "opening recents panel"); 707 if (mRecentsPanel != null) { 708 disable(StatusBarManager.DISABLE_BACK); 709 mRecentsPanel.setVisibility(View.VISIBLE); 710 mRecentsPanel.show(true, true); 711 } 712 break; 713 case MSG_CLOSE_RECENTS_PANEL: 714 if (DEBUG) Slog.d(TAG, "closing recents panel"); 715 if (mRecentsPanel != null && mRecentsPanel.isShowing()) { 716 disable(StatusBarManager.DISABLE_NONE); 717 mRecentsPanel.show(false, true); 718 } 719 break; 720 case MSG_OPEN_INPUT_METHODS_PANEL: 721 if (DEBUG) Slog.d(TAG, "opening input methods panel"); 722 if (mInputMethodsPanel != null) mInputMethodsPanel.openPanel(); 723 break; 724 case MSG_CLOSE_INPUT_METHODS_PANEL: 725 if (DEBUG) Slog.d(TAG, "closing input methods panel"); 726 if (mInputMethodsPanel != null) mInputMethodsPanel.closePanel(false); 727 break; 728 case MSG_OPEN_COMPAT_MODE_PANEL: 729 if (DEBUG) Slog.d(TAG, "opening compat panel"); 730 if (mCompatModePanel != null) mCompatModePanel.openPanel(); 731 break; 732 case MSG_CLOSE_COMPAT_MODE_PANEL: 733 if (DEBUG) Slog.d(TAG, "closing compat panel"); 734 if (mCompatModePanel != null) mCompatModePanel.closePanel(); 735 break; 736 case MSG_SHOW_CHROME: 737 if (DEBUG) Slog.d(TAG, "hiding shadows (lights on)"); 738 mBarContents.setVisibility(View.VISIBLE); 739 mShadow.setVisibility(View.GONE); 740 mSystemUiVisibility &= ~View.SYSTEM_UI_FLAG_LOW_PROFILE; 741 notifyUiVisibilityChanged(); 742 break; 743 case MSG_HIDE_CHROME: 744 if (DEBUG) Slog.d(TAG, "showing shadows (lights out)"); 745 animateCollapse(); 746 visibilityChanged(false); 747 mBarContents.setVisibility(View.GONE); 748 mShadow.setVisibility(View.VISIBLE); 749 mSystemUiVisibility |= View.SYSTEM_UI_FLAG_LOW_PROFILE; 750 notifyUiVisibilityChanged(); 751 break; 752 case MSG_STOP_TICKER: 753 mTicker.halt(); 754 break; 755 } 756 } 757 } 758 759 public void addIcon(String slot, int index, int viewIndex, StatusBarIcon icon) { 760 if (DEBUG) Slog.d(TAG, "addIcon(" + slot + ") -> " + icon); 761 } 762 763 public void updateIcon(String slot, int index, int viewIndex, 764 StatusBarIcon old, StatusBarIcon icon) { 765 if (DEBUG) Slog.d(TAG, "updateIcon(" + slot + ") -> " + icon); 766 } 767 768 public void removeIcon(String slot, int index, int viewIndex) { 769 if (DEBUG) Slog.d(TAG, "removeIcon(" + slot + ")"); 770 } 771 772 public void addNotification(IBinder key, StatusBarNotification notification) { 773 if (DEBUG) Slog.d(TAG, "addNotification(" + key + " -> " + notification + ")"); 774 addNotificationViews(key, notification); 775 776 final boolean immersive = isImmersive(); 777 if (false && immersive) { 778 // TODO: immersive mode popups for tablet 779 } else if (notification.notification.fullScreenIntent != null) { 780 // not immersive & a full-screen alert should be shown 781 Slog.w(TAG, "Notification has fullScreenIntent and activity is not immersive;" 782 + " sending fullScreenIntent"); 783 try { 784 notification.notification.fullScreenIntent.send(); 785 } catch (PendingIntent.CanceledException e) { 786 } 787 } else { 788 tick(key, notification, true); 789 } 790 791 setAreThereNotifications(); 792 } 793 794 public void updateNotification(IBinder key, StatusBarNotification notification) { 795 if (DEBUG) Slog.d(TAG, "updateNotification(" + key + " -> " + notification + ")"); 796 797 final NotificationData.Entry oldEntry = mNotificationData.findByKey(key); 798 if (oldEntry == null) { 799 Slog.w(TAG, "updateNotification for unknown key: " + key); 800 return; 801 } 802 803 final StatusBarNotification oldNotification = oldEntry.notification; 804 final RemoteViews oldContentView = oldNotification.notification.contentView; 805 806 final RemoteViews contentView = notification.notification.contentView; 807 808 if (DEBUG) { 809 Slog.d(TAG, "old notification: when=" + oldNotification.notification.when 810 + " ongoing=" + oldNotification.isOngoing() 811 + " expanded=" + oldEntry.expanded 812 + " contentView=" + oldContentView 813 + " rowParent=" + oldEntry.row.getParent()); 814 Slog.d(TAG, "new notification: when=" + notification.notification.when 815 + " ongoing=" + oldNotification.isOngoing() 816 + " contentView=" + contentView); 817 } 818 819 // Can we just reapply the RemoteViews in place? If when didn't change, the order 820 // didn't change. 821 boolean contentsUnchanged = oldEntry.expanded != null 822 && contentView != null && oldContentView != null 823 && contentView.getPackage() != null 824 && oldContentView.getPackage() != null 825 && oldContentView.getPackage().equals(contentView.getPackage()) 826 && oldContentView.getLayoutId() == contentView.getLayoutId(); 827 ViewGroup rowParent = (ViewGroup) oldEntry.row.getParent(); 828 boolean orderUnchanged = notification.notification.when==oldNotification.notification.when 829 && notification.priority == oldNotification.priority; 830 // priority now encompasses isOngoing() 831 boolean isLastAnyway = rowParent.indexOfChild(oldEntry.row) == rowParent.getChildCount()-1; 832 if (contentsUnchanged && (orderUnchanged || isLastAnyway)) { 833 if (DEBUG) Slog.d(TAG, "reusing notification for key: " + key); 834 oldEntry.notification = notification; 835 try { 836 // Reapply the RemoteViews 837 contentView.reapply(mContext, oldEntry.content); 838 // update the contentIntent 839 final PendingIntent contentIntent = notification.notification.contentIntent; 840 if (contentIntent != null) { 841 oldEntry.content.setOnClickListener(new NotificationClicker(contentIntent, 842 notification.pkg, notification.tag, notification.id)); 843 } else { 844 oldEntry.content.setOnClickListener(null); 845 } 846 // Update the icon. 847 final StatusBarIcon ic = new StatusBarIcon(notification.pkg, 848 notification.notification.icon, notification.notification.iconLevel, 849 notification.notification.number, 850 notification.notification.tickerText); 851 if (!oldEntry.icon.set(ic)) { 852 handleNotificationError(key, notification, "Couldn't update icon: " + ic); 853 return; 854 } 855 // Update the large icon 856 if (notification.notification.largeIcon != null) { 857 oldEntry.largeIcon.setImageBitmap(notification.notification.largeIcon); 858 } else { 859 oldEntry.largeIcon.getLayoutParams().width = 0; 860 oldEntry.largeIcon.setVisibility(View.INVISIBLE); 861 } 862 863 if (NOTIFICATION_PEEK_ENABLED && key == mNotificationPeekKey) { 864 // must update the peek window 865 Message peekMsg = mHandler.obtainMessage(MSG_OPEN_NOTIFICATION_PEEK); 866 peekMsg.arg1 = mNotificationPeekIndex; 867 mHandler.removeMessages(MSG_OPEN_NOTIFICATION_PEEK); 868 mHandler.sendMessage(peekMsg); 869 } 870 } 871 catch (RuntimeException e) { 872 // It failed to add cleanly. Log, and remove the view from the panel. 873 Slog.w(TAG, "Couldn't reapply views for package " + contentView.getPackage(), e); 874 removeNotificationViews(key); 875 addNotificationViews(key, notification); 876 } 877 } else { 878 if (DEBUG) Slog.d(TAG, "not reusing notification for key: " + key); 879 removeNotificationViews(key); 880 addNotificationViews(key, notification); 881 } 882 883 // Restart the ticker if it's still running 884 if (notification.notification.tickerText != null 885 && !TextUtils.equals(notification.notification.tickerText, 886 oldEntry.notification.notification.tickerText)) { 887 tick(key, notification, false); 888 } 889 890 setAreThereNotifications(); 891 } 892 893 public void removeNotification(IBinder key) { 894 if (DEBUG) Slog.d(TAG, "removeNotification(" + key + ")"); 895 removeNotificationViews(key); 896 mTicker.remove(key); 897 setAreThereNotifications(); 898 } 899 900 public void showClock(boolean show) { 901 View clock = mBarContents.findViewById(R.id.clock); 902 View network_text = mBarContents.findViewById(R.id.network_text); 903 if (clock != null) { 904 clock.setVisibility(show ? View.VISIBLE : View.GONE); 905 } 906 if (network_text != null) { 907 network_text.setVisibility((!show) ? View.VISIBLE : View.GONE); 908 } 909 } 910 911 public void disable(int state) { 912 int old = mDisabled; 913 int diff = state ^ old; 914 mDisabled = state; 915 916 // act accordingly 917 if ((diff & StatusBarManager.DISABLE_CLOCK) != 0) { 918 boolean show = (state & StatusBarManager.DISABLE_CLOCK) == 0; 919 Slog.i(TAG, "DISABLE_CLOCK: " + (show ? "no" : "yes")); 920 showClock(show); 921 } 922 if ((diff & StatusBarManager.DISABLE_SYSTEM_INFO) != 0) { 923 boolean show = (state & StatusBarManager.DISABLE_SYSTEM_INFO) == 0; 924 Slog.i(TAG, "DISABLE_SYSTEM_INFO: " + (show ? "no" : "yes")); 925 mNotificationTrigger.setVisibility(show ? View.VISIBLE : View.GONE); 926 } 927 if ((diff & StatusBarManager.DISABLE_EXPAND) != 0) { 928 if ((state & StatusBarManager.DISABLE_EXPAND) != 0) { 929 Slog.i(TAG, "DISABLE_EXPAND: yes"); 930 animateCollapse(); 931 visibilityChanged(false); 932 } 933 } 934 if ((diff & StatusBarManager.DISABLE_NOTIFICATION_ICONS) != 0) { 935 mNotificationDNDMode = Prefs.read(mContext) 936 .getBoolean(Prefs.DO_NOT_DISTURB_PREF, Prefs.DO_NOT_DISTURB_DEFAULT); 937 938 if ((state & StatusBarManager.DISABLE_NOTIFICATION_ICONS) != 0) { 939 Slog.i(TAG, "DISABLE_NOTIFICATION_ICONS: yes" + (mNotificationDNDMode?" (DND)":"")); 940 mTicker.halt(); 941 } else { 942 Slog.i(TAG, "DISABLE_NOTIFICATION_ICONS: no" + (mNotificationDNDMode?" (DND)":"")); 943 } 944 945 // refresh icons to show either notifications or the DND message 946 reloadAllNotificationIcons(); 947 } else if ((diff & StatusBarManager.DISABLE_NOTIFICATION_TICKER) != 0) { 948 if ((state & StatusBarManager.DISABLE_NOTIFICATION_TICKER) != 0) { 949 mTicker.halt(); 950 } 951 } 952 if ((diff & (StatusBarManager.DISABLE_NAVIGATION | StatusBarManager.DISABLE_BACK)) != 0) { 953 setNavigationVisibility(state & 954 (StatusBarManager.DISABLE_NAVIGATION | StatusBarManager.DISABLE_BACK)); 955 } 956 } 957 958 private void setNavigationVisibility(int visibility) { 959 boolean disableNavigation = ((visibility & StatusBarManager.DISABLE_NAVIGATION) != 0); 960 boolean disableBack = ((visibility & StatusBarManager.DISABLE_BACK) != 0); 961 962 Slog.i(TAG, "DISABLE_BACK: " + (disableBack ? "yes" : "no")); 963 Slog.i(TAG, "DISABLE_NAVIGATION: " + (disableNavigation ? "yes" : "no")); 964 965 if (disableNavigation && disableBack) { 966 mNavigationArea.setVisibility(View.INVISIBLE); 967 } else { 968 int backVisiblity = (disableBack ? View.INVISIBLE : View.VISIBLE); 969 int navVisibility = (disableNavigation ? View.INVISIBLE : View.VISIBLE); 970 971 mBackButton.setVisibility(backVisiblity); 972 mHomeButton.setVisibility(navVisibility); 973 mRecentButton.setVisibility(navVisibility); 974 // don't change menu button visibility here 975 976 mNavigationArea.setVisibility(View.VISIBLE); 977 } 978 979 mInputMethodSwitchButton.setScreenLocked(disableNavigation); 980 } 981 982 private boolean hasTicker(Notification n) { 983 return n.tickerView != null || !TextUtils.isEmpty(n.tickerText); 984 } 985 986 private void tick(IBinder key, StatusBarNotification n, boolean firstTime) { 987 // Don't show the ticker when the windowshade is open. 988 if (mNotificationPanel.isShowing()) { 989 return; 990 } 991 // If they asked for FLAG_ONLY_ALERT_ONCE, then only show this notification 992 // if it's a new notification. 993 if (!firstTime && (n.notification.flags & Notification.FLAG_ONLY_ALERT_ONCE) != 0) { 994 return; 995 } 996 // Show the ticker if one is requested. Also don't do this 997 // until status bar window is attached to the window manager, 998 // because... well, what's the point otherwise? And trying to 999 // run a ticker without being attached will crash! 1000 if (hasTicker(n.notification) && mStatusBarView.getWindowToken() != null) { 1001 if (0 == (mDisabled & (StatusBarManager.DISABLE_NOTIFICATION_ICONS 1002 | StatusBarManager.DISABLE_NOTIFICATION_TICKER))) { 1003 mTicker.add(key, n); 1004 mFeedbackIconArea.setVisibility(View.GONE); 1005 } 1006 } 1007 } 1008 1009 // called by TabletTicker when it's done with all queued ticks 1010 public void doneTicking() { 1011 mFeedbackIconArea.setVisibility(View.VISIBLE); 1012 } 1013 1014 public void animateExpand() { 1015 if (NOTIFICATION_PEEK_ENABLED) { 1016 mHandler.removeMessages(MSG_CLOSE_NOTIFICATION_PEEK); 1017 mHandler.removeMessages(MSG_OPEN_NOTIFICATION_PEEK); 1018 mHandler.sendEmptyMessage(MSG_CLOSE_NOTIFICATION_PEEK); 1019 } 1020 mHandler.removeMessages(MSG_OPEN_NOTIFICATION_PANEL); 1021 mHandler.sendEmptyMessage(MSG_OPEN_NOTIFICATION_PANEL); 1022 } 1023 1024 public void animateCollapse() { 1025 mHandler.removeMessages(MSG_CLOSE_NOTIFICATION_PANEL); 1026 mHandler.sendEmptyMessage(MSG_CLOSE_NOTIFICATION_PANEL); 1027 mHandler.removeMessages(MSG_CLOSE_RECENTS_PANEL); 1028 mHandler.sendEmptyMessage(MSG_CLOSE_RECENTS_PANEL); 1029 mHandler.removeMessages(MSG_CLOSE_INPUT_METHODS_PANEL); 1030 mHandler.sendEmptyMessage(MSG_CLOSE_INPUT_METHODS_PANEL); 1031 mHandler.removeMessages(MSG_CLOSE_COMPAT_MODE_PANEL); 1032 mHandler.sendEmptyMessage(MSG_CLOSE_COMPAT_MODE_PANEL); 1033 if (NOTIFICATION_PEEK_ENABLED) { 1034 mHandler.removeMessages(MSG_CLOSE_NOTIFICATION_PEEK); 1035 mHandler.sendEmptyMessage(MSG_CLOSE_NOTIFICATION_PEEK); 1036 } 1037 } 1038 1039 /** 1040 * The LEDs are turned o)ff when the notification panel is shown, even just a little bit. 1041 * This was added last-minute and is inconsistent with the way the rest of the notifications 1042 * are handled, because the notification isn't really cancelled. The lights are just 1043 * turned off. If any other notifications happen, the lights will turn back on. Steve says 1044 * this is what he wants. (see bug 1131461) 1045 */ 1046 void visibilityChanged(boolean visible) { 1047 if (mPanelSlightlyVisible != visible) { 1048 mPanelSlightlyVisible = visible; 1049 try { 1050 mBarService.onPanelRevealed(); 1051 } catch (RemoteException ex) { 1052 // Won't fail unless the world has ended. 1053 } 1054 } 1055 } 1056 1057 private void notifyUiVisibilityChanged() { 1058 try { 1059 mWindowManager.statusBarVisibilityChanged(mSystemUiVisibility); 1060 } catch (RemoteException ex) { 1061 } 1062 } 1063 1064 @Override // CommandQueue 1065 public void setSystemUiVisibility(int vis) { 1066 if (vis != mSystemUiVisibility) { 1067 mSystemUiVisibility = vis; 1068 1069 mHandler.removeMessages(MSG_HIDE_CHROME); 1070 mHandler.removeMessages(MSG_SHOW_CHROME); 1071 mHandler.sendEmptyMessage(0 == (vis & View.SYSTEM_UI_FLAG_LOW_PROFILE) 1072 ? MSG_SHOW_CHROME : MSG_HIDE_CHROME); 1073 1074 notifyUiVisibilityChanged(); 1075 } 1076 } 1077 1078 public void setLightsOn(boolean on) { 1079 // Policy note: if the frontmost activity needs the menu key, we assume it is a legacy app 1080 // that can't handle lights-out mode. 1081 if (mMenuButton.getVisibility() == View.VISIBLE) { 1082 on = true; 1083 } 1084 1085 Slog.v(TAG, "setLightsOn(" + on + ")"); 1086 if (on) { 1087 setSystemUiVisibility(mSystemUiVisibility & ~View.SYSTEM_UI_FLAG_LOW_PROFILE); 1088 } else { 1089 setSystemUiVisibility(mSystemUiVisibility | View.SYSTEM_UI_FLAG_LOW_PROFILE); 1090 } 1091 } 1092 1093 public void topAppWindowChanged(boolean showMenu) { 1094 if (DEBUG) { 1095 Slog.d(TAG, (showMenu?"showing":"hiding") + " the MENU button"); 1096 } 1097 mMenuButton.setVisibility(showMenu ? View.VISIBLE : View.GONE); 1098 1099 // See above re: lights-out policy for legacy apps. 1100 if (showMenu) setLightsOn(true); 1101 1102 mCompatModeButton.refresh(); 1103 if (mCompatModeButton.getVisibility() == View.VISIBLE) { 1104 if (DEBUG_COMPAT_HELP 1105 || ! Prefs.read(mContext).getBoolean(Prefs.SHOWN_COMPAT_MODE_HELP, false)) { 1106 showCompatibilityHelp(); 1107 } 1108 } else { 1109 hideCompatibilityHelp(); 1110 mCompatModePanel.closePanel(); 1111 } 1112 } 1113 1114 private void showCompatibilityHelp() { 1115 if (mCompatibilityHelpDialog != null) { 1116 return; 1117 } 1118 1119 mCompatibilityHelpDialog = View.inflate(mContext, R.layout.compat_mode_help, null); 1120 View button = mCompatibilityHelpDialog.findViewById(R.id.button); 1121 1122 button.setOnClickListener(new View.OnClickListener() { 1123 @Override 1124 public void onClick(View v) { 1125 hideCompatibilityHelp(); 1126 SharedPreferences.Editor editor = Prefs.edit(mContext); 1127 editor.putBoolean(Prefs.SHOWN_COMPAT_MODE_HELP, true); 1128 editor.apply(); 1129 } 1130 }); 1131 1132 WindowManager.LayoutParams lp = new WindowManager.LayoutParams( 1133 ViewGroup.LayoutParams.MATCH_PARENT, 1134 ViewGroup.LayoutParams.MATCH_PARENT, 1135 WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG, 1136 WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN 1137 | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS 1138 | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM, 1139 PixelFormat.TRANSLUCENT); 1140 lp.setTitle("CompatibilityModeDialog"); 1141 lp.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED 1142 | WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING; 1143 lp.windowAnimations = com.android.internal.R.style.Animation_ZoomButtons; // simple fade 1144 1145 WindowManagerImpl.getDefault().addView(mCompatibilityHelpDialog, lp); 1146 } 1147 1148 private void hideCompatibilityHelp() { 1149 if (mCompatibilityHelpDialog != null) { 1150 WindowManagerImpl.getDefault().removeView(mCompatibilityHelpDialog); 1151 mCompatibilityHelpDialog = null; 1152 } 1153 } 1154 1155 public void setImeWindowStatus(IBinder token, int vis, int backDisposition) { 1156 mInputMethodSwitchButton.setImeWindowStatus(token, 1157 (vis & InputMethodService.IME_ACTIVE) != 0); 1158 updateNotificationIcons(); 1159 mInputMethodsPanel.setImeToken(token); 1160 int res; 1161 switch (backDisposition) { 1162 case InputMethodService.BACK_DISPOSITION_WILL_NOT_DISMISS: 1163 res = R.drawable.ic_sysbar_back; 1164 break; 1165 case InputMethodService.BACK_DISPOSITION_WILL_DISMISS: 1166 res = R.drawable.ic_sysbar_back_ime; 1167 break; 1168 case InputMethodService.BACK_DISPOSITION_DEFAULT: 1169 default: 1170 if ((vis & InputMethodService.IME_VISIBLE) != 0) { 1171 res = R.drawable.ic_sysbar_back_ime; 1172 } else { 1173 res = R.drawable.ic_sysbar_back; 1174 } 1175 break; 1176 } 1177 mBackButton.setImageResource(res); 1178 if (FAKE_SPACE_BAR) { 1179 mFakeSpaceBar.setVisibility(((vis & InputMethodService.IME_VISIBLE) != 0) 1180 ? View.VISIBLE : View.GONE); 1181 } 1182 } 1183 1184 @Override 1185 public void setHardKeyboardStatus(boolean available, boolean enabled) { 1186 if (DEBUG) { 1187 Slog.d(TAG, "Set hard keyboard status: available=" + available 1188 + ", enabled=" + enabled); 1189 } 1190 mInputMethodSwitchButton.setHardKeyboardStatus(available); 1191 updateNotificationIcons(); 1192 mInputMethodsPanel.setHardKeyboardStatus(available, enabled); 1193 } 1194 1195 @Override 1196 public void onHardKeyboardEnabledChange(boolean enabled) { 1197 try { 1198 mBarService.setHardKeyboardEnabled(enabled); 1199 } catch (RemoteException ex) { 1200 } 1201 } 1202 1203 private boolean isImmersive() { 1204 try { 1205 return ActivityManagerNative.getDefault().isTopActivityImmersive(); 1206 //Slog.d(TAG, "Top activity is " + (immersive?"immersive":"not immersive")); 1207 } catch (RemoteException ex) { 1208 // the end is nigh 1209 return false; 1210 } 1211 } 1212 1213 private void setAreThereNotifications() { 1214 if (mNotificationPanel != null) { 1215 mNotificationPanel.setClearable(mNotificationData.hasClearableItems()); 1216 } 1217 } 1218 1219 /** 1220 * Cancel this notification and tell the status bar service about the failure. Hold no locks. 1221 */ 1222 void handleNotificationError(IBinder key, StatusBarNotification n, String message) { 1223 removeNotification(key); 1224 try { 1225 mBarService.onNotificationError(n.pkg, n.tag, n.id, n.uid, n.initialPid, message); 1226 } catch (RemoteException ex) { 1227 // The end is nigh. 1228 } 1229 } 1230 1231 private void sendKey(KeyEvent key) { 1232 try { 1233 if (DEBUG) Slog.d(TAG, "injecting key event: " + key); 1234 mWindowManager.injectInputEventNoWait(key); 1235 } catch (RemoteException ex) { 1236 } 1237 } 1238 1239 private View.OnClickListener mOnClickListener = new View.OnClickListener() { 1240 public void onClick(View v) { 1241 if (v == mRecentButton) { 1242 onClickRecentButton(); 1243 } else if (v == mInputMethodSwitchButton) { 1244 onClickInputMethodSwitchButton(); 1245 } else if (v == mCompatModeButton) { 1246 onClickCompatModeButton(); 1247 } 1248 } 1249 }; 1250 1251 public void onClickRecentButton() { 1252 if (DEBUG) Slog.d(TAG, "clicked recent apps; disabled=" + mDisabled); 1253 if ((mDisabled & StatusBarManager.DISABLE_EXPAND) == 0) { 1254 int msg = (mRecentsPanel.getVisibility() == View.GONE) 1255 ? MSG_OPEN_RECENTS_PANEL 1256 : MSG_CLOSE_RECENTS_PANEL; 1257 mHandler.removeMessages(msg); 1258 mHandler.sendEmptyMessage(msg); 1259 } 1260 } 1261 1262 public void onClickInputMethodSwitchButton() { 1263 if (DEBUG) Slog.d(TAG, "clicked input methods panel; disabled=" + mDisabled); 1264 int msg = (mInputMethodsPanel.getVisibility() == View.GONE) ? 1265 MSG_OPEN_INPUT_METHODS_PANEL : MSG_CLOSE_INPUT_METHODS_PANEL; 1266 mHandler.removeMessages(msg); 1267 mHandler.sendEmptyMessage(msg); 1268 } 1269 1270 public void onClickCompatModeButton() { 1271 int msg = (mCompatModePanel.getVisibility() == View.GONE) ? 1272 MSG_OPEN_COMPAT_MODE_PANEL : MSG_CLOSE_COMPAT_MODE_PANEL; 1273 mHandler.removeMessages(msg); 1274 mHandler.sendEmptyMessage(msg); 1275 } 1276 1277 public NotificationClicker makeClicker(PendingIntent intent, String pkg, String tag, int id) { 1278 return new NotificationClicker(intent, pkg, tag, id); 1279 } 1280 1281 private class NotificationClicker implements View.OnClickListener { 1282 private PendingIntent mIntent; 1283 private String mPkg; 1284 private String mTag; 1285 private int mId; 1286 1287 NotificationClicker(PendingIntent intent, String pkg, String tag, int id) { 1288 mIntent = intent; 1289 mPkg = pkg; 1290 mTag = tag; 1291 mId = id; 1292 } 1293 1294 public void onClick(View v) { 1295 try { 1296 // The intent we are sending is for the application, which 1297 // won't have permission to immediately start an activity after 1298 // the user switches to home. We know it is safe to do at this 1299 // point, so make sure new activity switches are now allowed. 1300 ActivityManagerNative.getDefault().resumeAppSwitches(); 1301 } catch (RemoteException e) { 1302 } 1303 1304 if (mIntent != null) { 1305 int[] pos = new int[2]; 1306 v.getLocationOnScreen(pos); 1307 Intent overlay = new Intent(); 1308 overlay.setSourceBounds( 1309 new Rect(pos[0], pos[1], pos[0]+v.getWidth(), pos[1]+v.getHeight())); 1310 try { 1311 mIntent.send(mContext, 0, overlay); 1312 } catch (PendingIntent.CanceledException e) { 1313 // the stack trace isn't very helpful here. Just log the exception message. 1314 Slog.w(TAG, "Sending contentIntent failed: " + e); 1315 } 1316 } 1317 1318 try { 1319 mBarService.onNotificationClick(mPkg, mTag, mId); 1320 } catch (RemoteException ex) { 1321 // system process is dead if we're here. 1322 } 1323 1324 // close the shade if it was open 1325 animateCollapse(); 1326 visibilityChanged(false); 1327 1328 // If this click was on the intruder alert, hide that instead 1329// mHandler.sendEmptyMessage(MSG_HIDE_INTRUDER); 1330 } 1331 } 1332 1333 StatusBarNotification removeNotificationViews(IBinder key) { 1334 NotificationData.Entry entry = mNotificationData.remove(key); 1335 if (entry == null) { 1336 Slog.w(TAG, "removeNotification for unknown key: " + key); 1337 return null; 1338 } 1339 // Remove the expanded view. 1340 ViewGroup rowParent = (ViewGroup)entry.row.getParent(); 1341 if (rowParent != null) rowParent.removeView(entry.row); 1342 1343 if (NOTIFICATION_PEEK_ENABLED && key == mNotificationPeekKey) { 1344 // must close the peek as well, since it's gone 1345 mHandler.sendEmptyMessage(MSG_CLOSE_NOTIFICATION_PEEK); 1346 } 1347 // Remove the icon. 1348// ViewGroup iconParent = (ViewGroup)entry.icon.getParent(); 1349// if (iconParent != null) iconParent.removeView(entry.icon); 1350 updateNotificationIcons(); 1351 1352 return entry.notification; 1353 } 1354 1355 private class NotificationTriggerTouchListener implements View.OnTouchListener { 1356 VelocityTracker mVT; 1357 float mInitialTouchX, mInitialTouchY; 1358 int mTouchSlop; 1359 1360 public NotificationTriggerTouchListener() { 1361 mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop(); 1362 } 1363 1364 private Runnable mHiliteOnR = new Runnable() { public void run() { 1365 mNotificationArea.setBackgroundResource( 1366 com.android.internal.R.drawable.list_selector_pressed_holo_dark); 1367 }}; 1368 public void hilite(final boolean on) { 1369 if (on) { 1370 mNotificationArea.postDelayed(mHiliteOnR, 100); 1371 } else { 1372 mNotificationArea.removeCallbacks(mHiliteOnR); 1373 mNotificationArea.setBackgroundDrawable(null); 1374 } 1375 } 1376 1377 public boolean onTouch(View v, MotionEvent event) { 1378// Slog.d(TAG, String.format("touch: (%.1f, %.1f) initial: (%.1f, %.1f)", 1379// event.getX(), 1380// event.getY(), 1381// mInitialTouchX, 1382// mInitialTouchY)); 1383 1384 if ((mDisabled & StatusBarManager.DISABLE_EXPAND) != 0) { 1385 return true; 1386 } 1387 1388 final int action = event.getAction(); 1389 switch (action) { 1390 case MotionEvent.ACTION_DOWN: 1391 mVT = VelocityTracker.obtain(); 1392 mInitialTouchX = event.getX(); 1393 mInitialTouchY = event.getY(); 1394 hilite(true); 1395 // fall through 1396 case MotionEvent.ACTION_OUTSIDE: 1397 case MotionEvent.ACTION_MOVE: 1398 // check for fling 1399 if (mVT != null) { 1400 mVT.addMovement(event); 1401 mVT.computeCurrentVelocity(1000); // pixels per second 1402 // require a little more oomph once we're already in peekaboo mode 1403 if (mVT.getYVelocity() < -mNotificationFlingVelocity) { 1404 animateExpand(); 1405 visibilityChanged(true); 1406 hilite(false); 1407 mVT.recycle(); 1408 mVT = null; 1409 } 1410 } 1411 return true; 1412 case MotionEvent.ACTION_UP: 1413 case MotionEvent.ACTION_CANCEL: 1414 hilite(false); 1415 if (mVT != null) { 1416 if (action == MotionEvent.ACTION_UP 1417 // was this a sloppy tap? 1418 && Math.abs(event.getX() - mInitialTouchX) < mTouchSlop 1419 && Math.abs(event.getY() - mInitialTouchY) < (mTouchSlop / 3) 1420 // dragging off the bottom doesn't count 1421 && (int)event.getY() < v.getBottom()) { 1422 animateExpand(); 1423 visibilityChanged(true); 1424 v.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED); 1425 v.playSoundEffect(SoundEffectConstants.CLICK); 1426 } 1427 1428 mVT.recycle(); 1429 mVT = null; 1430 return true; 1431 } 1432 } 1433 return false; 1434 } 1435 } 1436 1437 public void resetNotificationPeekFadeTimer() { 1438 if (DEBUG) { 1439 Slog.d(TAG, "setting peek fade timer for " + NOTIFICATION_PEEK_FADE_DELAY 1440 + "ms from now"); 1441 } 1442 mHandler.removeMessages(MSG_CLOSE_NOTIFICATION_PEEK); 1443 mHandler.sendEmptyMessageDelayed(MSG_CLOSE_NOTIFICATION_PEEK, 1444 NOTIFICATION_PEEK_FADE_DELAY); 1445 } 1446 1447 private class NotificationIconTouchListener implements View.OnTouchListener { 1448 VelocityTracker mVT; 1449 int mPeekIndex; 1450 float mInitialTouchX, mInitialTouchY; 1451 int mTouchSlop; 1452 1453 public NotificationIconTouchListener() { 1454 mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop(); 1455 } 1456 1457 public boolean onTouch(View v, MotionEvent event) { 1458 boolean peeking = mNotificationPeekWindow.getVisibility() != View.GONE; 1459 boolean panelShowing = mNotificationPanel.isShowing(); 1460 if (panelShowing) return false; 1461 1462 int numIcons = mIconLayout.getChildCount(); 1463 int newPeekIndex = (int)(event.getX() * numIcons / mIconLayout.getWidth()); 1464 if (newPeekIndex > numIcons - 1) newPeekIndex = numIcons - 1; 1465 else if (newPeekIndex < 0) newPeekIndex = 0; 1466 1467 final int action = event.getAction(); 1468 switch (action) { 1469 case MotionEvent.ACTION_DOWN: 1470 mVT = VelocityTracker.obtain(); 1471 mInitialTouchX = event.getX(); 1472 mInitialTouchY = event.getY(); 1473 mPeekIndex = -1; 1474 1475 // fall through 1476 case MotionEvent.ACTION_OUTSIDE: 1477 case MotionEvent.ACTION_MOVE: 1478 // peek and switch icons if necessary 1479 1480 if (newPeekIndex != mPeekIndex) { 1481 mPeekIndex = newPeekIndex; 1482 1483 if (DEBUG) Slog.d(TAG, "will peek at notification #" + mPeekIndex); 1484 Message peekMsg = mHandler.obtainMessage(MSG_OPEN_NOTIFICATION_PEEK); 1485 peekMsg.arg1 = mPeekIndex; 1486 1487 mHandler.removeMessages(MSG_OPEN_NOTIFICATION_PEEK); 1488 1489 if (peeking) { 1490 // no delay if we're scrubbing left-right 1491 mHandler.sendMessage(peekMsg); 1492 } else { 1493 // wait for fling 1494 mHandler.sendMessageDelayed(peekMsg, NOTIFICATION_PEEK_HOLD_THRESH); 1495 } 1496 } 1497 1498 // check for fling 1499 if (mVT != null) { 1500 mVT.addMovement(event); 1501 mVT.computeCurrentVelocity(1000); // pixels per second 1502 // require a little more oomph once we're already in peekaboo mode 1503 if (!panelShowing && ( 1504 (peeking && mVT.getYVelocity() < -mNotificationFlingVelocity*3) 1505 || (mVT.getYVelocity() < -mNotificationFlingVelocity))) { 1506 mHandler.removeMessages(MSG_OPEN_NOTIFICATION_PEEK); 1507 mHandler.removeMessages(MSG_OPEN_NOTIFICATION_PANEL); 1508 mHandler.sendEmptyMessage(MSG_CLOSE_NOTIFICATION_PEEK); 1509 mHandler.sendEmptyMessage(MSG_OPEN_NOTIFICATION_PANEL); 1510 } 1511 } 1512 return true; 1513 case MotionEvent.ACTION_UP: 1514 case MotionEvent.ACTION_CANCEL: 1515 mHandler.removeMessages(MSG_OPEN_NOTIFICATION_PEEK); 1516 if (!peeking) { 1517 if (action == MotionEvent.ACTION_UP 1518 // was this a sloppy tap? 1519 && Math.abs(event.getX() - mInitialTouchX) < mTouchSlop 1520 && Math.abs(event.getY() - mInitialTouchY) < (mTouchSlop / 3) 1521 // dragging off the bottom doesn't count 1522 && (int)event.getY() < v.getBottom()) { 1523 Message peekMsg = mHandler.obtainMessage(MSG_OPEN_NOTIFICATION_PEEK); 1524 peekMsg.arg1 = mPeekIndex; 1525 mHandler.removeMessages(MSG_OPEN_NOTIFICATION_PEEK); 1526 mHandler.sendMessage(peekMsg); 1527 1528 v.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED); 1529 v.playSoundEffect(SoundEffectConstants.CLICK); 1530 1531 peeking = true; // not technically true yet, but the next line will run 1532 } 1533 } 1534 1535 if (peeking) { 1536 resetNotificationPeekFadeTimer(); 1537 } 1538 1539 mVT.recycle(); 1540 mVT = null; 1541 return true; 1542 } 1543 return false; 1544 } 1545 } 1546 1547 StatusBarIconView addNotificationViews(IBinder key, StatusBarNotification notification) { 1548 if (DEBUG) { 1549 Slog.d(TAG, "addNotificationViews(key=" + key + ", notification=" + notification); 1550 } 1551 // Construct the icon. 1552 final StatusBarIconView iconView = new StatusBarIconView(mContext, 1553 notification.pkg + "/0x" + Integer.toHexString(notification.id), 1554 notification.notification); 1555 iconView.setScaleType(ImageView.ScaleType.CENTER_INSIDE); 1556 1557 final StatusBarIcon ic = new StatusBarIcon(notification.pkg, 1558 notification.notification.icon, 1559 notification.notification.iconLevel, 1560 notification.notification.number, 1561 notification.notification.tickerText); 1562 if (!iconView.set(ic)) { 1563 handleNotificationError(key, notification, "Couldn't attach StatusBarIcon: " + ic); 1564 return null; 1565 } 1566 // Construct the expanded view. 1567 NotificationData.Entry entry = new NotificationData.Entry(key, notification, iconView); 1568 if (!inflateViews(entry, mPile)) { 1569 handleNotificationError(key, notification, "Couldn't expand RemoteViews for: " 1570 + notification); 1571 return null; 1572 } 1573 1574 // Add the icon. 1575 int pos = mNotificationData.add(entry); 1576 if (DEBUG) { 1577 Slog.d(TAG, "addNotificationViews: added at " + pos); 1578 } 1579 updateNotificationIcons(); 1580 1581 return iconView; 1582 } 1583 1584 private void reloadAllNotificationIcons() { 1585 if (mIconLayout == null) return; 1586 mIconLayout.removeAllViews(); 1587 updateNotificationIcons(); 1588 } 1589 1590 private void updateNotificationIcons() { 1591 // XXX: need to implement a new limited linear layout class 1592 // to avoid removing & readding everything 1593 1594 if (mIconLayout == null) return; 1595 1596 // first, populate the main notification panel 1597 loadNotificationPanel(); 1598 1599 final LinearLayout.LayoutParams params 1600 = new LinearLayout.LayoutParams(mIconSize + 2*mIconHPadding, mNaturalBarHeight); 1601 1602 // alternate behavior in DND mode 1603 if (mNotificationDNDMode) { 1604 if (mIconLayout.getChildCount() == 0) { 1605 final Notification dndNotification = new Notification.Builder(mContext) 1606 .setContentTitle(mContext.getText(R.string.notifications_off_title)) 1607 .setContentText(mContext.getText(R.string.notifications_off_text)) 1608 .setSmallIcon(R.drawable.ic_notification_dnd) 1609 .setOngoing(true) 1610 .getNotification(); 1611 1612 final StatusBarIconView iconView = new StatusBarIconView(mContext, "_dnd", 1613 dndNotification); 1614 iconView.setImageResource(R.drawable.ic_notification_dnd); 1615 iconView.setScaleType(ImageView.ScaleType.CENTER_INSIDE); 1616 iconView.setPadding(mIconHPadding, 0, mIconHPadding, 0); 1617 1618 mNotificationDNDDummyEntry = new NotificationData.Entry( 1619 null, 1620 new StatusBarNotification("", 0, "", 0, 0, dndNotification), 1621 iconView); 1622 1623 mIconLayout.addView(iconView, params); 1624 } 1625 1626 return; 1627 } else if (0 != (mDisabled & StatusBarManager.DISABLE_NOTIFICATION_ICONS)) { 1628 // if icons are disabled but we're not in DND mode, this is probably Setup and we should 1629 // just leave the area totally empty 1630 return; 1631 } 1632 1633 int N = mNotificationData.size(); 1634 1635 if (DEBUG) { 1636 Slog.d(TAG, "refreshing icons: " + N + " notifications, mIconLayout=" + mIconLayout); 1637 } 1638 1639 ArrayList<View> toShow = new ArrayList<View>(); 1640 1641 // Extra Special Icons 1642 // The IME switcher and compatibility mode icons take the place of notifications. You didn't 1643 // need to see all those new emails, did you? 1644 int maxNotificationIconsCount = mMaxNotificationIcons; 1645 if (mInputMethodSwitchButton.getVisibility() != View.GONE) maxNotificationIconsCount --; 1646 if (mCompatModeButton.getVisibility() != View.GONE) maxNotificationIconsCount --; 1647 1648 for (int i=0; i< maxNotificationIconsCount; i++) { 1649 if (i>=N) break; 1650 toShow.add(mNotificationData.get(N-i-1).icon); 1651 } 1652 1653 ArrayList<View> toRemove = new ArrayList<View>(); 1654 for (int i=0; i<mIconLayout.getChildCount(); i++) { 1655 View child = mIconLayout.getChildAt(i); 1656 if (!toShow.contains(child)) { 1657 toRemove.add(child); 1658 } 1659 } 1660 1661 for (View remove : toRemove) { 1662 mIconLayout.removeView(remove); 1663 } 1664 1665 for (int i=0; i<toShow.size(); i++) { 1666 View v = toShow.get(i); 1667 v.setPadding(mIconHPadding, 0, mIconHPadding, 0); 1668 if (v.getParent() == null) { 1669 mIconLayout.addView(v, i, params); 1670 } 1671 } 1672 } 1673 1674 private void loadNotificationPanel() { 1675 int N = mNotificationData.size(); 1676 1677 ArrayList<View> toShow = new ArrayList<View>(); 1678 1679 for (int i=0; i<N; i++) { 1680 View row = mNotificationData.get(N-i-1).row; 1681 toShow.add(row); 1682 } 1683 1684 ArrayList<View> toRemove = new ArrayList<View>(); 1685 for (int i=0; i<mPile.getChildCount(); i++) { 1686 View child = mPile.getChildAt(i); 1687 if (!toShow.contains(child)) { 1688 toRemove.add(child); 1689 } 1690 } 1691 1692 for (View remove : toRemove) { 1693 mPile.removeView(remove); 1694 } 1695 1696 for (int i=0; i<toShow.size(); i++) { 1697 View v = toShow.get(i); 1698 if (v.getParent() == null) { 1699 mPile.addView(v, N-1-i); // the notification panel has newest at the bottom 1700 } 1701 } 1702 1703 mNotificationPanel.setNotificationCount(N); 1704 } 1705 1706 void workAroundBadLayerDrawableOpacity(View v) { 1707 LayerDrawable d = (LayerDrawable)v.getBackground(); 1708 if (d == null) return; 1709 v.setBackgroundDrawable(null); 1710 d.setOpacity(PixelFormat.TRANSLUCENT); 1711 v.setBackgroundDrawable(d); 1712 } 1713 1714 private boolean inflateViews(NotificationData.Entry entry, ViewGroup parent) { 1715 StatusBarNotification sbn = entry.notification; 1716 RemoteViews remoteViews = sbn.notification.contentView; 1717 if (remoteViews == null) { 1718 return false; 1719 } 1720 1721 // create the row view 1722 LayoutInflater inflater = (LayoutInflater)mContext.getSystemService( 1723 Context.LAYOUT_INFLATER_SERVICE); 1724 View row = inflater.inflate(R.layout.status_bar_notification_row, parent, false); 1725 workAroundBadLayerDrawableOpacity(row); 1726 View vetoButton = row.findViewById(R.id.veto); 1727 if (entry.notification.isClearable()) { 1728 final String _pkg = sbn.pkg; 1729 final String _tag = sbn.tag; 1730 final int _id = sbn.id; 1731 vetoButton.setOnClickListener(new View.OnClickListener() { 1732 public void onClick(View v) { 1733 try { 1734 mBarService.onNotificationClear(_pkg, _tag, _id); 1735 } catch (RemoteException ex) { 1736 // system process is dead if we're here. 1737 } 1738 } 1739 }); 1740 } else { 1741 if ((sbn.notification.flags & Notification.FLAG_ONGOING_EVENT) == 0) { 1742 vetoButton.setVisibility(View.INVISIBLE); 1743 vetoButton.setContentDescription("VETO"); 1744 } else { 1745 vetoButton.setVisibility(View.GONE); 1746 } 1747 } 1748 vetoButton.setContentDescription(mContext.getString( 1749 R.string.accessibility_remove_notification)); 1750 1751 // the large icon 1752 ImageView largeIcon = (ImageView)row.findViewById(R.id.large_icon); 1753 if (sbn.notification.largeIcon != null) { 1754 largeIcon.setImageBitmap(sbn.notification.largeIcon); 1755 largeIcon.setContentDescription(sbn.notification.tickerText); 1756 } else { 1757 largeIcon.getLayoutParams().width = 0; 1758 largeIcon.setVisibility(View.INVISIBLE); 1759 } 1760 largeIcon.setContentDescription(sbn.notification.tickerText); 1761 1762 // bind the click event to the content area 1763 ViewGroup content = (ViewGroup)row.findViewById(R.id.content); 1764 // XXX: update to allow controls within notification views 1765 content.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS); 1766// content.setOnFocusChangeListener(mFocusChangeListener); 1767 PendingIntent contentIntent = sbn.notification.contentIntent; 1768 if (contentIntent != null) { 1769 content.setOnClickListener(new NotificationClicker(contentIntent, 1770 sbn.pkg, sbn.tag, sbn.id)); 1771 } else { 1772 content.setOnClickListener(null); 1773 } 1774 1775 View expanded = null; 1776 Exception exception = null; 1777 try { 1778 expanded = remoteViews.apply(mContext, content); 1779 } 1780 catch (RuntimeException e) { 1781 exception = e; 1782 } 1783 if (expanded == null) { 1784 final String ident = sbn.pkg + "/0x" + Integer.toHexString(sbn.id); 1785 Slog.e(TAG, "couldn't inflate view for notification " + ident, exception); 1786 return false; 1787 } else { 1788 content.addView(expanded); 1789 row.setDrawingCacheEnabled(true); 1790 } 1791 1792 entry.row = row; 1793 entry.content = content; 1794 entry.expanded = expanded; 1795 entry.largeIcon = largeIcon; 1796 1797 return true; 1798 } 1799 1800 public void clearAll() { 1801 try { 1802 mBarService.onClearAllNotifications(); 1803 } catch (RemoteException ex) { 1804 // system process is dead if we're here. 1805 } 1806 animateCollapse(); 1807 visibilityChanged(false); 1808 } 1809 1810 public void userActivity() { 1811 } 1812 1813 public void toggleRecentApps() { 1814 int msg = (mRecentsPanel.getVisibility() == View.GONE) 1815 ? MSG_OPEN_RECENTS_PANEL : MSG_CLOSE_RECENTS_PANEL; 1816 mHandler.removeMessages(msg); 1817 mHandler.sendEmptyMessage(msg); 1818 } 1819 1820 public class TouchOutsideListener implements View.OnTouchListener { 1821 private int mMsg; 1822 private StatusBarPanel mPanel; 1823 1824 public TouchOutsideListener(int msg, StatusBarPanel panel) { 1825 mMsg = msg; 1826 mPanel = panel; 1827 } 1828 1829 public boolean onTouch(View v, MotionEvent ev) { 1830 final int action = ev.getAction(); 1831 if (action == MotionEvent.ACTION_OUTSIDE 1832 || (action == MotionEvent.ACTION_DOWN 1833 && !mPanel.isInContentArea((int)ev.getX(), (int)ev.getY()))) { 1834 mHandler.removeMessages(mMsg); 1835 mHandler.sendEmptyMessage(mMsg); 1836 return true; 1837 } 1838 return false; 1839 } 1840 } 1841 1842 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 1843 pw.print("mDisabled=0x"); 1844 pw.println(Integer.toHexString(mDisabled)); 1845 pw.println("mNetworkController:"); 1846 mNetworkController.dump(fd, pw, args); 1847 } 1848} 1849 1850 1851