Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit eca505ed authored by Charles Chen's avatar Charles Chen Committed by Chris Li
Browse files

Implement the runtime APIs

Implement the following APIs:
- finishActivityStacks
Finish a set of ActivityStacks.
It is useful to expand the primary container.

- invalidateTopVisibleSplitAttributes:
Update the container actively and trigger the SplitAttrbutes
calculatator function.
It is useful to update SplitAttributes regardless of
device and window state change.

- updateSplitAttributes
Update the SplitAttributes of a specific split and also
overrides its default SplitAttributes.
It is useful if an app doesn't set its SplitAttributes
calculator function, but want to customize SplitAttributes of
a split pair without updating the rule.

Bug: 263565444
Test: presubmit

Change-Id: Icc502f5e106d380ed1e5656445fbd20a1a391c8e
parent bef72a40
Loading
Loading
Loading
Loading
+56 −12
Original line number Diff line number Diff line
@@ -21,9 +21,11 @@ import android.os.Binder;
import android.os.IBinder;
import android.util.Pair;
import android.util.Size;
import android.window.TaskFragmentParentInfo;
import android.window.WindowContainerTransaction;

import androidx.annotation.NonNull;
import androidx.window.extensions.core.util.function.Function;

/**
 * Client-side descriptor of a split that holds two containers.
@@ -35,8 +37,12 @@ class SplitContainer {
    private final TaskFragmentContainer mSecondaryContainer;
    @NonNull
    private final SplitRule mSplitRule;
    /** @see SplitContainer#getCurrentSplitAttributes() */
    @NonNull
    private SplitAttributes mSplitAttributes;
    private SplitAttributes mCurrentSplitAttributes;
    /** @see SplitContainer#getDefaultSplitAttributes() */
    @NonNull
    private SplitAttributes mDefaultSplitAttributes;
    @NonNull
    private final IBinder mToken;

