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