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