Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 5ecdab57 authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Initial unit tests for AssistStructure"

parents 5fc7e233 ce47000f
Loading
Loading
Loading
Loading
+2 −2
Original line number Diff line number Diff line
@@ -2173,7 +2173,7 @@ public class AssistStructure implements Parcelable {
                    + ", hints=" + Arrays.toString(node.getAutofillHints())
                    + ", value=" + node.getAutofillValue()
                    + ", sanitized=" + node.isSanitized()
                    + ", importantFor=" + node.getImportantForAutofill());
                    + ", important=" + node.getImportantForAutofill());
        }

        final int NCHILDREN = node.getChildCount();
+8 −0
Original line number Diff line number Diff line
@@ -1401,6 +1401,14 @@
        <service android:name="android.content.CrossUserContentService"
                android:exported="true" />

        <activity android:name="android.app.assist.EmptyLayoutActivity"
                  android:label="My Title">
           <intent-filter>
               <action android:name="android.intent.action.MAIN" />
               <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
           </intent-filter>
       </activity>

    </application>

    <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+26 −0
Original line number Diff line number Diff line
<?xml version="1.0" encoding="utf-8"?>
<!--
 * Copyright (C) 2018 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.
-->

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/parent"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:focusable="true"
    android:focusableInTouchMode="true"
    android:orientation="vertical">
</LinearLayout>
+269 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2018 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.app.assist;

import static android.view.View.IMPORTANT_FOR_AUTOFILL_AUTO;

import static com.google.common.truth.Truth.assertThat;

import android.app.assist.AssistStructure.ViewNode;
import android.content.ComponentName;
import android.content.Context;
import android.os.Parcel;
import android.os.SystemClock;
import android.support.test.InstrumentationRegistry;
import android.support.test.rule.ActivityTestRule;
import android.support.test.runner.AndroidJUnit4;
import android.util.Log;
import android.widget.EditText;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.TextView;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;

/**
 * Unit test for {@link AssistStructure}.
 *
 * <p>To run it: {@code atest app.assist.AssistStructureTest}
 *
 * <p>TODO: right now this test is focused in the parcelization of a big object, due to an
 * upcoming refactoring on the Autofill parcelization that does not use
 * {@link AssistStructure#ensureData()} to load the structure (which in turn requires calls from
 * system server to the app). Ideally it should be emprove to:
 *
 * <ol>
 *    <li>Add tests for Assist (to make sure autofill properties are not parcelized).
 *    <li>Assert all properties and make sure just the relevant properties (for Autofill or Assist)
 *    are parcelized.
 *    <li>Add tests for Autofill requests with the {@code FLAG_MANUAL_REQUEST} flag.
 * </ol>
 */
@RunWith(AndroidJUnit4.class)
public class AssistStructureTest {

    private static final String TAG = "AssistStructureTest";

    private static final boolean FOR_AUTOFILL = true;
    private static final int NO_FLAGS = 0;

    private static final int BIG_VIEW_SIZE = 10_000_000;
    private static final char BIG_VIEW_CHAR = '6';
    private static final String BIG_STRING = repeat(BIG_VIEW_CHAR, BIG_VIEW_SIZE);
    // Cannot be much big because it could hang test due to blocking GC
    private static final int NUMBER_SMALL_VIEWS = 10_000;

    private EmptyLayoutActivity mActivity;

    private final ActivityTestRule<EmptyLayoutActivity> mActivityTestRule =
            new ActivityTestRule<>(EmptyLayoutActivity.class);

    private final Context mContext = InstrumentationRegistry.getTargetContext();

    @Before
    public void setup() {
        mActivity = mActivityTestRule.launchActivity(null);
    }

    @Test
    public void testParcelizationForAutofill_manySmallViews() {
        Log.d(TAG, "Adding " + NUMBER_SMALL_VIEWS + " small views");

        for (int i = 1; i <= NUMBER_SMALL_VIEWS; i++) {
            mActivity.addView(newSmallView());
        }

        waitUntilViewsAreLaidOff();

        AssistStructure structure = new AssistStructure(mActivity, FOR_AUTOFILL, NO_FLAGS);

        // Check properties on "original" structure
        assertStructureWithManySmallViews(structure);

        // Check properties on "cloned" structure
        AssistStructure clone = cloneThroughParcel(structure);
        assertStructureWithManySmallViews(clone);
    }

    private void assertStructureWithManySmallViews(AssistStructure structure) {
        int i = 0;
        try {
            assertPackageName(structure);

            assertThat(structure.getWindowNodeCount()).isEqualTo(1);

            ViewNode rootView = structure.getWindowNodeAt(0).getRootViewNode();
            assertThat(rootView.getClassName()).isEqualTo(FrameLayout.class.getName());
            assertThat(rootView.getChildCount()).isEqualTo(2); // title and parent
            assertThat(rootView.getAutofillId()).isNotNull();
            assertThat(rootView.getImportantForAutofill()).isEqualTo(IMPORTANT_FOR_AUTOFILL_AUTO);

            // Title
            ViewNode title = rootView.getChildAt(0);
            assertThat(title.getClassName()).isEqualTo(TextView.class.getName());
            assertThat(title.getChildCount()).isEqualTo(0);
            assertThat(title.getText().toString()).isEqualTo("My Title");
            assertThat(title.getAutofillId()).isNotNull();

            // Parent
            ViewNode parent = rootView.getChildAt(1);
            assertThat(parent.getClassName()).isEqualTo(LinearLayout.class.getName());
            assertThat(parent.getChildCount()).isEqualTo(NUMBER_SMALL_VIEWS);
            assertThat(parent.getIdEntry()).isEqualTo("parent");
            assertThat(parent.getAutofillId()).isNotNull();

            // Children
            for (i = 0; i < NUMBER_SMALL_VIEWS; i++) {
                ViewNode smallView = parent.getChildAt(i);
                assertSmallView(smallView);
            }
        } catch (RuntimeException | Error e) {
            Log.e(TAG, "dumping structure because of error at index #" + i + ": " + e);
            structure.dump(true);
            throw e;
        }
    }

