Android车载双屏移动的实现(源码)

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


前言

车载双屏移动,采用SurfaceControl镜像方法实现。

一、什么是双屏移动?

Android系统驱动两个屏,将其中一个屏的应用移动到另一个屏中,如下图所示。

在这里插入图片描述

二、实现原理

假设,将屏A中当前应用移动到屏B。
首先,镜像屏A当前应用的图层,记为Mirror;
其次,将Mirror挂载到屏B;
接着,移动屏A的图层时,屏B中的Mirror跟着移动;
最后,移动完成后,屏A的当前应用通过移栈方式,移动到屏B。

三、实现步骤

直接上代码。

package com.android.server.wm;

import static com.android.server.wm.ActivityTaskSupervisor.PRESERVE_WINDOWS;

import android.util.Log;

import android.graphics.Matrix;
import android.graphics.Rect;
import android.view.SurfaceControl;
import android.view.SurfaceControl.Transaction;

import android.animation.ValueAnimator;
import android.animation.ValueAnimator.AnimatorUpdateListener;

public class DoubleScreenMoveController {

	private final static String TAG = "DoubleScreenMoveController";

    private final RootWindowContainer mRootWindowContainer;
	private final DisplayContent mDisplayContent;
    private final WindowManagerService mWmService;
	private final SurfaceControl.Transaction mTransaction;

    private SurfaceControl mMovedSurface = null;
    private SurfaceControl mMirroredSurface = null;

	private int mMovedActivityWidth = 0;
	private int mMovedActivityHeight = 0;
	private boolean mKeepMoving = false;

    public DoubleScreenMoveController(WindowManagerService wms,
		                              DisplayContent displayContent, 
		                              RootWindowContainer rwc) {
        this.mWmService = wms;
        this.mDisplayContent = displayContent;
		this.mRootWindowContainer = rwc;
		this.mTransaction = wms.mTransactionFactory.get();
    }

	public int getDisplayCount() {
        return mWmService.mRoot.getChildCount();
	}
	
	public void startMoving() {
		 DisplayContent targetDisplay = getTargetDisplayContent();
		 if (targetDisplay != mDisplayContent && targetDisplay != null) {
			 try {
				 mirrorDisplay(mDisplayContent, targetDisplay);
				 //staskBelowVisibility();
			 } catch (Exception e) {
				 Log.e(TAG, "moveTaskToTargetDisplay Exception: ",e);
				 stopMoving();
			 }
		 }
	}


	public void goMoving(int x, boolean isMovingRight) {
          if (mMovedSurface != null && mMirroredSurface != null) {
			  int offsetX = isMovingRight ? x : -x;
			  Matrix matrix = new Matrix();
              matrix.reset();
			  matrix.postTranslate(offsetX, 0);
			  mTransaction
			  	.setMatrix(mMovedSurface, matrix, new float[9])
			  	.apply();
          }
	}