@@ -48,7 +54,8 @@ class SplitContainer {
        mPrimaryContainer = primaryContainer;
        mSecondaryContainer = secondaryContainer;
        mSplitRule = splitRule;
        mSplitAttributes = splitAttributes;
        mDefaultSplitAttributes = splitRule.getDefaultSplitAttributes();
        mCurrentSplitAttributes = splitAttributes;
        mToken = new Binder("SplitContainer");

        if (shouldFinishPrimaryWithSecondary(splitRule)) {
@@ -82,9 +89,37 @@ class SplitContainer {
        return mSplitRule;
    }

    /**
     * Returns the current {@link SplitAttributes} this {@code SplitContainer} is showing.
     * <p>
     * If the {@code SplitAttributes} calculator function is not set by
     * {@link SplitController#setSplitAttributesCalculator(Function)}, the current
     * {@code SplitAttributes} is either to expand the containers if the size constraints of
     * {@link #getSplitRule()} are not satisfied,
     * or the {@link #getDefaultSplitAttributes()}, otherwise.
     * </p><p>
     * If the {@code SplitAttributes} calculator function is set, the current
     * {@code SplitAttributes} will be customized by the function, which can be any
     * {@code SplitAttributes}.
     * </p>
     *
     * @see SplitAttributes.SplitType.ExpandContainersSplitType
     */
    @NonNull
    SplitAttributes getSplitAttributes() {
        return mSplitAttributes;
    SplitAttributes getCurrentSplitAttributes() {
        return mCurrentSplitAttributes;
    }

    /**
     * Returns the default {@link SplitAttributes} when the parent task container bounds satisfy
     * {@link #getSplitRule()} constraints.
     * <p>
     * The value is usually from {@link SplitRule#getDefaultSplitAttributes} unless it is overridden
     * by {@link SplitController#updateSplitAttributes(IBinder, SplitAttributes)}.
     */
    @NonNull
    SplitAttributes getDefaultSplitAttributes() {
        return mDefaultSplitAttributes;
    }

    @NonNull
@@ -95,11 +130,19 @@ class SplitContainer {
    /**
     * Updates the {@link SplitAttributes} to this container.
     * It is usually used when there's a folding state change or
     * {@link SplitController#onTaskFragmentParentInfoChanged(WindowContainerTransaction, int,
     * Configuration)}.
     * {@link SplitController#onTaskFragmentParentInfoChanged(WindowContainerTransaction,
     * int, TaskFragmentParentInfo)}.
     */
    void updateCurrentSplitAttributes(@NonNull SplitAttributes splitAttributes) {
        mCurrentSplitAttributes = splitAttributes;
    }

    /**
     * Overrides the default {@link SplitAttributes} to this container, which may be different
     * from {@link SplitRule#getDefaultSplitAttributes}.
     */
    void setSplitAttributes(@NonNull SplitAttributes splitAttributes) {
        mSplitAttributes = splitAttributes;
    void updateDefaultSplitAttributes(@NonNull SplitAttributes splitAttributes) {
        mDefaultSplitAttributes = splitAttributes;
    }

    @NonNull
@@ -121,7 +164,7 @@ class SplitContainer {
    @NonNull
    SplitInfo toSplitInfo() {
        return new SplitInfo(mPrimaryContainer.toActivityStack(),
                mSecondaryContainer.toActivityStack(), mSplitAttributes, mToken);
                mSecondaryContainer.toActivityStack(), mCurrentSplitAttributes, mToken);
    }

    static boolean shouldFinishPrimaryWithSecondary(@NonNull SplitRule splitRule) {
@@ -180,9 +223,10 @@ class SplitContainer {
    public String toString() {
        return "SplitContainer{"
                + " primaryContainer=" + mPrimaryContainer
                + " secondaryContainer=" + mSecondaryContainer
                + " splitRule=" + mSplitRule
                + " splitAttributes" + mSplitAttributes
                + ", secondaryContainer=" + mSecondaryContainer
                + ", splitRule=" + mSplitRule
                + ", currentSplitAttributes" + mCurrentSplitAttributes
                + ", defaultSplitAttributes" + mDefaultSplitAttributes
                + "}";
    }
}
+165 −47
Original line number Diff line number Diff line
@@ -87,6 +87,7 @@ import androidx.window.extensions.layout.WindowLayoutComponentImpl;
import com.android.internal.annotations.VisibleForTesting;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Set;
@@ -279,6 +280,98 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
        }
    }

    @Override
    public void finishActivityStacks(@NonNull Set<IBinder> activityStackTokens) {
        if (activityStackTokens.isEmpty()) {
            return;
        }
        synchronized (mLock) {
            // Translate ActivityStack to TaskFragmentContainer.
            final List<TaskFragmentContainer> pendingFinishingContainers =
                    activityStackTokens.stream()
                    .map(token -> {
                        synchronized (mLock) {
                            return getContainer(token);
                        }
                    }).filter(Objects::nonNull)
                    .toList();

            if (pendingFinishingContainers.isEmpty()) {
                return;
            }
            // Start transaction with close transit type.
            final TransactionRecord transactionRecord = mTransactionManager.startNewTransaction();
            transactionRecord.setOriginType(TASK_FRAGMENT_TRANSIT_CLOSE);
            final WindowContainerTransaction wct = transactionRecord.getTransaction();

            forAllTaskContainers(taskContainer -> {
                synchronized (mLock) {
                    final List<TaskFragmentContainer> containers = taskContainer.mContainers;
                    // Clean up the TaskFragmentContainers by the z-order from the lowest.
                    for (int i = 0; i < containers.size() - 1; i++) {
                        final TaskFragmentContainer container = containers.get(i);
                        if (pendingFinishingContainers.contains(container)) {
                            // Don't update records here to prevent double invocation.
                            container.finish(false /* shouldFinishDependant */, mPresenter,
                                    wct, this, false /* shouldRemoveRecord */);
                        }
                    }
                    // Remove container records.
                    removeContainers(taskContainer, pendingFinishingContainers);
                    // Update the change to the client side.
                    updateContainersInTaskIfVisible(wct, taskContainer.getTaskId());
                }
            });

            // Apply the transaction.
            transactionRecord.apply(false /* shouldApplyIndependently */);
        }
    }

    @Override
    public void invalidateTopVisibleSplitAttributes() {
        synchronized (mLock) {
            WindowContainerTransaction wct = mTransactionManager.startNewTransaction()
                    .getTransaction();
            forAllTaskContainers(taskContainer -> {
                synchronized (mLock) {
                    updateContainersInTaskIfVisible(wct, taskContainer.getTaskId());
                }
            });
            mTransactionManager.getCurrentTransactionRecord()
                    .apply(false /* shouldApplyIndependently */);
        }
    }

    @GuardedBy("mLock")
    private void forAllTaskContainers(@NonNull Consumer<TaskContainer> callback) {
        for (int i = mTaskContainers.size() - 1; i >= 0; --i) {
            callback.accept(mTaskContainers.valueAt(i));
        }
    }

    @Override
    public void updateSplitAttributes(@NonNull IBinder splitInfoToken,
            @NonNull SplitAttributes splitAttributes) {
        synchronized (mLock) {
            final SplitContainer splitContainer = getSplitContainer(splitInfoToken);
            if (splitContainer == null) {
                Log.w(TAG, "Cannot find SplitContainer for token:" + splitInfoToken);
                return;
            }
            WindowContainerTransaction wct = mTransactionManager.startNewTransaction()
                    .getTransaction();
            if (updateSplitContainerIfNeeded(splitContainer, wct, splitAttributes)) {
                splitContainer.updateDefaultSplitAttributes(splitAttributes);
                mTransactionManager.getCurrentTransactionRecord()
                        .apply(false /* shouldApplyIndependently */);
            } else {
                // Abort if the SplitContainer wasn't updated.
                mTransactionManager.getCurrentTransactionRecord().abort();
            }
        }
    }

    /**
     * Called when the transaction is ready so that the organizer can update the TaskFragments based
     * on the changes in transaction.
@@ -648,35 +741,6 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
        }
    }

    /** Returns whether the given {@link TaskContainer} may show in split. */
    // Suppress GuardedBy warning because lint asks to mark this method as
    // @GuardedBy(mPresenter.mController.mLock), which is mLock itself
    @SuppressWarnings("GuardedBy")
    @GuardedBy("mLock")
    private boolean mayShowSplit(@NonNull TaskContainer taskContainer) {
        // No split inside PIP.
        if (taskContainer.isInPictureInPicture()) {
            return false;
        }
        // Always assume the TaskContainer if SplitAttributesCalculator is set
        if (mSplitAttributesCalculator != null) {
            return true;
        }
        // Check if the parent container bounds can support any split rule.
        for (EmbeddingRule rule : mSplitRules) {
            if (!(rule instanceof SplitRule)) {
                continue;
            }
            final SplitRule splitRule = (SplitRule) rule;
            final SplitAttributes splitAttributes = mPresenter.computeSplitAttributes(
                    taskContainer.getTaskProperties(), splitRule, null /* minDimensionsPair */);
            if (shouldShowSplit(splitAttributes)) {
                return true;
            }
        }
        return false;
    }

    @VisibleForTesting
    @GuardedBy("mLock")
    void onActivityCreated(@NonNull WindowContainerTransaction wct,
@@ -1360,20 +1424,33 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
     * Removes the container from bookkeeping records.
     */
    void removeContainer(@NonNull TaskFragmentContainer container) {
        removeContainers(container.getTaskContainer(), Collections.singletonList(container));
    }

    /**
     * Removes containers from bookkeeping records.
     */
    void removeContainers(@NonNull TaskContainer taskContainer,
            @NonNull List<TaskFragmentContainer> containers) {
        // Remove all split containers that included this one
        final TaskContainer taskContainer = container.getTaskContainer();
        taskContainer.mContainers.remove(container);
        taskContainer.mContainers.removeAll(containers);
        // Marked as a pending removal which will be removed after it is actually removed on the
        // server side (#onTaskFragmentVanished).
        // In this way, we can keep track of the Task bounds until we no longer have any
        // TaskFragment there.
        taskContainer.mFinishedContainer.add(container.getTaskFragmentToken());
        taskContainer.mFinishedContainer.addAll(containers.stream().map(
                TaskFragmentContainer::getTaskFragmentToken).toList());

        // Cleanup any split references.
        final List<SplitContainer> containersToRemove = new ArrayList<>();
        for (SplitContainer splitContainer : taskContainer.mSplitContainers) {
            if (container.equals(splitContainer.getSecondaryContainer())
                    || container.equals(splitContainer.getPrimaryContainer())) {
            if (containersToRemove.contains(splitContainer)) {
                // Don't need to check because it has been in the remove list.
                continue;
            }
            if (containers.stream().anyMatch(container ->
                    splitContainer.getPrimaryContainer().equals(container)
                            || splitContainer.getSecondaryContainer().equals(container))) {
                containersToRemove.add(splitContainer);
            }
        }
@@ -1381,7 +1458,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen

        // Cleanup any dependent references.
        for (TaskFragmentContainer containerToUpdate : taskContainer.mContainers) {
            containerToUpdate.removeContainerToFinishOnExit(container);
            containerToUpdate.removeContainersToFinishOnExit(containers);
        }
    }

@@ -1461,26 +1538,53 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
        if (splitContainer == null) {
            return;
        }

        updateSplitContainerIfNeeded(splitContainer, wct, null /* splitAttributes */);
    }

    /**
     * Updates {@link SplitContainer} with the given {@link SplitAttributes} if the
     * {@link SplitContainer} is the top most and not finished. If passed {@link SplitAttributes}
     * are {@code null}, the {@link SplitAttributes} will be calculated with
     * {@link SplitPresenter#computeSplitAttributes(TaskContainer.TaskProperties, SplitRule, Pair)}.
     *
     * @param splitContainer The {@link SplitContainer} to update
     * @param splitAttributes Update with this {@code splitAttributes} if it is not {@code null}.
     *                        Otherwise, use the value calculated by
     *                        {@link SplitPresenter#computeSplitAttributes(
     *                        TaskContainer.TaskProperties, SplitRule, Pair)}
     *
     * @return {@code true} if the update succeed. Otherwise, returns {@code false}.
     */
    @GuardedBy("mLock")
    private boolean updateSplitContainerIfNeeded(@NonNull SplitContainer splitContainer,
            @NonNull WindowContainerTransaction wct, @Nullable SplitAttributes splitAttributes) {
        if (!isTopMostSplit(splitContainer)) {
            // Skip position update - it isn't the topmost split.
            return;
            return false;
        }
        if (splitContainer.getPrimaryContainer().isFinished()
                || splitContainer.getSecondaryContainer().isFinished()) {
            // Skip position update - one or both containers are finished.
            return;
            return false;
        }
        final TaskContainer taskContainer = splitContainer.getTaskContainer();
        if (splitAttributes == null) {
            final TaskContainer.TaskProperties taskProperties = splitContainer.getTaskContainer()
                    .getTaskProperties();
            final SplitRule splitRule = splitContainer.getSplitRule();
            final SplitAttributes defaultSplitAttributes = splitContainer
                    .getDefaultSplitAttributes();
            final Pair<Size, Size> minDimensionsPair = splitContainer.getMinDimensionsPair();
        final SplitAttributes splitAttributes = mPresenter.computeSplitAttributes(
                taskContainer.getTaskProperties(), splitRule, minDimensionsPair);
        splitContainer.setSplitAttributes(splitAttributes);
            splitAttributes = mPresenter.computeSplitAttributes(taskProperties, splitRule,
                    defaultSplitAttributes, minDimensionsPair);
        }
        splitContainer.updateCurrentSplitAttributes(splitAttributes);
        if (dismissPlaceholderIfNecessary(wct, splitContainer)) {
            // Placeholder was finished, the positions will be updated when its container is emptied
            return;
            return true;
        }
        mPresenter.updateSplitContainer(splitContainer, container, wct);
        mPresenter.updateSplitContainer(splitContainer, wct);
        return true;
    }

    /** Whether the given split is the topmost split in the Task. */
@@ -1576,7 +1680,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
        final Pair<Size, Size> minDimensionsPair = getActivityIntentMinDimensionsPair(activity,
                placeholderRule.getPlaceholderIntent());
        final SplitAttributes splitAttributes = mPresenter.computeSplitAttributes(taskProperties,
                placeholderRule, minDimensionsPair);
                placeholderRule, placeholderRule.getDefaultSplitAttributes(), minDimensionsPair);
        if (!SplitPresenter.shouldShowSplit(splitAttributes)) {
            return false;
        }
@@ -1655,7 +1759,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
            // The placeholder should remain after it was first shown.
            return false;
        }
        final SplitAttributes splitAttributes = splitContainer.getSplitAttributes();
        final SplitAttributes splitAttributes = splitContainer.getCurrentSplitAttributes();
        if (SplitPresenter.shouldShowSplit(splitAttributes)) {
            return false;
        }
@@ -1797,6 +1901,20 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
        return null;
    }

    @Nullable
    @GuardedBy("mLock")
    SplitContainer getSplitContainer(@NonNull IBinder token) {
        for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
            final List<SplitContainer> containers = mTaskContainers.valueAt(i).mSplitContainers;
            for (SplitContainer container : containers) {
                if (container.getToken().equals(token)) {
                    return container;
                }
            }
        }
        return null;
    }

    @Nullable
    @GuardedBy("mLock")
    TaskContainer getTaskContainer(int taskId) {
+10 −17
Original line number Diff line number Diff line
@@ -179,7 +179,7 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
        final Pair<Size, Size> minDimensionsPair = getActivityIntentMinDimensionsPair(
                primaryActivity, secondaryIntent);
        final SplitAttributes splitAttributes = computeSplitAttributes(taskProperties, rule,
                minDimensionsPair);
                rule.getDefaultSplitAttributes(), minDimensionsPair);
        final Rect primaryRelBounds = getRelBoundsForPosition(POSITION_START, taskProperties,
                splitAttributes);
        final TaskFragmentContainer primaryContainer = prepareContainerForActivity(wct,
@@ -225,7 +225,7 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
        final Pair<Size, Size> minDimensionsPair = getActivitiesMinDimensionsPair(primaryActivity,
                secondaryActivity);
        final SplitAttributes splitAttributes = computeSplitAttributes(taskProperties, rule,
                minDimensionsPair);
                rule.getDefaultSplitAttributes(), minDimensionsPair);
        final Rect primaryRelBounds = getRelBoundsForPosition(POSITION_START, taskProperties,
                splitAttributes);
        final TaskFragmentContainer primaryContainer = prepareContainerForActivity(wct,
@@ -334,11 +334,9 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
    /**
     * Updates the positions of containers in an existing split.
     * @param splitContainer The split container to be updated.
     * @param updatedContainer The task fragment that was updated and caused this split update.
     * @param wct WindowContainerTransaction that this update should be performed with.
     */
    void updateSplitContainer(@NonNull SplitContainer splitContainer,
            @NonNull TaskFragmentContainer updatedContainer,
            @NonNull WindowContainerTransaction wct) {
        // Getting the parent configuration using the updated container - it will have the recent
        // value.
@@ -348,8 +346,9 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
        if (activity == null) {
            return;
        }
        final TaskProperties taskProperties = getTaskProperties(updatedContainer);
        final SplitAttributes splitAttributes = splitContainer.getSplitAttributes();
        final TaskContainer taskContainer = splitContainer.getTaskContainer();
        final TaskProperties taskProperties = taskContainer.getTaskProperties();
        final SplitAttributes splitAttributes = splitContainer.getCurrentSplitAttributes();
        final Rect primaryRelBounds = getRelBoundsForPosition(POSITION_START, taskProperties,
                splitAttributes);
        final Rect secondaryRelBounds = getRelBoundsForPosition(POSITION_END, taskProperties,
@@ -370,7 +369,6 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
            // When placeholder is shown in split, we should keep the focus on the primary.
            wct.requestFocusOnTaskFragment(primaryContainer.getTaskFragmentToken());
        }
        final TaskContainer taskContainer = updatedContainer.getTaskContainer();
        final int windowingMode = taskContainer.getWindowingModeForSplitTaskFragment(
                primaryRelBounds);
        updateTaskFragmentWindowingModeIfRegistered(wct, primaryContainer, windowingMode);
@@ -515,9 +513,9 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
        // Expand the splitContainer if minimum dimensions are not satisfied.
        final TaskContainer taskContainer = splitContainer.getTaskContainer();
        final SplitAttributes splitAttributes = sanitizeSplitAttributes(
                taskContainer.getTaskProperties(), splitContainer.getSplitAttributes(),
                taskContainer.getTaskProperties(), splitContainer.getCurrentSplitAttributes(),
                minDimensionsPair);
        splitContainer.setSplitAttributes(splitAttributes);
        splitContainer.updateCurrentSplitAttributes(splitAttributes);
        if (!shouldShowSplit(splitAttributes)) {
            // If the client side hasn't received TaskFragmentInfo yet, we can't change TaskFragment
            // bounds. Return failure to create a new SplitContainer which fills task bounds.
@@ -540,7 +538,7 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
    }

    static boolean shouldShowSplit(@NonNull SplitContainer splitContainer) {
        return shouldShowSplit(splitContainer.getSplitAttributes());
        return shouldShowSplit(splitContainer.getCurrentSplitAttributes());
    }

    static boolean shouldShowSplit(@NonNull SplitAttributes splitAttributes) {
@@ -549,12 +547,12 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {

    @NonNull
    SplitAttributes computeSplitAttributes(@NonNull TaskProperties taskProperties,
            @NonNull SplitRule rule, @Nullable Pair<Size, Size> minDimensionsPair) {
            @NonNull SplitRule rule, @NonNull SplitAttributes defaultSplitAttributes,
            @Nullable Pair<Size, Size> minDimensionsPair) {
        final Configuration taskConfiguration = taskProperties.getConfiguration();
        final WindowMetrics taskWindowMetrics = getTaskWindowMetrics(taskConfiguration);
        final Function<SplitAttributesCalculatorParams, SplitAttributes> calculator =
                mController.getSplitAttributesCalculator();
        final SplitAttributes defaultSplitAttributes = rule.getDefaultSplitAttributes();
        final boolean areDefaultConstraintsSatisfied = rule.checkParentMetrics(taskWindowMetrics);
        if (calculator == null) {
            if (!areDefaultConstraintsSatisfied) {
@@ -956,11 +954,6 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
        return bounds.width() > bounds.height();
    }

    @NonNull
    static TaskProperties getTaskProperties(@NonNull TaskFragmentContainer container) {
        return container.getTaskContainer().getTaskProperties();
    }

    @NonNull
    TaskProperties getTaskProperties(@NonNull Activity activity) {
        final TaskContainer taskContainer = mController.getTaskContainer(
+2 −11
Original line number Diff line number Diff line
@@ -107,12 +107,6 @@ class TaskContainer {
        return mIsVisible;
    }

    @NonNull
    Configuration getConfiguration() {
        // Make a copy in case the config is updated unexpectedly.
        return new Configuration(mConfiguration);
    }

    @NonNull
    TaskProperties getTaskProperties() {
        return new TaskProperties(mDisplayId, mConfiguration);
@@ -157,7 +151,7 @@ class TaskContainer {

    @WindowingMode
    private int getWindowingMode() {
        return getConfiguration().windowConfiguration.getWindowingMode();
        return mConfiguration.windowConfiguration.getWindowingMode();
    }

    /** Whether there is any {@link TaskFragmentContainer} below this Task. */
@@ -220,10 +214,7 @@ class TaskContainer {
        }
    }

    /**
     * A wrapper class which contains the display ID and {@link Configuration} of a
     * {@link TaskContainer}
     */
    /** A wrapper class which contains the information of {@link TaskContainer} */
    static final class TaskProperties {
        private final int mDisplayId;
        @NonNull
+23 −3

File changed.

Preview size limit exceeded, changes collapsed.

Loading