Home | History | Annotate | Download | only in wm
      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
     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
    102     public void tearDown() throws Exception {
    103         super.tearDown();
    104         if (mActivityView != null) {
    105             SystemUtil.runWithShellPermissionIdentity(() -> mActivityView.release());
    106         }
    107     }
    108 
    109     @Test
    110     public void testStartActivity() {
    111         launchActivityInActivityView(TEST_ACTIVITY);
    112         assertSingleLaunch(TEST_ACTIVITY);
    113     }
    114 
    115     @UiThreadTest
    116     @Test
    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 
    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
    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
    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
    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
    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 
    270     private void launchActivityInActivityView(ComponentName activity) {
    271         launchActivityInActivityView(activity, new Bundle());
    272     }
    273 
    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
    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 
    300         ActivityView getActivityView() {
    301             return mActivityView;
    302         }
    303     }
    304 }
    305