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.view.Display.DEFAULT_DISPLAY;
20 
21 import static androidx.window.extensions.embedding.SplitRule.FINISH_ALWAYS;
22 import static androidx.window.extensions.embedding.SplitRule.FINISH_NEVER;
23 
24 import static org.junit.Assert.assertFalse;
25 import static org.mockito.Mockito.doReturn;
26 import static org.mockito.Mockito.mock;
27 
28 import android.annotation.NonNull;
29 import android.app.Activity;
30 import android.content.Intent;
31 import android.content.pm.ActivityInfo;
32 import android.content.res.Configuration;
33 import android.content.res.Resources;
34 import android.graphics.Point;
35 import android.graphics.Rect;
36 import android.util.Pair;
37 import android.view.WindowMetrics;
38 import android.window.TaskFragmentInfo;
39 import android.window.WindowContainerToken;
40 
41 import androidx.window.extensions.core.util.function.Predicate;
42 import androidx.window.extensions.embedding.SplitAttributes.SplitType;
43 import androidx.window.extensions.layout.DisplayFeature;
44 import androidx.window.extensions.layout.FoldingFeature;
45 import androidx.window.extensions.layout.WindowLayoutInfo;
46 
47 import java.util.ArrayList;
48 import java.util.Collections;
49 import java.util.List;
50 
51 // Suppress GuardedBy warning on unit tests
52 @SuppressWarnings("GuardedBy")
53 public class EmbeddingTestUtils {
54     static final Rect TASK_BOUNDS = new Rect(0, 0, 600, 1200);
55     static final int TASK_ID = 10;
56     static final SplitType SPLIT_TYPE = SplitType.RatioSplitType.splitEqually();
57     static final SplitAttributes SPLIT_ATTRIBUTES = new SplitAttributes.Builder().build();
58     static final String TEST_TAG = "test";
59     /** Default finish behavior in Jetpack. */
60     static final int DEFAULT_FINISH_PRIMARY_WITH_SECONDARY = FINISH_NEVER;
61     static final int DEFAULT_FINISH_SECONDARY_WITH_PRIMARY = FINISH_ALWAYS;
62     private static final float SPLIT_RATIO = 0.5f;
63 
EmbeddingTestUtils()64     private EmbeddingTestUtils() {}
65 
66     /** Gets the bounds of a TaskFragment that is in split. */
getSplitBounds(boolean isPrimary)67     static Rect getSplitBounds(boolean isPrimary) {
68         return getSplitBounds(isPrimary, false /* shouldSplitHorizontally */);
69     }
70 
71     /** Gets the bounds of a TaskFragment that is in split. */
getSplitBounds(boolean isPrimary, boolean shouldSplitHorizontally)72     static Rect getSplitBounds(boolean isPrimary, boolean shouldSplitHorizontally) {
73         final int dimension = (int) (
74                 (shouldSplitHorizontally ? TASK_BOUNDS.height() : TASK_BOUNDS.width())
75                         * SPLIT_RATIO);
76         if (shouldSplitHorizontally) {
77             return isPrimary
78                     ? new Rect(
79                             TASK_BOUNDS.left,
80                             TASK_BOUNDS.top,
81                             TASK_BOUNDS.right,
82                             TASK_BOUNDS.top + dimension)
83                     : new Rect(
84                             TASK_BOUNDS.left,
85                             TASK_BOUNDS.top + dimension,
86                             TASK_BOUNDS.right,
87                             TASK_BOUNDS.bottom);
88         }
89         return isPrimary
90                 ? new Rect(
91                         TASK_BOUNDS.left,
92                         TASK_BOUNDS.top,
93                         TASK_BOUNDS.left + dimension,
94                         TASK_BOUNDS.bottom)
95                 : new Rect(
96                         TASK_BOUNDS.left + dimension,
97                         TASK_BOUNDS.top,
98                         TASK_BOUNDS.right,
99                         TASK_BOUNDS.bottom);
100     }
101 
102     /** Creates a rule to always split the given activity and the given intent. */
createSplitRule(@onNull Activity primaryActivity, @NonNull Intent secondaryIntent)103     static SplitRule createSplitRule(@NonNull Activity primaryActivity,
104             @NonNull Intent secondaryIntent) {
105         return createSplitRule(primaryActivity, secondaryIntent, true /* clearTop */);
106     }
107 
108     /** Creates a rule to always split the given activity and the given intent. */
createSplitRule(@onNull Activity primaryActivity, @NonNull Intent secondaryIntent, boolean clearTop)109     static SplitRule createSplitRule(@NonNull Activity primaryActivity,
110             @NonNull Intent secondaryIntent, boolean clearTop) {
111         final Pair<Activity, Intent> targetPair = new Pair<>(primaryActivity, secondaryIntent);
112         return createSplitPairRuleBuilder(
113                 activityPair -> false,
114                 targetPair::equals,
115                 w -> true)
116                 .setDefaultSplitAttributes(
117                         new SplitAttributes.Builder()
118                                 .setSplitType(SPLIT_TYPE)
119                                 .build()
120                 )
121                 .setShouldClearTop(clearTop)
122                 .setFinishPrimaryWithSecondary(DEFAULT_FINISH_PRIMARY_WITH_SECONDARY)
123                 .setFinishSecondaryWithPrimary(DEFAULT_FINISH_SECONDARY_WITH_PRIMARY)
124                 .setTag(TEST_TAG)
125                 .build();
126     }
127 
128     /** Creates a rule to always split the given activities. */
createSplitRule(@onNull Activity primaryActivity, @NonNull Activity secondaryActivity)129     static SplitRule createSplitRule(@NonNull Activity primaryActivity,
130             @NonNull Activity secondaryActivity) {
131         return createSplitRule(primaryActivity, secondaryActivity,
132                 DEFAULT_FINISH_PRIMARY_WITH_SECONDARY, DEFAULT_FINISH_SECONDARY_WITH_PRIMARY,
133                 true /* clearTop */);
134     }
135 
136     /** Creates a rule to always split the given activities. */
createSplitRule(@onNull Activity primaryActivity, @NonNull Activity secondaryActivity, boolean clearTop)137     static SplitRule createSplitRule(@NonNull Activity primaryActivity,
138             @NonNull Activity secondaryActivity, boolean clearTop) {
139         return createSplitRule(primaryActivity, secondaryActivity,
140                 DEFAULT_FINISH_PRIMARY_WITH_SECONDARY, DEFAULT_FINISH_SECONDARY_WITH_PRIMARY,
141                 clearTop);
142     }
143 
144     /** Creates a rule to always split the given activities with the given finish behaviors. */
createSplitRule(@onNull Activity primaryActivity, @NonNull Activity secondaryActivity, int finishPrimaryWithSecondary, int finishSecondaryWithPrimary, boolean clearTop)145     static SplitRule createSplitRule(@NonNull Activity primaryActivity,
146             @NonNull Activity secondaryActivity, int finishPrimaryWithSecondary,
147             int finishSecondaryWithPrimary, boolean clearTop) {
148         final Pair<Activity, Activity> targetPair = new Pair<>(primaryActivity, secondaryActivity);
149         return createSplitPairRuleBuilder(
150                 targetPair::equals,
151                 activityIntentPair -> false,
152                 w -> true)
153                 .setDefaultSplitAttributes(
154                         new SplitAttributes.Builder()
155                                 .setSplitType(SPLIT_TYPE)
156                                 .build()
157                 )
158                 .setFinishPrimaryWithSecondary(finishPrimaryWithSecondary)
159                 .setFinishSecondaryWithPrimary(finishSecondaryWithPrimary)
160                 .setShouldClearTop(clearTop)
161                 .setTag(TEST_TAG)
162                 .build();
163     }
164 
165     /** Creates a mock TaskFragmentInfo for the given TaskFragment. */
createMockTaskFragmentInfo(@onNull TaskFragmentContainer container, @NonNull Activity activity)166     static TaskFragmentInfo createMockTaskFragmentInfo(@NonNull TaskFragmentContainer container,
167             @NonNull Activity activity) {
168         return createMockTaskFragmentInfo(container, activity, true /* isVisible */);
169     }
170 
171     /** Creates a mock TaskFragmentInfo for the given TaskFragment. */
createMockTaskFragmentInfo(@onNull TaskFragmentContainer container, @NonNull Activity activity, boolean isVisible)172     static TaskFragmentInfo createMockTaskFragmentInfo(@NonNull TaskFragmentContainer container,
173             @NonNull Activity activity, boolean isVisible) {
174         return new TaskFragmentInfo(container.getTaskFragmentToken(),
175                 mock(WindowContainerToken.class),
176                 new Configuration(),
177                 1,
178                 isVisible,
179                 Collections.singletonList(activity.getActivityToken()),
180                 new ArrayList<>(),
181                 new Point(),
182                 false /* isTaskClearedForReuse */,
183                 false /* isTaskFragmentClearedForPip */,
184                 false /* isClearedForReorderActivityToFront */,
185                 new Point());
186     }
187 
createActivityInfoWithMinDimensions()188     static ActivityInfo createActivityInfoWithMinDimensions() {
189         ActivityInfo aInfo = new ActivityInfo();
190         final Rect primaryBounds = getSplitBounds(true /* isPrimary */);
191         aInfo.windowLayout = new ActivityInfo.WindowLayout(0, 0, 0, 0, 0,
192                 primaryBounds.width() + 1, primaryBounds.height() + 1);
193         return aInfo;
194     }
195 
createTestTaskContainer()196     static TaskContainer createTestTaskContainer() {
197         Resources resources = mock(Resources.class);
198         doReturn(new Configuration()).when(resources).getConfiguration();
199         Activity activity = mock(Activity.class);
200         doReturn(resources).when(activity).getResources();
201         doReturn(DEFAULT_DISPLAY).when(activity).getDisplayId();
202 
203         return new TaskContainer(TASK_ID, activity);
204     }
205 
createTestTaskContainer(@onNull SplitController controller)206     static TaskContainer createTestTaskContainer(@NonNull SplitController controller) {
207         final TaskContainer taskContainer = createTestTaskContainer();
208         final int taskId = taskContainer.getTaskId();
209         // Should not call to create TaskContainer with the same task id twice.
210         assertFalse(controller.mTaskContainers.contains(taskId));
211         controller.mTaskContainers.put(taskId, taskContainer);
212         return taskContainer;
213     }
214 
createWindowLayoutInfo()215     static WindowLayoutInfo createWindowLayoutInfo() {
216         final FoldingFeature foldingFeature = new FoldingFeature(
217                 new Rect(
218                         TASK_BOUNDS.left,
219                         TASK_BOUNDS.top + TASK_BOUNDS.height() / 2 - 5,
220                         TASK_BOUNDS.right,
221                         TASK_BOUNDS.top + TASK_BOUNDS.height() / 2 + 5
222                         ),
223                 FoldingFeature.TYPE_HINGE,
224                 FoldingFeature.STATE_HALF_OPENED);
225         final List<DisplayFeature> displayFeatures = new ArrayList<>();
226         displayFeatures.add(foldingFeature);
227         return new WindowLayoutInfo(displayFeatures);
228     }
229 
createActivityBuilder( @onNull Predicate<Activity> activityPredicate, @NonNull Predicate<Intent> intentPredicate)230     static ActivityRule.Builder createActivityBuilder(
231             @NonNull Predicate<Activity> activityPredicate,
232             @NonNull Predicate<Intent> intentPredicate) {
233         return new ActivityRule.Builder(activityPredicate, intentPredicate);
234     }
235 
createSplitPairRuleBuilder( @onNull Predicate<Pair<Activity, Activity>> activitiesPairPredicate, @NonNull Predicate<Pair<Activity, Intent>> activityIntentPairPredicate, @NonNull Predicate<WindowMetrics> windowMetricsPredicate)236     static SplitPairRule.Builder createSplitPairRuleBuilder(
237             @NonNull Predicate<Pair<Activity, Activity>> activitiesPairPredicate,
238             @NonNull Predicate<Pair<Activity, Intent>> activityIntentPairPredicate,
239             @NonNull Predicate<WindowMetrics> windowMetricsPredicate) {
240         return new SplitPairRule.Builder(activitiesPairPredicate, activityIntentPairPredicate,
241                 windowMetricsPredicate);
242     }
243 
createSplitPlaceholderRuleBuilder( @onNull Intent placeholderIntent, @NonNull Predicate<Activity> activityPredicate, @NonNull Predicate<Intent> intentPredicate, @NonNull Predicate<WindowMetrics> windowMetricsPredicate)244     static SplitPlaceholderRule.Builder createSplitPlaceholderRuleBuilder(
245             @NonNull Intent placeholderIntent, @NonNull Predicate<Activity> activityPredicate,
246             @NonNull Predicate<Intent> intentPredicate,
247             @NonNull Predicate<WindowMetrics> windowMetricsPredicate) {
248         return new SplitPlaceholderRule.Builder(placeholderIntent, activityPredicate,
249                 intentPredicate, windowMetricsPredicate);
250     }
251 
252     @NonNull
createTfContainer( @onNull SplitController splitController, @NonNull Activity activity)253     static TaskFragmentContainer createTfContainer(
254             @NonNull SplitController splitController, @NonNull Activity activity) {
255         return createTfContainer(splitController, TASK_ID, activity);
256     }
257 
258     @NonNull
createTfContainer( @onNull SplitController splitController, int taskId, @NonNull Activity activity)259     static TaskFragmentContainer createTfContainer(
260             @NonNull SplitController splitController, int taskId, @NonNull Activity activity) {
261         return new TaskFragmentContainer.Builder(splitController, taskId, activity)
262                 .setPendingAppearedActivity(activity).build();
263     }
264 }
265