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