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

Commit ae6d6b49 authored by Tomasz Mikolajewski's avatar Tomasz Mikolajewski
Browse files

Add scaffolds for performance tests of DocumentsUI

Bug: 27370274
Change-Id: I14dea1b85cd84c8bb3c0eee27b2954108bfa4f8b
parent a0021cbb
Loading
Loading
Loading
Loading
+22 −0
Original line number Diff line number Diff line
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)

LOCAL_MODULE_TAGS := tests
#LOCAL_SDK_VERSION := current

LOCAL_SRC_FILES := $(call all-java-files-under, src) \
    $(call all-java-files-under, ../tests/src/com/android/documentsui/bots) \
    ../tests/src/com/android/documentsui/ActivityTest.java \
    ../tests/src/com/android/documentsui/DocumentsProviderHelper.java \
    ../tests/src/com/android/documentsui/StubProvider.java

LOCAL_JAVA_LIBRARIES := android.test.runner
LOCAL_STATIC_JAVA_LIBRARIES := android-support-v4 mockito-target ub-uiautomator

LOCAL_PACKAGE_NAME := DocumentsUIPerfTests
LOCAL_INSTRUMENTATION_FOR := DocumentsUI

LOCAL_CERTIFICATE := platform

include $(BUILD_PACKAGE)
+24 −0
Original line number Diff line number Diff line
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.android.documentsui.perftests">

    <application>
        <uses-library android:name="android.test.runner" />
        <provider
            android:name="com.android.documentsui.StressProvider"
            android:authorities="com.android.documentsui.stressprovider"
            android:exported="true"
            android:grantUriPermissions="true"
            android:permission="android.permission.MANAGE_DOCUMENTS"
            android:enabled="true">
            <intent-filter>
                <action android:name="android.content.action.DOCUMENTS_PROVIDER" />
            </intent-filter>
        </provider>
    </application>

    <instrumentation android:name="android.test.InstrumentationTestRunner"
        android:targetPackage="com.android.documentsui"
        android:label="Performance tests for DocumentsUI" />

</manifest>
+144 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2016 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 com.android.documentsui;

import static com.android.documentsui.StressProvider.DEFAULT_AUTHORITY;
import static com.android.documentsui.StressProvider.STRESS_ROOT_0_ID;
import static com.android.documentsui.StressProvider.STRESS_ROOT_1_ID;

import android.app.Activity;
import android.net.Uri;
import android.os.Bundle;
import android.os.RemoteException;
import android.test.suitebuilder.annotation.LargeTest;
import android.util.Log;
import android.view.KeyEvent;

import com.android.documentsui.model.RootInfo;
import com.android.documentsui.EventListener;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Random;
import java.util.concurrent.CountDownLatch;

@LargeTest
public class FilesActivityPerfTest extends ActivityTest<FilesActivity> {

    // Constants starting with KEY_ are used to report metrics to APCT.
    private static final String KEY_FILES_LISTED_PERFORMANCE_FIRST =
            "files-listed-performance-first";

    private static final String KEY_FILES_LISTED_PERFORMANCE_MEDIAN =
            "files-listed-performance-median";

    private static final String TESTED_URI =
            "content://com.android.documentsui.stressprovider/document/STRESS_ROOT_1_DOC";

    private static final int NUM_MEASUREMENTS = 10;

    public FilesActivityPerfTest() {
        super(FilesActivity.class);
    }

    @Override
    protected RootInfo getInitialRoot() {
        return rootDir0;
    }

    @Override
    protected String getTestingProviderAuthority() {
        return DEFAULT_AUTHORITY;
    }

    @Override
    protected void setupTestingRoots() throws RemoteException {
        rootDir0 = mDocsHelper.getRoot(STRESS_ROOT_0_ID);
        rootDir1 = mDocsHelper.getRoot(STRESS_ROOT_1_ID);
    }

    @Override
    public void initTestFiles() throws RemoteException {
        // Nothing to create, already done by StressProvider.
    }

