summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/android/view/IWindowManager.aidl12
-rw-r--r--core/java/android/view/SurfaceControl.java24
-rw-r--r--core/jni/android_view_SurfaceControl.cpp11
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerService.java24
-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
9 files changed, 763 insertions, 1 deletions
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index 49e8800a36c6..35cfe9e591cf 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -644,4 +644,16 @@ interface IWindowManager
* Enables/disables SurfaceFlinger layer tracing.
*/
void setLayerTracing(boolean enabled);
+
+ /**
+ * Mirrors a specified display. The root of the mirrored hierarchy will be stored in
+ * outSurfaceControl.
+ * Requires the ACCESS_SURFACE_FLINGER permission.
+ *
+ * @param displayId The id of the display to mirror
+ * @param outSurfaceControl The SurfaceControl for the root of the mirrored hierarchy.
+ *
+ * @return true if the display was successfully mirrored.
+ */
+ boolean mirrorDisplay(int displayId, out SurfaceControl outSurfaceControl);
}
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index 6639fbf9551e..b685cf098b33 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -91,7 +91,7 @@ public final class SurfaceControl implements Parcelable {
boolean captureSecureLayers);
private static native ScreenshotGraphicBuffer nativeCaptureLayers(IBinder displayToken,
long layerObject, Rect sourceCrop, float frameScale, long[] excludeLayerObjects);
-
+ private static native long nativeMirrorSurface(long mirrorOfObject);
private static native long nativeCreateTransaction();
private static native long nativeGetNativeTransactionFinalizer();
private static native void nativeApplyTransaction(long transactionObj, boolean sync);
@@ -2034,6 +2034,28 @@ public final class SurfaceControl implements Parcelable {
return nativeSetDisplayBrightness(displayToken, brightness);
}
+ /**
+ * Creates a mirrored hierarchy for the mirrorOf {@link SurfaceControl}
+ *
+ * Real Hierarchy Mirror
+ * SC (value that's returned)
+ * |
+ * A A'
+ * | |
+ * B B'
+ *
+ * @param mirrorOf The root of the hierarchy that should be mirrored.
+ * @return A SurfaceControl that's the parent of the root of the mirrored hierarchy.
+ *
+ * @hide
+ */
+ public static SurfaceControl mirrorSurface(SurfaceControl mirrorOf) {
+ long nativeObj = nativeMirrorSurface(mirrorOf.mNativeObject);
+ SurfaceControl sc = new SurfaceControl();
+ sc.assignNativeObject(nativeObj);
+ return sc;
+ }
+
/**
* An atomic set of changes to a set of SurfaceControl.
*/
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index 1ca9383b920f..d5cd278063c0 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -1244,6 +1244,15 @@ static jlong nativeReadTransactionFromParcel(JNIEnv* env, jclass clazz, jobject
return reinterpret_cast<jlong>(transaction.release());
}
+static jlong nativeMirrorSurface(JNIEnv* env, jclass clazz, jlong mirrorOfObj) {
+ sp<SurfaceComposerClient> client = SurfaceComposerClient::getDefault();
+ SurfaceControl *mirrorOf = reinterpret_cast<SurfaceControl*>(mirrorOfObj);
+ sp<SurfaceControl> surface = client->mirrorSurface(mirrorOf);
+
+ surface->incStrong((void *)nativeCreate);
+ return reinterpret_cast<jlong>(surface.get());
+}
+
// ----------------------------------------------------------------------------
static const JNINativeMethod sSurfaceControlMethods[] = {
@@ -1394,6 +1403,8 @@ static const JNINativeMethod sSurfaceControlMethods[] = {
(void*)nativeReadTransactionFromParcel },
{"nativeWriteTransactionToParcel", "(JLandroid/os/Parcel;)V",
(void*)nativeWriteTransactionToParcel },
+ {"nativeMirrorSurface", "(J)J",
+ (void*)nativeMirrorSurface },
};
int register_android_view_SurfaceControl(JNIEnv* env)
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 169e4159346f..25b1428ad4a1 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -16,6 +16,7 @@
package com.android.server.wm;
+import static android.Manifest.permission.ACCESS_SURFACE_FLINGER;
import static android.Manifest.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS;
import static android.Manifest.permission.INTERNAL_SYSTEM_WINDOW;
import static android.Manifest.permission.MANAGE_APP_TOKENS;
@@ -7746,4 +7747,27 @@ public class WindowManagerService extends IWindowManager.Stub
Binder.restoreCallingIdentity(token);
}
}
+
+ @Override
+ public boolean mirrorDisplay(int displayId, SurfaceControl outSurfaceControl) {
+ if (!checkCallingPermission(ACCESS_SURFACE_FLINGER, "mirrorDisplay()")) {
+ throw new SecurityException("Requires ACCESS_SURFACE_FLINGER permission");
+ }
+
+ final SurfaceControl displaySc;
+ synchronized (mGlobalLock) {
+ DisplayContent displayContent = mRoot.getDisplayContent(displayId);
+ if (displayContent == null) {
+ Slog.e(TAG, "Invalid displayId " + displayId + " for mirrorDisplay");
+ return false;
+ }
+
+ displaySc = displayContent.getSurfaceControl();
+ }
+
+ final SurfaceControl mirror = SurfaceControl.mirrorSurface(displaySc);
+ outSurfaceControl.copyFrom(mirror);
+
+ return true;
+ }
}
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);
+ }
+ }
+ }
+}