summaryrefslogtreecommitdiff
path: root/tests/MirrorSurfaceTest
diff options
context:
space:
mode:
authorchaviw <chaviw@google.com>2019-09-19 09:50:11 -0700
committerchaviw <chaviw@google.com>2019-10-02 14:09:30 -0700
commita51724fc71bff2591a072ac19907018ae57e74cb (patch)
treedad379617f0176cc80a955489de667ebb92c73e0 /tests/MirrorSurfaceTest
parent4d3106313a8c2c97f7d8345de9e682947803856a (diff)
Added API to mirror display and added sample app.
Added the WMS.mirrorDisplay API to mirror a specified displayId. The request requires the ACCESS_SURFACE_FLINGER permission. Added a sample app to demonstrate the useablility of this API and different ways the mirror SurfaceControl can be used. Test: mmma tests/MirrorSurfaceTest and install apk Bug: 131622422 Change-Id: Ic86115786d37473650b3c7ed39cba0bd4c65b281
Diffstat (limited to 'tests/MirrorSurfaceTest')
-rw-r--r--tests/MirrorSurfaceTest/Android.bp6
-rw-r--r--tests/MirrorSurfaceTest/AndroidManifest.xml34
-rw-r--r--tests/MirrorSurfaceTest/res/layout/activity_mirror_surface.xml111
-rw-r--r--tests/MirrorSurfaceTest/res/layout/move_view.xml101
-rw-r--r--tests/MirrorSurfaceTest/src/com/google/android/test/mirrorsurface/MirrorSurfaceActivity.java441
5 files changed, 693 insertions, 0 deletions
diff --git a/tests/MirrorSurfaceTest/Android.bp b/tests/MirrorSurfaceTest/Android.bp
new file mode 100644
index 000000000000..e359c64cc982
--- /dev/null
+++ b/tests/MirrorSurfaceTest/Android.bp
@@ -0,0 +1,6 @@
+android_test {
+ name: "MirrorSurfaceTest",
+ srcs: ["src/**/*.java"],
+ platform_apis: true,
+ certificate: "platform",
+}
diff --git a/tests/MirrorSurfaceTest/AndroidManifest.xml b/tests/MirrorSurfaceTest/AndroidManifest.xml
new file mode 100644
index 000000000000..123cd0f26ff3
--- /dev/null
+++ b/tests/MirrorSurfaceTest/AndroidManifest.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.google.android.test.mirrorsurface">
+ <uses-permission android:name="android.permission.ACCESS_SURFACE_FLINGER"/>
+ <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
+
+ <application android:label="MirrorSurfaceTest">
+ <activity android:name=".MirrorSurfaceActivity"
+ android:label="Mirror Surface"
+ android:configChanges="orientation|screenSize">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.LAUNCHER"/>
+ <category android:name="android.intent.category.DEFAULT"/>
+ </intent-filter>
+ </activity>
+ </application>
+</manifest>
diff --git a/tests/MirrorSurfaceTest/res/layout/activity_mirror_surface.xml b/tests/MirrorSurfaceTest/res/layout/activity_mirror_surface.xml
new file mode 100644
index 000000000000..73b509f743d1
--- /dev/null
+++ b/tests/MirrorSurfaceTest/res/layout/activity_mirror_surface.xml
@@ -0,0 +1,111 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_horizontal"
+ android:layout_marginTop="20dp"
+ android:orientation="horizontal">
+
+ <Button
+ android:id="@+id/mirror_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginRight="20dp"
+ android:text="Mirror"
+ android:textSize="20dp" />
+
+ <Button
+ android:id="@+id/remove_mirror_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginRight="20dp"
+ android:text="Remove Mirror"
+ android:textSize="20dp" />
+ </LinearLayout>
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginLeft="40dp"
+ android:layout_marginRight="40dp"
+ android:layout_marginTop="10dp"
+ android:orientation="horizontal">
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginRight="20dp"
+ android:text="SCALE: " />
+
+ <EditText
+ android:hint="0.5"
+ android:id="@+id/scale"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:inputType="numberDecimal" />
+ </LinearLayout>
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginLeft="40dp"
+ android:layout_marginRight="40dp"
+ android:layout_marginTop="10dp"
+ android:orientation="horizontal">
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginRight="20dp"
+ android:text="DISPLAY FRAME: " />
+
+ <EditText
+ android:hint="0, 0, 20, 20"
+ android:id="@+id/displayFrame"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:inputType="numberDecimal|text"/>
+ </LinearLayout>
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginLeft="40dp"
+ android:layout_marginRight="40dp"
+ android:layout_marginTop="10dp"
+ android:orientation="horizontal">
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginRight="20dp"
+ android:text="SOURCE POSITION: " />
+
+ <TextView
+ android:id="@+id/sourcePosition"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:inputType="numberDecimal|text"/>
+ </LinearLayout>
+</LinearLayout>
diff --git a/tests/MirrorSurfaceTest/res/layout/move_view.xml b/tests/MirrorSurfaceTest/res/layout/move_view.xml
new file mode 100644
index 000000000000..57077006765e
--- /dev/null
+++ b/tests/MirrorSurfaceTest/res/layout/move_view.xml
@@ -0,0 +1,101 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_margin="20dp"
+ android:gravity="center">
+
+ <RelativeLayout
+ android:id="@+id/arrows"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content">
+
+ <ImageButton
+ android:id="@+id/up_arrow"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_toEndOf="@+id/right_arrow"
+ android:background="@android:color/holo_green_light"
+ android:padding="10dp"
+ android:src="@android:drawable/arrow_up_float" />
+
+ <ImageButton
+ android:id="@+id/down_arrow"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_below="@+id/up_arrow"
+ android:layout_marginTop="80dp"
+ android:layout_toEndOf="@+id/right_arrow"
+ android:background="@android:color/holo_green_light"
+ android:padding="10dp"
+ android:src="@android:drawable/arrow_down_float" />
+
+ <ImageButton
+ android:id="@+id/right_arrow"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignTop="@+id/up_arrow"
+ android:layout_alignBottom="@+id/down_arrow"
+ android:layout_marginTop="55dp"
+ android:layout_marginEnd="15dp"
+ android:layout_marginBottom="55dp"
+ android:background="@android:color/holo_green_light"
+ android:paddingLeft="10dp"
+ android:paddingRight="10dp"
+ android:rotation="90"
+ android:src="@android:drawable/arrow_down_float" />
+
+ <ImageButton
+ android:id="@+id/left_arrow"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignTop="@+id/up_arrow"
+ android:layout_alignBottom="@+id/down_arrow"
+ android:layout_marginStart="15dp"
+ android:layout_marginTop="55dp"
+ android:layout_marginBottom="55dp"
+ android:layout_toEndOf="@+id/down_arrow"
+ android:background="@android:color/holo_green_light"
+ android:paddingLeft="10dp"
+ android:paddingRight="10dp"
+ android:rotation="-90"
+ android:src="@android:drawable/arrow_down_float" />
+ </RelativeLayout>
+
+ <RelativeLayout
+
+ android:layout_gravity="center"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content">
+
+ <Button
+ android:id="@+id/zoom_in_button"
+ android:layout_width="40dp"
+ android:layout_marginBottom="-8dp"
+ android:layout_height="40dp"
+ android:text="+" />
+
+ <Button
+ android:layout_below="@+id/zoom_in_button"
+ android:id="@+id/zoom_out_button"
+ android:layout_width="40dp"
+ android:layout_height="40dp"
+ android:text="-" />
+ </RelativeLayout>
+</FrameLayout> \ No newline at end of file
diff --git a/tests/MirrorSurfaceTest/src/com/google/android/test/mirrorsurface/MirrorSurfaceActivity.java b/tests/MirrorSurfaceTest/src/com/google/android/test/mirrorsurface/MirrorSurfaceActivity.java
new file mode 100644
index 000000000000..b863713df15b
--- /dev/null
+++ b/tests/MirrorSurfaceTest/src/com/google/android/test/mirrorsurface/MirrorSurfaceActivity.java
@@ -0,0 +1,441 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.test.mirrorsurface;
+
+
+import android.app.Activity;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.PixelFormat;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.RemoteException;
+import android.util.DisplayMetrics;
+import android.view.Gravity;
+import android.view.IWindowManager;
+import android.view.MotionEvent;
+import android.view.Surface;
+import android.view.SurfaceControl;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.view.WindowManagerGlobal;
+import android.widget.EditText;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+public class MirrorSurfaceActivity extends Activity implements View.OnClickListener,
+ View.OnLongClickListener, View.OnTouchListener {
+ private static final int BORDER_SIZE = 10;
+ private static final int DEFAULT_SCALE = 2;
+ private static final int DEFAULT_BORDER_COLOR = Color.argb(255, 255, 153, 0);
+ private static final int MOVE_FRAME_AMOUNT = 20;
+
+ private IWindowManager mIWm;
+ private WindowManager mWm;
+
+ private SurfaceControl mSurfaceControl = new SurfaceControl();
+ private SurfaceControl mBorderSc;
+
+ private SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction();
+ private View mOverlayView;
+ private View mArrowOverlay;
+
+ private Rect mDisplayBounds = new Rect();
+
+ private EditText mScaleText;
+ private EditText mDisplayFrameText;
+ private TextView mSourcePositionText;
+
+ private Rect mTmpRect = new Rect();
+ private final Surface mTmpSurface = new Surface();
+
+ private boolean mHasMirror;
+
+ private Rect mCurrFrame = new Rect();
+ private float mCurrScale = DEFAULT_SCALE;
+
+ private final Handler mHandler = new Handler();
+
+ private MoveMirrorRunnable mMoveMirrorRunnable = new MoveMirrorRunnable();
+ private boolean mIsPressedDown = false;
+
+ private int mDisplayId;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ setContentView(R.layout.activity_mirror_surface);
+ mWm = (WindowManager) getSystemService(WINDOW_SERVICE);
+ mIWm = WindowManagerGlobal.getWindowManagerService();
+
+ DisplayMetrics displayMetrics = new DisplayMetrics();
+ getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
+ mDisplayBounds.set(0, 0, displayMetrics.widthPixels, displayMetrics.heightPixels);
+
+ mScaleText = findViewById(R.id.scale);
+ mDisplayFrameText = findViewById(R.id.displayFrame);
+ mSourcePositionText = findViewById(R.id.sourcePosition);
+
+ mCurrFrame.set(0, 0, mDisplayBounds.width() / 2, mDisplayBounds.height() / 2);
+ mCurrScale = DEFAULT_SCALE;
+
+ mDisplayId = getWindowManager().getDefaultDisplay().getDisplayId();
+ updateEditTexts();
+
+ findViewById(R.id.mirror_button).setOnClickListener(view -> {
+ if (mArrowOverlay == null) {
+ createArrowOverlay();
+ }
+ createOrUpdateMirror();
+ });
+
+ findViewById(R.id.remove_mirror_button).setOnClickListener(v -> {
+ removeMirror();
+ removeArrowOverlay();
+ });
+
+ createMirrorOverlay();
+ }
+
+ private void updateEditTexts() {
+ mDisplayFrameText.setText(
+ String.format("%s, %s, %s, %s", mCurrFrame.left, mCurrFrame.top, mCurrFrame.right,
+ mCurrFrame.bottom));
+ mScaleText.setText(String.valueOf(mCurrScale));
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ if (mOverlayView != null) {
+ removeMirror();
+ mWm.removeView(mOverlayView);
+ mOverlayView = null;
+ }
+ removeArrowOverlay();
+ }
+
+ private void createArrowOverlay() {
+ mArrowOverlay = getLayoutInflater().inflate(R.layout.move_view, null);
+ WindowManager.LayoutParams arrowParams = new WindowManager.LayoutParams(
+ ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT,
+ WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY,
+ WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
+ | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
+ | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
+ PixelFormat.RGBA_8888);
+ arrowParams.gravity = Gravity.RIGHT | Gravity.BOTTOM;
+ mWm.addView(mArrowOverlay, arrowParams);
+
+ View leftArrow = mArrowOverlay.findViewById(R.id.left_arrow);
+ View topArrow = mArrowOverlay.findViewById(R.id.up_arrow);
+ View rightArrow = mArrowOverlay.findViewById(R.id.right_arrow);
+ View bottomArrow = mArrowOverlay.findViewById(R.id.down_arrow);
+
+ leftArrow.setOnClickListener(this);
+ topArrow.setOnClickListener(this);
+ rightArrow.setOnClickListener(this);
+ bottomArrow.setOnClickListener(this);
+
+ leftArrow.setOnLongClickListener(this);
+ topArrow.setOnLongClickListener(this);
+ rightArrow.setOnLongClickListener(this);
+ bottomArrow.setOnLongClickListener(this);
+
+ leftArrow.setOnTouchListener(this);
+ topArrow.setOnTouchListener(this);
+ rightArrow.setOnTouchListener(this);
+ bottomArrow.setOnTouchListener(this);
+
+ mArrowOverlay.findViewById(R.id.zoom_in_button).setOnClickListener(v -> {
+ if (mCurrScale <= 1) {
+ mCurrScale *= 2;
+ } else {
+ mCurrScale += 0.5;
+ }
+
+ updateMirror(mCurrFrame, mCurrScale);
+ });
+ mArrowOverlay.findViewById(R.id.zoom_out_button).setOnClickListener(v -> {
+ if (mCurrScale <= 1) {
+ mCurrScale /= 2;
+ } else {
+ mCurrScale -= 0.5;
+ }
+
+ updateMirror(mCurrFrame, mCurrScale);
+ });
+ }
+
+ private void removeArrowOverlay() {
+ if (mArrowOverlay != null) {
+ mWm.removeView(mArrowOverlay);
+ mArrowOverlay = null;
+ }
+ }
+
+ private void createMirrorOverlay() {
+ mOverlayView = new LinearLayout(this);
+ WindowManager.LayoutParams params = new WindowManager.LayoutParams(mDisplayBounds.width(),
+ mDisplayBounds.height(),
+ WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY,
+ WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
+ | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
+ PixelFormat.RGBA_8888);
+ params.gravity = Gravity.LEFT | Gravity.TOP;
+ params.setTitle("Mirror Overlay");
+ mOverlayView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE
+ | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
+ | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
+ | View.SYSTEM_UI_FLAG_FULLSCREEN
+ | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
+ | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION);
+
+ mWm.addView(mOverlayView, params);
+
+ }
+
+ private void removeMirror() {
+ if (mSurfaceControl.isValid()) {
+ mTransaction.remove(mSurfaceControl).apply();
+ }
+ mHasMirror = false;
+ }
+
+ private void createOrUpdateMirror() {
+ if (mHasMirror) {
+ updateMirror(getDisplayFrame(), getScale());
+ } else {
+ createMirror(getDisplayFrame(), getScale());
+ }
+
+ }
+
+ private Rect getDisplayFrame() {
+ mTmpRect.setEmpty();
+ String[] frameVals = mDisplayFrameText.getText().toString().split("\\s*,\\s*");
+ if (frameVals.length != 4) {
+ return mTmpRect;
+ }
+
+ try {
+ mTmpRect.set(Integer.parseInt(frameVals[0]), Integer.parseInt(frameVals[1]),
+ Integer.parseInt(frameVals[2]), Integer.parseInt(frameVals[3]));
+ } catch (Exception e) {
+ mTmpRect.setEmpty();
+ }
+
+ return mTmpRect;
+ }
+
+ private float getScale() {
+ try {
+ return Float.parseFloat(mScaleText.getText().toString());
+ } catch (Exception e) {
+ return -1;
+ }
+ }
+
+ private void createMirror(Rect displayFrame, float scale) {
+ boolean success = false;
+ try {
+ success = mIWm.mirrorDisplay(mDisplayId, mSurfaceControl);
+ } catch (RemoteException e) {
+ }
+
+ if (!success) {
+ return;
+ }
+
+ if (!mSurfaceControl.isValid()) {
+ return;
+ }
+
+ mHasMirror = true;
+
+ mBorderSc = new SurfaceControl.Builder()
+ .setName("Mirror Border")
+ .setBufferSize(1, 1)
+ .setFormat(PixelFormat.TRANSLUCENT)
+ .build();
+
+ updateMirror(displayFrame, scale);
+
+ mTransaction
+ .show(mSurfaceControl)
+ .reparent(mSurfaceControl, mOverlayView.getViewRootImpl().getSurfaceControl())
+ .setLayer(mBorderSc, 1)
+ .show(mBorderSc)
+ .reparent(mBorderSc, mSurfaceControl)
+ .apply();
+ }
+
+ private void updateMirror(Rect displayFrame, float scale) {
+ if (displayFrame.isEmpty()) {
+ Rect bounds = mDisplayBounds;
+ int defaultCropW = Math.round(bounds.width() / 2);
+ int defaultCropH = Math.round(bounds.height() / 2);
+ displayFrame.set(0, 0, defaultCropW, defaultCropH);
+ }
+
+ if (scale <= 0) {
+ scale = DEFAULT_SCALE;
+ }
+
+ mCurrFrame.set(displayFrame);
+ mCurrScale = scale;
+
+ int width = (int) Math.ceil(displayFrame.width() / scale);
+ int height = (int) Math.ceil(displayFrame.height() / scale);
+
+ Rect sourceBounds = getSourceBounds(displayFrame, scale);
+
+ mTransaction.setGeometry(mSurfaceControl, sourceBounds, displayFrame, Surface.ROTATION_0)
+ .setPosition(mBorderSc, sourceBounds.left, sourceBounds.top)
+ .setBufferSize(mBorderSc, width, height)
+ .apply();
+
+ drawBorder(mBorderSc, width, height, (int) Math.ceil(BORDER_SIZE / scale));
+
+ mSourcePositionText.setText(sourceBounds.left + ", " + sourceBounds.top);
+ mDisplayFrameText.setText(
+ String.format("%s, %s, %s, %s", mCurrFrame.left, mCurrFrame.top, mCurrFrame.right,
+ mCurrFrame.bottom));
+ mScaleText.setText(String.valueOf(mCurrScale));
+ }
+
+ private void drawBorder(SurfaceControl borderSc, int width, int height, int borderSize) {
+ mTmpSurface.copyFrom(borderSc);
+
+ Canvas c = null;
+ try {
+ c = mTmpSurface.lockCanvas(null);
+ } catch (IllegalArgumentException | Surface.OutOfResourcesException e) {
+ }
+ if (c == null) {
+ return;
+ }
+
+ // Top
+ c.save();
+ c.clipRect(new Rect(0, 0, width, borderSize));
+ c.drawColor(DEFAULT_BORDER_COLOR);
+ c.restore();
+ // Left
+ c.save();
+ c.clipRect(new Rect(0, 0, borderSize, height));
+ c.drawColor(DEFAULT_BORDER_COLOR);
+ c.restore();
+ // Right
+ c.save();
+ c.clipRect(new Rect(width - borderSize, 0, width, height));
+ c.drawColor(DEFAULT_BORDER_COLOR);
+ c.restore();
+ // Bottom
+ c.save();
+ c.clipRect(new Rect(0, height - borderSize, width, height));
+ c.drawColor(DEFAULT_BORDER_COLOR);
+ c.restore();
+
+ mTmpSurface.unlockCanvasAndPost(c);
+ }
+
+ @Override
+ public void onClick(View v) {
+ Point offset = findOffset(v);
+ moveMirrorForArrows(offset.x, offset.y);
+ }
+
+ @Override
+ public boolean onLongClick(View v) {
+ mIsPressedDown = true;
+ Point point = findOffset(v);
+ mMoveMirrorRunnable.mXOffset = point.x;
+ mMoveMirrorRunnable.mYOffset = point.y;
+ mHandler.post(mMoveMirrorRunnable);
+ return false;
+ }
+
+ @Override
+ public boolean onTouch(View v, MotionEvent event) {
+ switch (event.getAction()) {
+ case MotionEvent.ACTION_UP:
+ case MotionEvent.ACTION_CANCEL:
+ mIsPressedDown = false;
+ break;
+ }
+ return false;
+ }
+
+ private Point findOffset(View v) {
+ Point offset = new Point(0, 0);
+
+ switch (v.getId()) {
+ case R.id.up_arrow:
+ offset.y = -MOVE_FRAME_AMOUNT;
+ break;
+ case R.id.down_arrow:
+ offset.y = MOVE_FRAME_AMOUNT;
+ break;
+ case R.id.right_arrow:
+ offset.x = -MOVE_FRAME_AMOUNT;
+ break;
+ case R.id.left_arrow:
+ offset.x = MOVE_FRAME_AMOUNT;
+ break;
+ }
+
+ return offset;
+ }
+
+ private void moveMirrorForArrows(int xOffset, int yOffset) {
+ mCurrFrame.offset(xOffset, yOffset);
+
+ updateMirror(mCurrFrame, mCurrScale);
+ }
+
+ /**
+ * Calculates the desired source bounds. This will be the area under from the center of the
+ * displayFrame, factoring in scale.
+ */
+ private Rect getSourceBounds(Rect displayFrame, float scale) {
+ int halfWidth = displayFrame.width() / 2;
+ int halfHeight = displayFrame.height() / 2;
+ int left = displayFrame.left + (halfWidth - (int) (halfWidth / scale));
+ int right = displayFrame.right - (halfWidth - (int) (halfWidth / scale));
+ int top = displayFrame.top + (halfHeight - (int) (halfHeight / scale));
+ int bottom = displayFrame.bottom - (halfHeight - (int) (halfHeight / scale));
+ return new Rect(left, top, right, bottom);
+ }
+
+ class MoveMirrorRunnable implements Runnable {
+ int mXOffset = 0;
+ int mYOffset = 0;
+
+ @Override
+ public void run() {
+ if (mIsPressedDown) {
+ moveMirrorForArrows(mXOffset, mYOffset);
+ mHandler.postDelayed(mMoveMirrorRunnable, 150);
+ }
+ }
+ }
+}