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

Commit 1a067899 authored by Tomasz Mikolajewski's avatar Tomasz Mikolajewski Committed by Android (Google) Code Review
Browse files

Merge "Add scaffolds for performance tests of DocumentsUI" into nyc-dev

parents 5b30f0df ae6d6b49
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