blob: e255ce234c6576b89da01df9912bfbf6b6e26012 [file] [log] [blame]
chaviwa51724f2019-09-19 09:50:11 -07001/*
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
17package com.google.android.test.mirrorsurface;
18
Andrii Kuliane57f2dc2020-01-26 20:59:07 -080019import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
chaviwa51724f2019-09-19 09:50:11 -070020
21import android.app.Activity;
22import android.graphics.Canvas;
23import android.graphics.Color;
24import android.graphics.PixelFormat;
25import android.graphics.Point;
26import android.graphics.Rect;
27import android.os.Bundle;
28import android.os.Handler;
29import android.os.RemoteException;
Andrii Kuliane57f2dc2020-01-26 20:59:07 -080030import android.util.Size;
chaviwa51724f2019-09-19 09:50:11 -070031import android.view.Gravity;
32import android.view.IWindowManager;
33import android.view.MotionEvent;
34import android.view.Surface;
35import android.view.SurfaceControl;
36import android.view.View;
37import android.view.ViewGroup;
38import android.view.WindowManager;
39import android.view.WindowManagerGlobal;
40import android.widget.EditText;
41import android.widget.LinearLayout;
42import android.widget.TextView;
43
44public class MirrorSurfaceActivity extends Activity implements View.OnClickListener,
45 View.OnLongClickListener, View.OnTouchListener {
46 private static final int BORDER_SIZE = 10;
47 private static final int DEFAULT_SCALE = 2;
48 private static final int DEFAULT_BORDER_COLOR = Color.argb(255, 255, 153, 0);
49 private static final int MOVE_FRAME_AMOUNT = 20;
50
51 private IWindowManager mIWm;
Andrii Kuliane57f2dc2020-01-26 20:59:07 -080052 // An instance of WindowManager that is adjusted for adding windows with type
53 // TYPE_APPLICATION_OVERLAY.
chaviwa51724f2019-09-19 09:50:11 -070054 private WindowManager mWm;
55
56 private SurfaceControl mSurfaceControl = new SurfaceControl();
57 private SurfaceControl mBorderSc;
58
59 private SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction();
60 private View mOverlayView;
61 private View mArrowOverlay;
62
Andrii Kuliane57f2dc2020-01-26 20:59:07 -080063 private Rect mWindowBounds = new Rect();
chaviwa51724f2019-09-19 09:50:11 -070064
65 private EditText mScaleText;
66 private EditText mDisplayFrameText;
67 private TextView mSourcePositionText;
68
69 private Rect mTmpRect = new Rect();
70 private final Surface mTmpSurface = new Surface();
71
72 private boolean mHasMirror;
73
74 private Rect mCurrFrame = new Rect();
75 private float mCurrScale = DEFAULT_SCALE;
76
77 private final Handler mHandler = new Handler();
78
79 private MoveMirrorRunnable mMoveMirrorRunnable = new MoveMirrorRunnable();
80 private boolean mIsPressedDown = false;
81
82 private int mDisplayId;
83
84 @Override
85 protected void onCreate(Bundle savedInstanceState) {
86 super.onCreate(savedInstanceState);
87
88 setContentView(R.layout.activity_mirror_surface);
Andrii Kuliane57f2dc2020-01-26 20:59:07 -080089 mWm = createWindowContext(TYPE_APPLICATION_OVERLAY, null /* options */)
90 .getSystemService(WindowManager.class);
chaviwa51724f2019-09-19 09:50:11 -070091 mIWm = WindowManagerGlobal.getWindowManagerService();
92
Andrii Kuliane57f2dc2020-01-26 20:59:07 -080093 Size windowSize = mWm.getCurrentWindowMetrics().getSize();
94 mWindowBounds.set(0, 0, windowSize.getWidth(), windowSize.getHeight());
chaviwa51724f2019-09-19 09:50:11 -070095
96 mScaleText = findViewById(R.id.scale);
97 mDisplayFrameText = findViewById(R.id.displayFrame);
98 mSourcePositionText = findViewById(R.id.sourcePosition);
99
Andrii Kuliane57f2dc2020-01-26 20:59:07 -0800100 mCurrFrame.set(0, 0, mWindowBounds.width() / 2, mWindowBounds.height() / 2);
chaviwa51724f2019-09-19 09:50:11 -0700101 mCurrScale = DEFAULT_SCALE;
102
Andrii Kuliane57f2dc2020-01-26 20:59:07 -0800103 mDisplayId = getDisplay().getDisplayId();
chaviwa51724f2019-09-19 09:50:11 -0700104 updateEditTexts();
105
106 findViewById(R.id.mirror_button).setOnClickListener(view -> {
107 if (mArrowOverlay == null) {
108 createArrowOverlay();
109 }
110 createOrUpdateMirror();
111 });
112
113 findViewById(R.id.remove_mirror_button).setOnClickListener(v -> {
114 removeMirror();
115 removeArrowOverlay();
116 });
117
118 createMirrorOverlay();
119 }
120
121 private void updateEditTexts() {
122 mDisplayFrameText.setText(
123 String.format("%s, %s, %s, %s", mCurrFrame.left, mCurrFrame.top, mCurrFrame.right,
124 mCurrFrame.bottom));
125 mScaleText.setText(String.valueOf(mCurrScale));
126 }
127
128 @Override
129 protected void onDestroy() {
130 super.onDestroy();
131 if (mOverlayView != null) {
132 removeMirror();
133 mWm.removeView(mOverlayView);
134 mOverlayView = null;
135 }
136 removeArrowOverlay();
137 }
138
139 private void createArrowOverlay() {
140 mArrowOverlay = getLayoutInflater().inflate(R.layout.move_view, null);
141 WindowManager.LayoutParams arrowParams = new WindowManager.LayoutParams(
142 ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT,
143 WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY,
144 WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
145 | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
146 | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
147 PixelFormat.RGBA_8888);
148 arrowParams.gravity = Gravity.RIGHT | Gravity.BOTTOM;
149 mWm.addView(mArrowOverlay, arrowParams);
150
151 View leftArrow = mArrowOverlay.findViewById(R.id.left_arrow);
152 View topArrow = mArrowOverlay.findViewById(R.id.up_arrow);
153 View rightArrow = mArrowOverlay.findViewById(R.id.right_arrow);
154 View bottomArrow = mArrowOverlay.findViewById(R.id.down_arrow);
155
156 leftArrow.setOnClickListener(this);
157 topArrow.setOnClickListener(this);
158 rightArrow.setOnClickListener(this);
159 bottomArrow.setOnClickListener(this);
160
161 leftArrow.setOnLongClickListener(this);
162 topArrow.setOnLongClickListener(this);
163 rightArrow.setOnLongClickListener(this);
164 bottomArrow.setOnLongClickListener(this);
165
166 leftArrow.setOnTouchListener(this);
167 topArrow.setOnTouchListener(this);
168 rightArrow.setOnTouchListener(this);
169 bottomArrow.setOnTouchListener(this);
170
171 mArrowOverlay.findViewById(R.id.zoom_in_button).setOnClickListener(v -> {
172 if (mCurrScale <= 1) {
173 mCurrScale *= 2;
174 } else {
175 mCurrScale += 0.5;
176 }
177
178 updateMirror(mCurrFrame, mCurrScale);
179 });
180 mArrowOverlay.findViewById(R.id.zoom_out_button).setOnClickListener(v -> {
181 if (mCurrScale <= 1) {
182 mCurrScale /= 2;
183 } else {
184 mCurrScale -= 0.5;
185 }
186
187 updateMirror(mCurrFrame, mCurrScale);
188 });
189 }
190
191 private void removeArrowOverlay() {
192 if (mArrowOverlay != null) {
193 mWm.removeView(mArrowOverlay);
194 mArrowOverlay = null;
195 }
196 }
197
198 private void createMirrorOverlay() {
199 mOverlayView = new LinearLayout(this);
Andrii Kuliane57f2dc2020-01-26 20:59:07 -0800200 WindowManager.LayoutParams params = new WindowManager.LayoutParams(mWindowBounds.width(),
201 mWindowBounds.height(),
chaviwa51724f2019-09-19 09:50:11 -0700202 WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY,
203 WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
204 | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
205 PixelFormat.RGBA_8888);
206 params.gravity = Gravity.LEFT | Gravity.TOP;
207 params.setTitle("Mirror Overlay");
208 mOverlayView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE
209 | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
210 | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
211 | View.SYSTEM_UI_FLAG_FULLSCREEN
212 | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
213 | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION);
214
215 mWm.addView(mOverlayView, params);
216
217 }
218
219 private void removeMirror() {
220 if (mSurfaceControl.isValid()) {
221 mTransaction.remove(mSurfaceControl).apply();
222 }
223 mHasMirror = false;
224 }
225
226 private void createOrUpdateMirror() {
227 if (mHasMirror) {
228 updateMirror(getDisplayFrame(), getScale());
229 } else {
230 createMirror(getDisplayFrame(), getScale());
231 }
232
233 }
234
235 private Rect getDisplayFrame() {
236 mTmpRect.setEmpty();
237 String[] frameVals = mDisplayFrameText.getText().toString().split("\\s*,\\s*");
238 if (frameVals.length != 4) {
239 return mTmpRect;
240 }
241
242 try {
243 mTmpRect.set(Integer.parseInt(frameVals[0]), Integer.parseInt(frameVals[1]),
244 Integer.parseInt(frameVals[2]), Integer.parseInt(frameVals[3]));
245 } catch (Exception e) {
246 mTmpRect.setEmpty();
247 }
248
249 return mTmpRect;
250 }
251
252 private float getScale() {
253 try {
254 return Float.parseFloat(mScaleText.getText().toString());
255 } catch (Exception e) {
256 return -1;
257 }
258 }
259
260 private void createMirror(Rect displayFrame, float scale) {
261 boolean success = false;
262 try {
263 success = mIWm.mirrorDisplay(mDisplayId, mSurfaceControl);
264 } catch (RemoteException e) {
265 }
266
267 if (!success) {
268 return;
269 }
270
271 if (!mSurfaceControl.isValid()) {
272 return;
273 }
274
275 mHasMirror = true;
276
277 mBorderSc = new SurfaceControl.Builder()
278 .setName("Mirror Border")
279 .setBufferSize(1, 1)
280 .setFormat(PixelFormat.TRANSLUCENT)
281 .build();
282
283 updateMirror(displayFrame, scale);
284
285 mTransaction
286 .show(mSurfaceControl)
287 .reparent(mSurfaceControl, mOverlayView.getViewRootImpl().getSurfaceControl())
288 .setLayer(mBorderSc, 1)
289 .show(mBorderSc)
290 .reparent(mBorderSc, mSurfaceControl)
291 .apply();
292 }
293
294 private void updateMirror(Rect displayFrame, float scale) {
295 if (displayFrame.isEmpty()) {
Andrii Kuliane57f2dc2020-01-26 20:59:07 -0800296 Rect bounds = mWindowBounds;
chaviwa51724f2019-09-19 09:50:11 -0700297 int defaultCropW = Math.round(bounds.width() / 2);
298 int defaultCropH = Math.round(bounds.height() / 2);
299 displayFrame.set(0, 0, defaultCropW, defaultCropH);
300 }
301
302 if (scale <= 0) {
303 scale = DEFAULT_SCALE;
304 }
305
306 mCurrFrame.set(displayFrame);
307 mCurrScale = scale;
308
309 int width = (int) Math.ceil(displayFrame.width() / scale);
310 int height = (int) Math.ceil(displayFrame.height() / scale);
311
312 Rect sourceBounds = getSourceBounds(displayFrame, scale);
313
314 mTransaction.setGeometry(mSurfaceControl, sourceBounds, displayFrame, Surface.ROTATION_0)
315 .setPosition(mBorderSc, sourceBounds.left, sourceBounds.top)
316 .setBufferSize(mBorderSc, width, height)
317 .apply();
318
319 drawBorder(mBorderSc, width, height, (int) Math.ceil(BORDER_SIZE / scale));
320
321 mSourcePositionText.setText(sourceBounds.left + ", " + sourceBounds.top);
322 mDisplayFrameText.setText(
323 String.format("%s, %s, %s, %s", mCurrFrame.left, mCurrFrame.top, mCurrFrame.right,
324 mCurrFrame.bottom));
325 mScaleText.setText(String.valueOf(mCurrScale));
326 }
327
328 private void drawBorder(SurfaceControl borderSc, int width, int height, int borderSize) {
329 mTmpSurface.copyFrom(borderSc);
330
331 Canvas c = null;
332 try {
333 c = mTmpSurface.lockCanvas(null);
334 } catch (IllegalArgumentException | Surface.OutOfResourcesException e) {
335 }
336 if (c == null) {
337 return;
338 }
339
340 // Top
341 c.save();
342 c.clipRect(new Rect(0, 0, width, borderSize));
343 c.drawColor(DEFAULT_BORDER_COLOR);
344 c.restore();
345 // Left
346 c.save();
347 c.clipRect(new Rect(0, 0, borderSize, height));
348 c.drawColor(DEFAULT_BORDER_COLOR);
349 c.restore();
350 // Right
351 c.save();
352 c.clipRect(new Rect(width - borderSize, 0, width, height));
353 c.drawColor(DEFAULT_BORDER_COLOR);
354 c.restore();
355 // Bottom
356 c.save();
357 c.clipRect(new Rect(0, height - borderSize, width, height));
358 c.drawColor(DEFAULT_BORDER_COLOR);
359 c.restore();
360
361 mTmpSurface.unlockCanvasAndPost(c);
362 }
363
364 @Override
365 public void onClick(View v) {
366 Point offset = findOffset(v);
367 moveMirrorForArrows(offset.x, offset.y);
368 }
369
370 @Override
371 public boolean onLongClick(View v) {
372 mIsPressedDown = true;
373 Point point = findOffset(v);
374 mMoveMirrorRunnable.mXOffset = point.x;
375 mMoveMirrorRunnable.mYOffset = point.y;
376 mHandler.post(mMoveMirrorRunnable);
377 return false;
378 }
379
380 @Override
381 public boolean onTouch(View v, MotionEvent event) {
382 switch (event.getAction()) {
383 case MotionEvent.ACTION_UP:
384 case MotionEvent.ACTION_CANCEL:
385 mIsPressedDown = false;
386 break;
387 }
388 return false;
389 }
390
391 private Point findOffset(View v) {
392 Point offset = new Point(0, 0);
393
394 switch (v.getId()) {
395 case R.id.up_arrow:
396 offset.y = -MOVE_FRAME_AMOUNT;
397 break;
398 case R.id.down_arrow:
399 offset.y = MOVE_FRAME_AMOUNT;
400 break;
401 case R.id.right_arrow:
402 offset.x = -MOVE_FRAME_AMOUNT;
403 break;
404 case R.id.left_arrow:
405 offset.x = MOVE_FRAME_AMOUNT;
406 break;
407 }
408
409 return offset;
410 }
411
412 private void moveMirrorForArrows(int xOffset, int yOffset) {
413 mCurrFrame.offset(xOffset, yOffset);
414
415 updateMirror(mCurrFrame, mCurrScale);
416 }
417
418 /**
419 * Calculates the desired source bounds. This will be the area under from the center of the
420 * displayFrame, factoring in scale.
421 */
422 private Rect getSourceBounds(Rect displayFrame, float scale) {
423 int halfWidth = displayFrame.width() / 2;
424 int halfHeight = displayFrame.height() / 2;
425 int left = displayFrame.left + (halfWidth - (int) (halfWidth / scale));
426 int right = displayFrame.right - (halfWidth - (int) (halfWidth / scale));
427 int top = displayFrame.top + (halfHeight - (int) (halfHeight / scale));
428 int bottom = displayFrame.bottom - (halfHeight - (int) (halfHeight / scale));
429 return new Rect(left, top, right, bottom);
430 }
431
432 class MoveMirrorRunnable implements Runnable {
433 int mXOffset = 0;
434 int mYOffset = 0;
435
436 @Override
437 public void run() {
438 if (mIsPressedDown) {
439 moveMirrorForArrows(mXOffset, mYOffset);
440 mHandler.postDelayed(mMoveMirrorRunnable, 150);
441 }
442 }
443 }
444}