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