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 boolean mIsSetViewBound; 47 private WindowInsets mContentWindowInsets; 48 private WindowInsets mDecorViewWindowInsets; 49 private Rect mDecorBound; 50 private Rect mContentBound; 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 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 99 protected void onResume() { 100 super.onResume(); 101 mClickCount = 0; 102 } 103 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 */ 115 WindowInsets getContentViewWindowInsets() { 116 showVisualBoundary(mContentWindowInsets); 117 return mContentWindowInsets; 118 } 119 120 /** 121 * To get the WindowInsets that comes from onApplyWindowInsets(DecorView, insets). 122 */ 123 WindowInsets getDecorViewWindowInsets() { 124 showVisualBoundary(mDecorViewWindowInsets); 125 return mDecorViewWindowInsets; 126 } 127 128 129 /** 130 * To catch the WindowInsets that passwd to the content view. 131 * This WindowInset should have already consumed the SystemWindowInset. 132 */ 133 @Override 134 public WindowInsets onApplyWindowInsets(View v, WindowInsets insets) { 135 if (insets != null) { 136 if (v == mContent) { 137 mContentWindowInsets = new WindowInsets.Builder(insets).build(); 138 } else { 139 mDecorViewWindowInsets = new WindowInsets.Builder(insets).build(); 140 } 141 showInfoInTextView(); 142 showVisualBoundary(mDecorViewWindowInsets); 143 } 144 145 return insets; 146 } 147 148 @Override 149 public void onAttachedToWindow() { 150 super.onAttachedToWindow(); 151 152 mContent.post(() -> { 153 mContentBound = getViewBound(mContent); 154 mDecorBound = getViewBound(getWindow().getDecorView()); 155 showInfoInTextView(); 156 157 if (mInitialFinishCallBack != null) { 158 mInitialFinishCallBack.accept(true); 159 } 160 }); 161 } 162 163 /** 164 * To present the WindowInsets information to mContent. 165 * To show all of results of getSystemWindowInsets(), getMandatorySytemGestureInsets(), 166 * getSystemGestureInsets(), getTappableElementsInsets() and the exclude rects 167 */ 168 @MainThread 169 public void setSystemGestureExclusion(boolean isSetViewBoundary) { 170 mIsSetViewBound = isSetViewBoundary; 171 List<Rect> rects = new ArrayList<>(); 172 if (mIsSetViewBound) { 173 rects.add(new Rect(0 /* content view full match activity's width*/, 174 0 /* content view full match activity's height */, 175 mContent.getWidth(), 176 mContent.getHeight())); 177 } 178 179 getContentView().setSystemGestureExclusionRects(rects); 180 showInfoInTextView(); 181 } 182 183 private void showInfoInTextView() { 184 StringBuilder sb = new StringBuilder(); 185 sb.append(mIsSetViewBound ? "setSystemGestureExclusionRects" : "no set").append("\n"); 186 sb.append("exclude rect list = " + Arrays.deepToString(mContent 187 .getSystemGestureExclusionRects().toArray())).append("\n"); 188 189 if (mDecorViewWindowInsets != null) { 190 sb.append("onApplyWindowInsets mDecorViewWindowInsets = " + mDecorViewWindowInsets); 191 sb.append("\n"); 192 sb.append("getSystemWindowInsets = " + mDecorViewWindowInsets.getSystemWindowInsets()); 193 sb.append("\n"); 194 sb.append("getSystemGestureInsets = " 195 + mDecorViewWindowInsets.getSystemGestureInsets()).append("\n"); 196 sb.append("getMandatorySystemGestureInsets = " 197 + mDecorViewWindowInsets.getMandatorySystemGestureInsets()).append("\n"); 198 sb.append("getTappableElementInsets = " 199 + mDecorViewWindowInsets.getTappableElementInsets()).append("\n"); 200 sb.append("decor boundary = ").append(mDecorBound).append("\n"); 201 } 202 if (mContentWindowInsets != null) { 203 sb.append("------------------------").append("\n"); 204 sb.append("onApplyWindowInsets mContentWindowInsets = " + mContentWindowInsets); 205 sb.append("\n"); 206 sb.append("getSystemWindowInsets = " + mContentWindowInsets.getSystemWindowInsets()); 207 sb.append("\n"); 208 sb.append("getSystemGestureInsets = " 209 + mContentWindowInsets.getSystemGestureInsets()).append("\n"); 210 sb.append("getMandatorySystemGestureInsets = " 211 + mContentWindowInsets.getMandatorySystemGestureInsets()).append("\n"); 212 sb.append("getTappableElementInsets = " 213 + mContentWindowInsets.getTappableElementInsets()).append("\n"); 214 sb.append("content boundary = ").append(mContentBound).append("\n"); 215 } 216 217 Display display = getDisplay(); 218 if (display != null) { 219 sb.append("------------------------").append("\n"); 220 DisplayCutout displayCutout = display.getCutout(); 221 if (displayCutout != null) { 222 sb.append("displayCutout = ").append(displayCutout.toString()).append("\n"); 223 } else { 224 sb.append("Display cut out = null\n"); 225 } 226 227 sb.append("real size = (").append(mDisplayMetrics.widthPixels).append(",") 228 .append(mDisplayMetrics.heightPixels).append(")\n"); 229 } 230 231 232 mContent.setText(sb.toString()); 233 } 234 235 @MainThread 236 public View getContentView() { 237 return mContent; 238 } 239 240 Rect getViewBound(View view) { 241 int [] screenlocation = new int[2]; 242 view.getLocationOnScreen(screenlocation); 243 return new Rect(screenlocation[0], screenlocation[1], 244 screenlocation[0] + view.getWidth(), 245 screenlocation[1] + view.getHeight()); 246 } 247 248 /** 249 * To count the draggable boundary that has consume the related insets. 250 **/ 251 @MainThread 252 public Rect getOperationArea(Insets insets, WindowInsets windowInsets) { 253 int left = insets.left; 254 int top = insets.top; 255 int right = insets.right; 256 int bottom = insets.bottom; 257 258 final DisplayCutout cutout = windowInsets.getDisplayCutout(); 259 if (cutout != null) { 260 int slack = (int) (DISPLAY_CUTOUT_SLACK_DP * mDisplayMetrics.density); 261 if (cutout.getSafeInsetLeft() > 0) { 262 left = Math.max(left, cutout.getSafeInsetLeft() + slack); 263 } 264 if (cutout.getSafeInsetTop() > 0) { 265 top = Math.max(top, cutout.getSafeInsetTop() + slack); 266 } 267 if (cutout.getSafeInsetRight() > 0) { 268 right = Math.max(right, cutout.getSafeInsetRight() + slack); 269 } 270 if (cutout.getSafeInsetBottom() > 0) { 271 bottom = Math.max(bottom, cutout.getSafeInsetBottom() + slack); 272 } 273 } 274 275 Rect windowBoundary = getViewBound(getContentView()); 276 Rect rect = new Rect(windowBoundary); 277 rect.left += left; 278 rect.top += top; 279 rect.right -= right; 280 rect.bottom -= bottom; 281 282 return rect; 283 } 284 285 @MainThread 286 public List<Point> getActionCancelPoints() { 287 return ((WindowInsetsPresenterDrawable) mContent.getBackground()).getActionCancelPoints(); 288 } 289 290 @MainThread 291 public List<Point> getActionDownPoints() { 292 return ((WindowInsetsPresenterDrawable) mContent.getBackground()).getActionDownPoints(); 293 } 294 295 @MainThread 296 public List<Point> getActionUpPoints() { 297 return ((WindowInsetsPresenterDrawable) mContent.getBackground()).getActionUpPoints(); 298 } 299 300 /** 301 * To set the callback to notify the onClickListener is triggered. 302 * @param clickConsumer trigger the callback after clicking view. 303 */ 304 public void setOnClickConsumer( 305 Consumer<View> clickConsumer) { 306 mClickConsumer = clickConsumer; 307 } 308 309 /** 310 * Because the view needs the focus to catch the ACTION_DOWN otherwise do nothing. 311 * Only for the focus to receive the ACTION_DOWN, ACTION_MOVE, ACTION_UP and ACTION_CANCEL. 312 **/ 313 @Override 314 public void onClick(View v) { 315 mClickCount++; 316 if (mClickConsumer != null) { 317 mClickConsumer.accept(v); 318 } 319 } 320 321 public int getClickCount() { 322 return mClickCount; 323 } 324 325 /** 326 * To set the callback to notify the test with the initial finish. 327 * @param initialFinishCallBack trigger the callback after initial finish. 328 */ 329 public void setInitialFinishCallBack( 330 Consumer<Boolean> initialFinishCallBack) { 331 mInitialFinishCallBack = initialFinishCallBack; 332 } 333 } 334