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