• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2022 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package androidx.window.extensions.embedding;
18 
19 import static android.app.ActivityTaskManager.INVALID_TASK_ID;
20 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
21 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
22 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
23 import static android.app.WindowConfiguration.inMultiWindowMode;
24 
25 import android.app.Activity;
26 import android.app.ActivityClient;
27 import android.app.WindowConfiguration;
28 import android.app.WindowConfiguration.WindowingMode;
29 import android.content.res.Configuration;
30 import android.graphics.Rect;
31 import android.os.IBinder;
32 import android.util.ArraySet;
33 import android.util.Log;
34 import android.window.TaskFragmentInfo;
35 import android.window.TaskFragmentParentInfo;
36 import android.window.WindowContainerTransaction;
37 
38 import androidx.annotation.NonNull;
39 import androidx.annotation.Nullable;
40 
41 import java.util.ArrayList;
42 import java.util.List;
43 import java.util.Set;
44 
45 /** Represents TaskFragments and split pairs below a Task. */
46 class TaskContainer {
47     private static final String TAG = TaskContainer.class.getSimpleName();
48 
49     /** The unique task id. */
50     private final int mTaskId;
51 
52     /** Active TaskFragments in this Task. */
53     @NonNull
54     final List<TaskFragmentContainer> mContainers = new ArrayList<>();
55 
56     /** Active split pairs in this Task. */
57     @NonNull
58     final List<SplitContainer> mSplitContainers = new ArrayList<>();
59 
60     @NonNull
61     private final Configuration mConfiguration;
62 
63     private int mDisplayId;
64 
65     private boolean mIsVisible;
66 
67     /**
68      * TaskFragments that the organizer has requested to be closed. They should be removed when
69      * the organizer receives
70      * {@link SplitController#onTaskFragmentVanished(WindowContainerTransaction, TaskFragmentInfo)}
71      * event for them.
72      */
73     final Set<IBinder> mFinishedContainer = new ArraySet<>();
74 
75     /**
76      * The {@link TaskContainer} constructor
77      *
78      * @param taskId The ID of the Task, which must match {@link Activity#getTaskId()} with
79      *               {@code activityInTask}.
80      * @param activityInTask The {@link Activity} in the Task with {@code taskId}. It is used to
81      *                       initialize the {@link TaskContainer} properties.
82      *
83      */
TaskContainer(int taskId, @NonNull Activity activityInTask)84     TaskContainer(int taskId, @NonNull Activity activityInTask) {
85         if (taskId == INVALID_TASK_ID) {
86             throw new IllegalArgumentException("Invalid Task id");
87         }
88         mTaskId = taskId;
89         final TaskProperties taskProperties = TaskProperties
90                 .getTaskPropertiesFromActivity(activityInTask);
91         mConfiguration = taskProperties.getConfiguration();
92         mDisplayId = taskProperties.getDisplayId();
93         // Note that it is always called when there's a new Activity is started, which implies
94         // the host task is visible.
95         mIsVisible = true;
96     }
97 
getTaskId()98     int getTaskId() {
99         return mTaskId;
100     }
101 
getDisplayId()102     int getDisplayId() {
103         return mDisplayId;
104     }
105 
isVisible()106     boolean isVisible() {
107         return mIsVisible;
108     }
109 
110     @NonNull
getConfiguration()111     Configuration getConfiguration() {
112         // Make a copy in case the config is updated unexpectedly.
113         return new Configuration(mConfiguration);
114     }
115 
116     @NonNull
getTaskProperties()117     TaskProperties getTaskProperties() {
118         return new TaskProperties(mDisplayId, mConfiguration);
119     }
120 
updateTaskFragmentParentInfo(@onNull TaskFragmentParentInfo info)121     void updateTaskFragmentParentInfo(@NonNull TaskFragmentParentInfo info) {
122         mConfiguration.setTo(info.getConfiguration());
123         mDisplayId = info.getDisplayId();
124         mIsVisible = info.isVisible();
125     }
126 
127     /**
128      * Returns the windowing mode for the TaskFragments below this Task, which should be split with
129      * other TaskFragments.
130      *
131      * @param taskFragmentBounds    Requested bounds for the TaskFragment. It will be empty when
132      *                              the pair of TaskFragments are stacked due to the limited space.
133      */
134     @WindowingMode
getWindowingModeForSplitTaskFragment(@ullable Rect taskFragmentBounds)135     int getWindowingModeForSplitTaskFragment(@Nullable Rect taskFragmentBounds) {
136         // Only set to multi-windowing mode if the pair are showing side-by-side. Otherwise, it
137         // will be set to UNDEFINED which will then inherit the Task windowing mode.
138         if (taskFragmentBounds == null || taskFragmentBounds.isEmpty() || isInPictureInPicture()) {
139             return WINDOWING_MODE_UNDEFINED;
140         }
141         // We use WINDOWING_MODE_MULTI_WINDOW when the Task is fullscreen.
142         // However, when the Task is in other multi windowing mode, such as Freeform, we need to
143         // have the activity windowing mode to match the Task, otherwise things like
144         // DecorCaptionView won't work correctly. As a result, have the TaskFragment to be in the
145         // Task windowing mode if the Task is in multi window.
146         // TODO we won't need this anymore after we migrate Freeform caption to WM Shell.
147         return isInMultiWindow() ? getWindowingMode() : WINDOWING_MODE_MULTI_WINDOW;
148     }
149 
isInPictureInPicture()150     boolean isInPictureInPicture() {
151         return getWindowingMode() == WINDOWING_MODE_PINNED;
152     }
153 
isInMultiWindow()154     boolean isInMultiWindow() {
155         return WindowConfiguration.inMultiWindowMode(getWindowingMode());
156     }
157 
158     @WindowingMode
getWindowingMode()159     private int getWindowingMode() {
160         return getConfiguration().windowConfiguration.getWindowingMode();
161     }
162 
163     /** Whether there is any {@link TaskFragmentContainer} below this Task. */
isEmpty()164     boolean isEmpty() {
165         return mContainers.isEmpty() && mFinishedContainer.isEmpty();
166     }
167 
168     /** Called when the activity is destroyed. */
onActivityDestroyed(@onNull IBinder activityToken)169     void onActivityDestroyed(@NonNull IBinder activityToken) {
170         for (TaskFragmentContainer container : mContainers) {
171             container.onActivityDestroyed(activityToken);
172         }
173     }
174 
175     /** Removes the pending appeared activity from all TaskFragments in this Task. */
cleanupPendingAppearedActivity(@onNull IBinder activityToken)176     void cleanupPendingAppearedActivity(@NonNull IBinder activityToken) {
177         for (TaskFragmentContainer container : mContainers) {
178             container.removePendingAppearedActivity(activityToken);
179         }
180     }
181 
182     @Nullable
getTopTaskFragmentContainer()183     TaskFragmentContainer getTopTaskFragmentContainer() {
184         if (mContainers.isEmpty()) {
185             return null;
186         }
187         return mContainers.get(mContainers.size() - 1);
188     }
189 
190     @Nullable
getTopNonFinishingActivity()191     Activity getTopNonFinishingActivity() {
192         for (int i = mContainers.size() - 1; i >= 0; i--) {
193             final Activity activity = mContainers.get(i).getTopNonFinishingActivity();
194             if (activity != null) {
195                 return activity;
196             }
197         }
198         return null;
199     }
200 
indexOf(@onNull TaskFragmentContainer child)201     int indexOf(@NonNull TaskFragmentContainer child) {
202         return mContainers.indexOf(child);
203     }
204 
205     /** Whether the Task is in an intermediate state waiting for the server update.*/
isInIntermediateState()206     boolean isInIntermediateState() {
207         for (TaskFragmentContainer container : mContainers) {
208             if (container.isInIntermediateState()) {
209                 // We are in an intermediate state to wait for server update on this TaskFragment.
210                 return true;
211             }
212         }
213         return false;
214     }
215 
216     /** Adds the descriptors of split states in this Task to {@code outSplitStates}. */
getSplitStates(@onNull List<SplitInfo> outSplitStates)217     void getSplitStates(@NonNull List<SplitInfo> outSplitStates) {
218         for (SplitContainer container : mSplitContainers) {
219             outSplitStates.add(container.toSplitInfo());
220         }
221     }
222 
223     /**
224      * A wrapper class which contains the display ID and {@link Configuration} of a
225      * {@link TaskContainer}
226      */
227     static final class TaskProperties {
228         private final int mDisplayId;
229         @NonNull
230         private final Configuration mConfiguration;
231 
TaskProperties(int displayId, @NonNull Configuration configuration)232         TaskProperties(int displayId, @NonNull Configuration configuration) {
233             mDisplayId = displayId;
234             mConfiguration = configuration;
235         }
236 
getDisplayId()237         int getDisplayId() {
238             return mDisplayId;
239         }
240 
241         @NonNull
getConfiguration()242         Configuration getConfiguration() {
243             return mConfiguration;
244         }
245 
246         /**
247          * Obtains the {@link TaskProperties} for the task that the provided {@link Activity} is
248          * associated with.
249          * <p>
250          * Note that for most case, caller should use
251          * {@link SplitPresenter#getTaskProperties(Activity)} instead. This method is used before
252          * the {@code activity} goes into split.
253          * </p><p>
254          * If the {@link Activity} is in fullscreen, override
255          * {@link WindowConfiguration#getBounds()} with {@link WindowConfiguration#getMaxBounds()}
256          * in case the {@link Activity} is letterboxed. Otherwise, get the Task
257          * {@link Configuration} from the server side or use {@link Activity}'s
258          * {@link Configuration} as a fallback if the Task {@link Configuration} cannot be obtained.
259          */
260         @NonNull
getTaskPropertiesFromActivity(@onNull Activity activity)261         static TaskProperties getTaskPropertiesFromActivity(@NonNull Activity activity) {
262             final int displayId = activity.getDisplayId();
263             // Use a copy of configuration because activity's configuration may be updated later,
264             // or we may get unexpected TaskContainer's configuration if Activity's configuration is
265             // updated. An example is Activity is going to be in split.
266             final Configuration activityConfig = new Configuration(
267                     activity.getResources().getConfiguration());
268             final WindowConfiguration windowConfiguration = activityConfig.windowConfiguration;
269             final int windowingMode = windowConfiguration.getWindowingMode();
270             if (!inMultiWindowMode(windowingMode)) {
271                 // Use the max bounds in fullscreen in case the Activity is letterboxed.
272                 windowConfiguration.setBounds(windowConfiguration.getMaxBounds());
273                 return new TaskProperties(displayId, activityConfig);
274             }
275             final Configuration taskConfig = ActivityClient.getInstance()
276                     .getTaskConfiguration(activity.getActivityToken());
277             if (taskConfig == null) {
278                 Log.w(TAG, "Could not obtain task configuration for activity:" + activity);
279                 // Still report activity config if task config cannot be obtained from the server
280                 // side.
281                 return new TaskProperties(displayId, activityConfig);
282             }
283             return new TaskProperties(displayId, taskConfig);
284         }
285     }
286 }
287