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