android framework实战开发之WINDOWING_MODE_FREEFORM自由窗口相关

这篇具有很好参考价值的文章主要介绍了android framework实战开发之WINDOWING_MODE_FREEFORM自由窗口相关。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

hi,粉丝朋友们!
今天开始就进入正式的自由窗口的相关的内容讲解,blog只是一些知识点的记录,更多的干货,还请看马哥的视频,及视频配套资料。
android freeform模式,framework教程,分屏,android,framework,车载车机开发,系统开发面试,自由窗口,分屏,系统开发

b站免费视频教程讲解:
https://www.bilibili.com/video/BV1wj411o7A9/
android freeform模式,framework教程,分屏,android,framework,车载车机开发,系统开发面试,自由窗口,分屏,系统开发

aosp默认并没有公开自由窗口模式,如果需要体验自由窗口模式必须要用如下命令进行开启

adb shell settings put global enable_freeform_support 1
adb shell settings put global force_resizable_activities 1

输入完成后,可以在多任务的menu中发现freeform:
android freeform模式,framework教程,分屏,android,framework,车载车机开发,系统开发面试,自由窗口,分屏,系统开发

点击这个freeform按钮即可以进入到自由窗口模式。

点击进入Freeform的systemserver端执行

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;
        }
    }

这里点击时候会触发startActivityFromRecents,会跨进程调到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 {
            //根据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;
                }
 //省略
    }

这里其实表面看还是比较简单的,主要就是一个根据taskId获取Task对象,然后再调用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设置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.
                    //调用task的resize
                    task.resize(false /* relayout */, false /* forced */);
                }
            }
//省略
            final ActivityRecord r = task.getTopNonFinishingActivity();
            //这里进行最关键的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.
            	//调用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);
			//这里又调用到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) {
  //省略
  //又调用到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更新
    }

这里虽然层数多,但是目标还是比较简单,那就是把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在哪里进行设置的?
下面列出堆栈

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相关的不是全屏,也不是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、明明设置的bounds大小是150x150,为啥窗口变大了,是在哪里修改的?
作业。。下节课视频公布

自由窗口模式的DecorCaptionView

android freeform模式,framework教程,分屏,android,framework,车载车机开发,系统开发面试,自由窗口,分屏,系统开发
DecorCaptionView 创建过程:

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时候就进行的创建,那么systemserver端是哪里触发的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)

对应两个按钮的处理:

    @Override
    public boolean onSingleTapUp(MotionEvent e) {
        if (mClickTarget == mMaximize) {
            toggleFreeformWindowingMode();//自由模式变成全屏
        } else if (mClickTarget == mClose) {
         //直接退出自由模式,其实本质就是Activity退出
            mOwner.dispatchOnWindowDismissed(
                    true /*finishTask*/, false /*suppressWindowTransition*/);
        }
        return true;
    }
    //最后toggleFreeformWindowingMode会跨进程调用到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设置成fullscreen
                    rootTask.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
                    rootTask.setBounds(null);
                }   
                //省略
        } finally {
            Binder.restoreCallingIdentity(ident);
        }
    }

freeform坐标矫正相关:

桌面初始化传递坐标区域:
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);//这里给定了一个初始的坐标区域
            activityOptions.setLaunchBounds(r);
            return activityOptions;
        }

刚开始给定的区域大小可以看得出是比较小的,才150x150,所以明显实际上要大于这个150,其实这个坐标还会在systemserver中进行矫正

systemserver中task对坐标区域进行矫正:
在进行对task的configration进行设置时候,其实Task是需要对config进行再一次的检测校验才可以,即不是你想设置task的任何configration就可以设置什么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进行校验从新设定
            thisTask.resolveLeafTaskOnlyOverrideConfigs(newParentConfig,
                    mTmpBounds /* previousBounds */);
        }computeConfigResourceOverrides(getResolvedOverrideConfiguration(), newParentConfig);
    }
       void resolveLeafTaskOnlyOverrideConfigs(Configuration newParentConfig, Rect previousBounds) {
        //省略
			//这里会进行相关的最小尺寸适配,这里就把窗口的坐标变大
        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; //这里是关键,会对top的区域进行再一次修正
        if (offsetTop > 0) {
            outBounds.offset(0, offsetTop);
        }
    }

