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