diff options
-rw-r--r-- | core/java/android/view/IWindowManager.aidl | 12 | ||||
-rw-r--r-- | core/java/android/view/SurfaceControl.java | 24 | ||||
-rw-r--r-- | core/jni/android_view_SurfaceControl.cpp | 11 | ||||
-rw-r--r-- | services/core/java/com/android/server/wm/WindowManagerService.java | 24 | ||||
-rw-r--r-- | tests/MirrorSurfaceTest/Android.bp | 6 | ||||
-rw-r--r-- | tests/MirrorSurfaceTest/AndroidManifest.xml | 34 | ||||
-rw-r--r-- | tests/MirrorSurfaceTest/res/layout/activity_mirror_surface.xml | 111 | ||||
-rw-r--r-- | tests/MirrorSurfaceTest/res/layout/move_view.xml | 101 | ||||
-rw-r--r-- | tests/MirrorSurfaceTest/src/com/google/android/test/mirrorsurface/MirrorSurfaceActivity.java | 441 |
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); + } + } + } +} |