1 /* 2 * Copyright (C) 2019 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 android.systemui.cts; 18 19 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES; 20 21 import android.annotation.MainThread; 22 import android.graphics.Insets; 23 import android.graphics.Point; 24 import android.graphics.Rect; 25 import android.os.Bundle; 26 import android.util.DisplayMetrics; 27 import android.view.Display; 28 import android.view.DisplayCutout; 29 import android.view.Gravity; 30 import android.view.View; 31 import android.view.ViewGroup; 32 import android.view.Window; 33 import android.view.WindowInsets; 34 import android.widget.TextView; 35 36 import java.util.ArrayList; 37 import java.util.Arrays; 38 import java.util.List; 39 import java.util.function.Consumer; 40 41 public class WindowInsetsActivity extends LightBarBaseActivity implements View.OnClickListener, 42 View.OnApplyWindowInsetsListener { 43 private static final int DISPLAY_CUTOUT_SLACK_DP = 20; 44 45 private TextView mContent; 46 private WindowInsets mContentWindowInsets; 47 private WindowInsets mDecorViewWindowInsets; 48 private Rect mDecorBound; 49 private Rect mContentBoundOnScreen; 50 private Rect mContentBoundInWindow; 51 52 private Consumer<Boolean> mInitialFinishCallBack; 53 private int mClickCount; 54 private Consumer<View> mClickConsumer; 55 private final DisplayMetrics mDisplayMetrics = new DisplayMetrics(); 56 57 58 /** 59 * To setup the Activity to get better diagnoise. 60 * To setup WindowInsetPresenterDrawable that will show the boundary of the windowInset and 61 * the touch track. 62 */ 63 @Override onCreate(Bundle bundle)64 protected void onCreate(Bundle bundle) { 65 getWindow().requestFeature(Window.FEATURE_NO_TITLE); 66 67 super.onCreate(bundle); 68 69 mContent = new TextView(this); 70 mContent.setTextSize(10); 71 mContent.setGravity(Gravity.CENTER); 72 mContent.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 73 ViewGroup.LayoutParams.MATCH_PARENT)); 74 WindowInsetsPresenterDrawable presenterDrawable = 75 new WindowInsetsPresenterDrawable(getWindow().getDecorView().getRootWindowInsets()); 76 mContent.setOnTouchListener(presenterDrawable); 77 mContent.setBackground(presenterDrawable); 78 mContent.setOnApplyWindowInsetsListener(this::onApplyWindowInsets); 79 mContent.getRootView().setOnApplyWindowInsetsListener(this::onApplyWindowInsets); 80 getWindow().getDecorView().setOnApplyWindowInsetsListener(this::onApplyWindowInsets); 81 getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN 82 | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION 83 | View.SYSTEM_UI_FLAG_LAYOUT_STABLE); 84 getWindow().getAttributes().layoutInDisplayCutoutMode 85 = LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES; 86 getWindow().setStatusBarColor(0x22ff0000); 87 getWindow().setNavigationBarColor(0x22ff0000); 88 mContent.setOnClickListener(this); 89 mContent.requestApplyInsets(); 90 setContentView(mContent); 91 92 mContentWindowInsets = getWindow().getDecorView().getRootWindowInsets(); 93 mInitialFinishCallBack = null; 94 95 getDisplay().getRealMetrics(mDisplayMetrics); 96 } 97 98 @Override onResume()99 protected void onResume() { 100 super.onResume(); 101 mClickCount = 0; 102 } 103 showVisualBoundary(WindowInsets insets)104 private void showVisualBoundary(WindowInsets insets) { 105 if (insets != null) { 106 WindowInsetsPresenterDrawable presenterDrawable = 107 (WindowInsetsPresenterDrawable) mContent.getBackground(); 108 presenterDrawable.setWindowInsets(insets); 109 } 110 } 111 112 /** 113 * To get the WindowInsets that comes from onApplyWindowInsets(mContent, insets). 114 */ getContentViewWindowInsets()115 WindowInsets getContentViewWindowInsets() { 116 showVisualBoundary(mContentWindowInsets); 117 return mContentWindowInsets; 118 } 119 120 /** 121 * To get the WindowInsets that comes from onApplyWindowInsets(DecorView, insets). 122 */ getDecorViewWindowInsets()123 WindowInsets getDecorViewWindowInsets() { 124 showVisualBoundary(mDecorViewWindowInsets); 125 return mDecorViewWindowInsets; 126 } 127 getContentBoundOnScreen()128 Rect getContentBoundOnScreen() { 129 return mContentBoundOnScreen; 130 } 131 getContentBoundInWindow()132 Rect getContentBoundInWindow() { 133 return mContentBoundInWindow; 134 } 135 136 /** 137 * To catch the WindowInsets that passwd to the content view. 138 * This WindowInset should have already consumed the SystemWindowInset. 139 */ 140 @Override onApplyWindowInsets(View v, WindowInsets insets)141 public WindowInsets onApplyWindowInsets(View v, WindowInsets insets) { 142 if (insets != null) { 143 if (v == mContent) { 144 mContentWindowInsets = new WindowInsets.Builder(insets).build(); 145 } else { 146 mDecorViewWindowInsets = new WindowInsets.Builder(insets).build(); 147 } 148 showInfoInTextView(); 149 showVisualBoundary(mDecorViewWindowInsets); 150 } 151 152 return insets; 153 } 154 155 @Override onAttachedToWindow()156 public void onAttachedToWindow() { 157 super.onAttachedToWindow(); 158 159 mContent.post(() -> { 160 mContentBoundOnScreen = getViewBoundOnScreen(mContent); 161 mContentBoundInWindow = getViewBoundInWindow(mContent); 162 mDecorBound = getViewBoundOnScreen(getWindow().getDecorView()); 163 showInfoInTextView(); 164 165 if (mInitialFinishCallBack != null) { 166 mInitialFinishCallBack.accept(true); 167 } 168 }); 169 } 170 171 /** 172 * To present the WindowInsets information to mContent. 173 * To show all of results of getSystemWindowInsets(), getMandatorySytemGestureInsets(), 174 * getSystemGestureInsets(), getTappableElementsInsets() and the exclude rects 175 * 176 * @param rect the rectangle want to add or pass into to setSystemGestureExclusionRects 177 */ 178 @MainThread setSystemGestureExclusion(Rect rect)179 public void setSystemGestureExclusion(Rect rect) { 180 List<Rect> rects = new ArrayList<>(); 181 if (rect != null) { 182 rects.add(rect); 183 } 184 getContentView().setSystemGestureExclusionRects(rects); 185 showInfoInTextView(); 186 } 187 showInfoInTextView()188 private void showInfoInTextView() { 189 StringBuilder sb = new StringBuilder(); 190 sb.append("exclude rect list = " + Arrays.deepToString(mContent 191 .getSystemGestureExclusionRects().toArray())).append("\n"); 192 193 if (mDecorViewWindowInsets != null) { 194 sb.append("onApplyWindowInsets mDecorViewWindowInsets = " + mDecorViewWindowInsets); 195 sb.append("\n"); 196 sb.append("getSystemWindowInsets = " + mDecorViewWindowInsets.getSystemWindowInsets()); 197 sb.append("\n"); 198 sb.append("getSystemGestureInsets = " 199 + mDecorViewWindowInsets.getSystemGestureInsets()).append("\n"); 200 sb.append("getMandatorySystemGestureInsets = " 201 + mDecorViewWindowInsets.getMandatorySystemGestureInsets()).append("\n"); 202 sb.append("getTappableElementInsets = " 203 + mDecorViewWindowInsets.getTappableElementInsets()).append("\n"); 204 sb.append("decor boundary = ").append(mDecorBound).append("\n"); 205 } 206 if (mContentWindowInsets != null) { 207 sb.append("------------------------").append("\n"); 208 sb.append("onApplyWindowInsets mContentWindowInsets = " + mContentWindowInsets); 209 sb.append("\n"); 210 sb.append("getSystemWindowInsets = " + mContentWindowInsets.getSystemWindowInsets()); 211 sb.append("\n"); 212 sb.append("getSystemGestureInsets = " 213 + mContentWindowInsets.getSystemGestureInsets()).append("\n"); 214 sb.append("getMandatorySystemGestureInsets = " 215 + mContentWindowInsets.getMandatorySystemGestureInsets()).append("\n"); 216 sb.append("getTappableElementInsets = " 217 + mContentWindowInsets.getTappableElementInsets()).append("\n"); 218 sb.append("content boundary on screen = ").append(mContentBoundOnScreen).append("\n"); 219 sb.append("content boundary in window = ").append(mContentBoundInWindow).append("\n"); 220 } 221 222 Display display = getDisplay(); 223 if (display != null) { 224 sb.append("------------------------").append("\n"); 225 DisplayCutout displayCutout = display.getCutout(); 226 if (displayCutout != null) { 227 sb.append("displayCutout = ").append(displayCutout.toString()).append("\n"); 228 } else { 229 sb.append("Display cut out = null\n"); 230 } 231 232 sb.append("real size = (").append(mDisplayMetrics.widthPixels).append(",") 233 .append(mDisplayMetrics.heightPixels).append(")\n"); 234 } 235 236 237 mContent.setText(sb.toString()); 238 } 239 240 @MainThread getContentView()241 public View getContentView() { 242 return mContent; 243 } 244 getViewBoundOnScreen(View view)245 Rect getViewBoundOnScreen(View view) { 246 int [] location = new int[2]; 247 view.getLocationOnScreen(location); 248 return new Rect(location[0], location[1], 249 location[0] + view.getWidth(), 250 location[1] + view.getHeight()); 251 } 252 getViewBoundInWindow(View view)253 Rect getViewBoundInWindow(View view) { 254 int [] location = new int[2]; 255 view.getLocationInWindow(location); 256 return new Rect(location[0], location[1], 257 location[0] + view.getWidth(), 258 location[1] + view.getHeight()); 259 } 260 261 @MainThread getActionBounds(Insets insets, WindowInsets windowInsets)262 public Rect getActionBounds(Insets insets, WindowInsets windowInsets) { 263 return calculateBoundsWithInsets(insets, windowInsets, mContentBoundOnScreen); 264 } 265 266 @MainThread getSystemGestureExclusionBounds(Insets insets, WindowInsets windowInsets)267 public Rect getSystemGestureExclusionBounds(Insets insets, WindowInsets windowInsets) { 268 return calculateBoundsWithInsets(insets, windowInsets, mContentBoundInWindow); 269 } 270 271 /** 272 * Calculate the bounds for performing actions(click, tap or swipe) or for setting the exclusion 273 * rect and the coordinate space of the return Rect could be the display or window coordinate 274 * space which is determined by the passed in refRect. 275 * 276 * @param insets the insets to be tested. 277 * @param windowInsets the WindowInsets that pass to the activity. 278 * @param refRect the rect which determines whether the return rect is the display or the window 279 * coordinate space. 280 * @return the bounds for performing actions or for setting the exclusion rect. 281 **/ calculateBoundsWithInsets(Insets insets, WindowInsets windowInsets, Rect refRect)282 private Rect calculateBoundsWithInsets(Insets insets, WindowInsets windowInsets, Rect refRect) { 283 int left = insets.left; 284 int top = insets.top; 285 int right = insets.right; 286 int bottom = insets.bottom; 287 288 final DisplayCutout cutout = windowInsets.getDisplayCutout(); 289 if (cutout != null) { 290 int slack = (int) (DISPLAY_CUTOUT_SLACK_DP * mDisplayMetrics.density); 291 if (cutout.getSafeInsetLeft() > 0) { 292 left = Math.max(left, cutout.getSafeInsetLeft() + slack); 293 } 294 if (cutout.getSafeInsetTop() > 0) { 295 top = Math.max(top, cutout.getSafeInsetTop() + slack); 296 } 297 if (cutout.getSafeInsetRight() > 0) { 298 right = Math.max(right, cutout.getSafeInsetRight() + slack); 299 } 300 if (cutout.getSafeInsetBottom() > 0) { 301 bottom = Math.max(bottom, cutout.getSafeInsetBottom() + slack); 302 } 303 } 304 305 Rect rect = new Rect(refRect); 306 rect.left += left; 307 rect.top += top; 308 rect.right -= right; 309 rect.bottom -= bottom; 310 311 return rect; 312 } 313 314 @MainThread getActionCancelPoints()315 public List<Point> getActionCancelPoints() { 316 return ((WindowInsetsPresenterDrawable) mContent.getBackground()).getActionCancelPoints(); 317 } 318 319 @MainThread getActionDownPoints()320 public List<Point> getActionDownPoints() { 321 return ((WindowInsetsPresenterDrawable) mContent.getBackground()).getActionDownPoints(); 322 } 323 324 @MainThread getActionUpPoints()325 public List<Point> getActionUpPoints() { 326 return ((WindowInsetsPresenterDrawable) mContent.getBackground()).getActionUpPoints(); 327 } 328 329 /** 330 * To set the callback to notify the onClickListener is triggered. 331 * @param clickConsumer trigger the callback after clicking view. 332 */ setOnClickConsumer( Consumer<View> clickConsumer)333 public void setOnClickConsumer( 334 Consumer<View> clickConsumer) { 335 mClickConsumer = clickConsumer; 336 } 337 338 /** 339 * Because the view needs the focus to catch the ACTION_DOWN otherwise do nothing. 340 * Only for the focus to receive the ACTION_DOWN, ACTION_MOVE, ACTION_UP and ACTION_CANCEL. 341 **/ 342 @Override onClick(View v)343 public void onClick(View v) { 344 mClickCount++; 345 if (mClickConsumer != null) { 346 mClickConsumer.accept(v); 347 } 348 } 349 getClickCount()350 public int getClickCount() { 351 return mClickCount; 352 } 353 354 /** 355 * To set the callback to notify the test with the initial finish. 356 * @param initialFinishCallBack trigger the callback after initial finish. 357 */ setInitialFinishCallBack( Consumer<Boolean> initialFinishCallBack)358 public void setInitialFinishCallBack( 359 Consumer<Boolean> initialFinishCallBack) { 360 mInitialFinishCallBack = initialFinishCallBack; 361 } 362 } 363