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