自由窗口模式的移动

自由窗口的移到拖拽必须要在顶部的CaptionView部分才可以,不然长按其他地方是不行的
android freeform模式,framework教程,分屏,android,framework,车载车机开发,系统开发面试,自由窗口,分屏,系统开发应用进程:
frameworks/base/core/java/com/android/internal/widget/DecorCaptionView.java

 @Override
    public boolean onTouch(View v, MotionEvent e) {
       
//省略
            case MotionEvent.ACTION_MOVE:
       
//省略
//这里app进程发起startMovingTask调用
                    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进行跨进程调用到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

   @Override
    public boolean startMovingTask(IWindow window, float startX, float startY) {
        final long ident = Binder.clearCallingIdentity();
        try {
        //直接调用了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.
            //最重要调用了startPositioningLocked方法进行相关的触摸事件识别注册初始化
            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;
			//进行触摸识别监听的TaskPositioner相关注册等,再接下来如果有触摸事件产生则会调用到TaskPositioner的onInputEvent方法进行相关处理
			
        mTaskPositioner = TaskPositioner.create(mService);
        mTaskPositioner.register(displayContent, win);
			
        //省略
        return true;
    }

//TaskPositioner的onInputEvent方法进行相关触摸事件处理
private boolean onInputEvent(InputEvent event) {
      //省略
        switch (motionEvent.getAction()) {
             //省略

            case MotionEvent.ACTION_MOVE: {
            //进行相关的move操作处理
                synchronized (mService.mGlobalLock) {
                //根据坐标进行相关的bounds位置等计算
                    mDragEnded = notifyMoveLocked(newX, newY);
                    mTask.getDimBounds(mTmpRect);
                }
                if (!mTmpRect.equals(mWindowDragBounds)) {
                    //有了新计算的bounds进行task的对应resize操作
                    mService.mAtmService.resizeTask(
                            mTask.mTaskId, mWindowDragBounds, RESIZE_MODE_USER);
                }
            }
            break;
   //省略
        }
        return true;
    }

android freeform模式,framework教程,分屏,android,framework,车载车机开发,系统开发面试,自由窗口,分屏,系统开发文章来源地址https://www.toymoban.com/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)) {
                        //这里会进行区域的判定是否在区域内,不在区域内则执行handleTapOutsideTask
                        mService.mTaskPositioningController.handleTapOutsideTask(
                                mDisplayContent, x, y);
                    }
                }
            }
            //handleTapOutsideTask方法就会进行关键的区域判定是否触摸到了自由窗口task周围
