• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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