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 Point(), 181 false /* isTaskClearedForReuse */, 182 false /* isTaskFragmentClearedForPip */, 183 false /* isClearedForReorderActivityToFront */, 184 new Point()); 185 } 186 createActivityInfoWithMinDimensions()187 static ActivityInfo createActivityInfoWithMinDimensions() { 188 ActivityInfo aInfo = new ActivityInfo(); 189 final Rect primaryBounds = getSplitBounds(true /* isPrimary */); 190 aInfo.windowLayout = new ActivityInfo.WindowLayout(0, 0, 0, 0, 0, 191 primaryBounds.width() + 1, primaryBounds.height() + 1); 192 return aInfo; 193 } 194 createTestTaskContainer()195 static TaskContainer createTestTaskContainer() { 196 Resources resources = mock(Resources.class); 197 doReturn(new Configuration()).when(resources).getConfiguration(); 198 Activity activity = mock(Activity.class); 199 doReturn(resources).when(activity).getResources(); 200 doReturn(DEFAULT_DISPLAY).when(activity).getDisplayId(); 201 202 return new TaskContainer(TASK_ID, activity); 203 } 204 createTestTaskContainer(@onNull SplitController controller)205 static TaskContainer createTestTaskContainer(@NonNull SplitController controller) { 206 final TaskContainer taskContainer = createTestTaskContainer(); 207 final int taskId = taskContainer.getTaskId(); 208 // Should not call to create TaskContainer with the same task id twice. 209 assertFalse(controller.mTaskContainers.contains(taskId)); 210 controller.mTaskContainers.put(taskId, taskContainer); 211 return taskContainer; 212 } 213 createWindowLayoutInfo()214 static WindowLayoutInfo createWindowLayoutInfo() { 215 final FoldingFeature foldingFeature = new FoldingFeature( 216 new Rect( 217 TASK_BOUNDS.left, 218 TASK_BOUNDS.top + TASK_BOUNDS.height() / 2 - 5, 219 TASK_BOUNDS.right, 220 TASK_BOUNDS.top + TASK_BOUNDS.height() / 2 + 5 221 ), 222 FoldingFeature.TYPE_HINGE, 223 FoldingFeature.STATE_HALF_OPENED); 224 final List<DisplayFeature> displayFeatures = new ArrayList<>(); 225 displayFeatures.add(foldingFeature); 226 return new WindowLayoutInfo(displayFeatures); 227 } 228 createActivityBuilder( @onNull Predicate<Activity> activityPredicate, @NonNull Predicate<Intent> intentPredicate)229 static ActivityRule.Builder createActivityBuilder( 230 @NonNull Predicate<Activity> activityPredicate, 231 @NonNull Predicate<Intent> intentPredicate) { 232 return new ActivityRule.Builder(activityPredicate, intentPredicate); 233 } 234 createSplitPairRuleBuilder( @onNull Predicate<Pair<Activity, Activity>> activitiesPairPredicate, @NonNull Predicate<Pair<Activity, Intent>> activityIntentPairPredicate, @NonNull Predicate<WindowMetrics> windowMetricsPredicate)235 static SplitPairRule.Builder createSplitPairRuleBuilder( 236 @NonNull Predicate<Pair<Activity, Activity>> activitiesPairPredicate, 237 @NonNull Predicate<Pair<Activity, Intent>> activityIntentPairPredicate, 238 @NonNull Predicate<WindowMetrics> windowMetricsPredicate) { 239 return new SplitPairRule.Builder(activitiesPairPredicate, activityIntentPairPredicate, 240 windowMetricsPredicate); 241 } 242 createSplitPlaceholderRuleBuilder( @onNull Intent placeholderIntent, @NonNull Predicate<Activity> activityPredicate, @NonNull Predicate<Intent> intentPredicate, @NonNull Predicate<WindowMetrics> windowMetricsPredicate)243 static SplitPlaceholderRule.Builder createSplitPlaceholderRuleBuilder( 244 @NonNull Intent placeholderIntent, @NonNull Predicate<Activity> activityPredicate, 245 @NonNull Predicate<Intent> intentPredicate, 246 @NonNull Predicate<WindowMetrics> windowMetricsPredicate) { 247 return new SplitPlaceholderRule.Builder(placeholderIntent, activityPredicate, 248 intentPredicate, windowMetricsPredicate); 249 } 250 } 251