    public void testFilesListedPerformance() throws Exception {
        final BaseActivity activity = getActivity();

        final List<Long> measurements = new ArrayList<Long>();
        CountDownLatch signal;
        EventListener listener;
        for (int i = 0; i < 10; i++) {
            signal = new CountDownLatch(1);
            listener = new EventListener() {
                @Override
                public void onDirectoryNavigated(Uri uri) {
                    if (uri != null && TESTED_URI.equals(uri.toString())) {
                        mStartTime = System.currentTimeMillis();
                    } else {
                        mStartTime = -1;
                    }
                }

                @Override
                public void onDirectoryLoaded(Uri uri) {
                    if (uri == null || !TESTED_URI.equals(uri.toString())) {
                        return;
                    }
                    assertTrue(mStartTime != -1);
                    getInstrumentation().waitForIdle(new Runnable() {
                        @Override
                        public void run() {
                            assertTrue(mStartTime != -1);
                            measurements.add(System.currentTimeMillis() - mStartTime);
                            signal.countDown();
                        }
                    });
                }

                private long mStartTime = -1;
            };

            try {
                activity.addEventListener(listener);
                bots.roots.openRoot(STRESS_ROOT_1_ID);
                signal.await();
            } finally {
                activity.removeEventListener(listener);
            }

            assertEquals(i, measurements.size());

            // Go back to the empty root.
            bots.roots.openRoot(STRESS_ROOT_0_ID);
        }

        assertEquals(NUM_MEASUREMENTS, measurements.size());

        final Bundle status = new Bundle();
        status.putDouble(KEY_FILES_LISTED_PERFORMANCE_FIRST, measurements.get(0));

        final Long[] rawMeasurements = measurements.toArray(new Long[NUM_MEASUREMENTS]);
        Arrays.sort(rawMeasurements);

        final long median = rawMeasurements[NUM_MEASUREMENTS / 2 - 1];
        status.putDouble(KEY_FILES_LISTED_PERFORMANCE_MEDIAN, median);

        getInstrumentation().sendStatus(Activity.RESULT_OK, status);
    }
}
+149 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2016 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 com.android.documentsui;

import android.content.Context;
import android.content.pm.ProviderInfo;
import android.database.Cursor;
import android.database.MatrixCursor.RowBuilder;
import android.database.MatrixCursor;
import android.os.CancellationSignal;
import android.os.FileUtils;
import android.os.ParcelFileDescriptor;
import android.provider.DocumentsContract.Document;
import android.provider.DocumentsContract.Root;
import android.provider.DocumentsContract;
import android.provider.DocumentsProvider;

import java.io.File;
import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;

/**
 * Provider with thousands of files for testing loading time of directories in DocumentsUI.
 * It doesn't support any file operations.
 */
public class StressProvider extends DocumentsProvider {

    public static final String DEFAULT_AUTHORITY = "com.android.documentsui.stressprovider";

    // Empty root.
    public static final String STRESS_ROOT_0_ID = "STRESS_ROOT_0";

    // Root with thousands of items.
    public static final String STRESS_ROOT_1_ID = "STRESS_ROOT_1";

    private static final String STRESS_ROOT_0_DOC_ID = "STRESS_ROOT_0_DOC";
    private static final String STRESS_ROOT_1_DOC_ID = "STRESS_ROOT_1_DOC";

    private static final String[] DEFAULT_ROOT_PROJECTION = new String[] {
            Root.COLUMN_ROOT_ID, Root.COLUMN_FLAGS, Root.COLUMN_TITLE, Root.COLUMN_DOCUMENT_ID,
            Root.COLUMN_AVAILABLE_BYTES
    };
    private static final String[] DEFAULT_DOCUMENT_PROJECTION = new String[] {
            Document.COLUMN_DOCUMENT_ID, Document.COLUMN_MIME_TYPE, Document.COLUMN_DISPLAY_NAME,
            Document.COLUMN_LAST_MODIFIED, Document.COLUMN_FLAGS, Document.COLUMN_SIZE,
    };

    private String mAuthority = DEFAULT_AUTHORITY;
    private ArrayList<String> mIds = new ArrayList<>();

    @Override
    public void attachInfo(Context context, ProviderInfo info) {
        mAuthority = info.authority;
        super.attachInfo(context, info);
    }

    @Override
    public boolean onCreate() {
        mIds = new ArrayList();
        for (int i = 0; i < 10000; i++) {
            mIds.add(createRandomId(i));
        }
        mIds.add(STRESS_ROOT_0_DOC_ID);
        mIds.add(STRESS_ROOT_1_DOC_ID);
        return true;
    }

