1 /* 2 * Copyright (C) 2020 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.shade; 18 19 import static android.os.Trace.TRACE_TAG_APP; 20 21 import static com.android.systemui.statusbar.phone.CentralSurfaces.DEBUG; 22 23 import android.annotation.ColorInt; 24 import android.annotation.DrawableRes; 25 import android.annotation.LayoutRes; 26 import android.content.Context; 27 import android.content.res.Configuration; 28 import android.graphics.Canvas; 29 import android.graphics.Paint; 30 import android.graphics.Rect; 31 import android.graphics.drawable.Drawable; 32 import android.net.Uri; 33 import android.os.Bundle; 34 import android.os.Trace; 35 import android.util.AttributeSet; 36 import android.view.ActionMode; 37 import android.view.InputQueue; 38 import android.view.KeyEvent; 39 import android.view.LayoutInflater; 40 import android.view.Menu; 41 import android.view.MenuItem; 42 import android.view.MotionEvent; 43 import android.view.SurfaceHolder; 44 import android.view.View; 45 import android.view.ViewGroup; 46 import android.view.ViewTreeObserver; 47 import android.view.Window; 48 import android.view.WindowInsetsController; 49 50 import com.android.internal.view.FloatingActionMode; 51 import com.android.internal.widget.floatingtoolbar.FloatingToolbar; 52 import com.android.systemui.scene.ui.view.WindowRootView; 53 54 /** 55 * Combined keyguard and notification panel view. Also holding backdrop and scrims. This view can 56 * serve as the root view of the main SysUI window, but because other views can also serve that 57 * purpose, users of this class cannot assume it is the root. 58 */ 59 public class NotificationShadeWindowView extends WindowRootView { 60 public static final String TAG = "NotificationShadeWindowView"; 61 62 // Implements the floating action mode for TextView's Cut/Copy/Past menu. Normally provided by 63 // DecorView, but since this is a special window we have to roll our own. 64 private View mFloatingActionModeOriginatingView; 65 private ActionMode mFloatingActionMode; 66 private FloatingToolbar mFloatingToolbar; 67 private ViewTreeObserver.OnPreDrawListener mFloatingToolbarPreDrawListener; 68 69 private InteractionEventHandler mInteractionEventHandler; 70 NotificationShadeWindowView(Context context, AttributeSet attrs)71 public NotificationShadeWindowView(Context context, AttributeSet attrs) { 72 super(context, attrs); 73 setMotionEventSplittingEnabled(false); 74 } 75 76 @Override onAttachedToWindow()77 protected void onAttachedToWindow() { 78 super.onAttachedToWindow(); 79 setWillNotDraw(!DEBUG); 80 } 81 82 @Override dispatchKeyEvent(KeyEvent event)83 public boolean dispatchKeyEvent(KeyEvent event) { 84 if (mInteractionEventHandler.interceptMediaKey(event)) { 85 return true; 86 } 87 88 if (super.dispatchKeyEvent(event)) { 89 return true; 90 } 91 92 return mInteractionEventHandler.dispatchKeyEvent(event); 93 } 94 95 @Override dispatchKeyEventPreIme(KeyEvent event)96 public boolean dispatchKeyEventPreIme(KeyEvent event) { 97 return mInteractionEventHandler.dispatchKeyEventPreIme(event); 98 } 99 setInteractionEventHandler(InteractionEventHandler listener)100 protected void setInteractionEventHandler(InteractionEventHandler listener) { 101 mInteractionEventHandler = listener; 102 } 103 104 @Override dispatchTouchEvent(MotionEvent ev)105 public boolean dispatchTouchEvent(MotionEvent ev) { 106 Boolean result = mInteractionEventHandler.handleDispatchTouchEvent(ev); 107 108 result = result != null ? result : super.dispatchTouchEvent(ev); 109 110 TouchLogger.logDispatchTouch(TAG, ev, result); 111 112 mInteractionEventHandler.dispatchTouchEventComplete(); 113 114 return result; 115 } 116 117 @Override onInterceptTouchEvent(MotionEvent ev)118 public boolean onInterceptTouchEvent(MotionEvent ev) { 119 boolean intercept = mInteractionEventHandler.shouldInterceptTouchEvent(ev); 120 if (!intercept) { 121 intercept = super.onInterceptTouchEvent(ev); 122 } 123 if (intercept) { 124 mInteractionEventHandler.didIntercept(ev); 125 } 126 127 return intercept; 128 } 129 130 @Override onTouchEvent(MotionEvent ev)131 public boolean onTouchEvent(MotionEvent ev) { 132 boolean handled = mInteractionEventHandler.handleTouchEvent(ev); 133 134 if (!handled) { 135 handled = super.onTouchEvent(ev); 136 } 137 138 if (!handled) { 139 mInteractionEventHandler.didNotHandleTouchEvent(ev); 140 } 141 142 return handled; 143 } 144 145 @Override onDraw(Canvas canvas)146 public void onDraw(Canvas canvas) { 147 super.onDraw(canvas); 148 if (DEBUG) { 149 Paint pt = new Paint(); 150 pt.setColor(0x80FFFF00); 151 pt.setStrokeWidth(12.0f); 152 pt.setStyle(Paint.Style.STROKE); 153 canvas.drawRect(0, 0, canvas.getWidth(), canvas.getHeight(), pt); 154 } 155 } 156 157 @Override startActionModeForChild(View originalView, ActionMode.Callback callback, int type)158 public ActionMode startActionModeForChild(View originalView, ActionMode.Callback callback, 159 int type) { 160 if (type == ActionMode.TYPE_FLOATING) { 161 return startActionMode(originalView, callback); 162 } 163 return super.startActionModeForChild(originalView, callback, type); 164 } 165 createFloatingActionMode( View originatingView, ActionMode.Callback2 callback)166 private ActionMode createFloatingActionMode( 167 View originatingView, ActionMode.Callback2 callback) { 168 if (mFloatingActionMode != null) { 169 mFloatingActionMode.finish(); 170 } 171 cleanupFloatingActionModeViews(); 172 mFloatingToolbar = new FloatingToolbar(mFakeWindow); 173 final FloatingActionMode mode = 174 new FloatingActionMode(mContext, callback, originatingView, mFloatingToolbar); 175 mFloatingActionModeOriginatingView = originatingView; 176 mFloatingToolbarPreDrawListener = () -> { 177 mode.updateViewLocationInWindow(); 178 return true; 179 }; 180 return mode; 181 } 182 setHandledFloatingActionMode(ActionMode mode)183 private void setHandledFloatingActionMode(ActionMode mode) { 184 mFloatingActionMode = mode; 185 mFloatingActionMode.invalidate(); // Will show the floating toolbar if necessary. 186 mFloatingActionModeOriginatingView.getViewTreeObserver() 187 .addOnPreDrawListener(mFloatingToolbarPreDrawListener); 188 } 189 cleanupFloatingActionModeViews()190 private void cleanupFloatingActionModeViews() { 191 if (mFloatingToolbar != null) { 192 mFloatingToolbar.dismiss(); 193 mFloatingToolbar = null; 194 } 195 if (mFloatingActionModeOriginatingView != null) { 196 if (mFloatingToolbarPreDrawListener != null) { 197 mFloatingActionModeOriginatingView.getViewTreeObserver() 198 .removeOnPreDrawListener(mFloatingToolbarPreDrawListener); 199 mFloatingToolbarPreDrawListener = null; 200 } 201 mFloatingActionModeOriginatingView = null; 202 } 203 } 204 startActionMode( View originatingView, ActionMode.Callback callback)205 private ActionMode startActionMode( 206 View originatingView, ActionMode.Callback callback) { 207 ActionMode.Callback2 wrappedCallback = new ActionModeCallback2Wrapper(callback); 208 ActionMode mode = createFloatingActionMode(originatingView, wrappedCallback); 209 if (wrappedCallback.onCreateActionMode(mode, mode.getMenu())) { 210 setHandledFloatingActionMode(mode); 211 } else { 212 mode = null; 213 } 214 return mode; 215 } 216 217 @Override onMeasure(int widthMeasureSpec, int heightMeasureSpec)218 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 219 Trace.beginSection("NotificationShadeWindowView#onMeasure"); 220 super.onMeasure(widthMeasureSpec, heightMeasureSpec); 221 Trace.endSection(); 222 } 223 224 @Override requestLayout()225 public void requestLayout() { 226 Trace.instant(TRACE_TAG_APP, "NotificationShadeWindowView#requestLayout"); 227 super.requestLayout(); 228 } 229 230 private class ActionModeCallback2Wrapper extends ActionMode.Callback2 { 231 private final ActionMode.Callback mWrapped; 232 ActionModeCallback2Wrapper(ActionMode.Callback wrapped)233 ActionModeCallback2Wrapper(ActionMode.Callback wrapped) { 234 mWrapped = wrapped; 235 } 236 onCreateActionMode(ActionMode mode, Menu menu)237 public boolean onCreateActionMode(ActionMode mode, Menu menu) { 238 return mWrapped.onCreateActionMode(mode, menu); 239 } 240 onPrepareActionMode(ActionMode mode, Menu menu)241 public boolean onPrepareActionMode(ActionMode mode, Menu menu) { 242 requestFitSystemWindows(); 243 return mWrapped.onPrepareActionMode(mode, menu); 244 } 245 onActionItemClicked(ActionMode mode, MenuItem item)246 public boolean onActionItemClicked(ActionMode mode, MenuItem item) { 247 return mWrapped.onActionItemClicked(mode, item); 248 } 249 onDestroyActionMode(ActionMode mode)250 public void onDestroyActionMode(ActionMode mode) { 251 mWrapped.onDestroyActionMode(mode); 252 if (mode == mFloatingActionMode) { 253 cleanupFloatingActionModeViews(); 254 mFloatingActionMode = null; 255 } 256 requestFitSystemWindows(); 257 } 258 259 @Override onGetContentRect(ActionMode mode, View view, Rect outRect)260 public void onGetContentRect(ActionMode mode, View view, Rect outRect) { 261 if (mWrapped instanceof ActionMode.Callback2) { 262 ((ActionMode.Callback2) mWrapped).onGetContentRect(mode, view, outRect); 263 } else { 264 super.onGetContentRect(mode, view, outRect); 265 } 266 } 267 } 268 269 interface InteractionEventHandler { 270 /** 271 * Returns a result for {@link ViewGroup#dispatchTouchEvent(MotionEvent)} or null to defer 272 * to the super method. 273 */ handleDispatchTouchEvent(MotionEvent ev)274 Boolean handleDispatchTouchEvent(MotionEvent ev); 275 276 /** 277 * Called after all dispatching is done. 278 */ 279 dispatchTouchEventComplete()280 void dispatchTouchEventComplete(); 281 282 /** 283 * Returns if the view should intercept the touch event. 284 * 285 * The touch event may still be interecepted if 286 * {@link ViewGroup#onInterceptTouchEvent(MotionEvent)} decides to do so. 287 */ shouldInterceptTouchEvent(MotionEvent ev)288 boolean shouldInterceptTouchEvent(MotionEvent ev); 289 290 /** 291 * Called when the view decides to intercept the touch event. 292 */ didIntercept(MotionEvent ev)293 void didIntercept(MotionEvent ev); 294 handleTouchEvent(MotionEvent ev)295 boolean handleTouchEvent(MotionEvent ev); 296 didNotHandleTouchEvent(MotionEvent ev)297 void didNotHandleTouchEvent(MotionEvent ev); 298 interceptMediaKey(KeyEvent event)299 boolean interceptMediaKey(KeyEvent event); 300 dispatchKeyEvent(KeyEvent event)301 boolean dispatchKeyEvent(KeyEvent event); 302 dispatchKeyEventPreIme(KeyEvent event)303 boolean dispatchKeyEventPreIme(KeyEvent event); 304 } 305 306 /** 307 * Minimal window to satisfy FloatingToolbar. 308 */ 309 private final Window mFakeWindow = new Window(mContext) { 310 @Override 311 public void takeSurface(SurfaceHolder.Callback2 callback) { 312 } 313 314 @Override 315 public void takeInputQueue(InputQueue.Callback callback) { 316 } 317 318 @Override 319 public boolean isFloating() { 320 return false; 321 } 322 323 @Override 324 public void alwaysReadCloseOnTouchAttr() { 325 } 326 327 @Override 328 public void setContentView(@LayoutRes int layoutResID) { 329 } 330 331 @Override 332 public void setContentView(View view) { 333 } 334 335 @Override 336 public void setContentView(View view, ViewGroup.LayoutParams params) { 337 } 338 339 @Override 340 public void addContentView(View view, ViewGroup.LayoutParams params) { 341 } 342 343 @Override 344 public void clearContentView() { 345 } 346 347 @Override 348 public View getCurrentFocus() { 349 return null; 350 } 351 352 @Override 353 public LayoutInflater getLayoutInflater() { 354 return null; 355 } 356 357 @Override 358 public void setTitle(CharSequence title) { 359 } 360 361 @Override 362 public void setTitleColor(@ColorInt int textColor) { 363 } 364 365 @Override 366 public void openPanel(int featureId, KeyEvent event) { 367 } 368 369 @Override 370 public void closePanel(int featureId) { 371 } 372 373 @Override 374 public void togglePanel(int featureId, KeyEvent event) { 375 } 376 377 @Override 378 public void invalidatePanelMenu(int featureId) { 379 } 380 381 @Override 382 public boolean performPanelShortcut(int featureId, int keyCode, KeyEvent event, int flags) { 383 return false; 384 } 385 386 @Override 387 public boolean performPanelIdentifierAction(int featureId, int id, int flags) { 388 return false; 389 } 390 391 @Override 392 public void closeAllPanels() { 393 } 394 395 @Override 396 public boolean performContextMenuIdentifierAction(int id, int flags) { 397 return false; 398 } 399 400 @Override 401 public void onConfigurationChanged(Configuration newConfig) { 402 } 403 404 @Override 405 public void setBackgroundDrawable(Drawable drawable) { 406 } 407 408 @Override 409 public void setFeatureDrawableResource(int featureId, @DrawableRes int resId) { 410 } 411 412 @Override 413 public void setFeatureDrawableUri(int featureId, Uri uri) { 414 } 415 416 @Override 417 public void setFeatureDrawable(int featureId, Drawable drawable) { 418 } 419 420 @Override 421 public void setFeatureDrawableAlpha(int featureId, int alpha) { 422 } 423 424 @Override 425 public void setFeatureInt(int featureId, int value) { 426 } 427 428 @Override 429 public void takeKeyEvents(boolean get) { 430 } 431 432 @Override 433 public boolean superDispatchKeyEvent(KeyEvent event) { 434 return false; 435 } 436 437 @Override 438 public boolean superDispatchKeyShortcutEvent(KeyEvent event) { 439 return false; 440 } 441 442 @Override 443 public boolean superDispatchTouchEvent(MotionEvent event) { 444 return false; 445 } 446 447 @Override 448 public boolean superDispatchTrackballEvent(MotionEvent event) { 449 return false; 450 } 451 452 @Override 453 public boolean superDispatchGenericMotionEvent(MotionEvent event) { 454 return false; 455 } 456 457 @Override 458 public View getDecorView() { 459 return NotificationShadeWindowView.this; 460 } 461 462 @Override 463 public View peekDecorView() { 464 return null; 465 } 466 467 @Override 468 public Bundle saveHierarchyState() { 469 return null; 470 } 471 472 @Override 473 public void restoreHierarchyState(Bundle savedInstanceState) { 474 } 475 476 @Override 477 protected void onActive() { 478 } 479 480 @Override 481 public void setChildDrawable(int featureId, Drawable drawable) { 482 } 483 484 @Override 485 public void setChildInt(int featureId, int value) { 486 } 487 488 @Override 489 public boolean isShortcutKey(int keyCode, KeyEvent event) { 490 return false; 491 } 492 493 @Override 494 public void setVolumeControlStream(int streamType) { 495 } 496 497 @Override 498 public int getVolumeControlStream() { 499 return 0; 500 } 501 502 @Override 503 public int getStatusBarColor() { 504 return 0; 505 } 506 507 @Override 508 public void setStatusBarColor(@ColorInt int color) { 509 } 510 511 @Override 512 public int getNavigationBarColor() { 513 return 0; 514 } 515 516 @Override 517 public void setNavigationBarColor(@ColorInt int color) { 518 } 519 520 @Override 521 public void setDecorCaptionShade(int decorCaptionShade) { 522 } 523 524 @Override 525 public void setResizingCaptionDrawable(Drawable drawable) { 526 } 527 528 @Override 529 public void onMultiWindowModeChanged() { 530 } 531 532 @Override 533 public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode) { 534 } 535 536 @Override 537 public WindowInsetsController getInsetsController() { 538 return null; 539 } 540 }; 541 542 } 543 544