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