    @Override
    public Cursor queryRoots(String[] projection) throws FileNotFoundException {
        final MatrixCursor result = new MatrixCursor(DEFAULT_ROOT_PROJECTION);
        includeRoot(result, STRESS_ROOT_0_ID, STRESS_ROOT_0_DOC_ID);
        includeRoot(result, STRESS_ROOT_1_ID, STRESS_ROOT_1_DOC_ID);
        return result;
    }

    @Override
    public Cursor queryDocument(String documentId, String[] projection)
            throws FileNotFoundException {
        final MatrixCursor result = new MatrixCursor(DEFAULT_DOCUMENT_PROJECTION);
        includeDocument(result, documentId);
        return result;
    }

    @Override
    public Cursor queryChildDocuments(String parentDocumentId, String[] projection, String sortOrder)
            throws FileNotFoundException {
        final MatrixCursor result = new MatrixCursor(DEFAULT_DOCUMENT_PROJECTION);
        if (STRESS_ROOT_1_DOC_ID.equals(parentDocumentId)) {
            for (String id : mIds) {
                includeDocument(result, id);
            }
        }
        return result;
    }

    @Override
    public ParcelFileDescriptor openDocument(String docId, String mode, CancellationSignal signal)
            throws FileNotFoundException {
        throw new UnsupportedOperationException();
    }

    private void includeRoot(MatrixCursor result, String rootId, String docId) {
        final RowBuilder row = result.newRow();
        row.add(Root.COLUMN_ROOT_ID, rootId);
        row.add(Root.COLUMN_FLAGS, 0);
        row.add(Root.COLUMN_TITLE, rootId);
        row.add(Root.COLUMN_DOCUMENT_ID, docId);
    }

    private void includeDocument(MatrixCursor result, String id) {
        final RowBuilder row = result.newRow();
        row.add(Document.COLUMN_DOCUMENT_ID, id);
        row.add(Document.COLUMN_DISPLAY_NAME, id);
        row.add(Document.COLUMN_SIZE, 0);
        row.add(Document.COLUMN_MIME_TYPE, DocumentsContract.Document.MIME_TYPE_DIR);
        row.add(Document.COLUMN_FLAGS, 0);
        row.add(Document.COLUMN_LAST_MODIFIED, null);
    }

    private static String getDocumentIdForFile(File file) {
        return file.getAbsolutePath();
    }

    private String createRandomId(int index) {
        final Random random = new Random(index);
        final StringBuilder builder = new StringBuilder();
        for (int i = 0; i < 20; i++) {
            builder.append((char) (random.nextInt(96) + 32));
        }
        builder.append(index);  // Append a number to guarantee uniqueness.
        return builder.toString();
    }
}
+26 −0
Original line number Diff line number Diff line
@@ -39,6 +39,7 @@ import android.provider.DocumentsContract.Root;
import android.support.annotation.CallSuper;
import android.support.annotation.LayoutRes;
import android.support.annotation.Nullable;
import android.support.annotation.VisibleForTesting;
import android.util.Log;
import android.view.KeyEvent;
import android.view.Menu;
@@ -67,6 +68,7 @@ public abstract class BaseActivity extends Activity
    SearchViewManager mSearchManager;
    DrawerController mDrawer;
    NavigationView mNavigator;
    List<EventListener> mEventListeners = new ArrayList<>();

    private final String mTag;

@@ -329,6 +331,8 @@ public abstract class BaseActivity extends Activity
    void openContainerDocument(DocumentInfo doc) {
        assert(doc.isContainer());

        notifyDirectoryNavigated(doc.derivedUri);

        mState.pushDocument(doc);
        // Show an opening animation only if pressing "back" would get us back to the
        // previous directory. Especially after opening a root document, pressing
@@ -594,6 +598,28 @@ public abstract class BaseActivity extends Activity
        return super.onKeyDown(keyCode, event);
    }

    @VisibleForTesting
    public void addEventListener(EventListener listener) {
        mEventListeners.add(listener);
    }

    @VisibleForTesting
    public void removeEventListener(EventListener listener) {
        mEventListeners.remove(listener);
    }

    public void notifyDirectoryLoaded(Uri uri) {
        for (EventListener listener : mEventListeners) {
            listener.onDirectoryLoaded(uri);
        }
    }

    void notifyDirectoryNavigated(Uri uri) {
        for (EventListener listener : mEventListeners) {
            listener.onDirectoryNavigated(uri);
        }
    }

    /**
     * Toggles focus between the navigation drawer and the directory listing. If the drawer isn't
     * locked, open/close it as appropriate.
Loading