1 /* 2 * Copyright (C) 2019 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 android.view.inspector.cts; 18 19 import static org.junit.Assert.assertEquals; 20 21 import android.Manifest; 22 import android.platform.test.annotations.AppModeSdkSandbox; 23 import android.platform.test.annotations.RequiresFlagsEnabled; 24 import android.view.View; 25 import android.view.WindowManager; 26 import android.view.cts.CtsActivity; 27 import android.view.inspector.WindowInspector; 28 29 import androidx.annotation.GuardedBy; 30 import androidx.annotation.NonNull; 31 import androidx.test.ext.junit.rules.ActivityScenarioRule; 32 import androidx.test.filters.SmallTest; 33 import androidx.test.runner.AndroidJUnit4; 34 35 import com.android.compatibility.common.util.AdoptShellPermissionsRule; 36 37 import org.junit.After; 38 import org.junit.Rule; 39 import org.junit.Test; 40 import org.junit.runner.RunWith; 41 42 import java.util.ArrayList; 43 import java.util.List; 44 import java.util.Objects; 45 import java.util.concurrent.CountDownLatch; 46 import java.util.concurrent.Executor; 47 import java.util.concurrent.TimeUnit; 48 import java.util.function.Consumer; 49 50 /** 51 * Tests for {@link WindowInspector}. 52 */ 53 @SmallTest 54 @RunWith(AndroidJUnit4.class) 55 @AppModeSdkSandbox(reason = "Allow test in the SDK sandbox (does not prevent other modes).") 56 public class WindowInspectorTest { 57 58 @Rule(order = 0) 59 public AdoptShellPermissionsRule mAdoptShellPermissionsRule = new AdoptShellPermissionsRule( 60 androidx.test.platform.app.InstrumentationRegistry 61 .getInstrumentation().getUiAutomation(), 62 Manifest.permission.START_ACTIVITIES_FROM_SDK_SANDBOX); 63 64 @Rule(order = 1) 65 public ActivityScenarioRule<CtsActivity> mActivityScenarioRule = 66 new ActivityScenarioRule<>(CtsActivity.class); 67 68 private final List<Consumer<List<View>>> mListeners = new ArrayList<>(); 69 70 @After tearDown()71 public void tearDown() { 72 for (Consumer<List<View>> listener : mListeners) { 73 WindowInspector.removeGlobalWindowViewsListener(listener); 74 } 75 } 76 77 @Test testGetGlobalWindowViews()78 public void testGetGlobalWindowViews() { 79 mActivityScenarioRule 80 .getScenario() 81 .onActivity( 82 (activity) -> { 83 List<View> views = WindowInspector.getGlobalWindowViews(); 84 assertEquals( 85 "Only the activity window view is present", 1, views.size()); 86 87 View view = views.getFirst(); 88 assertEquals( 89 "The activity window view is the decor view", 90 view, 91 activity.getWindow().getDecorView()); 92 }); 93 } 94 95 /** 96 * Tests that when a listener is added the current value of {@link 97 * WindowInspector#getGlobalWindowViews()} is reported. 98 * 99 * @throws InterruptedException when interrupted. 100 */ 101 @Test 102 @RequiresFlagsEnabled(android.view.flags.Flags.FLAG_ROOT_VIEW_CHANGED_LISTENER) testAddRootViewListener_returnsRootView()103 public void testAddRootViewListener_returnsRootView() throws InterruptedException { 104 final RootViewCollector collector = new RootViewCollector(1); 105 addListenerToWindowInspector(Runnable::run, collector); 106 107 final List<View> expected = WindowInspector.getGlobalWindowViews(); 108 collector.waitForElements(); 109 110 final List<List<View>> elements = collector.getElements(); 111 112 assertEquals(1, elements.size()); 113 assertEquals(expected, elements.getFirst()); 114 } 115 116 /** 117 * Tests that when a listener is added a second time then the operation is ignored. 118 * 119 * @throws InterruptedException when interrupted. 120 */ 121 @Test 122 @RequiresFlagsEnabled(android.view.flags.Flags.FLAG_ROOT_VIEW_CHANGED_LISTENER) testAddRootViewListener_doesNotDoubleRegisterListener()123 public void testAddRootViewListener_doesNotDoubleRegisterListener() 124 throws InterruptedException { 125 final RootViewCollector collector = new RootViewCollector(1); 126 addListenerToWindowInspector(Runnable::run, collector); 127 addListenerToWindowInspector(Runnable::run, collector); 128 129 final List<View> expected = WindowInspector.getGlobalWindowViews(); 130 collector.waitForElements(); 131 132 final List<List<View>> elements = collector.getElements(); 133 134 assertEquals(1, elements.size()); 135 assertEquals(expected, elements.getFirst()); 136 } 137 138 /** 139 * Tests that when a {@link View} is added through {@link WindowManager} the new view is 140 * reported to a listener. 141 * 142 * @throws InterruptedException when interrupted. 143 */ 144 @Test 145 @RequiresFlagsEnabled(android.view.flags.Flags.FLAG_ROOT_VIEW_CHANGED_LISTENER) testAddedViewIsReported()146 public void testAddedViewIsReported() throws InterruptedException { 147 final RootViewCollector collector = new RootViewCollector(2); 148 addListenerToWindowInspector(Runnable::run, collector); 149 final List<View> expected = new ArrayList<>(); 150 final List<View> decorView = new ArrayList<>(); 151 152 mActivityScenarioRule 153 .getScenario() 154 .onActivity( 155 (activity) -> { 156 expected.add(activity.getWindow().getDecorView()); 157 decorView.add(activity.getWindow().getDecorView()); 158 159 WindowManager windowManager = activity.getWindowManager(); 160 View view = new View(activity.getApplicationContext()); 161 WindowManager.LayoutParams layoutParams = 162 new WindowManager.LayoutParams(); 163 windowManager.addView(view, layoutParams); 164 165 expected.add(view); 166 }); 167 168 collector.waitForElements(); 169 170 final List<List<View>> elements = collector.getElements(); 171 172 assertEquals(2, elements.size()); 173 assertEquals(decorView, elements.getFirst()); 174 assertEquals(expected, elements.get(1)); 175 } 176 177 /** 178 * Tests that when a {@link View} is removed through {@link WindowManager} then the listener is 179 * updated with the {@link View} removed. 180 * 181 * @throws InterruptedException when interrupted. 182 */ 183 @Test 184 @RequiresFlagsEnabled(android.view.flags.Flags.FLAG_ROOT_VIEW_CHANGED_LISTENER) testRemovedViewIsReported()185 public void testRemovedViewIsReported() throws InterruptedException { 186 final RootViewCollector collector = new RootViewCollector(3); 187 addListenerToWindowInspector(Runnable::run, collector); 188 final List<View> allViews = new ArrayList<>(); 189 final List<View> decorViewList = new ArrayList<>(); 190 191 mActivityScenarioRule 192 .getScenario() 193 .onActivity( 194 (activity) -> { 195 allViews.add(activity.getWindow().getDecorView()); 196 decorViewList.add(activity.getWindow().getDecorView()); 197 198 WindowManager windowManager = activity.getWindowManager(); 199 View view = new View(activity.getApplicationContext()); 200 WindowManager.LayoutParams layoutParams = 201 new WindowManager.LayoutParams(); 202 windowManager.addView(view, layoutParams); 203 204 allViews.add(view); 205 }); 206 mActivityScenarioRule 207 .getScenario() 208 .onActivity( 209 (activity) -> { 210 List<View> rootViews = WindowInspector.getGlobalWindowViews(); 211 212 List<View> viewsToRemove = new ArrayList<>(); 213 View activityView = activity.getWindow().getDecorView(); 214 for (View v : rootViews) { 215 if (!activityView.equals(v)) { 216 viewsToRemove.add(v); 217 } 218 } 219 220 for (View v : viewsToRemove) { 221 activity.getWindowManager().removeView(v); 222 } 223 }); 224 225 collector.waitForElements(); 226 227 final List<List<View>> elements = collector.getElements(); 228 229 assertEquals(3, elements.size()); 230 assertEquals(decorViewList, elements.getFirst()); 231 assertEquals(allViews, elements.get(1)); 232 assertEquals(decorViewList, elements.getLast()); 233 } 234 addListenerToWindowInspector( @onNull Executor executor, @NonNull Consumer<List<View>> consumer)235 private void addListenerToWindowInspector( 236 @NonNull Executor executor, @NonNull Consumer<List<View>> consumer) { 237 WindowInspector.addGlobalWindowViewsListener(executor, consumer); 238 mListeners.add(consumer); 239 } 240 241 private static final class RootViewCollector implements Consumer<List<View>> { 242 private final Object mLock = new Object(); 243 244 @GuardedBy("mLock") 245 private final List<List<View>> mElements = new ArrayList<>(); 246 247 private final CountDownLatch mCountDownLatch; 248 RootViewCollector(int expectedValueCount)249 RootViewCollector(int expectedValueCount) { 250 mCountDownLatch = new CountDownLatch(expectedValueCount); 251 } 252 253 @Override accept(@onNull List<View> views)254 public void accept(@NonNull List<View> views) { 255 synchronized (mLock) { 256 mElements.add(Objects.requireNonNull(views)); 257 mCountDownLatch.countDown(); 258 } 259 } 260 waitForElements()261 public boolean waitForElements() throws InterruptedException { 262 return mCountDownLatch.await(3, TimeUnit.SECONDS); 263 } 264 getElements()265 public List<List<View>> getElements() { 266 synchronized (mLock) { 267 return new ArrayList<>(mElements); 268 } 269 } 270 } 271 } 272