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