Android window structure (2) Add Home Task

  After the Android hierarchy is constructed, the first task, HomeTask, needs to be added. HomeTask is the task where the Launcher application is located. Now follow the previous article Android window structure (1) and continue to analyze the addition of HomeTask.
  In the RootWindowContainer class, after the initialization of DisplayContent is completed, the getOrCreateRootHomeTask(ON_TOP) method of the default TaskDisplayArea will be called to generate HomeTask. Let's start from here:

    @Nullable
    Task getOrCreateRootHomeTask(boolean onTop) {
    
    
        Task homeTask = getRootHomeTask();
        // Take into account if this TaskDisplayArea can have a home task before trying to
        // create the root task
        if (homeTask == null && canHostHomeTask()) {
    
    
            homeTask = createRootTask(WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_HOME, onTop);
        }
        return homeTask;
    }
    Task getRootHomeTask() {
    
    
        return mRootHomeTask;
    }    
    Task createRootTask(int windowingMode, int activityType, boolean onTop) {
    
    
        return createRootTask(windowingMode, activityType, onTop, null /* activityOptions */);
    }
    Task createRootTask(int windowingMode, int activityType, boolean onTop, ActivityOptions opts) {
    
    
        return new Task.Builder(mAtmService)
                .setWindowingMode(windowingMode)
                .setActivityType(activityType)
                .setParent(this)
                .setOnTop(onTop)
                .setActivityOptions(opts)
                .build();
    }    

  If the mRootHomeTask of the TaskDisplayArea class is null, it means that it has not been generated for it, so you need to call createRootTask() to generate it. And here windowingMode=WINDOWING_MODE_UNDEFINED, activityType=ACTIVITY_TYPE_HOME.
  Generating Task also uses the builder mode, setting windowingMode to WINDOWING_MODE_UNDEFINED, activityType to ACTIVITY_TYPE_HOME, parent container to TaskDisplayArea class object, whether it is true on the top, and ActivityOptions, here is null. Finally, call the build() method to generate a Task object.
  Take a look at the build() of the Task's construction class Build in sections, code section 1:

        Task build() {
    
    
            if (mParent != null && mParent instanceof TaskDisplayArea) {
    
    
                validateRootTask((TaskDisplayArea) mParent);
            }

  build() calls validateRootTask((TaskDisplayArea) mParent) to do some root task validation when its parent container is a TaskDisplayArea object.

root task verification

  The verification of the root task is in the method validateRootTask(TaskDisplayArea tda), take a look:

        private void validateRootTask(TaskDisplayArea tda) {
    
    
            if (mActivityType == ACTIVITY_TYPE_UNDEFINED && !mCreatedByOrganizer) {
    
    
                // Can't have an undefined root task type yet...so re-map to standard. Anyone
                // that wants anything else should be passing it in anyways...except for the task
                // organizer.
                mActivityType = ACTIVITY_TYPE_STANDARD;
            }

            if (mActivityType != ACTIVITY_TYPE_STANDARD
                    && mActivityType != ACTIVITY_TYPE_UNDEFINED) {
    
    
                // For now there can be only one root task of a particular non-standard activity
                // type on a display. So, get that ignoring whatever windowing mode it is
                // currently in.
                Task rootTask = tda.getRootTask(WINDOWING_MODE_UNDEFINED, mActivityType);
                if (rootTask != null) {
    
    
                    throw new IllegalArgumentException("Root task=" + rootTask + " of activityType="
                            + mActivityType + " already on display=" + tda
                            + ". Can't have multiple.");
                }
            }

            if (!TaskDisplayArea.isWindowingModeSupported(mWindowingMode,
                    mAtmService.mSupportsMultiWindow,
                    mAtmService.mSupportsSplitScreenMultiWindow,
                    mAtmService.mSupportsFreeformWindowManagement,
                    mAtmService.mSupportsPictureInPicture, mActivityType)) {
    
    
                throw new IllegalArgumentException("Can't create root task for unsupported "
                        + "windowingMode=" + mWindowingMode);
            }

            if (mWindowingMode == WINDOWING_MODE_PINNED
                    && mActivityType != ACTIVITY_TYPE_STANDARD) {
    
    
                throw new IllegalArgumentException(
                        "Root task with pinned windowing mode cannot with "
                                + "non-standard activity type.");
            }

            if (mWindowingMode == WINDOWING_MODE_PINNED && tda.getRootPinnedTask() != null) {
    
    
                // Only 1 root task can be PINNED at a time, so dismiss the existing one
                tda.getRootPinnedTask().dismissPip();
            }

            if (mIntent != null) {
    
    
                mLaunchFlags |= mIntent.getFlags();
            }

            // Task created by organizer are added as root.
            final Task launchRootTask = mCreatedByOrganizer
                    ? null : tda.getLaunchRootTask(mWindowingMode, mActivityType, mActivityOptions,
                    mSourceTask, mLaunchFlags);
            if (launchRootTask != null) {
    
    
                // Since this task will be put into a root task, its windowingMode will be
                // inherited.
                mWindowingMode = WINDOWING_MODE_UNDEFINED;
                mParent = launchRootTask;
            }

            mTaskId = tda.getNextRootTaskId();
        }

  The mCreatedByOrganizer variable represents that the task was created by the task organizer. If not, and the mActivityType of the task is not specified, it is set to ACTIVITY_TYPE_STANDARD.
  The mActivityType types of tasks include ACTIVITY_TYPE_UNDEFINED, ACTIVITY_TYPE_STANDARD, ACTIVITY_TYPE_HOME, ACTIVITY_TYPE_RECENTS, ACTIVITY_TYPE_ASSISTANT, and ACTIVITY_TYPE_DREAM. Among them, ACTIVITY_TYPE_UNDEFINED is the value when the ActivityType is not specified. ACTIVITY_TYPE_STANDARD is a standard type, and the rest are called special types.
  In addition to the two types of ACTIVITY_TYPE_STANDARD and ACTIVITY_TYPE_UNDEFINED, if there is already a root Task object of a special activity type in the TaskDisplayArea object, it is not allowed to create a new one, and an IllegalArgumentException will be reported.
  Then, the TaskDisplayArea.isWindowingModeSupported() method will be used to determine whether the WindowingMode and the activity type are supported. If not, an IllegalArgumentException will be reported.
  When WindowingMode is WINDOWING_MODE_PINNED, the activity type must be a standard type, otherwise, an error will be reported.
  If WindowingMode is WINDOWING_MODE_PINNED, and there is already a root task PINNED in the TaskDisplayArea object, because only one is allowed to exist at the same time, it is discarded.
  If mIntent is not null, set the builder's mLaunchFlags to mIntent.getFlags().
  The getLaunchRootTask() of the TaskDisplayArea object mainly processes the Task specified in the parameter mActivityOptions and some tasks cached in the TaskDisplayArea object. When the HomeTask is generated, this method returns null. If null is not returned, the mParent of the builder object will be set to it.
  Finally, the task Id will be generated. It is related to the user id. For example, if the user id is 0, the task id ranges from 0-99999. Each new task ID will be incremented by 1.

set some properties

  The next step is to set some attribute information. build() code segment 2:

            if (mActivityInfo == null) {
    
    
                mActivityInfo = new ActivityInfo();
                mActivityInfo.applicationInfo = new ApplicationInfo();
            }

            mUserId = UserHandle.getUserId(mActivityInfo.applicationInfo.uid);
            mTaskAffiliation = mTaskId;
            mLastTimeMoved = System.currentTimeMillis();
            mNeverRelinquishIdentity = true;
            mCallingUid = mActivityInfo.applicationInfo.uid;
            mCallingPackage = mActivityInfo.packageName;
            mResizeMode = mActivityInfo.resizeMode;
            mSupportsPictureInPicture = mActivityInfo.supportsPictureInPicture();
            if (mActivityOptions != null) {
    
    
                mRemoveWithTaskOrganizer = mActivityOptions.getRemoveWithTaskOranizer();
            }

  If the builder does not specify mActivityInfo, create mActivityInfo object and ApplicationInfo object directly.
  Get mUserId, here is the user Id obtained through uid.

    /**
     * Returns the user id for a given uid.
     * @hide
     */
    @UnsupportedAppUsage
    @TestApi
    public static @UserIdInt int getUserId(int uid) {
    
    
        if (MU_ENABLED) {
    
    
            return uid / PER_USER_RANGE;
        } else {
    
    
            return UserHandle.USER_SYSTEM;
        }
    }

  When supporting multiple users, directly divide uid by PER_USER_RANGE (100000) to get user id. Got 0 here.
  Then set the mTaskAffiliation of the builder object to the newly generated task Id.
  mNeverRelinquishIdentity is set to true, which represents whether to discard the original identity information.
  mCallingUid is set to the default uid of the ApplicationInfo object just generated, which is 0.
  mCallingPackage is set to the default packageName of the newly generated ActivityInfo object, which is null.
  mResizeMode is set to the default resizeMode of the newly generated ActivityInfo object, which is RESIZE_MODE_RESIZEABLE, and the size can be reset.
  mSupportsPictureInPicture is set to the default of the generated ActivityInfo object to false.

Create a Task object

  The next step is to call the buildInner() object to create the Task object. build() code segment three:

            final Task task = buildInner();
            task.mHasBeenVisible = mHasBeenVisible;

  Look at the code of buildInner() again:

        /** Don't use {@link Builder#buildInner()} directly. This is only used by XML parser. */
        @VisibleForTesting
        Task buildInner() {
    
    
            return new Task(mAtmService, mTaskId, mIntent, mAffinityIntent, mAffinity,
                    mRootAffinity, mRealActivity, mOrigActivity, mRootWasReset, mAutoRemoveRecents,
                    mAskedCompatMode, mUserId, mEffectiveUid, mLastDescription, mLastTimeMoved,
                    mNeverRelinquishIdentity, mLastTaskDescription, mLastSnapshotData,
                    mTaskAffiliation, mPrevAffiliateTaskId, mNextAffiliateTaskId, mCallingUid,
                    mCallingPackage, mCallingFeatureId, mResizeMode, mSupportsPictureInPicture,
                    mRealActivitySuspended, mUserSetupComplete, mMinWidth, mMinHeight,
                    mActivityInfo, mVoiceSession, mVoiceInteractor, mCreatedByOrganizer,
                    mLaunchCookie, mDeferTaskAppear, mRemoveWithTaskOrganizer);
        }

  Call the Task's constructor directly.

Before adding the Task object to the parent container, set the ActivityType

  build() code segment 4:

            // Set activity type before adding the root task to TaskDisplayArea, so home task can
            // be cached, see TaskDisplayArea#addRootTaskReferenceIfNeeded().
            if (mActivityType != ACTIVITY_TYPE_UNDEFINED) {
    
    
                task.setActivityType(mActivityType);
            }

  mActivityType is currently ACTIVITY_TYPE_HOME, so call the setActivityType(mActivityType) method of the Task object.

    /** Sets the activity type to associate with the configuration container. */
    public void setActivityType(/*@WindowConfiguration.ActivityType*/ int activityType) {
    
    
        int currentActivityType = getActivityType();
        if (currentActivityType == activityType) {
    
    
            return;
        }
        if (currentActivityType != ACTIVITY_TYPE_UNDEFINED) {
    
    
            throw new IllegalStateException("Can't change activity type once set: " + this
                    + " activityType=" + activityTypeToString(activityType));
        }
        mRequestsTmpConfig.setTo(getRequestedOverrideConfiguration());
        mRequestsTmpConfig.windowConfiguration.setActivityType(activityType);
        onRequestedOverrideConfigurationChanged(mRequestsTmpConfig);
    }

  Setting the ActivityType of the task, which is related to the configuration of the window container class, is still somewhat complicated. Look at its logic.
  If the current ActivityType is the same as the set ActivityType, there is no need to reset it. If the ActivityType has been set before, it is not allowed to set it to another type now. Then put the current configuration of the task into mRequestsTmpConfig, then modify the ActivityType corresponding to mRequestsTmpConfig to the set value, and finally call the onRequestedOverrideConfigurationChanged(mRequestsTmpConfig) method.
  Look at the method of obtaining the ActivityType of the Task class:

    @Override
    public int getActivityType() {
    
    
        final int applicationType = super.getActivityType();
        if (applicationType != ACTIVITY_TYPE_UNDEFINED || !hasChild()) {
    
    
            return applicationType;
        }
        return getTopChild().getActivityType();
    }

  First call the getActivityType() method of the parent class. If the type has been specified, or it has no children, the type will be returned at this time. If the type returned by the parent class is ACTIVITY_TYPE_UNDEFINED (unspecified), and there are children, the ActivityType of the top child is taken at this time.
  The parent class is WindowContainer, which does not implement this method. Look up ConfigurationContainer and look at its getActivityType():

    /** Returns the activity type associated with the the configuration container. */
    /*@WindowConfiguration.ActivityType*/
    public int getActivityType() {
    
    
        return mFullConfiguration.windowConfiguration.getActivityType();
    }

  mFullConfiguration is a Configuration class object. In addition to this, the ConfigurationContainer class has several Configuration class objects, namely mRequestedOverrideConfiguration, mResolvedOverrideConfiguration, mFullConfiguration, and mMergedOverrideConfiguration variables.
  mRequestedOverrideConfiguration is the configuration requested to be changed, and mResolvedOverrideConfiguration is the changed configuration. In the end, they are the same, but the time points of their changes during the change process are different, which will be seen later in the analysis below.
  mFullConfiguration is the combination of its parent container and its own configuration. Every time it changes, it is passed down as a parameter to the child container. Finally, pass its changed configuration content to the child's mFullConfiguration. Continue to look at the onRequestedOverrideConfigurationChanged(mRequestsTmpConfig) method in the setActivityType(/ @WindowConfiguration.ActivityType / int activityType) function. Here, in order to clarify mRequestedOverrideConfiguration, mResolvedOverrideConfiguration, mFullConfiguration, and mMergedOverrideConfiguration, first look at the use of member variables of the TaskerConfiguration
  class . methods in the class.

ConfigurationContainer类里的onRequestedOverrideConfigurationChanged()

    /**
     * Update override configuration and recalculate full config.
     * @see #mRequestedOverrideConfiguration
     * @see #mFullConfiguration
     */
    public void onRequestedOverrideConfigurationChanged(Configuration overrideConfiguration) {
    
    
        // Pre-compute this here, so we don't need to go through the entire Configuration when
        // writing to proto (which has significant cost if we write a lot of empty configurations).
        mHasOverrideConfiguration = !Configuration.EMPTY.equals(overrideConfiguration);
        mRequestedOverrideConfiguration.setTo(overrideConfiguration);
        final Rect newBounds = mRequestedOverrideConfiguration.windowConfiguration.getBounds();
        if (mHasOverrideConfiguration && providesMaxBounds()
                && diffRequestedOverrideMaxBounds(newBounds) != BOUNDS_CHANGE_NONE) {
    
    
            mRequestedOverrideConfiguration.windowConfiguration.setMaxBounds(newBounds);
        }
        // Update full configuration of this container and all its children.
        final ConfigurationContainer parent = getParent();
        onConfigurationChanged(parent != null ? parent.getConfiguration() : Configuration.EMPTY);
    }

  The parameter overrideConfiguration is the value after mRequestedOverrideConfiguration modifies the ActivityType. If overrideConfiguration is not equal to Configuration.EMPTY, set mHasOverrideConfiguration to true, indicating that there is a modified content. Then set the value in mRequestedOverrideConfiguration to the value in parameter overrideConfiguration. Note that the value of mRequestedOverrideConfiguration has changed at this time.
  The next step is to check whether the maxBounds of mRequestedOverrideConfiguration needs to be set to bounds. The condition is mHasOverrideConfiguration is true, providesMaxBounds() is true, diffRequestedOverrideMaxBounds(newBounds) != BOUNDS_CHANGE_NONE.
  providesMaxBounds() look at the implementation:

    protected boolean providesMaxBounds() {
    
    
        return false;
    }

  This method represents whether to provide the largest border for its children, true is provided, false is not provided. It is not provided by default, and subclasses can implement this method.
  diffRequestedOverrideMaxBounds(newBounds) compares the parameter newBounds with the maxBounds configured by mRequestedOverrideConfiguration to see if any changes have occurred.
  The onRequestedOverrideConfigurationChanged(mRequestsTmpConfig) method will finally call onConfigurationChanged(Configuration newParentConfig) to perform operations after the configuration changes. Pay attention to its parameters, if the parent container parent exists, it will be set to the mFullConfiguration of the parent container. If not present, set to Configuration.EMPTY (empty configuration).
  When does the parent container exist? It is when the parent container addChild(). This method will be covered in a moment. Therefore, the parent container does not exist yet, and this parameter is an empty configuration.
  Then look at onConfigurationChanged(Configuration newParentConfig) of the ConfigurationContainer class:

    /**
     * Notify that parent config changed and we need to update full configuration.
     * @see #mFullConfiguration
     */
    public void onConfigurationChanged(Configuration newParentConfig) {
    
    
        mResolvedTmpConfig.setTo(mResolvedOverrideConfiguration);
        resolveOverrideConfiguration(newParentConfig);
        mFullConfiguration.setTo(newParentConfig);
        mFullConfiguration.updateFrom(mResolvedOverrideConfiguration);
        onMergedOverrideConfigurationChanged();
        if (!mResolvedTmpConfig.equals(mResolvedOverrideConfiguration)) {
    
    
            // This depends on the assumption that change-listeners don't do
            // their own override resolution. This way, dependent hierarchies
            // can stay properly synced-up with a primary hierarchy's constraints.
            // Since the hierarchies will be merged, this whole thing will go away
            // before the assumption will be broken.
            // Inform listeners of the change.
            for (int i = mChangeListeners.size() - 1; i >= 0; --i) {
    
    
                mChangeListeners.get(i).onRequestedOverrideConfigurationChanged(
                        mResolvedOverrideConfiguration);
            }
        }
        for (int i = mChangeListeners.size() - 1; i >= 0; --i) {
    
    
            mChangeListeners.get(i).onMergedOverrideConfigurationChanged(
                    mMergedOverrideConfiguration);
        }
        for (int i = getChildCount() - 1; i >= 0; --i) {
    
    
            dispatchConfigurationToChild(getChildAt(i), mFullConfiguration);
        }
    }

  First store the value of resolveOverrideConfiguration in mResolvedTmpConfig, the value of resolveOverrideConfiguration is still unchanged at this time, and then call resolveOverrideConfiguration(newParentConfig), which is implemented in the ConfigurationContainer class as follows:

    /**
     * Resolves the current requested override configuration into
     * {@link #mResolvedOverrideConfiguration}
     *
     * @param newParentConfig The new parent configuration to resolve overrides against.
     */
    void resolveOverrideConfiguration(Configuration newParentConfig) {
    
    
        mResolvedOverrideConfiguration.setTo(mRequestedOverrideConfiguration);
    }

  Just set the value of mResolvedOverrideConfiguration to the value of mRequestedOverrideConfiguration, and mRequestedOverrideConfiguration contains the changed value. Now mResolvedOverrideConfiguration and mRequestedOverrideConfiguration data are consistent. Why are the parameters in this method not used? At this time, it is used to allow subclasses to rewrite this method.
  onConfigurationChanged() then sets the value of mFullConfiguration, first set to the value of the parameter newParentConfig, we know that if the parent container exists, it is the configuration value of the parent container, in this case mFullConfiguration is first set to the configuration value of the parent container. An empty configuration if the parent container does not exist. Then call updateFrom() to update the data from mResolvedOverrideConfiguration. See it, so that mFullConfiguration combines the parent container with the data after its own changes. If the value in the parent container is different from the value of the corresponding attribute in mResolvedOverrideConfiguration (not counting the default configuration value), the value in mResolvedOverrideConfiguration will be used.
  Then call onMergedOverrideConfigurationChanged() to process the mMergedOverrideConfiguration member variable. Look at the code:

    void onMergedOverrideConfigurationChanged() {
    
    
        final ConfigurationContainer parent = getParent();
        if (parent != null) {
    
    
            mMergedOverrideConfiguration.setTo(parent.getMergedOverrideConfiguration());
            mMergedOverrideConfiguration.updateFrom(mResolvedOverrideConfiguration);
        } else {
    
    
            mMergedOverrideConfiguration.setTo(mResolvedOverrideConfiguration);
        }
        for (int i = getChildCount() - 1; i >= 0; --i) {
    
    
            final ConfigurationContainer child = getChildAt(i);
            child.onMergedOverrideConfigurationChanged();
        }
    }

  If the parent container exists, first set mMergedOverrideConfiguration to the mMergedOverrideConfiguration of the parent container. Then, take the value of the configuration variable mResolvedOverrideConfiguration of the current object. If the parent container does not exist, sets mMergedOverrideConfiguration to the current object's configuration value.
  Continue to call the child's onMergedOverrideConfigurationChanged() method, passing the container's mMergedOverrideConfiguration to its child.
  onConfigurationChanged() will judge next! mResolvedTmpConfig.equals(mResolvedOverrideConfiguration, because mResolvedTmpConfig is the configuration value before the change, which is different from the current one, which means that the attribute value has changed. The registered onRequestedOverrideConfigurationChanged() interface function will be called for callback notification .These registered callbacks are all in the variable mChangeListeners collection.
  onConfigurationChanged() then calls onMergedOverrideConfigurationChanged() of the callback interface, because mMergedOverrideConfiguration has changed.
  OnConfigurationChanged() finally calls the child's onConfigurationChanged(Configuration newParentConfig) method to convert the current object The mFullConfiguration is passed to the child's mFullConfiguration.
  We found that both mFullConfiguration and mMergedOverrideConfiguration are passed from the parent container to the child container. When did they start assigning values? When adding to the parent container, the onParentChanged(ConfigurationContainer newParent, ConfigurationContainer oldParent) method will call onConfigurationChanged(newParent.mFullConfiguration).
  Through the Android window structure (1) , this article knows that the root of the window hierarchy is the RootWindowContainer object. If it has been added according to the tree structure, mFullConfiguration and mMergedOverrideConfiguration should be the same. But it is clearly pointed out in the comments that the two are different. It must be the processing in the subclass, which leads to the difference. This issue is left for other articles to explore.

onConfigurationChanged(Configuration newParentConfig) in the Task class

  This is the onRequestedOverrideConfigurationChanged() in the ConfigurationContainer class we analyzed. In fact, the onConfigurationChanged(Configuration newParentConfig) method is rewritten in the Task. In fact, its logic is quite complicated, involving changes in various modes. Here we choose the most To explain it from a simple point of view, because the Task object has not been added to the parent container, the parameter newParentConfig is an empty configuration. I will extract the relevant code:

@Override
    public void onConfigurationChanged(Configuration newParentConfig) {
    
    
    		……
    		onConfigurationChangedInner(newParentConfig);
    		……
    } 
    private void onConfigurationChangedInner(Configuration newParentConfig) {
    
    
    	……
		super.onConfigurationChanged(newParentConfig);
		……
	}       		

  That's right, in this case, the configured ActivityType is actually set to ACTIVITY_TYPE_HOME.

Add the Home Task object to the parent container

  build() code segment five:

            if (mParent != null) {
    
    
                if (mParent instanceof Task) {
    
    
                    final Task parentTask = (Task) mParent;
                    parentTask.addChild(task, mOnTop ? POSITION_TOP : POSITION_BOTTOM,
                            (mActivityInfo.flags & FLAG_SHOW_FOR_ALL_USERS) != 0);
                } else {
    
    
                    mParent.addChild(task, mOnTop ? POSITION_TOP : POSITION_BOTTOM);
                }
            }

  Here mParent is the TaskDisplayArea object, so here is the addChild(WindowContainer child, int position) of TaskDisplayArea:

    @Override
    void addChild(WindowContainer child, int position) {
    
    
        if (child.asTaskDisplayArea() != null) {
    
    
            if (DEBUG_ROOT_TASK) {
    
    
                Slog.d(TAG_WM, "Set TaskDisplayArea=" + child + " on taskDisplayArea=" + this);
            }
            super.addChild(child, position);
        } else if (child.asTask() != null) {
    
    
            addChildTask(child.asTask(), position);
        } else {
    
    
            throw new IllegalArgumentException(
                    "TaskDisplayArea can only add Task and TaskDisplayArea, but found "
                            + child);
        }
    }

  It can be seen that the child of the TaskDisplayArea object can be a TaskDisplayArea object or a Task object. Here child is Home Task, so it is Task object. Call addChildTask():

    private void addChildTask(Task task, int position) {
    
    
        if (DEBUG_ROOT_TASK) Slog.d(TAG_WM, "Set task=" + task + " on taskDisplayArea=" + this);

        addRootTaskReferenceIfNeeded(task);
        position = findPositionForRootTask(position, task, true /* adding */);

        super.addChild(task, position);
        if (mPreferredTopFocusableRootTask != null
                && task.isFocusable()
                && mPreferredTopFocusableRootTask.compareTo(task) < 0) {
    
    
            // Clear preferred top because the adding focusable task has a higher z-order.
            mPreferredTopFocusableRootTask = null;
        }
        mAtmService.updateSleepIfNeededLocked();
        onRootTaskOrderChanged(task);
    }

  1. Call addRootTaskReferenceIfNeeded(task), set the member variable of the corresponding type
  2. Get the position of the task
  3. Call the addChild(task, position) method of the parent class to add the task
  4. If the newly added task is focusable and has more For high z-order, modify mPreferredTopFocusableRootTask = null.
  5. According to the sleep state, make some settings.
  6. Call back the interface for root task sequence change.
  Here we mainly talk about the first three steps.

1、addRootTaskReferenceIfNeeded(task)

    void addRootTaskReferenceIfNeeded(Task rootTask) {
    
    
        if (rootTask.isActivityTypeHome()) {
    
    
            if (mRootHomeTask != null) {
    
    
                if (!rootTask.isDescendantOf(mRootHomeTask)) {
    
    
                    throw new IllegalArgumentException("addRootTaskReferenceIfNeeded: root home"
                            + " task=" + mRootHomeTask + " already exist on display=" + this
                            + " rootTask=" + rootTask);
                }
            } else {
    
    
                mRootHomeTask = rootTask;
            }
        } else if (rootTask.isActivityTypeRecents()) {
    
    
            if (mRootRecentsTask != null) {
    
    
                if (!rootTask.isDescendantOf(mRootRecentsTask)) {
    
    
                    throw new IllegalArgumentException("addRootTaskReferenceIfNeeded: root recents"
                            + " task=" + mRootRecentsTask + " already exist on display=" + this
                            + " rootTask=" + rootTask);
                }
            } else {
    
    
                mRootRecentsTask = rootTask;
            }
        }

        if (!rootTask.isRootTask()) {
    
    
            return;
        }
        final int windowingMode = rootTask.getWindowingMode();
        if (windowingMode == WINDOWING_MODE_PINNED) {
    
    
            if (mRootPinnedTask != null) {
    
    
                throw new IllegalArgumentException(
                        "addRootTaskReferenceIfNeeded: root pinned task=" + mRootPinnedTask
                                + " already exist on display=" + this + " rootTask=" + rootTask);
            }
            mRootPinnedTask = rootTask;
        } else if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
    
    
            if (mRootSplitScreenPrimaryTask != null) {
    
    
                throw new IllegalArgumentException(
                        "addRootTaskReferenceIfNeeded: root split screen primary task="
                                + mRootSplitScreenPrimaryTask
                                + " already exist on display=" + this + " rootTask=" + rootTask);
            }
            mRootSplitScreenPrimaryTask = rootTask;
        }
    }

  It can be seen that this method is to set the member variables mRootHomeTask, mRootRecentsTask, mRootPinnedTask, mRootSplitScreenPrimaryTask corresponding to the TaskDisplayArea object.
  mRootHomeTask and mRootRecentsTask are judged according to the ActivityType of the task. And if mRootHomeTask or mRootRecentsTask has already been set, when adding the same type of Task, if it is not a child descendant of the existing mRootHomeTask or mRootRecentsTask, an exception will be reported.
  mRootPinnedTask and mRootSplitScreenPrimaryTask are judged according to the windowingMode, and after they have been set, adding them will also report an error.
  For the current Home Task, after this step, set mRootHomeTask to the corresponding task value.

2. Get the location of the task

    private int findPositionForRootTask(int requestedPosition, Task rootTask, boolean adding) {
    
    
        // The max possible position we can insert the root task at.
        int maxPosition = findMaxPositionForRootTask(rootTask);
        // The min possible position we can insert the root task at.
        int minPosition = findMinPositionForRootTask(rootTask);

        // Cap the requested position to something reasonable for the previous position check
        // below.
        if (requestedPosition == POSITION_TOP) {
    
    
            requestedPosition = mChildren.size();
        } else if (requestedPosition == POSITION_BOTTOM) {
    
    
            requestedPosition = 0;
        }

        int targetPosition = requestedPosition;
        targetPosition = Math.min(targetPosition, maxPosition);
        targetPosition = Math.max(targetPosition, minPosition);

        int prevPosition = mChildren.indexOf(rootTask);
        // The positions we calculated above (maxPosition, minPosition) do not take into
        // consideration the following edge cases.
        // 1) We need to adjust the position depending on the value "adding".
        // 2) When we are moving a root task to another position, we also need to adjust the
        //    position depending on whether the root task is moving to a higher or lower position.
        if ((targetPosition != requestedPosition) && (adding || targetPosition < prevPosition)) {
    
    
            targetPosition++;
        }

        return targetPosition;
    }

findMaxPositionForRootTask(Task rootTask)

  Look at the method of obtaining the maximum position of findMaxPositionForRootTask (Task rootTask):

    private int findMaxPositionForRootTask(Task rootTask) {
    
    
        for (int i = mChildren.size() - 1; i >= 0; --i) {
    
    
            final WindowContainer curr = mChildren.get(i);
            // Since a root task could be repositioned while still being one of the children, we
            // check if 'curr' is the same root task and skip it if so
            final boolean sameRootTask = curr == rootTask;
            if (getPriority(curr) <= getPriority(rootTask) && !sameRootTask) {
    
    
                return i;
            }
        }
        return 0;
    }
    private int getPriority(WindowContainer child) {
    
    
        final TaskDisplayArea tda = child.asTaskDisplayArea();
        if (tda != null) {
    
    
            // Use the top child priority as the TaskDisplayArea priority.
            return tda.getPriority(tda.getTopChild());
        }
        final Task rootTask = child.asTask();
        if (mWmService.mAssistantOnTopOfDream && rootTask.isActivityTypeAssistant()) return 4;
        if (rootTask.isActivityTypeDream()) return 3;
        if (rootTask.inPinnedWindowingMode()) return 2;
        if (rootTask.isAlwaysOnTop()) return 1;
        return 0;
    }    

  findMaxPositionForRootTask() looks for subclasses from top to bottom. If a priority is found that is lower than or equal to it, and the subclass is different from the currently added Task object, the position is found.
  Look at the task priority method getPriority(), if mWmService.mAssistantOnTopOfDream is true, and isActivityTypeAssistant(), its priority is up to 4; if isActivityTypeDream(), the priority is 3; if WindowMode is WINDOWING_MODE_PINNED, the priority The priority is 2; if isAlwaysOnTop() has a priority of 1, the others have a priority of 0.
  You also need to look at isAlwaysOnTop() of the Task class:

    @Override
    public boolean isAlwaysOnTop() {
    
    
        return !isForceHidden() && super.isAlwaysOnTop();
    }

  !isForceHidden() means that the mForceHiddenFlags flag is not set; super.isAlwaysOnTop() is the isAlwaysOnTop() of the parent ConfigurationContainer class, which mainly depends on the configuration of its member variable mFullConfiguration, in the isAlwaysOnTop() of the WindowConfiguration class:

    public boolean isAlwaysOnTop() {
    
    
        if (mWindowingMode == WINDOWING_MODE_PINNED) return true;
        if (mActivityType == ACTIVITY_TYPE_DREAM) return true;
        if (mAlwaysOnTop != ALWAYS_ON_TOP_ON) return false;
        return mWindowingMode == WINDOWING_MODE_FREEFORM
                    || mWindowingMode == WINDOWING_MODE_MULTI_WINDOW;
    }

  It can be seen that if mWindowingMode is WINDOWING_MODE_PINNED and mActivityType is ACTIVITY_TYPE_DREAM, both are set to true. The mode is WINDOWING_MODE_PINNED, which is a lock mode of Android. After it is turned on, the user can only operate in the current APP, so it isAlwaysOnTop(). ACTIVITY_TYPE_DREAM is related to the wallpaper, and it must be on the upper level.
  In addition, in other cases, if mAlwaysOnTop is not set to ALWAYS_ON_TOP_ON, it will directly return false. If it is set, check whether mWindowingMode is WINDOWING_MODE_FREEFORM or WINDOWING_MODE_MULTI_WINDOW, if it is these two, it is also true.

findMinPositionForRootTask(Task rootTask)

    private int findMinPositionForRootTask(Task rootTask) {
    
    
        int minPosition = POSITION_BOTTOM;
        for (int i = 0; i < mChildren.size(); ++i) {
    
    
            if (getPriority(mChildren.get(i)) < getPriority(rootTask)) {
    
    
                minPosition = i;
            } else {
    
    
                break;
            }
        }

        if (rootTask.isAlwaysOnTop()) {
    
    
            // Since a root task could be repositioned while still being one of the children, we
            // check if this always-on-top root task already exists and if so, set the minPosition
            // to its previous position.
            // Use mChildren.indexOf instead of getTaskIndexOf because we need to place the rootTask
            // as a direct child.
            final int currentIndex = mChildren.indexOf(rootTask);
            if (currentIndex > minPosition) {
    
    
                minPosition = currentIndex;
            }
        }
        return minPosition;
    }

  The minimum position is the last position in the child with a lower priority than the task. If the task is always on top, and the task is already a child, the minimum position takes where it is currently.
  findPositionForRootTask() then sets its value according to the parameter requestedPosition. Then assign it to targetPosition, then take the minimum value of it and the previously calculated maximum position maxPosition, and then take the maximum value of minPosition between it and the previously calculated minimum position.
  If targetPosition != requestedPosition, that is, targetPosition takes a value from the minPosition or maxPosition just calculated. We know that when calculating these two values ​​just now, its priority is lower than the current task, so the position needs to be increased by 1. Therefore, when the parameter adding is true, the corresponding position is increased by 1.
  It also mentions a targetPosition < prevPosition situation, which should be the case of transferring the child from a high position to a low position. In this case, also increment by 1.
  This gets the position added to the child.

3. The addChild(task, position) method of the parent class

  The implementation is in addChild(E child, int index) of the WindowContainer class:

    @CallSuper
    void addChild(E child, int index) {
    
    
        if (!child.mReparenting && child.getParent() != null) {
    
    
            throw new IllegalArgumentException("addChild: container=" + child.getName()
                    + " is already a child of container=" + child.getParent().getName()
                    + " can't add to container=" + getName()
                    + "\n callers=" + Debug.getCallers(15, "\n"));
        }

        if ((index < 0 && index != POSITION_BOTTOM)
                || (index > mChildren.size() && index != POSITION_TOP)) {
    
    
            throw new IllegalArgumentException("addChild: invalid position=" + index
                    + ", children number=" + mChildren.size());
        }

        if (index == POSITION_TOP) {
    
    
            index = mChildren.size();
        } else if (index == POSITION_BOTTOM) {
    
    
            index = 0;
        }

        mChildren.add(index, child);

        // Set the parent after we've actually added a child in case a subclass depends on this.
        child.setParent(this);
    }

  If index is POSITION_TOP or POSITION_BOTTOM, set its value to the top or bottom. Then add the parameter task child to its children collection mChildren.
  Finally call the child's setParent(this) method. setParent(this) of the WindowContainer class:

    final protected void setParent(WindowContainer<WindowContainer> parent) {
    
    
        final WindowContainer oldParent = mParent;
        mParent = parent;

        if (mParent != null) {
    
    
            mParent.onChildAdded(this);
        }
        if (!mReparenting) {
    
    
            onSyncReparent(oldParent, mParent);
            if (mParent != null && mParent.mDisplayContent != null
                    && mDisplayContent != mParent.mDisplayContent) {
    
    
                onDisplayChanged(mParent.mDisplayContent);
            }
            onParentChanged(mParent, oldParent);
        }
    }

  1. Put the original parent container into the oldParent variable, and then set the new container object to mParent.
  2. The set parent container is not null, call its onChildAdded(this) to tell the parent container that it has added a child.
  3. mReparenting is performing the reparenting operation. When its value is false, that is, when the reparenting operation is not performed, onSyncReparent(oldParent, mParent) will be executed.
  4. When the reparenting operation is not performed, if the screen display object of the current object is different from that of the parent container, call onDisplayChanged(), execute, and the display screen is replaced.
  5. When the reparenting operation is not performed, execute the onParentChanged(mParent, oldParent) method to change the parent container.
  Here we mainly talk about the content of steps 2, 4, and 5.

onChildAdded(this)

  The implementation code of onChildAdded(this) is in the WindowContainer class:

    private void onChildAdded(WindowContainer child) {
    
    
        mTreeWeight += child.mTreeWeight;
        WindowContainer parent = getParent();
        while (parent != null) {
    
    
            parent.mTreeWeight += child.mTreeWeight;
            parent = parent.getParent();
        }
        onChildPositionChanged(child);
    }

  It mainly increases the height of the number. The last is to call the onChildPositionChanged(child) method. We know that the current parent container is the TaskDisplayArea object. See the implementation of this method in the TaskDisplayArea class:

    @Override
    void onChildPositionChanged(WindowContainer child) {
    
    
        super.onChildPositionChanged(child);
        mRootWindowContainer.invalidateTaskLayers();
    }

  The main thing is to call onChildPositionChanged(child) of the parent class. In the parent class, the type is checked. If the types are inconsistent, an exception will be reported.

onDisplayChanged() of the Task class

    @Override
    void onDisplayChanged(DisplayContent dc) {
    
    
        final boolean isRootTask = isRootTask();
        if (!isRootTask) {
    
    
            adjustBoundsForDisplayChangeIfNeeded(dc);
        }
        super.onDisplayChanged(dc);
        if (isLeafTask()) {
    
    
            final int displayId = (dc != null) ? dc.getDisplayId() : INVALID_DISPLAY;
            mWmService.mAtmService.getTaskChangeNotificationController().notifyTaskDisplayChanged(
                    mTaskId, displayId);
        }
        if (isRootTask()) {
    
    
            updateSurfaceBounds();
        }
    }

  Mainly, the onDisplayChanged(dc) method of the parent class is called. If it is a leaf task, it will execute the task display screen change notification. If it is a root task, it will perform the operation of updating the frame of the display interface.
  Its parent class is WindowContainer, look at its implementation:

    void onDisplayChanged(DisplayContent dc) {
    
    
        if (mDisplayContent != null && mDisplayContent.mChangingContainers.remove(this)) {
    
    
            // Cancel any change transition queued-up for this container on the old display.
            mSurfaceFreezer.unfreeze(getPendingTransaction());
        }
        mDisplayContent = dc;
        if (dc != null && dc != this) {
    
    
            dc.getPendingTransaction().merge(mPendingTransaction);
        }
        for (int i = mChildren.size() - 1; i >= 0; --i) {
    
    
            final WindowContainer child = mChildren.get(i);
            child.onDisplayChanged(dc);
        }
        for (int i = mListeners.size() - 1; i >= 0; --i) {
    
    
            mListeners.get(i).onDisplayChanged(dc);
        }
    }

  The assignment of mDisplayContent is realized here. And merge the mPendingTransaction of the object into the mPendingTransaction of the display screen object.
  If the object has children, the child's onDisplayChanged(dc) will be called. If the callback interface is registered, execute the callback.

Then set WindowMode

  build() code segment six:

            // Set windowing mode after attached to display area or it abort silently.
            if (mWindowingMode != WINDOWING_MODE_UNDEFINED) {
    
    
                task.setWindowingMode(mWindowingMode, true /* creating */);
            }
            return task;
        }

  When we add Home Task, the mWindowingMode passed in is WINDOWING_MODE_UNDEFINED. So this step is skipped.

  This adds the Home Task to the window hierarchy as a child of the TaskDisplayArea object.

Guess you like

Origin blog.csdn.net/q1165328963/article/details/127841486