hi,粉絲朋友們!
今天開始就進入正式的自由窗口的相關(guān)的內(nèi)容講解,blog只是一些知識點的記錄,更多的干貨,還請看馬哥的視頻,及視頻配套資料。
b站免費視頻教程講解:
https://www.bilibili.com/video/BV1wj411o7A9/
aosp默認并沒有公開自由窗口模式,如果需要體驗自由窗口模式必須要用如下命令進行開啟
adb shell settings put global enable_freeform_support 1
adb shell settings put global force_resizable_activities 1
輸入完成后,可以在多任務(wù)的menu中發(fā)現(xiàn)freeform:
點擊這個freeform按鈕即可以進入到自由窗口模式。
點擊進入Freeform的systemserver端執(zhí)行
packages/apps/Launcher3/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
class MultiWindowSystemShortcut extends SystemShortcut<BaseDraggingActivity> {
//省略
@Override
public void onClick(View view) {
Task.TaskKey taskKey = mTaskView.getTask().key;
final int taskId = taskKey.id;
//省略
ActivityOptions options = mFactory.makeLaunchOptions(mTarget);
if (options != null) {
options.setSplashScreenStyle(SplashScreen.SPLASH_SCREEN_STYLE_ICON);
}
if (options != null
&& ActivityManagerWrapper.getInstance().startActivityFromRecents(taskId,
options)) {
//省略
}
/**
* Starts a task from Recents synchronously.
*/
public boolean startActivityFromRecents(int taskId, ActivityOptions options) {
try {
Bundle optsBundle = options == null ? null : options.toBundle();
getService().startActivityFromRecents(taskId, optsBundle);
return true;
} catch (Exception e) {
return false;
}
}
這里點擊時候會觸發(fā)startActivityFromRecents,會跨進程調(diào)到ActivityTaskManagerService
frameworks/base/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@Override
public final int startActivityFromRecents(int taskId, Bundle bOptions) {
mAmInternal.enforceCallingPermission(START_TASKS_FROM_RECENTS,
"startActivityFromRecents()");
final int callingPid = Binder.getCallingPid();
final int callingUid = Binder.getCallingUid();
final SafeActivityOptions safeOptions = SafeActivityOptions.fromBundle(bOptions);
final long origId = Binder.clearCallingIdentity();
try {
return mTaskSupervisor.startActivityFromRecents(callingPid, callingUid, taskId,
safeOptions);
} finally {
Binder.restoreCallingIdentity(origId);
}
}
接下來重點看看TaskSupervisor.startActivityFromRecents
/**
* Start the given task from the recent tasks. Do not hold WM global lock when calling this
* method to avoid potential deadlock or permission deny by UriGrantsManager when resolving
* activity (see {@link ActivityStarter.Request#resolveActivity} and
* {@link com.android.server.am.ContentProviderHelper#checkContentProviderUriPermission}).
*
* @return The result code of starter.
*/
int startActivityFromRecents(int callingPid, int callingUid, int taskId,
SafeActivityOptions options) {
synchronized (mService.mGlobalLock) {
int activityType = ACTIVITY_TYPE_UNDEFINED;
if (activityOptions != null) {
activityType = activityOptions.getLaunchActivityType();
final int windowingMode = activityOptions.getLaunchWindowingMode();
//省略部分
try {
//根據(jù)taskId獲取Task對象
task = mRootWindowContainer.anyTaskForId(taskId,
MATCH_ATTACHED_TASK_OR_RECENT_TASKS_AND_RESTORE, activityOptions, ON_TOP);
//省略部分
// If the user must confirm credentials (e.g. when first launching a work
// app and the Work Challenge is present) let startActivityInPackage handle
// the intercepting.
if (!mService.mAmInternal.shouldConfirmCredentials(task.mUserId)
&& task.getRootActivity() != null) {
//省略部分
try {
//把這個Freeform的Task移到最頂部前端
mService.moveTaskToFrontLocked(null /* appThread */,
null /* callingPackage */, task.mTaskId, 0, options);
// Apply options to prevent pendingOptions be taken when scheduling
// activity lifecycle transaction to make sure the override pending app
// transition will be applied immediately.
targetActivity.applyOptionsAnimation();
} finally {
mActivityMetricsLogger.notifyActivityLaunched(launchingState,
START_TASK_TO_FRONT, false /* newActivityCreated */,
targetActivity, activityOptions);
}
mService.getActivityStartController().postStartActivityProcessingForLastStarter(
task.getTopNonFinishingActivity(), ActivityManager.START_TASK_TO_FRONT,
task.getRootTask());
// As it doesn't go to ActivityStarter.executeRequest() path, we need to resume
// app switching here also.
mService.resumeAppSwitches();
return ActivityManager.START_TASK_TO_FRONT;
}
//省略
}
這里其實表面看還是比較簡單的,主要就是一個根據(jù)taskId獲取Task對象,然后再調(diào)用ActivityTaskManagerService的moveTaskToFrontLocked方法來把Task移到最前端
void moveTaskToFrontLocked(@Nullable IApplicationThread appThread,
@Nullable String callingPackage, int taskId, int flags, SafeActivityOptions options) {
//省略
final ActivityStarter starter = getActivityStartController().obtainStarter(
null /* intent */, "moveTaskToFront");
//省略
try {
//這里又進行了一次id到Task的獲取
final Task task = mRootWindowContainer.anyTaskForId(taskId);
//省略
ActivityOptions realOptions = options != null
? options.getOptions(mTaskSupervisor)
: null;
mTaskSupervisor.findTaskToMoveToFront(task, flags, realOptions, "moveTaskToFront",
false /* forceNonResizable */);
//省略
}
這里最重要的方法又變成了findTaskToMoveToFront方法
/** This doesn't just find a task, it also moves the task to front. */
void findTaskToMoveToFront(Task task, int flags, ActivityOptions options, String reason,
boolean forceNonResizeable) {
Task currentRootTask = task.getRootTask();
//省略
if (task.isResizeable() && canUseActivityOptionsLaunchBounds(options)) {
final Rect bounds = options.getLaunchBounds();
task.setBounds(bounds);// 直接給task設(shè)置bounds
Task targetRootTask =
mRootWindowContainer.getOrCreateRootTask(null, options, task, ON_TOP);
//省略
//這里判斷一般針對pip那種
if (targetRootTask.shouldResizeRootTaskWithLaunchBounds()) {
targetRootTask.resize(bounds, !PRESERVE_WINDOWS, !DEFER_RESUME);
} else {
// WM resizeTask must be done after the task is moved to the correct stack,
// because Task's setBounds() also updates dim layer's bounds, but that has
// dependency on the root task.
//調(diào)用task的resize
task.resize(false /* relayout */, false /* forced */);
}
}
//省略
final ActivityRecord r = task.getTopNonFinishingActivity();
//這里進行最關(guān)鍵的moveTaskToFront,會把task真正移到前臺
currentRootTask.moveTaskToFront(task, false /* noAnimation */, options,
r == null ? null : r.appTimeTracker, reason);
//省略
} finally {
mUserLeaving = false;
}
}
再看moveTaskToFront
final void moveTaskToFront(Task tr, boolean noAnimation, ActivityOptions options,
AppTimeTracker timeTracker, boolean deferResume, String reason) {
//省略
// Set focus to the top running activity of this task and move all its parents to top.
//調(diào)用ActivityRecord方法moveFocusableActivityToTop
top.moveFocusableActivityToTop(reason);
//省略
if (!deferResume) {
mRootWindowContainer.resumeFocusedTasksTopActivities();
}
} finally {
mDisplayContent.continueUpdateImeTarget();
}
}
//frameworks/base/services/core/java/com/android/server/wm/ActivityRecord.java
boolean moveFocusableActivityToTop(String reason) {
//省略
ProtoLog.d(WM_DEBUG_FOCUS, "moveFocusableActivityToTop: activity=%s", this);
//這里又調(diào)用到moveToFront
rootTask.moveToFront(reason, task);
// Report top activity change to tracking services and WM
if (mRootWindowContainer.getTopResumedActivity() == this) {
//進行setResumedActivityUncheckLocked
mAtmService.setResumedActivityUncheckLocked(this, reason);
}
return true;
}
void moveToFront(String reason, Task task) {
//省略
//又調(diào)用到moveToFrontInner
moveToFrontInner(reason, task);
}
void moveToFrontInner(String reason, Task task) {
//省略
final TaskDisplayArea taskDisplayArea = getDisplayArea();
//省略
//這里把task放置到最top,即最前段
task.getParent().positionChildAt(POSITION_TOP, task, true /* includingParents */);
taskDisplayArea.updateLastFocusedRootTask(lastFocusedTask, reason);//進行focus更新
}
這里雖然層數(shù)多,但是目標還是比較簡單,那就是把FreeForm的Task放到最前端這里搞個堆棧,就不貼時序圖了:
moveToFrontInner:4688, Task (com.android.server.wm)
moveToFront:4662, Task (com.android.server.wm)
moveFocusableActivityToTop:3240, ActivityRecord (com.android.server.wm)
moveTaskToFront:5557, Task (com.android.server.wm)
moveTaskToFront:5511, Task (com.android.server.wm)
findTaskToMoveToFront:1474, ActivityTaskSupervisor (com.android.server.wm)
moveTaskToFrontLocked:2158, ActivityTaskManagerService (com.android.server.wm)
startActivityFromRecents:2575, ActivityTaskSupervisor (com.android.server.wm)
startActivityFromRecents:1747, ActivityTaskManagerService (com.android.server.wm)
onTransact:1182, IActivityTaskManager$Stub (android.app)
onTransact:5183, ActivityTaskManagerService (com.android.server.wm)
execTransactInternal:1280, Binder (android.os)
execTransact:1244, Binder (android.os)
同時解答幾個疑問點:
1、 task的windowmode在哪里進行設(shè)置的?
下面列出堆棧
getOrCreateRootTask:993, TaskDisplayArea (com.android.server.wm)
getOrCreateRootTask:1030, TaskDisplayArea (com.android.server.wm)
getOrCreateRootTask:2880, RootWindowContainer (com.android.server.wm)
getOrCreateRootTask:2771, RootWindowContainer (com.android.server.wm)
anyTaskForId:3278, RootWindowContainer (com.android.server.wm)
startActivityFromRecents:2546, ActivityTaskSupervisor (com.android.server.wm)
startActivityFromRecents:1747, ActivityTaskManagerService (com.android.server.wm)
onTransact:1182, IActivityTaskManager$Stub (android.app)
onTransact:5183, ActivityTaskManagerService (com.android.server.wm)
execTransactInternal:1280, Binder (android.os)
execTransact:1244, Binder (android.os)
2、為啥 freeform底部activity不會進行pasue?
frameworks/base/services/core/java/com/android/server/wm/TaskFragment.java
的getVisibility中
final int otherWindowingMode = other.getWindowingMode();
//桌面頂部的Activity相關(guān)的不是全屏,也不是WINDOWING_MODE_MULTI_WINDOW
if (otherWindowingMode == WINDOWING_MODE_FULLSCREEN) {
if (isTranslucent(other, starting)) {
// Can be visible behind a translucent fullscreen TaskFragment.
gotTranslucentFullscreen = true;
continue;
}
return TASK_FRAGMENT_VISIBILITY_INVISIBLE;
} else if (otherWindowingMode == WINDOWING_MODE_MULTI_WINDOW
&& other.matchParentBounds()) {
if (isTranslucent(other, starting)) {
// Can be visible behind a translucent TaskFragment.
gotTranslucentFullscreen = true;
continue;
}
// Multi-window TaskFragment that matches parent bounds would occlude other children
return TASK_FRAGMENT_VISIBILITY_INVISIBLE;
}
3、明明設(shè)置的bounds大小是150x150,為啥窗口變大了,是在哪里修改的?
作業(yè)。。下節(jié)課視頻公布
自由窗口模式的DecorCaptionView
DecorCaptionView 創(chuàng)建過程:
06-12 14:03:01.774 1495 1495 I lsm33 : createDecorCaptionView
06-12 14:03:01.774 1495 1495 I lsm33 : java.lang.Exception
06-12 14:03:01.774 1495 1495 I lsm33 : at com.android.internal.policy.DecorView.createDecorCaptionView(DecorView.java:2275)
06-12 14:03:01.774 1495 1495 I lsm33 : at com.android.internal.policy.DecorView.onResourcesLoaded(DecorView.java:2208)
06-12 14:03:01.774 1495 1495 I lsm33 : at com.android.internal.policy.PhoneWindow.generateLayout(PhoneWindow.java:2674)
06-12 14:03:01.774 1495 1495 I lsm33 : at com.android.internal.policy.PhoneWindow.installDecor(PhoneWindow.java:2737)
06-12 14:03:01.774 1495 1495 I lsm33 : at com.android.internal.policy.PhoneWindow.getDecorView(PhoneWindow.java:2144)
06-12 14:03:01.774 1495 1495 I lsm33 : at androidx.appcompat.app.AppCompatActivity.initViewTreeOwners(AppCompatActivity.java:219)
06-12 14:03:01.774 1495 1495 I lsm33 : at androidx.appcompat.app.AppCompatActivity.setContentView(AppCompatActivity.java:194)
06-12 14:03:01.774 1495 1495 I lsm33 : at com.android.messaging.ui.conversationlist.ConversationListActivity.onCreate(ConversationListActivity.java:35)
06-12 14:03:01.774 1495 1495 I lsm33 : at android.app.Activity.performCreate(Activity.java:8290)
06-12 14:03:01.774 1495 1495 I lsm33 : at android.app.Activity.performCreate(Activity.java:8269)
06-12 14:03:01.774 1495 1495 I lsm33 : at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1384)
06-12 14:03:01.774 1495 1495 I lsm33 : at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3657)
06-12 14:03:01.774 1495 1495 I lsm33 : at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3813)
06-12 14:03:01.774 1495 1495 I lsm33 : at android.app.ActivityThread.handleRelaunchActivityInner(ActivityThread.java:5791)
06-12 14:03:01.774 1495 1495 I lsm33 : at android.app.ActivityThread.handleRelaunchActivity(ActivityThread.java:5682)
06-12 14:03:01.774 1495 1495 I lsm33 : at android.app.servertransaction.ActivityRelaunchItem.execute(ActivityRelaunchItem.java:71)
06-12 14:03:01.774 1495 1495 I lsm33 : at android.app.servertransaction.ActivityTransactionItem.execute(ActivityTransactionItem.java:45)
06-12 14:03:01.774 1495 1495 I lsm33 : at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135)
06-12 14:03:01.774 1495 1495 I lsm33 : at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95)
06-12 14:03:01.774 1495 1495 I lsm33 : at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2308)
06-12 14:03:01.774 1495 1495 I lsm33 : at android.os.Handler.dispatchMessage(Handler.java:106)
06-12 14:03:01.774 1495 1495 I lsm33 : at android.os.Looper.loopOnce(Looper.java:201)
06-12 14:03:01.774 1495 1495 I lsm33 : at android.os.Looper.loop(Looper.java:288)
06-12 14:03:01.774 1495 1495 I lsm33 : at android.app.ActivityThread.main(ActivityThread.java:7897)
06-12 14:03:01.774 1495 1495 I lsm33 : at java.lang.reflect.Method.invoke(Native Method)
06-12 14:03:01.774 1495 1495 I lsm33 : at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548)
06-12 14:03:01.774 1495 1495 I lsm33 : at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:936)
06-12 14:03:01.775 1495 1495 I lsm33 : onResourcesLoaded mDecorCaptionView = com.android.internal.widget.DecorCaptionView{80346bf V.E...... ......ID 0,0-818,1631}
可以看出其實是relaunch時候就進行的創(chuàng)建,那么systemserver端是哪里觸發(fā)的relaunch操作呢?
06-26 15:31:31.242 556 2018 I lsm222 : relaunchActivityLocked ActivityRecord{c8b69f4 u0 com.android.messaging/.ui.conversationlist.ConversationListActivity} t45}
06-26 15:31:31.242 556 2018 I lsm222 : java.lang.Exception
06-26 15:31:31.242 556 2018 I lsm222 : at com.android.server.wm.ActivityRecord.relaunchActivityLocked(ActivityRecord.java:8970)
06-26 15:31:31.242 556 2018 I lsm222 : at com.android.server.wm.ActivityRecord.ensureActivityConfiguration(ActivityRecord.java:8856)
06-26 15:31:31.242 556 2018 I lsm222 : at com.android.server.wm.ActivityRecord.ensureActivityConfiguration(ActivityRecord.java:8700)
06-26 15:31:31.242 556 2018 I lsm222 : at com.android.server.wm.ActivityTaskManagerService.ensureConfigAndVisibilityAfterUpdate(ActivityTaskManagerService.java:4914)
06-26 15:31:31.242 556 2018 I lsm222 : at com.android.server.wm.DisplayContent.updateDisplayOverrideConfigurationLocked(DisplayContent.java:5993)
06-26 15:31:31.242 556 2018 I lsm222 : at com.android.server.wm.RootWindowContainer.ensureVisibilityAndConfig(RootWindowContainer.java:1769)
06-26 15:31:31.242 556 2018 I lsm222 : at com.android.server.wm.TaskFragment.resumeTopActivity(TaskFragment.java:1379)
06-26 15:31:31.242 556 2018 I lsm222 : at com.android.server.wm.Task.resumeTopActivityInnerLocked(Task.java:5003)
06-26 15:31:31.242 556 2018 I lsm222 : at com.android.server.wm.Task.resumeTopActivityUncheckedLocked(Task.java:4938)
06-26 15:31:31.242 556 2018 I lsm222 : at com.android.server.wm.Task.resumeTopActivityUncheckedLocked(Task.java:4984)
06-26 15:31:31.242 556 2018 I lsm222 : at com.android.server.wm.ActivityRecord.makeActiveIfNeeded(ActivityRecord.java:5765)
06-26 15:31:31.242 556 2018 I lsm222 : at com.android.server.wm.RootWindowContainer.lambda$resumeFocusedTasksTopActivities$18(RootWindowContainer.java:2288)
06-26 15:31:31.242 556 2018 I lsm222 : at com.android.server.wm.RootWindowContainer$$ExternalSyntheticLambda18.accept(Unknown Source:13)
06-26 15:31:31.242 556 2018 I lsm222 : at com.android.server.wm.Task.forAllRootTasks(Task.java:3172)
06-26 15:31:31.242 556 2018 I lsm222 : at com.android.server.wm.WindowContainer.forAllRootTasks(WindowContainer.java:2014)
06-26 15:31:31.242 556 2018 I lsm222 : at com.android.server.wm.WindowContainer.forAllRootTasks(WindowContainer.java:2014)
06-26 15:31:31.242 556 2018 I lsm222 : at com.android.server.wm.WindowContainer.forAllRootTasks(WindowContainer.java:2014)
06-26 15:31:31.242 556 2018 I lsm222 : at com.android.server.wm.WindowContainer.forAllRootTasks(WindowContainer.java:2014)
06-26 15:31:31.242 556 2018 I lsm222 : at com.android.server.wm.WindowContainer.forAllRootTasks(WindowContainer.java:2014)
06-26 15:31:31.242 556 2018 I lsm222 : at com.android.server.wm.WindowContainer.forAllRootTasks(WindowContainer.java:2014)
06-26 15:31:31.242 556 2018 I lsm222 : at com.android.server.wm.WindowContainer.forAllRootTasks(WindowContainer.java:2007)
06-26 15:31:31.242 556 2018 I lsm222 : at com.android.server.wm.RootWindowContainer.resumeFocusedTasksTopActivities(RootWindowContainer.java:2268)
06-26 15:31:31.242 556 2018 I lsm222 : at com.android.server.wm.RootWindowContainer.resumeFocusedTasksTopActivities(RootWindowContainer.java:2246)
06-26 15:31:31.242 556 2018 I lsm222 : at com.android.server.wm.RootWindowContainer.resumeFocusedTasksTopActivities(RootWindowContainer.java:2241)
06-26 15:31:31.242 556 2018 I lsm222 : at com.android.server.wm.Task.moveTaskToFront(Task.java:5577)
06-26 15:31:31.242 556 2018 I lsm222 : at com.android.server.wm.Task.moveTaskToFront(Task.java:5511)
06-26 15:31:31.242 556 2018 I lsm222 : at com.android.server.wm.ActivityTaskSupervisor.findTaskToMoveToFront(ActivityTaskSupervisor.java:1474)
06-26 15:31:31.242 556 2018 I lsm222 : at com.android.server.wm.ActivityTaskManagerService.moveTaskToFrontLocked(ActivityTaskManagerService.java:2158)
06-26 15:31:31.242 556 2018 I lsm222 : at com.android.server.wm.ActivityTaskSupervisor.startActivityFromRecents(ActivityTaskSupervisor.java:2575)
06-26 15:31:31.242 556 2018 I lsm222 : at com.android.server.wm.ActivityTaskManagerService.startActivityFromRecents(ActivityTaskManagerService.java:1747)
06-26 15:31:31.242 556 2018 I lsm222 : at android.app.IActivityTaskManager$Stub.onTransact(IActivityTaskManager.java:1182)
06-26 15:31:31.242 556 2018 I lsm222 : at com.android.server.wm.ActivityTaskManagerService.onTransact(ActivityTaskManagerService.java:5183)
06-26 15:31:31.242 556 2018 I lsm222 : at android.os.Binder.execTransactInternal(Binder.java:1280)
06-26 15:31:31.242 556 2018 I lsm222 : at android.os.Binder.execTransact(Binder.java:1244)
對應(yīng)兩個按鈕的處理:
@Override
public boolean onSingleTapUp(MotionEvent e) {
if (mClickTarget == mMaximize) {
toggleFreeformWindowingMode();//自由模式變成全屏
} else if (mClickTarget == mClose) {
//直接退出自由模式,其實本質(zhì)就是Activity退出
mOwner.dispatchOnWindowDismissed(
true /*finishTask*/, false /*suppressWindowTransition*/);
}
return true;
}
//最后toggleFreeformWindowingMode會跨進程調(diào)用到atms
@Override
public void toggleFreeformWindowingMode() {
ActivityClient.getInstance().toggleFreeformWindowingMode(mToken);
}
// frameworks/base/services/core/java/com/android/server/wm/ActivityClientController.java
@Override
public void toggleFreeformWindowingMode(IBinder token) {
final long ident = Binder.clearCallingIdentity();
try {
synchronized (mGlobalLock) {
//省略
if (rootTask.inFreeformWindowingMode()) {
//把windowmode設(shè)置成fullscreen
rootTask.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
rootTask.setBounds(null);
}
//省略
} finally {
Binder.restoreCallingIdentity(ident);
}
}
freeform坐標矯正相關(guān):
桌面初始化傳遞坐標區(qū)域:
packages/apps/Launcher3/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
@Override
protected ActivityOptions makeLaunchOptions(Activity activity) {
ActivityOptions activityOptions = ActivityOptionsCompat.makeFreeformOptions();
// Arbitrary bounds only because freeform is in dev mode right now
Rect r = new Rect(50, 50, 200, 200);//這里給定了一個初始的坐標區(qū)域
activityOptions.setLaunchBounds(r);
return activityOptions;
}
剛開始給定的區(qū)域大小可以看得出是比較小的,才150x150,所以明顯實際上要大于這個150,其實這個坐標還會在systemserver中進行矯正
systemserver中task對坐標區(qū)域進行矯正:
在進行對task的configration進行設(shè)置時候,其實Task是需要對config進行再一次的檢測校驗才可以,即不是你想設(shè)置task的任何configration就可以設(shè)置什么configration
void resolveOverrideConfiguration(Configuration newParentConfig) {
mTmpBounds.set(getResolvedOverrideConfiguration().windowConfiguration.getBounds());
super.resolveOverrideConfiguration(newParentConfig);
int windowingMode =
getResolvedOverrideConfiguration().windowConfiguration.getWindowingMode();
final int parentWindowingMode = newParentConfig.windowConfiguration.getWindowingMode();
// Resolve override windowing mode to fullscreen for home task (even on freeform
// display), or split-screen if in split-screen mode.
if (getActivityType() == ACTIVITY_TYPE_HOME && windowingMode == WINDOWING_MODE_UNDEFINED) {
windowingMode = WINDOWING_MODE_FULLSCREEN;
getResolvedOverrideConfiguration().windowConfiguration.setWindowingMode(windowingMode);
}
// Do not allow tasks not support multi window to be in a multi-window mode, unless it is in
// pinned windowing mode.
if (!supportsMultiWindow()) {
final int candidateWindowingMode =
windowingMode != WINDOWING_MODE_UNDEFINED ? windowingMode : parentWindowingMode;
if (WindowConfiguration.inMultiWindowMode(candidateWindowingMode)
&& candidateWindowingMode != WINDOWING_MODE_PINNED) {
getResolvedOverrideConfiguration().windowConfiguration.setWindowingMode(
WINDOWING_MODE_FULLSCREEN);
}
}
final Task thisTask = asTask();
// Embedded Task's configuration should go with parent TaskFragment, so we don't re-compute
// configuration here.
if (thisTask != null && !thisTask.isEmbedded()) {
//這里會對task的override的Config進行校驗從新設(shè)定
thisTask.resolveLeafTaskOnlyOverrideConfigs(newParentConfig,
mTmpBounds /* previousBounds */);
}computeConfigResourceOverrides(getResolvedOverrideConfiguration(), newParentConfig);
}
void resolveLeafTaskOnlyOverrideConfigs(Configuration newParentConfig, Rect previousBounds) {
//省略
//這里會進行相關(guān)的最小尺寸適配,這里就把窗口的坐標變大
adjustForMinimalTaskDimensions(outOverrideBounds, previousBounds, newParentConfig);
if (windowingMode == WINDOWING_MODE_FREEFORM) {
computeFreeformBounds(outOverrideBounds, newParentConfig);
return;
}
}
void adjustForMinimalTaskDimensions(@NonNull Rect bounds, @NonNull Rect previousBounds,
@NonNull Configuration parentConfig) {
int minWidth = mMinWidth;
int minHeight = mMinHeight;
// If the task has no requested minimal size, we'd like to enforce a minimal size
// so that the user can not render the task fragment too small to manipulate. We don't need
// to do this for the root pinned task as the bounds are controlled by the system.
if (!inPinnedWindowingMode()) {
// Use Display specific min sizes when there is one associated with this Task.
final int defaultMinSizeDp = mDisplayContent == null
? DEFAULT_MIN_TASK_SIZE_DP : mDisplayContent.mMinSizeOfResizeableTaskDp;
//這里就相當于wms中獲取了mMinSizeOfResizeableTaskDp最小坐標,這里一般是220dp默認
final float density = (float) parentConfig.densityDpi / DisplayMetrics.DENSITY_DEFAULT;
final int defaultMinSize = (int) (defaultMinSizeDp * density);
if (minWidth == INVALID_MIN_SIZE) {
minWidth = defaultMinSize;
}
if (minHeight == INVALID_MIN_SIZE) {
minHeight = defaultMinSize;
}
}
}
/** Computes bounds for {@link WindowConfiguration#WINDOWING_MODE_FREEFORM}. */
private void computeFreeformBounds(@NonNull Rect outBounds,
@NonNull Configuration newParentConfig) {
// by policy, make sure the window remains within parent somewhere
final float density =
((float) newParentConfig.densityDpi) / DisplayMetrics.DENSITY_DEFAULT;
final Rect parentBounds =
new Rect(newParentConfig.windowConfiguration.getBounds());
final DisplayContent display = getDisplayContent();
if (display != null) {
// If a freeform window moves below system bar, there is no way to move it again
// by touch. Because its caption is covered by system bar. So we exclude them
// from root task bounds. and then caption will be shown inside stable area.
final Rect stableBounds = new Rect();
display.getStableRect(stableBounds);
parentBounds.intersect(stableBounds);
}
fitWithinBounds(outBounds, parentBounds,
(int) (density * WindowState.MINIMUM_VISIBLE_WIDTH_IN_DP),
(int) (density * WindowState.MINIMUM_VISIBLE_HEIGHT_IN_DP));
// Prevent to overlap caption with stable insets.
int offsetTop = parentBounds.top - outBounds.top; //這里是關(guān)鍵,會對top的區(qū)域進行再一次修正
if (offsetTop > 0) {
outBounds.offset(0, offsetTop);
}
}
自由窗口模式的移動
自由窗口的移到拖拽必須要在頂部的CaptionView部分才可以,不然長按其他地方是不行的應(yīng)用進程:
frameworks/base/core/java/com/android/internal/widget/DecorCaptionView.java
@Override
public boolean onTouch(View v, MotionEvent e) {
//省略
case MotionEvent.ACTION_MOVE:
//省略
//這里app進程發(fā)起startMovingTask調(diào)用
startMovingTask(e.getRawX(), e.getRawY());
break;
public final boolean startMovingTask(float startX, float startY) {
if (ViewDebug.DEBUG_POSITIONING) {
Log.d(VIEW_LOG_TAG, "startMovingTask: {" + startX + "," + startY + "}");
}
try {
//這里最后通過Session進行跨進程調(diào)用到systemserver
return mAttachInfo.mSession.startMovingTask(mAttachInfo.mWindow, startX, startY);
} catch (RemoteException e) {
Log.e(VIEW_LOG_TAG, "Unable to start moving", e);
}
return false;
}
systemserver進程
frameworks/base/services/core/java/com/android/server/wm/Session.java文章來源:http://www.zghlxwxcb.cn/news/detail-737047.html
@Override
public boolean startMovingTask(IWindow window, float startX, float startY) {
final long ident = Binder.clearCallingIdentity();
try {
//直接調(diào)用了TaskositioningController的startMovingTask
return mService.mTaskPositioningController.startMovingTask(window, startX, startY);
} finally {
Binder.restoreCallingIdentity(ident);
}
}
boolean startMovingTask(IWindow window, float startX, float startY) {
WindowState win = null;
synchronized (mService.mGlobalLock) {
win = mService.windowForClientLocked(null, window, false);
// win shouldn't be null here, pass it down to startPositioningLocked
// to get warning if it's null.
//最重要調(diào)用了startPositioningLocked方法進行相關(guān)的觸摸事件識別注冊初始化
if (!startPositioningLocked(
win, false /*resize*/, false /*preserveOrientation*/, startX, startY)) {
return false;
}
mService.mAtmService.setFocusedTask(win.getTask().mTaskId);
}
return true;
}
private boolean startPositioningLocked(WindowState win, boolean resize,
boolean preserveOrientation, float startX, float startY) {
//省略
mPositioningDisplay = displayContent;
//進行觸摸識別監(jiān)聽的TaskPositioner相關(guān)注冊等,再接下來如果有觸摸事件產(chǎn)生則會調(diào)用到TaskPositioner的onInputEvent方法進行相關(guān)處理
mTaskPositioner = TaskPositioner.create(mService);
mTaskPositioner.register(displayContent, win);
//省略
return true;
}
//TaskPositioner的onInputEvent方法進行相關(guān)觸摸事件處理
private boolean onInputEvent(InputEvent event) {
//省略
switch (motionEvent.getAction()) {
//省略
case MotionEvent.ACTION_MOVE: {
//進行相關(guān)的move操作處理
synchronized (mService.mGlobalLock) {
//根據(jù)坐標進行相關(guān)的bounds位置等計算
mDragEnded = notifyMoveLocked(newX, newY);
mTask.getDimBounds(mTmpRect);
}
if (!mTmpRect.equals(mWindowDragBounds)) {
//有了新計算的bounds進行task的對應(yīng)resize操作
mService.mAtmService.resizeTask(
mTask.mTaskId, mWindowDragBounds, RESIZE_MODE_USER);
}
}
break;
//省略
}
return true;
}
文章來源地址http://www.zghlxwxcb.cn/news/detail-737047.html
自由窗口模式的拖拽變大
frameworks/base/services/core/java/com/android/server/wm/TaskTapPointerEventListener.java
@Override
public void onPointerEvent(MotionEvent motionEvent) {
switch (motionEvent.getActionMasked()) {
case MotionEvent.ACTION_DOWN: {
final int x;
final int y;
if (motionEvent.getSource() == InputDevice.SOURCE_MOUSE) {
x = (int) motionEvent.getXCursorPosition();
y = (int) motionEvent.getYCursorPosition();
} else {
x = (int) motionEvent.getX();
y = (int) motionEvent.getY();
}
synchronized (this) {
if (!mTouchExcludeRegion.contains(x, y)) {
//這里會進行區(qū)域的判定是否在區(qū)域內(nèi),不在區(qū)域內(nèi)則執(zhí)行handleTapOutsideTask
mService.mTaskPositioningController.handleTapOutsideTask(
mDisplayContent, x, y);
}
}
}
//handleTapOutsideTask方法就會進行關(guān)鍵的區(qū)域判定是否觸摸到了自由窗口task周圍
void handleTapOutsideTask(DisplayContent displayContent, int x, int y) {
mService.mH.post(() -> {
synchronized (mService.mGlobalLock) {
//根據(jù)x,y坐標進行Task的尋找
final Task task = displayContent.findTaskForResizePoint(x, y);
//尋找到了對應(yīng)的task
if (task != null) {
if (!task.isResizeable()) {
// The task is not resizable, so don't do anything when the user drags the
// the resize handles.
return;
}
//如果找到了task,那就開啟相關(guān)的task拖拽識別,后面的邏輯就到了TaskPositioner中,startPositioningLocked部分和上個移動的沒有啥區(qū)別這里就不進行講解
if (!startPositioningLocked(task.getTopVisibleAppMainWindow(), true /*resize*/,
task.preserveOrientationOnResize(), x, y)) {
return;
}
mService.mAtmService.setFocusedTask(task.mTaskId);
}
}
});
}
/**
* Find the task whose outside touch area (for resizing) (x, y) falls within.
* Returns null if the touch doesn't fall into a resizing area.
*/
@Nullable
Task findTaskForResizePoint(int x, int y) {
//這里的尋找就是有一個關(guān)鍵的delta,這個就是task的觸摸邊界范圍,即要觸摸在delta范圍以內(nèi),不然觸摸就不起作用
final int delta = dipToPixel(RESIZE_HANDLE_WIDTH_IN_DP, mDisplayMetrics);
return getItemFromTaskDisplayAreas(taskDisplayArea ->
mTmpTaskForResizePointSearchResult.process(taskDisplayArea, x, y, delta));
}
到了這里,關(guān)于android framework實戰(zhàn)開發(fā)之WINDOWING_MODE_FREEFORM自由窗口相關(guān)的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!