    @Test
    public void testParcelizationForAutofill_oneBigView() {
        Log.d(TAG, "Adding view with " + BIG_VIEW_SIZE + " chars");

        mActivity.addView(newBigView());
        waitUntilViewsAreLaidOff();

        AssistStructure structure = new AssistStructure(mActivity, FOR_AUTOFILL, NO_FLAGS);

        // Check properties on "original" structure
        assertStructureWithOneBigView(structure);

        // Check properties on "cloned" structure
        AssistStructure clone = cloneThroughParcel(structure);
        assertStructureWithOneBigView(clone);
    }

    private void assertStructureWithOneBigView(AssistStructure structure) {
        try {
            assertPackageName(structure);

            assertThat(structure.getWindowNodeCount()).isEqualTo(1);

            ViewNode rootView = structure.getWindowNodeAt(0).getRootViewNode();
            assertThat(rootView.getClassName()).isEqualTo(FrameLayout.class.getName());
            assertThat(rootView.getChildCount()).isEqualTo(2); // title and parent
            assertThat(rootView.getAutofillId()).isNotNull();
            assertThat(rootView.getImportantForAutofill()).isEqualTo(IMPORTANT_FOR_AUTOFILL_AUTO);

            // Title
            ViewNode title = rootView.getChildAt(0);
            assertThat(title.getClassName()).isEqualTo(TextView.class.getName());
            assertThat(title.getChildCount()).isEqualTo(0);
            assertThat(title.getText().toString()).isEqualTo("My Title");
            assertThat(title.getAutofillId()).isNotNull();

            // Parent
            ViewNode parent = rootView.getChildAt(1);
            assertThat(parent.getClassName()).isEqualTo(LinearLayout.class.getName());
            assertThat(parent.getChildCount()).isEqualTo(1);
            assertThat(parent.getIdEntry()).isEqualTo("parent");
            assertThat(parent.getAutofillId()).isNotNull();

            // Children
            ViewNode bigView = parent.getChildAt(0);
            assertBigView(bigView);
        } catch (RuntimeException | Error e) {
            Log.e(TAG, "dumping structure because of error: " + e);
            structure.dump(true);
            throw e;
        }
    }

    private EditText newSmallView() {
        EditText view = new EditText(mContext);
        view.setText("I AM GROOT");
        return view;
    }

    private void assertSmallView(ViewNode view) {
        assertThat(view.getClassName()).isEqualTo(EditText.class.getName());
        assertThat(view.getChildCount()).isEqualTo(0);
        assertThat(view.getIdEntry()).isNull();
        assertThat(view.getAutofillId()).isNotNull();
        assertThat(view.getText().toString()).isEqualTo("I AM GROOT");
    }

    private EditText newBigView() {
        EditText view = new EditText(mContext);
        view.setText("Big Hint in Little View");
        view.setAutofillHints(BIG_STRING);
        return view;
    }

    private void assertBigView(ViewNode view) {
        assertThat(view.getClassName()).isEqualTo(EditText.class.getName());
        assertThat(view.getChildCount()).isEqualTo(0);
        assertThat(view.getIdEntry()).isNull();
        assertThat(view.getAutofillId()).isNotNull();
        assertThat(view.getText().toString()).isEqualTo("Big Hint in Little View");

        String[] hints = view.getAutofillHints();
        assertThat(hints.length).isEqualTo(1);
        String hint = hints[0];
        // Cannot assert the whole string because it takes too long and crashes the test by ANR
        assertThat(hint.length()).isEqualTo(BIG_VIEW_SIZE);
        assertThat(hint.charAt(0)).isEqualTo(BIG_VIEW_CHAR);
        assertThat(hint.charAt(BIG_VIEW_SIZE - 1)).isEqualTo(BIG_VIEW_CHAR);
    }

    private void assertPackageName(AssistStructure structure) {
        assertThat(structure.getActivityComponent()).isEqualTo(
                new ComponentName("com.android.frameworks.coretests",
                        "android.app.assist.EmptyLayoutActivity"));
    }

    private AssistStructure cloneThroughParcel(AssistStructure structure) {
        Parcel parcel = Parcel.obtain();

        try {
            // Write to parcel
            parcel.setDataPosition(0); // Sanity / paranoid check
            structure.writeToParcel(parcel, NO_FLAGS);

            // Read from parcel
            parcel.setDataPosition(0);
            AssistStructure clone = AssistStructure.CREATOR.createFromParcel(parcel);
            assertThat(clone).isNotNull();
            return clone;
        } finally {
            parcel.recycle();
        }
    }

    private void waitUntilViewsAreLaidOff() {
        // TODO: use a more robust mechanism than just sleeping
        SystemClock.sleep(3000);
    }

    // TODO: use some common helper
    private static String repeat(char c, int size) {
        StringBuilder builder = new StringBuilder(size);
        for (int i = 1; i <= size; i++) {
            builder.append(c);
        }
        return builder.toString();
    }
}
+44 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2018 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.app.assist;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.WindowManager.LayoutParams;
import android.widget.LinearLayout;

import com.android.frameworks.coretests.R;

public class EmptyLayoutActivity extends Activity {

    private LinearLayout mParent;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.empty_layout);

        mParent = findViewById(R.id.parent);
    }

    public void addView(View view) {
        view.setLayoutParams(new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT,
                LayoutParams.WRAP_CONTENT));
        runOnUiThread(() -> mParent.addView(view));
    }
}