RK3288_Android 7.1 雙屏異顯流程
異顯功能使能
—— 即控制 異顯功能 能否 使用,以及異顯功能是否 啓用
控制代碼位置:
packages/apps/Settings/src/com/android/settings/HdmiSettings.java
-
屬性 1:
android.provider.Settings.DUAL_SCREEN_MODE
異顯功能全局開關 —— 0 表示 關閉;1 表示 打開 -
屬性 2:
android.provider.Settings.DUAL_SCREEN_ICON_USED
異顯功能狀態 —— 0 表示 同顯;1 表示 異顯
以上部分並不是雙屏異顯的關鍵
觸發異顯
控制代碼位置:
framework/base/core/java/com/android/internal/policy/DecorView.java
- 關鍵方法 1:
initDualScreenConfig()
進行異顯觸發配置的初始化
方法 1 主要流程:
-
String dualScreenKeyCodes = SystemProperties.get("sys.dual_screen.keycodes");
獲取 sys.dual_screen.keycodes 屬性值(該屬性值標記 異顯觸發鍵)
該屬性值默認是 24,25,分別對應 音量鍵 + 和 音量鍵 - -
mDualScreenCodeInterval = SystemProperties.getLong("sys.dual_screen.interval", 2000L);
獲取 sys.dual_screen_interval 屬性值(該屬性標記 觸發時間間隔)
- 關鍵方法 2:
trigerDualScreen()
觸發異顯
方法 2 關鍵步驟:
getRootWindowSession().setOnlyShowInExtendDisplay(getWindow(),-1);
通過 Session 調用 WMS(WindowManagerService) 的 setOnlyShowInExtendDisplay 方法,進入異顯流程
- 關鍵方法 3:
trigerSyncScreen()
觸發同顯
(暫時不展開)
異顯流程
主要就是上一環節中調用的 WMS 的 setOnlyShowInExtendDisplay
控制代碼位置:
frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java
setOnlyShowInExtendDisplay() 主要流程:
1
if (mDisplayContents == null || mDisplayContents.size() <= 1) {
return;
}
- 如果展示內容爲 null 或小於等於 1 時,退出函數
- 通常爲副屏不存在時纔會發生上述情況(即只有一個屏)
2
final int displayCount = mDisplayContents.size();
DisplayContent defaultContent = getDefaultDisplayContentLocked();
int displayId = 0;
DisplayContent secondDisplayContent = null;
for (int i = 0; i < displayCount;i++) {
final DisplayContent content = mDisplayContents.valueAt(i);
if (content != defaultContent) {
secondDisplayContent = content;
displayId = secondDisplayContent.getDisplayId();
break;
}
}
3
if(secondDisplayContent == null){
return;
}
if(!okToDisplay()){
return;
}
WindowState current = windowForClientLocked(session, client, false);
if(isHomeWindow(current)){
return;
}
AppWindowToken wtoken = current.mAppToken;
if(wtoken == null){
return;
}
- 如果存在副屏需要顯示的內容爲 null,副屏未就緒(包括副屏未開啓、處於 Frozen 狀態等)或主屏窗口處於 Home 等情況時,return 退出方法,結束異顯流程
4
int groupId = wtoken.mTask.mTaskId;
mH.sendMessage(mH.obtainMessage(H.DO_TASK_DISPLAY_CHANGED, groupId, -1));
- 獲取主屏當前應用的 taskId
- 發送 “DO_TASK_DISPLAY_CHANGED” Message
- 隨後 handlerMessage 方法接收到 Message 之後,調用 moveTransitionToSecondDisplay() 方法
moveTransitionToSecondDisplay() 主要流程:
1
if (!isShowDualScreen()) {
mSecondTaskIds.clear();
} else {
if (mSecondDisplayTaskId != -1) {
return;
}
}
- if 語句判斷是否處於異顯狀態
- 當不處於異顯狀態時,先清空副屏的 mSecondTaskIds 列表,準備接收副屏需要顯示的內容
- 當處於異顯狀態時,執行 else 語句,若判斷副屏的 taskId 不等於 -1,即任務棧正常,則 return 退出方法,保持異顯的狀態
2
Settings.System.putInt(mContext.getContentResolver(), Settings.DUAL_SCREEN_ICON_USED, 0);
- 先將 DUAL_SCREEN_ICON_USED 屬性值設置爲同顯狀態
3
List<Integer> allTaskIds = null;
try {
allTaskIds = mActivityManager.getAllTaskIds();
} catch (Exception e) {
if(DEBUG) Log.i(TAG_DUALSCREEN, "WindowManagerService->getAllTaskIds->e:" + e);
}
if (allTaskIds == null || allTaskIds.size() < 2)
return;
- 通過 ActivityManager 獲取所有任務的 taskId
- if 語句判斷 allTaskIds 爲 null 或者 allTaskIds 任務 Id 數小於 2 時,return 退出方法
4
if(mDisplayContents == null || mDisplayContents.size() <= 1){
return;
}
final int displayCount = mDisplayContents.size();
DisplayContent defaultContent = getDefaultDisplayContentLocked();
int displayId = 0;
DisplayContent secondDisplayContent = null;
for(int i = 0; i < displayCount;i++){
final DisplayContent content = mDisplayContents.valueAt(i);
if(content != defaultContent){
secondDisplayContent = content;
displayId = secondDisplayContent.getDisplayId();
if(DEBUG) Log.d(TAG_DUALSCREEN, "moveTransitionToSecondDisplay->secondDisplayId:" + displayId);
break;
}
}
if(secondDisplayContent == null){
return;
}
if(!okToDisplay()){
return;
}
- 這部分跟 setOnlyShowInExtendDisplay() 方法的內容類似
5
SurfaceControl.openTransaction();
WindowState win = null;
WindowList defaultWindows = defaultContent.getWindowList();
- openTransaction() 和 endTransaction() 之間是對 Surface 的操作
- 獲取主屏的 windows 列表(主屏所有的 windows)
6
int topTaskId = -1;
if (allTaskIds != null && allTaskIds.size() > 0) {
topTaskId = allTaskIds.get(0);
mSecondTaskIds.add(topTaskId);
}
- 獲取頂層應用的 taskId,添加到 mSecondTaskIds 列表中
7
for(int i= defaultWindows.size()-1;i>=0;i--){
win = defaultWindows.get(i);
if(win == null){
continue;
}
if (win.mAppToken == null){
continue;
}
boolean isSurface=false;
int windowTaskId=-1;
if(win.taskId==-1&&win.mAttachedWindow!=null && win.mAttachedWindow.mAppToken.mTask.mTaskId==topTaskId){
isSurface=true;
Log.i("DualScreenIs","isSurface = "+isSurface);
} else if(win.mAppToken.mTask == null){
continue;
}else{
windowTaskId = win.mAppToken.mTask.mTaskId;
}
if(windowTaskId == topTaskId||isSurface){
if(DEBUG) Log.i(TAG_DUALSCREEN, "moveTransitionToSecondDisplay->add win:" + win);
defaultWindows.remove(win);
mTempWindowList.add(win);
win.mDisplayContent = secondDisplayContent;
if(DEBUG) Log.i(TAG_DUALSCREEN,"win.mDisplayContent = "+win.mDisplayContent+ " secondDisplayContent = "+secondDisplayContent);
if(win.mWinAnimator != null){
int layerStack = secondDisplayContent.getDisplay().getLayerStack();
if(win.mWinAnimator.mSurfaceController!= null){
win.mWinAnimator.mSurfaceController.mSurfaceControl.setLayerStack(layerStack);
}
}
secondDisplayAddList.add(0,win);
mSecondTopPackageName = win.getOwningPackage();
}
}
- 該 for 循環會對主屏的所有 windows 都遍歷一遍
- 首先獲取 windows,若 win 爲 null,或者 mAppToken 爲 null,則跳出該次循環,直接進入下一次循環
- 判斷 win 是否處於頂層,如果是,將標誌位 isSurface 賦值爲 true
- 若 win 不是處於頂層,且 win.mAppToken.mTask 是空,則跳出該次循環,直接進入下一次循環
- 如果不是上述兩種情況,則將 windowTaskId 賦值爲 win 的 mTaskId
- 判斷當前 win 是否處於頂層,如果是,則從主屏的 WindowList 中移除該窗口,並將該窗口添加進一個臨時的 WindowList。設置該窗口的 mDisplayContent 爲副屏的 secondDisplayContent。設置該窗口的 win.layerStack 爲副屏的 display.layerStack。然後將該 win 添加進副屏的 windowlist 中,並獲取到該 win 對應的包名,保存爲副屏的頂層包名 mSecondTopPackageName
8
DisplayContent displayContent = getDefaultDisplayContentLocked();
if (displayContent != null) {
final DisplayInfo displayInfo = displayContent.getDisplayInfo();
int rotation = 0;
if(displayInfo.logicalWidth > displayInfo.logicalHeight) {
rotation = Surface.ROTATION_90;
} else {
rotation = Surface.ROTATION_0;
}
Settings.System.putInt(mContext.getContentResolver(), Settings.System.USER_ROTATION, rotation);
}
- 這部分主要是根據設備的長寬比,確定屏幕的方向
9
secondDisplayWindows.clear();
secondDisplayWindows.addAll(secondDisplayAddList);
- 清空副屏的 secondDisplayWindows.clear()
- 將前面第 7 步 for 循環中,secondDisplayAddList 保存的 windows,全部添加到 secondDisplayWindows(這個纔是正式的副屏的 WindowList)
10
for (int i = 0; i < displayCount; i++) {
final DisplayContent content = mDisplayContents.valueAt(i);
mLayersController.assignLayersLocked(content.getWindowList());
content.layoutNeeded = true;
}
11
mSecondDisplayTaskId = topTaskId;
misMovingToSecond = true;
Settings.System.putInt(mContext.getContentResolver(), Settings.DUAL_SCREEN_ICON_USED, 1);
- 將副屏的顯示 taskId 設置爲頂層應用的 topTaskId
- 將表示 window 正在移動到副屏的標誌位 misMovingToSecond 設置爲 true
- 設置屬性值 DUAL_SCREEN_ICON_USED 爲 1,表示處於異顯狀態
12
curMoveTaskId = getLaunchTaskId();
if (curMoveTaskId == -1) {
curMoveTaskId = allTaskIds.get(1);
}
if (DEBUG) Log.i(TAG_DUALSCREEN, "WindowManagerService->curMoveTaskId:" + curMoveTaskId );
switchFocusWindow(curMoveTaskId);
- 獲取當前應用的 taskId,如果該 taskId 等於 -1,獲取第二個 taskId,賦值給 curMoveTaskId
- 將 Focus 的 window 設置爲 curMoveTaskId 所對應的 window
13
updateFocusedWindowLocked(UPDATE_FOCUS_WILL_PLACE_SURFACES, false);
mAppTransition.setReady();
mWindowPlacerLocked.performSurfacePlacement();
- 調用 performSurfacePlacement(),刷新每個 display 及窗口顯示的大小和位置
14
currentTimeout = Settings.System.getLong(mContext.getContentResolver(),Settings.System.SCREEN_OFF_TIMEOUT,3000);
Settings.System.putInt(mContext.getContentResolver(), Settings.System.SCREEN_OFF_TIMEOUT,2147483647);
Binder.restoreCallingIdentity(origId);
- 獲取待機時間
- 設置待機時間