Home | History | Annotate | Download | only in cts
      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