	public void keepMoving(int offsetX, boolean isMovingRight, boolean isCanceled) {
		if (mKeepMoving) return;
		mKeepMoving = true;
        int startX = offsetX;
		int endX = !isCanceled ? mMovedActivityWidth : 0;

		ValueAnimator valueAnimator = ValueAnimator.ofInt(startX, endX);
		valueAnimator.setDuration(500);
		valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
			public void onAnimationUpdate(ValueAnimator animation) {
                int value = (int) animation.getAnimatedValue();
				goMoving(value, isMovingRight);
				if (value == endX) {
					if (!isCanceled) {
                        moveTaskToTargetDisplay();
					}
                    stopMoving();
					mKeepMoving = false;
				}
            }
		});
		valueAnimator.start();
	}

	public void stopMoving() {
        removeMirroredSurface();
	}

	private void removeMirroredSurface() {
        if (mMirroredSurface != null) {
            // Do not wait for the mirrored surface to be garbage collected, but clean up
            // immediately.
            mTransaction.remove(mMirroredSurface).apply();
            mMirroredSurface = null;
        }
	}

	private void mirrorDisplay(DisplayContent displayContent, DisplayContent targetContent) {
		if (mMirroredSurface != null) return;
		ActivityRecord ar = displayContent.topRunningActivity();
		if (ar == null) return;
		SurfaceControl sc = ar.getSurfaceControl();
        final Rect containerBounds = ar.getWindowConfiguration().getBounds();
		int width = containerBounds.width();
		int height = containerBounds.height();

		Log.i(TAG, "mirrorDisplay size:"+width+"x"+height);
		
		// Create a mirrored hierarchy for the SurfaceControl of the DisplayArea to capture.
		SurfaceControl mirror = SurfaceControl.mirrorSurface(sc);
		mTransaction
		// Set the mMirroredSurface's parent to the root SurfaceControl for this
		// DisplayContent. This brings the new mirrored hierarchy under this DisplayContent,
		// so SurfaceControl will write the layers of this hierarchy to the output surface
		// provided by the app.
		.reparent(mirror, targetContent.getSurfaceControl());
		// Reparent the SurfaceControl of this DisplayContent to null, to prevent content
		// being added to it. This ensures that no app launched explicitly on the
		// VirtualDisplay will show up as part of the mirrored content.
		//.reparent(mWindowingLayer, null)
		//.reparent(mOverlayLayer, null);
		// Crop the area to capture to exclude the 'extra' wallpaper that is used
		// for parallax (b/189930234).
		mTransaction
		.setWindowCrop(mirror, width*2, height)
		// Scale the root mirror SurfaceControl, based upon the size difference between the
		// source (DisplayArea to capture) and output (surface the app reads images from).
		.setMatrix(mirror, 1, 0 , 0 , 1)
		// Position needs to be updated when the mirrored DisplayArea has changed, since
		// the content will no longer be centered in the output surface.
		.setPosition(mirror, -width , 0)
		.setBufferSize(mirror, width*2, height)
		.apply();

        mMovedActivityWidth = width;
		mMovedActivityHeight = height;
        mMirroredSurface = mirror;
		mMovedSurface = sc;
	}
	
	private void moveTaskToTargetDisplay() {			 
		 DisplayContent targetDisplay = getTargetDisplayContent();
		 if (targetDisplay != mDisplayContent && targetDisplay != null) {
			 try {
				 Task rootTask = mDisplayContent.getTopRootTask();
				 if (rootTask.isActivityTypeHome()) {
					 Log.w(TAG, "Cannot move Home UI");
					 return;
				 }

				 int rootTaskId = rootTask.mTaskId;
				 mRootWindowContainer.moveRootTaskToDisplay(rootTaskId, targetDisplay.mDisplayId, true);

				 Matrix matrix = new Matrix();
				 matrix.reset();
				 matrix.postTranslate(0, 0);
				 mTransaction
				 	.setMatrix(mMovedSurface, matrix, new float[9])
				 	.apply();
			 } catch (Exception e) {
				 Log.e(TAG, "moveTaskToTargetDisplay Exception: ",e);
				 stopMoving();
			 }
		 }
	}

	private void cancelMoveTaskToTargetDisplay() {
		 DisplayContent targetDisplay = getTargetDisplayContent();
		 if (targetDisplay != mDisplayContent && targetDisplay != null) {
 			 try {
				 Task rootTask = targetDisplay.getTopRootTask();
				 if (rootTask.isActivityTypeHome()) {
					 Log.w(TAG, "Cannot move Home UI");
					 return;
				 }
				 
				 int rootTaskId = rootTask.mTaskId;	
				 mRootWindowContainer.moveRootTaskToDisplay(rootTaskId, mDisplayContent.mDisplayId, true);
			 } catch (Exception e) {
				 Log.e(TAG, "cancelMoveTaskToTargetDisplay Exception: ",e);	 
			 }            

		 }
	}

	private DisplayContent getTargetDisplayContent() {
		DisplayContent targetDisplay = null;
		if (mRootWindowContainer.getChildCount() == 2) {
			targetDisplay = (mRootWindowContainer.getChildAt(0) == mDisplayContent) 
			   ? mRootWindowContainer.getChildAt(1): mRootWindowContainer.getChildAt(0);
		}

		return targetDisplay;
	}

}

代码不多,细节不详说了。

总结

本方案关键是在于如何镜像图层。
幸运的是,Android12 SurfaceControl新增mirrorSurface方法,该方法对已知图层进行镜像。

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
要下载Android车载导航Launcher附源码,首先需要找到合适的网站或平台。可以通过搜索引擎搜索相关关键词,如“Android车载导航Launcher源码下载”来寻找相关资源。或者也可以通过开发者社区,如GitHub、StackOverflow等寻找开源项目。 一旦找到合适的下载资源,确保源码与您所需的功能和需求相匹配。仔细阅读项目的说明文档、许可证和用户评价等信息,确保该源码符合您的要求。 在进行下载之前,确保您具备开发环境以及相应的开发工具和技术知识。一般情况下,车载导航Launcher是基于Android平台的,因此您需要拥有Android开发环境和Android开发工具包(SDK)。 下载源码后,解压文件并打开项目。您可以使用Android Studio等IDE(集成开发环境)来导入项目,并根据您的需求进行配置和修改。 在进行任何修改之前,建议先阅读项目的文档和代码结构,熟悉项目的整体逻辑和功能。这样能更好地理解代码,并确保修改不会影响原有功能的稳定性和正确性。 修改完毕后,您可以根据自己的需要进行构建、编译和测试。确保应用车载设备上的兼容性和稳定性。 最后,如果您对该源码进行了改进或优化,也可以考虑将您的成果贡献给开源社区,以便其他开发者也能受益。 总之,下载Android车载导航Launcher附源码需要找到合适的下载资源、具备开发环境和工具、熟悉项目结构和代码逻辑、进行修改和测试。这样才能成功地下载并使用源码来满足您的需求。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值