1 /* 2 * Copyright (C) 2012 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 17 package com.android.systemui.statusbar.phone; 18 19 import android.annotation.ColorInt; 20 import android.annotation.DrawableRes; 21 import android.annotation.LayoutRes; 22 import android.app.StatusBarManager; 23 import android.content.Context; 24 import android.content.res.Configuration; 25 import android.content.res.TypedArray; 26 import android.graphics.Canvas; 27 import android.graphics.Paint; 28 import android.graphics.PorterDuff; 29 import android.graphics.PorterDuffXfermode; 30 import android.graphics.Rect; 31 import android.graphics.drawable.Drawable; 32 import android.media.session.MediaSessionLegacyHelper; 33 import android.net.Uri; 34 import android.os.Bundle; 35 import android.os.IBinder; 36 import android.util.AttributeSet; 37 import android.view.ActionMode; 38 import android.view.InputQueue; 39 import android.view.KeyEvent; 40 import android.view.LayoutInflater; 41 import android.view.Menu; 42 import android.view.MenuItem; 43 import android.view.MotionEvent; 44 import android.view.SurfaceHolder; 45 import android.view.View; 46 import android.view.ViewGroup; 47 import android.view.ViewTreeObserver; 48 import android.view.Window; 49 import android.view.WindowManager; 50 import android.view.WindowManagerGlobal; 51 import android.widget.FrameLayout; 52 53 import com.android.internal.view.FloatingActionMode; 54 import com.android.internal.widget.FloatingToolbar; 55 import com.android.systemui.R; 56 import com.android.systemui.classifier.FalsingManager; 57 import com.android.systemui.statusbar.BaseStatusBar; 58 import com.android.systemui.statusbar.DragDownHelper; 59 import com.android.systemui.statusbar.StatusBarState; 60 import com.android.systemui.statusbar.stack.NotificationStackScrollLayout; 61 62 63 public class StatusBarWindowView extends FrameLayout { 64 public static final String TAG = "StatusBarWindowView"; 65 public static final boolean DEBUG = BaseStatusBar.DEBUG; 66 67 private DragDownHelper mDragDownHelper; 68 private NotificationStackScrollLayout mStackScrollLayout; 69 private NotificationPanelView mNotificationPanel; 70 private View mBrightnessMirror; 71 72 private int mRightInset = 0; 73 private int mLeftInset = 0; 74 75 private PhoneStatusBar mService; 76 private final Paint mTransparentSrcPaint = new Paint(); 77 private FalsingManager mFalsingManager; 78 79 // Implements the floating action mode for TextView's Cut/Copy/Past menu. Normally provided by 80 // DecorView, but since this is a special window we have to roll our own. 81 private View mFloatingActionModeOriginatingView; 82 private ActionMode mFloatingActionMode; 83 private FloatingToolbar mFloatingToolbar; 84 private ViewTreeObserver.OnPreDrawListener mFloatingToolbarPreDrawListener; 85 86 public StatusBarWindowView(Context context, AttributeSet attrs) { 87 super(context, attrs); 88 setMotionEventSplittingEnabled(false); 89 mTransparentSrcPaint.setColor(0); 90 mTransparentSrcPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC)); 91 mFalsingManager = FalsingManager.getInstance(context); 92 } 93 94 @Override 95 protected boolean fitSystemWindows(Rect insets) { 96 if (getFitsSystemWindows()) { 97 boolean paddingChanged = insets.top != getPaddingTop() 98 || insets.bottom != getPaddingBottom(); 99 100 // Super-special right inset handling, because scrims and backdrop need to ignore it. 101 if (insets.right != mRightInset || insets.left != mLeftInset) { 102 mRightInset = insets.right; 103 mLeftInset = insets.left; 104 applyMargins(); 105 } 106 // Drop top inset, and pass through bottom inset. 107 if (paddingChanged) { 108 setPadding(0, 0, 0, 0); 109 } 110 insets.left = 0; 111 insets.top = 0; 112 insets.right = 0; 113 } else { 114 if (mRightInset != 0 || mLeftInset != 0) { 115 mRightInset = 0; 116 mLeftInset = 0; 117 applyMargins(); 118 } 119 boolean changed = getPaddingLeft() != 0 120 || getPaddingRight() != 0 121 || getPaddingTop() != 0 122 || getPaddingBottom() != 0; 123 if (changed) { 124 setPadding(0, 0, 0, 0); 125 } 126 insets.top = 0; 127 } 128 return false; 129 } 130 131 private void applyMargins() { 132 mService.mScrimController.setLeftInset(mLeftInset); 133 final int N = getChildCount(); 134 for (int i = 0; i < N; i++) { 135 View child = getChildAt(i); 136 if (child.getLayoutParams() instanceof LayoutParams) { 137 LayoutParams lp = (LayoutParams) child.getLayoutParams(); 138 if (!lp.ignoreRightInset 139 && (lp.rightMargin != mRightInset || lp.leftMargin != mLeftInset)) { 140 lp.rightMargin = mRightInset; 141 lp.leftMargin = mLeftInset; 142 child.requestLayout(); 143 } 144 } 145 } 146 } 147 148 @Override 149 public FrameLayout.LayoutParams generateLayoutParams(AttributeSet attrs) { 150 return new LayoutParams(getContext(), attrs); 151 } 152 153 @Override 154 protected FrameLayout.LayoutParams generateDefaultLayoutParams() { 155 return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); 156 } 157 158 @Override 159 protected void onFinishInflate() { 160 super.onFinishInflate(); 161 mStackScrollLayout = (NotificationStackScrollLayout) findViewById( 162 R.id.notification_stack_scroller); 163 mNotificationPanel = (NotificationPanelView) findViewById(R.id.notification_panel); 164 mBrightnessMirror = findViewById(R.id.brightness_mirror); 165 } 166 167 public void setService(PhoneStatusBar service) { 168 mService = service; 169 mDragDownHelper = new DragDownHelper(getContext(), this, mStackScrollLayout, mService); 170 } 171 172 @Override 173 protected void onAttachedToWindow () { 174 super.onAttachedToWindow(); 175 176 // We need to ensure that our window doesn't suffer from overdraw which would normally 177 // occur if our window is translucent. Since we are drawing the whole window anyway with 178 // the scrim, we don't need the window to be cleared in the beginning. 179 if (mService.isScrimSrcModeEnabled()) { 180 IBinder windowToken = getWindowToken(); 181 WindowManager.LayoutParams lp = (WindowManager.LayoutParams) getLayoutParams(); 182 lp.token = windowToken; 183 setLayoutParams(lp); 184 WindowManagerGlobal.getInstance().changeCanvasOpacity(windowToken, true); 185 setWillNotDraw(false); 186 } else { 187 setWillNotDraw(!DEBUG); 188 } 189 } 190 191 @Override 192 public boolean dispatchKeyEvent(KeyEvent event) { 193 if (mService.interceptMediaKey(event)) { 194 return true; 195 } 196 if (super.dispatchKeyEvent(event)) { 197 return true; 198 } 199 boolean down = event.getAction() == KeyEvent.ACTION_DOWN; 200 switch (event.getKeyCode()) { 201 case KeyEvent.KEYCODE_BACK: 202 if (!down) { 203 mService.onBackPressed(); 204 } 205 return true; 206 case KeyEvent.KEYCODE_MENU: 207 if (!down) { 208 return mService.onMenuPressed(); 209 } 210 case KeyEvent.KEYCODE_SPACE: 211 if (!down) { 212 return mService.onSpacePressed(); 213 } 214 break; 215 case KeyEvent.KEYCODE_VOLUME_DOWN: 216 case KeyEvent.KEYCODE_VOLUME_UP: 217 if (mService.isDozing()) { 218 MediaSessionLegacyHelper.getHelper(mContext).sendVolumeKeyEvent(event, true); 219 return true; 220 } 221 break; 222 } 223 return false; 224 } 225 226 @Override 227 public boolean dispatchTouchEvent(MotionEvent ev) { 228 mFalsingManager.onTouchEvent(ev, getWidth(), getHeight()); 229 if (mBrightnessMirror != null && mBrightnessMirror.getVisibility() == VISIBLE) { 230 // Disallow new pointers while the brightness mirror is visible. This is so that you 231 // can't touch anything other than the brightness slider while the mirror is showing 232 // and the rest of the panel is transparent. 233 if (ev.getActionMasked() == MotionEvent.ACTION_POINTER_DOWN) { 234 return false; 235 } 236 } 237 if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) { 238 mStackScrollLayout.closeControlsIfOutsideTouch(ev); 239 } 240 241 return super.dispatchTouchEvent(ev); 242 } 243 244 @Override 245 public boolean onInterceptTouchEvent(MotionEvent ev) { 246 boolean intercept = false; 247 if (mNotificationPanel.isFullyExpanded() 248 && mStackScrollLayout.getVisibility() == View.VISIBLE 249 && mService.getBarState() == StatusBarState.KEYGUARD 250 && !mService.isBouncerShowing()) { 251 intercept = mDragDownHelper.onInterceptTouchEvent(ev); 252 // wake up on a touch down event, if dozing 253 if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) { 254 mService.wakeUpIfDozing(ev.getEventTime(), ev); 255 } 256 } 257 if (!intercept) { 258 super.onInterceptTouchEvent(ev); 259 } 260 if (intercept) { 261 MotionEvent cancellation = MotionEvent.obtain(ev); 262 cancellation.setAction(MotionEvent.ACTION_CANCEL); 263 mStackScrollLayout.onInterceptTouchEvent(cancellation); 264 mNotificationPanel.onInterceptTouchEvent(cancellation); 265 cancellation.recycle(); 266 } 267 return intercept; 268 } 269 270 @Override 271 public boolean onTouchEvent(MotionEvent ev) { 272 boolean handled = false; 273 if (mService.getBarState() == StatusBarState.KEYGUARD) { 274 handled = mDragDownHelper.onTouchEvent(ev); 275 } 276 if (!handled) { 277 handled = super.onTouchEvent(ev); 278 } 279 final int action = ev.getAction(); 280 if (!handled && (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL)) { 281 mService.setInteracting(StatusBarManager.WINDOW_STATUS_BAR, false); 282 } 283 return handled; 284 } 285 286 @Override 287 public void onDraw(Canvas canvas) { 288 super.onDraw(canvas); 289 if (mService.isScrimSrcModeEnabled()) { 290 // We need to ensure that our window is always drawn fully even when we have paddings, 291 // since we simulate it to be opaque. 292 int paddedBottom = getHeight() - getPaddingBottom(); 293 int paddedRight = getWidth() - getPaddingRight(); 294 if (getPaddingTop() != 0) { 295 canvas.drawRect(0, 0, getWidth(), getPaddingTop(), mTransparentSrcPaint); 296 } 297 if (getPaddingBottom() != 0) { 298 canvas.drawRect(0, paddedBottom, getWidth(), getHeight(), mTransparentSrcPaint); 299 } 300 if (getPaddingLeft() != 0) { 301 canvas.drawRect(0, getPaddingTop(), getPaddingLeft(), paddedBottom, 302 mTransparentSrcPaint); 303 } 304 if (getPaddingRight() != 0) { 305 canvas.drawRect(paddedRight, getPaddingTop(), getWidth(), paddedBottom, 306 mTransparentSrcPaint); 307 } 308 } 309 if (DEBUG) { 310 Paint pt = new Paint(); 311 pt.setColor(0x80FFFF00); 312 pt.setStrokeWidth(12.0f); 313 pt.setStyle(Paint.Style.STROKE); 314 canvas.drawRect(0, 0, canvas.getWidth(), canvas.getHeight(), pt); 315 } 316 } 317 318 public void cancelExpandHelper() { 319 if (mStackScrollLayout != null) { 320 mStackScrollLayout.cancelExpandHelper(); 321 } 322 } 323 324 public class LayoutParams extends FrameLayout.LayoutParams { 325 326 public boolean ignoreRightInset; 327 328 public LayoutParams(int width, int height) { 329 super(width, height); 330 } 331 332 public LayoutParams(Context c, AttributeSet attrs) { 333 super(c, attrs); 334 335 TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.StatusBarWindowView_Layout); 336 ignoreRightInset = a.getBoolean( 337 R.styleable.StatusBarWindowView_Layout_ignoreRightInset, false); 338 a.recycle(); 339 } 340 } 341 342 @Override 343 public ActionMode startActionModeForChild(View originalView, ActionMode.Callback callback, 344 int type) { 345 if (type == ActionMode.TYPE_FLOATING) { 346 return startActionMode(originalView, callback, type); 347 } 348 return super.startActionModeForChild(originalView, callback, type); 349 } 350 351 private ActionMode createFloatingActionMode( 352 View originatingView, ActionMode.Callback2 callback) { 353 if (mFloatingActionMode != null) { 354 mFloatingActionMode.finish(); 355 } 356 cleanupFloatingActionModeViews(); 357 final FloatingActionMode mode = 358 new FloatingActionMode(mContext, callback, originatingView); 359 mFloatingActionModeOriginatingView = originatingView; 360 mFloatingToolbarPreDrawListener = 361 new ViewTreeObserver.OnPreDrawListener() { 362 @Override 363 public boolean onPreDraw() { 364 mode.updateViewLocationInWindow(); 365 return true; 366 } 367 }; 368 return mode; 369 } 370 371 private void setHandledFloatingActionMode(ActionMode mode) { 372 mFloatingActionMode = mode; 373 mFloatingToolbar = new FloatingToolbar(mContext, mFakeWindow); 374 ((FloatingActionMode) mFloatingActionMode).setFloatingToolbar(mFloatingToolbar); 375 mFloatingActionMode.invalidate(); // Will show the floating toolbar if necessary. 376 mFloatingActionModeOriginatingView.getViewTreeObserver() 377 .addOnPreDrawListener(mFloatingToolbarPreDrawListener); 378 } 379 380 private void cleanupFloatingActionModeViews() { 381 if (mFloatingToolbar != null) { 382 mFloatingToolbar.dismiss(); 383 mFloatingToolbar = null; 384 } 385 if (mFloatingActionModeOriginatingView != null) { 386 if (mFloatingToolbarPreDrawListener != null) { 387 mFloatingActionModeOriginatingView.getViewTreeObserver() 388 .removeOnPreDrawListener(mFloatingToolbarPreDrawListener); 389 mFloatingToolbarPreDrawListener = null; 390 } 391 mFloatingActionModeOriginatingView = null; 392 } 393 } 394 395 private ActionMode startActionMode( 396 View originatingView, ActionMode.Callback callback, int type) { 397 ActionMode.Callback2 wrappedCallback = new ActionModeCallback2Wrapper(callback); 398 ActionMode mode = createFloatingActionMode(originatingView, wrappedCallback); 399 if (mode != null && wrappedCallback.onCreateActionMode(mode, mode.getMenu())) { 400 setHandledFloatingActionMode(mode); 401 } else { 402 mode = null; 403 } 404 return mode; 405 } 406 407 private class ActionModeCallback2Wrapper extends ActionMode.Callback2 { 408 private final ActionMode.Callback mWrapped; 409 410 public ActionModeCallback2Wrapper(ActionMode.Callback wrapped) { 411 mWrapped = wrapped; 412 } 413 414 public boolean onCreateActionMode(ActionMode mode, Menu menu) { 415 return mWrapped.onCreateActionMode(mode, menu); 416 } 417 418 public boolean onPrepareActionMode(ActionMode mode, Menu menu) { 419 requestFitSystemWindows(); 420 return mWrapped.onPrepareActionMode(mode, menu); 421 } 422 423 public boolean onActionItemClicked(ActionMode mode, MenuItem item) { 424 return mWrapped.onActionItemClicked(mode, item); 425 } 426 427 public void onDestroyActionMode(ActionMode mode) { 428 mWrapped.onDestroyActionMode(mode); 429 if (mode == mFloatingActionMode) { 430 cleanupFloatingActionModeViews(); 431 mFloatingActionMode = null; 432 } 433 requestFitSystemWindows(); 434 } 435 436 @Override 437 public void onGetContentRect(ActionMode mode, View view, Rect outRect) { 438 if (mWrapped instanceof ActionMode.Callback2) { 439 ((ActionMode.Callback2) mWrapped).onGetContentRect(mode, view, outRect); 440 } else { 441 super.onGetContentRect(mode, view, outRect); 442 } 443 } 444 } 445 446 /** 447 * Minimal window to satisfy FloatingToolbar. 448 */ 449 private Window mFakeWindow = new Window(mContext) { 450 @Override 451 public void takeSurface(SurfaceHolder.Callback2 callback) { 452 } 453 454 @Override 455 public void takeInputQueue(InputQueue.Callback callback) { 456 } 457 458 @Override 459 public boolean isFloating() { 460 return false; 461 } 462 463 @Override 464 public void alwaysReadCloseOnTouchAttr() { 465 } 466 467 @Override 468 public void setContentView(@LayoutRes int layoutResID) { 469 } 470 471 @Override 472 public void setContentView(View view) { 473 } 474 475 @Override 476 public void setContentView(View view, ViewGroup.LayoutParams params) { 477 } 478 479 @Override 480 public void addContentView(View view, ViewGroup.LayoutParams params) { 481 } 482 483 @Override 484 public void clearContentView() { 485 } 486 487 @Override 488 public View getCurrentFocus() { 489 return null; 490 } 491 492 @Override 493 public LayoutInflater getLayoutInflater() { 494 return null; 495 } 496 497 @Override 498 public void setTitle(CharSequence title) { 499 } 500 501 @Override 502 public void setTitleColor(@ColorInt int textColor) { 503 } 504 505 @Override 506 public void openPanel(int featureId, KeyEvent event) { 507 } 508 509 @Override 510 public void closePanel(int featureId) { 511 } 512 513 @Override 514 public void togglePanel(int featureId, KeyEvent event) { 515 } 516 517 @Override 518 public void invalidatePanelMenu(int featureId) { 519 } 520 521 @Override 522 public boolean performPanelShortcut(int featureId, int keyCode, KeyEvent event, int flags) { 523 return false; 524 } 525 526 @Override 527 public boolean performPanelIdentifierAction(int featureId, int id, int flags) { 528 return false; 529 } 530 531 @Override 532 public void closeAllPanels() { 533 } 534 535 @Override 536 public boolean performContextMenuIdentifierAction(int id, int flags) { 537 return false; 538 } 539 540 @Override 541 public void onConfigurationChanged(Configuration newConfig) { 542 } 543 544 @Override 545 public void setBackgroundDrawable(Drawable drawable) { 546 } 547 548 @Override 549 public void setFeatureDrawableResource(int featureId, @DrawableRes int resId) { 550 } 551 552 @Override 553 public void setFeatureDrawableUri(int featureId, Uri uri) { 554 } 555 556 @Override 557 public void setFeatureDrawable(int featureId, Drawable drawable) { 558 } 559 560 @Override 561 public void setFeatureDrawableAlpha(int featureId, int alpha) { 562 } 563 564 @Override 565 public void setFeatureInt(int featureId, int value) { 566 } 567 568 @Override 569 public void takeKeyEvents(boolean get) { 570 } 571 572 @Override 573 public boolean superDispatchKeyEvent(KeyEvent event) { 574 return false; 575 } 576 577 @Override 578 public boolean superDispatchKeyShortcutEvent(KeyEvent event) { 579 return false; 580 } 581 582 @Override 583 public boolean superDispatchTouchEvent(MotionEvent event) { 584 return false; 585 } 586 587 @Override 588 public boolean superDispatchTrackballEvent(MotionEvent event) { 589 return false; 590 } 591 592 @Override 593 public boolean superDispatchGenericMotionEvent(MotionEvent event) { 594 return false; 595 } 596 597 @Override 598 public View getDecorView() { 599 return StatusBarWindowView.this; 600 } 601 602 @Override 603 public View peekDecorView() { 604 return null; 605 } 606 607 @Override 608 public Bundle saveHierarchyState() { 609 return null; 610 } 611 612 @Override 613 public void restoreHierarchyState(Bundle savedInstanceState) { 614 } 615 616 @Override 617 protected void onActive() { 618 } 619 620 @Override 621 public void setChildDrawable(int featureId, Drawable drawable) { 622 } 623 624 @Override 625 public void setChildInt(int featureId, int value) { 626 } 627 628 @Override 629 public boolean isShortcutKey(int keyCode, KeyEvent event) { 630 return false; 631 } 632 633 @Override 634 public void setVolumeControlStream(int streamType) { 635 } 636 637 @Override 638 public int getVolumeControlStream() { 639 return 0; 640 } 641 642 @Override 643 public int getStatusBarColor() { 644 return 0; 645 } 646 647 @Override 648 public void setStatusBarColor(@ColorInt int color) { 649 } 650 651 @Override 652 public int getNavigationBarColor() { 653 return 0; 654 } 655 656 @Override 657 public void setNavigationBarColor(@ColorInt int color) { 658 } 659 660 @Override 661 public void setDecorCaptionShade(int decorCaptionShade) { 662 } 663 664 @Override 665 public void setResizingCaptionDrawable(Drawable drawable) { 666 } 667 668 @Override 669 public void onMultiWindowModeChanged() { 670 } 671 672 @Override 673 public void reportActivityRelaunched() { 674 } 675 }; 676 677 } 678 679