Mirror Display、Mirror Task

随着车载屏幕增加,车机的操作日渐花哨。

本文讲述一下针对两个屏幕(包括虚拟屏)以上车机的屏幕复制以及task复制,即将主屏幕的显示信息mirror到副屏上。

关于mirror源的获取同task信息的获取。当registerOrganizer时会同时返回task信息以及SurfaceControl,SurfaceControl就是我们需要mirror的源。

获取流程参照:

Android12后Task信息获取-CSDN博客Android12之后就没有所谓的Stack了,task信息的获取是通过TaskOrganizer,根据代码设定每次registerOrganizer时都会默认取最后一次的organizer对象,因此建议全局只能有一个实例。registerOrganizer方法需要开机启动时执行,启动task时是与organizer关联的,当启动task之后,在调用registerOrganizer方法,页面可能会黑掉。注:CarLauncher在registerOrganizer时就已经获取了Task的list。https://blog.csdn.net/qq_28095461/article/details/133432831

MirrorDisplay

官方还是很给力的,给出了mirrordisplay的测试代码,亲测好用。

mirrordisplay只会mirror当前task以及header,NavigationBar,并不会mirror telop。

mirror后的源显示后可以进行点击操作,被mirror源可同步响应。

奉上Android13 aosp test源码:

/*
 * 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 static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;

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.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;
import android.window.WindowMetricsHelper;

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;
    // An instance of WindowManager that is adjusted for adding windows with type
    // TYPE_APPLICATION_OVERLAY.
    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 mWindowBounds = 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 = createWindowContext(TYPE_APPLICATION_OVERLAY, null /* options */)
                .getSystemService(WindowManager.class);
        mIWm = WindowManagerGlobal.getWindowManagerService();

        Rect windowBounds = WindowMetricsHelper.getBoundsExcludingNavigationBarAndCutout(
                mWm.getCurrentWindowMetrics());
        mWindowBounds.set(0, 0, windowBounds.width(), windowBounds.height());

        mScaleText = findViewById(R.id.scale);
        mDisplayFrameText = findViewById(R.id.displayFrame);
        mSourcePositionText = findViewById(R.id.sourcePosition);

        mCurrFrame.set(0, 0, mWindowBounds.width() / 2, mWindowBounds.height() / 2);
        mCurrScale = DEFAULT_SCALE;

        mDisplayId = getDisplay().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(mWindowBounds.width(),
                mWindowBounds.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 = mWindowBounds;
            int defaultCropW = bounds.width() / 2;
            int defaultCropH = 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);
            }
        }
    }
}
MirrorTask

mirrortask的方法与mirrorDisplay相同,不同的是mirror后的效果,只有task信息没有header以及navigationbar的信息。

修改后的代码:(所有显示代码都无需修改,只需要修改获取SurfaceControl的方法以及mirror的方法)。

MonitorTaskListener mMonitorTaskListener = new MonitorTaskListener();   
mShellTaskOrganizer = new ShellTaskOrganizer(new HandlerExecutor(getMainThreadHandler()));          
mShellTaskOrganizer.addListenerForType(mMonitorTaskListener, TASK_LISTENER_TYPE_FULLSCREEN);           
List<TaskAppearedInfo> listTask = mShellTaskOrganizer.registerOrganizer();         
if (listTask.size()>0) {            
    final SurfaceControl mirror = SurfaceControl.mirrorSurface(listTask.get(0).getLeash());
    mSurfaceControl.copyFrom(mirror, "mirrortask");
    Log.i(TAG, listTask.get(0).getLeash().toString());
    Log.i(TAG, mSurfaceControl.toString());
    Log.i(TAG, listTask.get(0).getTaskInfo().topActivity.getClassName().toString()); 
}

注意:Mirror后的SurfaceControl只能等比例的zoom,否则会变形。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值