void handleTapOutsideTask(DisplayContent displayContent, int x, int y) {
        mService.mH.post(() -> {
            synchronized (mService.mGlobalLock) {
            //根据x,y坐标进行Task的寻找
                final Task task = displayContent.findTaskForResizePoint(x, y);
                //寻找到了对应的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,那就开启相关的task拖拽识别,后面的逻辑就到了TaskPositioner中,startPositioningLocked部分和上个移动的没有啥区别这里就不进行讲解
                    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) {
    //这里的寻找就是有一个关键的delta,这个就是task的触摸边界范围,即要触摸在delta范围以内,不然触摸就不起作用
        final int delta = dipToPixel(RESIZE_HANDLE_WIDTH_IN_DP, mDisplayMetrics);
        return getItemFromTaskDisplayAreas(taskDisplayArea ->
                mTmpTaskForResizePointSearchResult.process(taskDisplayArea, x, y, delta));
    }

到了这里,关于android framework实战开发之WINDOWING_MODE_FREEFORM自由窗口相关的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Android Framework层开发

    查看源码工具: SourceInsight Instrumentation :可以理解为ActivityThread的一个工具类,在ActivityThread中初始化,一个进程只存在一个Instrumentation对象,在每个Activity初始化时,会通过Activity的Attach方法,将该引用传递给Activity。Activity所有生命周期的方法都有该类来执行。 Init进程(pi

    2024年02月04日
    浏览(45)
  • 我是如何从Android开发转framework开发的

    转framework开发快一年了,一直都想写一篇文章,分享一下自己的工作心得,也让做应用开发的小伙伴对framework开发有一定的了解,但因为种种原因耽搁了,今天就趁着工作闲暇之余,聊聊我从应用开发转framework开发的心路历程,自己也是刚开始学着写文章,文笔不太好,请见

    2024年02月02日
    浏览(52)
  • 【Android Framework (十二) 】- 智能硬件设备开发

    针对我过往工作经历,曾在一家智能科技任职Android开发工程师,简单介绍下关于任职期间接触和开发过的一些项目经历,智能多与物联网(LOT)进行联系,从对Android智能硬件一无所知到现在算是略有小成,期间踩了很多坑,也接触到了许多非Android方面的知识,现用文章的方

    2024年02月12日
    浏览(45)
  • android framework-Pixel3真机系统内置第三方apk实战

    ./make/target/product/mainline_arm64.mk 这个是系统应用的配置 ./make/target/product/handheld_product.mk 这个就是我们要配置的文件 在下面添加我们新增的 1、source build/envset.sh 2、lunch并且选择对应的product 3、make 4、adb devices(检查设备是否连接) 5、adb reboot bootloader(重启设备,进入fastboot状态)

    2024年02月12日
    浏览(63)
  • android多屏触摸相关的详解方案-安卓framework开发手机车载车机系统开发课程

    直播免费视频课程地址:https://www.bilibili.com/video/BV1hN4y1R7t2/ 在做双屏相关需求开发过程中,经常会有对两个屏幕都要求可以正确触摸的场景。但是目前我们模拟器默认创建的双屏其实是没有办法进行触摸的 静态修改方案 使用命令查看display2即副屏的信息情况 adb shell dumpsys d

    2024年02月11日
    浏览(52)
  • 人人都能升薪?深扒Android开发中的通天神器Framework

    近年来,客户端领域的竞争日益激烈,很多安卓开发者抱怨内卷现象普遍存在。在这种背景下,开发者们面对的压力越来越大,很难掌握足够的技能去顺利发展。解决内卷,突破迷茫,是许多开发者面临的挑战。小米的高管在脉脉上提供了一条不错的建议,即如下: 但对于大

    2024年02月02日
    浏览(42)
  • Android相机开发实战,面试题+笔记+项目实战

    private SurfaceView mSurfaceView; private SurfaceHolder mHolder; private Size mPreviewSize; private Size adapterSize; //private List mSupportedPreviewSizes; private Camera mCamera; private boolean isSupportAutoFocus = false; private Camera.Parameters parameters = null; private Context mContext; //private int mCurrentCameraId = 0; private int screenWidth; pri

    2024年04月16日
    浏览(47)
  • 手机移动开发技术,,Android开发经典实战

    面试题库 按照系统分类 按照大厂分类 《2017-2020字节跳动Android面试真题解析》 大神手写整理笔记类 《Android框架体系架构》 书籍类 不需要太多,精就好! 《第一行代码第二版》 技能提升资料库 一共十个专题,包括了Android进阶所有学习资料,Android进阶视频,Flutter,java基础

    2024年04月13日
    浏览(48)
  • Android相机开发实战,Android程序员必看

    由于需求不同,所以选择的方案固然也不同,至于第二种调用系统相机,这里就不过多讲解了,使用Intent对象设置一个Action动作即可,跳转时使用startActivityForResult,然后在onActivityResult处理相关数据便可,关键代码: intent.setAction(“android.media.action.STILL_IMAGE_CAMERA”); 至于使用

    2024年04月15日
    浏览(48)
  • Android企业级实战-界面篇-2,android音频开发

    ?xml version=\\\"1.0\\\" encoding=\\\"utf-8\\\"? RelativeLayout xmlns:android=“http://schemas.android.com/apk/res/android” android:id=“@id/user_profile_layout” android:layout_width=“fill_parent” android:layout_height=“100.0dip” android:clickable=“true” android:clipChildren=“false” ImageView android:id=“@id/profile_cover” android:layout_width=“

    2024年04月27日
    浏览(53)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包