• 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.server.wm;
18 
19 import static android.server.wm.ActivityManagerState.STATE_RESUMED;
20 import static android.server.wm.ActivityManagerState.STATE_STOPPED;
21 import static android.server.wm.app.Components.INPUT_METHOD_TEST_ACTIVITY;
22 import static android.server.wm.app.Components.InputMethodTestActivity.EXTRA_PRIVATE_IME_OPTIONS;
23 import static android.server.wm.app.Components.InputMethodTestActivity.EXTRA_TEST_CURSOR_ANCHOR_INFO;
24 import static android.server.wm.app.Components.TEST_ACTIVITY;
25 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
26 
27 import static androidx.test.InstrumentationRegistry.getInstrumentation;
28 
29 import static com.android.cts.mockime.ImeEventStreamTestUtils.editorMatcher;
30 import static com.android.cts.mockime.ImeEventStreamTestUtils.expectCommand;
31 import static com.android.cts.mockime.ImeEventStreamTestUtils.expectEvent;
32 
33 import static org.junit.Assert.assertEquals;
34 import static org.junit.Assert.assertNotNull;
35 import static org.junit.Assert.assertTrue;
36 import static org.junit.Assume.assumeTrue;
37 
38 import android.app.Activity;
39 import android.app.ActivityView;
40 import android.app.Instrumentation;
41 import android.content.ComponentName;
42 import android.content.Intent;
43 import android.content.pm.PackageManager;
44 import android.graphics.Matrix;
45 import android.graphics.Point;
46 import android.graphics.Rect;
47 import android.os.Bundle;
48 import android.os.SystemClock;
49 import android.platform.test.annotations.Presubmit;
50 import android.view.View;
51 import android.view.ViewGroup;
52 import android.view.inputmethod.CursorAnchorInfo;
53 import android.view.inputmethod.InputConnection;
54 
55 import androidx.test.annotation.UiThreadTest;
56 import androidx.test.rule.ActivityTestRule;
57 
58 import com.android.compatibility.common.util.SystemUtil;
59 import com.android.cts.mockime.ImeCommand;
60 import com.android.cts.mockime.ImeEvent;
61 import com.android.cts.mockime.ImeEventStream;
62 import com.android.cts.mockime.ImeSettings;
63 import com.android.cts.mockime.MockImeSession;
64 
65 import org.junit.After;
66 import org.junit.Before;
67 import org.junit.Rule;
68 import org.junit.Test;
69 
70 import java.util.concurrent.TimeUnit;
71 import java.util.concurrent.atomic.AtomicReference;
72 
73 /**
74  * Build/Install/Run:
75  *      atest CtsWindowManagerDeviceTestCases:ActivityViewTest
76  */
77 @Presubmit
78 public class ActivityViewTest extends ActivityManagerTestBase {
79     private static final long IME_EVENT_TIMEOUT = TimeUnit.SECONDS.toMillis(10);
80 
81     private Instrumentation mInstrumentation;
82     private ActivityView mActivityView;
83 
84     @Rule
85     public final ActivityTestRule<ActivityViewTestActivity> mActivityRule =
86             new ActivityTestRule<>(ActivityViewTestActivity.class, true /* initialTouchMode */,
87                 false /* launchActivity */);
88 
89     @Before
setUp()90     public void setUp() throws Exception {
91         super.setUp();
92         assumeTrue(supportsMultiDisplay());
93         mInstrumentation = getInstrumentation();
94         SystemUtil.runWithShellPermissionIdentity(() -> {
95             ActivityViewTestActivity activity = mActivityRule.launchActivity(null);
96             mActivityView = activity.getActivityView();
97         });
98         separateTestJournal();
99     }
100 
101     @After
tearDown()102     public void tearDown() throws Exception {
103         super.tearDown();
104         if (mActivityView != null) {
105             SystemUtil.runWithShellPermissionIdentity(() -> mActivityView.release());
106         }
107     }
108 
109     @Test
testStartActivity()110     public void testStartActivity() {
111         launchActivityInActivityView(TEST_ACTIVITY);
112         assertSingleLaunch(TEST_ACTIVITY);
113     }
114 
115     @UiThreadTest
116     @Test
testResizeActivityView()117     public void testResizeActivityView() {
118         final int width = 500;
119         final int height = 500;
120 
121         launchActivityInActivityView(TEST_ACTIVITY);
122         assertSingleLaunch(TEST_ACTIVITY);
123 
124         mActivityView.layout(0, 0, width, height);
125 
126         boolean boundsMatched = checkDisplaySize(TEST_ACTIVITY, width, height);
127         assertTrue("displayWidth and displayHeight must equal " + width + "x" + height,
128                 boundsMatched);
129     }
130 
checkDisplaySize(ComponentName activity, int requestedWidth, int requestedHeight)131     private boolean checkDisplaySize(ComponentName activity, int requestedWidth,
132             int requestedHeight) {
133         final int maxTries = 5;
134         final int retryIntervalMs = 1000;
135 
136         boolean boundsMatched = false;
137 
138         // Display size for the activity may not get updated right away. Retry in case.
139         for (int i = 0; i < maxTries; i++) {
140             mAmWmState.getWmState().computeState();
141             int displayId = mAmWmState.getAmState().getDisplayByActivity(activity);
142             WindowManagerState.Display display = mAmWmState.getWmState().getDisplay(displayId);
143             int avDisplayWidth = 0;
144             int avDisplayHeight = 0;
145             if (display != null) {
146                 Rect bounds = display.mFullConfiguration.windowConfiguration.getAppBounds();
147                 if (bounds != null) {
148                     avDisplayWidth = bounds.width();
149                     avDisplayHeight = bounds.height();
150                 }
151             }
152             boundsMatched = avDisplayWidth == requestedWidth && avDisplayHeight == requestedHeight;
153             if (boundsMatched) {
154                 return true;
155             }
156 
157             // wait and try again
158             SystemClock.sleep(retryIntervalMs);
159         }
160 
161         return boundsMatched;
162     }
163 
164     @Test
testAppStoppedWithVisibilityGone()165     public void testAppStoppedWithVisibilityGone() {
166         launchActivityInActivityView(TEST_ACTIVITY);
167         assertSingleLaunch(TEST_ACTIVITY);
168 
169         separateTestJournal();
170         mInstrumentation.runOnMainSync(() -> mActivityView.setVisibility(View.GONE));
171         mInstrumentation.waitForIdleSync();
172         mAmWmState.waitForActivityState(TEST_ACTIVITY, STATE_STOPPED);
173 
174         assertLifecycleCounts(TEST_ACTIVITY, 0, 0, 0, 1, 1, 0, CountSpec.DONT_CARE);
175     }
176 
177     @Test
testAppStoppedWithVisibilityInvisible()178     public void testAppStoppedWithVisibilityInvisible() {
179         launchActivityInActivityView(TEST_ACTIVITY);
180         assertSingleLaunch(TEST_ACTIVITY);
181 
182         separateTestJournal();
183         mInstrumentation.runOnMainSync(() -> mActivityView.setVisibility(View.INVISIBLE));
184         mInstrumentation.waitForIdleSync();
185         mAmWmState.waitForActivityState(TEST_ACTIVITY, STATE_STOPPED);
186 
187         assertLifecycleCounts(TEST_ACTIVITY, 0, 0, 0, 1, 1, 0, CountSpec.DONT_CARE);
188     }
189 
190     @Test
testAppStopAndStartWithVisibilityChange()191     public void testAppStopAndStartWithVisibilityChange() {
192         launchActivityInActivityView(TEST_ACTIVITY);
193         assertSingleLaunch(TEST_ACTIVITY);
194 
195         separateTestJournal();
196         mInstrumentation.runOnMainSync(() -> mActivityView.setVisibility(View.INVISIBLE));
197         mInstrumentation.waitForIdleSync();
198         mAmWmState.waitForActivityState(TEST_ACTIVITY, STATE_STOPPED);
199 
200         assertLifecycleCounts(TEST_ACTIVITY, 0, 0, 0, 1, 1, 0, CountSpec.DONT_CARE);
201 
202         separateTestJournal();
203         mInstrumentation.runOnMainSync(() -> mActivityView.setVisibility(View.VISIBLE));
204         mInstrumentation.waitForIdleSync();
205         mAmWmState.waitForActivityState(TEST_ACTIVITY, STATE_RESUMED);
206 
207         assertLifecycleCounts(TEST_ACTIVITY, 0, 1, 1, 0, 0, 0, CountSpec.DONT_CARE);
208     }
209 
210     @Test
testInputMethod()211     public void testInputMethod() throws Exception {
212         assumeTrue("MockIme cannot be used for devices that do not support installable IMEs",
213                 mInstrumentation.getContext().getPackageManager().hasSystemFeature(
214                         PackageManager.FEATURE_INPUT_METHODS));
215 
216         final String uniqueKey =
217                 ActivityViewTest.class.getSimpleName() + "/" + SystemClock.elapsedRealtimeNanos();
218 
219         final String privateImeOptions = uniqueKey + "/privateImeOptions";
220 
221         final CursorAnchorInfo mockResult = new CursorAnchorInfo.Builder()
222                 .setMatrix(new Matrix())
223                 .setInsertionMarkerLocation(3.0f, 4.0f, 5.0f, 6.0f, 0)
224                 .setSelectionRange(7, 8)
225                 .build();
226 
227         final Bundle extras = new Bundle();
228         extras.putString(EXTRA_PRIVATE_IME_OPTIONS, privateImeOptions);
229         extras.putParcelable(EXTRA_TEST_CURSOR_ANCHOR_INFO, mockResult);
230 
231         try (final MockImeSession imeSession = MockImeSession.create(mContext,
232                 mInstrumentation.getUiAutomation(), new ImeSettings.Builder())) {
233             final ImeEventStream stream = imeSession.openEventStream();
234             launchActivityInActivityView(INPUT_METHOD_TEST_ACTIVITY, extras);
235 
236             // IME's seeing uniqueStringValue means that a valid connection is successfully
237             // established from INPUT_METHOD_TEST_ACTIVITY the MockIme
238             expectEvent(stream, editorMatcher("onStartInput", privateImeOptions),
239                     IME_EVENT_TIMEOUT);
240 
241             // Make sure that InputConnection#requestCursorUpdates() works.
242             final ImeCommand cursorUpdatesCommand = imeSession.callRequestCursorUpdates(
243                     InputConnection.CURSOR_UPDATE_IMMEDIATE);
244             final ImeEvent cursorUpdatesEvent = expectCommand(
245                     stream, cursorUpdatesCommand, IME_EVENT_TIMEOUT);
246             assertTrue(cursorUpdatesEvent.getReturnBooleanValue());
247 
248             // Make sure that MockIme received the object sent above.
249             final CursorAnchorInfo receivedInfo = expectEvent(stream,
250                     event -> "onUpdateCursorAnchorInfo".equals(event.getEventName()),
251                     IME_EVENT_TIMEOUT).getArguments().getParcelable("cursorAnchorInfo");
252             assertNotNull(receivedInfo);
253 
254             // Get the location of ActivityView in the default display's screen coordinates.
255             final AtomicReference<Point> offsetRef = new AtomicReference<>();
256             mInstrumentation.runOnMainSync(() -> {
257                 final int[] xy = new int[2];
258                 mActivityView.getLocationOnScreen(xy);
259                 offsetRef.set(new Point(xy[0], xy[1]));
260             });
261             final Point offset = offsetRef.get();
262 
263             // Make sure that the received CursorAnchorInfo has an adjusted Matrix.
264             final Matrix expectedMatrix = mockResult.getMatrix();
265             expectedMatrix.postTranslate(offset.x, offset.y);
266             assertEquals(expectedMatrix, receivedInfo.getMatrix());
267         }
268     }
269 
launchActivityInActivityView(ComponentName activity)270     private void launchActivityInActivityView(ComponentName activity) {
271         launchActivityInActivityView(activity, new Bundle());
272     }
273 
launchActivityInActivityView(ComponentName activity, Bundle extras)274     private void launchActivityInActivityView(ComponentName activity, Bundle extras) {
275         Intent intent = new Intent();
276         intent.setComponent(activity);
277         intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
278         intent.putExtras(extras);
279         SystemUtil.runWithShellPermissionIdentity(() -> mActivityView.startActivity(intent));
280         mAmWmState.waitForValidState(activity);
281     }
282 
283     // Test activity
284     public static class ActivityViewTestActivity extends Activity {
285         private ActivityView mActivityView;
286 
287         @Override
onCreate(Bundle savedInstanceState)288         public void onCreate(Bundle savedInstanceState) {
289             super.onCreate(savedInstanceState);
290 
291             mActivityView = new ActivityView(this);
292             setContentView(mActivityView);
293 
294             ViewGroup.LayoutParams layoutParams = mActivityView.getLayoutParams();
295             layoutParams.width = MATCH_PARENT;
296             layoutParams.height = MATCH_PARENT;
297             mActivityView.requestLayout();
298         }
299 
getActivityView()300         ActivityView getActivityView() {
301             return mActivityView;
302         }
303     }
304 }
305