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