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. */ 166 @NonNull createMockTaskFragmentInfo(@onNull TaskFragmentContainer container, @NonNull Activity activity)167 static TaskFragmentInfo createMockTaskFragmentInfo(@NonNull TaskFragmentContainer container, 168 @NonNull Activity activity) { 169 return createMockTaskFragmentInfo(container, activity, true /* isVisible */); 170 } 171 172 /** Creates a mock TaskFragmentInfo for the given TaskFragment. */ 173 @NonNull createMockTaskFragmentInfo(@onNull TaskFragmentContainer container, @NonNull Activity activity, boolean isVisible)174 static TaskFragmentInfo createMockTaskFragmentInfo(@NonNull TaskFragmentContainer container, 175 @NonNull Activity activity, boolean isVisible) { 176 return new TaskFragmentInfo(container.getTaskFragmentToken(), 177 mock(WindowContainerToken.class), 178 new Configuration(), 179 1, 180 isVisible, 181 Collections.singletonList(activity.getActivityToken()), 182 new ArrayList<>(), 183 new Point(), 184 false /* isTaskClearedForReuse */, 185 false /* isTaskFragmentClearedForPip */, 186 false /* isClearedForReorderActivityToFront */, 187 new Point(), 188 false /* isTopChild */); 189 } 190 191 /** Creates a mock TaskFragmentInfo for the given TaskFragment. */ 192 @NonNull createMockTaskFragmentInfo(@onNull TaskFragmentContainer container, @NonNull Activity activity, boolean isVisible, boolean isOnTop)193 static TaskFragmentInfo createMockTaskFragmentInfo(@NonNull TaskFragmentContainer container, 194 @NonNull Activity activity, boolean isVisible, boolean isOnTop) { 195 return new TaskFragmentInfo(container.getTaskFragmentToken(), 196 mock(WindowContainerToken.class), 197 new Configuration(), 198 1, 199 isVisible, 200 Collections.singletonList(activity.getActivityToken()), 201 new ArrayList<>(), 202 new Point(), 203 false /* isTaskClearedForReuse */, 204 false /* isTaskFragmentClearedForPip */, 205 false /* isClearedForReorderActivityToFront */, 206 new Point(), 207 isOnTop); 208 } 209 createActivityInfoWithMinDimensions()210 static ActivityInfo createActivityInfoWithMinDimensions() { 211 ActivityInfo aInfo = new ActivityInfo(); 212 final Rect primaryBounds = getSplitBounds(true /* isPrimary */); 213 aInfo.windowLayout = new ActivityInfo.WindowLayout(0, 0, 0, 0, 0, 214 primaryBounds.width() + 1, primaryBounds.height() + 1); 215 return aInfo; 216 } 217 createTestTaskContainer()218 static TaskContainer createTestTaskContainer() { 219 Resources resources = mock(Resources.class); 220 doReturn(new Configuration()).when(resources).getConfiguration(); 221 Activity activity = mock(Activity.class); 222 doReturn(resources).when(activity).getResources(); 223 doReturn(DEFAULT_DISPLAY).when(activity).getDisplayId(); 224 225 return new TaskContainer(TASK_ID, activity, mock(SplitController.class)); 226 } 227 createTestTaskContainer(@onNull SplitController controller)228 static TaskContainer createTestTaskContainer(@NonNull SplitController controller) { 229 final TaskContainer taskContainer = createTestTaskContainer(); 230 final int taskId = taskContainer.getTaskId(); 231 // Should not call to create TaskContainer with the same task id twice. 232 assertFalse(controller.mTaskContainers.contains(taskId)); 233 controller.mTaskContainers.put(taskId, taskContainer); 234 return taskContainer; 235 } 236 createWindowLayoutInfo()237 static WindowLayoutInfo createWindowLayoutInfo() { 238 final FoldingFeature foldingFeature = new FoldingFeature( 239 new Rect( 240 TASK_BOUNDS.left, 241 TASK_BOUNDS.top + TASK_BOUNDS.height() / 2 - 5, 242 TASK_BOUNDS.right, 243 TASK_BOUNDS.top + TASK_BOUNDS.height() / 2 + 5 244 ), 245 FoldingFeature.TYPE_HINGE, 246 FoldingFeature.STATE_HALF_OPENED); 247 final List<DisplayFeature> displayFeatures = new ArrayList<>(); 248 displayFeatures.add(foldingFeature); 249 return new WindowLayoutInfo(displayFeatures); 250 } 251 createActivityBuilder( @onNull Predicate<Activity> activityPredicate, @NonNull Predicate<Intent> intentPredicate)252 static ActivityRule.Builder createActivityBuilder( 253 @NonNull Predicate<Activity> activityPredicate, 254 @NonNull Predicate<Intent> intentPredicate) { 255 return new ActivityRule.Builder(activityPredicate, intentPredicate); 256 } 257 createSplitPairRuleBuilder( @onNull Predicate<Pair<Activity, Activity>> activitiesPairPredicate, @NonNull Predicate<Pair<Activity, Intent>> activityIntentPairPredicate, @NonNull Predicate<WindowMetrics> windowMetricsPredicate)258 static SplitPairRule.Builder createSplitPairRuleBuilder( 259 @NonNull Predicate<Pair<Activity, Activity>> activitiesPairPredicate, 260 @NonNull Predicate<Pair<Activity, Intent>> activityIntentPairPredicate, 261 @NonNull Predicate<WindowMetrics> windowMetricsPredicate) { 262 return new SplitPairRule.Builder(activitiesPairPredicate, activityIntentPairPredicate, 263 windowMetricsPredicate); 264 } 265 createSplitPlaceholderRuleBuilder( @onNull Intent placeholderIntent, @NonNull Predicate<Activity> activityPredicate, @NonNull Predicate<Intent> intentPredicate, @NonNull Predicate<WindowMetrics> windowMetricsPredicate)266 static SplitPlaceholderRule.Builder createSplitPlaceholderRuleBuilder( 267 @NonNull Intent placeholderIntent, @NonNull Predicate<Activity> activityPredicate, 268 @NonNull Predicate<Intent> intentPredicate, 269 @NonNull Predicate<WindowMetrics> windowMetricsPredicate) { 270 return new SplitPlaceholderRule.Builder(placeholderIntent, activityPredicate, 271 intentPredicate, windowMetricsPredicate); 272 } 273 274 @NonNull createTfContainer( @onNull SplitController splitController, @NonNull Activity activity)275 static TaskFragmentContainer createTfContainer( 276 @NonNull SplitController splitController, @NonNull Activity activity) { 277 return createTfContainer(splitController, TASK_ID, activity); 278 } 279 280 @NonNull createTfContainer( @onNull SplitController splitController, int taskId, @NonNull Activity activity)281 static TaskFragmentContainer createTfContainer( 282 @NonNull SplitController splitController, int taskId, @NonNull Activity activity) { 283 return new TaskFragmentContainer.Builder(splitController, taskId, activity) 284 .setPendingAppearedActivity(activity).build(); 285 } 286 } 287