1 /* 2 * Copyright (C) 2020 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 package com.android.quickstep.util; 17 18 import static org.mockito.ArgumentMatchers.any; 19 import static org.mockito.Mockito.doReturn; 20 import static org.mockito.Mockito.mock; 21 import static org.mockito.Mockito.when; 22 23 import android.content.Context; 24 import android.content.res.Configuration; 25 import android.graphics.Point; 26 import android.graphics.Rect; 27 import android.graphics.RectF; 28 import android.util.ArrayMap; 29 import android.util.DisplayMetrics; 30 import android.view.RemoteAnimationTarget; 31 import android.view.Surface; 32 33 import androidx.test.ext.junit.runners.AndroidJUnit4; 34 import androidx.test.filters.SmallTest; 35 36 import com.android.launcher3.DeviceProfile; 37 import com.android.launcher3.InvariantDeviceProfile; 38 import com.android.launcher3.dagger.LauncherAppComponent; 39 import com.android.launcher3.dagger.LauncherAppSingleton; 40 import com.android.launcher3.util.AllModulesMinusWMProxy; 41 import com.android.launcher3.util.DisplayController; 42 import com.android.launcher3.util.DisplayController.Info; 43 import com.android.launcher3.util.LauncherModelHelper; 44 import com.android.launcher3.util.NavigationMode; 45 import com.android.launcher3.util.RotationUtils; 46 import com.android.launcher3.util.WindowBounds; 47 import com.android.launcher3.util.window.CachedDisplayInfo; 48 import com.android.launcher3.util.window.WindowManagerProxy; 49 import com.android.quickstep.FallbackActivityInterface; 50 import com.android.quickstep.util.SurfaceTransaction.MockProperties; 51 52 import dagger.BindsInstance; 53 import dagger.Component; 54 55 import org.hamcrest.Description; 56 import org.hamcrest.TypeSafeMatcher; 57 import org.junit.Assert; 58 import org.junit.Test; 59 import org.junit.runner.RunWith; 60 61 import java.util.ArrayList; 62 import java.util.List; 63 64 @SmallTest 65 @RunWith(AndroidJUnit4.class) 66 public class TaskViewSimulatorTest { 67 68 @Test taskProperlyScaled_portrait_noRotation_sameInsets1()69 public void taskProperlyScaled_portrait_noRotation_sameInsets1() { 70 new TaskMatrixVerifier() 71 .withLauncherSize(1200, 2450) 72 .withDensityDpi(420) 73 .withInsets(new Rect(0, 80, 0, 120)) 74 .verifyNoTransforms(); 75 } 76 77 @Test taskProperlyScaled_portrait_noRotation_sameInsets2()78 public void taskProperlyScaled_portrait_noRotation_sameInsets2() { 79 new TaskMatrixVerifier() 80 .withLauncherSize(1200, 2450) 81 .withDensityDpi(420) 82 .withInsets(new Rect(55, 80, 55, 120)) 83 .verifyNoTransforms(); 84 } 85 86 @Test taskProperlyScaled_landscape_noRotation_sameInsets1()87 public void taskProperlyScaled_landscape_noRotation_sameInsets1() { 88 new TaskMatrixVerifier() 89 .withLauncherSize(2450, 1250) 90 .withDensityDpi(420) 91 .withInsets(new Rect(0, 80, 0, 40)) 92 .verifyNoTransforms(); 93 } 94 95 @Test taskProperlyScaled_landscape_noRotation_sameInsets2()96 public void taskProperlyScaled_landscape_noRotation_sameInsets2() { 97 new TaskMatrixVerifier() 98 .withLauncherSize(2450, 1250) 99 .withDensityDpi(420) 100 .withInsets(new Rect(0, 80, 120, 0)) 101 .verifyNoTransforms(); 102 } 103 104 @Test taskProperlyScaled_landscape_noRotation_sameInsets3()105 public void taskProperlyScaled_landscape_noRotation_sameInsets3() { 106 new TaskMatrixVerifier() 107 .withLauncherSize(2450, 1250) 108 .withDensityDpi(420) 109 .withInsets(new Rect(55, 80, 55, 120)) 110 .verifyNoTransforms(); 111 } 112 113 @Test taskProperlyScaled_landscape_rotated()114 public void taskProperlyScaled_landscape_rotated() { 115 new TaskMatrixVerifier() 116 .withLauncherSize(1200, 2450) 117 .withDensityDpi(420) 118 .withInsets(new Rect(0, 80, 0, 120)) 119 .withAppBounds( 120 new Rect(0, 0, 2450, 1200), 121 new Rect(0, 80, 0, 120), 122 Surface.ROTATION_90) 123 .verifyNoTransforms(); 124 } 125 126 private static class TaskMatrixVerifier extends TransformParams { 127 128 private Point mDisplaySize = new Point(); 129 private int mDensityDpi = DisplayMetrics.DENSITY_DEFAULT; 130 private Rect mDisplayInsets = new Rect(); 131 private Rect mAppBounds = new Rect(); 132 private Rect mLauncherInsets = new Rect(); 133 134 private Rect mAppInsets; 135 136 private int mAppRotation = -1; 137 private DeviceProfile mDeviceProfile; 138 withLauncherSize(int width, int height)139 TaskMatrixVerifier withLauncherSize(int width, int height) { 140 mDisplaySize.set(width, height); 141 if (mAppBounds.isEmpty()) { 142 mAppBounds.set(0, 0, width, height); 143 } 144 return this; 145 } 146 withDensityDpi(int densityDpi)147 TaskMatrixVerifier withDensityDpi(int densityDpi) { 148 mDensityDpi = densityDpi; 149 return this; 150 } 151 withInsets(Rect insets)152 TaskMatrixVerifier withInsets(Rect insets) { 153 mDisplayInsets.set(insets); 154 mLauncherInsets.set(insets); 155 return this; 156 } 157 withAppBounds(Rect bounds, Rect insets, int appRotation)158 TaskMatrixVerifier withAppBounds(Rect bounds, Rect insets, int appRotation) { 159 mAppBounds.set(bounds); 160 mAppInsets = insets; 161 mAppRotation = appRotation; 162 return this; 163 } 164 verifyNoTransforms()165 void verifyNoTransforms() { 166 LauncherModelHelper helper = new LauncherModelHelper(); 167 try { 168 DisplayController mockController = mock(DisplayController.class); 169 170 helper.sandboxContext.initDaggerComponent( 171 DaggerTaskViewSimulatorTest_TaskViewSimulatorTestComponent.builder() 172 .bindDisplayController(mockController)); 173 int rotation = mDisplaySize.x > mDisplaySize.y 174 ? Surface.ROTATION_90 : Surface.ROTATION_0; 175 CachedDisplayInfo cdi = new CachedDisplayInfo(mDisplaySize, rotation); 176 WindowBounds wm = new WindowBounds( 177 new Rect(0, 0, mDisplaySize.x, mDisplaySize.y), 178 mDisplayInsets); 179 List<WindowBounds> allBounds = new ArrayList<>(4); 180 for (int i = 0; i < 4; i++) { 181 Rect boundsR = new Rect(wm.bounds); 182 Rect insetsR = new Rect(wm.insets); 183 184 RotationUtils.rotateRect(insetsR, RotationUtils.deltaRotation(rotation, i)); 185 RotationUtils.rotateRect(boundsR, RotationUtils.deltaRotation(rotation, i)); 186 boundsR.set(0, 0, Math.abs(boundsR.width()), Math.abs(boundsR.height())); 187 allBounds.add(new WindowBounds(boundsR, insetsR)); 188 } 189 190 WindowManagerProxy wmProxy = mock(WindowManagerProxy.class); 191 doReturn(cdi).when(wmProxy).getDisplayInfo(any()); 192 doReturn(wm).when(wmProxy).getRealBounds(any(), any()); 193 doReturn(NavigationMode.NO_BUTTON).when(wmProxy).getNavigationMode(any()); 194 195 ArrayMap<CachedDisplayInfo, List<WindowBounds>> perDisplayBoundsCache = 196 new ArrayMap<>(); 197 perDisplayBoundsCache.put(cdi.normalize(wmProxy), allBounds); 198 199 Configuration configuration = new Configuration(); 200 configuration.densityDpi = mDensityDpi; 201 Context configurationContext = helper.sandboxContext.createConfigurationContext( 202 configuration); 203 204 DisplayController.Info info = new Info( 205 configurationContext, wmProxy, perDisplayBoundsCache); 206 when(mockController.getInfo()).thenReturn(info); 207 208 mDeviceProfile = InvariantDeviceProfile.INSTANCE.get(helper.sandboxContext) 209 .getBestMatch(mAppBounds.width(), mAppBounds.height(), rotation); 210 mDeviceProfile.updateInsets(mLauncherInsets); 211 212 TaskViewSimulator tvs = new TaskViewSimulator(helper.sandboxContext, 213 FallbackActivityInterface.INSTANCE, false, 0); 214 tvs.setDp(mDeviceProfile); 215 216 int launcherRotation = info.rotation; 217 if (mAppRotation < 0) { 218 mAppRotation = launcherRotation; 219 } 220 221 tvs.getOrientationState().update(launcherRotation, mAppRotation); 222 if (mAppInsets == null) { 223 mAppInsets = new Rect(mLauncherInsets); 224 } 225 tvs.setPreviewBounds(mAppBounds, mAppInsets); 226 227 tvs.fullScreenProgress.value = 1; 228 tvs.recentsViewScale.value = tvs.getFullScreenScale(); 229 tvs.apply(this); 230 } finally { 231 helper.destroy(); 232 } 233 } 234 235 @Override createSurfaceParams(BuilderProxy proxy)236 public SurfaceTransaction createSurfaceParams(BuilderProxy proxy) { 237 RecordingSurfaceTransaction transaction = new RecordingSurfaceTransaction(); 238 proxy.onBuildTargetParams( 239 transaction.mockProperties, mock(RemoteAnimationTarget.class), this); 240 return transaction; 241 } 242 243 @Override applySurfaceParams(SurfaceTransaction params)244 public void applySurfaceParams(SurfaceTransaction params) { 245 Assert.assertTrue(params instanceof RecordingSurfaceTransaction); 246 MockProperties p = ((RecordingSurfaceTransaction) params).mockProperties; 247 248 // Verify that the task position remains the same 249 RectF newAppBounds = new RectF(mAppBounds); 250 p.matrix.mapRect(newAppBounds); 251 Assert.assertThat(newAppBounds, new AlmostSame(mAppBounds)); 252 253 System.err.println("Bounds mapped: " + mAppBounds + " => " + newAppBounds); 254 } 255 } 256 257 private static class AlmostSame extends TypeSafeMatcher<RectF> { 258 259 // Allow .1% error margin to account for float to int conversions 260 private final float mErrorFactor = .001f; 261 private final Rect mExpected; 262 AlmostSame(Rect expected)263 AlmostSame(Rect expected) { 264 mExpected = expected; 265 } 266 267 @Override matchesSafely(RectF item)268 protected boolean matchesSafely(RectF item) { 269 float errorWidth = mErrorFactor * mExpected.width(); 270 float errorHeight = mErrorFactor * mExpected.height(); 271 return Math.abs(item.left - mExpected.left) < errorWidth 272 && Math.abs(item.top - mExpected.top) < errorHeight 273 && Math.abs(item.right - mExpected.right) < errorWidth 274 && Math.abs(item.bottom - mExpected.bottom) < errorHeight; 275 } 276 277 @Override describeTo(Description description)278 public void describeTo(Description description) { 279 description.appendValue(mExpected); 280 } 281 } 282 283 @LauncherAppSingleton 284 @Component(modules = {AllModulesMinusWMProxy.class}) 285 interface TaskViewSimulatorTestComponent extends LauncherAppComponent { 286 287 @Component.Builder 288 interface Builder extends LauncherAppComponent.Builder { 289 290 @BindsInstance bindDisplayController(DisplayController controller)291 Builder bindDisplayController(DisplayController controller); 292 build()293 TaskViewSimulatorTestComponent build(); 294 } 295 } 296 } 297