1 /* 2 * Copyright (C) 2016 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.view.cts; 18 19 import static org.junit.Assert.fail; 20 21 import android.app.Activity; 22 import android.content.Context; 23 import android.content.pm.ActivityInfo; 24 import android.graphics.Canvas; 25 import android.graphics.Color; 26 import android.graphics.Paint; 27 import android.graphics.Point; 28 import android.graphics.Rect; 29 import android.os.Bundle; 30 import android.util.Log; 31 import android.view.View; 32 import android.view.ViewTreeObserver.OnDrawListener; 33 import android.view.Window; 34 import android.view.WindowInsets; 35 import android.view.cts.util.DisplayUtils; 36 import android.widget.FrameLayout; 37 38 import java.util.concurrent.CountDownLatch; 39 import java.util.concurrent.TimeUnit; 40 41 public class PixelCopyViewProducerActivity extends Activity implements OnDrawListener, 42 View.OnApplyWindowInsetsListener{ 43 private static final int[] ORIENTATIONS = { 44 ActivityInfo.SCREEN_ORIENTATION_PORTRAIT, 45 ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE, 46 ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT, 47 ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE, 48 }; 49 // TODO: Lower this (or remove it entirely) by leveraging things like 50 // ViewTreeObserver#registerFrameCommitCallback (and possibly display orientation listeners?) 51 private static final int DRAW_FRAME_COUNT_BEFORE_CAPTURE = 10; 52 private int mCurrentOrientation = 0; 53 private View mContent; 54 private Rect mContentBounds = new Rect(); 55 private Rect mOutsets = new Rect(); 56 private CountDownLatch mFence = new CountDownLatch(DRAW_FRAME_COUNT_BEFORE_CAPTURE); 57 private boolean mSupportsRotation; 58 59 @Override onCreate(Bundle savedInstanceState)60 protected void onCreate(Bundle savedInstanceState) { 61 super.onCreate(savedInstanceState); 62 63 // Check if the device supports both of portrait and landscape orientation screens. 64 mSupportsRotation = DisplayUtils.supportOrientationRequest(this); 65 if (mSupportsRotation) { 66 Log.d("PixelCopyTest", "Setting orientation index = " + mCurrentOrientation); 67 setRequestedOrientation(ORIENTATIONS[mCurrentOrientation]); 68 } 69 70 mContent = new ColoredGrid(this); 71 setContentView(mContent); 72 73 Window window = getWindow(); 74 window.setDecorFitsSystemWindows(false); 75 window.setStatusBarColor(Color.TRANSPARENT); 76 window.setStatusBarContrastEnforced(false); 77 window.setNavigationBarColor(Color.TRANSPARENT); 78 window.setNavigationBarContrastEnforced(false); 79 80 mContent.getViewTreeObserver().addOnDrawListener(this); 81 mContent.setOnApplyWindowInsetsListener(this); 82 } 83 getContentView()84 public View getContentView() { 85 return mContent; 86 } 87 88 @Override onDraw()89 public void onDraw() { 90 final int requestedOrientation = ORIENTATIONS[mCurrentOrientation]; 91 boolean isRequestingPortrait = 92 requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT 93 || requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT; 94 if (mSupportsRotation && (isRequestingPortrait != DisplayUtils.isDevicePortrait(this))) { 95 return; 96 } 97 mContent.post(() -> { 98 Point offset = new Point(); 99 // We pass mContentBounds here just as a throwaway rect, we don't care about 100 // the visible rect just the global offset. 101 mContent.getGlobalVisibleRect(mContentBounds, offset); 102 mContentBounds.set(offset.x - mOutsets.left, offset.y - mOutsets.top, 103 offset.x - mOutsets.left + mContent.getWidth(), 104 offset.y - mOutsets.top + mContent.getHeight()); 105 mFence.countDown(); 106 if (mFence.getCount() > 0) { 107 mContent.invalidate(); 108 } 109 }); 110 } 111 112 @Override onApplyWindowInsets(View v, WindowInsets in)113 public WindowInsets onApplyWindowInsets(View v, WindowInsets in) { 114 if (in.isRound()) { 115 FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) mContent.getLayoutParams(); 116 params.setMargins(in.getSystemWindowInsetLeft(), in.getSystemWindowInsetTop(), 117 in.getSystemWindowInsetRight(), in.getSystemWindowInsetBottom()); 118 mOutsets = new Rect(in.getSystemWindowInsetLeft(), in.getSystemWindowInsetTop(), 119 in.getSystemWindowInsetRight(), in.getSystemWindowInsetBottom()); 120 mContent.setLayoutParams(params); 121 } 122 return in; 123 } 124 waitForFirstDrawCompleted(int timeout, TimeUnit unit)125 public void waitForFirstDrawCompleted(int timeout, TimeUnit unit) { 126 try { 127 if (!mFence.await(timeout, unit)) { 128 fail("Timeout"); 129 } 130 } catch (InterruptedException ex) { 131 fail(ex.getMessage()); 132 } 133 } 134 rotate()135 public boolean rotate() { 136 if (!mSupportsRotation) { 137 // Do not rotate the screen if it is not supported. 138 return false; 139 } 140 mFence = new CountDownLatch(DRAW_FRAME_COUNT_BEFORE_CAPTURE); 141 runOnUiThread(() -> { 142 mCurrentOrientation = (mCurrentOrientation + 1) % ORIENTATIONS.length; 143 Log.d("PixelCopyTest", "Setting orientation index = " + mCurrentOrientation); 144 setRequestedOrientation(ORIENTATIONS[mCurrentOrientation]); 145 }); 146 waitForFirstDrawCompleted(10, TimeUnit.SECONDS); 147 return mCurrentOrientation != 0; 148 } 149 150 // Convert a rect in normalized 0-100 dimensions to the bounds of the actual View. normalizedToSurface(Rect inOut)151 public void normalizedToSurface(Rect inOut) { 152 float sx = mContentBounds.width() / 100.0f; 153 float sy = mContentBounds.height() / 100.0f; 154 inOut.left = (int) (inOut.left * sx); 155 inOut.top = (int) (inOut.top * sy); 156 inOut.right = (int) (inOut.right * sx + 0.5f); 157 inOut.bottom = (int) (inOut.bottom * sy + 0.5f); 158 inOut.offset(mContentBounds.left, mContentBounds.top); 159 } 160 161 private static final class ColoredGrid extends View { 162 private Paint mPaint = new Paint(); 163 private Rect mRect = new Rect(); 164 ColoredGrid(Context context)165 ColoredGrid(Context context) { 166 super(context); 167 setWillNotDraw(false); 168 } 169 170 @Override onDraw(Canvas canvas)171 protected void onDraw(Canvas canvas) { 172 int cx = getWidth() / 2; 173 int cy = getHeight() / 2; 174 final int BORDER_WIDTH = 2; 175 176 canvas.drawColor(Color.YELLOW); 177 178 mRect.set(BORDER_WIDTH, BORDER_WIDTH, cx, cy); 179 mPaint.setColor(Color.RED); 180 canvas.drawRect(mRect, mPaint); 181 182 mRect.set(cx, BORDER_WIDTH, getWidth() - BORDER_WIDTH, cy); 183 mPaint.setColor(Color.GREEN); 184 canvas.drawRect(mRect, mPaint); 185 186 mRect.set(BORDER_WIDTH, cy, cx, getHeight() - BORDER_WIDTH); 187 mPaint.setColor(Color.BLUE); 188 canvas.drawRect(mRect, mPaint); 189 190 mRect.set(cx, cy, getWidth() - BORDER_WIDTH, getHeight() - BORDER_WIDTH); 191 mPaint.setColor(Color.BLACK); 192 canvas.drawRect(mRect, mPaint); 193 } 194 } 195 } 196