我是做车机ivi开发的,目前有个需求是仿特斯拉的娱乐系统显示,用一个大屏左边来显示仪表os、右边来显示ivi。
我的ivi是基于Android os来发的,所以分屏方案也是基于Android的应用分屏来实现的。
但是原来Android的应用分屏不能完全满足我们的要求:
1、我们要一启动左边显示仪表os、右边显示ivi。
2、左右分屏大小要固定,不能拖拽,不支持用户进入recent view画面,选择recent app。
3、原生的Android不支持home stack分屏显示,我们需要在显示home的时候强制修改home的大小。
4、不支持应用分屏的app,要全屏显示,退出全屏app之后又要回到左边显示仪表os、右边显示ivi home。
5、config配置是否支持分屏显示
基本就是要解决上面几个问题。
首先 Android 提供了分屏的api, 调用ActivityManagerService中setTaskWindowingModeSplitScreenPrimary() 、resizeStack() 来实现分屏。
// mLeftScreenTask 就是正在运行的要分屏的apk,SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT是分屏模式为分屏apk在上面或者右边,mSplitLeftRect是要分屏的应用占全屏大小
mAm.setTaskWindowingModeSplitScreenPrimary(mLeftScreenTask.id,
SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT,
true, true, mSplitLeftRect, false);
// 调整分屏apk的占平大小。
mAm.resizeStack(mLeftScreenTask.stackId, mSplitLeftRect,
false, false, false, -1);
其实我们左边显示的是任意的一个apk,只是起到占位的作用,后面会通过视频流的形式将仪表的os投影过来。
这块就注释掉Android原生的一些功能。
frameworks/base/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
// 显示 recent app的地方
public void showRecents(boolean triggeredFromAltTab, boolean draggingInRecents,
boolean animate, int growTarget) {
/** case : aiways No need to display the recent app to give users a choice,
So comment out the code
final SystemServicesProxy ssp = Recents.getSystemServices();
final MutableBoolean isHomeStackVisible = new MutableBoolean(true);
final boolean isRecentsVisible = Recents.getSystemServices().isRecentsActivityVisible(
isHomeStackVisible);
final boolean fromHome = isHomeStackVisible.value;
final boolean launchedWhileDockingTask =
Recents.getSystemServices().getSplitScreenPrimaryStack() != null;
mTriggeredFromAltTab = triggeredFromAltTab;
mDraggingInRecents = draggingInRecents;
mLaunchedWhileDocking = launchedWhileDockingTask;
if (mFastAltTabTrigger.isAsleep()) {
// Fast alt-tab duration has elapsed, fall through to showing Recents and reset
mFastAltTabTrigger.stopDozing();
} else if (mFastAltTabTrigger.isDozing()) {
// Fast alt-tab duration has not elapsed. If this is triggered by a different
// showRecents() call, then ignore that call for now.
// TODO: We can not handle quick tabs that happen between the initial showRecents() call
// that started the activity and the activity starting up. The severity of this
// is inversely proportional to the FAST_ALT_TAB_DELAY_MS duration though.
if (!triggeredFromAltTab) {
return;
}
mFastAltTabTrigger.stopDozing();
} else if (triggeredFromAltTab) {
// The fast alt-tab detector is not yet running, so start the trigger and wait for the
// hideRecents() call, or for the fast alt-tab duration to elapse
mFastAltTabTrigger.startDozing();
return;
}
try {
// Check if the top task is in the home stack, and start the recents activity
final boolean forceVisible = launchedWhileDockingTask || draggingInRecents;
if (forceVisible || !isRecentsVisible) {
ActivityManager.RunningTaskInfo runningTask =
ActivityManagerWrapper.getInstance().getRunningTask();
startRecentsActivityAndDismissKeyguardIfNeeded(runningTask,
isHomeStackVisible.value || fromHome, animate, growTarget);
}
} catch (ActivityNotFoundException e) {
Log.e(TAG, "Failed to launch RecentsActivity", e);
}*/
}
frameworks/base/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
// 这个也是第一次进入分屏要显示empty view,我们不需要,就注释掉了
public void showEmptyView(int msgResId) {
/** case : aiways No need to display the empty view to give users ,
So comment out the code
mTaskStackView.setVisibility(View.INVISIBLE);
mEmptyView.setText(msgResId);
mEmptyView.setVisibility(View.VISIBLE);
mEmptyView.bringToFront();
mStackActionButton.bringToFront(); */
}
frameworks/base/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java
// 这个里面是监听,在home界面的时候会缩进分屏app的大小,我们这里要求分屏大小一直不变,就注释掉了
@Override
public void onDockedStackMinimizedChanged(boolean minimized, long animDuration,
boolean isHomeStackResizable) throws RemoteException {
/** case : When the android native function displays the home app,
the split screen screen needs to be indented, aiways don't need this function.
mHomeStackResizable = isHomeStackResizable;
updateMinimizedDockedStack(minimized, animDuration, isHomeStackResizable); */
}
frameworks/base/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
@Override
// 这个是提供给用户自由调整分屏大小的地方,我们不需要也注释掉了
public boolean onTouch(View v, MotionEvent event) {
convertToScreenCoordinates(event);
final int action = event.getAction() & MotionEvent.ACTION_MASK;
// switch (action) {
// case MotionEvent.ACTION_DOWN:
// Log.d("DividerView", "ACTION_DOWN");
// mVelocityTracker = VelocityTracker.obtain();
// mVelocityTracker.addMovement(event);
// mStartX = (int) event.getX();
// mStartY = (int) event.getY();
// boolean result = startDragging(true /* animate */, true /* touching */);
// if (!result) {
// // Weren't able to start dragging successfully, so cancel it again.
// stopDragging();
// }
// mStartPosition = getCurrentPosition();
// mMoving = false;
// return result;
// case MotionEvent.ACTION_MOVE:
// Log.d("DividerView", "ACTION_MOVE");
// mVelocityTracker.addMovement(event);
// int x = (int) event.getX();
// int y = (int) event.getY();
// boolean exceededTouchSlop =
// isHorizontalDivision() && Math.abs(y - mStartY) > mTouchSlop
// || (!isHorizontalDivision() && Math.abs(x - mStartX) > mTouchSlop);
// if (!mMoving && exceededTouchSlop) {
// mStartX = x;
// mStartY = y;
// mMoving = true;
// }
// if (mMoving && mDockSide != WindowManager.DOCKED_INVALID) {
// SnapTarget snapTarget = getSnapAlgorithm().calculateSnapTarget(
// mStartPosition, 0 /* velocity */, false /* hardDismiss */);
// resizeStackDelayed(calculatePosition(x, y), mStartPosition, snapTarget);
// }
// break;
// case MotionEvent.ACTION_UP:
// Log.d("DividerView", "ACTION_UP");
// case MotionEvent.ACTION_CANCEL:
// Log.d("DividerView", "ACTION_CANCEL");
// mVelocityTracker.addMovement(event);
// x = (int) event.getRawX();
// y = (int) event.getRawY();
// mVelocityTracker.computeCurrentVelocity(1000);
// int position = calculatePosition(x, y);
// stopDragging(position, isHorizontalDivision() ? mVelocityTracker.getYVelocity()
// : mVelocityTracker.getXVelocity(), false /* avoidDismissStart */,
// true /* log */);
// mMoving = false;
// break;
// }
return true;
}
这个问题是home stack是一个独立的apk stack,和其他的apk不一样,Android原生的是不支持home 分屏的,所以我们这边在phoneWindowManager里面强制修改了home的显示大小
frameworks/base/services/core/java/com/android/server/policy/PhoneWindowManager.java
private void layoutScreenDecorWindows(DisplayFrames displayFrames, Rect pf, Rect df, Rect dcf) {
.........
// 判断如果是home apk就强制显示为HOME_WIDTH
if(win.getAttrs().packageName.equals(HOME_PACKAGE_NAME))
{
Slog.v(TAG,"laucher,displayFrames.mDisplayCutout:"+displayFrames.mDisplayCutout);
// dcf.right = HOME_WIDTH;
// of.right = HOME_WIDTH;
// df.right = HOME_WIDTH;
// pf.right = HOME_WIDTH;
// cf.right = HOME_WIDTH;
// vf.right = HOME_WIDTH;
dcf.left = HOME_WIDTH;
of.left = HOME_WIDTH;
df.left = HOME_WIDTH;
pf.left = HOME_WIDTH;
cf.left = HOME_WIDTH;
vf.left = HOME_WIDTH;
}
win.computeFrameLw(pf, df, of, cf, vf, dcf, sf, osf, displayFrames.mDisplayCutout,
parentFrameWasClippedByDisplayCutout);
.......
}
每一个应用的androidmanifest.xml 中可通过android:resizeableActivity=“true” 或 "false"来配置,是否支持分屏。
而我们可以通过监听 onActivityDismissingDockedStack来关闭分屏模式。
private TaskStackListener mTaskStackListener =
new TaskStackListener() {
@Override
public void onTaskStackChanged() {
ActivityManager.StackInfo topStack = getTopStackInfo();
Log.d(TAG, "onTaskStackChanged topStack.stackId: " + topStack.stackId);
Log.d(TAG, "onTaskStackChanged topStack name: " + topStack.topActivity.toString());
if (topStack != null) {
mCurrentTask = getTaskInfoByActivityName(topStack.topActivity);
if (topStack.topActivity.getPackageName().equals(LEFT_SCREEN_PKG_NAME)) {
mLeftScreenTask = mCurrentTask;
}
if (mCurrentTask.resizeMode != ActivityInfo.RESIZE_MODE_UNRESIZEABLE) {
showSplitScreen();
}
}
//first to start left screen apk;
if (mLeftScreenTask == null) {
startLeftScreenActivty();
}
}
@Override
public void onTaskCreated(int taskId, ComponentName componentName) {
Log.d(TAG, "onTaskCreated taskId: " + taskId);
Log.d(TAG, "onTaskCreated componentName: " + componentName.toString());
mCurrentTask = getTaskInfoByTaskId(taskId);
Log.d(TAG, "onTaskCreated getPackageName: " + mCurrentTask.topActivity.getPackageName());
if (mCurrentTask.topActivity.getPackageName().equals(LEFT_SCREEN_PKG_NAME)) {
mLeftScreenTask = mCurrentTask;
showSplitScreen();
}
}
@Override
public void onTaskMovedToFront(int taskId) {
Log.d(TAG, "onTaskMovedToFront taskId: " + taskId);
}
public void onActivityForcedResizable(String packageName, int taskId,
int reason) {
Log.d(TAG, "onActivityForcedResizable -> packageName:" + packageName);
}
@Override
public void onActivityDismissingDockedStack() {
Log.d(TAG, "onActivityDismissingDockedStack");
hideSplitScreen(); // 关闭分屏
}
@Override
public void onActivityLaunchOnSecondaryDisplayFailed() {
}
};
frameworks/base/core/res/res/values/config.xml 里面有一个配置项
<!-- True if the device supports at least one form of multi-window.
E.g. freeform, split-screen, picture-in-picture. -->
<bool name="config_supportsMultiWindow">true</bool>
<!-- True if the device supports split screen as a form of multi-window. -->
<bool name="config_supportsSplitScreenMultiWindow">true</bool>
<!-- True if the device supports running activities on secondary displays. -->
<bool name="config_supportsMultiDisplay">true</bool>
原生的是支持分屏的,但是我们用别的设备时,在device下对 config_supportsSplitScreenMultiWindow做了overlay设置成了false。搞的我们去跟踪源码才找到这个配置项被改为了false,导致不能分屏
<bool name="config_supportsSplitScreenMultiWindow">false</bool>