1 /* 2 * Copyright (C) 2016 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 com.android.server.wm; 18 19 import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS; 20 import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW; 21 import static android.view.WindowManager.LayoutParams.FLAG_SECURE; 22 23 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; 24 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; 25 import static com.android.server.wm.TaskSnapshotController.SNAPSHOT_MODE_APP_THEME; 26 import static com.android.server.wm.TaskSnapshotController.SNAPSHOT_MODE_REAL; 27 28 import static org.junit.Assert.assertEquals; 29 import static org.junit.Assert.assertFalse; 30 import static org.junit.Assert.assertNotEquals; 31 import static org.junit.Assert.assertSame; 32 import static org.junit.Assert.assertTrue; 33 import static org.junit.Assert.fail; 34 import static org.mockito.ArgumentMatchers.eq; 35 import static org.mockito.Mockito.verify; 36 import static org.mockito.Mockito.when; 37 38 import android.app.WindowConfiguration; 39 import android.content.ComponentName; 40 import android.content.res.Configuration; 41 import android.graphics.ColorSpace; 42 import android.graphics.PixelFormat; 43 import android.graphics.Point; 44 import android.graphics.Rect; 45 import android.hardware.HardwareBuffer; 46 import android.platform.test.annotations.Presubmit; 47 import android.util.ArraySet; 48 import android.window.TaskSnapshot; 49 50 import androidx.test.filters.SmallTest; 51 52 import com.google.android.collect.Sets; 53 54 import org.junit.Test; 55 import org.junit.runner.RunWith; 56 import org.mockito.Mockito; 57 58 /** 59 * Test class for {@link TaskSnapshotController}. 60 * 61 * Build/Install/Run: 62 * * atest WmTests:TaskSnapshotControllerTest 63 */ 64 @SmallTest 65 @Presubmit 66 @RunWith(WindowTestRunner.class) 67 public class TaskSnapshotControllerTest extends WindowTestsBase { 68 69 @Test testGetClosingApps_closing()70 public void testGetClosingApps_closing() { 71 final WindowState closingWindow = createWindow(null, FIRST_APPLICATION_WINDOW, 72 "closingWindow"); 73 closingWindow.mActivityRecord.commitVisibility( 74 false /* visible */, true /* performLayout */); 75 final ArraySet<ActivityRecord> closingApps = new ArraySet<>(); 76 closingApps.add(closingWindow.mActivityRecord); 77 final ArraySet<Task> closingTasks = new ArraySet<>(); 78 getClosingTasks(closingApps, closingTasks); 79 assertEquals(1, closingTasks.size()); 80 assertEquals(closingWindow.mActivityRecord.getTask(), closingTasks.valueAt(0)); 81 } 82 83 @Test testGetClosingApps_notClosing()84 public void testGetClosingApps_notClosing() { 85 final WindowState closingWindow = createWindow(null, FIRST_APPLICATION_WINDOW, 86 "closingWindow"); 87 final WindowState openingWindow = createAppWindow(closingWindow.getTask(), 88 FIRST_APPLICATION_WINDOW, "openingWindow"); 89 closingWindow.mActivityRecord.commitVisibility( 90 false /* visible */, true /* performLayout */); 91 openingWindow.mActivityRecord.commitVisibility( 92 true /* visible */, true /* performLayout */); 93 final ArraySet<ActivityRecord> closingApps = new ArraySet<>(); 94 closingApps.add(closingWindow.mActivityRecord); 95 final ArraySet<Task> closingTasks = new ArraySet<>(); 96 getClosingTasks(closingApps, closingTasks); 97 assertEquals(0, closingTasks.size()); 98 } 99 100 @Test testGetClosingApps_skipClosingAppsSnapshotTasks()101 public void testGetClosingApps_skipClosingAppsSnapshotTasks() { 102 final WindowState closingWindow = createWindow(null, FIRST_APPLICATION_WINDOW, 103 "closingWindow"); 104 closingWindow.mActivityRecord.commitVisibility( 105 false /* visible */, true /* performLayout */); 106 final ArraySet<ActivityRecord> closingApps = new ArraySet<>(); 107 closingApps.add(closingWindow.mActivityRecord); 108 final ArraySet<Task> closingTasks = new ArraySet<>(); 109 mWm.mTaskSnapshotController.addSkipClosingAppSnapshotTasks( 110 Sets.newArraySet(closingWindow.mActivityRecord.getTask())); 111 getClosingTasks(closingApps, closingTasks); 112 assertEquals(0, closingTasks.size()); 113 } 114 115 /** Retrieves all closing tasks based on the list of closing apps during an app transition. */ getClosingTasks(ArraySet<ActivityRecord> closingApps, ArraySet<Task> outClosingTasks)116 private void getClosingTasks(ArraySet<ActivityRecord> closingApps, 117 ArraySet<Task> outClosingTasks) { 118 outClosingTasks.clear(); 119 for (int i = closingApps.size() - 1; i >= 0; i--) { 120 final ActivityRecord activity = closingApps.valueAt(i); 121 final Task task = activity.getTask(); 122 if (task == null) continue; 123 124 mWm.mTaskSnapshotController.getClosingTasksInner(task, outClosingTasks); 125 } 126 } 127 128 @Test testGetSnapshotMode()129 public void testGetSnapshotMode() { 130 final WindowState disabledWindow = createWindow(null, 131 FIRST_APPLICATION_WINDOW, mDisplayContent, "disabledWindow"); 132 disabledWindow.mActivityRecord.setRecentsScreenshotEnabled(false); 133 assertEquals(SNAPSHOT_MODE_APP_THEME, 134 mWm.mTaskSnapshotController.getSnapshotMode(disabledWindow.getTask())); 135 136 final WindowState normalWindow = createWindow(null, 137 FIRST_APPLICATION_WINDOW, mDisplayContent, "normalWindow"); 138 assertEquals(SNAPSHOT_MODE_REAL, 139 mWm.mTaskSnapshotController.getSnapshotMode(normalWindow.getTask())); 140 141 final WindowState secureWindow = createWindow(null, 142 FIRST_APPLICATION_WINDOW, mDisplayContent, "secureWindow"); 143 secureWindow.mAttrs.flags |= FLAG_SECURE; 144 assertEquals(SNAPSHOT_MODE_APP_THEME, 145 mWm.mTaskSnapshotController.getSnapshotMode(secureWindow.getTask())); 146 } 147 148 @Test testSnapshotBuilder()149 public void testSnapshotBuilder() { 150 final HardwareBuffer buffer = Mockito.mock(HardwareBuffer.class); 151 final ColorSpace sRGB = ColorSpace.get(ColorSpace.Named.SRGB); 152 final long id = 1234L; 153 final ComponentName activityComponent = new ComponentName("package", ".Class"); 154 final int windowingMode = WindowConfiguration.WINDOWING_MODE_FULLSCREEN; 155 final int appearance = APPEARANCE_LIGHT_STATUS_BARS; 156 final int pixelFormat = PixelFormat.RGBA_8888; 157 final int orientation = Configuration.ORIENTATION_PORTRAIT; 158 final float scaleFraction = 0.25f; 159 final Rect contentInsets = new Rect(1, 2, 3, 4); 160 final Rect letterboxInsets = new Rect(5, 6, 7, 8); 161 final Point taskSize = new Point(9, 10); 162 163 try { 164 TaskSnapshot.Builder builder = 165 new TaskSnapshot.Builder(); 166 builder.setId(id); 167 builder.setTopActivityComponent(activityComponent); 168 builder.setAppearance(appearance); 169 builder.setWindowingMode(windowingMode); 170 builder.setColorSpace(sRGB); 171 builder.setOrientation(orientation); 172 builder.setContentInsets(contentInsets); 173 builder.setLetterboxInsets(letterboxInsets); 174 builder.setIsTranslucent(true); 175 builder.setSnapshot(buffer); 176 builder.setIsRealSnapshot(true); 177 builder.setPixelFormat(pixelFormat); 178 builder.setTaskSize(taskSize); 179 180 // Not part of TaskSnapshot itself, used in screenshot process 181 assertEquals(pixelFormat, builder.getPixelFormat()); 182 183 TaskSnapshot snapshot = builder.build(); 184 assertEquals(id, snapshot.getId()); 185 assertEquals(activityComponent, snapshot.getTopActivityComponent()); 186 assertEquals(appearance, snapshot.getAppearance()); 187 assertEquals(windowingMode, snapshot.getWindowingMode()); 188 assertEquals(sRGB, snapshot.getColorSpace()); 189 // Snapshots created with the Builder class are always high-res. The only way to get a 190 // low-res snapshot is to load it from the disk in TaskSnapshotLoader. 191 assertFalse(snapshot.isLowResolution()); 192 assertEquals(orientation, snapshot.getOrientation()); 193 assertEquals(contentInsets, snapshot.getContentInsets()); 194 assertEquals(letterboxInsets, snapshot.getLetterboxInsets()); 195 assertTrue(snapshot.isTranslucent()); 196 assertSame(buffer, snapshot.getHardwareBuffer()); 197 assertTrue(snapshot.isRealSnapshot()); 198 assertEquals(taskSize, snapshot.getTaskSize()); 199 } finally { 200 if (buffer != null) { 201 buffer.close(); 202 } 203 } 204 } 205 206 @SetupWindows(addWindows = { W_ACTIVITY, W_INPUT_METHOD }) 207 @Test testCreateTaskSnapshotWithExcludingIme()208 public void testCreateTaskSnapshotWithExcludingIme() { 209 Task task = mAppWindow.mActivityRecord.getTask(); 210 spyOn(task); 211 spyOn(mDisplayContent); 212 when(task.getDisplayContent().shouldImeAttachedToApp()).thenReturn(false); 213 // Intentionally set the SurfaceControl of input method window as null. 214 mDisplayContent.mInputMethodWindow.setSurfaceControl(null); 215 // Verify no NPE happens when calling createTaskSnapshot. 216 try { 217 final TaskSnapshot.Builder builder = new TaskSnapshot.Builder(); 218 mWm.mTaskSnapshotController.createSnapshot(mAppWindow.mActivityRecord.getTask(), 219 1f /* scaleFraction */, PixelFormat.UNKNOWN, null /* outTaskSize */, builder); 220 } catch (NullPointerException e) { 221 fail("There should be no exception when calling createTaskSnapshot"); 222 } 223 } 224 225 @SetupWindows(addWindows = { W_ACTIVITY, W_INPUT_METHOD }) 226 @Test testCreateTaskSnapshotWithIncludingIme()227 public void testCreateTaskSnapshotWithIncludingIme() { 228 Task task = mAppWindow.mActivityRecord.getTask(); 229 spyOn(task); 230 spyOn(mDisplayContent); 231 spyOn(mDisplayContent.mInputMethodWindow); 232 when(task.getDisplayContent().shouldImeAttachedToApp()).thenReturn(true); 233 // Intentionally set the IME window is in visible state. 234 doReturn(true).when(mDisplayContent.mInputMethodWindow).isVisible(); 235 // Verify no NPE happens when calling createTaskSnapshot. 236 try { 237 final TaskSnapshot.Builder builder = new TaskSnapshot.Builder(); 238 spyOn(builder); 239 mWm.mTaskSnapshotController.createSnapshot( 240 mAppWindow.mActivityRecord.getTask(), 1f /* scaleFraction */, 241 PixelFormat.UNKNOWN, null /* outTaskSize */, builder); 242 // Verify the builder should includes IME surface. 243 verify(builder).setHasImeSurface(eq(true)); 244 builder.setColorSpace(ColorSpace.get(ColorSpace.Named.SRGB)); 245 builder.setTaskSize(new Point(100, 100)); 246 final TaskSnapshot snapshot = builder.build(); 247 assertTrue(snapshot.hasImeSurface()); 248 } catch (NullPointerException e) { 249 fail("There should be no exception when calling createTaskSnapshot"); 250 } 251 } 252 253 @SetupWindows(addWindows = W_ACTIVITY) 254 @Test testPrepareTaskSnapshot()255 public void testPrepareTaskSnapshot() { 256 mAppWindow.mWinAnimator.mLastAlpha = 1f; 257 spyOn(mAppWindow.mWinAnimator); 258 doReturn(true).when(mAppWindow.mWinAnimator).getShown(); 259 doReturn(true).when(mAppWindow.mActivityRecord).isSurfaceShowing(); 260 261 final TaskSnapshot.Builder builder = 262 new TaskSnapshot.Builder(); 263 boolean success = mWm.mTaskSnapshotController.prepareTaskSnapshot( 264 mAppWindow.mActivityRecord.getTask(), PixelFormat.UNKNOWN, builder); 265 266 assertTrue(success); 267 // The pixel format should be selected automatically. 268 assertNotEquals(PixelFormat.UNKNOWN, builder.getPixelFormat()); 269 270 // Snapshot should not be taken while the rotation of activity and task are different. 271 doReturn(true).when(mAppWindow.mActivityRecord).hasFixedRotationTransform(); 272 success = mWm.mTaskSnapshotController.prepareTaskSnapshot( 273 mAppWindow.mActivityRecord.getTask(), PixelFormat.UNKNOWN, builder); 274 275 assertFalse(success); 276 } 277 } 278