blob: 5dfa8749141265bab7c74ebf85f4a2ec8474b3d5 [file] [log] [blame]
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License
*/
package android.server.wm;
import static android.server.wm.WindowManagerState.STATE_RESUMED;
import static android.server.wm.WindowManagerState.STATE_STOPPED;
import static android.server.wm.app.Components.INPUT_METHOD_TEST_ACTIVITY;
import static android.server.wm.app.Components.InputMethodTestActivity.EXTRA_PRIVATE_IME_OPTIONS;
import static android.server.wm.app.Components.InputMethodTestActivity.EXTRA_TEST_CURSOR_ANCHOR_INFO;
import static android.server.wm.app.Components.TEST_ACTIVITY;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
import static com.android.cts.mockime.ImeEventStreamTestUtils.editorMatcher;
import static com.android.cts.mockime.ImeEventStreamTestUtils.expectCommand;
import static com.android.cts.mockime.ImeEventStreamTestUtils.expectEvent;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeTrue;
import android.app.Activity;
import android.app.ActivityView;
import android.app.Instrumentation;
import android.content.ComponentName;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.Matrix;
import android.graphics.Point;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.SystemClock;
import android.platform.test.annotations.Presubmit;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.CursorAnchorInfo;
import android.view.inputmethod.InputConnection;
import androidx.test.annotation.UiThreadTest;
import androidx.test.rule.ActivityTestRule;
import com.android.compatibility.common.util.SystemUtil;
import com.android.cts.mockime.ImeCommand;
import com.android.cts.mockime.ImeEvent;
import com.android.cts.mockime.ImeEventStream;
import com.android.cts.mockime.MockImeSession;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
/**
* Build/Install/Run:
* atest CtsWindowManagerDeviceTestCases:ActivityViewTest
*/
@Presubmit
@android.server.wm.annotation.Group3
public class ActivityViewTest extends ActivityManagerTestBase {
private static final long IME_EVENT_TIMEOUT = TimeUnit.SECONDS.toMillis(10);
private Instrumentation mInstrumentation;
private ActivityView mActivityView;
@Rule
public final ActivityTestRule<ActivityViewTestActivity> mActivityRule =
new ActivityTestRule<>(ActivityViewTestActivity.class, true /* initialTouchMode */,
false /* launchActivity */);
@Before
public void setUp() throws Exception {
super.setUp();
assumeTrue(supportsMultiDisplay());
mInstrumentation = getInstrumentation();
SystemUtil.runWithShellPermissionIdentity(() -> {
ActivityViewTestActivity activity = mActivityRule.launchActivity(null);
mActivityView = activity.getActivityView();
});
separateTestJournal();
}
@After
public void tearDown() throws Throwable {
if (mActivityView != null) {
// Detach ActivityView before releasing to avoid accessing removed display.
mActivityRule.runOnUiThread(
() -> ((ViewGroup) mActivityView.getParent()).removeView(mActivityView));
SystemUtil.runWithShellPermissionIdentity(() -> mActivityView.release());
}
}
@Test
public void testStartActivity() {
launchActivityInActivityView(TEST_ACTIVITY);
assertSingleLaunch(TEST_ACTIVITY);
}
@UiThreadTest
@Test
public void testResizeActivityView() {
final int width = 500;
final int height = 500;
launchActivityInActivityView(TEST_ACTIVITY);
assertSingleLaunch(TEST_ACTIVITY);
mActivityView.layout(0, 0, width, height);
boolean boundsMatched = checkDisplaySize(TEST_ACTIVITY, width, height);
assertTrue("displayWidth and displayHeight must equal " + width + "x" + height,
boundsMatched);
}
/** @return {@code true} if the display size for the activity matches the given size. */
private boolean checkDisplaySize(ComponentName activity, int requestedWidth,
int requestedHeight) {
// Display size for the activity may not get updated right away. Retry in case.
return Condition.waitFor("display size=" + requestedWidth + "x" + requestedHeight, () -> {
final WindowManagerState wmState = mWmState;
wmState.computeState();
final int displayId = mWmState.getDisplayByActivity(activity);
final WindowManagerState.DisplayContent display = wmState.getDisplay(displayId);
int avDisplayWidth = 0;
int avDisplayHeight = 0;
if (display != null) {
Rect bounds = display.mFullConfiguration.windowConfiguration.getAppBounds();
if (bounds != null) {
avDisplayWidth = bounds.width();
avDisplayHeight = bounds.height();
}
}
return avDisplayWidth == requestedWidth && avDisplayHeight == requestedHeight;
});
}
@Test
public void testAppStoppedWithVisibilityGone() {
launchActivityInActivityView(TEST_ACTIVITY);
assertSingleLaunch(TEST_ACTIVITY);
separateTestJournal();
mInstrumentation.runOnMainSync(() -> mActivityView.setVisibility(View.GONE));
mInstrumentation.waitForIdleSync();
mWmState.waitForActivityState(TEST_ACTIVITY, STATE_STOPPED);
assertLifecycleCounts(TEST_ACTIVITY, 0, 0, 0, 1, 1, 0, CountSpec.DONT_CARE);
}
@Test
public void testAppStoppedWithVisibilityInvisible() {
launchActivityInActivityView(TEST_ACTIVITY);
assertSingleLaunch(TEST_ACTIVITY);
separateTestJournal();
mInstrumentation.runOnMainSync(() -> mActivityView.setVisibility(View.INVISIBLE));
mInstrumentation.waitForIdleSync();
mWmState.waitForActivityState(TEST_ACTIVITY, STATE_STOPPED);
assertLifecycleCounts(TEST_ACTIVITY, 0, 0, 0, 1, 1, 0, CountSpec.DONT_CARE);
}
@Test
public void testAppStopAndStartWithVisibilityChange() {
launchActivityInActivityView(TEST_ACTIVITY);
assertSingleLaunch(TEST_ACTIVITY);
separateTestJournal();
mInstrumentation.runOnMainSync(() -> mActivityView.setVisibility(View.INVISIBLE));
mInstrumentation.waitForIdleSync();
mWmState.waitForActivityState(TEST_ACTIVITY, STATE_STOPPED);
assertLifecycleCounts(TEST_ACTIVITY, 0, 0, 0, 1, 1, 0, CountSpec.DONT_CARE);
separateTestJournal();
mInstrumentation.runOnMainSync(() -> mActivityView.setVisibility(View.VISIBLE));
mInstrumentation.waitForIdleSync();
mWmState.waitForActivityState(TEST_ACTIVITY, STATE_RESUMED);
assertLifecycleCounts(TEST_ACTIVITY, 0, 1, 1, 0, 0, 0, CountSpec.DONT_CARE);
}
@Test
public void testInputMethod() throws Exception {
assumeTrue("MockIme cannot be used for devices that do not support installable IMEs",
mInstrumentation.getContext().getPackageManager().hasSystemFeature(
PackageManager.FEATURE_INPUT_METHODS));
final String uniqueKey =
ActivityViewTest.class.getSimpleName() + "/" + SystemClock.elapsedRealtimeNanos();
final String privateImeOptions = uniqueKey + "/privateImeOptions";
final CursorAnchorInfo mockResult = new CursorAnchorInfo.Builder()
.setMatrix(new Matrix())
.setInsertionMarkerLocation(3.0f, 4.0f, 5.0f, 6.0f, 0)
.setSelectionRange(7, 8)
.build();
final Bundle extras = new Bundle();
extras.putString(EXTRA_PRIVATE_IME_OPTIONS, privateImeOptions);
extras.putParcelable(EXTRA_TEST_CURSOR_ANCHOR_INFO, mockResult);
final MockImeSession imeSession = MockImeHelper.createManagedMockImeSession(this);
final ImeEventStream stream = imeSession.openEventStream();
launchActivityInActivityView(INPUT_METHOD_TEST_ACTIVITY, extras);
tapOnDisplayCenter(mActivityView.getVirtualDisplayId());
// IME's seeing uniqueStringValue means that a valid connection is successfully
// established from INPUT_METHOD_TEST_ACTIVITY the MockIme.
expectEvent(stream, editorMatcher("onStartInput", privateImeOptions), IME_EVENT_TIMEOUT);
// Make sure that InputConnection#requestCursorUpdates() works.
final ImeCommand cursorUpdatesCommand = imeSession.callRequestCursorUpdates(
InputConnection.CURSOR_UPDATE_IMMEDIATE);
final ImeEvent cursorUpdatesEvent = expectCommand(
stream, cursorUpdatesCommand, IME_EVENT_TIMEOUT);
assertTrue(cursorUpdatesEvent.getReturnBooleanValue());
// Make sure that MockIme received the object sent above.
final CursorAnchorInfo receivedInfo = expectEvent(stream,
event -> "onUpdateCursorAnchorInfo".equals(event.getEventName()),
IME_EVENT_TIMEOUT).getArguments().getParcelable("cursorAnchorInfo");
assertNotNull(receivedInfo);
// Get the location of ActivityView in the default display's screen coordinates.
final AtomicReference<Point> offsetRef = new AtomicReference<>();
mInstrumentation.runOnMainSync(() -> {
final int[] xy = new int[2];
mActivityView.getLocationOnScreen(xy);
offsetRef.set(new Point(xy[0], xy[1]));
});
final Point offset = offsetRef.get();
// Make sure that the received CursorAnchorInfo has an adjusted Matrix.
final Matrix expectedMatrix = mockResult.getMatrix();
expectedMatrix.postTranslate(offset.x, offset.y);
assertEquals(expectedMatrix, receivedInfo.getMatrix());
}
private void launchActivityInActivityView(ComponentName activity) {
launchActivityInActivityView(activity, new Bundle());
}
private void launchActivityInActivityView(ComponentName activity, Bundle extras) {
Intent intent = new Intent();
intent.setComponent(activity);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
intent.putExtras(extras);
SystemUtil.runWithShellPermissionIdentity(() -> mActivityView.startActivity(intent));
mWmState.waitForValidState(activity);
}
// Test activity
public static class ActivityViewTestActivity extends Activity {
private ActivityView mActivityView;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mActivityView = new ActivityView(this);
setContentView(mActivityView);
ViewGroup.LayoutParams layoutParams = mActivityView.getLayoutParams();
layoutParams.width = MATCH_PARENT;
layoutParams.height = MATCH_PARENT;
mActivityView.requestLayout();
}
ActivityView getActivityView() {
return mActivityView;
}
}
}